Row 89, round 1. The broker's `tasks` read takes optional filter and page arguments and returns a short form. The harness adds `list_tasks` arguments (open tasks by default), a `get_task` tool, and compact JSON replies capped below the 50000-character threshold where Claude Code 2.1.296 saves a tool result to a file outside the workspace, which was the PM's fault in Jason's test. The bundle's tools line names the harness's own tools. Candidate manifest b8c5bb3a (agents/dewey/work/queue-89/), basea3f6e9e6, no drift on the 12 paths. Builder Dewey. Approved by Darkwing (comment 27940). Sage's gate onf04185fcplus the patch 2026-10-12T22:00:55Z-22:03:38Z: bus 108, harness 65, webui 86, cli 115, control-board 124, every scripts/test-*.sh green. Live at the next bus restart (broker) and role session launch (harness), both Mos's call (decision 109); Mos had the notice. Co-Authored-By: Claude Opus 5.5 <[email protected]>
50 lines
2.0 KiB
JavaScript
50 lines
2.0 KiB
JavaScript
// Pi extension for a managed session, loaded with `-e` by adapters/pi.
|
|
// Three jobs:
|
|
// - register the session's typed tools, which call the runner's tool socket;
|
|
// - gate every tool call with gate.mjs (S0 lines 1 and 2: a block or a
|
|
// throw stops the tool);
|
|
// - write the turn marker on agent_end, so the runner can tell a finished
|
|
// turn from a pi that exited 0 mid-turn (S0 line 4).
|
|
// Missing or unreadable configuration throws here, which fails pi's start
|
|
// (S0 line 3): nothing is defaulted.
|
|
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { decide } from "./gate.mjs";
|
|
import { callTool, replyText } from "./tools.mjs";
|
|
|
|
function need(name) {
|
|
const value = process.env[name];
|
|
if (typeof value !== "string" || !value) throw new Error(`mosaic extension: ${name} is not set`);
|
|
return value;
|
|
}
|
|
|
|
export default function (pi) {
|
|
const policy = JSON.parse(readFileSync(need("MOSAIC_POLICY_FILE"), "utf8"));
|
|
const tools = JSON.parse(readFileSync(need("MOSAIC_TOOLS_FILE"), "utf8"));
|
|
const socket = need("MOSAIC_TOOL_SOCKET");
|
|
const marker = need("MOSAIC_TURN_MARKER");
|
|
if (policy.harness !== "pi" || !Array.isArray(policy.tools) || !Array.isArray(policy.typed)) throw new Error("mosaic extension: policy file is not a pi policy");
|
|
|
|
pi.on("tool_call", async (event) => {
|
|
const verdict = decide(policy, event.toolName, event.input);
|
|
return verdict.allow ? undefined : { block: true, reason: verdict.reason };
|
|
});
|
|
pi.on("agent_end", async () => {
|
|
writeFileSync(marker, `${new Date().toISOString()}\n`);
|
|
});
|
|
|
|
for (const t of tools) {
|
|
pi.registerTool({
|
|
name: t.name,
|
|
label: t.name,
|
|
description: t.description,
|
|
parameters: t.parameters,
|
|
async execute(_toolCallId, params) {
|
|
// A refusal, or a reply over REPLY_CAP, throws, which pi reports to the model as a tool error.
|
|
const result = await callTool(socket, t.name, params ?? {});
|
|
return { content: [{ type: "text", text: replyText(result) }], details: {} };
|
|
},
|
|
});
|
|
}
|
|
}
|