github-router 0.3.323 → 0.3.324
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/dist/{attribution-settings-CX-kIrT8.js → attribution-settings-sTUXqFzx.js} +366 -9
- package/dist/attribution-settings-sTUXqFzx.js.map +1 -0
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{claude-Cq1xGzbI.js → claude-Bni6m56W.js} +5 -5
- package/dist/{claude-Cq1xGzbI.js.map → claude-Bni6m56W.js.map} +1 -1
- package/dist/{codex-INt1hc_5.js → codex-DAnu246m.js} +2 -2
- package/dist/{codex-INt1hc_5.js.map → codex-DAnu246m.js.map} +1 -1
- package/dist/hooks.mjs +26 -0
- package/dist/hooks.sha256 +1 -1
- package/dist/{internal-first-mate-guard-mvfZal_M.js → internal-first-mate-guard-Cj_g7RFr.js} +2 -2
- package/dist/{internal-first-mate-guard-mvfZal_M.js.map → internal-first-mate-guard-Cj_g7RFr.js.map} +1 -1
- package/dist/{internal-first-mate-guard-Dvd1vZ4l.js → internal-first-mate-guard-ZqPHrYlh.js} +1 -1
- package/dist/{internal-worker-guard-BSoS4wzV.js → internal-worker-guard-Dd14aDXQ.js} +2 -2
- package/dist/{internal-worker-guard-BSoS4wzV.js.map → internal-worker-guard-Dd14aDXQ.js.map} +1 -1
- package/dist/main.js +6 -6
- package/dist/{serve-TiUhvpVp.js → serve-BGGcy8Nq.js} +6 -5
- package/dist/serve-BGGcy8Nq.js.map +1 -0
- package/dist/{server-setup-CgbHmupe.js → server-setup-CZ3tEsgq.js} +53 -2
- package/dist/server-setup-CZ3tEsgq.js.map +1 -0
- package/dist/{start-DNzOFabp.js → start-XFIxMRkE.js} +2 -2
- package/dist/{start-DNzOFabp.js.map → start-XFIxMRkE.js.map} +1 -1
- package/dist/{worker-dispatch-DwS99DXz.js → worker-dispatch-CcQygiAA.js} +27 -1
- package/dist/{worker-dispatch-DwS99DXz.js.map → worker-dispatch-CcQygiAA.js.map} +1 -1
- package/package.json +1 -1
- package/dist/attribution-settings-CX-kIrT8.js.map +0 -1
- package/dist/serve-TiUhvpVp.js.map +0 -1
- package/dist/server-setup-CgbHmupe.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { an as colbertDegradedWarning, on as provisionAndIndexColbert, pt as browserToolsEnabled, rn as provisionBrowserAssets, un as warmTreeSitterPool } from "./peer-mcp-personas-DcAErb_d.js";
|
|
2
|
-
import { R as runSelfUpdate, a as sharedServerArgs, i as setupAndServe, n as getCodexEnvVars, r as parseSharedArgs, s as startKeepAwake, t as getClaudeCodeEnvVars } from "./server-setup-
|
|
2
|
+
import { R as runSelfUpdate, a as sharedServerArgs, i as setupAndServe, n as getCodexEnvVars, r as parseSharedArgs, s as startKeepAwake, t as getClaudeCodeEnvVars } from "./server-setup-CZ3tEsgq.js";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import consola from "consola";
|
|
5
5
|
import process$1 from "node:process";
|
|
@@ -129,4 +129,4 @@ const start = defineCommand({
|
|
|
129
129
|
//#endregion
|
|
130
130
|
export { start };
|
|
131
131
|
|
|
132
|
-
//# sourceMappingURL=start-
|
|
132
|
+
//# sourceMappingURL=start-XFIxMRkE.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start-DNzOFabp.js","names":["process"],"sources":["../src/lib/shell.ts","../src/start.ts"],"sourcesContent":["import process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record<string, string | undefined>\n\nfunction getShell(): ShellName {\n const { platform, env } = process\n\n if (platform === \"win32\") {\n // Git Bash / MSYS2 / Cygwin set SHELL even on Windows\n if (env.SHELL) {\n if (env.SHELL.endsWith(\"zsh\")) return \"zsh\"\n if (env.SHELL.endsWith(\"fish\")) return \"fish\"\n if (env.SHELL.endsWith(\"bash\")) return \"bash\"\n return \"sh\"\n }\n\n // Windows PowerShell 5.x sets this\n if (env.POWERSHELL_DISTRIBUTION_CHANNEL) return \"powershell\"\n\n // PowerShell (both 5.x and 7+/pwsh) adds user-scoped module paths\n // at runtime. The system-level PSModulePath in CMD lacks these paths.\n if (env.PSModulePath) {\n const lower = env.PSModulePath.toLowerCase()\n if (\n lower.includes(\"documents\\\\powershell\")\n || lower.includes(\"documents\\\\windowspowershell\")\n ) {\n return \"powershell\"\n }\n }\n\n return \"cmd\"\n }\n\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n}\n\nfunction quotePosixValue(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`\n}\n\nfunction quotePowerShellValue(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${quotePowerShellValue(value)}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set \"${key}=${value}\"`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${quotePosixValue(value)}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${quotePosixValue(value)}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator =\n shell === \"cmd\" ? \" & \" : shell === \"powershell\" ? \"; \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\n\nimport { generateEnvScript } from \"./lib/shell\"\nimport { DEFAULT_CODEX_MODEL, DEFAULT_PORT } from \"./lib/port\"\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport { browserToolsEnabled } from \"./lib/mcp-capabilities\"\nimport { colbertDegradedWarning, provisionAndIndexColbert } from \"./lib/colbert\"\nimport { startKeepAwake } from \"./lib/keep-awake\"\nimport { warmTreeSitterPool } from \"./lib/tree-sitter-pool/pool\"\nimport {\n getClaudeCodeEnvVars,\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { runSelfUpdate } from \"./lib/self-update\"\n\nfunction printAndCopyCommand(command: string, label: string): void {\n consola.box(`${label}\\n\\n${command}`)\n try {\n clipboard.writeSync(command)\n consola.success(`Copied ${label} command to clipboard!`)\n } catch {\n consola.warn(\"Failed to copy to clipboard. Copy the command above manually.\")\n }\n}\n\nfunction generateClaudeCodeCommand(serverUrl: string, model?: string) {\n const envVars = getClaudeCodeEnvVars(serverUrl, model)\n const command = generateEnvScript(envVars, \"claude --dangerously-skip-permissions\")\n printAndCopyCommand(command, \"Claude Code\")\n}\n\nfunction generateCodexCommand(serverUrl: string, model?: string) {\n const codexModel = model ?? DEFAULT_CODEX_MODEL\n const envVars = getCodexEnvVars(serverUrl)\n const command = generateEnvScript(envVars, `codex -m ${codexModel}`)\n printAndCopyCommand(command, \"Codex CLI\")\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the github-router server\",\n },\n args: {\n ...sharedServerArgs,\n cc: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n cx: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Codex CLI with Copilot API config\",\n },\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model (used with --cc or --cx)\",\n },\n },\n async run({ args }) {\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n const { serverUrl } = await setupAndServe({\n ...parsed,\n port: parsed.port ?? DEFAULT_PORT,\n silent: false,\n })\n\n // Best-effort self-update (detached, applies next launch). Runs\n // after the server is listening so the bounded probe can't delay it.\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Best-effort ColBERT semantic-search provision + background index of\n // the launch cwd (if a git repo). Opt-IN via --search (no-op otherwise);\n // never blocks launch, never throws.\n void provisionAndIndexColbert()\n\n // Surface a terminally-failed semantic index to the HUMAN (see the note\n // in claude.ts). Fire-and-forget; lexical search still works.\n void colbertDegradedWarning()\n .then((warning) => {\n if (warning) process.stderr.write(`${warning}\\n`)\n })\n .catch(() => {})\n\n // Ready one tree-sitter worker in the background so the first code search\n // avoids WASM/grammar initialization. One worker keeps launch CPU bounded;\n // demand grows the pool lazily. Opt out with GH_ROUTER_DISABLE_TS_POOL_WARMUP=1.\n void warmTreeSitterPool()\n\n // Best-effort: keep the machine awake while the proxy serves (win32\n // default-on; opt out with GH_ROUTER_DISABLE_KEEP_AWAKE=1). Holds a\n // SetThreadExecutionState assertion via a persistent PowerShell\n // helper. Self-registers its own SIGINT/SIGTERM/exit reaper — load-\n // bearing here, because `start` has no launchChild/onShutdown.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the\n // stable app-dir and stamp the running version, so a one-time \"Load\n // unpacked\" survives npx/bunx upgrades. Gated on --browse; never\n // blocks launch, never throws.\n if (browserToolsEnabled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n if (args.cc) generateClaudeCodeCommand(serverUrl, args.model)\n if (args.cx) generateCodexCommand(serverUrl, args.model)\n\n consola.box(\n `🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`,\n )\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"start-XFIxMRkE.js","names":["process"],"sources":["../src/lib/shell.ts","../src/start.ts"],"sourcesContent":["import process from \"node:process\"\n\ntype ShellName = \"bash\" | \"zsh\" | \"fish\" | \"powershell\" | \"cmd\" | \"sh\"\ntype EnvVars = Record<string, string | undefined>\n\nfunction getShell(): ShellName {\n const { platform, env } = process\n\n if (platform === \"win32\") {\n // Git Bash / MSYS2 / Cygwin set SHELL even on Windows\n if (env.SHELL) {\n if (env.SHELL.endsWith(\"zsh\")) return \"zsh\"\n if (env.SHELL.endsWith(\"fish\")) return \"fish\"\n if (env.SHELL.endsWith(\"bash\")) return \"bash\"\n return \"sh\"\n }\n\n // Windows PowerShell 5.x sets this\n if (env.POWERSHELL_DISTRIBUTION_CHANNEL) return \"powershell\"\n\n // PowerShell (both 5.x and 7+/pwsh) adds user-scoped module paths\n // at runtime. The system-level PSModulePath in CMD lacks these paths.\n if (env.PSModulePath) {\n const lower = env.PSModulePath.toLowerCase()\n if (\n lower.includes(\"documents\\\\powershell\")\n || lower.includes(\"documents\\\\windowspowershell\")\n ) {\n return \"powershell\"\n }\n }\n\n return \"cmd\"\n }\n\n const shellPath = env.SHELL\n if (shellPath) {\n if (shellPath.endsWith(\"zsh\")) return \"zsh\"\n if (shellPath.endsWith(\"fish\")) return \"fish\"\n if (shellPath.endsWith(\"bash\")) return \"bash\"\n }\n\n return \"sh\"\n}\n\nfunction quotePosixValue(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`\n}\n\nfunction quotePowerShellValue(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`\n}\n\n/**\n * Generates a copy-pasteable script to set multiple environment variables\n * and run a subsequent command.\n * @param {EnvVars} envVars - An object of environment variables to set.\n * @param {string} commandToRun - The command to run after setting the variables.\n * @returns {string} The formatted script string.\n */\nexport function generateEnvScript(\n envVars: EnvVars,\n commandToRun: string = \"\",\n): string {\n const shell = getShell()\n const filteredEnvVars = Object.entries(envVars).filter(\n ([, value]) => value !== undefined,\n ) as Array<[string, string]>\n\n let commandBlock: string\n\n switch (shell) {\n case \"powershell\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `$env:${key} = ${quotePowerShellValue(value)}`)\n .join(\"; \")\n break\n }\n case \"cmd\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set \"${key}=${value}\"`)\n .join(\" & \")\n break\n }\n case \"fish\": {\n commandBlock = filteredEnvVars\n .map(([key, value]) => `set -gx ${key} ${quotePosixValue(value)}`)\n .join(\"; \")\n break\n }\n default: {\n // bash, zsh, sh\n const assignments = filteredEnvVars\n .map(([key, value]) => `${key}=${quotePosixValue(value)}`)\n .join(\" \")\n commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : \"\"\n break\n }\n }\n\n if (commandBlock && commandToRun) {\n const separator =\n shell === \"cmd\" ? \" & \" : shell === \"powershell\" ? \"; \" : \" && \"\n return `${commandBlock}${separator}${commandToRun}`\n }\n\n return commandBlock || commandToRun\n}\n","#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport clipboard from \"clipboardy\"\nimport consola from \"consola\"\n\nimport { generateEnvScript } from \"./lib/shell\"\nimport { DEFAULT_CODEX_MODEL, DEFAULT_PORT } from \"./lib/port\"\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport { browserToolsEnabled } from \"./lib/mcp-capabilities\"\nimport { colbertDegradedWarning, provisionAndIndexColbert } from \"./lib/colbert\"\nimport { startKeepAwake } from \"./lib/keep-awake\"\nimport { warmTreeSitterPool } from \"./lib/tree-sitter-pool/pool\"\nimport {\n getClaudeCodeEnvVars,\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { runSelfUpdate } from \"./lib/self-update\"\n\nfunction printAndCopyCommand(command: string, label: string): void {\n consola.box(`${label}\\n\\n${command}`)\n try {\n clipboard.writeSync(command)\n consola.success(`Copied ${label} command to clipboard!`)\n } catch {\n consola.warn(\"Failed to copy to clipboard. Copy the command above manually.\")\n }\n}\n\nfunction generateClaudeCodeCommand(serverUrl: string, model?: string) {\n const envVars = getClaudeCodeEnvVars(serverUrl, model)\n const command = generateEnvScript(envVars, \"claude --dangerously-skip-permissions\")\n printAndCopyCommand(command, \"Claude Code\")\n}\n\nfunction generateCodexCommand(serverUrl: string, model?: string) {\n const codexModel = model ?? DEFAULT_CODEX_MODEL\n const envVars = getCodexEnvVars(serverUrl)\n const command = generateEnvScript(envVars, `codex -m ${codexModel}`)\n printAndCopyCommand(command, \"Codex CLI\")\n}\n\nexport const start = defineCommand({\n meta: {\n name: \"start\",\n description: \"Start the github-router server\",\n },\n args: {\n ...sharedServerArgs,\n cc: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Claude Code with Copilot API config\",\n },\n cx: {\n type: \"boolean\",\n default: false,\n description:\n \"Generate a command to launch Codex CLI with Copilot API config\",\n },\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model (used with --cc or --cx)\",\n },\n },\n async run({ args }) {\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n const { serverUrl } = await setupAndServe({\n ...parsed,\n port: parsed.port ?? DEFAULT_PORT,\n silent: false,\n })\n\n // Best-effort self-update (detached, applies next launch). Runs\n // after the server is listening so the bounded probe can't delay it.\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Best-effort ColBERT semantic-search provision + background index of\n // the launch cwd (if a git repo). Opt-IN via --search (no-op otherwise);\n // never blocks launch, never throws.\n void provisionAndIndexColbert()\n\n // Surface a terminally-failed semantic index to the HUMAN (see the note\n // in claude.ts). Fire-and-forget; lexical search still works.\n void colbertDegradedWarning()\n .then((warning) => {\n if (warning) process.stderr.write(`${warning}\\n`)\n })\n .catch(() => {})\n\n // Ready one tree-sitter worker in the background so the first code search\n // avoids WASM/grammar initialization. One worker keeps launch CPU bounded;\n // demand grows the pool lazily. Opt out with GH_ROUTER_DISABLE_TS_POOL_WARMUP=1.\n void warmTreeSitterPool()\n\n // Best-effort: keep the machine awake while the proxy serves (win32\n // default-on; opt out with GH_ROUTER_DISABLE_KEEP_AWAKE=1). Holds a\n // SetThreadExecutionState assertion via a persistent PowerShell\n // helper. Self-registers its own SIGINT/SIGTERM/exit reaper — load-\n // bearing here, because `start` has no launchChild/onShutdown.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the\n // stable app-dir and stamp the running version, so a one-time \"Load\n // unpacked\" survives npx/bunx upgrades. Gated on --browse; never\n // blocks launch, never throws.\n if (browserToolsEnabled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n if (args.cc) generateClaudeCodeCommand(serverUrl, args.model)\n if (args.cx) generateCodexCommand(serverUrl, args.model)\n\n consola.box(\n `🌐 Usage Viewer: https://animeshkundu.github.io/github-router/dashboard.html?endpoint=${serverUrl}/usage`,\n )\n },\n})\n"],"mappings":"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"}
|
|
@@ -220,7 +220,33 @@ function parseModesCsv(csv) {
|
|
|
220
220
|
const out = csv.split(",").map((s) => s.trim()).filter((s) => known.has(s));
|
|
221
221
|
return out.length > 0 ? out : [...CORE_WORKER_MODES];
|
|
222
222
|
}
|
|
223
|
+
Object.freeze({
|
|
224
|
+
"gather-context-explore": Object.freeze({
|
|
225
|
+
dispatcher: "explore",
|
|
226
|
+
modelAlias: "gh-router-skill-gather-context-explore-high",
|
|
227
|
+
timeoutMs: 18e4,
|
|
228
|
+
worktree: false
|
|
229
|
+
}),
|
|
230
|
+
"implement-task": Object.freeze({
|
|
231
|
+
dispatcher: "implement",
|
|
232
|
+
modelAlias: "gh-router-skill-implement-task-max",
|
|
233
|
+
timeoutMs: 6e5,
|
|
234
|
+
worktree: true
|
|
235
|
+
}),
|
|
236
|
+
"review-pass1": Object.freeze({
|
|
237
|
+
dispatcher: "review",
|
|
238
|
+
modelAlias: "gh-router-skill-review-pass1-max",
|
|
239
|
+
timeoutMs: 3e5,
|
|
240
|
+
worktree: false
|
|
241
|
+
}),
|
|
242
|
+
"review-pass2": Object.freeze({
|
|
243
|
+
dispatcher: "review",
|
|
244
|
+
modelAlias: "gh-router-skill-review-pass2-medium",
|
|
245
|
+
timeoutMs: 3e5,
|
|
246
|
+
worktree: false
|
|
247
|
+
})
|
|
248
|
+
});
|
|
223
249
|
//#endregion
|
|
224
250
|
export { dispatcherAgentName as a, dispatcherTools as c, decideWorkerGuard as i, guardToolMatcher as l, activeDispatchModes as n, dispatcherDescription as o, buildWorkerGuardHookCommand as r, dispatcherPrompt as s, ALL_WORKER_DISPATCH_MODES as t, parseModesCsv as u };
|
|
225
251
|
|
|
226
|
-
//# sourceMappingURL=worker-dispatch-
|
|
252
|
+
//# sourceMappingURL=worker-dispatch-CcQygiAA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-dispatch-DwS99DXz.js","names":[],"sources":["../src/lib/worker-dispatch.ts"],"sourcesContent":["/**\n * Frozen contract for the NON-BLOCKING workers surface.\n *\n * The `workers` MCP tools (`explore`/`implement`/`review`/`plan`/`test`, and\n * `browse` when the browse agent is enabled) BLOCK the caller for up to 6h\n * (`runWorkerAgent`). The MAIN Claude Code agent must never block on one, so a\n * per-mode `worker-*` DISPATCHER SUBAGENT — which Claude Code runs in the\n * background and reports on via a completion notification — is the only\n * sanctioned way to run a worker. This module is the single source of truth for\n * three things that must never drift:\n *\n * 1. the tool → dispatcher map (`mcp__<workersKey>__<mode>` ↔ `worker-<mode>`),\n * 2. the PreToolUse GUARD decision that denies a raw worker call from the main\n * agent (or any non-dispatcher subagent) and redirects it to the matching\n * `worker-*` agent, allowing it only from the dispatcher itself, and\n * 3. the dispatcher subagent bodies (description / system prompt / `tools:`\n * allowlist).\n *\n * Imported by `codex-mcp-config.ts` (dispatcher `.md` generation + the hook\n * command it bakes into settings.json) AND `internal-worker-guard.ts` (the\n * runtime PreToolUse hook). Keeping the map here means the settings matcher, the\n * redirect target, the dispatcher names, and the `tools:` allowlist are all\n * derived from one place.\n *\n * Discrimination mechanism: a Claude Code PreToolUse payload carries\n * `agent_type` (the invoking subagent's name) ONLY inside a subagent context —\n * absent for the top-level/main agent. So `agent_type === \"worker-<mode>\"` is\n * the reliable \"this call came from the dispatcher, allow it\" signal, and its\n * absence (or any other value) means \"main agent or an unrelated subagent →\n * deny\". This is the SAME field this repo's Stop / prompt-submit hooks already\n * key off (`isSubagentContext` in `orchestration/stop-gate-policy.ts`); the\n * guard here is the inverse (allow-only-dispatcher rather than skip-any-sub).\n */\n\nimport { buildSelfCommand, type SelfInvocation } from \"./hook-launcher/self-invocation\"\n\n/** The five always-available worker modes (gated by `workerToolsEnabled()`). */\nexport const CORE_WORKER_MODES = [\n \"explore\",\n \"implement\",\n \"review\",\n \"plan\",\n \"test\",\n] as const\n\n/** The browse worker mode, gated separately by `browseAgentEnabled()`. */\nexport const BROWSE_WORKER_MODE = \"browse\" as const\n\nexport type CoreWorkerMode = (typeof CORE_WORKER_MODES)[number]\nexport type WorkerDispatchMode = CoreWorkerMode | typeof BROWSE_WORKER_MODE\n\n/** Every mode the surface can expose — used to build the sweep-regex allowlist\n * and to validate `--modes`. Order is stable (core first, browse last). */\nexport const ALL_WORKER_DISPATCH_MODES: ReadonlyArray<WorkerDispatchMode> = [\n ...CORE_WORKER_MODES,\n BROWSE_WORKER_MODE,\n]\n\n/** The dispatcher subagent name for a mode, e.g. `implement` → `worker-implement`. */\nexport function dispatcherAgentName(mode: WorkerDispatchMode): string {\n return `worker-${mode}`\n}\n\n/** Every possible dispatcher subagent name (used by the stale-`.md` sweep). */\nexport const ALL_DISPATCHER_AGENT_NAMES: ReadonlyArray<string> =\n ALL_WORKER_DISPATCH_MODES.map(dispatcherAgentName)\n\n/** The MCP tool name the workers server exposes for a mode under the resolved key. */\nexport function workerToolName(workersKey: string, mode: WorkerDispatchMode): string {\n return `mcp__${workersKey}__${mode}`\n}\n\n/** The active dispatch modes for a launch: the five core modes plus `browse`\n * only when the browse agent is enabled. */\nexport function activeDispatchModes(opts: { browse: boolean }): Array<WorkerDispatchMode> {\n return opts.browse ? [...CORE_WORKER_MODES, BROWSE_WORKER_MODE] : [...CORE_WORKER_MODES]\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n\n/**\n * The Claude Code PreToolUse `matcher` (a regex over the tool name) that scopes\n * the guard hook to exactly the active worker tools — nothing else invokes the\n * hook. Anchored + exact-alternation so an unrelated `mcp__<key>__status` (a\n * future non-blocking tool with no dispatcher) is never matched, hence never\n * denied.\n */\nexport function guardToolMatcher(workersKey: string, modes: ReadonlyArray<WorkerDispatchMode>): string {\n const alt = modes.map((m) => escapeRegex(m)).join(\"|\")\n return `^mcp__${escapeRegex(workersKey)}__(${alt})$`\n}\n\n/** Parse the worker mode out of a tool name for the resolved key, or null if it\n * isn't one of the recognized worker tools. */\nexport function parseWorkerToolCall(\n toolName: string,\n workersKey: string,\n modes: ReadonlyArray<WorkerDispatchMode>,\n): WorkerDispatchMode | null {\n const prefix = `mcp__${workersKey}__`\n if (!toolName.startsWith(prefix)) return null\n const rest = toolName.slice(prefix.length)\n return (modes as ReadonlyArray<string>).includes(rest) ? (rest as WorkerDispatchMode) : null\n}\n\n/** The `permissionDecisionReason` shown to the model on a deny, steering it to\n * the matching background dispatcher. `mode` is null when the payload was\n * unparseable (fail-closed generic message). */\nexport function guardDenyReason(mode: WorkerDispatchMode | null): string {\n const target = mode\n ? `Agent(subagent_type: \"${dispatcherAgentName(mode)}\", prompt: <your worker brief>)`\n : `the matching background \\`worker-*\\` agent via the Agent tool`\n return (\n \"Workers run as background subagents in this session so your turn never blocks. \"\n + `Re-issue this as ${target}. `\n + \"It returns immediately and delivers the worker's result as a completion notification — \"\n + \"do not call the raw `mcp__…__` worker tool from the main thread.\"\n )\n}\n\n/** The stdout JSON a PreToolUse hook prints to DENY a tool call. */\nexport function guardDenyOutput(mode: WorkerDispatchMode | null): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: guardDenyReason(mode),\n },\n })\n}\n\nexport interface WorkerGuardResult {\n /** JSON to print to stdout (a deny decision), or null to ALLOW (print nothing). */\n output: string | null\n /** Machine-readable verdict for tests/telemetry. */\n verdict: \"allow-dispatcher\" | \"allow-non-worker\" | \"deny-main\" | \"deny-malformed\"\n}\n\n/**\n * Pure PreToolUse guard decision. Given the raw stdin payload, the resolved\n * workers key, and the active modes, decide whether to DENY the tool call.\n *\n * Rules (fail toward protecting the \"main never blocks\" invariant):\n * - Payload unparseable / no string `tool_name`: the matcher only fires this\n * hook for worker tools, so a payload we can't read is still a worker call\n * → DENY (fail closed, generic redirect).\n * - `tool_name` is not a recognized worker tool for this key: ALLOW (not a\n * tool we guard — never deny a non-worker tool).\n * - `agent_type` equals one of the active dispatcher names: ALLOW (the call\n * came from the dispatcher subagent that is meant to run the worker).\n * - Otherwise (main agent: `agent_type` absent; or a non-dispatcher subagent):\n * DENY with a redirect to the matching `worker-<mode>` agent.\n */\nexport function decideWorkerGuard(input: {\n stdin: string\n workersKey: string\n modes: ReadonlyArray<WorkerDispatchMode>\n}): WorkerGuardResult {\n let payload: { tool_name?: unknown; agent_type?: unknown } | null = null\n try {\n const parsed: unknown = JSON.parse(input.stdin)\n if (parsed && typeof parsed === \"object\") {\n payload = parsed as { tool_name?: unknown; agent_type?: unknown }\n }\n } catch {\n payload = null\n }\n\n const toolName = typeof payload?.tool_name === \"string\" ? payload.tool_name : null\n if (!toolName) {\n // Fail CLOSED: the settings matcher only routes worker-tool calls here, so\n // an unreadable payload is still a worker call we must not let through.\n return { output: guardDenyOutput(null), verdict: \"deny-malformed\" }\n }\n\n const mode = parseWorkerToolCall(toolName, input.workersKey, input.modes)\n if (mode === null) {\n // Not one of the worker tools we guard (matcher over-fired, or a future\n // non-blocking tool under the same server) → allow the normal flow.\n return { output: null, verdict: \"allow-non-worker\" }\n }\n\n const agentType = payload?.agent_type\n if (typeof agentType === \"string\" && agentType === dispatcherAgentName(mode)) {\n // The call originated inside the `worker-<mode>` dispatcher subagent that is\n // meant to run THIS worker. Require an exact mode match (not just \"any\n // dispatcher\"), so a read-only `worker-explore` can't invoke the\n // write-capable `implement` worker if it misroutes or is prompt-injected.\n // If a matching dispatcher is still denied, the payload that reached this\n // hook did not carry this exact top-level `agent_type`, or a different\n // PreToolUse hook denied the call after this guard allowed it.\n return { output: null, verdict: \"allow-dispatcher\" }\n }\n\n // Main agent (agent_type absent), a non-dispatcher subagent, or a dispatcher\n // for a DIFFERENT mode → deny + redirect. Denying non-dispatcher subagents\n // too closes the transitive-blocking hole (a foreground subagent that blocked\n // on a worker would transitively block the main agent).\n return { output: guardDenyOutput(mode), verdict: \"deny-main\" }\n}\n\n// ─── Dispatcher subagent bodies ──────────────────────────────────────────────\n\n/** One-line human description shown to the lead when picking a subagent. Uses\n * the documented \"Use proactively\" auto-delegation idiom. */\nexport function dispatcherDescription(mode: WorkerDispatchMode): string {\n const blurb: Record<WorkerDispatchMode, string> = {\n explore:\n \"Non-blocking `explore` worker: dispatches a read-only autonomous worker (its own context) in the background and delivers its summary as a completion notification.\",\n implement:\n \"Non-blocking `implement` worker: dispatches an autonomous coding worker (read/write/bash) that ALWAYS runs in an isolated git worktree and returns the diff (for in-place edits use the `implementer` subagent), in the background, and delivers its result as a completion notification.\",\n review:\n \"Non-blocking `review` worker: the backgrounded counterpart of the `reviewer` subagent, running a cross-lab model. It has explore's read-and-search tools plus `bash`, so it can VERIFY rather than only read: reproduce a failure, or run the build or suite to check a claim. By default it works directly in the workspace, where it gets no edit/write tools but `bash` still runs real commands, so a build or test it invokes can touch the tree. Pass `worktree: true` to run it isolated in a replayed git worktree instead, which also lets it author a throwaway probe test. Returns severity-ranked findings with file:line and a go/no-go, as a completion notification. Use `explore` instead when you just need to find or understand something.\",\n plan:\n \"Non-blocking `plan` worker: dispatches a read-only planner that returns an ordered implementation plan, in the background, and delivers it as a completion notification.\",\n test:\n \"Non-blocking `test` worker: dispatches an independent test author (in an isolated git worktree) that writes tests trying to break the implementation, in the background, and delivers pass/fail as a completion notification.\",\n browse:\n \"Non-blocking `browse` worker: dispatches an autonomous browser agent in the background and delivers its result as a completion notification.\",\n }\n return (\n `${blurb[mode]} Use proactively for any ${mode}-mode worker task so a long run never blocks your turn: `\n + \"it returns immediately and notifies you when done.\"\n )\n}\n\n/** The dispatcher subagent's full system prompt: call the one worker tool once,\n * relay verbatim, do nothing else. */\nexport function dispatcherPrompt(mode: WorkerDispatchMode, workersKey: string): string {\n const tool = workerToolName(workersKey, mode)\n const briefField = mode === \"browse\" ? \"task\" : \"prompt\"\n const briefDescription = mode === \"browse\" ? \"the lead's browse task, copied verbatim\" : \"the lead's worker brief, copied verbatim\"\n const modeSpecificPassThrough = mode === \"browse\"\n ? \"\\n - `sessionId` (optional): pass through if the lead specified one\"\n : mode === \"review\"\n ? \"\\n - `worktree` (optional): pass `true` only if the lead asked for an isolated review\"\n : \"\"\n return [\n `# Subagent: ${dispatcherAgentName(mode)}`,\n \"\",\n `You are a thin DISPATCHER for the \\`${mode}\\` worker. You run in the background so the`,\n \"lead agent's turn is never blocked while the (up-to-6-hour) worker runs.\",\n \"\",\n \"## Your only job\",\n \"\",\n `Call the \\`${tool}\\` tool EXACTLY ONCE, passing through the fields from the lead's brief:`,\n ` - \\`${briefField}\\`: ${briefDescription}`,\n \" - `workspace`: ALWAYS pass this. Use the absolute path of the directory the\",\n \" lead named, or, when the lead named none, your own current working\",\n \" directory. The proxy runs as a separate long-lived process and cannot see\",\n \" where you are, so if you omit this it falls back to the session's directory,\",\n \" which may be a different checkout than the files the lead is asking about.\",\n \" - `model` / `thinking` (optional): only if the lead specified them\",\n \" - `maxWallClockMs` (optional): per-call wall-clock budget in ms, if the lead specified one\"\n + modeSpecificPassThrough,\n \"\",\n \"When the tool returns, output its result VERBATIM as your final message. That final\",\n \"message is what the lead receives in the completion notification — it IS the result.\",\n \"\",\n \"## Hard rules\",\n \"\",\n \"- Call the worker tool exactly once. Do not retry on a normal (non-error) return.\",\n \"- Do NOT attempt the task yourself, do NOT read/edit files, do NOT run other tools.\",\n \"- Do NOT spawn other agents (you have no Agent tool and must not try to gain one).\",\n \"- Do NOT summarize, paraphrase, or add commentary — relay the worker output verbatim.\",\n \"- If the worker returns an error, relay that error verbatim (do not mask it).\",\n ].join(\"\\n\")\n}\n\n/** The `tools:` frontmatter allowlist for a dispatcher: the workers MCP server\n * wildcard (`mcp__<workersKey>__*`). Claude Code's `tools:` field supports\n * MCP patterns only at SERVER granularity (`mcp__<server>__*`), not individual\n * tool names, so this grants exactly the workers tools and NOTHING else — no\n * Agent/Task (so it cannot spawn further agents → no recursion), no Read/Bash\n * (so it cannot do extra work). The dispatcher's prompt narrows it to the one\n * mode; the guard allows only the exact dispatcher for that worker mode. */\nexport function dispatcherTools(_mode: WorkerDispatchMode, workersKey: string): Array<string> {\n return [`mcp__${workersKey}__*`]\n}\n\n/**\n * Build the shell command Claude Code runs for the workers `PreToolUse` guard\n * hook — the running github-router via its node/bun binary so it works\n * regardless of PATH. Mirrors `buildPromptSubmitHookCommand`.\n *\n * The resolved `workersKey` and the active `modes` are baked into the command\n * ARGS (not env): `mergeStopHookIntoSettings` dedups hooks by the command\n * string only, so baking the key/modes makes a changed resolution produce a\n * DISTINCT command — no stale-matcher entry can survive from a prior launch.\n */\nexport function buildWorkerGuardHookCommand(\n invocation: SelfInvocation,\n workersKey: string,\n modes: ReadonlyArray<WorkerDispatchMode>,\n): string {\n const q = (s: string): string => `\"${s}\"`\n const args = `internal-worker-guard --workers-key ${q(workersKey)} --modes ${q(modes.join(\",\"))}`\n return buildSelfCommand(invocation, args)\n}\n\n/** Parse a `--modes` CSV back into validated modes (drops unknown tokens). */\nexport function parseModesCsv(csv: string | undefined): Array<WorkerDispatchMode> {\n if (!csv) return [...CORE_WORKER_MODES]\n const known = new Set<string>(ALL_WORKER_DISPATCH_MODES)\n const out = csv\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => known.has(s)) as Array<WorkerDispatchMode>\n return out.length > 0 ? out : [...CORE_WORKER_MODES]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker-dispatch-CcQygiAA.js","names":[],"sources":["../src/lib/worker-dispatch.ts"],"sourcesContent":["/**\n * Frozen contract for the NON-BLOCKING workers surface.\n *\n * The `workers` MCP tools (`explore`/`implement`/`review`/`plan`/`test`, and\n * `browse` when the browse agent is enabled) BLOCK the caller for up to 6h\n * (`runWorkerAgent`). The MAIN Claude Code agent must never block on one, so a\n * per-mode `worker-*` DISPATCHER SUBAGENT — which Claude Code runs in the\n * background and reports on via a completion notification — is the only\n * sanctioned way to run a worker. This module is the single source of truth for\n * three things that must never drift:\n *\n * 1. the tool → dispatcher map (`mcp__<workersKey>__<mode>` ↔ `worker-<mode>`),\n * 2. the PreToolUse GUARD decision that denies a raw worker call from the main\n * agent (or any non-dispatcher subagent) and redirects it to the matching\n * `worker-*` agent, allowing it only from the dispatcher itself, and\n * 3. the dispatcher subagent bodies (description / system prompt / `tools:`\n * allowlist).\n *\n * Imported by `codex-mcp-config.ts` (dispatcher `.md` generation + the hook\n * command it bakes into settings.json) AND `internal-worker-guard.ts` (the\n * runtime PreToolUse hook). Keeping the map here means the settings matcher, the\n * redirect target, the dispatcher names, and the `tools:` allowlist are all\n * derived from one place.\n *\n * Discrimination mechanism: a Claude Code PreToolUse payload carries\n * `agent_type` (the invoking subagent's name) ONLY inside a subagent context —\n * absent for the top-level/main agent. So `agent_type === \"worker-<mode>\"` is\n * the reliable \"this call came from the dispatcher, allow it\" signal, and its\n * absence (or any other value) means \"main agent or an unrelated subagent →\n * deny\". This is the SAME field this repo's Stop / prompt-submit hooks already\n * key off (`isSubagentContext` in `orchestration/stop-gate-policy.ts`); the\n * guard here is the inverse (allow-only-dispatcher rather than skip-any-sub).\n */\n\nimport { buildSelfCommand, type SelfInvocation } from \"./hook-launcher/self-invocation\"\n\n/** The five always-available worker modes (gated by `workerToolsEnabled()`). */\nexport const CORE_WORKER_MODES = [\n \"explore\",\n \"implement\",\n \"review\",\n \"plan\",\n \"test\",\n] as const\n\n/** The browse worker mode, gated separately by `browseAgentEnabled()`. */\nexport const BROWSE_WORKER_MODE = \"browse\" as const\n\nexport type CoreWorkerMode = (typeof CORE_WORKER_MODES)[number]\nexport type WorkerDispatchMode = CoreWorkerMode | typeof BROWSE_WORKER_MODE\n\n/** Every mode the surface can expose — used to build the sweep-regex allowlist\n * and to validate `--modes`. Order is stable (core first, browse last). */\nexport const ALL_WORKER_DISPATCH_MODES: ReadonlyArray<WorkerDispatchMode> = [\n ...CORE_WORKER_MODES,\n BROWSE_WORKER_MODE,\n]\n\n/** The dispatcher subagent name for a mode, e.g. `implement` → `worker-implement`. */\nexport function dispatcherAgentName(mode: WorkerDispatchMode): string {\n return `worker-${mode}`\n}\n\n/** Every possible dispatcher subagent name (used by the stale-`.md` sweep). */\nexport const ALL_DISPATCHER_AGENT_NAMES: ReadonlyArray<string> =\n ALL_WORKER_DISPATCH_MODES.map(dispatcherAgentName)\n\n/** The MCP tool name the workers server exposes for a mode under the resolved key. */\nexport function workerToolName(workersKey: string, mode: WorkerDispatchMode): string {\n return `mcp__${workersKey}__${mode}`\n}\n\n/** The active dispatch modes for a launch: the five core modes plus `browse`\n * only when the browse agent is enabled. */\nexport function activeDispatchModes(opts: { browse: boolean }): Array<WorkerDispatchMode> {\n return opts.browse ? [...CORE_WORKER_MODES, BROWSE_WORKER_MODE] : [...CORE_WORKER_MODES]\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n\n/**\n * The Claude Code PreToolUse `matcher` (a regex over the tool name) that scopes\n * the guard hook to exactly the active worker tools — nothing else invokes the\n * hook. Anchored + exact-alternation so an unrelated `mcp__<key>__status` (a\n * future non-blocking tool with no dispatcher) is never matched, hence never\n * denied.\n */\nexport function guardToolMatcher(workersKey: string, modes: ReadonlyArray<WorkerDispatchMode>): string {\n const alt = modes.map((m) => escapeRegex(m)).join(\"|\")\n return `^mcp__${escapeRegex(workersKey)}__(${alt})$`\n}\n\n/** Parse the worker mode out of a tool name for the resolved key, or null if it\n * isn't one of the recognized worker tools. */\nexport function parseWorkerToolCall(\n toolName: string,\n workersKey: string,\n modes: ReadonlyArray<WorkerDispatchMode>,\n): WorkerDispatchMode | null {\n const prefix = `mcp__${workersKey}__`\n if (!toolName.startsWith(prefix)) return null\n const rest = toolName.slice(prefix.length)\n return (modes as ReadonlyArray<string>).includes(rest) ? (rest as WorkerDispatchMode) : null\n}\n\n/** The `permissionDecisionReason` shown to the model on a deny, steering it to\n * the matching background dispatcher. `mode` is null when the payload was\n * unparseable (fail-closed generic message). */\nexport function guardDenyReason(mode: WorkerDispatchMode | null): string {\n const target = mode\n ? `Agent(subagent_type: \"${dispatcherAgentName(mode)}\", prompt: <your worker brief>)`\n : `the matching background \\`worker-*\\` agent via the Agent tool`\n return (\n \"Workers run as background subagents in this session so your turn never blocks. \"\n + `Re-issue this as ${target}. `\n + \"It returns immediately and delivers the worker's result as a completion notification — \"\n + \"do not call the raw `mcp__…__` worker tool from the main thread.\"\n )\n}\n\n/** The stdout JSON a PreToolUse hook prints to DENY a tool call. */\nexport function guardDenyOutput(mode: WorkerDispatchMode | null): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: guardDenyReason(mode),\n },\n })\n}\n\nexport interface WorkerGuardResult {\n /** JSON to print to stdout (a deny decision), or null to ALLOW (print nothing). */\n output: string | null\n /** Machine-readable verdict for tests/telemetry. */\n verdict: \"allow-dispatcher\" | \"allow-non-worker\" | \"deny-main\" | \"deny-malformed\"\n}\n\n/**\n * Pure PreToolUse guard decision. Given the raw stdin payload, the resolved\n * workers key, and the active modes, decide whether to DENY the tool call.\n *\n * Rules (fail toward protecting the \"main never blocks\" invariant):\n * - Payload unparseable / no string `tool_name`: the matcher only fires this\n * hook for worker tools, so a payload we can't read is still a worker call\n * → DENY (fail closed, generic redirect).\n * - `tool_name` is not a recognized worker tool for this key: ALLOW (not a\n * tool we guard — never deny a non-worker tool).\n * - `agent_type` equals one of the active dispatcher names: ALLOW (the call\n * came from the dispatcher subagent that is meant to run the worker).\n * - Otherwise (main agent: `agent_type` absent; or a non-dispatcher subagent):\n * DENY with a redirect to the matching `worker-<mode>` agent.\n */\nexport function decideWorkerGuard(input: {\n stdin: string\n workersKey: string\n modes: ReadonlyArray<WorkerDispatchMode>\n}): WorkerGuardResult {\n let payload: { tool_name?: unknown; agent_type?: unknown } | null = null\n try {\n const parsed: unknown = JSON.parse(input.stdin)\n if (parsed && typeof parsed === \"object\") {\n payload = parsed as { tool_name?: unknown; agent_type?: unknown }\n }\n } catch {\n payload = null\n }\n\n const toolName = typeof payload?.tool_name === \"string\" ? payload.tool_name : null\n if (!toolName) {\n // Fail CLOSED: the settings matcher only routes worker-tool calls here, so\n // an unreadable payload is still a worker call we must not let through.\n return { output: guardDenyOutput(null), verdict: \"deny-malformed\" }\n }\n\n const mode = parseWorkerToolCall(toolName, input.workersKey, input.modes)\n if (mode === null) {\n // Not one of the worker tools we guard (matcher over-fired, or a future\n // non-blocking tool under the same server) → allow the normal flow.\n return { output: null, verdict: \"allow-non-worker\" }\n }\n\n const agentType = payload?.agent_type\n if (typeof agentType === \"string\" && agentType === dispatcherAgentName(mode)) {\n // The call originated inside the `worker-<mode>` dispatcher subagent that is\n // meant to run THIS worker. Require an exact mode match (not just \"any\n // dispatcher\"), so a read-only `worker-explore` can't invoke the\n // write-capable `implement` worker if it misroutes or is prompt-injected.\n // If a matching dispatcher is still denied, the payload that reached this\n // hook did not carry this exact top-level `agent_type`, or a different\n // PreToolUse hook denied the call after this guard allowed it.\n return { output: null, verdict: \"allow-dispatcher\" }\n }\n\n // Main agent (agent_type absent), a non-dispatcher subagent, or a dispatcher\n // for a DIFFERENT mode → deny + redirect. Denying non-dispatcher subagents\n // too closes the transitive-blocking hole (a foreground subagent that blocked\n // on a worker would transitively block the main agent).\n return { output: guardDenyOutput(mode), verdict: \"deny-main\" }\n}\n\n// ─── Dispatcher subagent bodies ──────────────────────────────────────────────\n\n/** One-line human description shown to the lead when picking a subagent. Uses\n * the documented \"Use proactively\" auto-delegation idiom. */\nexport function dispatcherDescription(mode: WorkerDispatchMode): string {\n const blurb: Record<WorkerDispatchMode, string> = {\n explore:\n \"Non-blocking `explore` worker: dispatches a read-only autonomous worker (its own context) in the background and delivers its summary as a completion notification.\",\n implement:\n \"Non-blocking `implement` worker: dispatches an autonomous coding worker (read/write/bash) that ALWAYS runs in an isolated git worktree and returns the diff (for in-place edits use the `implementer` subagent), in the background, and delivers its result as a completion notification.\",\n review:\n \"Non-blocking `review` worker: the backgrounded counterpart of the `reviewer` subagent, running a cross-lab model. It has explore's read-and-search tools plus `bash`, so it can VERIFY rather than only read: reproduce a failure, or run the build or suite to check a claim. By default it works directly in the workspace, where it gets no edit/write tools but `bash` still runs real commands, so a build or test it invokes can touch the tree. Pass `worktree: true` to run it isolated in a replayed git worktree instead, which also lets it author a throwaway probe test. Returns severity-ranked findings with file:line and a go/no-go, as a completion notification. Use `explore` instead when you just need to find or understand something.\",\n plan:\n \"Non-blocking `plan` worker: dispatches a read-only planner that returns an ordered implementation plan, in the background, and delivers it as a completion notification.\",\n test:\n \"Non-blocking `test` worker: dispatches an independent test author (in an isolated git worktree) that writes tests trying to break the implementation, in the background, and delivers pass/fail as a completion notification.\",\n browse:\n \"Non-blocking `browse` worker: dispatches an autonomous browser agent in the background and delivers its result as a completion notification.\",\n }\n return (\n `${blurb[mode]} Use proactively for any ${mode}-mode worker task so a long run never blocks your turn: `\n + \"it returns immediately and notifies you when done.\"\n )\n}\n\n/** The dispatcher subagent's full system prompt: call the one worker tool once,\n * relay verbatim, do nothing else. */\nexport function dispatcherPrompt(mode: WorkerDispatchMode, workersKey: string): string {\n const tool = workerToolName(workersKey, mode)\n const briefField = mode === \"browse\" ? \"task\" : \"prompt\"\n const briefDescription = mode === \"browse\" ? \"the lead's browse task, copied verbatim\" : \"the lead's worker brief, copied verbatim\"\n const modeSpecificPassThrough = mode === \"browse\"\n ? \"\\n - `sessionId` (optional): pass through if the lead specified one\"\n : mode === \"review\"\n ? \"\\n - `worktree` (optional): pass `true` only if the lead asked for an isolated review\"\n : \"\"\n return [\n `# Subagent: ${dispatcherAgentName(mode)}`,\n \"\",\n `You are a thin DISPATCHER for the \\`${mode}\\` worker. You run in the background so the`,\n \"lead agent's turn is never blocked while the (up-to-6-hour) worker runs.\",\n \"\",\n \"## Your only job\",\n \"\",\n `Call the \\`${tool}\\` tool EXACTLY ONCE, passing through the fields from the lead's brief:`,\n ` - \\`${briefField}\\`: ${briefDescription}`,\n \" - `workspace`: ALWAYS pass this. Use the absolute path of the directory the\",\n \" lead named, or, when the lead named none, your own current working\",\n \" directory. The proxy runs as a separate long-lived process and cannot see\",\n \" where you are, so if you omit this it falls back to the session's directory,\",\n \" which may be a different checkout than the files the lead is asking about.\",\n \" - `model` / `thinking` (optional): only if the lead specified them\",\n \" - `maxWallClockMs` (optional): per-call wall-clock budget in ms, if the lead specified one\"\n + modeSpecificPassThrough,\n \"\",\n \"When the tool returns, output its result VERBATIM as your final message. That final\",\n \"message is what the lead receives in the completion notification — it IS the result.\",\n \"\",\n \"## Hard rules\",\n \"\",\n \"- Call the worker tool exactly once. Do not retry on a normal (non-error) return.\",\n \"- Do NOT attempt the task yourself, do NOT read/edit files, do NOT run other tools.\",\n \"- Do NOT spawn other agents (you have no Agent tool and must not try to gain one).\",\n \"- Do NOT summarize, paraphrase, or add commentary — relay the worker output verbatim.\",\n \"- If the worker returns an error, relay that error verbatim (do not mask it).\",\n ].join(\"\\n\")\n}\n\n/** The `tools:` frontmatter allowlist for a dispatcher: the workers MCP server\n * wildcard (`mcp__<workersKey>__*`). Claude Code's `tools:` field supports\n * MCP patterns only at SERVER granularity (`mcp__<server>__*`), not individual\n * tool names, so this grants exactly the workers tools and NOTHING else — no\n * Agent/Task (so it cannot spawn further agents → no recursion), no Read/Bash\n * (so it cannot do extra work). The dispatcher's prompt narrows it to the one\n * mode; the guard allows only the exact dispatcher for that worker mode. */\nexport function dispatcherTools(_mode: WorkerDispatchMode, workersKey: string): Array<string> {\n return [`mcp__${workersKey}__*`]\n}\n\n/**\n * Build the shell command Claude Code runs for the workers `PreToolUse` guard\n * hook — the running github-router via its node/bun binary so it works\n * regardless of PATH. Mirrors `buildPromptSubmitHookCommand`.\n *\n * The resolved `workersKey` and the active `modes` are baked into the command\n * ARGS (not env): `mergeStopHookIntoSettings` dedups hooks by the command\n * string only, so baking the key/modes makes a changed resolution produce a\n * DISTINCT command — no stale-matcher entry can survive from a prior launch.\n */\nexport function buildWorkerGuardHookCommand(\n invocation: SelfInvocation,\n workersKey: string,\n modes: ReadonlyArray<WorkerDispatchMode>,\n): string {\n const q = (s: string): string => `\"${s}\"`\n const args = `internal-worker-guard --workers-key ${q(workersKey)} --modes ${q(modes.join(\",\"))}`\n return buildSelfCommand(invocation, args)\n}\n\n/** Parse a `--modes` CSV back into validated modes (drops unknown tokens). */\nexport function parseModesCsv(csv: string | undefined): Array<WorkerDispatchMode> {\n if (!csv) return [...CORE_WORKER_MODES]\n const known = new Set<string>(ALL_WORKER_DISPATCH_MODES)\n const out = csv\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => known.has(s)) as Array<WorkerDispatchMode>\n return out.length > 0 ? out : [...CORE_WORKER_MODES]\n}\n\n// ─── Pipeline skill worker configs (additive, non-guard) ─────────────────────\n\n/**\n * Bounded worker configs for the `/gh-gather-context`, `/gh-plan`, and\n * `/gh-implement` pipeline skills. These reuse the existing\n * `worker-explore` / `worker-implement` / `worker-review` / `worker-plan`\n * dispatchers (so the PreToolUse guard above is untouched); the configs only\n * pin the 200K-default model alias, the wall-clock budget, and the worktree\n * policy each skill brief must use. All models are bare slugs (no `[1m]`).\n *\n * No output-token cap is listed: worker tools accept `{prompt, model,\n * thinking, workspace, maxWallClockMs, worktree}` and have no output-token\n * parameter, so such a cap would be unenforced. `timeoutMs` IS enforceable:\n * the skill bodies instruct the lead to pass it as the real `maxWallClockMs`\n * worker arg (honored by `worker-agent/budget.ts`, clamped to the per-call\n * ceiling in `peer-mcp-personas.ts`).\n */\nexport type SkillWorkerRole =\n | \"gather-context-explore\"\n | \"implement-task\"\n | \"review-pass1\"\n | \"review-pass2\"\n\nexport interface SkillWorkerConfig {\n /** Existing dispatcher to reuse (guard-compatible, no new modes). */\n dispatcher: WorkerDispatchMode\n /** Router-owned model alias (200K default, effort pinned). */\n modelAlias: string\n /**\n * Wall-clock budget per worker call, passed by the lead as the real\n * `maxWallClockMs` worker arg (well under the 6h per-call ceiling).\n */\n timeoutMs: number\n /** Whether the worker runs in an isolated git worktree. */\n worktree: boolean\n}\n\nexport const SKILL_WORKER_CONFIGS: Readonly<Record<SkillWorkerRole, SkillWorkerConfig>> = Object.freeze({\n \"gather-context-explore\": Object.freeze({\n dispatcher: \"explore\",\n modelAlias: \"gh-router-skill-gather-context-explore-high\",\n timeoutMs: 180_000,\n worktree: false,\n }),\n \"implement-task\": Object.freeze({\n dispatcher: \"implement\",\n modelAlias: \"gh-router-skill-implement-task-max\",\n timeoutMs: 600_000,\n worktree: true,\n }),\n \"review-pass1\": Object.freeze({\n dispatcher: \"review\",\n modelAlias: \"gh-router-skill-review-pass1-max\",\n timeoutMs: 300_000,\n worktree: false,\n }),\n \"review-pass2\": Object.freeze({\n dispatcher: \"review\",\n modelAlias: \"gh-router-skill-review-pass2-medium\",\n timeoutMs: 300_000,\n worktree: false,\n }),\n})\n\nexport function skillWorkerConfig(role: SkillWorkerRole): SkillWorkerConfig {\n return SKILL_WORKER_CONFIGS[role]\n}\n"],"mappings":"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"}
|