github-router 0.3.315 → 0.3.316

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.
Files changed (49) hide show
  1. package/dist/{attribution-settings-DXmmyuEE.js → attribution-settings-jcsSeVur.js} +116 -17
  2. package/dist/attribution-settings-jcsSeVur.js.map +1 -0
  3. package/dist/{auth-D8t41c5D.js → auth-JfxL9q7z.js} +3 -3
  4. package/dist/{auth-D8t41c5D.js.map → auth-JfxL9q7z.js.map} +1 -1
  5. package/dist/browser-ext/manifest.json +1 -1
  6. package/dist/{check-usage-BpBKOjGZ.js → check-usage-DXUEQzt_.js} +3 -3
  7. package/dist/{check-usage-BpBKOjGZ.js.map → check-usage-DXUEQzt_.js.map} +1 -1
  8. package/dist/{claude-Cmmu3-cL.js → claude-CJ4Nd2_3.js} +94 -29
  9. package/dist/claude-CJ4Nd2_3.js.map +1 -0
  10. package/dist/{codex-DqTKKoEx.js → codex-CwuLqNZF.js} +6 -6
  11. package/dist/codex-CwuLqNZF.js.map +1 -0
  12. package/dist/engine-DwrxceOS.js +2 -0
  13. package/dist/{gate-discovery-CPk6Z8yc.js → gate-discovery-z04ywOzB.js} +2 -2
  14. package/dist/{gate-discovery-CPk6Z8yc.js.map → gate-discovery-z04ywOzB.js.map} +1 -1
  15. package/dist/{get-copilot-usage-FSQE8szQ.js → get-copilot-usage-BMD_cJNT.js} +3 -3
  16. package/dist/{get-copilot-usage-FSQE8szQ.js.map → get-copilot-usage-BMD_cJNT.js.map} +1 -1
  17. package/dist/hooks.mjs +1 -0
  18. package/dist/hooks.sha256 +1 -1
  19. package/dist/{internal-max-dispatch-guard-l1NGYHtF.js → internal-max-dispatch-guard-BNqQ2wMc.js} +2 -2
  20. package/dist/{internal-max-dispatch-guard-l1NGYHtF.js.map → internal-max-dispatch-guard-BNqQ2wMc.js.map} +1 -1
  21. package/dist/{internal-max-dispatch-guard-BzKsY9ze.js → internal-max-dispatch-guard-Bz7a2Ntf.js} +1 -1
  22. package/dist/{internal-stop-hook-BKiKYeda.js → internal-stop-hook-UN1JHtjj.js} +2 -2
  23. package/dist/{internal-stop-hook-BKiKYeda.js.map → internal-stop-hook-UN1JHtjj.js.map} +1 -1
  24. package/dist/main.js +9 -9
  25. package/dist/{max-profile-contract-BcrMlrmo.js → max-profile-contract-Bl7I_EOC.js} +2 -2
  26. package/dist/{max-profile-contract-BcrMlrmo.js.map → max-profile-contract-Bl7I_EOC.js.map} +1 -1
  27. package/dist/{models-DgLeb-Dv.js → models-CVzyU5mJ.js} +2 -2
  28. package/dist/{models-DgLeb-Dv.js.map → models-CVzyU5mJ.js.map} +1 -1
  29. package/dist/{peer-mcp-personas-CYWJSxPK.js → peer-mcp-personas-CQ-0Piyr.js} +166 -45
  30. package/dist/peer-mcp-personas-CQ-0Piyr.js.map +1 -0
  31. package/dist/{provision-AwSjt-vR.js → provision-_R7ZMygj.js} +2 -2
  32. package/dist/{provision-AwSjt-vR.js.map → provision-_R7ZMygj.js.map} +1 -1
  33. package/dist/{serve-BG2O6Slz.js → serve-DU66K5kv.js} +7 -7
  34. package/dist/{serve-BG2O6Slz.js.map → serve-DU66K5kv.js.map} +1 -1
  35. package/dist/{server-setup-D7ABbEs1.js → server-setup-iLG8LrfL.js} +165 -27
  36. package/dist/server-setup-iLG8LrfL.js.map +1 -0
  37. package/dist/{start-Dpwpnq99.js → start-DQTcm0o_.js} +3 -3
  38. package/dist/{start-Dpwpnq99.js.map → start-DQTcm0o_.js.map} +1 -1
  39. package/dist/{state-B8adscCv.js → state-BlMpEFAt.js} +2 -1
  40. package/dist/{state-B8adscCv.js.map → state-BlMpEFAt.js.map} +1 -1
  41. package/dist/{token-ClzS23Y2.js → token-C1gthnbV.js} +2 -2
  42. package/dist/{token-ClzS23Y2.js.map → token-C1gthnbV.js.map} +1 -1
  43. package/package.json +1 -1
  44. package/dist/attribution-settings-DXmmyuEE.js.map +0 -1
  45. package/dist/claude-Cmmu3-cL.js.map +0 -1
  46. package/dist/codex-DqTKKoEx.js.map +0 -1
  47. package/dist/engine-b9XaKLp0.js +0 -2
  48. package/dist/peer-mcp-personas-CYWJSxPK.js.map +0 -1
  49. package/dist/server-setup-D7ABbEs1.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { A as TOOLBELT_TOOLS, D as toolbeltEnabled, Fn as withInstallLock, M as satisfiesMinVersion, O as toolbeltSkipSet, cn as collapsePathKeys, j as assetFor, k as vscodeRipgrepPath, nn as extractZipMember, tn as extractTarGzMember } from "./peer-mcp-personas-CYWJSxPK.js";
1
+ import { A as TOOLBELT_TOOLS, D as toolbeltEnabled, Gn as withInstallLock, M as satisfiesMinVersion, O as toolbeltSkipSet, an as extractTarGzMember, fn as collapsePathKeys, j as assetFor, k as vscodeRipgrepPath, on as extractZipMember } from "./peer-mcp-personas-CQ-0Piyr.js";
2
2
  import { t as PATHS } from "./paths-CMxlQEfQ.js";
3
3
  import { i as parseBoolEnv, n as killChildProcessTree, o as resolveExecutable, s as runCommandCapture } from "./exec-COUT4QsS.js";
4
4
  import { a as sweepRegistry } from "./lifecycle-IwotwedG.js";
