github-router 0.3.321 → 0.3.323

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 (105) hide show
  1. package/dist/{aic-ledger-BrDqsuge.js → aic-ledger-DRU89gJg.js} +20 -3
  2. package/dist/aic-ledger-DRU89gJg.js.map +1 -0
  3. package/dist/{attribution-settings-DHWo0lyG.js → attribution-settings-CX-kIrT8.js} +292 -101
  4. package/dist/attribution-settings-CX-kIrT8.js.map +1 -0
  5. package/dist/{auth-Iun7ftd8.js → auth-DSLN1arH.js} +3 -3
  6. package/dist/{auth-Iun7ftd8.js.map → auth-DSLN1arH.js.map} +1 -1
  7. package/dist/browser-ext/manifest.json +1 -1
  8. package/dist/{check-usage-1c-0z3iy.js → check-usage-B_12ALvf.js} +4 -4
  9. package/dist/{check-usage-1c-0z3iy.js.map → check-usage-B_12ALvf.js.map} +1 -1
  10. package/dist/{claude-uAFPKvuS.js → claude-Cq1xGzbI.js} +61 -49
  11. package/dist/claude-Cq1xGzbI.js.map +1 -0
  12. package/dist/{codex-CVHdye0h.js → codex-INt1hc_5.js} +5 -5
  13. package/dist/{codex-CVHdye0h.js.map → codex-INt1hc_5.js.map} +1 -1
  14. package/dist/{copilot-discount-BnJkEojk.js → copilot-discount-D8t7Q3Tp.js} +2 -2
  15. package/dist/{copilot-discount-BnJkEojk.js.map → copilot-discount-D8t7Q3Tp.js.map} +1 -1
  16. package/dist/{debug-CDyXUPcC.js → debug-D8iD6G4O.js} +2 -2
  17. package/dist/{debug-CDyXUPcC.js.map → debug-D8iD6G4O.js.map} +1 -1
  18. package/dist/engine-4uuaURb0.js +2 -0
  19. package/dist/{fast-profile-contract-_DhIWank.js → fast-profile-contract-DmsyQQrw.js} +6 -11
  20. package/dist/fast-profile-contract-DmsyQQrw.js.map +1 -0
  21. package/dist/{gate-discovery-DUBuxiLb.js → gate-discovery-Z0QyjqM7.js} +5 -5
  22. package/dist/{gate-discovery-DUBuxiLb.js.map → gate-discovery-Z0QyjqM7.js.map} +1 -1
  23. package/dist/{get-copilot-usage-BWCAzmSc.js → get-copilot-usage-Bs-FEPMf.js} +2 -2
  24. package/dist/{get-copilot-usage-BWCAzmSc.js.map → get-copilot-usage-Bs-FEPMf.js.map} +1 -1
  25. package/dist/hooks.mjs +6 -12
  26. package/dist/hooks.sha256 +1 -1
  27. package/dist/{internal-aic-status-Dc2JQDsX.js → internal-aic-status-BaUKlisM.js} +3 -3
  28. package/dist/{internal-aic-status-Dc2JQDsX.js.map → internal-aic-status-BaUKlisM.js.map} +1 -1
  29. package/dist/{internal-artifact-open-CA1trJ15.js → internal-artifact-open-U-Ht7R-O.js} +2 -2
  30. package/dist/{internal-artifact-open-CA1trJ15.js.map → internal-artifact-open-U-Ht7R-O.js.map} +1 -1
  31. package/dist/{internal-fast-dispatch-guard-CZ4BZuAG.js → internal-fast-dispatch-guard-Dnjoj0pV.js} +4 -5
  32. package/dist/internal-fast-dispatch-guard-Dnjoj0pV.js.map +1 -0
  33. package/dist/{internal-fast-dispatch-guard-D5kozMpo.js → internal-fast-dispatch-guard-uztSu0c8.js} +1 -1
  34. package/dist/{internal-first-mate-guard-B_or0eN3.js → internal-first-mate-guard-Dvd1vZ4l.js} +1 -1
  35. package/dist/{internal-first-mate-guard-CiFH6X1H.js → internal-first-mate-guard-mvfZal_M.js} +3 -3
  36. package/dist/{internal-first-mate-guard-CiFH6X1H.js.map → internal-first-mate-guard-mvfZal_M.js.map} +1 -1
  37. package/dist/{internal-max-dispatch-guard-BGOHRYRt.js → internal-max-dispatch-guard-CxG_c3cc.js} +1 -1
  38. package/dist/{internal-max-dispatch-guard-CnBZxo5l.js → internal-max-dispatch-guard-Sp_rCYk_.js} +2 -2
  39. package/dist/{internal-max-dispatch-guard-CnBZxo5l.js.map → internal-max-dispatch-guard-Sp_rCYk_.js.map} +1 -1
  40. package/dist/{internal-plan-review-C73snnD8.js → internal-plan-review-Co01iHOW.js} +3 -3
  41. package/dist/{internal-plan-review-C73snnD8.js.map → internal-plan-review-Co01iHOW.js.map} +1 -1
  42. package/dist/{internal-prompt-submit-dov4UvBo.js → internal-prompt-submit-dflz0bqH.js} +4 -4
  43. package/dist/{internal-prompt-submit-dov4UvBo.js.map → internal-prompt-submit-dflz0bqH.js.map} +1 -1
  44. package/dist/{internal-session-bind-C3hrb9rP.js → internal-session-bind-BVQy7Vd4.js} +2 -2
  45. package/dist/{internal-session-bind-C3hrb9rP.js.map → internal-session-bind-BVQy7Vd4.js.map} +1 -1
  46. package/dist/{internal-stop-hook-DCjYE2x0.js → internal-stop-hook-DihBVzn2.js} +5 -5
  47. package/dist/{internal-stop-hook-DCjYE2x0.js.map → internal-stop-hook-DihBVzn2.js.map} +1 -1
  48. package/dist/{internal-stop-review-MBVYtrYS.js → internal-stop-review-CrnG4IJo.js} +2 -2
  49. package/dist/{internal-stop-review-MBVYtrYS.js.map → internal-stop-review-CrnG4IJo.js.map} +1 -1
  50. package/dist/{internal-worker-guard-dxGawAY3.js → internal-worker-guard-BSoS4wzV.js} +2 -2
  51. package/dist/{internal-worker-guard-dxGawAY3.js.map → internal-worker-guard-BSoS4wzV.js.map} +1 -1
  52. package/dist/{internal-workspace-header-_aEXOz5q.js → internal-workspace-header-booLOzwD.js} +2 -2
  53. package/dist/{internal-workspace-header-_aEXOz5q.js.map → internal-workspace-header-booLOzwD.js.map} +1 -1
  54. package/dist/{lifecycle-CJB2BwSJ.js → lifecycle-BYPEqFdV.js} +2 -2
  55. package/dist/{lifecycle-CJB2BwSJ.js.map → lifecycle-BYPEqFdV.js.map} +1 -1
  56. package/dist/{lifecycle-_BE0SnNs.js → lifecycle-C1LDvDD6.js} +2 -2
  57. package/dist/{lifecycle-_BE0SnNs.js.map → lifecycle-C1LDvDD6.js.map} +1 -1
  58. package/dist/lifecycle-Cz99P30h.js +2 -0
  59. package/dist/lifecycle-DqTFEJpS.js +2 -0
  60. package/dist/main.js +20 -20
  61. package/dist/{mcp-workspace-header-mXRLE8kg.js → mcp-workspace-header-BIW0SRgs.js} +2 -2
  62. package/dist/{mcp-workspace-header-mXRLE8kg.js.map → mcp-workspace-header-BIW0SRgs.js.map} +1 -1
  63. package/dist/{models-CSFIVxAH.js → models-B9tdaM6B.js} +3 -3
  64. package/dist/{models-CSFIVxAH.js.map → models-B9tdaM6B.js.map} +1 -1
  65. package/dist/{orchestration-B8kypXcZ.js → orchestration-DvsdorFq.js} +2 -2
  66. package/dist/{orchestration-B8kypXcZ.js.map → orchestration-DvsdorFq.js.map} +1 -1
  67. package/dist/{paths-CHBAj_t9.js → paths-BnZwolac.js} +4 -4
  68. package/dist/{paths-CHBAj_t9.js.map → paths-BnZwolac.js.map} +1 -1
  69. package/dist/paths-BzM6uxmd.js +2 -0
  70. package/dist/{peer-mcp-personas-BerVEkZ9.js → peer-mcp-personas-DcAErb_d.js} +209 -71
  71. package/dist/peer-mcp-personas-DcAErb_d.js.map +1 -0
  72. package/dist/{plan-review-hook-BMmw7wxR.js → plan-review-hook-BlsGnG02.js} +3 -3
  73. package/dist/{plan-review-hook-BMmw7wxR.js.map → plan-review-hook-BlsGnG02.js.map} +1 -1
  74. package/dist/{prompt-submit-hook-CGgT5m4Q.js → prompt-submit-hook-BJFLzCaT.js} +3 -3
  75. package/dist/{prompt-submit-hook-CGgT5m4Q.js.map → prompt-submit-hook-BJFLzCaT.js.map} +1 -1
  76. package/dist/{provision-o4bXbsvB.js → provision-B-a5KUCL.js} +4 -4
  77. package/dist/{provision-o4bXbsvB.js.map → provision-B-a5KUCL.js.map} +1 -1
  78. package/dist/{self-invocation-DahN1gw9.js → self-invocation-opSXHEs0.js} +2 -2
  79. package/dist/{self-invocation-DahN1gw9.js.map → self-invocation-opSXHEs0.js.map} +1 -1
  80. package/dist/{serve-DSL2Fcnc.js → serve-TiUhvpVp.js} +12 -12
  81. package/dist/{serve-DSL2Fcnc.js.map → serve-TiUhvpVp.js.map} +1 -1
  82. package/dist/{server-setup-CXXKmPIE.js → server-setup-CgbHmupe.js} +205 -57
  83. package/dist/server-setup-CgbHmupe.js.map +1 -0
  84. package/dist/{start-Cl_1UMg_.js → start-DNzOFabp.js} +3 -3
  85. package/dist/{start-Cl_1UMg_.js.map → start-DNzOFabp.js.map} +1 -1
  86. package/dist/{stop-gate-hook-D7N759TG.js → stop-gate-hook-DvNM8zFf.js} +3 -3
  87. package/dist/{stop-gate-hook-D7N759TG.js.map → stop-gate-hook-DvNM8zFf.js.map} +1 -1
  88. package/dist/{stop-gate-policy-C0gt04R0.js → stop-gate-policy-C5pXbY59.js} +2 -2
  89. package/dist/{stop-gate-policy-C0gt04R0.js.map → stop-gate-policy-C5pXbY59.js.map} +1 -1
  90. package/dist/{token-86uk6y4P.js → token-Css-ARGL.js} +2 -2
  91. package/dist/{token-86uk6y4P.js.map → token-Css-ARGL.js.map} +1 -1
  92. package/dist/{worker-dispatch-B-OA7tvU.js → worker-dispatch-DwS99DXz.js} +2 -2
  93. package/dist/{worker-dispatch-B-OA7tvU.js.map → worker-dispatch-DwS99DXz.js.map} +1 -1
  94. package/package.json +1 -1
  95. package/dist/aic-ledger-BrDqsuge.js.map +0 -1
  96. package/dist/attribution-settings-DHWo0lyG.js.map +0 -1
  97. package/dist/claude-uAFPKvuS.js.map +0 -1
  98. package/dist/engine-57iIqICe.js +0 -2
  99. package/dist/fast-profile-contract-_DhIWank.js.map +0 -1
  100. package/dist/internal-fast-dispatch-guard-CZ4BZuAG.js.map +0 -1
  101. package/dist/lifecycle-D0KsoSqz.js +0 -2
  102. package/dist/lifecycle-DimEguJO.js +0 -2
  103. package/dist/paths-eHoOwFzZ.js +0 -2
  104. package/dist/peer-mcp-personas-BerVEkZ9.js.map +0 -1
  105. package/dist/server-setup-CXXKmPIE.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as buildSelfCommand } from "./self-invocation-DahN1gw9.js";
