@tekmidian/pai 0.40.1 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/aibroker-client-BP74gGwD.mjs +215 -0
- package/dist/aibroker-client-BP74gGwD.mjs.map +1 -0
- package/dist/aibroker-client-C6m71p6O.mjs +3 -0
- package/dist/{auto-route-BwzmlW3W.mjs → auto-route-_5zzJz5q.mjs} +2 -2
- package/dist/{auto-route-BwzmlW3W.mjs.map → auto-route-_5zzJz5q.mjs.map} +1 -1
- package/dist/browser-mcp/index.d.mts +1 -0
- package/dist/browser-mcp/index.mjs +352 -0
- package/dist/browser-mcp/index.mjs.map +1 -0
- package/dist/{chain-DhVHVnmT.mjs → chain-DGXNoTwc.mjs} +93 -320
- package/dist/chain-DGXNoTwc.mjs.map +1 -0
- package/dist/cli/index.mjs +12 -10
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +12 -10
- package/dist/{clusters-DRHhfli9.mjs → clusters-j0ZuNYp9.mjs} +1 -1
- package/dist/{clusters-DRHhfli9.mjs.map → clusters-j0ZuNYp9.mjs.map} +1 -1
- package/dist/{config-D1G9IFpn.mjs → config-B64vFg14.mjs} +3 -50
- package/dist/config-B64vFg14.mjs.map +1 -0
- package/dist/{config-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
- package/dist/{config-Dl8lT4Lu.mjs → config-DgzR5sfb.mjs} +2 -2
- package/dist/{config-Dl8lT4Lu.mjs.map → config-DgzR5sfb.mjs.map} +1 -1
- package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache-dUnSZwaA.mjs} +4 -4
- package/dist/{context-handover-cache-C6_rs7JT.mjs.map → context-handover-cache-dUnSZwaA.mjs.map} +1 -1
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-CKBkhOsZ.mjs +23 -0
- package/dist/{daemon-CpdVUnl_.mjs → daemon-DsvP8KMy.mjs} +357 -27
- package/dist/daemon-DsvP8KMy.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +20 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/detector-C-7KRi42.mjs +5 -0
- package/dist/{detector-smpCm7ku.mjs → detector-C73Nxm94.mjs} +1 -1
- package/dist/{detector-smpCm7ku.mjs.map → detector-C73Nxm94.mjs.map} +1 -1
- package/dist/{factory-Bc5EyN54.mjs → factory-DEb9NmHJ.mjs} +7 -7
- package/dist/{factory-Bc5EyN54.mjs.map → factory-DEb9NmHJ.mjs.map} +1 -1
- package/dist/factory-Dbo8yNPi.mjs +3 -0
- package/dist/{fallback-CdXv-Np0.mjs → fallback-5UJO-RyX.mjs} +5 -4
- package/dist/{fallback-CdXv-Np0.mjs.map → fallback-5UJO-RyX.mjs.map} +1 -1
- package/dist/hooks/worker-status-line.mjs +9 -4
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +593 -0
- package/dist/hooks/worker-supervision.mjs.map +7 -0
- package/dist/{indexer-backend-rohVVKrA.mjs → indexer-backend-BPTzKFZU.mjs} +1 -1
- package/dist/{indexer-backend-rohVVKrA.mjs.map → indexer-backend-BPTzKFZU.mjs.map} +1 -1
- package/dist/{ipc-client-C7AVpsyv.mjs → ipc-client-BmypMNYk.mjs} +2 -2
- package/dist/{ipc-client-C7AVpsyv.mjs.map → ipc-client-BmypMNYk.mjs.map} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
- package/dist/{latent-ideas-iNp5s55R.mjs → latent-ideas-CtSca4qG.mjs} +2 -2
- package/dist/{latent-ideas-iNp5s55R.mjs.map → latent-ideas-CtSca4qG.mjs.map} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs → link-boost-Bucnz3n8.mjs} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs.map → link-boost-Bucnz3n8.mjs.map} +1 -1
- package/dist/{main-resolver-CM1IHbuu.mjs → main-resolver-BgIrC-D9.mjs} +4 -214
- package/dist/main-resolver-BgIrC-D9.mjs.map +1 -0
- package/dist/main-resolver-DXGuygFG.mjs +7 -0
- package/dist/{neighborhood-AGL7oXQ2.mjs → neighborhood-CM7Jgb51.mjs} +1 -1
- package/dist/{neighborhood-AGL7oXQ2.mjs.map → neighborhood-CM7Jgb51.mjs.map} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs → note-context-DCwE2A-M.mjs} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs.map → note-context-DCwE2A-M.mjs.map} +1 -1
- package/dist/{planner-Cm3g6fWH.mjs → planner-BLDJt3ZS.mjs} +6 -5
- package/dist/{planner-Cm3g6fWH.mjs.map → planner-BLDJt3ZS.mjs.map} +1 -1
- package/dist/{postgres-Dwxe290H.mjs → postgres-CtOzfoQU.mjs} +1 -1
- package/dist/{postgres-Dwxe290H.mjs.map → postgres-CtOzfoQU.mjs.map} +1 -1
- package/dist/{program-AR0qlYRg.mjs → program-CruRnLtc.mjs} +99 -84
- package/dist/program-CruRnLtc.mjs.map +1 -0
- package/dist/query-feedback-DGoVRaWD.mjs +3 -0
- package/dist/{query-feedback-DZfjI34w.mjs → query-feedback-Dqz-HC__.mjs} +1 -1
- package/dist/{query-feedback-DZfjI34w.mjs.map → query-feedback-Dqz-HC__.mjs.map} +1 -1
- package/dist/router-BTIGZR2_.mjs +3 -0
- package/dist/{router-DHracTUC.mjs → router-DD6D4j31.mjs} +1 -1
- package/dist/{router-DHracTUC.mjs.map → router-DD6D4j31.mjs.map} +1 -1
- package/dist/runtime-paths-rni52zHX.mjs +50 -0
- package/dist/runtime-paths-rni52zHX.mjs.map +1 -0
- package/dist/scope-NIwCGxRI.mjs +351 -0
- package/dist/scope-NIwCGxRI.mjs.map +1 -0
- package/dist/{server-BOuAOj9b.mjs → server-Cp7tNzNC.mjs} +19 -2
- package/dist/server-Cp7tNzNC.mjs.map +1 -0
- package/dist/skills/Worker/SKILL.md +13 -0
- package/dist/{sources-ju6AA7lL.mjs → sources-CS43CpuS.mjs} +1 -1
- package/dist/{sources-ju6AA7lL.mjs.map → sources-CS43CpuS.mjs.map} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs → sqlite-Cq3gJcq9.mjs} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs.map → sqlite-Cq3gJcq9.mjs.map} +1 -1
- package/dist/{state-ahFAbD8b.mjs → state-BTa10SOM.mjs} +1 -1
- package/dist/{state-ahFAbD8b.mjs.map → state-BTa10SOM.mjs.map} +1 -1
- package/dist/{state-DH_mp5Xe.mjs → state-RXepvbnO.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs → themes-B7h9YDiy.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs.map → themes-B7h9YDiy.mjs.map} +1 -1
- package/dist/{tools-COna9vZR.mjs → tools-BW3NLMPk.mjs} +1 -1
- package/dist/{tools-CMlHa9lZ.mjs → tools-LlzcV_eK.mjs} +14 -14
- package/dist/{tools-CMlHa9lZ.mjs.map → tools-LlzcV_eK.mjs.map} +1 -1
- package/dist/{trace-DTP6k4B_.mjs → trace-u4hT-pLD.mjs} +1 -1
- package/dist/{trace-DTP6k4B_.mjs.map → trace-u4hT-pLD.mjs.map} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs → vault-indexer-mata9S7b.mjs} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs.map → vault-indexer-mata9S7b.mjs.map} +1 -1
- package/dist/{work-queue-worker-B3nl8yyn.mjs → work-queue-worker-7Vkli4Yh.mjs} +4 -4
- package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-7Vkli4Yh.mjs.map} +1 -1
- package/dist/work-queue-worker-D1p2V-ae.mjs +14 -0
- package/dist/{zettelkasten-DDvKRm2V.mjs → zettelkasten-F0D_XfQ4.mjs} +3 -3
- package/dist/{zettelkasten-DDvKRm2V.mjs.map → zettelkasten-F0D_XfQ4.mjs.map} +1 -1
- package/docs/commands/README.md +2 -2
- package/docs/commands/mcp.md +4 -4
- package/docs/commands/worker.md +3 -0
- package/docs/worker.md +40 -0
- package/package.json +1 -1
- package/plugins/productivity/hooks/hooks.json +5 -0
- package/src/hooks/ts/user-prompt/worker-supervision.ts +123 -0
- package/statusline-command.sh +65 -73
- package/dist/chain-DhVHVnmT.mjs.map +0 -1
- package/dist/config-D1G9IFpn.mjs.map +0 -1
- package/dist/daemon-BSL9sXxm.mjs +0 -22
- package/dist/daemon-CpdVUnl_.mjs.map +0 -1
- package/dist/detector-BLVOCiJ_.mjs +0 -5
- package/dist/factory-C7ua_3ik.mjs +0 -3
- package/dist/main-resolver-CM1IHbuu.mjs.map +0 -1
- package/dist/main-resolver-E5WlilhM.mjs +0 -6
- package/dist/program-AR0qlYRg.mjs.map +0 -1
- package/dist/query-feedback-CGcEiGtY.mjs +0 -3
- package/dist/router-CAtyVQud.mjs +0 -3
- package/dist/server-BOuAOj9b.mjs.map +0 -1
- package/dist/work-queue-worker-BM1GcAAq.mjs +0 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["z.number","z.string","z.boolean"],"sources":["../../src/browser-mcp/bridge-client.ts","../../src/browser-mcp/tools.ts","../../src/browser-mcp/index.ts"],"sourcesContent":["/**\n * WebSocket client for the browser bridge.\n *\n * Talks to the native host's hand-rolled WS server (ws://127.0.0.1:8756) with\n * the global WebSocket — node >= 22, no external dependency. Requests carry an\n * id; replies are correlated by it. The socket is connected lazily per call\n * and reconnected on demand, so the server survives Chrome being closed.\n */\n\nexport const BRIDGE_PORT = Number(process.env.PAI_BROWSER_BRIDGE_PORT || 8756);\nexport const BRIDGE_URL = `ws://127.0.0.1:${BRIDGE_PORT}`;\n\nexport const NOT_CONNECTED_MESSAGE =\n \"Chrome bridge not connected — start Chrome (the PAI browser-bridge extension must be running) and try again.\";\n\n/** Minimal shape of the pieces of WebSocket this client uses. */\nexport interface WebSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n onopen: (() => void) | null;\n onclose: (() => void) | null;\n onerror: (() => void) | null;\n onmessage: ((ev: { data: string }) => void) | null;\n}\n\nexport type WebSocketFactory = (url: string) => WebSocketLike;\n\nexport const globalWebSocketFactory: WebSocketFactory = (url) => {\n const WS = (globalThis as { WebSocket?: new (url: string) => WebSocketLike }).WebSocket;\n if (!WS) {\n throw new Error(\n \"this node runtime has no global WebSocket — pai-browser-mcp needs node >= 22\"\n );\n }\n return new WS(url);\n};\n\nexport class NotConnectedError extends Error {\n constructor() {\n super(NOT_CONNECTED_MESSAGE);\n this.name = \"NotConnectedError\";\n }\n}\n\nexport interface BridgeReply {\n id: number;\n ok: boolean;\n result?: unknown;\n error?: string;\n}\n\nconst CONNECT_TIMEOUT_MS = 5_000;\nconst REQUEST_TIMEOUT_MS = 5_000;\nconst OPEN = 1;\n\nexport class BridgeClient {\n private socket: WebSocketLike | null = null;\n private connecting: Promise<WebSocketLike> | null = null;\n private nextId = 1;\n private readonly pending = new Map<\n number,\n { resolve: (r: BridgeReply) => void; reject: (e: Error) => void; timer: NodeJS.Timeout }\n >();\n\n constructor(\n private readonly url: string = BRIDGE_URL,\n private readonly socketFactory: WebSocketFactory = globalWebSocketFactory\n ) {}\n\n /** True when the underlying socket is open. */\n get connected(): boolean {\n return this.socket !== null && this.socket.readyState === OPEN;\n }\n\n private ensureSocket(): Promise<WebSocketLike> {\n if (this.socket && this.socket.readyState === OPEN) return Promise.resolve(this.socket);\n if (this.connecting) return this.connecting; // concurrent sends share one socket\n this.dropSocket();\n const attempt = new Promise<WebSocketLike>((resolve, reject) => {\n let socket: WebSocketLike;\n try {\n socket = this.socketFactory(this.url);\n } catch (e) {\n reject(e);\n return;\n }\n const timer = setTimeout(() => {\n reject(new NotConnectedError());\n socket.onopen = null;\n socket.onclose = null;\n socket.onerror = null;\n socket.onmessage = null;\n try {\n socket.close();\n } catch {\n /* already gone */\n }\n }, CONNECT_TIMEOUT_MS);\n socket.onopen = () => {\n clearTimeout(timer);\n this.socket = socket;\n resolve(socket);\n };\n socket.onclose = () => {\n clearTimeout(timer);\n this.dropSocket();\n reject(new NotConnectedError());\n };\n socket.onerror = () => {\n clearTimeout(timer);\n this.dropSocket();\n reject(new NotConnectedError());\n };\n socket.onmessage = (ev) => this.onMessage(ev);\n });\n this.connecting = attempt;\n const clear = () => {\n if (this.connecting === attempt) this.connecting = null;\n };\n attempt.then(clear, clear);\n return attempt;\n }\n\n private dropSocket(): void {\n if (this.socket) {\n this.socket.onopen = null;\n this.socket.onclose = null;\n this.socket.onerror = null;\n this.socket.onmessage = null;\n try {\n this.socket.close();\n } catch {\n /* already gone */\n }\n this.socket = null;\n }\n // Fail everything still waiting — the peer is gone.\n for (const [, p] of this.pending) {\n clearTimeout(p.timer);\n p.reject(new NotConnectedError());\n }\n this.pending.clear();\n }\n\n private onMessage(ev: { data: string }): void {\n let reply: BridgeReply;\n try {\n reply = JSON.parse(ev.data) as BridgeReply;\n } catch {\n return; // bridge noise (keepalive pings echo nothing back); ignore\n }\n const p = reply?.id !== undefined ? this.pending.get(reply.id) : undefined;\n if (!p) return;\n this.pending.delete(reply.id);\n clearTimeout(p.timer);\n p.resolve(reply);\n }\n\n /**\n * Sends one command and awaits its correlated reply. Connection problems\n * and timeouts both surface as NotConnectedError with the user-facing text.\n */\n async send(command: string, params: Record<string, unknown> = {}): Promise<unknown> {\n await this.ensureSocket();\n const id = this.nextId++;\n const frame = JSON.stringify({ id, command, ...params });\n return new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new NotConnectedError());\n }, REQUEST_TIMEOUT_MS);\n this.pending.set(id, {\n resolve: (reply) => (reply.ok ? resolve(reply.result) : reject(new Error(reply.error ?? \"bridge error\"))),\n reject,\n timer,\n });\n try {\n this.socket!.send(frame);\n } catch (e) {\n this.pending.delete(id);\n clearTimeout(timer);\n reject(new NotConnectedError());\n }\n });\n }\n\n close(): void {\n this.dropSocket();\n }\n}\n","/**\n * pai-browser MCP tool table.\n *\n * One definition per tool: MCP-facing name/schema, the wire command it maps\n * to on the browser bridge, param translation, and result formatting. Kept as\n * a data table so tests can drive every mapping without a transport, and so\n * registerBrowserTools stays a five-line loop.\n */\n\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { NotConnectedError } from \"./bridge-client.js\";\n\nexport interface BridgeLike {\n send(command: string, params?: Record<string, unknown>): Promise<unknown>;\n}\n\nexport interface BrowserToolDef {\n name: string;\n description: string;\n /** zod raw shape for server.tool() */\n shape: z.ZodRawShape;\n /** wire command the extension handles */\n command: string;\n /** MCP args → wire params */\n toParams: (args: Record<string, unknown>) => Record<string, unknown>;\n /** wire result → text shown to the model */\n format?: (result: unknown) => string;\n}\n\nconst tabSchema = z.number().int().describe(\"Tab id, as returned by tabs_list.\");\n\nfunction asRecord(v: unknown): Record<string, unknown> {\n return (v ?? {}) as Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// Result formatters\n// ---------------------------------------------------------------------------\n\nfunction formatTabs(result: unknown): string {\n const tabs = (Array.isArray(result) ? result : asRecord(result).tabs) as Array<\n Record<string, unknown>\n >;\n if (!Array.isArray(tabs) || tabs.length === 0) return \"(no open tabs)\";\n return tabs\n .map(\n (t) =>\n `${t.id}${t.active ? \" *\" : \" \"} ${String(t.title ?? \"\")}\\n ${String(t.url ?? \"\")}`\n )\n .join(\"\\n\");\n}\n\nfunction formatSnapshot(result: unknown): string {\n return String(asRecord(result).yaml ?? \"\");\n}\n\nfunction formatLogs(result: unknown): string {\n const entries = asRecord(result).entries as Array<Record<string, unknown>>;\n if (!Array.isArray(entries) || entries.length === 0) return \"(no console output captured)\";\n return entries\n .map((e) => {\n const kind = e.source === \"console\" ? String(e.type ?? \"log\") : String(e.level ?? \"log\");\n return `[${kind}] ${String(e.text ?? \"\")}`;\n })\n .join(\"\\n\");\n}\n\nfunction formatEval(result: unknown): string {\n const value = asRecord(result).value;\n return typeof value === \"string\" ? value : JSON.stringify(value, null, 2);\n}\n\nfunction formatScreenshot(result: unknown): string {\n // Raw base64 PNG — the tool contract is \"base64 png\", not prose.\n return String(asRecord(result).base64 ?? \"\");\n}\n\n// ---------------------------------------------------------------------------\n// Tool table: MCP name → wire command\n// ---------------------------------------------------------------------------\n\nexport const BROWSER_TOOLS: BrowserToolDef[] = [\n {\n name: \"tabs_list\",\n description: \"List the tabs of the user's real running Chrome (id, title, url, active).\",\n shape: {},\n command: \"list_tabs\",\n toParams: () => ({}),\n format: formatTabs,\n },\n {\n name: \"tab_open\",\n description: \"Open a new tab in the user's Chrome and return its tab id.\",\n shape: {\n url: z.string().describe(\"URL to open.\"),\n active: z.boolean().optional().describe(\"Focus the new tab (default true).\"),\n },\n command: \"open_tab\",\n toParams: (a) => ({ url: a.url, ...(a.active !== undefined ? { active: a.active } : {}) }),\n },\n {\n name: \"tab_select\",\n description: \"Bring a tab to the front (activate it and focus its window).\",\n shape: { tab: tabSchema },\n command: \"select_tab\",\n toParams: (a) => ({ tabId: a.tab }),\n },\n {\n name: \"tab_close\",\n description: \"Close a tab.\",\n shape: { tab: tabSchema },\n command: \"close_tab\",\n toParams: (a) => ({ tabId: a.tab }),\n },\n {\n name: \"dom_snapshot\",\n description:\n \"Snapshot a tab's DOM as an accessibility-tree YAML with [ref=sN] ids for interactive elements. \" +\n \"Take a snapshot before dom_click/dom_type; refs stay valid until the next snapshot or navigation.\",\n shape: { tab: tabSchema },\n command: \"snapshot\",\n toParams: (a) => ({ tabId: a.tab }),\n format: formatSnapshot,\n },\n {\n name: \"dom_click\",\n description: \"Click an element by its [ref=sN] from the latest dom_snapshot (trusted CDP event).\",\n shape: { tab: tabSchema, ref: z.string().describe(\"Ref id from dom_snapshot, e.g. s3.\") },\n command: \"click\",\n toParams: (a) => ({ tabId: a.tab, ref: a.ref }),\n },\n {\n name: \"dom_type\",\n description:\n \"Focus an element by its [ref=sN] and type text into it (inserts at the caret, works in SPAs).\",\n shape: {\n tab: tabSchema,\n ref: z.string().describe(\"Ref id from dom_snapshot, e.g. s5.\"),\n text: z.string().describe(\"Text to type.\"),\n },\n command: \"type\",\n toParams: (a) => ({ tabId: a.tab, ref: a.ref, text: a.text }),\n },\n {\n name: \"page_text\",\n description: \"Read a tab's visible text (document.body.innerText).\",\n shape: { tab: tabSchema },\n command: \"eval\",\n toParams: () => ({ code: \"document.body.innerText\" }),\n format: formatEval,\n },\n {\n name: \"eval_js\",\n description: \"Evaluate JavaScript in a tab and return the JSON-safe result. Awaits promises.\",\n shape: {\n tab: tabSchema,\n code: z.string().describe(\"JavaScript expression to evaluate.\"),\n },\n command: \"eval\",\n toParams: (a) => ({ code: a.code }),\n format: formatEval,\n },\n {\n name: \"tab_screenshot\",\n description: \"Screenshot a tab (PNG, base64).\",\n shape: { tab: tabSchema },\n command: \"screenshot\",\n toParams: (a) => ({ tabId: a.tab }),\n format: formatScreenshot,\n },\n {\n name: \"console_logs\",\n description: \"Read the console output captured for a tab since its last snapshot.\",\n shape: { tab: tabSchema },\n command: \"console_logs\",\n toParams: (a) => ({ tabId: a.tab }),\n format: formatLogs,\n },\n];\n\n/** Runs one tool against the bridge; returns MCP content + isError. */\nexport async function runBrowserTool(\n def: BrowserToolDef,\n args: Record<string, unknown>,\n bridge: BridgeLike\n): Promise<{ content: Array<{ type: \"text\"; text: string }>; isError?: boolean }> {\n try {\n const result = await bridge.send(def.command, def.toParams(args));\n return { content: [{ type: \"text\", text: def.format ? def.format(result) : JSON.stringify(result, null, 2) }] };\n } catch (e) {\n const msg =\n e instanceof NotConnectedError\n ? e.message\n : e instanceof Error\n ? e.message\n : String(e);\n return { content: [{ type: \"text\", text: msg }], isError: true };\n }\n}\n\n/** Registers every browser tool on an MCP server. */\nexport function registerBrowserTools(server: McpServer, bridge: BridgeLike): void {\n for (const def of BROWSER_TOOLS) {\n server.tool(def.name, def.description, def.shape, (args) => runBrowserTool(def, args, bridge));\n }\n}\n","#!/usr/bin/env node\n/**\n * pai-browser-mcp — stdio MCP server for the PAI browser bridge.\n *\n * The provider-independent claude-in-chrome replacement: drives the user's\n * real running Chrome (tabs + DOM) through the browser-bridge extension's\n * native messaging host, over ws://127.0.0.1:8756. Any MCP client works —\n * nothing here is Anthropic-specific.\n *\n * provider → MCP (this server) → WebSocket → native host → extension → Chrome\n *\n * If Chrome is not running (or the extension is not loaded), every tool\n * returns a clear \"start Chrome\" error; the server itself stays alive and\n * reconnects lazily per call.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { BridgeClient, globalWebSocketFactory } from \"./bridge-client.js\";\nimport { registerBrowserTools } from \"./tools.js\";\n\nasync function main(): Promise<void> {\n if (globalThis.WebSocket === undefined) {\n // Node < 22 has no global WebSocket client; say so instead of crashing\n // with an opaque TypeError when the first tool call connects.\n process.stderr.write(\n \"pai-browser-mcp: this node runtime has no global WebSocket — run with node >= 22\\n\"\n );\n process.exit(1);\n }\n\n const server = new McpServer({\n name: \"pai-browser\",\n version: \"0.1.0\",\n });\n\n const bridge = new BridgeClient(undefined, globalWebSocketFactory);\n registerBrowserTools(server, bridge);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((e) => {\n process.stderr.write(`pai-browser-mcp fatal error: ${String(e)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
@@ -1,93 +1,13 @@
|
|
|
1
|
-
import { a as WorkersConfigError, h as resolveModelCapability, l as isModelCapability, m as readWorkersSection, o as assertProviderRunnable, p as providerKeyPath, s as classModelCapability } from "./config-
|
|
2
|
-
import { _ as workersLogDir, d as setCooldown, f as eventsPath, g as statusPath, h as panesDir, i as buildRunEnv, m as noMcpConfigPath, n as ensureProxyRunning, o as isQuotaFailure, p as ledgerPath, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveTarget } from "./server-
|
|
3
|
-
import {
|
|
1
|
+
import { a as WorkersConfigError, h as resolveModelCapability, l as isModelCapability, m as readWorkersSection, o as assertProviderRunnable, p as providerKeyPath, s as classModelCapability } from "./config-DgzR5sfb.mjs";
|
|
2
|
+
import { _ as workersLogDir, d as setCooldown, f as eventsPath, g as statusPath, h as panesDir, i as buildRunEnv, m as noMcpConfigPath, n as ensureProxyRunning, o as isQuotaFailure, p as ledgerPath, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveTarget } from "./server-Cp7tNzNC.mjs";
|
|
3
|
+
import { S as stripPromptValues, _ as newWorkerId, a as resolveSpawnerSession, b as parseRunnerArgs, d as alive, g as loadStatuses, h as loadStatus, i as resolveSession, l as UNLABELED, m as describeTool, o as scopeKey, r as itermUuid, v as nowStamp, x as shortText, y as saveStatus } from "./scope-NIwCGxRI.mjs";
|
|
4
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
4
5
|
import { homedir, tmpdir } from "node:os";
|
|
5
|
-
import { dirname, join, relative } from "node:path";
|
|
6
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
7
|
import { connect, createServer } from "node:net";
|
|
7
8
|
import { execFileSync, spawn } from "node:child_process";
|
|
8
9
|
import { createInterface } from "node:readline";
|
|
9
10
|
|
|
10
|
-
//#region src/workers/args.ts
|
|
11
|
-
function parseRunnerArgs(argv) {
|
|
12
|
-
let prompt = null;
|
|
13
|
-
let outputFormat = "text";
|
|
14
|
-
const rest = [];
|
|
15
|
-
let headless = false;
|
|
16
|
-
let callerModel = false;
|
|
17
|
-
let callerMcpConfig = false;
|
|
18
|
-
let callerSystemPrompt = false;
|
|
19
|
-
const mcp = [];
|
|
20
|
-
let i = 0;
|
|
21
|
-
while (i < argv.length) {
|
|
22
|
-
const a = argv[i];
|
|
23
|
-
if (a === "-p" || a === "--print") {
|
|
24
|
-
headless = true;
|
|
25
|
-
rest.push(a);
|
|
26
|
-
if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
27
|
-
prompt = argv[i + 1];
|
|
28
|
-
rest.push(prompt);
|
|
29
|
-
i += 1;
|
|
30
|
-
}
|
|
31
|
-
} else if (a === "--output-format") {
|
|
32
|
-
const v = argv[i + 1];
|
|
33
|
-
if (v === "json" || v === "stream-json") outputFormat = v;
|
|
34
|
-
i += 1;
|
|
35
|
-
} else if (a.startsWith("--output-format=")) {
|
|
36
|
-
const v = a.slice(16);
|
|
37
|
-
if (v === "json" || v === "stream-json") outputFormat = v;
|
|
38
|
-
} else if (a === "--verbose") {} else if (a === "--mcp") {
|
|
39
|
-
const v = argv[i + 1];
|
|
40
|
-
if (v !== void 0 && !v.startsWith("-")) {
|
|
41
|
-
mcp.push(v);
|
|
42
|
-
i += 1;
|
|
43
|
-
}
|
|
44
|
-
} else if (a.startsWith("--mcp=")) mcp.push(a.slice(6));
|
|
45
|
-
else {
|
|
46
|
-
if (a === "--model") callerModel = true;
|
|
47
|
-
if (a.startsWith("--model=")) callerModel = true;
|
|
48
|
-
if (a === "--mcp-config") callerMcpConfig = true;
|
|
49
|
-
if (a.startsWith("--mcp-config=")) callerMcpConfig = true;
|
|
50
|
-
if (a === "--append-system-prompt") callerSystemPrompt = true;
|
|
51
|
-
if (a.startsWith("--append-system-prompt=")) callerSystemPrompt = true;
|
|
52
|
-
if (prompt === null && !a.startsWith("-") && rest.length > 0 && (rest[rest.length - 1] === "-p" || rest[rest.length - 1] === "--print")) prompt = a;
|
|
53
|
-
rest.push(a);
|
|
54
|
-
}
|
|
55
|
-
i += 1;
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
prompt,
|
|
59
|
-
outputFormat,
|
|
60
|
-
rest,
|
|
61
|
-
headless,
|
|
62
|
-
callerModel,
|
|
63
|
-
callerMcpConfig,
|
|
64
|
-
callerSystemPrompt,
|
|
65
|
-
mcp
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Turn `-p "<prompt>"` into bare `-p` (same for --print): for stream-json
|
|
70
|
-
* stdin runs the prompt moves to the first user message on stdin, so the
|
|
71
|
-
* value must not stay on the command line. Only prompt values directly
|
|
72
|
-
* following the flag are touched; everything else passes through.
|
|
73
|
-
*/
|
|
74
|
-
function stripPromptValues(argv) {
|
|
75
|
-
const out = [];
|
|
76
|
-
for (let i = 0; i < argv.length; i++) {
|
|
77
|
-
const a = argv[i];
|
|
78
|
-
const isPrint = a === "-p" || a === "--print";
|
|
79
|
-
out.push(a);
|
|
80
|
-
if (isPrint && i + 1 < argv.length && !argv[i + 1].startsWith("-")) i += 1;
|
|
81
|
-
}
|
|
82
|
-
return out;
|
|
83
|
-
}
|
|
84
|
-
/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */
|
|
85
|
-
function shortText(s, n) {
|
|
86
|
-
const t = String(s ?? "").split(/\s+/).filter(Boolean).join(" ");
|
|
87
|
-
return t.length <= n ? t : t.slice(0, n - 1) + "…";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
//#endregion
|
|
91
11
|
//#region src/workers/ledger.ts
|
|
92
12
|
/**
|
|
93
13
|
* ledger.ts — the routing ledger, one append-only log for every worker event.
|
|
@@ -157,231 +77,6 @@ function ledgerSummary(path, scope, lastN = 15, now = /* @__PURE__ */ new Date()
|
|
|
157
77
|
};
|
|
158
78
|
}
|
|
159
79
|
|
|
160
|
-
//#endregion
|
|
161
|
-
//#region src/workers/status.ts
|
|
162
|
-
/**
|
|
163
|
-
* status.ts — the live per-worker status file.
|
|
164
|
-
*
|
|
165
|
-
* One JSON file per run, rewritten atomically on every turn, read by `ps`,
|
|
166
|
-
* `follow`, the status line and the pane logic. Same field set the Python
|
|
167
|
-
* runner wrote, plus `provider` (routing is multi-provider now) and `session`
|
|
168
|
-
* (the AIBroker identity of the launching session, see scope.ts).
|
|
169
|
-
*/
|
|
170
|
-
/** Label a worker gets when launched with neither --label nor a prompt. */
|
|
171
|
-
const UNLABELED = "unlabeled";
|
|
172
|
-
/** Context-meter percentage 0–100, null when the numbers are missing. */
|
|
173
|
-
function contextPercent(s) {
|
|
174
|
-
if (!s.contextTokens || !s.contextWindow) return null;
|
|
175
|
-
return Math.round(s.contextTokens / s.contextWindow * 100);
|
|
176
|
-
}
|
|
177
|
-
/** Compact token count: 84k, 200k, 900. */
|
|
178
|
-
function fmtContextK(n) {
|
|
179
|
-
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* The one context-meter label, shared by every renderer so identical
|
|
183
|
-
* numbers always render identically: `ctx 84k/200k (42%)` — used-style
|
|
184
|
-
* percent. Empty when the numbers are missing (callers drop the part).
|
|
185
|
-
*/
|
|
186
|
-
function contextLabel(s) {
|
|
187
|
-
const pct = contextPercent(s);
|
|
188
|
-
if (pct === null) return "";
|
|
189
|
-
return `ctx ${fmtContextK(s.contextTokens ?? 0)}/${fmtContextK(s.contextWindow ?? 0)} (${pct}%)`;
|
|
190
|
-
}
|
|
191
|
-
/** Timestamp format shared by status files and the ledger. */
|
|
192
|
-
function nowStamp(d = /* @__PURE__ */ new Date()) {
|
|
193
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
194
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
195
|
-
}
|
|
196
|
-
let lastId = "";
|
|
197
|
-
let idSeq = 0;
|
|
198
|
-
function newWorkerId(d = /* @__PURE__ */ new Date(), pid = process.pid) {
|
|
199
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
200
|
-
const base = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${pid}`;
|
|
201
|
-
if (base === lastId) {
|
|
202
|
-
idSeq += 1;
|
|
203
|
-
return `${base}-${String(idSeq).padStart(2, "0")}`;
|
|
204
|
-
}
|
|
205
|
-
lastId = base;
|
|
206
|
-
idSeq = 0;
|
|
207
|
-
return base;
|
|
208
|
-
}
|
|
209
|
-
/** Write status atomically (temp + rename) and stamp `updated`. */
|
|
210
|
-
function saveStatus(logDir, status, d = /* @__PURE__ */ new Date()) {
|
|
211
|
-
status.updated = nowStamp(d);
|
|
212
|
-
const path = statusPath(logDir, status.id);
|
|
213
|
-
const tmp = `${path}.tmp`;
|
|
214
|
-
writeFileSync(tmp, JSON.stringify(status), "utf8");
|
|
215
|
-
renameSync(tmp, path);
|
|
216
|
-
}
|
|
217
|
-
/** Load every status file in the logDir, oldest id first, skipping damage. */
|
|
218
|
-
function loadStatuses(logDir) {
|
|
219
|
-
if (!existsSync(logDir)) return [];
|
|
220
|
-
const out = [];
|
|
221
|
-
for (const name of readdirSync(logDir).sort()) {
|
|
222
|
-
if (!name.endsWith(".status")) continue;
|
|
223
|
-
try {
|
|
224
|
-
out.push(JSON.parse(readFileSync(join(logDir, name), "utf8")));
|
|
225
|
-
} catch {}
|
|
226
|
-
}
|
|
227
|
-
return out;
|
|
228
|
-
}
|
|
229
|
-
function loadStatus(logDir, id) {
|
|
230
|
-
const path = statusPath(logDir, id);
|
|
231
|
-
if (!existsSync(path)) return null;
|
|
232
|
-
try {
|
|
233
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
234
|
-
} catch {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
function alive(pid) {
|
|
239
|
-
if (!pid || pid <= 0) return false;
|
|
240
|
-
try {
|
|
241
|
-
process.kill(pid, 0);
|
|
242
|
-
return true;
|
|
243
|
-
} catch {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
/** "42s" under 90s, "7m" above — the coarse age the table and bar show. */
|
|
248
|
-
function ageOf(ts, now = /* @__PURE__ */ new Date()) {
|
|
249
|
-
const t = Date.parse(ts.replace(" ", "T"));
|
|
250
|
-
if (Number.isNaN(t)) return "?";
|
|
251
|
-
const s = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
252
|
-
return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;
|
|
253
|
-
}
|
|
254
|
-
/** One-line description of a tool call, e.g. "Bash: npm test". */
|
|
255
|
-
function describeTool(name, inp) {
|
|
256
|
-
if (typeof inp !== "object" || inp === null) return name;
|
|
257
|
-
const i = inp;
|
|
258
|
-
const get = (k) => typeof i[k] === "string" ? i[k] : "";
|
|
259
|
-
if (name === "Bash") return `Bash: ${shortText(get("command"), 70)}`;
|
|
260
|
-
if (name === "Read" || name === "Edit" || name === "Write" || name === "MultiEdit") return `${name}: ${get("file_path").split("/").pop() ?? ""}`;
|
|
261
|
-
if (name === "Grep" || name === "Glob") return `${name}: ${shortText(get("pattern"), 50)}`;
|
|
262
|
-
if (name === "WebSearch") return `${name}: ${shortText(get("query"), 50)}`;
|
|
263
|
-
if (name === "WebFetch") return `${name}: ${shortText(get("url"), 60)}`;
|
|
264
|
-
return name;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
//#endregion
|
|
268
|
-
//#region src/workers/scope.ts
|
|
269
|
-
/**
|
|
270
|
-
* scope.ts — which workers "belong" to the terminal asking about them.
|
|
271
|
-
*
|
|
272
|
-
* Two identities, in priority order:
|
|
273
|
-
*
|
|
274
|
-
* 1. AIBroker's session registry (~/.aibroker/session-names.json): iTerm
|
|
275
|
-
* session UUID → session name. The launching session's UUID is stored in
|
|
276
|
-
* each worker's status file as `session.id`, so every pane of a named
|
|
277
|
-
* session sees its workers — regardless of tab layout.
|
|
278
|
-
* 2. The iTerm tab key (`w<window>t<tab>` prefix of ITERM_SESSION_ID): the
|
|
279
|
-
* original heuristic, kept as the fallback when no registry entry
|
|
280
|
-
* matches (AIBroker absent, unnamed session, non-iTerm terminal).
|
|
281
|
-
*
|
|
282
|
-
* If neither is available, viewers fall back to "all workers".
|
|
283
|
-
*
|
|
284
|
-
* A third case: workers spawned from a Claude Code Bash tool (the
|
|
285
|
-
* orchestrator pattern) have neither — Claude Code exports no terminal or
|
|
286
|
-
* session identity to its Bash children. Those record `spawnerSession`, the
|
|
287
|
-
* orchestrator's claude session id bridged through the status line's
|
|
288
|
-
* session map (see below), so their tab still claims them.
|
|
289
|
-
*/
|
|
290
|
-
const AIBROKER_REGISTRY = join(homedir(), ".aibroker", "session-names.json");
|
|
291
|
-
/**
|
|
292
|
-
* `w<window>t<tab>` prefix of an iTerm session id, "" if empty or malformed.
|
|
293
|
-
* Panes split from the same tab share this prefix and differ only in `p<n>`.
|
|
294
|
-
*/
|
|
295
|
-
function tabKey(term) {
|
|
296
|
-
if (!term) return "";
|
|
297
|
-
const head = term.split("p", 1)[0];
|
|
298
|
-
const parts = head.slice(1).split("t");
|
|
299
|
-
if (head.startsWith("w") && parts.length === 2 && parts.every((p) => /^\d+$/.test(p))) return head;
|
|
300
|
-
return "";
|
|
301
|
-
}
|
|
302
|
-
/** Tab key of the iTerm tab this process runs in, "" if not inside iTerm2. */
|
|
303
|
-
function currentTabKey(env = process.env) {
|
|
304
|
-
return tabKey(env.ITERM_SESSION_ID ?? "");
|
|
305
|
-
}
|
|
306
|
-
/** The iTerm UUID part of an ITERM_SESSION_ID (after the last colon). */
|
|
307
|
-
function itermUuid(term) {
|
|
308
|
-
if (!term) return "";
|
|
309
|
-
return term.split(":").pop() ?? "";
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Resolve the AIBroker session for an ITERM_SESSION_ID. Reads the persistent
|
|
313
|
-
* name registry (the same store `aibroker_rename` writes); returns null when
|
|
314
|
-
* AIBroker is absent or the session is not in it — callers then use the tab
|
|
315
|
-
* key, which is the pre-AIBroker behaviour.
|
|
316
|
-
*/
|
|
317
|
-
function resolveSession(term, registryPath = AIBROKER_REGISTRY) {
|
|
318
|
-
const uuid = itermUuid(term);
|
|
319
|
-
if (!uuid) return null;
|
|
320
|
-
let names;
|
|
321
|
-
try {
|
|
322
|
-
if (!existsSync(registryPath)) return null;
|
|
323
|
-
names = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
324
|
-
} catch {
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
const name = names[uuid];
|
|
328
|
-
if (typeof name !== "string" || !name) return null;
|
|
329
|
-
return {
|
|
330
|
-
id: uuid,
|
|
331
|
-
name
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* True when `worker` was launched from `term` (the terminal asking about it).
|
|
336
|
-
* Session-id match first; the tab key only when no registry entry matched —
|
|
337
|
-
* a session without a name keeps the tab-scoped behaviour it always had.
|
|
338
|
-
*/
|
|
339
|
-
function workerInScope(worker, term) {
|
|
340
|
-
if (!term) return false;
|
|
341
|
-
const uuid = itermUuid(term);
|
|
342
|
-
if (worker.session?.id && uuid) return worker.session.id === uuid;
|
|
343
|
-
return tabKey(worker.term) === tabKey(term) && tabKey(term) !== "";
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Registry key for pane files: the session id when AIBroker knows this
|
|
347
|
-
* terminal, else the tab key, else the raw iTerm UUID.
|
|
348
|
-
*/
|
|
349
|
-
function scopeKey(term) {
|
|
350
|
-
const session = resolveSession(term);
|
|
351
|
-
if (session) return session.id;
|
|
352
|
-
return currentTabKey() || itermUuid(term);
|
|
353
|
-
}
|
|
354
|
-
/** `[Name]` when the worker has an AIBroker session name, else "". */
|
|
355
|
-
function sessionTag(worker) {
|
|
356
|
-
return worker.session?.name ? `[${worker.session.name}]` : "";
|
|
357
|
-
}
|
|
358
|
-
/** How old a map entry may be for a spawn to adopt it (status lines refresh constantly while a session lives). */
|
|
359
|
-
const SPAWNER_SESSION_TTL_MS = 10 * 6e4;
|
|
360
|
-
/** Map entries not refreshed within this window are pruned on write. */
|
|
361
|
-
const SESSION_MAP_PRUNE_MS = 60 * 6e4;
|
|
362
|
-
function sessionMapPath(logDir) {
|
|
363
|
-
return join(logDir, "claude-session-map.json");
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* The claude session a new run was spawned by, when it can be known: a run
|
|
367
|
-
* inside another worker inherits its spawner (chain stages keep the
|
|
368
|
-
* orchestrator's tab that way); otherwise a fresh map entry for `cwd`.
|
|
369
|
-
*/
|
|
370
|
-
function resolveSpawnerSession(logDir, cwd, env = process.env, now = Date.now()) {
|
|
371
|
-
const parentId = env.PAI_WORKER_ID;
|
|
372
|
-
if (parentId) {
|
|
373
|
-
const inherited = loadStatus(logDir, parentId)?.spawnerSession;
|
|
374
|
-
if (inherited) return inherited;
|
|
375
|
-
}
|
|
376
|
-
const path = sessionMapPath(logDir);
|
|
377
|
-
if (!existsSync(path) || !cwd) return null;
|
|
378
|
-
try {
|
|
379
|
-
const entry = JSON.parse(readFileSync(path, "utf8"))[cwd];
|
|
380
|
-
if (entry && entry.session && now - entry.ts < SPAWNER_SESSION_TTL_MS) return entry.session;
|
|
381
|
-
} catch {}
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
80
|
//#endregion
|
|
386
81
|
//#region src/workers/pane.ts
|
|
387
82
|
/**
|
|
@@ -1219,6 +914,11 @@ function worktreeWanted(flag, opts) {
|
|
|
1219
914
|
* degrade to an in-place run.
|
|
1220
915
|
*/
|
|
1221
916
|
function addWorktree(logDir, id, cwd) {
|
|
917
|
+
const swept = sweepOrphanWorktrees(logDir);
|
|
918
|
+
if (swept.length) appendLedger(ledgerPath(logDir), "WORKER-NOTE", {
|
|
919
|
+
id,
|
|
920
|
+
note: `swept orphan worktree(s): ${swept.join(", ")}`
|
|
921
|
+
});
|
|
1222
922
|
const dir = worktreePath(logDir, id);
|
|
1223
923
|
const branch = worktreeBranch(id);
|
|
1224
924
|
const base = git(cwd, ["rev-parse", "HEAD"]);
|
|
@@ -1235,6 +935,46 @@ function addWorktree(logDir, id, cwd) {
|
|
|
1235
935
|
base
|
|
1236
936
|
};
|
|
1237
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Remove worktrees (and their `worker/<id>` branches) whose worker no longer
|
|
940
|
+
* exists — no status file left in the log dir. Killed and failed runs clean
|
|
941
|
+
* up after themselves, but a `kill -9` or a crash strands a directory and a
|
|
942
|
+
* branch; every worktree creation sweeps first so they cannot accumulate.
|
|
943
|
+
* Directories younger than `minAgeMin` minutes are left alone: a run that is
|
|
944
|
+
* just starting owns its worktree a moment before its status file exists.
|
|
945
|
+
*/
|
|
946
|
+
function sweepOrphanWorktrees(logDir, minAgeMin = 10) {
|
|
947
|
+
const root = worktreesDir(logDir);
|
|
948
|
+
if (!existsSync(root)) return [];
|
|
949
|
+
const swept = [];
|
|
950
|
+
for (const ent of readdirSync(root, { withFileTypes: true })) {
|
|
951
|
+
if (!ent.isDirectory()) continue;
|
|
952
|
+
const id = ent.name;
|
|
953
|
+
const dir = join(root, id);
|
|
954
|
+
if (existsSync(statusPath(logDir, id))) continue;
|
|
955
|
+
try {
|
|
956
|
+
if ((Date.now() - statSync(dir).mtimeMs) / 6e4 < minAgeMin) continue;
|
|
957
|
+
} catch {}
|
|
958
|
+
let gitDir = null;
|
|
959
|
+
try {
|
|
960
|
+
const raw = git(dir, ["rev-parse", "--git-common-dir"]);
|
|
961
|
+
gitDir = isAbsolute(raw) ? raw : resolve(dir, raw);
|
|
962
|
+
} catch {
|
|
963
|
+
gitDir = null;
|
|
964
|
+
}
|
|
965
|
+
removeWorktree(gitDir ?? dir, dir, true);
|
|
966
|
+
if (gitDir) try {
|
|
967
|
+
git(gitDir, ["worktree", "prune"]);
|
|
968
|
+
git(gitDir, [
|
|
969
|
+
"branch",
|
|
970
|
+
"-D",
|
|
971
|
+
worktreeBranch(id)
|
|
972
|
+
]);
|
|
973
|
+
} catch {}
|
|
974
|
+
swept.push(id);
|
|
975
|
+
}
|
|
976
|
+
return swept;
|
|
977
|
+
}
|
|
1238
978
|
/** Commits the branch collected on top of its base. */
|
|
1239
979
|
function commitsSince(dir, base) {
|
|
1240
980
|
try {
|
|
@@ -1520,6 +1260,9 @@ const WORKER_CONTRACT_PROMPT = [
|
|
|
1520
1260
|
"notes: one line, the headline a reviewer reads first.",
|
|
1521
1261
|
"Never write shell heredocs — write the script to a file with the Write tool, then run it",
|
|
1522
1262
|
"(heredocs fail silently or get swallowed).",
|
|
1263
|
+
"Never inline code one-liners (python3 -c, node -e, ruby -e and friends) — the permission layer",
|
|
1264
|
+
"denies them (\"ztk: command denied by permission rules\"); write the script to a file with the",
|
|
1265
|
+
"Write tool and run it (python3 script.py), or use jq for JSON inspection.",
|
|
1523
1266
|
"",
|
|
1524
1267
|
`Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
|
|
1525
1268
|
"run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines —",
|
|
@@ -1607,9 +1350,11 @@ function relShort(p, cwd) {
|
|
|
1607
1350
|
* Headless workers start with NO MCP servers: every server definition is a
|
|
1608
1351
|
* prompt-time tool inventory the model pays to know, and a worker that only
|
|
1609
1352
|
* reads and edits files needs none of it. A run may opt in with `--mcp
|
|
1610
|
-
* name[,name…]
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1353
|
+
* name[,name…]`, a role carrying `"mcp": [...]`, or implicitly by naming
|
|
1354
|
+
* `mcp__server__tool` in --allowedTools (a grant without its server loaded is
|
|
1355
|
+
* a dead letter). Names may be single servers from ~/.claude.json's
|
|
1356
|
+
* `mcpServers` or `workers.mcpSets` set names, which expand to their member
|
|
1357
|
+
* list. The filtered config lands in
|
|
1613
1358
|
* `<logDir>/<id>.mcp.json` and is passed with `--strict-mcp-config
|
|
1614
1359
|
* --mcp-config` so exactly those servers load. MCP servers are chosen at
|
|
1615
1360
|
* launch only — a mid-run `say` cannot add any.
|
|
@@ -1646,6 +1391,22 @@ function expandMcpNames(names, config, claudeJson = CLAUDE_JSON) {
|
|
|
1646
1391
|
}
|
|
1647
1392
|
return out;
|
|
1648
1393
|
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Derive server names from tool grants: every `mcp__<server>__<tool>` (or bare
|
|
1396
|
+
* `mcp__<server>`, or `mcp__<server>__*`) in an --allowedTools list names a
|
|
1397
|
+
* server the run expects to be loaded. A grant is a dead letter unless its
|
|
1398
|
+
* server is in the filtered config, so the runner treats these as implicit
|
|
1399
|
+
* --mcp names — and only these; non-mcp grants load nothing.
|
|
1400
|
+
*/
|
|
1401
|
+
function mcpServersFromToolGrants(tools) {
|
|
1402
|
+
const out = [];
|
|
1403
|
+
for (const entry of tools) for (const name of entry.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
1404
|
+
if (!name.startsWith("mcp__")) continue;
|
|
1405
|
+
const server = name.slice(5).split("__")[0];
|
|
1406
|
+
if (server && !out.includes(server)) out.push(server);
|
|
1407
|
+
}
|
|
1408
|
+
return out;
|
|
1409
|
+
}
|
|
1649
1410
|
/** Path of a run's filtered MCP config. */
|
|
1650
1411
|
function runMcpConfigPath(logDir, id) {
|
|
1651
1412
|
return join(logDir, `${id}.mcp.json`);
|
|
@@ -1859,9 +1620,12 @@ function codexInstalled() {
|
|
|
1859
1620
|
* - env: ANTHROPIC_BASE_URL/AUTH_TOKEN from the provider (token from its
|
|
1860
1621
|
* key file, never from the environment), the three DEFAULT_*_MODEL vars,
|
|
1861
1622
|
* the provider's extra env, nonessential traffic off, and ANTHROPIC_API_KEY
|
|
1862
|
-
* stripped so nothing can fall back to Anthropic billing.
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1623
|
+
* stripped so nothing can fall back to Anthropic billing. Headless runs
|
|
1624
|
+
* also drop the spawner's session identity (messaging socket, session id,
|
|
1625
|
+
* nesting markers) — inherited, a worktree child starts tool-blind.
|
|
1626
|
+
* OpenAI-protocol providers point at the PAI proxy instead (started on
|
|
1627
|
+
* demand, the provider name in the URL path); codex-engine providers run
|
|
1628
|
+
* the Codex CLI.
|
|
1865
1629
|
* - headless (-p): strict empty MCP config unless the caller brings one or
|
|
1866
1630
|
* names servers via --mcp / a role (then a filtered <id>.mcp.json),
|
|
1867
1631
|
* PAI_WORKER=1 so PAI's per-session hooks leave it alone, the worker
|
|
@@ -1961,7 +1725,7 @@ async function runWorker(opts) {
|
|
|
1961
1725
|
const logDir = workersLogDir(config);
|
|
1962
1726
|
mkdirSync(logDir, { recursive: true });
|
|
1963
1727
|
if (opts.className === "plan" && !opts._planner) {
|
|
1964
|
-
const { runPlanner } = await import("./planner-
|
|
1728
|
+
const { runPlanner } = await import("./planner-BLDJt3ZS.mjs");
|
|
1965
1729
|
return runPlanner(opts);
|
|
1966
1730
|
}
|
|
1967
1731
|
const parent = launchParent(opts.parent);
|
|
@@ -2093,7 +1857,8 @@ async function executeRun(a) {
|
|
|
2093
1857
|
const wanted = [
|
|
2094
1858
|
...a.mcpFlag ? [a.mcpFlag] : [],
|
|
2095
1859
|
...parsed.mcp,
|
|
2096
|
-
...target.classMcp ?? []
|
|
1860
|
+
...target.classMcp ?? [],
|
|
1861
|
+
...mcpServersFromToolGrants(parsed.allowedTools)
|
|
2097
1862
|
];
|
|
2098
1863
|
if (wanted.length) mcpArgs = [
|
|
2099
1864
|
"--strict-mcp-config",
|
|
@@ -2182,6 +1947,9 @@ async function executeRun(a) {
|
|
|
2182
1947
|
killed: 1,
|
|
2183
1948
|
label
|
|
2184
1949
|
});
|
|
1950
|
+
if (worktree) try {
|
|
1951
|
+
recordWorktree(logDir, status, worktree, false);
|
|
1952
|
+
} catch {}
|
|
2185
1953
|
cleanup();
|
|
2186
1954
|
try {
|
|
2187
1955
|
proc.kill();
|
|
@@ -2190,6 +1958,7 @@ async function executeRun(a) {
|
|
|
2190
1958
|
};
|
|
2191
1959
|
process.once("SIGTERM", () => onSignal("SIGTERM"));
|
|
2192
1960
|
process.once("SIGINT", () => onSignal("SIGINT"));
|
|
1961
|
+
process.once("SIGHUP", () => onSignal("SIGHUP"));
|
|
2193
1962
|
const ctx = {
|
|
2194
1963
|
resultEvent: null,
|
|
2195
1964
|
resultReport: null
|
|
@@ -2425,6 +2194,7 @@ async function executeCodexRun(a) {
|
|
|
2425
2194
|
let killed = false;
|
|
2426
2195
|
process.once("SIGTERM", onCodexSignal("SIGTERM"));
|
|
2427
2196
|
process.once("SIGINT", onCodexSignal("SIGINT"));
|
|
2197
|
+
process.once("SIGHUP", onCodexSignal("SIGHUP"));
|
|
2428
2198
|
function onCodexSignal(sig) {
|
|
2429
2199
|
return () => {
|
|
2430
2200
|
killed = true;
|
|
@@ -2433,6 +2203,9 @@ async function executeCodexRun(a) {
|
|
|
2433
2203
|
status.secs = Math.floor((Date.now() - t0) / 1e3);
|
|
2434
2204
|
status.last = `killed by signal ${sig}`;
|
|
2435
2205
|
saveStatus(logDir, status);
|
|
2206
|
+
if (worktree) try {
|
|
2207
|
+
recordWorktree(logDir, status, worktree, false);
|
|
2208
|
+
} catch {}
|
|
2436
2209
|
try {
|
|
2437
2210
|
proc.kill();
|
|
2438
2211
|
} catch {}
|
|
@@ -2793,5 +2566,5 @@ async function runChain(opts, deps = {}) {
|
|
|
2793
2566
|
}
|
|
2794
2567
|
|
|
2795
2568
|
//#endregion
|
|
2796
|
-
export {
|
|
2797
|
-
//# sourceMappingURL=chain-
|
|
2569
|
+
export { openPaneForWorker as _, testProvider as a, renderReport as c, handoffFromInside as d, readInbox as f, openFollowPane as g, checkPaneForWorker as h, runWorker as i, discardWorker as l, workerDepth as m, swapPromptArg as n, describeMcp as o, sayToWorker as p, printResult as r, parseWorkerReport as s, runChain as t, mergeWorker as u, appendLedger as v, ledgerSummary as y };
|
|
2570
|
+
//# sourceMappingURL=chain-DGXNoTwc.mjs.map
|