@@ -682,4 +682,4 @@ async function exposedCommands(binDir) {
682
682
  //#endregion
683
683
  export { STRIPPED_AUTH_ROUTING_ENV_KEYS as a, launchChild as i, buildCodexProviderConfigFlags as n, getCodexVersion as r, provisionToolbelt as t };
684
684
 
685
- //# sourceMappingURL=provision-AwSjt-vR.js.map
685
+ //# sourceMappingURL=provision-_R7ZMygj.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"provision-AwSjt-vR.js","names":[],"sources":["../src/lib/process-guard/index.ts","../src/lib/stripped-env-keys.ts","../src/lib/launch.ts","../src/lib/toolbelt/provision.ts"],"sourcesContent":["/**\n * Crash-safe process guard (POSIX only).\n *\n * If the proxy dies WITHOUT running its own shutdown handlers (hard crash,\n * SIGKILL, OOM), nothing in `cleanup()` runs to reap the launched CLI's\n * process tree. This guard is the last-resort net for that case.\n *\n * Platform split:\n * - **Windows: no guard needed.** Node assigns each child to a Job\n * Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, so when the proxy\n * dies (any cause) the OS tears down the whole descendant tree —\n * verified: force-killing a Node parent kills child AND grandchild.\n * `startProcessGuard` is a no-op on win32.\n * - **POSIX: a detached `node -e` reaper.** There is no job-object\n * equivalent (a killed parent's children reparent to init), so we\n * spawn a detached watchdog that learns of proxy death via an inherited\n * stdin PIPE EOF (the proxy holds the write end; the OS closes it on\n * proxy death) and reaps the CLI's process group.\n *\n * Never kill the WRONG process: if the child's PID/PGID was recycled after\n * the child exited, a blind `kill(-pid)` could hit an innocent group. The\n * reaper snapshots the child's process start-time at startup and, on proxy\n * death, kills ONLY if the live PID's start-time still matches. A mismatch\n * / unreadable probe → skip (fail-safe). We never trade an orphan-leak for\n * killing the user's work.\n *\n * The reaper is detached + unref'd so it survives the proxy's SIGKILL, and\n * self-exits once it has acted (or after a 24h cap) so it can never become\n * the orphan it exists to prevent. Opt out with\n * `GH_ROUTER_DISABLE_PROCESS_GUARD=1`.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\"\nimport process from \"node:process\"\n\nimport { parseBoolEnv } from \"../exec\"\n\n/**\n * Live reaper children, held so the GC can't collect the `ChildProcess`\n * (and with it the stdin write-end that is the proxy-death signal) while\n * the proxy runs. Entries self-remove on the reaper's exit. We do NOT\n * proactively kill these on graceful shutdown: when the proxy exits the OS\n * closes the stdin pipe, the reaper hits EOF, re-verifies the child's\n * identity, and reaps any survivor that ignored the graceful SIGTERM —\n * exactly the backstop we want.\n */\nconst _activeReapers = new Set<ChildProcess>()\n\n/** Guard is on by default; `GH_ROUTER_DISABLE_PROCESS_GUARD=1` opts out. */\nexport function processGuardEnabled(): boolean {\n return parseBoolEnv(process.env.GH_ROUTER_DISABLE_PROCESS_GUARD) !== true\n}\n\n/**\n * Build the start-time-verified node reaper script (POSIX). PURE — `pid`\n * is our own integer. `detachedGroup` selects `kill(-pid)` (process group,\n * the detached CLI) vs `kill(pid)` on POSIX.\n *\n * Kept dependency-free (require'd builtins only) so it runs under `node -e`\n * with no module resolution against the dist bundle.\n */\nexport function buildNodeReaperScript(\n pid: number,\n detachedGroup: boolean,\n): string {\n const target = detachedGroup ? \"-PID\" : \"PID\"\n return `\nconst cp = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst PID = ${pid >>> 0};\nfunction startTime() {\n try {\n if (process.platform === \"linux\") {\n const stat = fs.readFileSync(\"/proc/\" + PID + \"/stat\", \"utf8\");\n const post = stat.slice(stat.lastIndexOf(\")\") + 1).trim().split(/\\\\s+/);\n return post[19] || null; // field 22 (starttime) = index 19 after comm\n }\n const out = cp.execFileSync(\"ps\", [\"-o\",\"lstart=\",\"-p\",String(PID)],\n { stdio: [\"ignore\",\"pipe\",\"ignore\"], timeout: 2000 }).toString().trim();\n return out || null;\n } catch { return null; }\n}\nfunction alive() { try { process.kill(PID, 0); return true; } catch { return false; } }\nfunction treeKill() {\n try { process.kill(${target}, \"SIGTERM\"); } catch {}\n // NOT unref'd: this must keep the loop alive to deliver the escalated\n // SIGKILL (the onDeath exit timer below stays alive past 500ms too).\n setTimeout(() => { try { process.kill(${target}, \"SIGKILL\"); } catch {} }, 500);\n}\nconst snap = (() => {\n // The child is definitely ours and alive at startup, so a null here is a\n // transient probe failure (e.g. a momentary 'ps' hiccup), NOT a real\n // identity loss. Retry a few times so a one-off failure can't silently\n // disable the guard for the rest of the run.\n for (let i = 0; i < 3; i++) { const s = startTime(); if (s !== null) return s; }\n return null;\n})();\nlet done = false;\nfunction onDeath() {\n if (done) return; done = true;\n // Re-verify identity: kill ONLY if the live PID is still our child.\n if (snap !== null && alive() && startTime() === snap) treeKill();\n // REF'd (NOT unref'd): stdin has closed, so an unref'd timer would let\n // the loop empty and the process exit immediately — dropping the 500ms\n // SIGKILL escalation. Keep the loop alive to deliver it, then exit.\n setTimeout(() => process.exit(0), 1500);\n}\nprocess.stdin.resume();\nprocess.stdin.on(\"end\", onDeath);\nprocess.stdin.on(\"close\", onDeath);\nprocess.stdin.on(\"error\", onDeath);\nconst cap = setTimeout(() => process.exit(0), 24*3600*1000); if (cap.unref) cap.unref();\n`.trim()\n}\n\n/**\n * Spawn the detached node reaper holding the stdin death-pipe. Always\n * `detached` so it outlives a force-kill of the proxy, and `unref`'d so it\n * never holds the proxy's event loop open. Returns null on spawn failure.\n */\nfunction spawnNodeReaper(pid: number, detachedGroup: boolean): ChildProcess | null {\n try {\n const child = spawn(\n process.execPath,\n [\"-e\", buildNodeReaperScript(pid, detachedGroup)],\n {\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n detached: true,\n windowsHide: true,\n shell: false,\n },\n )\n child.on(\"error\", () => {})\n child.stdin?.on(\"error\", () => {})\n // Hold a reference so the GC can't drop the ChildProcess (and its stdin\n // write-end, the proxy-death signal) mid-run; drop it on reaper exit.\n _activeReapers.add(child)\n child.once(\"exit\", () => _activeReapers.delete(child))\n child.unref()\n return child\n } catch {\n return null\n }\n}\n\n/**\n * Start the crash-safe guard for a launched CLI child. No-op on Windows\n * (Node's Job Object already reaps the tree on proxy death) and when\n * disabled / unspawnable. Fire-and-forget; never throws.\n */\nexport function startProcessGuard(child: ChildProcess): void {\n if (!processGuardEnabled()) return\n const pid = child.pid\n if (!pid) return\n // Windows: the runtime's KILL_ON_JOB_CLOSE job object is the crash net.\n if (process.platform === \"win32\") return\n // POSIX: the CLI is spawned detached (own group), so reap the group.\n spawnNodeReaper(pid, true)\n}\n","/**\n * Auth / routing / remote / wire-fingerprint environment keys that must never\n * reach a github-router-spawned Claude or Codex child — through ANY channel.\n *\n * Two channels carry these keys to a spawned agent, and both must be defended:\n * 1. The PARENT PROCESS ENV — a user's shell exports. Stripped by\n * `sanitizeParentEnv` (`src/lib/launch.ts`) before the proxy's overrides.\n * 2. The serve mirror `settings.json` `env` BLOCK — a user's own\n * `~/.claude/settings.json`, snapshot-mirrored and applied to the\n * SDK-spawned claude via `settingSources`. Stripped by\n * `sanitizeServeSettingsEnv` (`src/lib/mcp-permissions-settings.ts`).\n *\n * If any of these survived, a Bedrock/Vertex/gateway user's config would\n * re-route the session OFF the github-router proxy, inject real auth that wins\n * over the synthetic `.credentials.json`, or activate Claude Code's\n * unimplemented Bridge/remote code path. `CLAUDE_CODE_COORDINATOR_MODE` (a\n * toolset-stripper) is NOT here — it's serve-specific, added to the serve list\n * directly. `ANTHROPIC_SMALL_FAST_MODEL` is deliberately NOT stripped (users\n * legitimately rely on it; `resolveModel` translates unrecognized values).\n */\nexport const STRIPPED_AUTH_ROUTING_ENV_KEYS = [\n // Claude Code auth surface\n \"ANTHROPIC_API_KEY\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_BASE_URL\",\n \"ANTHROPIC_CUSTOM_HEADERS\",\n \"ANTHROPIC_MODEL\",\n \"CLAUDE_CODE_OAUTH_TOKEN\",\n // Alternate OAuth source (loads from an open file descriptor); stripping\n // prevents a third auth source alongside the synthetic .credentials.json.\n \"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR\",\n \"CLAUDE_CODE_USE_BEDROCK\",\n \"CLAUDE_CODE_USE_VERTEX\",\n \"CLAUDE_CODE_USE_FOUNDRY\",\n // Defense-in-depth: a parent- or settings-set CLAUDE_CONFIG_DIR would\n // redirect the session's config dir away from the router-owned mirror.\n \"CLAUDE_CONFIG_DIR\",\n // Claude Code Bridge / IDE remote-session surface — activates a remote code\n // path (POST /v1/code/sessions, /v1/environments/bridge, …) the proxy does\n // not implement. Stripping forces a local-only session.\n \"CLAUDE_BRIDGE_OAUTH_TOKEN\",\n \"CLAUDE_BRIDGE_BASE_URL\",\n \"CLAUDE_BRIDGE_SESSION_INGRESS_URL\",\n \"SESSION_INGRESS_URL\",\n \"CLAUDE_CODE_REMOTE\",\n \"CLAUDE_CODE_CONTAINER_ID\",\n \"CLAUDE_CODE_REMOTE_SESSION_ID\",\n \"CLAUDE_CODE_SESSION_ID\",\n // Emits `x-anthropic-additional-protection` — pure wire-fingerprint noise\n // that breaks the VS Code stealth posture.\n \"CLAUDE_CODE_ADDITIONAL_PROTECTION\",\n // Codex CLI auth surface\n \"OPENAI_API_KEY\",\n \"OPENAI_BASE_URL\",\n \"CODEX_HOME\",\n // ai-or-die per-tab session-bind / artifact-review surface — must not leak\n // into a nested launch and hijack the parent tab's sidecar / bearer token.\n \"AIORDIE_CLAUDE_BIND\",\n \"AIORDIE_TOKEN\",\n \"AIORDIE_INSECURE_TLS\",\n] as const\n","import { execFileSync, spawn, type ChildProcess } from \"node:child_process\"\nimport { existsSync } from \"node:fs\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\nimport consola from \"consola\"\n\nimport type { Server } from \"srvx\"\n\nimport { resolveExecutable, killChildProcessTree } from \"./exec\"\nimport { DEFAULT_CODEX_MODEL } from \"./port\"\nimport { startProcessGuard } from \"./process-guard\"\nimport { STRIPPED_AUTH_ROUTING_ENV_KEYS } from \"./stripped-env-keys\"\nimport { collapsePathKeys } from \"./toolbelt/path-inject\"\nimport { sweepRegistry } from \"./worker-agent/lifecycle\"\n\n/**\n * Auth-related env keys we strip from the parent before spawning the child CLI.\n * The proxy provides its own values for everything we care about, so the child\n * behaves as if the user had no parent-env auth at all. The canonical list lives\n * in `./stripped-env-keys` (shared with serve's settings.json `env` strip).\n *\n * Why strip rather than override-with-empty-string: Claude Code emits \"Auth\n * conflict\" warnings whenever both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY\n * are present (any value); stripping suppresses it AND prevents an inherited\n * real key from leaking. Cloud-provider toggles + OAuth tokens are simpler\n * dropped than overridden — a missing var is unambiguously absent everywhere.\n */\nconst STRIPPED_PARENT_ENV_KEYS = STRIPPED_AUTH_ROUTING_ENV_KEYS\n\n/**\n * Strip auth-related keys from a parent-process env object. The result\n * is suitable to spread into a spawned child's env BEFORE the proxy's\n * explicit overrides, so the proxy is the only source of truth for\n * auth — and stale shell exports can't leak through.\n */\nexport function sanitizeParentEnv(\n parent: NodeJS.ProcessEnv,\n): NodeJS.ProcessEnv {\n const sanitized: NodeJS.ProcessEnv = { ...parent }\n for (const key of STRIPPED_PARENT_ENV_KEYS) {\n delete sanitized[key]\n }\n return sanitized\n}\n\nfunction commandExists(name: string): boolean {\n try {\n execFileSync(process.platform === \"win32\" ? \"where.exe\" : \"which\", [name], {\n stdio: \"ignore\",\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Whether the launcher can execute `executable`.\n *\n * `buildLaunchCommand` resolves the CLI to an ABSOLUTE path (anti-shadow).\n * `where.exe` (and POSIX `which`) reject a full path argument — `where`\n * returns \"Could not find files for the given pattern(s)\" for an absolute\n * path even when the file exists — so the where/which probe is only valid\n * for bare command names. For an absolute path (already resolved against\n * PATH and existence-checked by `resolveExecutable`), check the\n * filesystem directly. Without this split, every launch where the CLI is\n * installed fails with a spurious \"not found on PATH\".\n */\nexport function isExecutableAvailable(executable: string): boolean {\n if (path.isAbsolute(executable)) return existsSync(executable)\n return commandExists(executable)\n}\n\n/**\n * Provider-config flags (`-c model_providers.github_router=...`) that\n * point Codex at our proxy. Extracted from `buildCodexCmd` so the new\n * `codex mcp-server` MCP-config builder can reuse the exact same\n * provider definition — drift between the two paths would silently\n * break the MCP wiring.\n */\nexport function buildCodexProviderConfigFlags(serverUrl: string): Array<string> {\n return [\n \"-c\",\n `model_providers.github_router={name=\"github-router\",base_url=\"${serverUrl}/v1\",wire_api=\"responses\",env_key=\"OPENAI_API_KEY\"}`,\n \"-c\",\n \"model_provider=github_router\",\n ]\n}\n\n/**\n * Inspect the installed `codex` binary. Used by the codex-MCP wiring\n * in `claude.ts` to gate `--codex-cli`. Codex 0.129.0 introduced the\n * `mcp-server` subcommand; older versions don't expose it, so we\n * downgrade to the HTTP backend with a warning.\n */\nexport function getCodexVersion(): { ok: boolean; version?: string } {\n if (!commandExists(\"codex\")) return { ok: false }\n let raw: string\n try {\n raw = execFileSync(\"codex\", [\"--version\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim()\n } catch {\n return { ok: false }\n }\n // Output examples: \"codex-cli 0.129.0\", \"codex 0.130.1-dev\"\n const m = /(\\d+)\\.(\\d+)\\.(\\d+)/.exec(raw)\n if (!m) return { ok: false, version: raw }\n const major = Number.parseInt(m[1], 10)\n const minor = Number.parseInt(m[2], 10)\n const version = `${m[1]}.${m[2]}.${m[3]}`\n // mcp-server requires codex >= 0.129.0\n const ok = major > 0 || (major === 0 && minor >= 129)\n return { ok, version }\n}\n\nexport interface LaunchTarget {\n kind: \"claude-code\" | \"codex\"\n envVars: Record<string, string>\n extraArgs: string[]\n model?: string\n /**\n * Proxy URL the spawned child should target. Required for Codex 0.129+\n * which stopped honoring OPENAI_BASE_URL and now needs an explicit\n * `-c model_providers.<name>.base_url=...` argument. Set by the codex\n * subcommand from the same `serverUrl` it computed for env vars.\n */\n serverUrl?: string\n}\n\n/**\n * Codex 0.129.0 broke two things the launcher had been relying on:\n * (1) `--full-auto` was removed in favor of `--sandbox` + `--ask-for-approval`;\n * passing it now exits the child immediately with\n * `error: unexpected argument '--full-auto' found`.\n * (2) `OPENAI_BASE_URL` is silently ignored — Codex hardcodes\n * `https://api.openai.com/v1/responses` and 401s out without an\n * explicit `-c model_providers.<name>.base_url` override.\n *\n * `buildCodexCmd` builds the launch argv that works on Codex 0.129+ while\n * still being compatible with older versions that accept the same flags.\n */\nfunction buildCodexCmd(target: LaunchTarget): string[] {\n const cmd: string[] = [\"codex\"]\n if (target.serverUrl) {\n cmd.push(...buildCodexProviderConfigFlags(target.serverUrl))\n }\n cmd.push(\n \"--sandbox\",\n \"workspace-write\",\n \"--ask-for-approval\",\n \"on-request\",\n \"-m\",\n target.model ?? DEFAULT_CODEX_MODEL,\n ...target.extraArgs,\n )\n return cmd\n}\n\nexport function buildLaunchCommand(target: LaunchTarget): {\n cmd: string[]\n env: Record<string, string | undefined>\n} {\n // F10: don't force `--dangerously-skip-permissions` when the caller already\n // requested an explicit `--permission-mode` (e.g. ai-or-die's claude-bridge\n // appends one for a fleet create_session with permissionMode). The two flags\n // conflict — claude rejects them together — and the explicit mode must win.\n const wantsPermissionMode =\n target.kind === \"claude-code\" &&\n target.extraArgs.some((arg) => arg === \"--permission-mode\" || arg.startsWith(\"--permission-mode=\"))\n const cmd: string[] =\n target.kind === \"claude-code\"\n ? wantsPermissionMode\n ? [\"claude\", ...target.extraArgs]\n : [\"claude\", \"--dangerously-skip-permissions\", ...target.extraArgs]\n : buildCodexCmd(target)\n\n // Anti-shadow: resolve the top-level CLI to an ABSOLUTE path against\n // the clean parent PATH (excluding the cwd). The spawned child's env\n // prepends the toolbelt bin dir to PATH; without this, a stray\n // `claude.cmd`/`codex.cmd` in that dir — or in an untrusted repo's cwd\n // (Windows resolves cwd before PATH under shell:true) — could shadow\n // the real CLI. Resolving here means the toolbelt PATH only affects\n // the agent's OWN tool lookups, never which CLI we launch.\n const resolved = resolveExecutable(cmd[0], { env: process.env })\n if (resolved) cmd[0] = resolved\n\n const env = collapsePathKeys({\n ...sanitizeParentEnv(process.env),\n ...target.envVars,\n })\n return { cmd, env }\n}\n\n/**\n * Whether a resolved Windows executable must be launched through cmd.exe\n * (`shell:true`). Only batch shims (`.cmd`/`.bat`) need it — and even then\n * cmd.exe stays alive as the CLI's parent, so `taskkill /T` reaps the\n * whole tree. A real `.exe` (e.g. the native-installer `claude.exe`) is\n * spawned DIRECTLY so the CLI is the direct child, with no cmd.exe\n * intermediary to orphan its node/MCP grandchildren on a kill.\n */\nexport function windowsLaunchNeedsShell(executable: string): boolean {\n const ext = path.extname(executable).toLowerCase()\n return ext === \".cmd\" || ext === \".bat\"\n}\n\nexport function launchChild(\n target: LaunchTarget,\n server: Server,\n options: {\n onShutdown?: () => Promise<void> | void\n /**\n * Optional exit summary (e.g. the AIC session total). Called once from\n * `exit()`, after cleanup, on every shutdown path (natural child exit,\n * signal escalation, spawn error). Return undefined for no output.\n * Best-effort: throwing content is swallowed so shutdown never blocks.\n */\n onExitSummary?: () => string | undefined\n } = {},\n): void {\n const { cmd, env } = buildLaunchCommand(target)\n\n const executable = cmd[0]\n if (!isExecutableAvailable(executable)) {\n const msg = `\"${executable}\" not found on PATH. Install it first, then try again.`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n process.exit(1)\n }\n\n let child: ChildProcess\n try {\n if (process.platform === \"win32\") {\n if (windowsLaunchNeedsShell(cmd[0])) {\n // A batch shim genuinely needs cmd.exe. cmd.exe stays alive as the\n // CLI's parent for the shim's lifetime, so the tree-kill (taskkill\n // /T) and the crash guard both reap the whole tree through it. Use\n // the full command as a single string to avoid the DEP0190\n // deprecation warning about shell + args.\n const quoted = cmd.map((a) => (a.includes(\" \") ? `\"${a}\"` : a)).join(\" \")\n child = spawn(quoted, [], {\n env,\n stdio: \"inherit\",\n shell: true,\n })\n } else {\n // A real .exe (e.g. the native-installer claude.exe) needs no\n // shell. Spawning it directly makes the CLI the DIRECT child, so\n // child.kill()/taskkill and the crash guard target the real\n // process instead of a cmd.exe wrapper that would orphan its\n // node/MCP grandchildren.\n child = spawn(cmd[0], cmd.slice(1), {\n env,\n stdio: \"inherit\",\n })\n }\n } else {\n // detached:true → the CLI leads its OWN process group, so cleanup()\n // can kill(-pgid) the whole tree (grandchildren included). We still\n // await its exit, so we do NOT unref(). Terminal Ctrl-C no longer\n // reaches the child directly — the signal handlers below forward it.\n child = spawn(cmd[0], cmd.slice(1), {\n env,\n stdio: \"inherit\",\n detached: true,\n })\n }\n } catch (error) {\n const msg = `Failed to launch ${executable}: ${error instanceof Error ? error.message : String(error)}`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n server.close(true).catch(() => {})\n if (options.onShutdown) {\n void Promise.resolve(options.onShutdown()).catch(() => {})\n }\n process.exit(1)\n }\n\n // Crash-safe net: if the proxy dies WITHOUT running cleanup() (hard\n // crash, SIGKILL/taskkill, OOM). On Windows this is the OS's job —\n // Node's KILL_ON_JOB_CLOSE job object reaps the tree — so this is a\n // no-op there. On POSIX (no such mechanism) it spawns a detached reaper\n // that sees the proxy's stdin-pipe EOF, re-verifies the child's\n // identity, and reaps the group (also backstopping a graceful shutdown\n // where the child ignores SIGTERM and the proxy exits before the\n // escalation lands). Fire-and-forget; self-acts and self-exits.\n startProcessGuard(child)\n\n let cleaned = false\n let exiting = false\n async function cleanup(): Promise<void> {\n if (cleaned) return\n cleaned = true\n\n // Arm the hard fail-safe FIRST, before any (potentially blocking) kill\n // or async teardown, so a wedged taskkill / server.close can't hang\n // shutdown indefinitely.\n const timeout = setTimeout(() => process.exit(1), 5000)\n\n // Tree-kill the whole CLI subtree, not just the direct child. On\n // Windows taskkill /T reaps grandchildren; on POSIX kill(-pgid) reaps\n // the detached process group. (Replaces a plain child.kill() that\n // orphaned node/MCP grandchildren on Windows.) A SIGTERM-ignoring\n // survivor that outlives our exit is caught by the crash guard above.\n try {\n killChildProcessTree(child, {\n detachedGroup: process.platform !== \"win32\",\n })\n } catch {\n // Already exited / best-effort.\n }\n\n try {\n await server.close(true)\n } catch {\n // Server already closed\n }\n if (options.onShutdown) {\n try {\n await options.onShutdown()\n } catch {\n // Best-effort cleanup; shutdown must not be blocked by it.\n }\n }\n clearTimeout(timeout)\n }\n\n function exit(code: number): void {\n if (exiting) return\n exiting = true\n try {\n const summary = options.onExitSummary?.()\n if (summary) process.stderr.write(`\\n${summary}\\n`)\n } catch {\n // Best-effort summary; never block shutdown.\n }\n process.exit(code)\n }\n\n // On POSIX the CLI is in its own process group (detached), so terminal\n // Ctrl-C reaches only the proxy. Forward the signal to the child group\n // so the CLI's own interactive handler runs (e.g. Claude's \"press\n // Ctrl-C again to exit\"); let its natural exit drive shutdown and\n // escalate to a hard tree-kill only if it ignores us past a grace.\n let forwardGrace: NodeJS.Timeout | null = null\n const lastForwardAt: Record<\"SIGINT\" | \"SIGTERM\", number> = {\n SIGINT: 0,\n SIGTERM: 0,\n }\n const onSignal = (sig: \"SIGINT\" | \"SIGTERM\") => {\n if (process.platform !== \"win32\" && child.pid && !cleaned) {\n // Debounce PER SIGNAL a sub-second burst into ONE forward. The other\n // subsystem signal handlers (keep-awake/colbert/worker/browser)\n // re-raise the SAME signal to restore default-terminate, which\n // re-invokes us several times within milliseconds — without this the\n // child would receive a volley and Claude's \"press Ctrl-C again\"\n // guard would never see a single press. Per-signal so a SIGTERM right\n // after a SIGINT still forwards. A genuine second human Ctrl-C\n // (seconds later) falls outside the window and forwards again.\n const now = Date.now()\n if (now - lastForwardAt[sig] > 250) {\n lastForwardAt[sig] = now\n try {\n process.kill(-child.pid, sig)\n } catch {\n // group already gone — fall through to the grace escalation\n }\n }\n const graceMs = sig === \"SIGINT\" ? 10000 : 3000\n if (!forwardGrace) {\n forwardGrace = setTimeout(() => {\n cleanup().then(() => exit(130)).catch(() => exit(1))\n }, graceMs)\n forwardGrace.unref?.()\n }\n return\n }\n // Windows: the console already delivered Ctrl-C to the direct child;\n // cleanup()'s taskkill /T is the escalation hammer.\n cleanup().then(() => exit(130)).catch(() => exit(1))\n }\n process.on(\"SIGINT\", () => onSignal(\"SIGINT\"))\n process.on(\"SIGTERM\", () => onSignal(\"SIGTERM\"))\n\n child.on(\"exit\", (exitCode, signal) => {\n // When the spawned CLI exits we may be holding worker-agent\n // worktrees that the proxy will never get a chance to clean up\n // through the per-call finally (the proxy itself is about to\n // shut down). Drain them synchronously here — same sweep the\n // SIGINT/SIGTERM handlers use. No-op if no worker tools were\n // ever invoked (registry is null).\n try {\n sweepRegistry()\n } catch {\n // best-effort; don't let cleanup failures override the exit code\n }\n // When killed by a signal, exitCode is null — derive from signal number\n const code = exitCode ?? (signal ? 128 : 1)\n cleanup().then(() => exit(code)).catch(() => exit(1))\n })\n child.on(\"error\", () => {\n cleanup().then(() => exit(1)).catch(() => exit(1))\n })\n}\n","/**\n * Toolbelt provisioner: materialize curated CLI tools into the durable\n * router `bin/` dir so the spawned agent can call them on PATH.\n *\n * Properties (all load-bearing):\n * - **Best-effort.** Every tool is isolated in try/catch; a failure\n * skips that one tool and never throws to the launcher.\n * - **Gap-fill.** A tool already on the user's PATH is NOT\n * materialized (and a stale copy is removed) so we never shadow the\n * user's own `jq`/`yq`/etc.\n * - **Verified.** The downloaded archive's SHA256 must match the\n * hardcoded manifest digest BEFORE extraction.\n * - **Safe extraction.** Only the expected regular-file binary is\n * pulled out; symlink/hardlink/device entries are rejected.\n * - **Concurrency-safe.** A cross-process lock serializes writers;\n * unique temp names + atomic rename avoid half-written binaries.\n * - **Integrity-pruned.** Unexpected files in `bin/` are removed each\n * launch so a prompt-injected agent can't plant a `git.cmd` that\n * shadows real tools in future sessions.\n */\n\nimport { randomBytes, createHash } from \"node:crypto\"\nimport { existsSync } from \"node:fs\"\nimport {\n chmod,\n copyFile,\n link,\n mkdir,\n readdir,\n readFile,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\nimport consola from \"consola\"\n\nimport { resolveExecutable, runCommandCapture } from \"../exec\"\nimport { PATHS } from \"../paths\"\nimport { withInstallLock } from \"../update-lock\"\n\nimport { extractTarGzMember, extractZipMember } from \"./extract\"\nimport { toolbeltEnabled, toolbeltSkipSet, vscodeRipgrepPath } from \"./index\"\nimport { assetFor, satisfiesMinVersion, TOOLBELT_TOOLS, type ToolAsset, type ToolSpec } from \"./manifest\"\n\n/** Per-download cap (bytes) — these binaries are a few MB at most. */\nconst MAX_DOWNLOAD_BYTES = 64 * 1024 * 1024\nconst DOWNLOAD_TIMEOUT_MS = 30_000\n\nconst EXE_EXT = process.platform === \"win32\" ? \".exe\" : \"\"\n\n/**\n * Materialize the toolbelt. Returns the list of command names exposed\n * in `bin/` after provisioning. Best-effort; never throws.\n */\nexport async function provisionToolbelt(): Promise<string[]> {\n if (!toolbeltEnabled()) return []\n const binDir = PATHS.TOOLBELT_BIN_DIR\n try {\n await mkdir(binDir, { recursive: true })\n } catch (err) {\n consola.debug(\"toolbelt: could not create bin dir:\", err)\n return []\n }\n\n const skip = toolbeltSkipSet()\n\n // Serialize across concurrent proxies. If another process holds the\n // lock it is provisioning; we skip and just report current state.\n await withInstallLock(\"toolbelt.lock\", async () => {\n await pruneUnexpected(binDir)\n await provisionRg(binDir, skip).catch((err) =>\n consola.debug(\"toolbelt: rg skipped:\", err),\n )\n await Promise.all(\n TOOLBELT_TOOLS.map((spec) =>\n provisionTool(spec, binDir, skip).catch((err) =>\n consola.debug(`toolbelt: ${spec.command} skipped:`, err),\n ),\n ),\n )\n })\n\n return exposedCommands(binDir)\n}\n\n/** Names allowed to live in `bin/` (managed binaries + their sidecars). */\nfunction expectedFileNames(): Set<string> {\n const names = new Set<string>()\n const add = (base: string) => {\n names.add(base + EXE_EXT)\n names.add(`${base}${EXE_EXT}.sha256`)\n }\n add(\"rg\")\n for (const spec of TOOLBELT_TOOLS) {\n add(spec.binBasename)\n for (const a of spec.aliases ?? []) add(a)\n }\n return names\n}\n\n/** Remove any file in `bin/` that isn't a managed binary or sidecar. */\nasync function pruneUnexpected(binDir: string): Promise<void> {\n const expected = expectedFileNames()\n let entries: string[]\n try {\n entries = await readdir(binDir)\n } catch {\n return\n }\n for (const name of entries) {\n if (name.endsWith(\".tmp\")) continue // in-flight temp from a peer\n if (!expected.has(name)) {\n await rm(path.join(binDir, name), { force: true }).catch(() => {})\n }\n }\n}\n\nasync function provisionRg(binDir: string, skip: Set<string>): Promise<void> {\n const dest = path.join(binDir, \"rg\" + EXE_EXT)\n if (skip.has(\"rg\") || resolveExecutable(\"rg\")) {\n // User has rg (or skipped) — don't shadow it. code_search uses its\n // own resolver regardless.\n await removeBin(dest)\n return\n }\n if (existsSync(dest)) return // already materialized\n const src = vscodeRipgrepPath()\n if (!src) return\n\n const tmp = tempName(dest)\n try {\n await link(src, tmp) // hardlink (same-volume fast path)\n } catch {\n await copyFile(src, tmp) // cross-volume (EXDEV) or other → copy\n }\n if (process.platform !== \"win32\") await chmod(tmp, 0o755).catch(() => {})\n await commit(tmp, dest)\n}\n\n/**\n * Does the user's own copy on PATH satisfy `spec.minVersion`?\n *\n * Returns true when there is nothing to check (no minVersion), and — this\n * is the load-bearing part — ALSO returns true when the probe fails or the\n * output cannot be parsed. Gap-fill exists so we never shadow a user's\n * pinned or wrapper install, and an unreadable `--version` is not evidence\n * that their tool is inadequate. We only step in front of it on positive\n * proof that it is too old.\n */\nasync function pathCopyIsAdequate(spec: ToolSpec, resolved: string): Promise<boolean> {\n if (!spec.minVersion) return true\n try {\n // `runCommandCapture`, NOT `runManagedExeCapture`: on Windows a tool\n // installed via npm resolves to a `.cmd` shim, which cannot be spawned\n // with `shell:false` (it throws EINVAL). That is exactly how the\n // ast-grep case presented — the probe threw, we treated the version as\n // unknown, and yielded to the outdated copy anyway. runCommandCapture\n // goes through buildExecInvocation, which routes shims via cmd.exe.\n const res = await runCommandCapture([resolved, \"--version\"], {\n timeoutMs: VERSION_PROBE_TIMEOUT_MS,\n })\n const verdict = satisfiesMinVersion(`${res.stdout} ${res.stderr}`, spec.minVersion)\n if (verdict === undefined) return true\n if (!verdict) {\n consola.debug(\n `[toolbelt] ${spec.command} on PATH is older than ${spec.minVersion}; using the router-pinned build instead`,\n )\n }\n return verdict\n } catch {\n return true\n }\n}\n\n/** A `--version` call should be instant; this only bounds a hung binary. */\nconst VERSION_PROBE_TIMEOUT_MS = 5_000\n\nasync function provisionTool(\n spec: ToolSpec,\n binDir: string,\n skip: Set<string>,\n): Promise<void> {\n const dest = path.join(binDir, spec.binBasename + EXE_EXT)\n const sidecar = `${dest}.sha256`\n const asset = assetFor(spec)\n\n if (skip.has(spec.command) || !asset) {\n await removeTool(spec, binDir)\n return\n }\n // Gap-fill: user already has it on PATH → remove ours, don't shadow.\n // Exception: a tool carrying `minVersion` is one this project's own code\n // depends on, so an older PATH copy is not \"good enough\" — it degrades\n // us silently. In that case we materialize ours anyway; the toolbelt bin\n // is prepended to the spawned agent's PATH and code_search checks the\n // router-owned paths first, so ours wins where it matters while the\n // user's shell keeps theirs.\n const onPath = resolveExecutable(spec.command)\n if (onPath && (await pathCopyIsAdequate(spec, onPath))) {\n await removeTool(spec, binDir)\n return\n }\n // Idempotent: present + sidecar matches → just ensure aliases exist.\n if (existsSync(dest) && (await sidecarMatches(sidecar, asset.sha256))) {\n await ensureAliases(spec, binDir, dest)\n return\n }\n\n const bytes = await downloadAndExtract(spec, asset)\n await atomicInstall(dest, bytes)\n await writeFile(sidecar, asset.sha256).catch(() => {})\n await ensureAliases(spec, binDir, dest)\n}\n\nasync function downloadAndExtract(\n spec: ToolSpec,\n asset: ToolAsset,\n): Promise<Buffer> {\n const data = await download(asset.url)\n const digest = createHash(\"sha256\").update(data).digest(\"hex\")\n if (digest !== asset.sha256) {\n throw new Error(\n `checksum mismatch for ${spec.command} (${asset.url}): expected ${asset.sha256}, got ${digest}`,\n )\n }\n if (asset.archive === \"raw\") return data\n const member =\n asset.archive === \"zip\"\n ? extractZipMember(data, spec.binBasename)\n : extractTarGzMember(data, spec.binBasename)\n if (!member) {\n throw new Error(`binary \"${spec.binBasename}\" not found in ${asset.url}`)\n }\n return member\n}\n\nasync function download(url: string): Promise<Buffer> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS)\n try {\n const res = await fetch(url, {\n signal: controller.signal,\n redirect: \"follow\",\n headers: { \"User-Agent\": \"github-router-toolbelt\" },\n })\n if (!res.ok) throw new Error(`download ${url}: HTTP ${res.status}`)\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.length > MAX_DOWNLOAD_BYTES) {\n throw new Error(`download ${url}: exceeds ${MAX_DOWNLOAD_BYTES} bytes`)\n }\n return buf\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Write to a unique temp then atomically rename into place. */\nasync function atomicInstall(dest: string, bytes: Buffer): Promise<void> {\n const tmp = tempName(dest)\n await writeFile(tmp, bytes)\n if (process.platform !== \"win32\") await chmod(tmp, 0o755).catch(() => {})\n await commit(tmp, dest)\n}\n\n/** Rename tmp→dest, handling Windows replace-existing / in-use locks. */\nasync function commit(tmp: string, dest: string): Promise<void> {\n try {\n await rename(tmp, dest)\n } catch {\n // Windows: rename won't replace an existing/locked dest. Remove and\n // retry once; if that fails (in-use .exe), leave the existing copy.\n try {\n await rm(dest, { force: true })\n await rename(tmp, dest)\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => {})\n throw err\n }\n }\n}\n\nasync function ensureAliases(\n spec: ToolSpec,\n binDir: string,\n dest: string,\n): Promise<void> {\n for (const alias of spec.aliases ?? []) {\n const ap = path.join(binDir, alias + EXE_EXT)\n if (existsSync(ap)) continue\n const tmp = tempName(ap)\n try {\n await copyFile(dest, tmp)\n if (process.platform !== \"win32\") await chmod(tmp, 0o755).catch(() => {})\n await commit(tmp, ap)\n } catch (err) {\n consola.debug(`toolbelt: alias ${alias} skipped:`, err)\n }\n }\n}\n\nasync function removeTool(spec: ToolSpec, binDir: string): Promise<void> {\n const dest = path.join(binDir, spec.binBasename + EXE_EXT)\n await removeBin(dest)\n for (const alias of spec.aliases ?? []) {\n await removeBin(path.join(binDir, alias + EXE_EXT))\n }\n}\n\nasync function removeBin(dest: string): Promise<void> {\n await rm(dest, { force: true }).catch(() => {})\n await rm(`${dest}.sha256`, { force: true }).catch(() => {})\n}\n\nasync function sidecarMatches(sidecar: string, sha256: string): Promise<boolean> {\n try {\n return (await readFile(sidecar, \"utf8\")).trim() === sha256\n } catch {\n return false\n }\n}\n\nfunction tempName(dest: string): string {\n return `${dest}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`\n}\n\n/** The command names currently exposed in `bin/`. */\nasync function exposedCommands(binDir: string): Promise<string[]> {\n let files: Set<string>\n try {\n files = new Set(await readdir(binDir))\n } catch {\n return []\n }\n const present = (base: string) => files.has(base + EXE_EXT)\n const out: string[] = []\n if (present(\"rg\")) out.push(\"rg\")\n for (const spec of TOOLBELT_TOOLS) {\n if (present(spec.binBasename)) out.push(spec.command)\n }\n return out\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"provision-_R7ZMygj.js","names":[],"sources":["../src/lib/process-guard/index.ts","../src/lib/stripped-env-keys.ts","../src/lib/launch.ts","../src/lib/toolbelt/provision.ts"],"sourcesContent":["/**\n * Crash-safe process guard (POSIX only).\n *\n * If the proxy dies WITHOUT running its own shutdown handlers (hard crash,\n * SIGKILL, OOM), nothing in `cleanup()` runs to reap the launched CLI's\n * process tree. This guard is the last-resort net for that case.\n *\n * Platform split:\n * - **Windows: no guard needed.** Node assigns each child to a Job\n * Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, so when the proxy\n * dies (any cause) the OS tears down the whole descendant tree —\n * verified: force-killing a Node parent kills child AND grandchild.\n * `startProcessGuard` is a no-op on win32.\n * - **POSIX: a detached `node -e` reaper.** There is no job-object\n * equivalent (a killed parent's children reparent to init), so we\n * spawn a detached watchdog that learns of proxy death via an inherited\n * stdin PIPE EOF (the proxy holds the write end; the OS closes it on\n * proxy death) and reaps the CLI's process group.\n *\n * Never kill the WRONG process: if the child's PID/PGID was recycled after\n * the child exited, a blind `kill(-pid)` could hit an innocent group. The\n * reaper snapshots the child's process start-time at startup and, on proxy\n * death, kills ONLY if the live PID's start-time still matches. A mismatch\n * / unreadable probe → skip (fail-safe). We never trade an orphan-leak for\n * killing the user's work.\n *\n * The reaper is detached + unref'd so it survives the proxy's SIGKILL, and\n * self-exits once it has acted (or after a 24h cap) so it can never become\n * the orphan it exists to prevent. Opt out with\n * `GH_ROUTER_DISABLE_PROCESS_GUARD=1`.\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\"\nimport process from \"node:process\"\n\nimport { parseBoolEnv } from \"../exec\"\n\n/**\n * Live reaper children, held so the GC can't collect the `ChildProcess`\n * (and with it the stdin write-end that is the proxy-death signal) while\n * the proxy runs. Entries self-remove on the reaper's exit. We do NOT\n * proactively kill these on graceful shutdown: when the proxy exits the OS\n * closes the stdin pipe, the reaper hits EOF, re-verifies the child's\n * identity, and reaps any survivor that ignored the graceful SIGTERM —\n * exactly the backstop we want.\n */\nconst _activeReapers = new Set<ChildProcess>()\n\n/** Guard is on by default; `GH_ROUTER_DISABLE_PROCESS_GUARD=1` opts out. */\nexport function processGuardEnabled(): boolean {\n return parseBoolEnv(process.env.GH_ROUTER_DISABLE_PROCESS_GUARD) !== true\n}\n\n/**\n * Build the start-time-verified node reaper script (POSIX). PURE — `pid`\n * is our own integer. `detachedGroup` selects `kill(-pid)` (process group,\n * the detached CLI) vs `kill(pid)` on POSIX.\n *\n * Kept dependency-free (require'd builtins only) so it runs under `node -e`\n * with no module resolution against the dist bundle.\n */\nexport function buildNodeReaperScript(\n pid: number,\n detachedGroup: boolean,\n): string {\n const target = detachedGroup ? \"-PID\" : \"PID\"\n return `\nconst cp = require(\"node:child_process\");\nconst fs = require(\"node:fs\");\nconst PID = ${pid >>> 0};\nfunction startTime() {\n try {\n if (process.platform === \"linux\") {\n const stat = fs.readFileSync(\"/proc/\" + PID + \"/stat\", \"utf8\");\n const post = stat.slice(stat.lastIndexOf(\")\") + 1).trim().split(/\\\\s+/);\n return post[19] || null; // field 22 (starttime) = index 19 after comm\n }\n const out = cp.execFileSync(\"ps\", [\"-o\",\"lstart=\",\"-p\",String(PID)],\n { stdio: [\"ignore\",\"pipe\",\"ignore\"], timeout: 2000 }).toString().trim();\n return out || null;\n } catch { return null; }\n}\nfunction alive() { try { process.kill(PID, 0); return true; } catch { return false; } }\nfunction treeKill() {\n try { process.kill(${target}, \"SIGTERM\"); } catch {}\n // NOT unref'd: this must keep the loop alive to deliver the escalated\n // SIGKILL (the onDeath exit timer below stays alive past 500ms too).\n setTimeout(() => { try { process.kill(${target}, \"SIGKILL\"); } catch {} }, 500);\n}\nconst snap = (() => {\n // The child is definitely ours and alive at startup, so a null here is a\n // transient probe failure (e.g. a momentary 'ps' hiccup), NOT a real\n // identity loss. Retry a few times so a one-off failure can't silently\n // disable the guard for the rest of the run.\n for (let i = 0; i < 3; i++) { const s = startTime(); if (s !== null) return s; }\n return null;\n})();\nlet done = false;\nfunction onDeath() {\n if (done) return; done = true;\n // Re-verify identity: kill ONLY if the live PID is still our child.\n if (snap !== null && alive() && startTime() === snap) treeKill();\n // REF'd (NOT unref'd): stdin has closed, so an unref'd timer would let\n // the loop empty and the process exit immediately — dropping the 500ms\n // SIGKILL escalation. Keep the loop alive to deliver it, then exit.\n setTimeout(() => process.exit(0), 1500);\n}\nprocess.stdin.resume();\nprocess.stdin.on(\"end\", onDeath);\nprocess.stdin.on(\"close\", onDeath);\nprocess.stdin.on(\"error\", onDeath);\nconst cap = setTimeout(() => process.exit(0), 24*3600*1000); if (cap.unref) cap.unref();\n`.trim()\n}\n\n/**\n * Spawn the detached node reaper holding the stdin death-pipe. Always\n * `detached` so it outlives a force-kill of the proxy, and `unref`'d so it\n * never holds the proxy's event loop open. Returns null on spawn failure.\n */\nfunction spawnNodeReaper(pid: number, detachedGroup: boolean): ChildProcess | null {\n try {\n const child = spawn(\n process.execPath,\n [\"-e\", buildNodeReaperScript(pid, detachedGroup)],\n {\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n detached: true,\n windowsHide: true,\n shell: false,\n },\n )\n child.on(\"error\", () => {})\n child.stdin?.on(\"error\", () => {})\n // Hold a reference so the GC can't drop the ChildProcess (and its stdin\n // write-end, the proxy-death signal) mid-run; drop it on reaper exit.\n _activeReapers.add(child)\n child.once(\"exit\", () => _activeReapers.delete(child))\n child.unref()\n return child\n } catch {\n return null\n }\n}\n\n/**\n * Start the crash-safe guard for a launched CLI child. No-op on Windows\n * (Node's Job Object already reaps the tree on proxy death) and when\n * disabled / unspawnable. Fire-and-forget; never throws.\n */\nexport function startProcessGuard(child: ChildProcess): void {\n if (!processGuardEnabled()) return\n const pid = child.pid\n if (!pid) return\n // Windows: the runtime's KILL_ON_JOB_CLOSE job object is the crash net.\n if (process.platform === \"win32\") return\n // POSIX: the CLI is spawned detached (own group), so reap the group.\n spawnNodeReaper(pid, true)\n}\n","/**\n * Auth / routing / remote / wire-fingerprint environment keys that must never\n * reach a github-router-spawned Claude or Codex child — through ANY channel.\n *\n * Two channels carry these keys to a spawned agent, and both must be defended:\n * 1. The PARENT PROCESS ENV — a user's shell exports. Stripped by\n * `sanitizeParentEnv` (`src/lib/launch.ts`) before the proxy's overrides.\n * 2. The serve mirror `settings.json` `env` BLOCK — a user's own\n * `~/.claude/settings.json`, snapshot-mirrored and applied to the\n * SDK-spawned claude via `settingSources`. Stripped by\n * `sanitizeServeSettingsEnv` (`src/lib/mcp-permissions-settings.ts`).\n *\n * If any of these survived, a Bedrock/Vertex/gateway user's config would\n * re-route the session OFF the github-router proxy, inject real auth that wins\n * over the synthetic `.credentials.json`, or activate Claude Code's\n * unimplemented Bridge/remote code path. `CLAUDE_CODE_COORDINATOR_MODE` (a\n * toolset-stripper) is NOT here — it's serve-specific, added to the serve list\n * directly. `ANTHROPIC_SMALL_FAST_MODEL` is deliberately NOT stripped (users\n * legitimately rely on it; `resolveModel` translates unrecognized values).\n */\nexport const STRIPPED_AUTH_ROUTING_ENV_KEYS = [\n // Claude Code auth surface\n \"ANTHROPIC_API_KEY\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_BASE_URL\",\n \"ANTHROPIC_CUSTOM_HEADERS\",\n \"ANTHROPIC_MODEL\",\n \"CLAUDE_CODE_OAUTH_TOKEN\",\n // Alternate OAuth source (loads from an open file descriptor); stripping\n // prevents a third auth source alongside the synthetic .credentials.json.\n \"CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR\",\n \"CLAUDE_CODE_USE_BEDROCK\",\n \"CLAUDE_CODE_USE_VERTEX\",\n \"CLAUDE_CODE_USE_FOUNDRY\",\n // Defense-in-depth: a parent- or settings-set CLAUDE_CONFIG_DIR would\n // redirect the session's config dir away from the router-owned mirror.\n \"CLAUDE_CONFIG_DIR\",\n // Claude Code Bridge / IDE remote-session surface — activates a remote code\n // path (POST /v1/code/sessions, /v1/environments/bridge, …) the proxy does\n // not implement. Stripping forces a local-only session.\n \"CLAUDE_BRIDGE_OAUTH_TOKEN\",\n \"CLAUDE_BRIDGE_BASE_URL\",\n \"CLAUDE_BRIDGE_SESSION_INGRESS_URL\",\n \"SESSION_INGRESS_URL\",\n \"CLAUDE_CODE_REMOTE\",\n \"CLAUDE_CODE_CONTAINER_ID\",\n \"CLAUDE_CODE_REMOTE_SESSION_ID\",\n \"CLAUDE_CODE_SESSION_ID\",\n // Emits `x-anthropic-additional-protection` — pure wire-fingerprint noise\n // that breaks the VS Code stealth posture.\n \"CLAUDE_CODE_ADDITIONAL_PROTECTION\",\n // Codex CLI auth surface\n \"OPENAI_API_KEY\",\n \"OPENAI_BASE_URL\",\n \"CODEX_HOME\",\n // ai-or-die per-tab session-bind / artifact-review surface — must not leak\n // into a nested launch and hijack the parent tab's sidecar / bearer token.\n \"AIORDIE_CLAUDE_BIND\",\n \"AIORDIE_TOKEN\",\n \"AIORDIE_INSECURE_TLS\",\n] as const\n","import { execFileSync, spawn, type ChildProcess } from \"node:child_process\"\nimport { existsSync } from \"node:fs\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\nimport consola from \"consola\"\n\nimport type { Server } from \"srvx\"\n\nimport { resolveExecutable, killChildProcessTree } from \"./exec\"\nimport { DEFAULT_CODEX_MODEL } from \"./port\"\nimport { startProcessGuard } from \"./process-guard\"\nimport { STRIPPED_AUTH_ROUTING_ENV_KEYS } from \"./stripped-env-keys\"\nimport { collapsePathKeys } from \"./toolbelt/path-inject\"\nimport { sweepRegistry } from \"./worker-agent/lifecycle\"\n\n/**\n * Auth-related env keys we strip from the parent before spawning the child CLI.\n * The proxy provides its own values for everything we care about, so the child\n * behaves as if the user had no parent-env auth at all. The canonical list lives\n * in `./stripped-env-keys` (shared with serve's settings.json `env` strip).\n *\n * Why strip rather than override-with-empty-string: Claude Code emits \"Auth\n * conflict\" warnings whenever both ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY\n * are present (any value); stripping suppresses it AND prevents an inherited\n * real key from leaking. Cloud-provider toggles + OAuth tokens are simpler\n * dropped than overridden — a missing var is unambiguously absent everywhere.\n */\nconst STRIPPED_PARENT_ENV_KEYS = STRIPPED_AUTH_ROUTING_ENV_KEYS\n\n/**\n * Strip auth-related keys from a parent-process env object. The result\n * is suitable to spread into a spawned child's env BEFORE the proxy's\n * explicit overrides, so the proxy is the only source of truth for\n * auth — and stale shell exports can't leak through.\n */\nexport function sanitizeParentEnv(\n parent: NodeJS.ProcessEnv,\n): NodeJS.ProcessEnv {\n const sanitized: NodeJS.ProcessEnv = { ...parent }\n for (const key of STRIPPED_PARENT_ENV_KEYS) {\n delete sanitized[key]\n }\n return sanitized\n}\n\nfunction commandExists(name: string): boolean {\n try {\n execFileSync(process.platform === \"win32\" ? \"where.exe\" : \"which\", [name], {\n stdio: \"ignore\",\n })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Whether the launcher can execute `executable`.\n *\n * `buildLaunchCommand` resolves the CLI to an ABSOLUTE path (anti-shadow).\n * `where.exe` (and POSIX `which`) reject a full path argument — `where`\n * returns \"Could not find files for the given pattern(s)\" for an absolute\n * path even when the file exists — so the where/which probe is only valid\n * for bare command names. For an absolute path (already resolved against\n * PATH and existence-checked by `resolveExecutable`), check the\n * filesystem directly. Without this split, every launch where the CLI is\n * installed fails with a spurious \"not found on PATH\".\n */\nexport function isExecutableAvailable(executable: string): boolean {\n if (path.isAbsolute(executable)) return existsSync(executable)\n return commandExists(executable)\n}\n\n/**\n * Provider-config flags (`-c model_providers.github_router=...`) that\n * point Codex at our proxy. Extracted from `buildCodexCmd` so the new\n * `codex mcp-server` MCP-config builder can reuse the exact same\n * provider definition — drift between the two paths would silently\n * break the MCP wiring.\n */\nexport function buildCodexProviderConfigFlags(serverUrl: string): Array<string> {\n return [\n \"-c\",\n `model_providers.github_router={name=\"github-router\",base_url=\"${serverUrl}/v1\",wire_api=\"responses\",env_key=\"OPENAI_API_KEY\"}`,\n \"-c\",\n \"model_provider=github_router\",\n ]\n}\n\n/**\n * Inspect the installed `codex` binary. Used by the codex-MCP wiring\n * in `claude.ts` to gate `--codex-cli`. Codex 0.129.0 introduced the\n * `mcp-server` subcommand; older versions don't expose it, so we\n * downgrade to the HTTP backend with a warning.\n */\nexport function getCodexVersion(): { ok: boolean; version?: string } {\n if (!commandExists(\"codex\")) return { ok: false }\n let raw: string\n try {\n raw = execFileSync(\"codex\", [\"--version\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim()\n } catch {\n return { ok: false }\n }\n // Output examples: \"codex-cli 0.129.0\", \"codex 0.130.1-dev\"\n const m = /(\\d+)\\.(\\d+)\\.(\\d+)/.exec(raw)\n if (!m) return { ok: false, version: raw }\n const major = Number.parseInt(m[1], 10)\n const minor = Number.parseInt(m[2], 10)\n const version = `${m[1]}.${m[2]}.${m[3]}`\n // mcp-server requires codex >= 0.129.0\n const ok = major > 0 || (major === 0 && minor >= 129)\n return { ok, version }\n}\n\nexport interface LaunchTarget {\n kind: \"claude-code\" | \"codex\"\n envVars: Record<string, string>\n extraArgs: string[]\n model?: string\n /**\n * Proxy URL the spawned child should target. Required for Codex 0.129+\n * which stopped honoring OPENAI_BASE_URL and now needs an explicit\n * `-c model_providers.<name>.base_url=...` argument. Set by the codex\n * subcommand from the same `serverUrl` it computed for env vars.\n */\n serverUrl?: string\n}\n\n/**\n * Codex 0.129.0 broke two things the launcher had been relying on:\n * (1) `--full-auto` was removed in favor of `--sandbox` + `--ask-for-approval`;\n * passing it now exits the child immediately with\n * `error: unexpected argument '--full-auto' found`.\n * (2) `OPENAI_BASE_URL` is silently ignored — Codex hardcodes\n * `https://api.openai.com/v1/responses` and 401s out without an\n * explicit `-c model_providers.<name>.base_url` override.\n *\n * `buildCodexCmd` builds the launch argv that works on Codex 0.129+ while\n * still being compatible with older versions that accept the same flags.\n */\nfunction buildCodexCmd(target: LaunchTarget): string[] {\n const cmd: string[] = [\"codex\"]\n if (target.serverUrl) {\n cmd.push(...buildCodexProviderConfigFlags(target.serverUrl))\n }\n cmd.push(\n \"--sandbox\",\n \"workspace-write\",\n \"--ask-for-approval\",\n \"on-request\",\n \"-m\",\n target.model ?? DEFAULT_CODEX_MODEL,\n ...target.extraArgs,\n )\n return cmd\n}\n\nexport function buildLaunchCommand(target: LaunchTarget): {\n cmd: string[]\n env: Record<string, string | undefined>\n} {\n // F10: don't force `--dangerously-skip-permissions` when the caller already\n // requested an explicit `--permission-mode` (e.g. ai-or-die's claude-bridge\n // appends one for a fleet create_session with permissionMode). The two flags\n // conflict — claude rejects them together — and the explicit mode must win.\n const wantsPermissionMode =\n target.kind === \"claude-code\" &&\n target.extraArgs.some((arg) => arg === \"--permission-mode\" || arg.startsWith(\"--permission-mode=\"))\n const cmd: string[] =\n target.kind === \"claude-code\"\n ? wantsPermissionMode\n ? [\"claude\", ...target.extraArgs]\n : [\"claude\", \"--dangerously-skip-permissions\", ...target.extraArgs]\n : buildCodexCmd(target)\n\n // Anti-shadow: resolve the top-level CLI to an ABSOLUTE path against\n // the clean parent PATH (excluding the cwd). The spawned child's env\n // prepends the toolbelt bin dir to PATH; without this, a stray\n // `claude.cmd`/`codex.cmd` in that dir — or in an untrusted repo's cwd\n // (Windows resolves cwd before PATH under shell:true) — could shadow\n // the real CLI. Resolving here means the toolbelt PATH only affects\n // the agent's OWN tool lookups, never which CLI we launch.\n const resolved = resolveExecutable(cmd[0], { env: process.env })\n if (resolved) cmd[0] = resolved\n\n const env = collapsePathKeys({\n ...sanitizeParentEnv(process.env),\n ...target.envVars,\n })\n return { cmd, env }\n}\n\n/**\n * Whether a resolved Windows executable must be launched through cmd.exe\n * (`shell:true`). Only batch shims (`.cmd`/`.bat`) need it — and even then\n * cmd.exe stays alive as the CLI's parent, so `taskkill /T` reaps the\n * whole tree. A real `.exe` (e.g. the native-installer `claude.exe`) is\n * spawned DIRECTLY so the CLI is the direct child, with no cmd.exe\n * intermediary to orphan its node/MCP grandchildren on a kill.\n */\nexport function windowsLaunchNeedsShell(executable: string): boolean {\n const ext = path.extname(executable).toLowerCase()\n return ext === \".cmd\" || ext === \".bat\"\n}\n\nexport function launchChild(\n target: LaunchTarget,\n server: Server,\n options: {\n onShutdown?: () => Promise<void> | void\n /**\n * Optional exit summary (e.g. the AIC session total). Called once from\n * `exit()`, after cleanup, on every shutdown path (natural child exit,\n * signal escalation, spawn error). Return undefined for no output.\n * Best-effort: throwing content is swallowed so shutdown never blocks.\n */\n onExitSummary?: () => string | undefined\n } = {},\n): void {\n const { cmd, env } = buildLaunchCommand(target)\n\n const executable = cmd[0]\n if (!isExecutableAvailable(executable)) {\n const msg = `\"${executable}\" not found on PATH. Install it first, then try again.`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n process.exit(1)\n }\n\n let child: ChildProcess\n try {\n if (process.platform === \"win32\") {\n if (windowsLaunchNeedsShell(cmd[0])) {\n // A batch shim genuinely needs cmd.exe. cmd.exe stays alive as the\n // CLI's parent for the shim's lifetime, so the tree-kill (taskkill\n // /T) and the crash guard both reap the whole tree through it. Use\n // the full command as a single string to avoid the DEP0190\n // deprecation warning about shell + args.\n const quoted = cmd.map((a) => (a.includes(\" \") ? `\"${a}\"` : a)).join(\" \")\n child = spawn(quoted, [], {\n env,\n stdio: \"inherit\",\n shell: true,\n })\n } else {\n // A real .exe (e.g. the native-installer claude.exe) needs no\n // shell. Spawning it directly makes the CLI the DIRECT child, so\n // child.kill()/taskkill and the crash guard target the real\n // process instead of a cmd.exe wrapper that would orphan its\n // node/MCP grandchildren.\n child = spawn(cmd[0], cmd.slice(1), {\n env,\n stdio: \"inherit\",\n })\n }\n } else {\n // detached:true → the CLI leads its OWN process group, so cleanup()\n // can kill(-pgid) the whole tree (grandchildren included). We still\n // await its exit, so we do NOT unref(). Terminal Ctrl-C no longer\n // reaches the child directly — the signal handlers below forward it.\n child = spawn(cmd[0], cmd.slice(1), {\n env,\n stdio: \"inherit\",\n detached: true,\n })\n }\n } catch (error) {\n const msg = `Failed to launch ${executable}: ${error instanceof Error ? error.message : String(error)}`\n consola.error(msg)\n process.stderr.write(msg + \"\\n\")\n server.close(true).catch(() => {})\n if (options.onShutdown) {\n void Promise.resolve(options.onShutdown()).catch(() => {})\n }\n process.exit(1)\n }\n\n // Crash-safe net: if the proxy dies WITHOUT running cleanup() (hard\n // crash, SIGKILL/taskkill, OOM). On Windows this is the OS's job —\n // Node's KILL_ON_JOB_CLOSE job object reaps the tree — so this is a\n // no-op there. On POSIX (no such mechanism) it spawns a detached reaper\n // that sees the proxy's stdin-pipe EOF, re-verifies the child's\n // identity, and reaps the group (also backstopping a graceful shutdown\n // where the child ignores SIGTERM and the proxy exits before the\n // escalation lands). Fire-and-forget; self-acts and self-exits.\n startProcessGuard(child)\n\n let cleaned = false\n let exiting = false\n async function cleanup(): Promise<void> {\n if (cleaned) return\n cleaned = true\n\n // Arm the hard fail-safe FIRST, before any (potentially blocking) kill\n // or async teardown, so a wedged taskkill / server.close can't hang\n // shutdown indefinitely.\n const timeout = setTimeout(() => process.exit(1), 5000)\n\n // Tree-kill the whole CLI subtree, not just the direct child. On\n // Windows taskkill /T reaps grandchildren; on POSIX kill(-pgid) reaps\n // the detached process group. (Replaces a plain child.kill() that\n // orphaned node/MCP grandchildren on Windows.) A SIGTERM-ignoring\n // survivor that outlives our exit is caught by the crash guard above.\n try {\n killChildProcessTree(child, {\n detachedGroup: process.platform !== \"win32\",\n })\n } catch {\n // Already exited / best-effort.\n }\n\n try {\n await server.close(true)\n } catch {\n // Server already closed\n }\n if (options.onShutdown) {\n try {\n await options.onShutdown()\n } catch {\n // Best-effort cleanup; shutdown must not be blocked by it.\n }\n }\n clearTimeout(timeout)\n }\n\n function exit(code: number): void {\n if (exiting) return\n exiting = true\n try {\n const summary = options.onExitSummary?.()\n if (summary) process.stderr.write(`\\n${summary}\\n`)\n } catch {\n // Best-effort summary; never block shutdown.\n }\n process.exit(code)\n }\n\n // On POSIX the CLI is in its own process group (detached), so terminal\n // Ctrl-C reaches only the proxy. Forward the signal to the child group\n // so the CLI's own interactive handler runs (e.g. Claude's \"press\n // Ctrl-C again to exit\"); let its natural exit drive shutdown and\n // escalate to a hard tree-kill only if it ignores us past a grace.\n let forwardGrace: NodeJS.Timeout | null = null\n const lastForwardAt: Record<\"SIGINT\" | \"SIGTERM\", number> = {\n SIGINT: 0,\n SIGTERM: 0,\n }\n const onSignal = (sig: \"SIGINT\" | \"SIGTERM\") => {\n if (process.platform !== \"win32\" && child.pid && !cleaned) {\n // Debounce PER SIGNAL a sub-second burst into ONE forward. The other\n // subsystem signal handlers (keep-awake/colbert/worker/browser)\n // re-raise the SAME signal to restore default-terminate, which\n // re-invokes us several times within milliseconds — without this the\n // child would receive a volley and Claude's \"press Ctrl-C again\"\n // guard would never see a single press. Per-signal so a SIGTERM right\n // after a SIGINT still forwards. A genuine second human Ctrl-C\n // (seconds later) falls outside the window and forwards again.\n const now = Date.now()\n if (now - lastForwardAt[sig] > 250) {\n lastForwardAt[sig] = now\n try {\n process.kill(-child.pid, sig)\n } catch {\n // group already gone — fall through to the grace escalation\n }\n }\n const graceMs = sig === \"SIGINT\" ? 10000 : 3000\n if (!forwardGrace) {\n forwardGrace = setTimeout(() => {\n cleanup().then(() => exit(130)).catch(() => exit(1))\n }, graceMs)\n forwardGrace.unref?.()\n }\n return\n }\n // Windows: the console already delivered Ctrl-C to the direct child;\n // cleanup()'s taskkill /T is the escalation hammer.\n cleanup().then(() => exit(130)).catch(() => exit(1))\n }\n process.on(\"SIGINT\", () => onSignal(\"SIGINT\"))\n process.on(\"SIGTERM\", () => onSignal(\"SIGTERM\"))\n\n child.on(\"exit\", (exitCode, signal) => {\n // When the spawned CLI exits we may be holding worker-agent\n // worktrees that the proxy will never get a chance to clean up\n // through the per-call finally (the proxy itself is about to\n // shut down). Drain them synchronously here — same sweep the\n // SIGINT/SIGTERM handlers use. No-op if no worker tools were\n // ever invoked (registry is null).\n try {\n sweepRegistry()\n } catch {\n // best-effort; don't let cleanup failures override the exit code\n }\n // When killed by a signal, exitCode is null — derive from signal number\n const code = exitCode ?? (signal ? 128 : 1)\n cleanup().then(() => exit(code)).catch(() => exit(1))\n })\n child.on(\"error\", () => {\n cleanup().then(() => exit(1)).catch(() => exit(1))\n })\n}\n","/**\n * Toolbelt provisioner: materialize curated CLI tools into the durable\n * router `bin/` dir so the spawned agent can call them on PATH.\n *\n * Properties (all load-bearing):\n * - **Best-effort.** Every tool is isolated in try/catch; a failure\n * skips that one tool and never throws to the launcher.\n * - **Gap-fill.** A tool already on the user's PATH is NOT\n * materialized (and a stale copy is removed) so we never shadow the\n * user's own `jq`/`yq`/etc.\n * - **Verified.** The downloaded archive's SHA256 must match the\n * hardcoded manifest digest BEFORE extraction.\n * - **Safe extraction.** Only the expected regular-file binary is\n * pulled out; symlink/hardlink/device entries are rejected.\n * - **Concurrency-safe.** A cross-process lock serializes writers;\n * unique temp names + atomic rename avoid half-written binaries.\n * - **Integrity-pruned.** Unexpected files in `bin/` are removed each\n * launch so a prompt-injected agent can't plant a `git.cmd` that\n * shadows real tools in future sessions.\n */\n\nimport { randomBytes, createHash } from \"node:crypto\"\nimport { existsSync } from \"node:fs\"\nimport {\n chmod,\n copyFile,\n link,\n mkdir,\n readdir,\n readFile,\n rename,\n rm,\n writeFile,\n} from \"node:fs/promises\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\nimport consola from \"consola\"\n\nimport { resolveExecutable, runCommandCapture } from \"../exec\"\nimport { PATHS } from \"../paths\"\nimport { withInstallLock } from \"../update-lock\"\n\nimport { extractTarGzMember, extractZipMember } from \"./extract\"\nimport { toolbeltEnabled, toolbeltSkipSet, vscodeRipgrepPath } from \"./index\"\nimport { assetFor, satisfiesMinVersion, TOOLBELT_TOOLS, type ToolAsset, type ToolSpec } from \"./manifest\"\n\n/** Per-download cap (bytes) — these binaries are a few MB at most. */\nconst MAX_DOWNLOAD_BYTES = 64 * 1024 * 1024\nconst DOWNLOAD_TIMEOUT_MS = 30_000\n\nconst EXE_EXT = process.platform === \"win32\" ? \".exe\" : \"\"\n\n/**\n * Materialize the toolbelt. Returns the list of command names exposed\n * in `bin/` after provisioning. Best-effort; never throws.\n */\nexport async function provisionToolbelt(): Promise<string[]> {\n if (!toolbeltEnabled()) return []\n const binDir = PATHS.TOOLBELT_BIN_DIR\n try {\n await mkdir(binDir, { recursive: true })\n } catch (err) {\n consola.debug(\"toolbelt: could not create bin dir:\", err)\n return []\n }\n\n const skip = toolbeltSkipSet()\n\n // Serialize across concurrent proxies. If another process holds the\n // lock it is provisioning; we skip and just report current state.\n await withInstallLock(\"toolbelt.lock\", async () => {\n await pruneUnexpected(binDir)\n await provisionRg(binDir, skip).catch((err) =>\n consola.debug(\"toolbelt: rg skipped:\", err),\n )\n await Promise.all(\n TOOLBELT_TOOLS.map((spec) =>\n provisionTool(spec, binDir, skip).catch((err) =>\n consola.debug(`toolbelt: ${spec.command} skipped:`, err),\n ),\n ),\n )\n })\n\n return exposedCommands(binDir)\n}\n\n/** Names allowed to live in `bin/` (managed binaries + their sidecars). */\nfunction expectedFileNames(): Set<string> {\n const names = new Set<string>()\n const add = (base: string) => {\n names.add(base + EXE_EXT)\n names.add(`${base}${EXE_EXT}.sha256`)\n }\n add(\"rg\")\n for (const spec of TOOLBELT_TOOLS) {\n add(spec.binBasename)\n for (const a of spec.aliases ?? []) add(a)\n }\n return names\n}\n\n/** Remove any file in `bin/` that isn't a managed binary or sidecar. */\nasync function pruneUnexpected(binDir: string): Promise<void> {\n const expected = expectedFileNames()\n let entries: string[]\n try {\n entries = await readdir(binDir)\n } catch {\n return\n }\n for (const name of entries) {\n if (name.endsWith(\".tmp\")) continue // in-flight temp from a peer\n if (!expected.has(name)) {\n await rm(path.join(binDir, name), { force: true }).catch(() => {})\n }\n }\n}\n\nasync function provisionRg(binDir: string, skip: Set<string>): Promise<void> {\n const dest = path.join(binDir, \"rg\" + EXE_EXT)\n if (skip.has(\"rg\") || resolveExecutable(\"rg\")) {\n // User has rg (or skipped) — don't shadow it. code_search uses its\n // own resolver regardless.\n await removeBin(dest)\n return\n }\n if (existsSync(dest)) return // already materialized\n const src = vscodeRipgrepPath()\n if (!src) return\n\n const tmp = tempName(dest)\n try {\n await link(src, tmp) // hardlink (same-volume fast path)\n } catch {\n await copyFile(src, tmp) // cross-volume (EXDEV) or other → copy\n }\n if (process.platform !== \"win32\") await chmod(tmp, 0o755).catch(() => {})\n await commit(tmp, dest)\n}\n\n/**\n * Does the user's own copy on PATH satisfy `spec.minVersion`?\n *\n * Returns true when there is nothing to check (no minVersion), and — this\n * is the load-bearing part — ALSO returns true when the probe fails or the\n * output cannot be parsed. Gap-fill exists so we never shadow a user's\n * pinned or wrapper install, and an unreadable `--version` is not evidence\n * that their tool is inadequate. We only step in front of it on positive\n * proof that it is too old.\n */\nasync function pathCopyIsAdequate(spec: ToolSpec, resolved: string): Promise<boolean> {\n if (!spec.minVersion) return true\n try {\n // `runCommandCapture`, NOT `runManagedExeCapture`: on Windows a tool\n // installed via npm resolves to a `.cmd` shim, which cannot be spawned\n // with `shell:false` (it throws EINVAL). That is exactly how the\n // ast-grep case presented — the probe threw, we treated the version as\n // unknown, and yielded to the outdated copy anyway. runCommandCapture\n // goes through buildExecInvocation, which routes shims via cmd.exe.\n const res = await runCommandCapture([resolved, \"--version\"], {\n timeoutMs: VERSION_PROBE_TIMEOUT_MS,\n })\n const verdict = satisfiesMinVersion(`${res.stdout} ${res.stderr}`, spec.minVersion)\n if (verdict === undefined) return true\n if (!verdict) {\n consola.debug(\n `[toolbelt] ${spec.command} on PATH is older than ${spec.minVersion}; using the router-pinned build instead`,\n )\n }\n return verdict\n } catch {\n return true\n }\n}\n\n/** A `--version` call should be instant; this only bounds a hung binary. */\nconst VERSION_PROBE_TIMEOUT_MS = 5_000\n\nasync function provisionTool(\n spec: ToolSpec,\n binDir: string,\n skip: Set<string>,\n): Promise<void> {\n const dest = path.join(binDir, spec.binBasename + EXE_EXT)\n const sidecar = `${dest}.sha256`\n const asset = assetFor(spec)\n\n if (skip.has(spec.command) || !asset) {\n await removeTool(spec, binDir)\n return\n }\n // Gap-fill: user already has it on PATH → remove ours, don't shadow.\n // Exception: a tool carrying `minVersion` is one this project's own code\n // depends on, so an older PATH copy is not \"good enough\" — it degrades\n // us silently. In that case we materialize ours anyway; the toolbelt bin\n // is prepended to the spawned agent's PATH and code_search checks the\n // router-owned paths first, so ours wins where it matters while the\n // user's shell keeps theirs.\n const onPath = resolveExecutable(spec.command)\n if (onPath && (await pathCopyIsAdequate(spec, onPath))) {\n await removeTool(spec, binDir)\n return\n }\n // Idempotent: present + sidecar matches → just ensure aliases exist.\n if (existsSync(dest) && (await sidecarMatches(sidecar, asset.sha256))) {\n await ensureAliases(spec, binDir, dest)\n return\n }\n\n const bytes = await downloadAndExtract(spec, asset)\n await atomicInstall(dest, bytes)\n await writeFile(sidecar, asset.sha256).catch(() => {})\n await ensureAliases(spec, binDir, dest)\n}\n\nasync function downloadAndExtract(\n spec: ToolSpec,\n asset: ToolAsset,\n): Promise<Buffer> {\n const data = await download(asset.url)\n const digest = createHash(\"sha256\").update(data).digest(\"hex\")\n if (digest !== asset.sha256) {\n throw new Error(\n `checksum mismatch for ${spec.command} (${asset.url}): expected ${asset.sha256}, got ${digest}`,\n )\n }\n if (asset.archive === \"raw\") return data\n const member =\n asset.archive === \"zip\"\n ? extractZipMember(data, spec.binBasename)\n : extractTarGzMember(data, spec.binBasename)\n if (!member) {\n throw new Error(`binary \"${spec.binBasename}\" not found in ${asset.url}`)\n }\n return member\n}\n\nasync function download(url: string): Promise<Buffer> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS)\n try {\n const res = await fetch(url, {\n signal: controller.signal,\n redirect: \"follow\",\n headers: { \"User-Agent\": \"github-router-toolbelt\" },\n })\n if (!res.ok) throw new Error(`download ${url}: HTTP ${res.status}`)\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.length > MAX_DOWNLOAD_BYTES) {\n throw new Error(`download ${url}: exceeds ${MAX_DOWNLOAD_BYTES} bytes`)\n }\n return buf\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** Write to a unique temp then atomically rename into place. */\nasync function atomicInstall(dest: string, bytes: Buffer): Promise<void> {\n const tmp = tempName(dest)\n await writeFile(tmp, bytes)\n if (process.platform !== \"win32\") await chmod(tmp, 0o755).catch(() => {})\n await commit(tmp, dest)\n}\n\n/** Rename tmp→dest, handling Windows replace-existing / in-use locks. */\nasync function commit(tmp: string, dest: string): Promise<void> {\n try {\n await rename(tmp, dest)\n } catch {\n // Windows: rename won't replace an existing/locked dest. Remove and\n // retry once; if that fails (in-use .exe), leave the existing copy.\n try {\n await rm(dest, { force: true })\n await rename(tmp, dest)\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => {})\n throw err\n }\n }\n}\n\nasync function ensureAliases(\n spec: ToolSpec,\n binDir: string,\n dest: string,\n): Promise<void> {\n for (const alias of spec.aliases ?? []) {\n const ap = path.join(binDir, alias + EXE_EXT)\n if (existsSync(ap)) continue\n const tmp = tempName(ap)\n try {\n await copyFile(dest, tmp)\n if (process.platform !== \"win32\") await chmod(tmp, 0o755).catch(() => {})\n await commit(tmp, ap)\n } catch (err) {\n consola.debug(`toolbelt: alias ${alias} skipped:`, err)\n }\n }\n}\n\nasync function removeTool(spec: ToolSpec, binDir: string): Promise<void> {\n const dest = path.join(binDir, spec.binBasename + EXE_EXT)\n await removeBin(dest)\n for (const alias of spec.aliases ?? []) {\n await removeBin(path.join(binDir, alias + EXE_EXT))\n }\n}\n\nasync function removeBin(dest: string): Promise<void> {\n await rm(dest, { force: true }).catch(() => {})\n await rm(`${dest}.sha256`, { force: true }).catch(() => {})\n}\n\nasync function sidecarMatches(sidecar: string, sha256: string): Promise<boolean> {\n try {\n return (await readFile(sidecar, \"utf8\")).trim() === sha256\n } catch {\n return false\n }\n}\n\nfunction tempName(dest: string): string {\n return `${dest}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`\n}\n\n/** The command names currently exposed in `bin/`. */\nasync function exposedCommands(binDir: string): Promise<string[]> {\n let files: Set<string>\n try {\n files = new Set(await readdir(binDir))\n } catch {\n return []\n }\n const present = (base: string) => files.has(base + EXE_EXT)\n const out: string[] = []\n if (present(\"rg\")) out.push(\"rg\")\n for (const spec of TOOLBELT_TOOLS) {\n if (present(spec.binBasename)) out.push(spec.command)\n }\n return out\n}\n"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
- import { At as scribeModel, Ct as generalPurposeFastModel, D as toolbeltEnabled, Dt as reviewerFastModel, E as buildToolbeltAwareness, Et as resolveGeminiReviewModel, Mt as workerToolsEnabled, Ot as reviewerModel, St as geminiAvailable, T as availableToolCommands, Tt as nativeSubagentModel, Zt as provisionBrowserAssets, bn as pickClaudeDefault, c as enumerateInjectedMcpToolNames, ct as browserCompoundToolsEnabled, en as provisionAndIndexColbert, fn as DEFAULT_CLAUDE_MODEL_FALLBACKS, it as agentToolsEnabled, jt as standInToolEnabled, kt as scoutModel, lt as browserToolsEnabled, o as buildPeerAwarenessSnippet, ot as brainstormModel, st as browseAgentEnabled, w as buildEnv, wt as implementerFastModel, xn as resolveLeadSlugArg, xt as fleetToolsEnabled } from "./peer-mcp-personas-CYWJSxPK.js";
1
+ import { At as resolveGeminiReviewModel, D as toolbeltEnabled, Dt as generalPurposeFastModel, E as buildToolbeltAwareness, Et as geminiAvailable, Ft as standInToolEnabled, It as workerToolsEnabled, Mt as reviewerModel, Nt as scoutModel, Ot as implementerFastModel, Pt as scribeModel, T as availableToolCommands, Tn as resolveLeadSlugArg, Tt as fleetToolsEnabled, at as agentToolsEnabled, c as enumerateInjectedMcpToolNames, ct as browseAgentEnabled, gn as DEFAULT_CLAUDE_MODEL_FALLBACKS, in as provisionAndIndexColbert, jt as reviewerFastModel, kt as nativeSubagentModel, lt as browserCompoundToolsEnabled, o as buildPeerAwarenessSnippet, st as brainstormModel, tn as provisionBrowserAssets, ut as browserToolsEnabled, w as buildEnv, wn as pickClaudeDefault } from "./peer-mcp-personas-CQ-0Piyr.js";
2
2
  import { t as getPackageVersion } from "./version-C8x2hQrZ.js";
3
3
  import { o as removeOwnClaudeConfigMirror, r as ensureClaudeConfigMirror, t as PATHS } from "./paths-CMxlQEfQ.js";
4
- import { t as state } from "./state-B8adscCv.js";
5
- import { d as resolveModel, m as getGitHubUser } from "./token-ClzS23Y2.js";
4
+ import { t as state } from "./state-BlMpEFAt.js";
5
+ import { d as resolveModel, m as getGitHubUser } from "./token-C1gthnbV.js";
6
6
  import { n as killChildProcessTree, o as resolveExecutable, s as runCommandCapture } from "./exec-COUT4QsS.js";
7
- import { S as runSelfUpdate, _ as resolveLaunchProfile, a as sharedServerArgs, c as stopKeepAwake, i as setupAndServe, l as injectModelPickerSettingsFile, r as parseSharedArgs, s as startKeepAwake, t as getClaudeCodeEnvVars } from "./server-setup-D7ABbEs1.js";
7
+ import { a as sharedServerArgs, c as stopKeepAwake, i as setupAndServe, l as injectModelPickerSettingsFile, r as parseSharedArgs, s as startKeepAwake, t as getClaudeCodeEnvVars, v as resolveLaunchProfile, w as runSelfUpdate } from "./server-setup-iLG8LrfL.js";
8
8
  import { n as hookLauncherDegradedWarning, r as resolveSelfInvocation } from "./self-invocation-cRl4RqfW.js";
9
- import { r as getCodexVersion, t as provisionToolbelt } from "./provision-AwSjt-vR.js";
9
+ import { r as getCodexVersion, t as provisionToolbelt } from "./provision-_R7ZMygj.js";
10
10
  import { s as injectStopHookIntoSettingsFile } from "./stop-gate-hook-BUCxa5Kc.js";
11
11
  import { l as guardToolMatcher, n as activeDispatchModes, r as buildWorkerGuardHookCommand } from "./worker-dispatch-DMIdrlsF.js";
12
- import { C as writePeerMcpRuntimeFiles, S as workersKeyOf, _ as sanitizeServeSettingsEnv, a as appendPeerAwarenessToMirroredClaudeMd, b as resolveCodexCliBackend, c as buildOperatingDefaultsDirective, d as prependStyleDirectiveToMirroredClaudeMd, g as planModeAllowRules, h as injectAllowRules, i as writeInjectedSkill, m as configureServeDefaultPermissionMode, n as INJECTED_SKILLS, o as appendToolbeltAwarenessToMirroredClaudeMd, p as SEAMLESS_BUILTIN_TOOLS, t as injectAttributionSuppressionIntoSettingsFile, u as prependOperatingDefaultsToMirroredClaudeMd, v as BUILTIN_SUBAGENT_DEFINITIONS, x as resolveGroupKeysFromMirror, y as injectPeerMcpIntoMirror } from "./attribution-settings-DXmmyuEE.js";
12
+ import { C as writePeerMcpRuntimeFiles, S as workersKeyOf, _ as sanitizeServeSettingsEnv, a as appendPeerAwarenessToMirroredClaudeMd, b as resolveCodexCliBackend, c as buildOperatingDefaultsDirective, d as prependStyleDirectiveToMirroredClaudeMd, g as planModeAllowRules, h as injectAllowRules, i as writeInjectedSkill, m as configureServeDefaultPermissionMode, n as INJECTED_SKILLS, o as appendToolbeltAwarenessToMirroredClaudeMd, p as SEAMLESS_BUILTIN_TOOLS, t as injectAttributionSuppressionIntoSettingsFile, u as prependOperatingDefaultsToMirroredClaudeMd, v as BUILTIN_SUBAGENT_DEFINITIONS, x as resolveGroupKeysFromMirror, y as injectPeerMcpIntoMirror } from "./attribution-settings-jcsSeVur.js";
13
13
  import { i as planReviewEnabled, t as buildPlanReviewHookCommand } from "./plan-review-hook-BIMwWkh6.js";
14
14
  import { t as buildPromptSubmitHookCommand } from "./prompt-submit-hook-BcFpjHXg.js";
15
15
  import { defineCommand } from "citty";
@@ -1559,4 +1559,4 @@ const serve = defineCommand({
1559
1559
  //#endregion
1560
1560
  export { serve };
1561
1561
 
1562
- //# sourceMappingURL=serve-BG2O6Slz.js.map
1562
+ //# sourceMappingURL=serve-DU66K5kv.js.map