// A tools bundle as a process (novox/hq ADR 0187): the stdio loop answers initialize, tools/list and // tools/call the way the node's runtime asks, and a tool that throws is a tool error, not a hang-up. import assert from "node:assert/strict"; import { test } from "node:test"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; test("a bundle on stdio lists its tools and answers a call, a seat's verb among them", async () => { const child = spawn(process.execPath, [fileURLToPath(new URL("./fixtures/stdio-bundle.mjs", import.meta.url))], { stdio: ["pipe", "pipe", "inherit"] }); const replies: any[] = []; let buffered = ""; child.stdout.on("data", (d) => { buffered += d.toString(); let at: number; while ((at = buffered.indexOf("\n")) >= 0) { replies.push(JSON.parse(buffered.slice(0, at))); buffered = buffered.slice(at + 1); } }); const ask = async (id: number, method: string, params?: unknown) => { child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n"); for (let i = 0; i < 100 && !replies.some((r) => r.id === id); i++) await new Promise((r) => setTimeout(r, 20)); return replies.find((r) => r.id === id); }; try { assert.equal((await ask(1, "initialize", { protocolVersion: "2025-03-26", capabilities: {} })).result.serverInfo.name, "lamp"); child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n"); const listed = (await ask(2, "tools/list")).result.tools; assert.deepEqual(listed.map((t: any) => t.name), ["brightness", "node-lamp.on", "fail"]); assert.deepEqual(listed[0].inputSchema, { type: "object", properties: { level: { type: "number", description: "0 to 10" } } }); const called = (await ask(3, "tools/call", { name: "brightness", arguments: { level: 7 } })).result; assert.deepEqual(JSON.parse(called.content[0].text), { level: 7 }); const failed = (await ask(4, "tools/call", { name: "fail", arguments: {} })).result; assert.equal(failed.isError, true); assert.equal(failed.content[0].text, "the bulb is gone"); assert.equal((await ask(5, "tools/call", { name: "nothing" })).error.code, -32602); } finally { child.stdin.end(); child.kill(); } });