1
+ import { t as buildSelfCommand } from "./self-invocation-opSXHEs0.js";
2
2
  //#region src/lib/worker-dispatch.ts
3
3
  /**
4
4
  * Frozen contract for the NON-BLOCKING workers surface.
@@ -223,4 +223,4 @@ function parseModesCsv(csv) {
223
223
  //#endregion
224
224
  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
225
 
226
- //# sourceMappingURL=worker-dispatch-B-OA7tvU.js.map
226
+ //# sourceMappingURL=worker-dispatch-DwS99DXz.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"worker-dispatch-B-OA7tvU.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-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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-router",
3
- "version": "0.3.321",
3
+ "version": "0.3.323",
4
4
  "license": "MIT",
5
5
  "description": "A reverse proxy that exposes GitHub Copilot as OpenAI and Anthropic compatible API endpoints.",
6
6
  "keywords": [
@@ -1 +0,0 @@
1
- {"version":3,"file":"aic-ledger-BrDqsuge.js","names":["fsp"],"sources":["../src/lib/aic-usage.ts","../src/lib/aic-ledger.ts"],"sourcesContent":["/**\n * AIC (AI Credits) extraction from upstream Copilot `copilot_usage` fields.\n *\n * Verified live (2026-09-14) on all three native endpoints: `/responses`\n * (non-stream top-level + terminal `response.completed` event), `/chat/completions`\n * (non-stream top-level), and `/v1/messages` (non-stream top-level + terminal\n * `message_delta` event). No special request header is required — the field\n * arrives with the proxy's stock headers.\n *\n * Shape:\n * ```json\n * \"copilot_usage\": {\n * \"token_details\": [\n * {\"batch_size\":1000000,\"cost_per_batch\":20000000000,\n * \"model\":\"gpt-5.6-luna\",\"token_count\":11,\"token_type\":\"input\"},\n * ...\n * ],\n * \"total_nano_aiu\": 820000\n * }\n * ```\n * Credits = `total_nano_aiu / 1e9` (SI nano prefix — the same convention the\n * Copilot SDK docs use; treat GitHub billing docs as source of truth before\n * surfacing currency equivalents). Per-type cost reconstructs as\n * `token_count × cost_per_batch / batch_size / 1e9` (verified arithmetically\n * against `total_nano_aiu` on live captures).\n *\n * All parsing is total and never throws: malformed entries are skipped, and a\n * missing/invalid field yields `undefined` so callers fall back cleanly.\n */\n\n/** One entry of `copilot_usage.token_details`. */\nexport interface CopilotTokenDetail {\n model?: string\n tokenType?: string\n tokenCount: number\n batchSize: number\n costPerBatch: number\n}\n\n/** Parsed `copilot_usage` object. */\nexport interface CopilotUsage {\n totalNanoAiu: number\n tokenDetails: Array<CopilotTokenDetail>\n}\n\n/** Nano-AIU per AI credit (SI prefix, per Copilot SDK docs). */\nexport const NANO_AIU_PER_CREDIT = 1_000_000_000\n\nfunction nonNegativeNumber(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) {\n return 0\n }\n return value\n}\n\nfunction parseTokenDetail(value: unknown): CopilotTokenDetail | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n const record = value as Record<string, unknown>\n const batchSize = nonNegativeNumber(record.batch_size)\n if (batchSize <= 0) return undefined\n return {\n ...(typeof record.model === \"string\" && record.model.length > 0\n ? { model: record.model }\n : {}),\n ...(typeof record.token_type === \"string\" && record.token_type.length > 0\n ? { tokenType: record.token_type }\n : {}),\n tokenCount: Math.floor(nonNegativeNumber(record.token_count)),\n batchSize: Math.floor(batchSize),\n costPerBatch: nonNegativeNumber(record.cost_per_batch),\n }\n}\n\n/**\n * Extract a validated `CopilotUsage` from an unknown value (typically\n * `responseBody.copilot_usage` or an SSE event's `copilot_usage`).\n * Returns `undefined` when absent or invalid — never throws.\n */\nexport function extractCopilotUsage(value: unknown): CopilotUsage | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n const record = value as Record<string, unknown>\n if (\n typeof record.total_nano_aiu !== \"number\"\n || !Number.isFinite(record.total_nano_aiu)\n || record.total_nano_aiu < 0\n ) {\n return undefined\n }\n const details: Array<CopilotTokenDetail> = []\n if (Array.isArray(record.token_details)) {\n for (const entry of record.token_details) {\n const parsed = parseTokenDetail(entry)\n if (parsed) details.push(parsed)\n }\n }\n return {\n totalNanoAiu: Math.floor(record.total_nano_aiu),\n tokenDetails: details,\n }\n}\n\n/** Convert nano-AIU to AI credits. */\nexport function nanoAiuToCredits(nanoAiu: number): number {\n if (!Number.isFinite(nanoAiu) || nanoAiu <= 0) return 0\n return nanoAiu / NANO_AIU_PER_CREDIT\n}\n\n/** Credits for one token-detail entry. */\nexport function creditsForDetail(detail: CopilotTokenDetail): number {\n return nanoAiuToCredits(\n (detail.tokenCount * detail.costPerBatch) / detail.batchSize,\n )\n}\n\n/**\n * Compact display: `12.42` for totals ≥ 0.01, more precision below.\n * Always a plain number string — callers add units/context.\n */\nexport function formatAic(credits: number): string {\n if (!Number.isFinite(credits) || credits <= 0) return \"0\"\n if (credits >= 100) return credits.toFixed(1)\n if (credits >= 1) return credits.toFixed(2)\n if (credits >= 0.01) return credits.toFixed(3)\n return credits.toPrecision(2)\n}\n","/**\n * Per-launch AIC (AI Credits) ledger.\n *\n * Every route handler records upstream `copilot_usage` here as a side effect\n * (bytes pass through untouched). The totals feed two consumers:\n *\n * 1. `internal-aic-status` — runs as a SEPARATE process on every status-line\n * tick, so it cannot read in-process memory. Each `record()` therefore\n * also persists an atomic snapshot file; the ledger path is handed to the\n * spawned child via `GH_ROUTER_AIC_LEDGER` and baked into the statusLine\n * command.\n * 2. The `claude` exit summary — reads the in-process snapshot.\n *\n * Keyed per proxy process (one file per PID under `<APP_DIR>/aic-ledger/`),\n * so concurrent launches never mix. Snapshot writes are best-effort and never\n * throw into the request path. Stale files (dead PID, >24h) are swept at\n * startup of the next record and on shutdown.\n */\n\nimport fs from \"node:fs\"\nimport fsp from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { PATHS } from \"~/lib/paths\"\n\nimport {\n extractCopilotUsage,\n formatAic,\n nanoAiuToCredits,\n type CopilotUsage,\n} from \"./aic-usage\"\n\nexport interface AicModelEntry {\n nanoAiu: number\n requests: number\n}\n\n/** Token counts per model per upstream `token_type`, for the cost table. */\nexport interface AicModelTokens {\n /** Upstream `input` type: fresh (uncached) input tokens. */\n input: number\n /** Upstream `cache_read` type. */\n cache_read: number\n /** Upstream `cache_write` type. */\n cache_write: number\n /** Upstream `output` type. */\n output: number\n /** Any other/missing `token_type`: never dropped silently, priced at input. */\n other: number\n}\n\nexport interface AicSnapshot {\n totalNanoAiu: number\n requests: number\n perModel: Record<string, AicModelEntry>\n /** Reconstructed nano-AIU per token_type (input/cache_read/cache_write/output). */\n perTokenType: Record<string, number>\n /** Raw token counts per model per type (exit cost table + rate math). */\n tokensByModel: Record<string, AicModelTokens>\n}\n\nfunction emptyModelTokens(): AicModelTokens {\n return { input: 0, cache_read: 0, cache_write: 0, output: 0, other: 0 }\n}\n\nfunction emptySnapshot(): AicSnapshot {\n return {\n totalNanoAiu: 0,\n requests: 0,\n perModel: {},\n perTokenType: {},\n tokensByModel: {},\n }\n}\n\nlet ledger: AicSnapshot = emptySnapshot()\n\n/** @internal — reset module state between test cases. */\nexport function __resetAicLedgerForTests(): void {\n ledger = emptySnapshot()\n}\n\nfunction ledgerDir(): string {\n return (\n process.env.GH_ROUTER_AIC_LEDGER_DIR ?? path.join(PATHS.APP_DIR, \"aic-ledger\")\n )\n}\n\n/**\n * Snapshot file for this proxy process. Overridable via `GH_ROUTER_AIC_LEDGER`\n * (used by tests and by the spawned child's env).\n */\nexport function aicLedgerPath(): string {\n const override = process.env.GH_ROUTER_AIC_LEDGER\n if (override && override.length > 0) return override\n return path.join(ledgerDir(), `${process.pid}.json`)\n}\n\n/** Best-effort persist; never throws. */\nfunction persistBestEffort(): void {\n try {\n const file = aicLedgerPath()\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n fs.writeFileSync(tmp, `${JSON.stringify(ledger)}\\n`, { mode: 0o600 })\n fs.renameSync(tmp, file)\n } catch {\n // Telemetry must never break a request.\n }\n}\n\n/**\n * Record one upstream `copilot_usage` reading. No-op on undefined.\n * `model` is the resolved (Copilot-side) model id when known.\n */\nexport function recordAic(\n model: string | undefined,\n usage: CopilotUsage | undefined,\n): void {\n if (!usage) return\n ledger.totalNanoAiu += usage.totalNanoAiu\n ledger.requests += 1\n const key = model && model.length > 0 ? model : \"unknown\"\n const entry = ledger.perModel[key] ?? { nanoAiu: 0, requests: 0 }\n entry.nanoAiu += usage.totalNanoAiu\n entry.requests += 1\n ledger.perModel[key] = entry\n ledger.tokensByModel[key] ??= emptyModelTokens()\n for (const detail of usage.tokenDetails) {\n const type = detail.tokenType ?? \"unknown\"\n // Reconstruct nano-AIU per token type from the priced detail.\n const nano = Math.round(\n (detail.tokenCount * detail.costPerBatch) / detail.batchSize,\n )\n ledger.perTokenType[type] = (ledger.perTokenType[type] ?? 0) + nano\n // Retain raw counts per model per type for the exit cost table. The\n // hook only reads them; pricing still flows exclusively through nano.\n const counts = ledger.tokensByModel[key] ?? emptyModelTokens()\n const bucket = tokenBucket(type)\n counts[bucket] += detail.tokenCount\n ledger.tokensByModel[key] = counts\n }\n persistBestEffort()\n}\n\n/**\n * Map an upstream `token_type` to its count bucket. Unknown/missing types\n * land in `other` (priced at the model's input factor downstream) rather\n * than being dropped — the observed universe is the four known types, this\n * is the just-in-case bucket.\n */\nfunction tokenBucket(type: string): keyof AicModelTokens {\n switch (type) {\n case \"input\":\n return \"input\"\n case \"cache_read\":\n return \"cache_read\"\n case \"cache_write\":\n return \"cache_write\"\n case \"output\":\n return \"output\"\n default:\n return \"other\"\n }\n}\n\n/**\n * Extract `copilot_usage` from a response object or SSE event-data object,\n * record it under `model`, and return its nano-AIU for `logRequest`.\n * Returns `undefined` when absent/invalid. Never throws.\n */\nexport function extractAndRecordAic(\n model: string | undefined,\n container: unknown,\n): number | undefined {\n if (!container || typeof container !== \"object\" || Array.isArray(container)) {\n return undefined\n }\n const usage = extractCopilotUsage(\n (container as Record<string, unknown>).copilot_usage,\n )\n if (!usage) return undefined\n recordAic(model, usage)\n return usage.totalNanoAiu\n}\n\n/**\n * Streaming-tap variant of `extractAndRecordAic`: identical, except a\n * zero-nano reading is treated as \"no reading yet\" — it is NOT recorded\n * and `undefined` is returned so the tap keeps scanning.\n *\n * Why: Copilot's `/chat/completions` streams emit interim chunks carrying\n * `copilot_usage` with `total_nano_aiu: 0` before the terminal chunk with\n * the real value (verified live on gemini-3.8-flash). A first-wins tap\n * built on `extractAndRecordAic` latches on the zero frame and drops the\n * terminal value, under-recording the whole stream as free. Every\n * streaming tap must use this variant; non-streaming callers keep\n * `extractAndRecordAic` so a genuinely free call still counts its request.\n */\nexport function extractAndRecordPricedAic(\n model: string | undefined,\n container: unknown,\n): number | undefined {\n if (!container || typeof container !== \"object\" || Array.isArray(container)) {\n return undefined\n }\n const usage = extractCopilotUsage(\n (container as Record<string, unknown>).copilot_usage,\n )\n if (!usage || usage.totalNanoAiu <= 0) return undefined\n recordAic(model, usage)\n return usage.totalNanoAiu\n}\n\n/** In-process snapshot (defensive copy). */\nexport function aicSnapshot(): AicSnapshot {\n return {\n totalNanoAiu: ledger.totalNanoAiu,\n requests: ledger.requests,\n perModel: Object.fromEntries(\n Object.entries(ledger.perModel).map(([k, v]) => [k, { ...v }]),\n ),\n perTokenType: { ...ledger.perTokenType },\n tokensByModel: Object.fromEntries(\n Object.entries(ledger.tokensByModel).map(([k, v]) => [k, { ...v }]),\n ),\n }\n}\n\n/**\n * Read a persisted snapshot file (the status-hook path). Returns undefined\n * when missing/unreadable — the hook then prints nothing.\n */\nexport function readAicSnapshotFile(file: string): AicSnapshot | undefined {\n try {\n const raw = fs.readFileSync(file, \"utf8\")\n const parsed: unknown = JSON.parse(raw)\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return undefined\n }\n const record = parsed as Record<string, unknown>\n if (\n typeof record.totalNanoAiu !== \"number\"\n || typeof record.requests !== \"number\"\n ) {\n return undefined\n }\n return {\n totalNanoAiu: Math.max(0, Math.floor(record.totalNanoAiu)),\n requests: Math.max(0, Math.floor(record.requests)),\n perModel:\n record.perModel && typeof record.perModel === \"object\"\n ? (record.perModel as AicSnapshot[\"perModel\"])\n : {},\n perTokenType:\n record.perTokenType && typeof record.perTokenType === \"object\"\n ? (record.perTokenType as AicSnapshot[\"perTokenType\"])\n : {},\n // Pre-tokens files (written before this field existed) read as empty\n // rather than failing the hook.\n tokensByModel: sanitizeModelTokens(record.tokensByModel),\n }\n } catch {\n return undefined\n }\n}\n\n/** Best-effort sanitize of persisted per-model token counts; `{}` when absent. */\nfunction sanitizeModelTokens(value: unknown): Record<string, AicModelTokens> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {}\n const out: Record<string, AicModelTokens> = {}\n for (const [model, counts] of Object.entries(value as Record<string, unknown>)) {\n if (!counts || typeof counts !== \"object\" || Array.isArray(counts)) continue\n const entry = counts as Record<string, unknown>\n const clean = emptyModelTokens()\n let ok = true\n for (const field of [\"input\", \"cache_read\", \"cache_write\", \"output\", \"other\"] as const) {\n const n = entry[field]\n if (n === undefined) continue\n if (typeof n !== \"number\" || !Number.isFinite(n) || n < 0) {\n ok = false\n break\n }\n clean[field] = Math.floor(n)\n }\n if (ok) out[model] = clean\n }\n return out\n}\n\n/** Total credits for a snapshot. */\nexport function aicTotalCredits(snapshot: AicSnapshot): number {\n return nanoAiuToCredits(snapshot.totalNanoAiu)\n}\n\n/** One-line status fragment: `[AIC 12.42]`. Empty string when nothing recorded. */\nexport function formatAicStatus(snapshot: AicSnapshot): string {\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) return \"\"\n return `[AIC ${formatAic(aicTotalCredits(snapshot))}]`\n}\n\n/**\n * Multi-line exit summary. `verbose` adds per-model and per-type breakdowns\n * (only from upstream-measured values — never estimates).\n */\nexport function formatAicExitSummary(\n snapshot: AicSnapshot,\n opts: { verbose?: boolean } = {},\n): string {\n const total = aicTotalCredits(snapshot)\n const reqWord = snapshot.requests === 1 ? \"request\" : \"requests\"\n const lines = [\n `AIC consumed this session: ${formatAic(total)} credits (~$${(total / 100).toFixed(2)}) across ${snapshot.requests} ${reqWord}`,\n ]\n if (opts.verbose) {\n const models = Object.entries(snapshot.perModel).sort(\n (a, b) => b[1].nanoAiu - a[1].nanoAiu,\n )\n for (const [model, entry] of models) {\n const rWord = entry.requests === 1 ? \"request\" : \"requests\"\n lines.push(\n ` ${model}: ${formatAic(nanoAiuToCredits(entry.nanoAiu))} credits across ${entry.requests} ${rWord}`,\n )\n }\n const types = Object.entries(snapshot.perTokenType).sort(\n (a, b) => b[1] - a[1],\n )\n if (types.length > 0) {\n lines.push(\n ` by type: ${types.map(([t, n]) => `${t}=${formatAic(nanoAiuToCredits(n))}`).join(\" \")}`,\n )\n }\n }\n return lines.join(\"\\n\")\n}\n\n/** Remove this process's snapshot file; never throws. */\nexport async function removeAicLedgerFile(): Promise<void> {\n try {\n await fsp.rm(aicLedgerPath(), { force: true })\n } catch {\n // Best-effort cleanup.\n }\n}\n\n/** Sweep stale ledger files (dead PID or >24h old); never throws. */\nexport async function sweepStaleAicLedgerFiles(): Promise<void> {\n try {\n const dir = ledgerDir()\n const entries = await fsp.readdir(dir).catch(() => [] as Array<string>)\n const now = Date.now()\n await Promise.all(\n entries.map(async (entry) => {\n if (!/^\\d+\\.json$/.test(entry)) return\n const file = path.join(dir, entry)\n try {\n const stat = await fsp.stat(file)\n const pid = Number(entry.slice(0, -\".json\".length))\n const alive = await isPidAlive(pid)\n if (!alive || now - stat.mtimeMs > 24 * 60 * 60 * 1000) {\n await fsp.rm(file, { force: true })\n }\n } catch {\n // Best-effort per file.\n }\n }),\n )\n } catch {\n // Best-effort sweep.\n }\n}\n\nasync function isPidAlive(pid: number): Promise<boolean> {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n"],"mappings":"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"}