A launched bundle serves under the module the runtime names (hq ADR 0193)
serveRegisteredOverStdio lists the registrations of the module named by MESH_SERVED_MODULE by their own names and every other registration as <seat>.<verb>. Before, the first registration was taken for the module's own, so a bundle registering its seat first listed the seat's verbs as its own tools. Contributors are given the process's environment, the bundle's own. 0.1.4.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@novox/mesh-sdk",
|
"name": "@novox/mesh-sdk",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"description": "The stable spine a Novox Mesh module's own code builds against.",
|
"description": "The stable spine a Novox Mesh module's own code builds against.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
+12
-5
@@ -89,13 +89,20 @@ export async function serveStdio(name: string, tools: ToolDefinition[]): Promise
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The word the node's runtime sets for a bundle it launches: the module it serves the bundle as
|
||||||
|
* (novox/hq ADR 0193). */
|
||||||
|
export const SERVED_AS = "MESH_SERVED_MODULE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serve everything registered with `registerModuleTools` over stdio: the one-line entry for a bundle
|
* Serve everything registered with `registerModuleTools` over stdio — the launcher every TypeScript
|
||||||
* that already registers the way the in-process shortcut expects, so one source serves both ways.
|
* bundle the runtime serves is started through (novox/hq ADR 0193). What the module the runtime
|
||||||
|
* serves this bundle as registered is listed by its own name; anything registered under another
|
||||||
|
* name — a seat this module implements — is listed `<seat>.<verb>`, whichever was registered first.
|
||||||
|
* Every contributor is given this process's environment, which is the bundle's own.
|
||||||
*/
|
*/
|
||||||
export async function serveRegisteredOverStdio(): Promise<void> {
|
export async function serveRegisteredOverStdio(as: string | undefined = process.env[SERVED_AS]): Promise<void> {
|
||||||
const groups = collectTools();
|
const groups = collectTools(process.env);
|
||||||
const name = groups[0]?.module ?? "bundle";
|
const name = as ?? groups[0]?.module ?? "bundle";
|
||||||
const tools: ToolDefinition[] = [];
|
const tools: ToolDefinition[] = [];
|
||||||
for (const g of groups) {
|
for (const g of groups) {
|
||||||
for (const t of g.tools) tools.push(g.module === name ? t : { ...t, name: `${g.module}.${t.name}` });
|
for (const t of g.tools) tools.push(g.module === name ? t : { ...t, name: `${g.module}.${t.name}` });
|
||||||
|
|||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
// A bundle that registers its seat's verbs before its own tools, as the packet filter does.
|
||||||
|
import { registerModuleTools } from "../../dist/tools/index.js";
|
||||||
|
import { serveRegisteredOverStdio } from "../../dist/stdio/index.js";
|
||||||
|
registerModuleTools("node-packet-filter", () => [{ name: "rules", description: "the ruleset", input: {}, run: async () => "r" }]);
|
||||||
|
registerModuleTools("nftables", (env) => [{ name: "firewall_rules", description: "the table", input: {}, run: async () => env.NFT_WORD ?? null }]);
|
||||||
|
await serveRegisteredOverStdio();
|
||||||
@@ -39,3 +39,32 @@ test("a bundle on stdio lists its tools and answers a call, a seat's verb among
|
|||||||
child.kill();
|
child.kill();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a bundle served as its module lists the seat's verbs as the seat's, whichever registered first (ADR 0193)", async () => {
|
||||||
|
const child = spawn(process.execPath, [fileURLToPath(new URL("./fixtures/stdio-seat-first.mjs", import.meta.url))],
|
||||||
|
{ stdio: ["pipe", "pipe", "inherit"], env: { ...process.env, MESH_SERVED_MODULE: "nftables", NFT_WORD: "given" } });
|
||||||
|
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, "nftables");
|
||||||
|
const listed = (await ask(2, "tools/list")).result.tools;
|
||||||
|
assert.deepEqual(listed.map((t: any) => t.name), ["node-packet-filter.rules", "firewall_rules"]);
|
||||||
|
const called = (await ask(3, "tools/call", { name: "firewall_rules", arguments: {} })).result;
|
||||||
|
assert.equal(called.content[0].text, JSON.stringify("given"));
|
||||||
|
} finally {
|
||||||
|
child.kill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user