A launched tool emits through its runtime, and calls run beside the stdio loop (hq ADR 0193)
A launched bundle holds no bus connection. The stdio loop binds a broker whose publish asks the runtime (mesh/publish) and resolves when the runtime has published; asking, answering and subscribing are refused by name. Tool calls run beside the loop, so a tool awaiting the runtime's answer to its emit does not deadlock the line that carries it. 0.1.5.
This commit is contained in:
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
// A launched bundle whose tool emits (novox/hq ADR 0193): the emit travels to the runtime over stdio.
|
||||
import { registerModuleTools } from "../../dist/tools/index.js";
|
||||
import { emit } from "../../dist/events/index.js";
|
||||
import { serveRegisteredOverStdio } from "../../dist/stdio/index.js";
|
||||
registerModuleTools("chime", () => [
|
||||
{ name: "ring", description: "rings and says so", input: {}, run: async () => { await emit("rang", { times: 1 }); return { rang: true }; } },
|
||||
]);
|
||||
await serveRegisteredOverStdio();
|
||||
@@ -68,3 +68,39 @@ test("a bundle served as its module lists the seat's verbs as the seat's, whiche
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
|
||||
test("a launched tool's emit is asked of the runtime, and the call answers once the runtime has published (ADR 0193)", async () => {
|
||||
const child = spawn(process.execPath, [fileURLToPath(new URL("./fixtures/stdio-emits.mjs", import.meta.url))],
|
||||
{ stdio: ["pipe", "pipe", "inherit"], env: { ...process.env, MESH_SERVED_MODULE: "chime", MESH_MODULE: "chime", MESH_NODE: "anchor" } });
|
||||
const seen: any[] = [];
|
||||
let buffered = "";
|
||||
child.stdout.on("data", (d) => {
|
||||
buffered += d.toString();
|
||||
let at: number;
|
||||
while ((at = buffered.indexOf("\n")) >= 0) {
|
||||
seen.push(JSON.parse(buffered.slice(0, at)));
|
||||
buffered = buffered.slice(at + 1);
|
||||
}
|
||||
});
|
||||
const until = async (match: (m: any) => boolean) => {
|
||||
for (let i = 0; i < 150 && !seen.some(match); i++) await new Promise((r) => setTimeout(r, 20));
|
||||
return seen.find(match);
|
||||
};
|
||||
const send = (m: unknown) => child.stdin.write(JSON.stringify(m) + "\n");
|
||||
try {
|
||||
send({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-03-26", capabilities: {} } });
|
||||
await until((m) => m.id === 1);
|
||||
send({ jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: "ring", arguments: {} } });
|
||||
const asked = await until((m) => m.method === "mesh/publish");
|
||||
assert.ok(asked, "the tool's emit was not asked of the runtime");
|
||||
assert.equal(asked.params.key, "rang");
|
||||
assert.deepEqual(asked.params.body, { times: 1 });
|
||||
assert.equal(asked.params.headers["x-source"], "chime");
|
||||
assert.equal(seen.find((m) => m.id === 2), undefined, "the call answered before its emit was published");
|
||||
send({ jsonrpc: "2.0", id: asked.id, result: {} });
|
||||
const answered = await until((m) => m.id === 2);
|
||||
assert.deepEqual(JSON.parse(answered.result.content[0].text), { rang: true });
|
||||
} finally {
|
||||
child.kill();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user