github-router 0.3.316 → 0.3.317
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{aic-ledger-BS47yb_C.js → aic-ledger-DY03LbBM.js} +2 -2
- package/dist/{aic-ledger-BS47yb_C.js.map → aic-ledger-DY03LbBM.js.map} +1 -1
- package/dist/{attribution-settings-jcsSeVur.js → attribution-settings-D6dAkP4e.js} +8 -8
- package/dist/{attribution-settings-jcsSeVur.js.map → attribution-settings-D6dAkP4e.js.map} +1 -1
- package/dist/{auth-JfxL9q7z.js → auth-Iun7ftd8.js} +3 -3
- package/dist/{auth-JfxL9q7z.js.map → auth-Iun7ftd8.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-DXUEQzt_.js → check-usage-1c-0z3iy.js} +4 -4
- package/dist/{check-usage-DXUEQzt_.js.map → check-usage-1c-0z3iy.js.map} +1 -1
- package/dist/{claude-CJ4Nd2_3.js → claude-aZ6OaFP5.js} +92 -28
- package/dist/claude-aZ6OaFP5.js.map +1 -0
- package/dist/{codex-CwuLqNZF.js → codex-PTdWu7er.js} +5 -5
- package/dist/{codex-CwuLqNZF.js.map → codex-PTdWu7er.js.map} +1 -1
- package/dist/{copilot-discount-IbXR5A28.js → copilot-discount-C3jx9Q-o.js} +2 -2
- package/dist/{copilot-discount-IbXR5A28.js.map → copilot-discount-C3jx9Q-o.js.map} +1 -1
- package/dist/{debug-CA7pg8V9.js → debug-CDyXUPcC.js} +2 -2
- package/dist/{debug-CA7pg8V9.js.map → debug-CDyXUPcC.js.map} +1 -1
- package/dist/engine-D5GNXylB.js +2 -0
- package/dist/{gate-discovery-z04ywOzB.js → gate-discovery-B17pvgE0.js} +5 -5
- package/dist/{gate-discovery-z04ywOzB.js.map → gate-discovery-B17pvgE0.js.map} +1 -1
- package/dist/{get-copilot-usage-BMD_cJNT.js → get-copilot-usage-BWCAzmSc.js} +2 -2
- package/dist/{get-copilot-usage-BMD_cJNT.js.map → get-copilot-usage-BWCAzmSc.js.map} +1 -1
- package/dist/hooks.mjs +124 -117
- package/dist/hooks.sha256 +1 -1
- package/dist/{internal-aic-status-BN0xRLSF.js → internal-aic-status-BiblmImt.js} +13 -6
- package/dist/{internal-aic-status-BN0xRLSF.js.map → internal-aic-status-BiblmImt.js.map} +1 -1
- package/dist/{internal-artifact-open-B3stUu74.js → internal-artifact-open-CA1trJ15.js} +2 -2
- package/dist/{internal-artifact-open-B3stUu74.js.map → internal-artifact-open-CA1trJ15.js.map} +1 -1
- package/dist/{internal-fast-dispatch-guard-CAwYj_Yz.js → internal-fast-dispatch-guard-CZ4BZuAG.js} +2 -2
- package/dist/{internal-fast-dispatch-guard-CAwYj_Yz.js.map → internal-fast-dispatch-guard-CZ4BZuAG.js.map} +1 -1
- package/dist/{internal-fast-dispatch-guard-2QjUlhqO.js → internal-fast-dispatch-guard-D5kozMpo.js} +1 -1
- package/dist/{internal-first-mate-guard-VgnHBp0s.js → internal-first-mate-guard-B_or0eN3.js} +1 -1
- package/dist/{internal-first-mate-guard-B0rbLb0L.js → internal-first-mate-guard-CiFH6X1H.js} +3 -3
- package/dist/{internal-first-mate-guard-B0rbLb0L.js.map → internal-first-mate-guard-CiFH6X1H.js.map} +1 -1
- package/dist/{internal-max-dispatch-guard-Bz7a2Ntf.js → internal-max-dispatch-guard-BGOHRYRt.js} +1 -1
- package/dist/{internal-max-dispatch-guard-BNqQ2wMc.js → internal-max-dispatch-guard-CnBZxo5l.js} +2 -2
- package/dist/{internal-max-dispatch-guard-BNqQ2wMc.js.map → internal-max-dispatch-guard-CnBZxo5l.js.map} +1 -1
- package/dist/{internal-plan-review-QUvNZmMR.js → internal-plan-review-C73snnD8.js} +3 -3
- package/dist/{internal-plan-review-QUvNZmMR.js.map → internal-plan-review-C73snnD8.js.map} +1 -1
- package/dist/{internal-prompt-submit-fj5ccft_.js → internal-prompt-submit-dov4UvBo.js} +4 -4
- package/dist/{internal-prompt-submit-fj5ccft_.js.map → internal-prompt-submit-dov4UvBo.js.map} +1 -1
- package/dist/{internal-session-bind-0Hmak8PR.js → internal-session-bind-C3hrb9rP.js} +2 -2
- package/dist/{internal-session-bind-0Hmak8PR.js.map → internal-session-bind-C3hrb9rP.js.map} +1 -1
- package/dist/{internal-stop-hook-UN1JHtjj.js → internal-stop-hook-DZ6CCP4C.js} +5 -5
- package/dist/{internal-stop-hook-UN1JHtjj.js.map → internal-stop-hook-DZ6CCP4C.js.map} +1 -1
- package/dist/{internal-stop-review-8v2llYDB.js → internal-stop-review-MBVYtrYS.js} +2 -2
- package/dist/{internal-stop-review-8v2llYDB.js.map → internal-stop-review-MBVYtrYS.js.map} +1 -1
- package/dist/{internal-worker-guard-BI4Pl0Ew.js → internal-worker-guard-dxGawAY3.js} +2 -2
- package/dist/{internal-worker-guard-BI4Pl0Ew.js.map → internal-worker-guard-dxGawAY3.js.map} +1 -1
- package/dist/{internal-workspace-header-kg9FoNXK.js → internal-workspace-header-_aEXOz5q.js} +2 -2
- package/dist/{internal-workspace-header-kg9FoNXK.js.map → internal-workspace-header-_aEXOz5q.js.map} +1 -1
- package/dist/{lifecycle-IwotwedG.js → lifecycle-CJB2BwSJ.js} +2 -2
- package/dist/{lifecycle-IwotwedG.js.map → lifecycle-CJB2BwSJ.js.map} +1 -1
- package/dist/lifecycle-D0KsoSqz.js +2 -0
- package/dist/lifecycle-DimEguJO.js +2 -0
- package/dist/{lifecycle-Cx1wVrLb.js → lifecycle-_BE0SnNs.js} +2 -2
- package/dist/{lifecycle-Cx1wVrLb.js.map → lifecycle-_BE0SnNs.js.map} +1 -1
- package/dist/main.js +20 -20
- package/dist/{mcp-workspace-header-BYgWioLH.js → mcp-workspace-header-mXRLE8kg.js} +2 -2
- package/dist/{mcp-workspace-header-BYgWioLH.js.map → mcp-workspace-header-mXRLE8kg.js.map} +1 -1
- package/dist/{models-CVzyU5mJ.js → models-CSFIVxAH.js} +3 -3
- package/dist/{models-CVzyU5mJ.js.map → models-CSFIVxAH.js.map} +1 -1
- package/dist/{orchestration-Dc3mO0G3.js → orchestration-B8kypXcZ.js} +2 -2
- package/dist/{orchestration-Dc3mO0G3.js.map → orchestration-B8kypXcZ.js.map} +1 -1
- package/dist/{paths-CMxlQEfQ.js → paths-CHBAj_t9.js} +36 -4
- package/dist/{paths-CMxlQEfQ.js.map → paths-CHBAj_t9.js.map} +1 -1
- package/dist/paths-eHoOwFzZ.js +2 -0
- package/dist/{peer-mcp-personas-CQ-0Piyr.js → peer-mcp-personas-CRMVuMVF.js} +37 -18
- package/dist/{peer-mcp-personas-CQ-0Piyr.js.map → peer-mcp-personas-CRMVuMVF.js.map} +1 -1
- package/dist/{plan-review-hook-BIMwWkh6.js → plan-review-hook-BMmw7wxR.js} +3 -3
- package/dist/{plan-review-hook-BIMwWkh6.js.map → plan-review-hook-BMmw7wxR.js.map} +1 -1
- package/dist/{prompt-submit-hook-BcFpjHXg.js → prompt-submit-hook-CGgT5m4Q.js} +3 -3
- package/dist/{prompt-submit-hook-BcFpjHXg.js.map → prompt-submit-hook-CGgT5m4Q.js.map} +1 -1
- package/dist/{provision-_R7ZMygj.js → provision-Bn8-NDp7.js} +4 -4
- package/dist/{provision-_R7ZMygj.js.map → provision-Bn8-NDp7.js.map} +1 -1
- package/dist/{self-invocation-cRl4RqfW.js → self-invocation-DahN1gw9.js} +2 -2
- package/dist/{self-invocation-cRl4RqfW.js.map → self-invocation-DahN1gw9.js.map} +1 -1
- package/dist/{serve-DU66K5kv.js → serve-CX4Ifk5p.js} +12 -12
- package/dist/{serve-DU66K5kv.js.map → serve-CX4Ifk5p.js.map} +1 -1
- package/dist/{server-setup-iLG8LrfL.js → server-setup-0PaS4m2B.js} +98 -48
- package/dist/server-setup-0PaS4m2B.js.map +1 -0
- package/dist/{start-DQTcm0o_.js → start-Bz_559ua.js} +3 -3
- package/dist/{start-DQTcm0o_.js.map → start-Bz_559ua.js.map} +1 -1
- package/dist/{stop-gate-hook-BUCxa5Kc.js → stop-gate-hook-D7N759TG.js} +72 -4
- package/dist/{stop-gate-hook-BUCxa5Kc.js.map → stop-gate-hook-D7N759TG.js.map} +1 -1
- package/dist/{stop-gate-policy--bpyxxXU.js → stop-gate-policy-C0gt04R0.js} +2 -2
- package/dist/{stop-gate-policy--bpyxxXU.js.map → stop-gate-policy-C0gt04R0.js.map} +1 -1
- package/dist/{token-C1gthnbV.js → token-86uk6y4P.js} +2 -2
- package/dist/{token-C1gthnbV.js.map → token-86uk6y4P.js.map} +1 -1
- package/dist/{worker-dispatch-DMIdrlsF.js → worker-dispatch-B-OA7tvU.js} +2 -2
- package/dist/{worker-dispatch-DMIdrlsF.js.map → worker-dispatch-B-OA7tvU.js.map} +1 -1
- package/package.json +1 -1
- package/dist/claude-CJ4Nd2_3.js.map +0 -1
- package/dist/engine-DwrxceOS.js +0 -2
- package/dist/lifecycle-BpqAyaAj.js +0 -2
- package/dist/lifecycle-CL2WiyeQ.js +0 -2
- package/dist/paths-BXXCVFvA.js +0 -2
- package/dist/server-setup-iLG8LrfL.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-aic-status-BN0xRLSF.js","names":[],"sources":["../src/lib/default-statusline.ts","../src/internal-aic-status.ts"],"sourcesContent":["/**\n * Rich default status line for `github-router claude` sessions.\n *\n * Replaces the need for users to maintain their own PowerShell/bash status\n * script: `internal-aic-status` renders these segments natively from the\n * Claude Code stdin JSON, then pins the `[AIC x.xx]` ledger fragment ahead\n * of them. Zero dependencies, cross-platform (Windows 11 is the primary\n * target — no `powershell`/`bash`/`jq` required), and fast (no shell\n * startup; one bounded `git rev-parse` at most).\n *\n * Segment order (left = highest priority; narrow terminals drop from the\n * right, `AIC` is never dropped):\n *\n * `[AIC] | [ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`\n *\n * The `$` segment is the DISCOUNTED actual (`~$` = factor-discounted\n * session total from `copilot-discount.ts`, capped at AIC × $0.01) — NOT\n * Claude's `cost.total_cost_usd`, which prices at Anthropic list rates and\n * reads ~10-100x high against Copilot billing (a 28-day sample: 41.1B tokens\n * for $5,142 ≈ $0.13/1M blended vs $15-75/1M list). Rendering list price\n * next to the real `[AIC]` billing unit would show two contradictory\n * numbers, so list price is deliberately not rendered at all.\n *\n * Width comes from `COLUMNS` (set by Claude Code for status scripts since\n * v2.1.153); fallback is 120 when unset/unparseable so a missing `COLUMNS`\n * never needlessly drops segments.\n */\n\nimport { spawnSync } from \"node:child_process\"\n\nimport { USD_PER_AIC_CREDIT } from \"./copilot-discount\"\nimport {\n buildExecInvocation,\n resolveExecutable,\n} from \"./exec\"\n\nconst ESC = \"\\x1b\"\nconst RESET = `${ESC}[0m`\nconst DIM = `${ESC}[90m`\nconst GREEN = `${ESC}[32m`\nconst YELLOW = `${ESC}[33m`\nconst RED = `${ESC}[31m`\nconst ORANGE = `${ESC}[38;5;208m`\nconst CYAN = `${ESC}[36m`\nconst MAGENTA = `${ESC}[35m`\nconst BLUE = `${ESC}[34m`\nconst AIC_COLOR = `${ESC}[1;33m`\n\nconst CTX_BAR_LEN = 10\nconst GIT_TIMEOUT_MS = 300\nconst WIDTH_FALLBACK = 120\nconst SEP_PLAIN = \" | \"\nconst SEP_TEXT = `${DIM} | ${RESET}`\n\nexport interface StatusInput {\n usedPct?: number\n totalInputTokens?: number\n totalOutputTokens?: number\n totalDurationMs?: number\n linesAdded?: number\n linesRemoved?: number\n cwd?: string\n modelName?: string\n}\n\nexport interface StatusSegment {\n text: string\n plain: string\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n return value as Record<string, unknown>\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\n/**\n * Parse Claude Code's status-line stdin JSON into the fields we render.\n * Total function: malformed JSON or unexpected shapes yield `{}` (every\n * segment then renders its `--` placeholder or is omitted) — never throws.\n */\nexport function parseStatusInput(raw: string): StatusInput {\n if (!raw.trim()) return {}\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch {\n return {}\n }\n const root = asRecord(parsed)\n if (!root) return {}\n const out: StatusInput = {}\n\n const ctx = asRecord(root.context_window)\n if (ctx) {\n const pct = finiteNumber(ctx.used_percentage)\n if (pct !== undefined) out.usedPct = pct\n const tIn = finiteNumber(ctx.total_input_tokens)\n if (tIn !== undefined && tIn >= 0) out.totalInputTokens = Math.floor(tIn)\n const tOut = finiteNumber(ctx.total_output_tokens)\n if (tOut !== undefined && tOut >= 0) out.totalOutputTokens = Math.floor(tOut)\n }\n\n const cost = asRecord(root.cost)\n if (cost) {\n const dur = finiteNumber(cost.total_duration_ms)\n if (dur !== undefined && dur >= 0) out.totalDurationMs = dur\n const la = finiteNumber(cost.total_lines_added)\n if (la !== undefined && la >= 0) out.linesAdded = Math.floor(la)\n const lr = finiteNumber(cost.total_lines_removed)\n if (lr !== undefined && lr >= 0) out.linesRemoved = Math.floor(lr)\n }\n\n const workspace = asRecord(root.workspace)\n const wsDir = workspace ? nonEmptyString(workspace.current_dir) : undefined\n const cwd = wsDir ?? nonEmptyString(root.cwd)\n if (cwd) out.cwd = cwd\n\n const model = asRecord(root.model)\n if (model) {\n const name =\n nonEmptyString(model.display_name) ?? nonEmptyString(model.id)\n if (name) out.modelName = name\n }\n\n return out\n}\n\n/** Compact token count: `999` → `999`, `15234` → `15.2k`, `2.1M`. */\nexport function compactTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`\n return `${n}`\n}\n\n/** `45000` → `0m45s`, `198000` → `3m18s`, `3720000` → `1h02m`. */\nexport function formatDurationMs(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n if (hours >= 1) {\n return `${hours}h${String(minutes).padStart(2, \"0\")}m`\n }\n return `${minutes}m${String(seconds).padStart(2, \"0\")}s`\n}\n\n/** Basename of a cwd, tolerant of `/` and `\\` separators + trailing slash. */\nexport function dirLeaf(cwd: string): string {\n const trimmed = cwd.replace(/[/\\\\]+$/, \"\")\n const parts = trimmed.split(/[/\\\\]/)\n return parts[parts.length - 1] ?? trimmed\n}\n\nexport function buildCtxSegment(usedPct: number | undefined): StatusSegment {\n if (usedPct === undefined || !Number.isFinite(usedPct)) {\n return {\n text: `${DIM}[----------] --%${RESET}`,\n plain: \"[----------] --%\",\n }\n }\n const u = Math.max(0, Math.min(100, Math.round(usedPct)))\n let filled = Math.round((u / 100) * CTX_BAR_LEN)\n if (filled > CTX_BAR_LEN) filled = CTX_BAR_LEN\n if (filled < 0) filled = 0\n const bar = `#`.repeat(filled) + `-`.repeat(CTX_BAR_LEN - filled)\n const color = u < 50 ? GREEN : u < 75 ? YELLOW : RED\n return {\n text: `${color}[${bar}] ${u}%${RESET}`,\n plain: `[${bar}] ${u}%`,\n }\n}\n\n/**\n * AIC credits → undiscounted USD. 1 credit ≈ $0.01 — the same convention as\n * the session exit summary (`aic-ledger.ts`). Kept for tests/callers that\n * need the raw conversion; the status line itself shows the\n * factor-discounted total from `copilot-discount.ts`.\n */\nexport function usdFromAicCredits(credits: number): number {\n return credits * USD_PER_AIC_CREDIT\n}\n\n/**\n * Discounted-actual `$` segment. Callers pass the capped total from\n * `discountedUsdForSnapshot` — this function only formats. Placeholder\n * `~$--` until the first priced upstream response lands in the ledger.\n * Thresholds are tuned for ACTUALS scale (a session runs ~$0.10-2.00) —\n * not the old list-price scale.\n */\nexport function buildUsdSegment(\n actualUsd: number | undefined,\n): StatusSegment {\n if (actualUsd === undefined || !Number.isFinite(actualUsd) || actualUsd < 0) {\n return { text: `${DIM}~$--${RESET}`, plain: \"~$--\" }\n }\n const c = actualUsd\n const color = c < 1 ? GREEN : c < 5 ? YELLOW : c < 20 ? ORANGE : RED\n const formatted = c < 10 ? c.toFixed(2) : c.toFixed(1)\n return {\n text: `${color}~$${formatted}${RESET}`,\n plain: `~$${formatted}`,\n }\n}\n\nexport function buildToksSegment(\n tIn: number | undefined,\n tOut: number | undefined,\n): StatusSegment {\n if (tIn === undefined || tOut === undefined) {\n return { text: `${DIM}--/--${RESET}`, plain: \"--/--\" }\n }\n const cin = compactTokens(tIn)\n const cout = compactTokens(tOut)\n return {\n text: `${CYAN}${cin}${RESET}${DIM}/${RESET}${MAGENTA}${cout}${RESET}`,\n plain: `${cin}/${cout}`,\n }\n}\n\nexport function buildDurSegment(durMs: number | undefined): StatusSegment {\n if (durMs === undefined || !Number.isFinite(durMs) || durMs < 0) {\n return { text: `${DIM}--${RESET}`, plain: \"--\" }\n }\n const formatted = formatDurationMs(durMs)\n return { text: `${DIM}${formatted}${RESET}`, plain: formatted }\n}\n\nexport function buildLinesSegment(\n added: number | undefined,\n removed: number | undefined,\n): StatusSegment {\n const a = added ?? 0\n const r = removed ?? 0\n return {\n text: `${GREEN}+${a}${RESET} ${RED}-${r}${RESET}`,\n plain: `+${a} -${r}`,\n }\n}\n\nexport function buildModelSegment(\n modelName: string | undefined,\n): StatusSegment | undefined {\n if (!modelName || modelName === \"--\") return undefined\n return {\n text: `${MAGENTA}${modelName}${RESET}`,\n plain: modelName,\n }\n}\n\nexport function buildDirGitSegment(\n cwd: string | undefined,\n branch: string | undefined,\n): StatusSegment | undefined {\n const leaf = cwd ? dirLeaf(cwd) : \"\"\n const br = branch && branch !== \"HEAD\" ? branch : \"\"\n if (!leaf && !br) return undefined\n if (leaf && br) {\n return {\n text: `${BLUE}${leaf}${RESET} ${CYAN}(${br})${RESET}`,\n plain: `${leaf} (${br})`,\n }\n }\n if (leaf) {\n return { text: `${BLUE}${leaf}${RESET}`, plain: leaf }\n }\n return { text: `${CYAN}${br}${RESET}`, plain: br }\n}\n\n/**\n * Resolve the current git branch for `cwd`. Bounded (`GIT_TIMEOUT_MS`) and\n * total: any failure (no git binary, not a repo, detached `HEAD`, timeout,\n * `%` in a Windows path that cmd.exe cannot safely quote) yields `\"\"`\n * (caller omits the branch) — never throws.\n */\nexport function resolveGitBranch(cwd: string | undefined): string {\n try {\n if (!cwd) return \"\"\n const gitPath = resolveExecutable(\"git\")\n if (!gitPath) return \"\"\n const { command, args, shell } = buildExecInvocation([\n gitPath,\n \"-C\",\n cwd,\n \"rev-parse\",\n \"--abbrev-ref\",\n \"HEAD\",\n ])\n const result = spawnSync(command, args, {\n shell,\n timeout: GIT_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n })\n if (result.error) return \"\"\n const out =\n typeof result.stdout === \"string\" ? result.stdout.trim() : \"\"\n if (!out || out === \"HEAD\") return \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\n/** Terminal width for the drop-right-to-left assembly. */\nexport function terminalWidth(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.COLUMNS\n if (raw !== undefined) {\n const n = Number(raw)\n if (Number.isInteger(n) && n > 0) return n\n }\n return WIDTH_FALLBACK\n}\n\n/**\n * Assemble the full status line. `aicFragment` (e.g. `[AIC 12.42]`) is\n * PINNED — it renders even when nothing else fits. `actualUsd` feeds the\n * `~$` segment (the capped factor-discounted total from\n * `discountedUsdForSnapshot`, same snapshot the fragment came from).\n * Droppable segments in priority order: ctx, model, dir/git, ~$, toks,\n * dur, lines.\n */\nexport function assembleStatusLine(\n aicFragment: string,\n input: StatusInput,\n opts: { width?: number; branch?: string; actualUsd?: number } = {},\n): string {\n const aic = aicFragment.trim()\n const aicText = aic ? `${AIC_COLOR}${aic}${RESET}` : \"\"\n\n const droppable: Array<StatusSegment> = [buildCtxSegment(input.usedPct)]\n const model = buildModelSegment(input.modelName)\n if (model) droppable.push(model)\n // Location context sits right after ctx/model: more useful than the\n // numbers when the bar is wide, still dropped before ctx/model.\n const dirGit = buildDirGitSegment(input.cwd, opts.branch)\n if (dirGit) droppable.push(dirGit)\n droppable.push(\n buildUsdSegment(opts.actualUsd),\n buildToksSegment(input.totalInputTokens, input.totalOutputTokens),\n buildDurSegment(input.totalDurationMs),\n buildLinesSegment(input.linesAdded, input.linesRemoved),\n )\n\n const width = opts.width ?? terminalWidth()\n const sepLen = SEP_PLAIN.length\n\n // Drop from the end (lowest priority) until the line fits. AIC is pinned:\n // it is excluded from the dropping loop entirely.\n let count = droppable.length\n while (count > 0) {\n let total = aic ? aic.length : 0\n for (let i = 0; i < count; i++) {\n if (aic || i > 0) total += sepLen\n total += droppable[i].plain.length\n }\n if (total <= width) break\n // Never drop the last segment when there is no AIC: an over-wide line\n // with real data beats an empty status line.\n if (count === 1 && !aic) break\n count--\n }\n\n const parts: Array<string> = []\n if (aic) parts.push(aicText)\n for (let i = 0; i < count; i++) parts.push(droppable[i].text)\n if (parts.length === 0) return \"\"\n return parts.join(SEP_TEXT)\n}\n\n/**\n * Convenience: parse stdin JSON + assemble with branch resolution.\n * `branchOverride` skips the `git` spawn (used by tests). `actualUsd`\n * comes from `discountedUsdForSnapshot` on the same ledger snapshot as\n * `aicFragment` (read by the caller — this module never touches the\n * ledger file).\n */\nexport function buildRichStatusLine(\n stdinRaw: string,\n aicFragment: string,\n opts: { width?: number; branchOverride?: string; actualUsd?: number } = {},\n): string {\n const input = parseStatusInput(stdinRaw)\n const branch =\n opts.branchOverride !== undefined\n ? opts.branchOverride\n : resolveGitBranch(input.cwd)\n return assembleStatusLine(aicFragment, input, {\n ...(opts.width !== undefined ? { width: opts.width } : {}),\n ...(branch ? { branch } : {}),\n ...(opts.actualUsd !== undefined ? { actualUsd: opts.actualUsd } : {}),\n })\n}\n","/**\n * The internal `internal-aic-status` subcommand: the executable a spawned\n * Claude Code session's `statusLine` invokes (registered into the mirrored\n * settings.json by the launcher — see `src/lib/aic-statusline-settings.ts`).\n *\n * Default-on rich status line: Claude Code's stdin JSON is rendered natively\n * (see `src/lib/default-statusline.ts`) as\n * `[ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`,\n * with this launch's AIC ledger total PINNED ahead of it: `[AIC 12.42] ...`.\n * The `~$` segment is the factor-discounted actual (per-model static\n * factors over ledger nano, capped at AIC × $0.01 — see\n * `src/lib/copilot-discount.ts`), not Claude's list-price `total_cost_usd`.\n * Narrow terminals drop segments right-to-left; AIC is never dropped.\n *\n * When the user already had a `statusLine` command, its text arrives via\n * `GH_ROUTER_AIC_USER_STATUSLINE` and is run instead of the rich default\n * (their script already renders ctx/cost/model itself — rendering both\n * would duplicate every segment), so the output stays one composed line:\n * `[AIC 12.42] <user line>`.\n *\n * Status-line contract: stdout is the rendered line (first line wins), fast\n * (<1s budget — ledger read is local, git branch has a 300ms cap, the user\n * command gets a 5s cap), and NEVER failing (any error → print whatever is\n * available → exit 0).\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { spawnSync } from \"node:child_process\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n formatAicStatus,\n readAicSnapshotFile,\n} from \"./lib/aic-ledger\"\nimport { discountedUsdForSnapshot } from \"./lib/copilot-discount\"\nimport { buildRichStatusLine } from \"./lib/default-statusline\"\n\n/**\n * Read stdin synchronously. Claude Code pipes session JSON to the\n * statusLine command; an unread pipe can SIGPIPE the writer on some\n * platforms. Returns \"\" when stdin is a TTY or unreadable — callers render\n * placeholders/omit segments rather than failing.\n */\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n // Best-effort; the ledger read below does not depend on stdin.\n return \"\"\n }\n}\n\n/**\n * Pure core: compose the status line from an AIC fragment and a user line.\n * Exported for unit tests.\n */\nexport function composeAicStatusLine(\n aicFragment: string,\n userLine: string,\n): string {\n const aic = aicFragment.trim()\n const user = userLine.split(\"\\n\")[0]?.trim() ?? \"\"\n if (aic && user) return `${aic} ${user}`\n return aic || user\n}\n\n/**\n * Run the user's own statusLine command (from `GH_ROUTER_AIC_USER_STATUSLINE`),\n * returning its first stdout line or \"\" on any failure/timeout. Exported for\n * unit tests (command + timeout injectable).\n */\nexport function runUserStatusLine(\n command: string,\n timeoutMs = 5000,\n): string {\n if (!command.trim()) return \"\"\n try {\n const result = spawnSync(command, {\n shell: true,\n timeout: timeoutMs,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n // Inherit env so user scripts see the same session context they would\n // under their own statusLine (cwd included — spawnSync inherits it).\n env: process.env,\n })\n if (result.error) return \"\"\n const out = typeof result.stdout === \"string\" ? result.stdout : \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\nfunction readAicSnapshot(): { fragment: string; actualUsd?: number } {\n try {\n const ledgerPath = process.env.GH_ROUTER_AIC_LEDGER\n if (!ledgerPath) return { fragment: \"\" }\n const snapshot = readAicSnapshotFile(ledgerPath)\n if (!snapshot) return { fragment: \"\" }\n // No priced responses yet → no fragment AND no USD: the `~$`\n // segment renders its `~$--` placeholder instead of `~$0.00`.\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) {\n return { fragment: \"\" }\n }\n return {\n fragment: formatAicStatus(snapshot),\n actualUsd: discountedUsdForSnapshot(snapshot).total,\n }\n } catch {\n return { fragment: \"\" }\n }\n}\n\nexport const internalAicStatus = defineCommand({\n meta: {\n name: \"internal-aic-status\",\n description:\n \"Internal: Claude Code statusLine handler printing AIC total + rich session line.\",\n },\n async run() {\n const stdinRaw = readStdin()\n const { fragment: aic, actualUsd } = readAicSnapshot()\n const userCommand = process.env.GH_ROUTER_AIC_USER_STATUSLINE ?? \"\"\n // Wrap mode: the user's own script already renders ctx/cost/model from\n // the same stdin JSON — run it and prepend AIC only.\n if (userCommand.trim()) {\n const userLine = runUserStatusLine(userCommand)\n const line = composeAicStatusLine(aic, userLine)\n if (line) process.stdout.write(`${line}\\n`)\n process.exitCode = 0\n return\n }\n // Default mode: native rich line with AIC pinned ahead of it. The\n // builder never throws; an empty result (no ledger yet AND no stdin)\n // prints nothing rather than a placeholder line.\n let line: string\n try {\n line = buildRichStatusLine(stdinRaw, aic, {\n ...(actualUsd !== undefined ? { actualUsd } : {}),\n })\n } catch {\n line = aic.trim()\n }\n if (line) process.stdout.write(`${line}\\n`)\n // Signal success WITHOUT tearing the loop down — same resolution as the\n // other internal hooks (see main.ts help-path comment): a hard\n // process.exit() can abort through a pipe on Windows.\n process.exitCode = 0\n },\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAM,MAAM;AACZ,MAAM,QAAQ,GAAG,IAAI;AACrB,MAAM,MAAM,GAAG,IAAI;AACnB,MAAM,QAAQ,GAAG,IAAI;AACrB,MAAM,SAAS,GAAG,IAAI;AACtB,MAAM,MAAM,GAAG,IAAI;AACnB,MAAM,SAAS,GAAG,IAAI;AACtB,MAAM,OAAO,GAAG,IAAI;AACpB,MAAM,UAAU,GAAG,IAAI;AACvB,MAAM,OAAO,GAAG,IAAI;AACpB,MAAM,YAAY,GAAG,IAAI;AAEzB,MAAM,cAAc;AACpB,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,WAAW,GAAG,IAAI,KAAK;AAkB7B,SAAS,SAAS,OAAqD;CACrE,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAC5D;CAEF,OAAO;AACT;AAEA,SAAS,aAAa,OAAoC;CACxD,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACvE;AAEA,SAAS,eAAe,OAAoC;CAC1D,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;AACjE;;;;;;AAOA,SAAgB,iBAAiB,KAA0B;CACzD,IAAI,CAAC,IAAI,KAAK,GAAG,OAAO,CAAC;CACzB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN,OAAO,CAAC;CACV;CACA,MAAM,OAAO,SAAS,MAAM;CAC5B,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,MAAmB,CAAC;CAE1B,MAAM,MAAM,SAAS,KAAK,cAAc;CACxC,IAAI,KAAK;EACP,MAAM,MAAM,aAAa,IAAI,eAAe;EAC5C,IAAI,QAAQ,KAAA,GAAW,IAAI,UAAU;EACrC,MAAM,MAAM,aAAa,IAAI,kBAAkB;EAC/C,IAAI,QAAQ,KAAA,KAAa,OAAO,GAAG,IAAI,mBAAmB,KAAK,MAAM,GAAG;EACxE,MAAM,OAAO,aAAa,IAAI,mBAAmB;EACjD,IAAI,SAAS,KAAA,KAAa,QAAQ,GAAG,IAAI,oBAAoB,KAAK,MAAM,IAAI;CAC9E;CAEA,MAAM,OAAO,SAAS,KAAK,IAAI;CAC/B,IAAI,MAAM;EACR,MAAM,MAAM,aAAa,KAAK,iBAAiB;EAC/C,IAAI,QAAQ,KAAA,KAAa,OAAO,GAAG,IAAI,kBAAkB;EACzD,MAAM,KAAK,aAAa,KAAK,iBAAiB;EAC9C,IAAI,OAAO,KAAA,KAAa,MAAM,GAAG,IAAI,aAAa,KAAK,MAAM,EAAE;EAC/D,MAAM,KAAK,aAAa,KAAK,mBAAmB;EAChD,IAAI,OAAO,KAAA,KAAa,MAAM,GAAG,IAAI,eAAe,KAAK,MAAM,EAAE;CACnE;CAEA,MAAM,YAAY,SAAS,KAAK,SAAS;CAEzC,MAAM,OADQ,YAAY,eAAe,UAAU,WAAW,IAAI,KAAA,MAC7C,eAAe,KAAK,GAAG;CAC5C,IAAI,KAAK,IAAI,MAAM;CAEnB,MAAM,QAAQ,SAAS,KAAK,KAAK;CACjC,IAAI,OAAO;EACT,MAAM,OACJ,eAAe,MAAM,YAAY,KAAK,eAAe,MAAM,EAAE;EAC/D,IAAI,MAAM,IAAI,YAAY;CAC5B;CAEA,OAAO;AACT;;AAGA,SAAgB,cAAc,GAAmB;CAC/C,IAAI,KAAK,KAAW,OAAO,IAAI,IAAI,IAAA,CAAW,QAAQ,CAAC,EAAE;CACzD,IAAI,KAAK,KAAM,OAAO,IAAI,IAAI,IAAA,CAAM,QAAQ,CAAC,EAAE;CAC/C,OAAO,GAAG;AACZ;;AAGA,SAAgB,iBAAiB,IAAoB;CACnD,MAAM,eAAe,KAAK,MAAM,KAAK,GAAI;CACzC,MAAM,QAAQ,KAAK,MAAM,eAAe,IAAI;CAC5C,MAAM,UAAU,KAAK,MAAO,eAAe,OAAQ,EAAE;CACrD,MAAM,UAAU,eAAe;CAC/B,IAAI,SAAS,GACX,OAAO,GAAG,MAAM,GAAG,OAAO,OAAO,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;CAEtD,OAAO,GAAG,QAAQ,GAAG,OAAO,OAAO,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;AACxD;;AAGA,SAAgB,QAAQ,KAAqB;CAC3C,MAAM,UAAU,IAAI,QAAQ,WAAW,EAAE;CACzC,MAAM,QAAQ,QAAQ,MAAM,OAAO;CACnC,OAAO,MAAM,MAAM,SAAS,MAAM;AACpC;AAEA,SAAgB,gBAAgB,SAA4C;CAC1E,IAAI,YAAY,KAAA,KAAa,CAAC,OAAO,SAAS,OAAO,GACnD,OAAO;EACL,MAAM,GAAG,IAAI,kBAAkB;EAC/B,OAAO;CACT;CAEF,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,OAAO,CAAC,CAAC;CACxD,IAAI,SAAS,KAAK,MAAO,IAAI,MAAO,WAAW;CAC/C,IAAI,SAAS,aAAa,SAAS;CACnC,IAAI,SAAS,GAAG,SAAS;CACzB,MAAM,MAAM,IAAI,OAAO,MAAM,IAAI,IAAI,OAAO,cAAc,MAAM;CAEhE,OAAO;EACL,MAAM,GAFM,IAAI,KAAK,QAAQ,IAAI,KAAK,SAAS,IAEhC,GAAG,IAAI,IAAI,EAAE,GAAG;EAC/B,OAAO,IAAI,IAAI,IAAI,EAAE;CACvB;AACF;;;;;;;;AAmBA,SAAgB,gBACd,WACe;CACf,IAAI,cAAc,KAAA,KAAa,CAAC,OAAO,SAAS,SAAS,KAAK,YAAY,GACxE,OAAO;EAAE,MAAM,GAAG,IAAI,MAAM;EAAS,OAAO;CAAO;CAErD,MAAM,IAAI;CACV,MAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,IAAI,SAAS,IAAI,KAAK,SAAS;CACjE,MAAM,YAAY,IAAI,KAAK,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACrD,OAAO;EACL,MAAM,GAAG,MAAM,IAAI,YAAY;EAC/B,OAAO,KAAK;CACd;AACF;AAEA,SAAgB,iBACd,KACA,MACe;CACf,IAAI,QAAQ,KAAA,KAAa,SAAS,KAAA,GAChC,OAAO;EAAE,MAAM,GAAG,IAAI,OAAO;EAAS,OAAO;CAAQ;CAEvD,MAAM,MAAM,cAAc,GAAG;CAC7B,MAAM,OAAO,cAAc,IAAI;CAC/B,OAAO;EACL,MAAM,GAAG,OAAO,MAAM,QAAQ,IAAI,GAAG,QAAQ,UAAU,OAAO;EAC9D,OAAO,GAAG,IAAI,GAAG;CACnB;AACF;AAEA,SAAgB,gBAAgB,OAA0C;CACxE,IAAI,UAAU,KAAA,KAAa,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAC5D,OAAO;EAAE,MAAM,GAAG,IAAI,IAAI;EAAS,OAAO;CAAK;CAEjD,MAAM,YAAY,iBAAiB,KAAK;CACxC,OAAO;EAAE,MAAM,GAAG,MAAM,YAAY;EAAS,OAAO;CAAU;AAChE;AAEA,SAAgB,kBACd,OACA,SACe;CACf,MAAM,IAAI,SAAS;CACnB,MAAM,IAAI,WAAW;CACrB,OAAO;EACL,MAAM,GAAG,MAAM,GAAG,IAAI,MAAM,GAAG,IAAI,GAAG,IAAI;EAC1C,OAAO,IAAI,EAAE,IAAI;CACnB;AACF;AAEA,SAAgB,kBACd,WAC2B;CAC3B,IAAI,CAAC,aAAa,cAAc,MAAM,OAAO,KAAA;CAC7C,OAAO;EACL,MAAM,GAAG,UAAU,YAAY;EAC/B,OAAO;CACT;AACF;AAEA,SAAgB,mBACd,KACA,QAC2B;CAC3B,MAAM,OAAO,MAAM,QAAQ,GAAG,IAAI;CAClC,MAAM,KAAK,UAAU,WAAW,SAAS,SAAS;CAClD,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO,KAAA;CACzB,IAAI,QAAQ,IACV,OAAO;EACL,MAAM,GAAG,OAAO,OAAO,MAAM,GAAG,KAAK,GAAG,GAAG,GAAG;EAC9C,OAAO,GAAG,KAAK,IAAI,GAAG;CACxB;CAEF,IAAI,MACF,OAAO;EAAE,MAAM,GAAG,OAAO,OAAO;EAAS,OAAO;CAAK;CAEvD,OAAO;EAAE,MAAM,GAAG,OAAO,KAAK;EAAS,OAAO;CAAG;AACnD;;;;;;;AAQA,SAAgB,iBAAiB,KAAiC;CAChE,IAAI;EACF,IAAI,CAAC,KAAK,OAAO;EACjB,MAAM,UAAU,kBAAkB,KAAK;EACvC,IAAI,CAAC,SAAS,OAAO;EACrB,MAAM,EAAE,SAAS,MAAM,UAAU,oBAAoB;GACnD;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;EACD,MAAM,SAAS,UAAU,SAAS,MAAM;GACtC;GACA,SAAS;GACT,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACpC,CAAC;EACD,IAAI,OAAO,OAAO,OAAO;EACzB,MAAM,MACJ,OAAO,OAAO,WAAW,WAAW,OAAO,OAAO,KAAK,IAAI;EAC7D,IAAI,CAAC,OAAO,QAAQ,QAAQ,OAAO;EACnC,OAAO,IAAI,MAAM,IAAI,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK;CACvC,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,cAAc,MAAyB,QAAQ,KAAa;CAC1E,MAAM,MAAM,IAAI;CAChB,IAAI,QAAQ,KAAA,GAAW;EACrB,MAAM,IAAI,OAAO,GAAG;EACpB,IAAI,OAAO,UAAU,CAAC,KAAK,IAAI,GAAG,OAAO;CAC3C;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,mBACd,aACA,OACA,OAAgE,CAAC,GACzD;CACR,MAAM,MAAM,YAAY,KAAK;CAC7B,MAAM,UAAU,MAAM,GAAG,YAAY,MAAM,UAAU;CAErD,MAAM,YAAkC,CAAC,gBAAgB,MAAM,OAAO,CAAC;CACvE,MAAM,QAAQ,kBAAkB,MAAM,SAAS;CAC/C,IAAI,OAAO,UAAU,KAAK,KAAK;CAG/B,MAAM,SAAS,mBAAmB,MAAM,KAAK,KAAK,MAAM;CACxD,IAAI,QAAQ,UAAU,KAAK,MAAM;CACjC,UAAU,KACR,gBAAgB,KAAK,SAAS,GAC9B,iBAAiB,MAAM,kBAAkB,MAAM,iBAAiB,GAChE,gBAAgB,MAAM,eAAe,GACrC,kBAAkB,MAAM,YAAY,MAAM,YAAY,CACxD;CAEA,MAAM,QAAQ,KAAK,SAAS,cAAc;CAC1C,MAAM,SAAS;CAIf,IAAI,QAAQ,UAAU;CACtB,OAAO,QAAQ,GAAG;EAChB,IAAI,QAAQ,MAAM,IAAI,SAAS;EAC/B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;GAC9B,IAAI,OAAO,IAAI,GAAG,SAAS;GAC3B,SAAS,UAAU,EAAE,CAAC,MAAM;EAC9B;EACA,IAAI,SAAS,OAAO;EAGpB,IAAI,UAAU,KAAK,CAAC,KAAK;EACzB;CACF;CAEA,MAAM,QAAuB,CAAC;CAC9B,IAAI,KAAK,MAAM,KAAK,OAAO;CAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC,IAAI;CAC5D,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,OAAO,MAAM,KAAK,QAAQ;AAC5B;;;;;;;;AASA,SAAgB,oBACd,UACA,aACA,OAAwE,CAAC,GACjE;CACR,MAAM,QAAQ,iBAAiB,QAAQ;CACvC,MAAM,SACJ,KAAK,mBAAmB,KAAA,IACpB,KAAK,iBACL,iBAAiB,MAAM,GAAG;CAChC,OAAO,mBAAmB,aAAa,OAAO;EAC5C,GAAI,KAAK,UAAU,KAAA,IAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EACxD,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,KAAK,cAAc,KAAA,IAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;CACtE,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrWA,SAAS,YAAoB;CAC3B,IAAI;EACF,IAAI,QAAQ,MAAM,OAAO,OAAO;EAChC,OAAO,aAAa,GAAG,MAAM;CAC/B,QAAQ;EAEN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,qBACd,aACA,UACQ;CACR,MAAM,MAAM,YAAY,KAAK;CAC7B,MAAM,OAAO,SAAS,MAAM,IAAI,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK;CAChD,IAAI,OAAO,MAAM,OAAO,GAAG,IAAI,GAAG;CAClC,OAAO,OAAO;AAChB;;;;;;AAOA,SAAgB,kBACd,SACA,YAAY,KACJ;CACR,IAAI,CAAC,QAAQ,KAAK,GAAG,OAAO;CAC5B,IAAI;EACF,MAAM,SAAS,UAAU,SAAS;GAChC,OAAO;GACP,SAAS;GACT,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;GAGlC,KAAK,QAAQ;EACf,CAAC;EACD,IAAI,OAAO,OAAO,OAAO;EAEzB,QADY,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS,GAAA,CACrD,MAAM,IAAI,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK;CACvC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,kBAA4D;CACnE,IAAI;EACF,MAAM,aAAa,QAAQ,IAAI;EAC/B,IAAI,CAAC,YAAY,OAAO,EAAE,UAAU,GAAG;EACvC,MAAM,WAAW,oBAAoB,UAAU;EAC/C,IAAI,CAAC,UAAU,OAAO,EAAE,UAAU,GAAG;EAGrC,IAAI,SAAS,aAAa,KAAK,SAAS,gBAAgB,GACtD,OAAO,EAAE,UAAU,GAAG;EAExB,OAAO;GACL,UAAU,gBAAgB,QAAQ;GAClC,WAAW,yBAAyB,QAAQ,CAAC,CAAC;EAChD;CACF,QAAQ;EACN,OAAO,EAAE,UAAU,GAAG;CACxB;AACF;AAEA,MAAa,oBAAoB,cAAc;CAC7C,MAAM;EACJ,MAAM;EACN,aACE;CACJ;CACA,MAAM,MAAM;EACV,MAAM,WAAW,UAAU;EAC3B,MAAM,EAAE,UAAU,KAAK,cAAc,gBAAgB;EACrD,MAAM,cAAc,QAAQ,IAAI,iCAAiC;EAGjE,IAAI,YAAY,KAAK,GAAG;GAEtB,MAAM,OAAO,qBAAqB,KADjB,kBAAkB,WACW,CAAC;GAC/C,IAAI,MAAM,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG;GAC1C,QAAQ,WAAW;GACnB;EACF;EAIA,IAAI;EACJ,IAAI;GACF,OAAO,oBAAoB,UAAU,KAAK,EACxC,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC,EACjD,CAAC;EACH,QAAQ;GACN,OAAO,IAAI,KAAK;EAClB;EACA,IAAI,MAAM,QAAQ,OAAO,MAAM,GAAG,KAAK,GAAG;EAI1C,QAAQ,WAAW;CACrB;AACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"internal-aic-status-BiblmImt.js","names":[],"sources":["../src/lib/default-statusline.ts","../src/internal-aic-status.ts"],"sourcesContent":["/**\n * Rich default status line for `github-router claude` sessions.\n *\n * Replaces the need for users to maintain their own PowerShell/bash status\n * script: `internal-aic-status` renders these segments natively from the\n * Claude Code stdin JSON, then pins the `[AIC x.xx]` ledger fragment ahead\n * of them. Zero dependencies, cross-platform (Windows 11 is the primary\n * target — no `powershell`/`bash`/`jq` required), and fast (no shell\n * startup; one bounded `git rev-parse` at most).\n *\n * Segment order (left = highest priority; narrow terminals drop from the\n * right, `AIC` is never dropped):\n *\n * `[AIC] | [ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`\n *\n * The `$` segment is the DISCOUNTED actual (`~$` = factor-discounted\n * session total from `copilot-discount.ts`, capped at AIC × $0.01) — NOT\n * Claude's `cost.total_cost_usd`, which prices at Anthropic list rates and\n * reads ~10-100x high against Copilot billing (a 28-day sample: 41.1B tokens\n * for $5,142 ≈ $0.13/1M blended vs $15-75/1M list). Rendering list price\n * next to the real `[AIC]` billing unit would show two contradictory\n * numbers, so list price is deliberately not rendered at all.\n *\n * Width comes from `COLUMNS` (set by Claude Code for status scripts since\n * v2.1.153); fallback is 120 when unset/unparseable so a missing `COLUMNS`\n * never needlessly drops segments.\n */\n\nimport { spawnSync } from \"node:child_process\"\n\nimport { USD_PER_AIC_CREDIT } from \"./copilot-discount\"\nimport {\n buildExecInvocation,\n resolveExecutable,\n} from \"./exec\"\n\nconst ESC = \"\\x1b\"\nconst RESET = `${ESC}[0m`\nconst DIM = `${ESC}[90m`\nconst GREEN = `${ESC}[32m`\nconst YELLOW = `${ESC}[33m`\nconst RED = `${ESC}[31m`\nconst ORANGE = `${ESC}[38;5;208m`\nconst CYAN = `${ESC}[36m`\nconst MAGENTA = `${ESC}[35m`\nconst BLUE = `${ESC}[34m`\nconst AIC_COLOR = `${ESC}[1;33m`\n\nconst CTX_BAR_LEN = 10\nconst GIT_TIMEOUT_MS = 300\nconst WIDTH_FALLBACK = 120\nconst SEP_PLAIN = \" | \"\nconst SEP_TEXT = `${DIM} | ${RESET}`\n\nexport interface StatusInput {\n usedPct?: number\n totalInputTokens?: number\n totalOutputTokens?: number\n totalDurationMs?: number\n linesAdded?: number\n linesRemoved?: number\n cwd?: string\n modelName?: string\n}\n\nexport interface StatusSegment {\n text: string\n plain: string\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined\n }\n return value as Record<string, unknown>\n}\n\nfunction finiteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined\n}\n\n/**\n * Parse Claude Code's status-line stdin JSON into the fields we render.\n * Total function: malformed JSON or unexpected shapes yield `{}` (every\n * segment then renders its `--` placeholder or is omitted) — never throws.\n */\nexport function parseStatusInput(raw: string): StatusInput {\n if (!raw.trim()) return {}\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch {\n return {}\n }\n const root = asRecord(parsed)\n if (!root) return {}\n const out: StatusInput = {}\n\n const ctx = asRecord(root.context_window)\n if (ctx) {\n const pct = finiteNumber(ctx.used_percentage)\n if (pct !== undefined) out.usedPct = pct\n const tIn = finiteNumber(ctx.total_input_tokens)\n if (tIn !== undefined && tIn >= 0) out.totalInputTokens = Math.floor(tIn)\n const tOut = finiteNumber(ctx.total_output_tokens)\n if (tOut !== undefined && tOut >= 0) out.totalOutputTokens = Math.floor(tOut)\n }\n\n const cost = asRecord(root.cost)\n if (cost) {\n const dur = finiteNumber(cost.total_duration_ms)\n if (dur !== undefined && dur >= 0) out.totalDurationMs = dur\n const la = finiteNumber(cost.total_lines_added)\n if (la !== undefined && la >= 0) out.linesAdded = Math.floor(la)\n const lr = finiteNumber(cost.total_lines_removed)\n if (lr !== undefined && lr >= 0) out.linesRemoved = Math.floor(lr)\n }\n\n const workspace = asRecord(root.workspace)\n const wsDir = workspace ? nonEmptyString(workspace.current_dir) : undefined\n const cwd = wsDir ?? nonEmptyString(root.cwd)\n if (cwd) out.cwd = cwd\n\n const model = asRecord(root.model)\n if (model) {\n const name =\n nonEmptyString(model.display_name) ?? nonEmptyString(model.id)\n if (name) out.modelName = name\n }\n\n return out\n}\n\n/** Compact token count: `999` → `999`, `15234` → `15.2k`, `2.1M`. */\nexport function compactTokens(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`\n if (n >= 1000) return `${(n / 1000).toFixed(1)}k`\n return `${n}`\n}\n\n/** `45000` → `0m45s`, `198000` → `3m18s`, `3720000` → `1h02m`. */\nexport function formatDurationMs(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n if (hours >= 1) {\n return `${hours}h${String(minutes).padStart(2, \"0\")}m`\n }\n return `${minutes}m${String(seconds).padStart(2, \"0\")}s`\n}\n\n/** Basename of a cwd, tolerant of `/` and `\\` separators + trailing slash. */\nexport function dirLeaf(cwd: string): string {\n const trimmed = cwd.replace(/[/\\\\]+$/, \"\")\n const parts = trimmed.split(/[/\\\\]/)\n return parts[parts.length - 1] ?? trimmed\n}\n\nexport function buildCtxSegment(usedPct: number | undefined): StatusSegment {\n if (usedPct === undefined || !Number.isFinite(usedPct)) {\n return {\n text: `${DIM}[----------] --%${RESET}`,\n plain: \"[----------] --%\",\n }\n }\n const u = Math.max(0, Math.min(100, Math.round(usedPct)))\n let filled = Math.round((u / 100) * CTX_BAR_LEN)\n if (filled > CTX_BAR_LEN) filled = CTX_BAR_LEN\n if (filled < 0) filled = 0\n const bar = `#`.repeat(filled) + `-`.repeat(CTX_BAR_LEN - filled)\n const color = u < 50 ? GREEN : u < 75 ? YELLOW : RED\n return {\n text: `${color}[${bar}] ${u}%${RESET}`,\n plain: `[${bar}] ${u}%`,\n }\n}\n\n/**\n * AIC credits → undiscounted USD. 1 credit ≈ $0.01 — the same convention as\n * the session exit summary (`aic-ledger.ts`). Kept for tests/callers that\n * need the raw conversion; the status line itself shows the\n * factor-discounted total from `copilot-discount.ts`.\n */\nexport function usdFromAicCredits(credits: number): number {\n return credits * USD_PER_AIC_CREDIT\n}\n\n/**\n * Discounted-actual `$` segment. Callers pass the capped total from\n * `discountedUsdForSnapshot` — this function only formats. Placeholder\n * `~$--` until the first priced upstream response lands in the ledger.\n * Thresholds are tuned for ACTUALS scale (a session runs ~$0.10-2.00) —\n * not the old list-price scale.\n */\nexport function buildUsdSegment(\n actualUsd: number | undefined,\n): StatusSegment {\n if (actualUsd === undefined || !Number.isFinite(actualUsd) || actualUsd < 0) {\n return { text: `${DIM}~$--${RESET}`, plain: \"~$--\" }\n }\n const c = actualUsd\n const color = c < 1 ? GREEN : c < 5 ? YELLOW : c < 20 ? ORANGE : RED\n const formatted = c < 10 ? c.toFixed(2) : c.toFixed(1)\n return {\n text: `${color}~$${formatted}${RESET}`,\n plain: `~$${formatted}`,\n }\n}\n\nexport function buildToksSegment(\n tIn: number | undefined,\n tOut: number | undefined,\n): StatusSegment {\n if (tIn === undefined || tOut === undefined) {\n return { text: `${DIM}--/--${RESET}`, plain: \"--/--\" }\n }\n const cin = compactTokens(tIn)\n const cout = compactTokens(tOut)\n return {\n text: `${CYAN}${cin}${RESET}${DIM}/${RESET}${MAGENTA}${cout}${RESET}`,\n plain: `${cin}/${cout}`,\n }\n}\n\nexport function buildDurSegment(durMs: number | undefined): StatusSegment {\n if (durMs === undefined || !Number.isFinite(durMs) || durMs < 0) {\n return { text: `${DIM}--${RESET}`, plain: \"--\" }\n }\n const formatted = formatDurationMs(durMs)\n return { text: `${DIM}${formatted}${RESET}`, plain: formatted }\n}\n\nexport function buildLinesSegment(\n added: number | undefined,\n removed: number | undefined,\n): StatusSegment {\n const a = added ?? 0\n const r = removed ?? 0\n return {\n text: `${GREEN}+${a}${RESET} ${RED}-${r}${RESET}`,\n plain: `+${a} -${r}`,\n }\n}\n\nexport function buildModelSegment(\n modelName: string | undefined,\n): StatusSegment | undefined {\n if (!modelName || modelName === \"--\") return undefined\n return {\n text: `${MAGENTA}${modelName}${RESET}`,\n plain: modelName,\n }\n}\n\nexport function buildDirGitSegment(\n cwd: string | undefined,\n branch: string | undefined,\n): StatusSegment | undefined {\n const leaf = cwd ? dirLeaf(cwd) : \"\"\n const br = branch && branch !== \"HEAD\" ? branch : \"\"\n if (!leaf && !br) return undefined\n if (leaf && br) {\n return {\n text: `${BLUE}${leaf}${RESET} ${CYAN}(${br})${RESET}`,\n plain: `${leaf} (${br})`,\n }\n }\n if (leaf) {\n return { text: `${BLUE}${leaf}${RESET}`, plain: leaf }\n }\n return { text: `${CYAN}${br}${RESET}`, plain: br }\n}\n\n/**\n * Resolve the current git branch for `cwd`. Bounded (`GIT_TIMEOUT_MS`) and\n * total: any failure (no git binary, not a repo, detached `HEAD`, timeout,\n * `%` in a Windows path that cmd.exe cannot safely quote) yields `\"\"`\n * (caller omits the branch) — never throws.\n */\nexport function resolveGitBranch(cwd: string | undefined): string {\n try {\n if (!cwd) return \"\"\n const gitPath = resolveExecutable(\"git\")\n if (!gitPath) return \"\"\n const { command, args, shell } = buildExecInvocation([\n gitPath,\n \"-C\",\n cwd,\n \"rev-parse\",\n \"--abbrev-ref\",\n \"HEAD\",\n ])\n const result = spawnSync(command, args, {\n shell,\n timeout: GIT_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n })\n if (result.error) return \"\"\n const out =\n typeof result.stdout === \"string\" ? result.stdout.trim() : \"\"\n if (!out || out === \"HEAD\") return \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\n/** Terminal width for the drop-right-to-left assembly. */\nexport function terminalWidth(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.COLUMNS\n if (raw !== undefined) {\n const n = Number(raw)\n if (Number.isInteger(n) && n > 0) return n\n }\n return WIDTH_FALLBACK\n}\n\n/**\n * Assemble the full status line. `aicFragment` (e.g. `[AIC 12.42]`) is\n * PINNED — it renders even when nothing else fits. `actualUsd` feeds the\n * `~$` segment (the capped factor-discounted total from\n * `discountedUsdForSnapshot`, same snapshot the fragment came from).\n * Droppable segments in priority order: ctx, model, dir/git, ~$, toks,\n * dur, lines.\n */\nexport function assembleStatusLine(\n aicFragment: string,\n input: StatusInput,\n opts: { width?: number; branch?: string; actualUsd?: number } = {},\n): string {\n const aic = aicFragment.trim()\n const aicText = aic ? `${AIC_COLOR}${aic}${RESET}` : \"\"\n\n const droppable: Array<StatusSegment> = [buildCtxSegment(input.usedPct)]\n const model = buildModelSegment(input.modelName)\n if (model) droppable.push(model)\n // Location context sits right after ctx/model: more useful than the\n // numbers when the bar is wide, still dropped before ctx/model.\n const dirGit = buildDirGitSegment(input.cwd, opts.branch)\n if (dirGit) droppable.push(dirGit)\n droppable.push(\n buildUsdSegment(opts.actualUsd),\n buildToksSegment(input.totalInputTokens, input.totalOutputTokens),\n buildDurSegment(input.totalDurationMs),\n buildLinesSegment(input.linesAdded, input.linesRemoved),\n )\n\n const width = opts.width ?? terminalWidth()\n const sepLen = SEP_PLAIN.length\n\n // Drop from the end (lowest priority) until the line fits. AIC is pinned:\n // it is excluded from the dropping loop entirely.\n let count = droppable.length\n while (count > 0) {\n let total = aic ? aic.length : 0\n for (let i = 0; i < count; i++) {\n if (aic || i > 0) total += sepLen\n total += droppable[i].plain.length\n }\n if (total <= width) break\n // Never drop the last segment when there is no AIC: an over-wide line\n // with real data beats an empty status line.\n if (count === 1 && !aic) break\n count--\n }\n\n const parts: Array<string> = []\n if (aic) parts.push(aicText)\n for (let i = 0; i < count; i++) parts.push(droppable[i].text)\n if (parts.length === 0) return \"\"\n return parts.join(SEP_TEXT)\n}\n\n/**\n * Convenience: parse stdin JSON + assemble with branch resolution.\n * `branchOverride` skips the `git` spawn (used by tests). `actualUsd`\n * comes from `discountedUsdForSnapshot` on the same ledger snapshot as\n * `aicFragment` (read by the caller — this module never touches the\n * ledger file).\n */\nexport function buildRichStatusLine(\n stdinRaw: string,\n aicFragment: string,\n opts: { width?: number; branchOverride?: string; actualUsd?: number } = {},\n): string {\n const input = parseStatusInput(stdinRaw)\n const branch =\n opts.branchOverride !== undefined\n ? opts.branchOverride\n : resolveGitBranch(input.cwd)\n return assembleStatusLine(aicFragment, input, {\n ...(opts.width !== undefined ? { width: opts.width } : {}),\n ...(branch ? { branch } : {}),\n ...(opts.actualUsd !== undefined ? { actualUsd: opts.actualUsd } : {}),\n })\n}\n","/**\n * The internal `internal-aic-status` subcommand: the executable a spawned\n * Claude Code session's `statusLine` invokes (registered into the mirrored\n * settings.json by the launcher — see `src/lib/aic-statusline-settings.ts`).\n *\n * Default-on rich status line: Claude Code's stdin JSON is rendered natively\n * (see `src/lib/default-statusline.ts`) as\n * `[ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`,\n * with this launch's AIC ledger total PINNED ahead of it: `[AIC 12.42] ...`.\n * The `~$` segment is the factor-discounted actual (per-model static\n * factors over ledger nano, capped at AIC × $0.01 — see\n * `src/lib/copilot-discount.ts`), not Claude's list-price `total_cost_usd`.\n * Narrow terminals drop segments right-to-left; AIC is never dropped.\n *\n * When the user already had a `statusLine` command, its text arrives via\n * `GH_ROUTER_AIC_USER_STATUSLINE` and is run instead of the rich default\n * (their script already renders ctx/cost/model itself — rendering both\n * would duplicate every segment), so the output stays one composed line:\n * `[AIC 12.42] <user line>`.\n *\n * Status-line contract: stdout is the rendered line (first line wins), fast\n * (<1s budget — ledger read is local, git branch has a 300ms cap, the user\n * command gets a 5s cap), and NEVER failing (any error → print whatever is\n * available → exit 0).\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { spawnSync } from \"node:child_process\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n formatAicStatus,\n readAicSnapshotFile,\n} from \"./lib/aic-ledger\"\nimport { discountedUsdForSnapshot } from \"./lib/copilot-discount\"\nimport { buildRichStatusLine } from \"./lib/default-statusline\"\n\n/**\n * Read stdin synchronously. Claude Code pipes session JSON to the\n * statusLine command; an unread pipe can SIGPIPE the writer on some\n * platforms. Returns \"\" when stdin is a TTY or unreadable — callers render\n * placeholders/omit segments rather than failing.\n */\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n // Best-effort; the ledger read below does not depend on stdin.\n return \"\"\n }\n}\n\n/**\n * Pure core: compose the status line from an AIC fragment and a user line.\n * Exported for unit tests.\n */\nexport function composeAicStatusLine(\n aicFragment: string,\n userLine: string,\n): string {\n const aic = aicFragment.trim()\n const user = userLine.split(\"\\n\")[0]?.trim() ?? \"\"\n if (aic && user) return `${aic} ${user}`\n return aic || user\n}\n\n/**\n * Run the user's own statusLine command (from `GH_ROUTER_AIC_USER_STATUSLINE`),\n * returning its first stdout line or \"\" on any failure/timeout. Exported for\n * unit tests (command + timeout injectable).\n *\n * Claude Code pipes session JSON to the statusLine command's stdin, and\n * user scripts commonly block reading it to EOF — so the received\n * `stdinRaw` is forwarded via `input` (written then closed) rather than\n * leaving stdin disconnected, which would hang such scripts until the\n * timeout and silently drop their segment.\n */\nexport function runUserStatusLine(\n command: string,\n stdinRaw: string,\n timeoutMs = 5000,\n): string {\n if (!command.trim()) return \"\"\n try {\n const result = spawnSync(command, {\n shell: true,\n timeout: timeoutMs,\n encoding: \"utf8\",\n input: stdinRaw,\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n // Inherit env so user scripts see the same session context they would\n // under their own statusLine (cwd included — spawnSync inherits it).\n env: process.env,\n })\n if (result.error) return \"\"\n const out = typeof result.stdout === \"string\" ? result.stdout : \"\"\n return out.split(\"\\n\")[0]?.trim() ?? \"\"\n } catch {\n return \"\"\n }\n}\n\nfunction readAicSnapshot(): { fragment: string; actualUsd?: number } {\n try {\n const ledgerPath = process.env.GH_ROUTER_AIC_LEDGER\n if (!ledgerPath) return { fragment: \"\" }\n const snapshot = readAicSnapshotFile(ledgerPath)\n if (!snapshot) return { fragment: \"\" }\n // No priced responses yet → no fragment AND no USD: the `~$`\n // segment renders its `~$--` placeholder instead of `~$0.00`.\n if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) {\n return { fragment: \"\" }\n }\n return {\n fragment: formatAicStatus(snapshot),\n actualUsd: discountedUsdForSnapshot(snapshot).total,\n }\n } catch {\n return { fragment: \"\" }\n }\n}\n\nexport const internalAicStatus = defineCommand({\n meta: {\n name: \"internal-aic-status\",\n description:\n \"Internal: Claude Code statusLine handler printing AIC total + rich session line.\",\n },\n async run() {\n const stdinRaw = readStdin()\n const { fragment: aic, actualUsd } = readAicSnapshot()\n const userCommand = process.env.GH_ROUTER_AIC_USER_STATUSLINE ?? \"\"\n // Wrap mode: the user's own script already renders ctx/cost/model from\n // the same stdin JSON — run it and prepend AIC only.\n if (userCommand.trim()) {\n const userLine = runUserStatusLine(userCommand, stdinRaw)\n const line = composeAicStatusLine(aic, userLine)\n if (line) process.stdout.write(`${line}\\n`)\n process.exitCode = 0\n return\n }\n // Default mode: native rich line with AIC pinned ahead of it. The\n // builder never throws; an empty result (no ledger yet AND no stdin)\n // prints nothing rather than a placeholder line.\n let line: string\n try {\n line = buildRichStatusLine(stdinRaw, aic, {\n ...(actualUsd !== undefined ? { actualUsd } : {}),\n })\n } catch {\n line = aic.trim()\n }\n if (line) process.stdout.write(`${line}\\n`)\n // Signal success WITHOUT tearing the loop down — same resolution as the\n // other internal hooks (see main.ts help-path comment): a hard\n // process.exit() can abort through a pipe on Windows.\n process.exitCode = 0\n },\n})\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as ArtifactClient } from "./client-CQMfroGV.js";
|
|
2
|
-
import { a as isSubagentContext } from "./stop-gate-policy
|
|
2
|
+
import { a as isSubagentContext } from "./stop-gate-policy-C0gt04R0.js";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -316,4 +316,4 @@ const internalArtifactOpen = defineCommand({
|
|
|
316
316
|
//#endregion
|
|
317
317
|
export { internalArtifactOpen };
|
|
318
318
|
|
|
319
|
-
//# sourceMappingURL=internal-artifact-open-
|
|
319
|
+
//# sourceMappingURL=internal-artifact-open-CA1trJ15.js.map
|
package/dist/{internal-artifact-open-B3stUu74.js.map → internal-artifact-open-CA1trJ15.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-artifact-open-B3stUu74.js","names":[],"sources":["../src/lib/artifact/plan-html.ts","../src/internal-artifact-open.ts"],"sourcesContent":["/**\n * Render a plan's markdown into a self-contained, styled HTML document for the\n * ai-or-die Artifact panel. HTML is the canonical review artifact (lavish's\n * model); converting here means the hands-off auto-open shows a formatted,\n * annotatable page instead of raw markdown.\n *\n * Security: the iframe ai-or-die serves this into runs with\n * `allow-scripts allow-same-origin`, so a `<script>` smuggled into the plan\n * markdown could read the per-session asset token. Two defenses:\n * 1. marked is configured to ESCAPE raw HTML tokens (render as visible text).\n * 2. `link`/`image` hrefs with script-bearing schemes (javascript:, vbscript:,\n * non-image data:) are neutralised before rendering.\n * No separate sanitizer/dep needed.\n */\nimport { Marked } from \"marked\"\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n}\n\n/** Decode the HTML character references a browser would resolve in an attribute\n * value, so an entity-obfuscated scheme (`javascript:`, `javascript:`)\n * cannot slip past the scheme check below. Numeric refs cover any letter; the\n * small named map covers the scheme-relevant punctuation. */\nconst NAMED_REFS: Record<string, string> = {\n colon: \":\", tab: \"\\t\", newline: \"\\n\", sol: \"/\", semi: \";\", amp: \"&\", lpar: \"(\", rpar: \")\",\n}\nfunction decodeEntities(s: string): string {\n return s.replace(/&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);?/g, (m, body: string) => {\n if (body[0] === \"#\") {\n const code = body[1] === \"x\" || body[1] === \"X\"\n ? Number.parseInt(body.slice(2), 16)\n : Number.parseInt(body.slice(1), 10)\n return Number.isFinite(code) && code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : m\n }\n return NAMED_REFS[body.toLowerCase()] ?? m\n })\n}\n\n/** Block script-bearing URL schemes (defense-in-depth on top of HTML escaping). */\nfunction isDangerousUrl(url: string, isImage: boolean): boolean {\n // Decode entities first (a browser resolves them in href), then drop ASCII\n // control chars + spaces (code <= 0x20) a browser ignores inside a scheme.\n const v = Array.from(decodeEntities(url)).filter((c) => c.charCodeAt(0) > 0x20).join(\"\").toLowerCase()\n if (v.startsWith(\"javascript:\") || v.startsWith(\"vbscript:\")) return true\n if (v.startsWith(\"data:\")) {\n // Allow only raster image data URIs (and only for <img>); SVG can script.\n return !(isImage && v.startsWith(\"data:image/\") && !v.startsWith(\"data:image/svg\"))\n }\n return false\n}\n\n/** A marked instance that escapes raw HTML (raw markup never becomes live). */\nfunction makeMarked(): Marked {\n const m = new Marked({ gfm: true, breaks: false })\n m.use({\n renderer: {\n // Raw HTML in the source is rendered as escaped text, never live markup.\n html(token: unknown): string {\n const text = typeof token === \"string\" ? token : ((token as { text?: string }).text ?? \"\")\n return escapeHtml(text)\n },\n },\n })\n return m\n}\n\ninterface MdToken {\n type?: string\n href?: string\n tokens?: MdToken[]\n items?: MdToken[]\n header?: Array<{ tokens?: MdToken[] }>\n rows?: Array<Array<{ tokens?: MdToken[] }>>\n}\n\n/**\n * Recursively neutralise script-bearing link/image hrefs in the lexed token tree.\n * Done as an explicit walk (not marked's `walkTokens` hook) because we render via\n * the low-level `lexer`+`parser` split, which does not run registered walkTokens.\n */\nfunction neutralizeUrls(tokens: MdToken[]): void {\n for (const t of tokens) {\n if ((t.type === \"link\" || t.type === \"image\") && typeof t.href === \"string\") {\n if (isDangerousUrl(t.href, t.type === \"image\")) t.href = \"#\"\n }\n if (t.tokens) neutralizeUrls(t.tokens)\n if (t.items) neutralizeUrls(t.items)\n if (t.header) for (const cell of t.header) if (cell.tokens) neutralizeUrls(cell.tokens)\n if (t.rows) for (const row of t.rows) for (const cell of row) if (cell.tokens) neutralizeUrls(cell.tokens)\n }\n}\n\n/** Add `data-source-line=\"N\"` to the first opening tag of a rendered block. */\nfunction tagSourceLine(html: string, line: number): string {\n return html.replace(/^(\\s*)<([a-zA-Z][\\w-]*)/, `$1<$2 data-source-line=\"${line}\"`)\n}\n\n/**\n * Render markdown to inner HTML, tagging each top-level block with its 1-based\n * source line so an annotation in the panel can map back to a plan line.\n */\nexport function renderMarkdownBody(source: string): string {\n const m = makeMarked()\n let tokens: Array<{ raw?: string }>\n try {\n tokens = m.lexer(source) as Array<{ raw?: string }>\n } catch {\n // Lexing should not fail, but never throw out of the hook path.\n return `<pre>${escapeHtml(source)}</pre>`\n }\n neutralizeUrls(tokens as MdToken[])\n let line = 1\n let out = \"\"\n for (const tok of tokens) {\n let html: string\n try {\n html = m.parser([tok] as Parameters<typeof m.parser>[0])\n } catch {\n html = \"\"\n }\n if (html.trim()) out += tagSourceLine(html, line)\n line += (tok.raw?.match(/\\n/g)?.length ?? 0)\n }\n return out || `<pre>${escapeHtml(source)}</pre>`\n}\n\n/**\n * Wrap rendered markdown in a complete, self-contained HTML document with\n * readable typography (works even before ai-or-die injects its annotation SDK).\n */\nexport function renderPlanHtml(source: string, title = \"Plan\"): string {\n return wrapArtifactDocument(renderMarkdownBody(source), title)\n}\n\nconst BASE_STYLE = ` :root {\n --bg: #0f1115; --fg: #f7f3ea; --muted: #aeb6c6; --border: #2a2f3a;\n --accent: #f4c95d; --code-bg: #171a21;\n --serif: 'Iowan Old Style', Georgia, 'Times New Roman', serif;\n --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; background: var(--bg); color: var(--fg); }\n body { font-family: var(--serif); font-size: 16px; line-height: 1.6;\n padding: 32px clamp(16px, 5vw, 56px); }\n main { max-width: 78ch; margin: 0 auto; }\n h1, h2, h3, h4 { font-family: var(--sans); line-height: 1.25; margin: 1.6em 0 0.5em; }\n h1 { font-size: 1.9em; } h2 { font-size: 1.45em; padding-bottom: 0.2em;\n border-bottom: 1px solid var(--border); } h3 { font-size: 1.2em; }\n p, li { color: var(--fg); }\n a { color: var(--accent); }\n code { font-family: var(--mono); font-size: 0.9em; background: var(--code-bg);\n padding: 0.12em 0.35em; border-radius: 5px; }\n pre { background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px;\n padding: 12px 14px; overflow: auto; }\n pre code { background: none; padding: 0; }\n blockquote { margin: 1em 0; padding: 0.2em 1em; border-left: 3px solid var(--accent);\n color: var(--muted); }\n table { border-collapse: collapse; width: 100%; margin: 1em 0; font-family: var(--sans);\n font-size: 0.95em; }\n th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }\n th { background: var(--code-bg); }\n hr { border: 0; border-top: 1px solid var(--border); margin: 1.5em 0; }\n ul, ol { padding-left: 1.4em; }`\n\nconst INTERACTIVE_STYLE = `\n .aod-group { margin: 1.4em 0; padding: 14px 16px; border: 1px solid var(--border);\n border-radius: 10px; background: var(--code-bg); }\n .aod-prompt { font-family: var(--sans); font-weight: 600; margin: 0 0 0.6em; }\n .aod-options { display: flex; flex-wrap: wrap; gap: 8px; }\n .aod-multi-select .aod-options { flex-direction: column; gap: 6px; }\n .aod-action, .aod-submit { font-family: var(--sans); font-size: 0.95em; font-weight: 600;\n cursor: pointer; border: 1px solid var(--border); border-radius: 8px;\n padding: 8px 12px; background: var(--bg); color: var(--fg); }\n .aod-action:hover, .aod-submit:hover { border-color: var(--accent); }\n .aod-submit { margin-top: 10px; background: var(--accent); color: #17130a; border-color: var(--accent); }\n .aod-check { display: flex; align-items: center; gap: 8px; font-family: var(--sans); cursor: pointer; }\n .aod-steps { list-style: decimal; }\n .aod-step { margin: 0.5em 0; }\n .aod-step-actions { display: inline-flex; gap: 6px; margin-left: 8px; }`\n\n/**\n * Wrap rendered inner HTML in the self-contained document shell. `extraStyle` is\n * appended to the inline stylesheet so the interactive variant can add its own\n * rules without a second `<style>` block. Static callers pass \"\" (default), so\n * the static document is byte-identical to the pre-refactor output.\n */\nfunction wrapArtifactDocument(body: string, title: string, extraStyle = \"\"): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(title)}</title>\n<style>\n${BASE_STYLE}${extraStyle}\n</style>\n</head>\n<body>\n<main>\n${body}\n</main>\n</body>\n</html>\n`\n}\n\n/* -------------------------------------------------------------------------- */\n/* Interactive artifacts (contract v2.2 §4: declarative data-aod-* vocab) */\n/* -------------------------------------------------------------------------- */\n\nexport interface InteractiveOption {\n /** Stable id echoed back as `elementId` in the action event. Required. */\n elementId: string\n /** Human-visible label. */\n label: string\n /** Optional value echoed as `data-aod-value` (checkbox submits use \"true\"/\"false\"). */\n value?: string\n}\n\n/** One immediate-fire choice group: each option posts an action the instant it is clicked. */\nexport interface ChooseOneBlock {\n kind: \"choose-one\"\n /** `data-aod-group` shared by the options (marks them as one decision). */\n group: string\n prompt?: string\n /** `data-aod-action` verb for every option (default \"choose\"). */\n action?: string\n options: InteractiveOption[]\n}\n\n/** A multi-select group: checkbox toggles plus one submit that carries {group, selected}. */\nexport interface MultiSelectBlock {\n kind: \"multi-select\"\n group: string\n prompt?: string\n submitLabel?: string\n submitElementId?: string\n options: InteractiveOption[]\n}\n\nexport interface StepAction {\n /** `data-aod-action` verb (e.g. \"approve\", \"skip\"). */\n action: string\n label: string\n value?: string\n}\n\nexport interface StepItem {\n /** Stable `data-aod-id` echoed as `elementId`. */\n elementId: string\n label: string\n sourceLine?: number\n actions: StepAction[]\n}\n\n/** An ordered list of plan steps, each with its own inline action buttons. */\nexport interface StepsBlock {\n kind: \"steps\"\n steps: StepItem[]\n}\n\nexport type InteractiveBlock = ChooseOneBlock | MultiSelectBlock | StepsBlock\n\nexport interface InteractivePlanModel {\n /** Optional prose rendered above the interactive blocks (same escaping as static). */\n markdown?: string\n blocks: InteractiveBlock[]\n}\n\n/** Build a ` data-x=\"y\"` attribute string, dropping undefined values, all escaped. */\nfunction dataAttrs(pairs: Array<[string, string | undefined]>): string {\n let out = \"\"\n for (const [name, value] of pairs) {\n if (value === undefined) continue\n out += ` ${name}=\"${escapeHtml(value)}\"`\n }\n return out\n}\n\nfunction renderChooseOne(block: ChooseOneBlock): string {\n const action = block.action && block.action.trim() ? block.action : \"choose\"\n const prompt = block.prompt ? `<p class=\"aod-prompt\">${escapeHtml(block.prompt)}</p>` : \"\"\n const options = block.options\n .filter((o) => o.elementId.trim() !== \"\")\n .map((o) => {\n const attrs = dataAttrs([\n [\"data-aod-action\", action],\n [\"data-aod-group\", block.group],\n [\"data-aod-id\", o.elementId],\n [\"data-aod-value\", o.value],\n ])\n return `<button type=\"button\" class=\"aod-action\"${attrs}>${escapeHtml(o.label)}</button>`\n })\n .join(\"\\n\")\n return `<div class=\"aod-group aod-choose-one\"${dataAttrs([[\"data-aod-group\", block.group]])}>\n${prompt}<div class=\"aod-options\">\n${options}\n</div>\n</div>`\n}\n\nfunction renderMultiSelect(block: MultiSelectBlock): string {\n const prompt = block.prompt ? `<p class=\"aod-prompt\">${escapeHtml(block.prompt)}</p>` : \"\"\n const options = block.options\n .filter((o) => o.elementId.trim() !== \"\")\n .map((o) => {\n const attrs = dataAttrs([\n [\"data-aod-action\", \"check\"],\n [\"data-aod-group\", block.group],\n [\"data-aod-id\", o.elementId],\n [\"data-aod-value\", o.value],\n ])\n return `<label class=\"aod-check\"><input type=\"checkbox\"${attrs}> <span>${escapeHtml(o.label)}</span></label>`\n })\n .join(\"\\n\")\n const submitId = block.submitElementId && block.submitElementId.trim()\n ? block.submitElementId\n : `${block.group}-submit`\n const submitAttrs = dataAttrs([\n [\"data-aod-action\", \"submit\"],\n [\"data-aod-group\", block.group],\n [\"data-aod-id\", submitId],\n ])\n const submitLabel = block.submitLabel && block.submitLabel.trim() ? block.submitLabel : \"Submit\"\n return `<div class=\"aod-group aod-multi-select\"${dataAttrs([[\"data-aod-group\", block.group]])}>\n${prompt}<div class=\"aod-options\">\n${options}\n</div>\n<button type=\"button\" class=\"aod-submit\"${submitAttrs}>${escapeHtml(submitLabel)}</button>\n</div>`\n}\n\nfunction renderSteps(block: StepsBlock): string {\n const items = block.steps\n .filter((s) => s.elementId.trim() !== \"\")\n .map((s) => {\n const liAttrs = dataAttrs([\n [\"data-aod-id\", s.elementId],\n [\"data-source-line\", s.sourceLine === undefined ? undefined : String(s.sourceLine)],\n ])\n const actions = s.actions\n .map((a) => {\n const attrs = dataAttrs([\n [\"data-aod-action\", a.action],\n [\"data-aod-id\", s.elementId],\n [\"data-aod-value\", a.value],\n ])\n return `<button type=\"button\" class=\"aod-action\"${attrs}>${escapeHtml(a.label)}</button>`\n })\n .join(\"\")\n return `<li class=\"aod-step\"${liAttrs}><span class=\"aod-step-label\">${escapeHtml(s.label)}</span><span class=\"aod-step-actions\">${actions}</span></li>`\n })\n .join(\"\\n\")\n return `<ol class=\"aod-steps\">\n${items}\n</ol>`\n}\n\nfunction renderInteractiveBlock(block: InteractiveBlock): string {\n switch (block.kind) {\n case \"choose-one\":\n return renderChooseOne(block)\n case \"multi-select\":\n return renderMultiSelect(block)\n case \"steps\":\n return renderSteps(block)\n default:\n return \"\"\n }\n}\n\n/**\n * Render a typed interactive plan model into a self-contained HTML document that\n * carries the declarative `data-aod-*` action vocabulary the ai-or-die panel SDK\n * wires (contract v2.2 §4). Optional prose (`markdown`) is rendered with the SAME\n * escaping + dangerous-URL neutralization as the static path; all control\n * attributes and labels are HTML-escaped, so no author string can break out of an\n * attribute or inject live markup.\n */\nexport function renderInteractivePlanHtml(model: InteractivePlanModel, title = \"Plan\"): string {\n const prose = model.markdown && model.markdown.trim() ? renderMarkdownBody(model.markdown) : \"\"\n const blocks = model.blocks.map(renderInteractiveBlock).filter((h) => h.trim() !== \"\").join(\"\\n\")\n const body = [prose, blocks].filter((s) => s.trim() !== \"\").join(\"\\n\")\n return wrapArtifactDocument(body || `<pre>${escapeHtml(JSON.stringify(model))}</pre>`, title, INTERACTIVE_STYLE)\n}\n","/**\n * The internal `internal-artifact-open` subcommand: invoked by a spawned Claude\n * Code session's `PostToolUse` hook (matcher `ExitPlanMode`), registered into the\n * mirrored settings.json by the launcher when running inside an ai-or-die tab\n * (`AIORDIE_SESSION_ID` set). It auto-opens the just-finalized plan in the\n * ai-or-die Artifact review panel so the human reviews it WITHOUT the model\n * having to call artifact_open itself.\n *\n * Auth: AIORDIE_TOKEN is stripped from the child env, so the token can't come via\n * env or argv (argv leaks to `ps`). The launcher writes a mode-600\n * `.aiordie-artifact.json` into CLAUDE_CONFIG_DIR; this hook reads it.\n *\n * Plan: the EXACT plan for THIS session comes from the ExitPlanMode payload —\n * `tool_input.planFilePath` (per-session; never the globally-newest file in the\n * shared plans dir) with `tool_input.plan` as the markdown fallback. The plan\n * markdown is rendered to a self-contained styled HTML file (HTML is the\n * canonical artifact, lavish's model — formatted + annotatable, not raw md), and\n * THAT `.html` is opened.\n *\n * Side-effect only: never writes stdout, never throws, always exits 0. Skips\n * subagent payloads (agent_id/agent_type) like the other hooks.\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { readFileSync, realpathSync, writeFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport path from \"node:path\"\n\nimport { ArtifactClient } from \"./lib/artifact/client\"\nimport { renderPlanHtml } from \"./lib/artifact/plan-html\"\nimport { isSubagentContext } from \"./lib/orchestration/stop-gate-policy\"\n\ninterface ArtifactCreds {\n baseUrl: string\n token: string\n sessionId: string\n insecureTLS: boolean\n}\n\ninterface ExitPlanPayload {\n planFilePath?: string\n planMarkdown?: string\n}\n\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nfunction credsPath(): string {\n const dir = process.env.CLAUDE_CONFIG_DIR || path.join(homedir(), \".claude\")\n return path.join(dir, \".aiordie-artifact.json\")\n}\n\nfunction isSubagent(payload: unknown): boolean {\n try {\n return isSubagentContext(payload as Parameters<typeof isSubagentContext>[0])\n } catch {\n return false\n }\n}\n\n/** Pull the per-session plan path + markdown content out of the hook payload. */\nexport function parseExitPlanPayload(raw: string): ExitPlanPayload {\n try {\n const obj = JSON.parse(raw) as { tool_input?: { planFilePath?: unknown; plan?: unknown } }\n const ti = obj.tool_input ?? {}\n const planFilePath = typeof ti.planFilePath === \"string\" && ti.planFilePath.trim() ? ti.planFilePath : undefined\n const planMarkdown = typeof ti.plan === \"string\" && ti.plan.trim() ? ti.plan : undefined\n return { planFilePath, planMarkdown }\n } catch {\n return {}\n }\n}\n\nfunction readCreds(): ArtifactCreds | undefined {\n try {\n const parsed = JSON.parse(readFileSync(credsPath(), \"utf8\")) as Partial<ArtifactCreds>\n if (!parsed.baseUrl || !parsed.token || !parsed.sessionId) return undefined\n return {\n baseUrl: parsed.baseUrl,\n token: parsed.token,\n sessionId: parsed.sessionId,\n insecureTLS: parsed.insecureTLS === true,\n }\n } catch {\n return undefined\n }\n}\n\n/**\n * Resolve the plan markdown + a title, preferring the file at `planFilePath`\n * (the source of truth, may be newer than the payload's snapshot), falling back\n * to the inline `tool_input.plan` content.\n */\nfunction resolvePlanMarkdown(p: ExitPlanPayload): { markdown: string; title: string; nearPath?: string } | undefined {\n if (p.planFilePath) {\n try {\n const md = readFileSync(p.planFilePath, \"utf8\")\n if (md.trim()) return { markdown: md, title: path.basename(p.planFilePath, path.extname(p.planFilePath)), nearPath: p.planFilePath }\n } catch {\n /* fall through to inline content */\n }\n }\n if (p.planMarkdown) return { markdown: p.planMarkdown, title: \"Plan\" }\n return undefined\n}\n\n/**\n * Write the rendered HTML next to the plan (an allowed root) when we have a real\n * path, else into the plans dir under CLAUDE_CONFIG_DIR. Returns the realpath of\n * the written `.html`, or undefined on failure.\n */\nfunction writePlanHtml(html: string, nearPath: string | undefined): string | undefined {\n let target: string\n if (nearPath) {\n // `<plan>.md` -> `<plan>.aiordie.html` (or append when there is no .md ext).\n const base = nearPath.replace(/\\.(md|markdown)$/i, \"\")\n target = `${base}.aiordie.html`\n } else {\n const dir = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(homedir(), \".claude\"), \"plans\")\n target = path.join(dir, `aiordie-plan-${process.pid}.html`)\n }\n try {\n writeFileSync(target, html, \"utf8\")\n try {\n return realpathSync.native(target)\n } catch {\n return target\n }\n } catch {\n return undefined\n }\n}\n\nexport const internalArtifactOpen = defineCommand({\n meta: { name: \"internal-artifact-open\", description: \"Open the finalized plan in the ai-or-die panel\" },\n async run() {\n try {\n const raw = readStdin()\n let payload: unknown\n try {\n payload = JSON.parse(raw)\n } catch {\n payload = undefined\n }\n if (isSubagent(payload)) return\n const creds = readCreds()\n if (!creds) return\n const plan = resolvePlanMarkdown(parseExitPlanPayload(raw))\n if (!plan) return\n const htmlPath = writePlanHtml(renderPlanHtml(plan.markdown, plan.title), plan.nearPath)\n if (!htmlPath) return\n const client = new ArtifactClient(creds)\n await client.open(htmlPath)\n } catch {\n /* side-effect only: never block, never throw */\n }\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"internal-artifact-open-CA1trJ15.js","names":[],"sources":["../src/lib/artifact/plan-html.ts","../src/internal-artifact-open.ts"],"sourcesContent":["/**\n * Render a plan's markdown into a self-contained, styled HTML document for the\n * ai-or-die Artifact panel. HTML is the canonical review artifact (lavish's\n * model); converting here means the hands-off auto-open shows a formatted,\n * annotatable page instead of raw markdown.\n *\n * Security: the iframe ai-or-die serves this into runs with\n * `allow-scripts allow-same-origin`, so a `<script>` smuggled into the plan\n * markdown could read the per-session asset token. Two defenses:\n * 1. marked is configured to ESCAPE raw HTML tokens (render as visible text).\n * 2. `link`/`image` hrefs with script-bearing schemes (javascript:, vbscript:,\n * non-image data:) are neutralised before rendering.\n * No separate sanitizer/dep needed.\n */\nimport { Marked } from \"marked\"\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\")\n}\n\n/** Decode the HTML character references a browser would resolve in an attribute\n * value, so an entity-obfuscated scheme (`javascript:`, `javascript:`)\n * cannot slip past the scheme check below. Numeric refs cover any letter; the\n * small named map covers the scheme-relevant punctuation. */\nconst NAMED_REFS: Record<string, string> = {\n colon: \":\", tab: \"\\t\", newline: \"\\n\", sol: \"/\", semi: \";\", amp: \"&\", lpar: \"(\", rpar: \")\",\n}\nfunction decodeEntities(s: string): string {\n return s.replace(/&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);?/g, (m, body: string) => {\n if (body[0] === \"#\") {\n const code = body[1] === \"x\" || body[1] === \"X\"\n ? Number.parseInt(body.slice(2), 16)\n : Number.parseInt(body.slice(1), 10)\n return Number.isFinite(code) && code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : m\n }\n return NAMED_REFS[body.toLowerCase()] ?? m\n })\n}\n\n/** Block script-bearing URL schemes (defense-in-depth on top of HTML escaping). */\nfunction isDangerousUrl(url: string, isImage: boolean): boolean {\n // Decode entities first (a browser resolves them in href), then drop ASCII\n // control chars + spaces (code <= 0x20) a browser ignores inside a scheme.\n const v = Array.from(decodeEntities(url)).filter((c) => c.charCodeAt(0) > 0x20).join(\"\").toLowerCase()\n if (v.startsWith(\"javascript:\") || v.startsWith(\"vbscript:\")) return true\n if (v.startsWith(\"data:\")) {\n // Allow only raster image data URIs (and only for <img>); SVG can script.\n return !(isImage && v.startsWith(\"data:image/\") && !v.startsWith(\"data:image/svg\"))\n }\n return false\n}\n\n/** A marked instance that escapes raw HTML (raw markup never becomes live). */\nfunction makeMarked(): Marked {\n const m = new Marked({ gfm: true, breaks: false })\n m.use({\n renderer: {\n // Raw HTML in the source is rendered as escaped text, never live markup.\n html(token: unknown): string {\n const text = typeof token === \"string\" ? token : ((token as { text?: string }).text ?? \"\")\n return escapeHtml(text)\n },\n },\n })\n return m\n}\n\ninterface MdToken {\n type?: string\n href?: string\n tokens?: MdToken[]\n items?: MdToken[]\n header?: Array<{ tokens?: MdToken[] }>\n rows?: Array<Array<{ tokens?: MdToken[] }>>\n}\n\n/**\n * Recursively neutralise script-bearing link/image hrefs in the lexed token tree.\n * Done as an explicit walk (not marked's `walkTokens` hook) because we render via\n * the low-level `lexer`+`parser` split, which does not run registered walkTokens.\n */\nfunction neutralizeUrls(tokens: MdToken[]): void {\n for (const t of tokens) {\n if ((t.type === \"link\" || t.type === \"image\") && typeof t.href === \"string\") {\n if (isDangerousUrl(t.href, t.type === \"image\")) t.href = \"#\"\n }\n if (t.tokens) neutralizeUrls(t.tokens)\n if (t.items) neutralizeUrls(t.items)\n if (t.header) for (const cell of t.header) if (cell.tokens) neutralizeUrls(cell.tokens)\n if (t.rows) for (const row of t.rows) for (const cell of row) if (cell.tokens) neutralizeUrls(cell.tokens)\n }\n}\n\n/** Add `data-source-line=\"N\"` to the first opening tag of a rendered block. */\nfunction tagSourceLine(html: string, line: number): string {\n return html.replace(/^(\\s*)<([a-zA-Z][\\w-]*)/, `$1<$2 data-source-line=\"${line}\"`)\n}\n\n/**\n * Render markdown to inner HTML, tagging each top-level block with its 1-based\n * source line so an annotation in the panel can map back to a plan line.\n */\nexport function renderMarkdownBody(source: string): string {\n const m = makeMarked()\n let tokens: Array<{ raw?: string }>\n try {\n tokens = m.lexer(source) as Array<{ raw?: string }>\n } catch {\n // Lexing should not fail, but never throw out of the hook path.\n return `<pre>${escapeHtml(source)}</pre>`\n }\n neutralizeUrls(tokens as MdToken[])\n let line = 1\n let out = \"\"\n for (const tok of tokens) {\n let html: string\n try {\n html = m.parser([tok] as Parameters<typeof m.parser>[0])\n } catch {\n html = \"\"\n }\n if (html.trim()) out += tagSourceLine(html, line)\n line += (tok.raw?.match(/\\n/g)?.length ?? 0)\n }\n return out || `<pre>${escapeHtml(source)}</pre>`\n}\n\n/**\n * Wrap rendered markdown in a complete, self-contained HTML document with\n * readable typography (works even before ai-or-die injects its annotation SDK).\n */\nexport function renderPlanHtml(source: string, title = \"Plan\"): string {\n return wrapArtifactDocument(renderMarkdownBody(source), title)\n}\n\nconst BASE_STYLE = ` :root {\n --bg: #0f1115; --fg: #f7f3ea; --muted: #aeb6c6; --border: #2a2f3a;\n --accent: #f4c95d; --code-bg: #171a21;\n --serif: 'Iowan Old Style', Georgia, 'Times New Roman', serif;\n --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;\n --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n }\n * { box-sizing: border-box; }\n html, body { margin: 0; background: var(--bg); color: var(--fg); }\n body { font-family: var(--serif); font-size: 16px; line-height: 1.6;\n padding: 32px clamp(16px, 5vw, 56px); }\n main { max-width: 78ch; margin: 0 auto; }\n h1, h2, h3, h4 { font-family: var(--sans); line-height: 1.25; margin: 1.6em 0 0.5em; }\n h1 { font-size: 1.9em; } h2 { font-size: 1.45em; padding-bottom: 0.2em;\n border-bottom: 1px solid var(--border); } h3 { font-size: 1.2em; }\n p, li { color: var(--fg); }\n a { color: var(--accent); }\n code { font-family: var(--mono); font-size: 0.9em; background: var(--code-bg);\n padding: 0.12em 0.35em; border-radius: 5px; }\n pre { background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px;\n padding: 12px 14px; overflow: auto; }\n pre code { background: none; padding: 0; }\n blockquote { margin: 1em 0; padding: 0.2em 1em; border-left: 3px solid var(--accent);\n color: var(--muted); }\n table { border-collapse: collapse; width: 100%; margin: 1em 0; font-family: var(--sans);\n font-size: 0.95em; }\n th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }\n th { background: var(--code-bg); }\n hr { border: 0; border-top: 1px solid var(--border); margin: 1.5em 0; }\n ul, ol { padding-left: 1.4em; }`\n\nconst INTERACTIVE_STYLE = `\n .aod-group { margin: 1.4em 0; padding: 14px 16px; border: 1px solid var(--border);\n border-radius: 10px; background: var(--code-bg); }\n .aod-prompt { font-family: var(--sans); font-weight: 600; margin: 0 0 0.6em; }\n .aod-options { display: flex; flex-wrap: wrap; gap: 8px; }\n .aod-multi-select .aod-options { flex-direction: column; gap: 6px; }\n .aod-action, .aod-submit { font-family: var(--sans); font-size: 0.95em; font-weight: 600;\n cursor: pointer; border: 1px solid var(--border); border-radius: 8px;\n padding: 8px 12px; background: var(--bg); color: var(--fg); }\n .aod-action:hover, .aod-submit:hover { border-color: var(--accent); }\n .aod-submit { margin-top: 10px; background: var(--accent); color: #17130a; border-color: var(--accent); }\n .aod-check { display: flex; align-items: center; gap: 8px; font-family: var(--sans); cursor: pointer; }\n .aod-steps { list-style: decimal; }\n .aod-step { margin: 0.5em 0; }\n .aod-step-actions { display: inline-flex; gap: 6px; margin-left: 8px; }`\n\n/**\n * Wrap rendered inner HTML in the self-contained document shell. `extraStyle` is\n * appended to the inline stylesheet so the interactive variant can add its own\n * rules without a second `<style>` block. Static callers pass \"\" (default), so\n * the static document is byte-identical to the pre-refactor output.\n */\nfunction wrapArtifactDocument(body: string, title: string, extraStyle = \"\"): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${escapeHtml(title)}</title>\n<style>\n${BASE_STYLE}${extraStyle}\n</style>\n</head>\n<body>\n<main>\n${body}\n</main>\n</body>\n</html>\n`\n}\n\n/* -------------------------------------------------------------------------- */\n/* Interactive artifacts (contract v2.2 §4: declarative data-aod-* vocab) */\n/* -------------------------------------------------------------------------- */\n\nexport interface InteractiveOption {\n /** Stable id echoed back as `elementId` in the action event. Required. */\n elementId: string\n /** Human-visible label. */\n label: string\n /** Optional value echoed as `data-aod-value` (checkbox submits use \"true\"/\"false\"). */\n value?: string\n}\n\n/** One immediate-fire choice group: each option posts an action the instant it is clicked. */\nexport interface ChooseOneBlock {\n kind: \"choose-one\"\n /** `data-aod-group` shared by the options (marks them as one decision). */\n group: string\n prompt?: string\n /** `data-aod-action` verb for every option (default \"choose\"). */\n action?: string\n options: InteractiveOption[]\n}\n\n/** A multi-select group: checkbox toggles plus one submit that carries {group, selected}. */\nexport interface MultiSelectBlock {\n kind: \"multi-select\"\n group: string\n prompt?: string\n submitLabel?: string\n submitElementId?: string\n options: InteractiveOption[]\n}\n\nexport interface StepAction {\n /** `data-aod-action` verb (e.g. \"approve\", \"skip\"). */\n action: string\n label: string\n value?: string\n}\n\nexport interface StepItem {\n /** Stable `data-aod-id` echoed as `elementId`. */\n elementId: string\n label: string\n sourceLine?: number\n actions: StepAction[]\n}\n\n/** An ordered list of plan steps, each with its own inline action buttons. */\nexport interface StepsBlock {\n kind: \"steps\"\n steps: StepItem[]\n}\n\nexport type InteractiveBlock = ChooseOneBlock | MultiSelectBlock | StepsBlock\n\nexport interface InteractivePlanModel {\n /** Optional prose rendered above the interactive blocks (same escaping as static). */\n markdown?: string\n blocks: InteractiveBlock[]\n}\n\n/** Build a ` data-x=\"y\"` attribute string, dropping undefined values, all escaped. */\nfunction dataAttrs(pairs: Array<[string, string | undefined]>): string {\n let out = \"\"\n for (const [name, value] of pairs) {\n if (value === undefined) continue\n out += ` ${name}=\"${escapeHtml(value)}\"`\n }\n return out\n}\n\nfunction renderChooseOne(block: ChooseOneBlock): string {\n const action = block.action && block.action.trim() ? block.action : \"choose\"\n const prompt = block.prompt ? `<p class=\"aod-prompt\">${escapeHtml(block.prompt)}</p>` : \"\"\n const options = block.options\n .filter((o) => o.elementId.trim() !== \"\")\n .map((o) => {\n const attrs = dataAttrs([\n [\"data-aod-action\", action],\n [\"data-aod-group\", block.group],\n [\"data-aod-id\", o.elementId],\n [\"data-aod-value\", o.value],\n ])\n return `<button type=\"button\" class=\"aod-action\"${attrs}>${escapeHtml(o.label)}</button>`\n })\n .join(\"\\n\")\n return `<div class=\"aod-group aod-choose-one\"${dataAttrs([[\"data-aod-group\", block.group]])}>\n${prompt}<div class=\"aod-options\">\n${options}\n</div>\n</div>`\n}\n\nfunction renderMultiSelect(block: MultiSelectBlock): string {\n const prompt = block.prompt ? `<p class=\"aod-prompt\">${escapeHtml(block.prompt)}</p>` : \"\"\n const options = block.options\n .filter((o) => o.elementId.trim() !== \"\")\n .map((o) => {\n const attrs = dataAttrs([\n [\"data-aod-action\", \"check\"],\n [\"data-aod-group\", block.group],\n [\"data-aod-id\", o.elementId],\n [\"data-aod-value\", o.value],\n ])\n return `<label class=\"aod-check\"><input type=\"checkbox\"${attrs}> <span>${escapeHtml(o.label)}</span></label>`\n })\n .join(\"\\n\")\n const submitId = block.submitElementId && block.submitElementId.trim()\n ? block.submitElementId\n : `${block.group}-submit`\n const submitAttrs = dataAttrs([\n [\"data-aod-action\", \"submit\"],\n [\"data-aod-group\", block.group],\n [\"data-aod-id\", submitId],\n ])\n const submitLabel = block.submitLabel && block.submitLabel.trim() ? block.submitLabel : \"Submit\"\n return `<div class=\"aod-group aod-multi-select\"${dataAttrs([[\"data-aod-group\", block.group]])}>\n${prompt}<div class=\"aod-options\">\n${options}\n</div>\n<button type=\"button\" class=\"aod-submit\"${submitAttrs}>${escapeHtml(submitLabel)}</button>\n</div>`\n}\n\nfunction renderSteps(block: StepsBlock): string {\n const items = block.steps\n .filter((s) => s.elementId.trim() !== \"\")\n .map((s) => {\n const liAttrs = dataAttrs([\n [\"data-aod-id\", s.elementId],\n [\"data-source-line\", s.sourceLine === undefined ? undefined : String(s.sourceLine)],\n ])\n const actions = s.actions\n .map((a) => {\n const attrs = dataAttrs([\n [\"data-aod-action\", a.action],\n [\"data-aod-id\", s.elementId],\n [\"data-aod-value\", a.value],\n ])\n return `<button type=\"button\" class=\"aod-action\"${attrs}>${escapeHtml(a.label)}</button>`\n })\n .join(\"\")\n return `<li class=\"aod-step\"${liAttrs}><span class=\"aod-step-label\">${escapeHtml(s.label)}</span><span class=\"aod-step-actions\">${actions}</span></li>`\n })\n .join(\"\\n\")\n return `<ol class=\"aod-steps\">\n${items}\n</ol>`\n}\n\nfunction renderInteractiveBlock(block: InteractiveBlock): string {\n switch (block.kind) {\n case \"choose-one\":\n return renderChooseOne(block)\n case \"multi-select\":\n return renderMultiSelect(block)\n case \"steps\":\n return renderSteps(block)\n default:\n return \"\"\n }\n}\n\n/**\n * Render a typed interactive plan model into a self-contained HTML document that\n * carries the declarative `data-aod-*` action vocabulary the ai-or-die panel SDK\n * wires (contract v2.2 §4). Optional prose (`markdown`) is rendered with the SAME\n * escaping + dangerous-URL neutralization as the static path; all control\n * attributes and labels are HTML-escaped, so no author string can break out of an\n * attribute or inject live markup.\n */\nexport function renderInteractivePlanHtml(model: InteractivePlanModel, title = \"Plan\"): string {\n const prose = model.markdown && model.markdown.trim() ? renderMarkdownBody(model.markdown) : \"\"\n const blocks = model.blocks.map(renderInteractiveBlock).filter((h) => h.trim() !== \"\").join(\"\\n\")\n const body = [prose, blocks].filter((s) => s.trim() !== \"\").join(\"\\n\")\n return wrapArtifactDocument(body || `<pre>${escapeHtml(JSON.stringify(model))}</pre>`, title, INTERACTIVE_STYLE)\n}\n","/**\n * The internal `internal-artifact-open` subcommand: invoked by a spawned Claude\n * Code session's `PostToolUse` hook (matcher `ExitPlanMode`), registered into the\n * mirrored settings.json by the launcher when running inside an ai-or-die tab\n * (`AIORDIE_SESSION_ID` set). It auto-opens the just-finalized plan in the\n * ai-or-die Artifact review panel so the human reviews it WITHOUT the model\n * having to call artifact_open itself.\n *\n * Auth: AIORDIE_TOKEN is stripped from the child env, so the token can't come via\n * env or argv (argv leaks to `ps`). The launcher writes a mode-600\n * `.aiordie-artifact.json` into CLAUDE_CONFIG_DIR; this hook reads it.\n *\n * Plan: the EXACT plan for THIS session comes from the ExitPlanMode payload —\n * `tool_input.planFilePath` (per-session; never the globally-newest file in the\n * shared plans dir) with `tool_input.plan` as the markdown fallback. The plan\n * markdown is rendered to a self-contained styled HTML file (HTML is the\n * canonical artifact, lavish's model — formatted + annotatable, not raw md), and\n * THAT `.html` is opened.\n *\n * Side-effect only: never writes stdout, never throws, always exits 0. Skips\n * subagent payloads (agent_id/agent_type) like the other hooks.\n */\n\nimport { defineCommand } from \"citty\"\n\nimport { readFileSync, realpathSync, writeFileSync } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport path from \"node:path\"\n\nimport { ArtifactClient } from \"./lib/artifact/client\"\nimport { renderPlanHtml } from \"./lib/artifact/plan-html\"\nimport { isSubagentContext } from \"./lib/orchestration/stop-gate-policy\"\n\ninterface ArtifactCreds {\n baseUrl: string\n token: string\n sessionId: string\n insecureTLS: boolean\n}\n\ninterface ExitPlanPayload {\n planFilePath?: string\n planMarkdown?: string\n}\n\nfunction readStdin(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nfunction credsPath(): string {\n const dir = process.env.CLAUDE_CONFIG_DIR || path.join(homedir(), \".claude\")\n return path.join(dir, \".aiordie-artifact.json\")\n}\n\nfunction isSubagent(payload: unknown): boolean {\n try {\n return isSubagentContext(payload as Parameters<typeof isSubagentContext>[0])\n } catch {\n return false\n }\n}\n\n/** Pull the per-session plan path + markdown content out of the hook payload. */\nexport function parseExitPlanPayload(raw: string): ExitPlanPayload {\n try {\n const obj = JSON.parse(raw) as { tool_input?: { planFilePath?: unknown; plan?: unknown } }\n const ti = obj.tool_input ?? {}\n const planFilePath = typeof ti.planFilePath === \"string\" && ti.planFilePath.trim() ? ti.planFilePath : undefined\n const planMarkdown = typeof ti.plan === \"string\" && ti.plan.trim() ? ti.plan : undefined\n return { planFilePath, planMarkdown }\n } catch {\n return {}\n }\n}\n\nfunction readCreds(): ArtifactCreds | undefined {\n try {\n const parsed = JSON.parse(readFileSync(credsPath(), \"utf8\")) as Partial<ArtifactCreds>\n if (!parsed.baseUrl || !parsed.token || !parsed.sessionId) return undefined\n return {\n baseUrl: parsed.baseUrl,\n token: parsed.token,\n sessionId: parsed.sessionId,\n insecureTLS: parsed.insecureTLS === true,\n }\n } catch {\n return undefined\n }\n}\n\n/**\n * Resolve the plan markdown + a title, preferring the file at `planFilePath`\n * (the source of truth, may be newer than the payload's snapshot), falling back\n * to the inline `tool_input.plan` content.\n */\nfunction resolvePlanMarkdown(p: ExitPlanPayload): { markdown: string; title: string; nearPath?: string } | undefined {\n if (p.planFilePath) {\n try {\n const md = readFileSync(p.planFilePath, \"utf8\")\n if (md.trim()) return { markdown: md, title: path.basename(p.planFilePath, path.extname(p.planFilePath)), nearPath: p.planFilePath }\n } catch {\n /* fall through to inline content */\n }\n }\n if (p.planMarkdown) return { markdown: p.planMarkdown, title: \"Plan\" }\n return undefined\n}\n\n/**\n * Write the rendered HTML next to the plan (an allowed root) when we have a real\n * path, else into the plans dir under CLAUDE_CONFIG_DIR. Returns the realpath of\n * the written `.html`, or undefined on failure.\n */\nfunction writePlanHtml(html: string, nearPath: string | undefined): string | undefined {\n let target: string\n if (nearPath) {\n // `<plan>.md` -> `<plan>.aiordie.html` (or append when there is no .md ext).\n const base = nearPath.replace(/\\.(md|markdown)$/i, \"\")\n target = `${base}.aiordie.html`\n } else {\n const dir = path.join(process.env.CLAUDE_CONFIG_DIR || path.join(homedir(), \".claude\"), \"plans\")\n target = path.join(dir, `aiordie-plan-${process.pid}.html`)\n }\n try {\n writeFileSync(target, html, \"utf8\")\n try {\n return realpathSync.native(target)\n } catch {\n return target\n }\n } catch {\n return undefined\n }\n}\n\nexport const internalArtifactOpen = defineCommand({\n meta: { name: \"internal-artifact-open\", description: \"Open the finalized plan in the ai-or-die panel\" },\n async run() {\n try {\n const raw = readStdin()\n let payload: unknown\n try {\n payload = JSON.parse(raw)\n } catch {\n payload = undefined\n }\n if (isSubagent(payload)) return\n const creds = readCreds()\n if (!creds) return\n const plan = resolvePlanMarkdown(parseExitPlanPayload(raw))\n if (!plan) return\n const htmlPath = writePlanHtml(renderPlanHtml(plan.markdown, plan.title), plan.nearPath)\n if (!htmlPath) return\n const client = new ArtifactClient(creds)\n await client.open(htmlPath)\n } catch {\n /* side-effect only: never block, never throw */\n }\n },\n})\n"],"mappings":"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"}
|
package/dist/{internal-fast-dispatch-guard-CAwYj_Yz.js → internal-fast-dispatch-guard-CZ4BZuAG.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { l as FAST_PROFILE_NATIVE_AGENT_NAMES, s as FAST_PROFILE_DELEGATION_GRAPH } from "./fast-profile-contract-_DhIWank.js";
|
|
2
|
-
import { t as buildSelfCommand } from "./self-invocation-
|
|
2
|
+
import { t as buildSelfCommand } from "./self-invocation-DahN1gw9.js";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
//#region src/lib/fast-dispatch-acl.ts
|
|
@@ -244,4 +244,4 @@ function buildFastDispatchGuardHookCommand(invocation, opts) {
|
|
|
244
244
|
//#endregion
|
|
245
245
|
export { internalFastDispatchGuard as n, FAST_DISPATCH_TOOL_MATCHER as r, buildFastDispatchGuardHookCommand as t };
|
|
246
246
|
|
|
247
|
-
//# sourceMappingURL=internal-fast-dispatch-guard-
|
|
247
|
+
//# sourceMappingURL=internal-fast-dispatch-guard-CZ4BZuAG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-fast-dispatch-guard-CAwYj_Yz.js","names":[],"sources":["../src/lib/fast-dispatch-acl.ts","../src/internal-fast-dispatch-guard.ts"],"sourcesContent":["/**\n * Pure policy for the fast profile's native Task/Agent delegation graph.\n *\n * This policy is compiled into the hook rather than read from per-launch files,\n * so cleanup or a partial runtime-file write cannot silently disarm the guard.\n * It is an in-session Task/Agent ACL, not a shell sandbox: role tool lists\n * narrow the available tools while this graph narrows native dispatch edges.\n */\n\nimport {\n FAST_PROFILE_DELEGATION_GRAPH,\n FAST_PROFILE_NATIVE_AGENT_NAMES,\n type FastProfileNativeAgentName,\n} from \"./fast-profile-contract\"\n\nexport const FAST_BROWSE_DISPATCH_AGENT = \"worker-browse\" as const\n\nexport const FAST_NATIVE_AGENT_NAMES = FAST_PROFILE_NATIVE_AGENT_NAMES\n\nexport type FastNativeAgentName = FastProfileNativeAgentName\n\nexport type FastDispatchTargetName = FastNativeAgentName | typeof FAST_BROWSE_DISPATCH_AGENT\nexport type FastDispatchCallerName = FastNativeAgentName | typeof FAST_BROWSE_DISPATCH_AGENT\n\nexport const FAST_NATIVE_AGENT_SET = new Set<string>(FAST_NATIVE_AGENT_NAMES)\n\n/** Frozen authority graph: each key is a caller and each value its targets. */\nexport const FAST_DISPATCH_GRAPH: Readonly<\n Record<FastDispatchCallerName, ReadonlySet<FastDispatchTargetName>>\n> = Object.freeze({\n Explore: new Set<FastDispatchTargetName>(FAST_PROFILE_DELEGATION_GRAPH.Explore),\n Plan: new Set<FastDispatchTargetName>(FAST_PROFILE_DELEGATION_GRAPH.Plan),\n \"general-purpose\": new Set<FastDispatchTargetName>(\n FAST_PROFILE_DELEGATION_GRAPH[\"general-purpose\"],\n ),\n implementer: new Set<FastDispatchTargetName>(\n FAST_PROFILE_DELEGATION_GRAPH.implementer,\n ),\n reviewer: new Set<FastDispatchTargetName>(FAST_PROFILE_DELEGATION_GRAPH.reviewer),\n \"worker-browse\": new Set<FastDispatchTargetName>([]),\n})\n\n/** The hook matcher is intentionally limited to native dispatch tool names. */\nexport const FAST_DISPATCH_TOOL_MATCHER = \"^(Task|Agent)$\"\n\ninterface DispatchPayload {\n tool_name?: unknown\n tool_input?: unknown\n agent_type?: unknown\n agent_id?: unknown\n parent_tool_use_id?: unknown\n agentType?: unknown\n agentId?: unknown\n parentToolUseId?: unknown\n}\n\nexport interface FastDispatchGuardOptions {\n /** Optional restriction on allowed target subagents (e.g. from launch profile or hook CLI). */\n allowedTargets?: ReadonlySet<string> | ReadonlyArray<string>\n /** Whether worker-browse is permitted as a target for the lead. */\n allowBrowse?: boolean\n}\n\nexport interface FastDispatchGuardResult {\n allowed: boolean\n reason?: string\n caller?: FastDispatchCallerName | \"lead\"\n target?: FastDispatchTargetName\n /** Cloned Agent/Task input with a caller model override removed. */\n updatedInput?: Record<string, unknown>\n verdict: \"allow-non-dispatch\" | \"allow\" | \"deny\"\n}\n\nfunction hasOwn(obj: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key)\n}\n\nfunction valuesForAliases(obj: Record<string, unknown>, names: ReadonlyArray<string>): Array<unknown> {\n return names.filter((name) => hasOwn(obj, name)).map((name) => obj[name])\n}\n\n/**\n * Resolve an aliased string field. Null and undefined are marker values. One\n * concrete value is accepted, while different concrete aliases are a conflict.\n */\nfunction resolveAliasedString(\n obj: Record<string, unknown>,\n names: ReadonlyArray<string>,\n): { present: boolean; value?: string; malformed: boolean; conflict: boolean } {\n const values = valuesForAliases(obj, names)\n if (values.length === 0) return { present: false, malformed: false, conflict: false }\n const concrete = values.filter((value) => value !== undefined && value !== null)\n if (concrete.some((value) => typeof value !== \"string\" || value.trim().length === 0)) {\n return { present: true, malformed: true, conflict: false }\n }\n const distinct = [...new Set((concrete as Array<string>).map((value) => value.trim()))]\n if (distinct.length > 1) return { present: true, malformed: false, conflict: true }\n return { present: true, value: distinct[0], malformed: false, conflict: false }\n}\n\nfunction deny(\n reason: string,\n target?: FastDispatchTargetName,\n caller?: FastDispatchCallerName | \"lead\",\n): FastDispatchGuardResult {\n return { allowed: false, reason, target, caller, verdict: \"deny\" }\n}\n\n/**\n * Continuation / reattachment target selectors that must be rejected on Fast dispatch.\n * Any non-null value in tool_input fails closed to prevent bypassing role edges.\n */\nconst CONTINUATION_FIELDS: ReadonlyArray<string> = [\n \"resume\",\n \"resume_session\",\n \"resumeSession\",\n \"resume_agent_id\",\n \"resumeAgentId\",\n \"continuation\",\n \"continue\",\n \"continue_session\",\n \"continueSession\",\n \"agent_id\",\n \"agentId\",\n \"session_id\",\n \"sessionId\",\n \"thread_id\",\n \"threadId\",\n \"task_id\",\n \"taskId\",\n \"target_agent_id\",\n \"targetAgentId\",\n \"reattach\",\n \"reattach_session\",\n \"reattachSession\",\n]\n\n/**\n * Evaluate one raw Claude Code PreToolUse payload.\n *\n * Classification happens before strict parsing. A malformed or unrelated\n * ordinary-tool hook therefore passes through, while a recognized Task/Agent\n * dispatch fails closed on malformed input or identity.\n */\nexport function decideFastDispatchGuard(\n stdin: string | unknown,\n opts?: FastDispatchGuardOptions,\n): FastDispatchGuardResult {\n let parsed: unknown\n try {\n parsed = typeof stdin === \"string\" ? JSON.parse(stdin) : stdin\n } catch {\n return deny(\"fast dispatch denied: malformed hook payload\")\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return deny(\"fast dispatch denied: hook payload must be an object\")\n }\n\n const payload = parsed as DispatchPayload\n if (typeof payload.tool_name !== \"string\") {\n return deny(\"fast dispatch denied: hook payload has no tool_name\")\n }\n const toolName = payload.tool_name\n if (!/^(Task|Agent)$/.test(toolName)) {\n return { allowed: true, verdict: \"allow-non-dispatch\" }\n }\n\n if (!payload.tool_input || typeof payload.tool_input !== \"object\" || Array.isArray(payload.tool_input)) {\n return deny(`fast dispatch denied: ${toolName} payload has malformed tool_input`)\n }\n const toolInput = payload.tool_input as Record<string, unknown>\n\n // Reject non-null continuation / reattachment / agent-id target selectors that could bypass role edges\n for (const field of CONTINUATION_FIELDS) {\n if (hasOwn(toolInput, field)) {\n const val = toolInput[field]\n if (val !== undefined && val !== null && val !== false) {\n return deny(`fast dispatch denied: ${toolName} continuation/reattachment (${field}) is not permitted in fast profile`)\n }\n }\n }\n\n const targetResult = resolveAliasedString(\n toolInput,\n [\"subagent_type\", \"subagentType\"],\n )\n if (targetResult.malformed) {\n return deny(`fast dispatch denied: ${toolName} target identity is malformed`)\n }\n if (targetResult.conflict) {\n return deny(`fast dispatch denied: ${toolName} target aliases conflict`)\n }\n const targetValue = targetResult.value\n\n const effectiveAllowedTargets = opts?.allowedTargets\n ? new Set(opts.allowedTargets)\n : new Set<string>([\n ...FAST_NATIVE_AGENT_NAMES,\n ...(opts?.allowBrowse ? [FAST_BROWSE_DISPATCH_AGENT] : []),\n ])\n\n if (!targetValue || !effectiveAllowedTargets.has(targetValue)) {\n return deny(\n `fast dispatch denied: ${toolName} target must be one of ${[...effectiveAllowedTargets].join(\", \")}`,\n )\n }\n const target = targetValue as FastDispatchTargetName\n\n // Invocation-level `model` outranks custom-agent frontmatter in Claude Code.\n // The fast profile promises fixed role models, so allowed dispatches must\n // remove that one caller-owned field while preserving every other input.\n const updatedInput = { ...toolInput }\n delete updatedInput.model\n\n const callerType = resolveAliasedString(payload as Record<string, unknown>, [\"agent_type\", \"agentType\"])\n const callerId = resolveAliasedString(payload as Record<string, unknown>, [\"agent_id\", \"agentId\"])\n const parentMarker = resolveAliasedString(payload as Record<string, unknown>, [\n \"parent_tool_use_id\",\n \"parentToolUseId\",\n ])\n if (callerType.malformed || callerId.malformed || parentMarker.malformed) {\n return deny(`fast dispatch denied: ${toolName} caller identity is malformed`, target)\n }\n if (callerType.conflict || callerId.conflict || parentMarker.conflict) {\n return deny(`fast dispatch denied: ${toolName} caller identity aliases conflict`, target)\n }\n\n const callerTypeValue = callerType.value\n const callerIdValue = callerId.value\n const parentValue = parentMarker.value\n\n // A non-null agent id or parent marker cannot impersonate a role by itself.\n // A valid role may be accompanied by either marker as supplemental evidence.\n if (!callerTypeValue && callerIdValue) {\n return deny(`fast dispatch denied: ${toolName} has agent_id without agent_type`, target)\n }\n if (!callerTypeValue && parentValue) {\n return deny(`fast dispatch denied: ${toolName} has only a parent execution marker`, target)\n }\n\n // Absent or null identity fields are the only lead representation. Empty\n // strings and non-string values were rejected above; unknown roles fail closed.\n if (!callerTypeValue) {\n const hasPresentMarker = callerType.present || callerId.present || parentMarker.present\n const hasUnresolvedNonNullMarker = [callerType, callerId, parentMarker].some(\n (marker) => marker.value !== undefined,\n )\n if (hasPresentMarker && hasUnresolvedNonNullMarker) {\n return deny(`fast dispatch denied: ${toolName} has no resolvable caller role`, target)\n }\n return {\n allowed: true,\n caller: \"lead\",\n target,\n updatedInput,\n verdict: \"allow\",\n }\n }\n\n const validCallers = new Set<string>([\n ...FAST_NATIVE_AGENT_NAMES,\n FAST_BROWSE_DISPATCH_AGENT,\n ])\n\n if (!validCallers.has(callerTypeValue)) {\n return deny(`fast dispatch denied: unknown caller role ${JSON.stringify(callerTypeValue)}`, target)\n }\n const caller = callerTypeValue as FastDispatchCallerName\n const allowedTargets = FAST_DISPATCH_GRAPH[caller] ?? new Set<FastDispatchTargetName>()\n if (!allowedTargets.has(target)) {\n return deny(`fast dispatch denied: ${caller} cannot invoke ${target}`, target, caller)\n }\n return { allowed: true, caller, target, updatedInput, verdict: \"allow\" }\n}\n\n/** JSON response consumed by Claude Code for an allowed, model-pinned dispatch. */\nexport function fastDispatchAllowOutput(\n updatedInput: Record<string, unknown>,\n): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"allow\",\n updatedInput,\n },\n })\n}\n\n/** JSON response consumed by Claude Code's PreToolUse hook protocol. */\nexport function fastDispatchDenyOutput(reason: string): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: reason,\n },\n })\n}\n","/**\n * Internal PreToolUse guard for the `-m fast` native Task/Agent ACL.\n *\n * The policy is intentionally pure and compiled into this hook. This entrypoint\n * only performs synchronous stdin I/O, emits Claude Code's deny envelope, and\n * exits naturally so Windows libuv teardown is not raced by process.exit().\n */\n\nimport { defineCommand } from \"citty\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n decideFastDispatchGuard,\n fastDispatchAllowOutput,\n fastDispatchDenyOutput,\n} from \"./lib/fast-dispatch-acl\"\nimport { buildSelfCommand, type SelfInvocation } from \"./lib/hook-launcher/self-invocation\"\n\nfunction readStdinSync(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nexport const internalFastDispatchGuard = defineCommand({\n meta: {\n name: \"internal-fast-dispatch-guard\",\n description:\n \"Internal: PreToolUse guard enforcing the fast-profile native Task/Agent ACL.\",\n },\n args: {\n allowBrowse: {\n type: \"boolean\",\n description: \"Whether worker-browse is allowed as a target.\",\n default: false,\n },\n allowedTargets: {\n type: \"string\",\n description: \"Comma-separated list of allowed target subagents.\",\n },\n },\n run({ args }) {\n const allowedTargets = args.allowedTargets\n ? args.allowedTargets.split(\",\").map((s) => s.trim()).filter(Boolean)\n : undefined\n const allowBrowse = args.allowBrowse === true || (allowedTargets ? allowedTargets.includes(\"worker-browse\") : false)\n const decision = decideFastDispatchGuard(readStdinSync(), {\n allowedTargets,\n allowBrowse,\n })\n if (!decision.allowed && decision.reason) {\n process.stdout.write(fastDispatchDenyOutput(decision.reason))\n } else if (decision.verdict === \"allow\" && decision.updatedInput) {\n process.stdout.write(fastDispatchAllowOutput(decision.updatedInput))\n }\n // Natural exit is required on Windows. Claude Code consumes the JSON\n // permission decision; an exit code is not used to deny this hook.\n process.exitCode = 0\n },\n})\n\n/** Build the persisted command for the fast-profile PreToolUse hook. */\nexport function buildFastDispatchGuardHookCommand(\n invocation: SelfInvocation,\n opts?: { allowBrowse?: boolean; allowedTargets?: ReadonlyArray<string> },\n): string {\n const flags: string[] = []\n if (opts?.allowBrowse) flags.push(\"--allowBrowse\")\n if (opts?.allowedTargets && opts.allowedTargets.length > 0) {\n flags.push(`--allowedTargets \"${opts.allowedTargets.join(\",\")}\"`)\n }\n const args = [\"internal-fast-dispatch-guard\", ...flags].join(\" \")\n return buildSelfCommand(invocation, args)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"internal-fast-dispatch-guard-CZ4BZuAG.js","names":[],"sources":["../src/lib/fast-dispatch-acl.ts","../src/internal-fast-dispatch-guard.ts"],"sourcesContent":["/**\n * Pure policy for the fast profile's native Task/Agent delegation graph.\n *\n * This policy is compiled into the hook rather than read from per-launch files,\n * so cleanup or a partial runtime-file write cannot silently disarm the guard.\n * It is an in-session Task/Agent ACL, not a shell sandbox: role tool lists\n * narrow the available tools while this graph narrows native dispatch edges.\n */\n\nimport {\n FAST_PROFILE_DELEGATION_GRAPH,\n FAST_PROFILE_NATIVE_AGENT_NAMES,\n type FastProfileNativeAgentName,\n} from \"./fast-profile-contract\"\n\nexport const FAST_BROWSE_DISPATCH_AGENT = \"worker-browse\" as const\n\nexport const FAST_NATIVE_AGENT_NAMES = FAST_PROFILE_NATIVE_AGENT_NAMES\n\nexport type FastNativeAgentName = FastProfileNativeAgentName\n\nexport type FastDispatchTargetName = FastNativeAgentName | typeof FAST_BROWSE_DISPATCH_AGENT\nexport type FastDispatchCallerName = FastNativeAgentName | typeof FAST_BROWSE_DISPATCH_AGENT\n\nexport const FAST_NATIVE_AGENT_SET = new Set<string>(FAST_NATIVE_AGENT_NAMES)\n\n/** Frozen authority graph: each key is a caller and each value its targets. */\nexport const FAST_DISPATCH_GRAPH: Readonly<\n Record<FastDispatchCallerName, ReadonlySet<FastDispatchTargetName>>\n> = Object.freeze({\n Explore: new Set<FastDispatchTargetName>(FAST_PROFILE_DELEGATION_GRAPH.Explore),\n Plan: new Set<FastDispatchTargetName>(FAST_PROFILE_DELEGATION_GRAPH.Plan),\n \"general-purpose\": new Set<FastDispatchTargetName>(\n FAST_PROFILE_DELEGATION_GRAPH[\"general-purpose\"],\n ),\n implementer: new Set<FastDispatchTargetName>(\n FAST_PROFILE_DELEGATION_GRAPH.implementer,\n ),\n reviewer: new Set<FastDispatchTargetName>(FAST_PROFILE_DELEGATION_GRAPH.reviewer),\n \"worker-browse\": new Set<FastDispatchTargetName>([]),\n})\n\n/** The hook matcher is intentionally limited to native dispatch tool names. */\nexport const FAST_DISPATCH_TOOL_MATCHER = \"^(Task|Agent)$\"\n\ninterface DispatchPayload {\n tool_name?: unknown\n tool_input?: unknown\n agent_type?: unknown\n agent_id?: unknown\n parent_tool_use_id?: unknown\n agentType?: unknown\n agentId?: unknown\n parentToolUseId?: unknown\n}\n\nexport interface FastDispatchGuardOptions {\n /** Optional restriction on allowed target subagents (e.g. from launch profile or hook CLI). */\n allowedTargets?: ReadonlySet<string> | ReadonlyArray<string>\n /** Whether worker-browse is permitted as a target for the lead. */\n allowBrowse?: boolean\n}\n\nexport interface FastDispatchGuardResult {\n allowed: boolean\n reason?: string\n caller?: FastDispatchCallerName | \"lead\"\n target?: FastDispatchTargetName\n /** Cloned Agent/Task input with a caller model override removed. */\n updatedInput?: Record<string, unknown>\n verdict: \"allow-non-dispatch\" | \"allow\" | \"deny\"\n}\n\nfunction hasOwn(obj: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key)\n}\n\nfunction valuesForAliases(obj: Record<string, unknown>, names: ReadonlyArray<string>): Array<unknown> {\n return names.filter((name) => hasOwn(obj, name)).map((name) => obj[name])\n}\n\n/**\n * Resolve an aliased string field. Null and undefined are marker values. One\n * concrete value is accepted, while different concrete aliases are a conflict.\n */\nfunction resolveAliasedString(\n obj: Record<string, unknown>,\n names: ReadonlyArray<string>,\n): { present: boolean; value?: string; malformed: boolean; conflict: boolean } {\n const values = valuesForAliases(obj, names)\n if (values.length === 0) return { present: false, malformed: false, conflict: false }\n const concrete = values.filter((value) => value !== undefined && value !== null)\n if (concrete.some((value) => typeof value !== \"string\" || value.trim().length === 0)) {\n return { present: true, malformed: true, conflict: false }\n }\n const distinct = [...new Set((concrete as Array<string>).map((value) => value.trim()))]\n if (distinct.length > 1) return { present: true, malformed: false, conflict: true }\n return { present: true, value: distinct[0], malformed: false, conflict: false }\n}\n\nfunction deny(\n reason: string,\n target?: FastDispatchTargetName,\n caller?: FastDispatchCallerName | \"lead\",\n): FastDispatchGuardResult {\n return { allowed: false, reason, target, caller, verdict: \"deny\" }\n}\n\n/**\n * Continuation / reattachment target selectors that must be rejected on Fast dispatch.\n * Any non-null value in tool_input fails closed to prevent bypassing role edges.\n */\nconst CONTINUATION_FIELDS: ReadonlyArray<string> = [\n \"resume\",\n \"resume_session\",\n \"resumeSession\",\n \"resume_agent_id\",\n \"resumeAgentId\",\n \"continuation\",\n \"continue\",\n \"continue_session\",\n \"continueSession\",\n \"agent_id\",\n \"agentId\",\n \"session_id\",\n \"sessionId\",\n \"thread_id\",\n \"threadId\",\n \"task_id\",\n \"taskId\",\n \"target_agent_id\",\n \"targetAgentId\",\n \"reattach\",\n \"reattach_session\",\n \"reattachSession\",\n]\n\n/**\n * Evaluate one raw Claude Code PreToolUse payload.\n *\n * Classification happens before strict parsing. A malformed or unrelated\n * ordinary-tool hook therefore passes through, while a recognized Task/Agent\n * dispatch fails closed on malformed input or identity.\n */\nexport function decideFastDispatchGuard(\n stdin: string | unknown,\n opts?: FastDispatchGuardOptions,\n): FastDispatchGuardResult {\n let parsed: unknown\n try {\n parsed = typeof stdin === \"string\" ? JSON.parse(stdin) : stdin\n } catch {\n return deny(\"fast dispatch denied: malformed hook payload\")\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return deny(\"fast dispatch denied: hook payload must be an object\")\n }\n\n const payload = parsed as DispatchPayload\n if (typeof payload.tool_name !== \"string\") {\n return deny(\"fast dispatch denied: hook payload has no tool_name\")\n }\n const toolName = payload.tool_name\n if (!/^(Task|Agent)$/.test(toolName)) {\n return { allowed: true, verdict: \"allow-non-dispatch\" }\n }\n\n if (!payload.tool_input || typeof payload.tool_input !== \"object\" || Array.isArray(payload.tool_input)) {\n return deny(`fast dispatch denied: ${toolName} payload has malformed tool_input`)\n }\n const toolInput = payload.tool_input as Record<string, unknown>\n\n // Reject non-null continuation / reattachment / agent-id target selectors that could bypass role edges\n for (const field of CONTINUATION_FIELDS) {\n if (hasOwn(toolInput, field)) {\n const val = toolInput[field]\n if (val !== undefined && val !== null && val !== false) {\n return deny(`fast dispatch denied: ${toolName} continuation/reattachment (${field}) is not permitted in fast profile`)\n }\n }\n }\n\n const targetResult = resolveAliasedString(\n toolInput,\n [\"subagent_type\", \"subagentType\"],\n )\n if (targetResult.malformed) {\n return deny(`fast dispatch denied: ${toolName} target identity is malformed`)\n }\n if (targetResult.conflict) {\n return deny(`fast dispatch denied: ${toolName} target aliases conflict`)\n }\n const targetValue = targetResult.value\n\n const effectiveAllowedTargets = opts?.allowedTargets\n ? new Set(opts.allowedTargets)\n : new Set<string>([\n ...FAST_NATIVE_AGENT_NAMES,\n ...(opts?.allowBrowse ? [FAST_BROWSE_DISPATCH_AGENT] : []),\n ])\n\n if (!targetValue || !effectiveAllowedTargets.has(targetValue)) {\n return deny(\n `fast dispatch denied: ${toolName} target must be one of ${[...effectiveAllowedTargets].join(\", \")}`,\n )\n }\n const target = targetValue as FastDispatchTargetName\n\n // Invocation-level `model` outranks custom-agent frontmatter in Claude Code.\n // The fast profile promises fixed role models, so allowed dispatches must\n // remove that one caller-owned field while preserving every other input.\n const updatedInput = { ...toolInput }\n delete updatedInput.model\n\n const callerType = resolveAliasedString(payload as Record<string, unknown>, [\"agent_type\", \"agentType\"])\n const callerId = resolveAliasedString(payload as Record<string, unknown>, [\"agent_id\", \"agentId\"])\n const parentMarker = resolveAliasedString(payload as Record<string, unknown>, [\n \"parent_tool_use_id\",\n \"parentToolUseId\",\n ])\n if (callerType.malformed || callerId.malformed || parentMarker.malformed) {\n return deny(`fast dispatch denied: ${toolName} caller identity is malformed`, target)\n }\n if (callerType.conflict || callerId.conflict || parentMarker.conflict) {\n return deny(`fast dispatch denied: ${toolName} caller identity aliases conflict`, target)\n }\n\n const callerTypeValue = callerType.value\n const callerIdValue = callerId.value\n const parentValue = parentMarker.value\n\n // A non-null agent id or parent marker cannot impersonate a role by itself.\n // A valid role may be accompanied by either marker as supplemental evidence.\n if (!callerTypeValue && callerIdValue) {\n return deny(`fast dispatch denied: ${toolName} has agent_id without agent_type`, target)\n }\n if (!callerTypeValue && parentValue) {\n return deny(`fast dispatch denied: ${toolName} has only a parent execution marker`, target)\n }\n\n // Absent or null identity fields are the only lead representation. Empty\n // strings and non-string values were rejected above; unknown roles fail closed.\n if (!callerTypeValue) {\n const hasPresentMarker = callerType.present || callerId.present || parentMarker.present\n const hasUnresolvedNonNullMarker = [callerType, callerId, parentMarker].some(\n (marker) => marker.value !== undefined,\n )\n if (hasPresentMarker && hasUnresolvedNonNullMarker) {\n return deny(`fast dispatch denied: ${toolName} has no resolvable caller role`, target)\n }\n return {\n allowed: true,\n caller: \"lead\",\n target,\n updatedInput,\n verdict: \"allow\",\n }\n }\n\n const validCallers = new Set<string>([\n ...FAST_NATIVE_AGENT_NAMES,\n FAST_BROWSE_DISPATCH_AGENT,\n ])\n\n if (!validCallers.has(callerTypeValue)) {\n return deny(`fast dispatch denied: unknown caller role ${JSON.stringify(callerTypeValue)}`, target)\n }\n const caller = callerTypeValue as FastDispatchCallerName\n const allowedTargets = FAST_DISPATCH_GRAPH[caller] ?? new Set<FastDispatchTargetName>()\n if (!allowedTargets.has(target)) {\n return deny(`fast dispatch denied: ${caller} cannot invoke ${target}`, target, caller)\n }\n return { allowed: true, caller, target, updatedInput, verdict: \"allow\" }\n}\n\n/** JSON response consumed by Claude Code for an allowed, model-pinned dispatch. */\nexport function fastDispatchAllowOutput(\n updatedInput: Record<string, unknown>,\n): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"allow\",\n updatedInput,\n },\n })\n}\n\n/** JSON response consumed by Claude Code's PreToolUse hook protocol. */\nexport function fastDispatchDenyOutput(reason: string): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: reason,\n },\n })\n}\n","/**\n * Internal PreToolUse guard for the `-m fast` native Task/Agent ACL.\n *\n * The policy is intentionally pure and compiled into this hook. This entrypoint\n * only performs synchronous stdin I/O, emits Claude Code's deny envelope, and\n * exits naturally so Windows libuv teardown is not raced by process.exit().\n */\n\nimport { defineCommand } from \"citty\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n decideFastDispatchGuard,\n fastDispatchAllowOutput,\n fastDispatchDenyOutput,\n} from \"./lib/fast-dispatch-acl\"\nimport { buildSelfCommand, type SelfInvocation } from \"./lib/hook-launcher/self-invocation\"\n\nfunction readStdinSync(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nexport const internalFastDispatchGuard = defineCommand({\n meta: {\n name: \"internal-fast-dispatch-guard\",\n description:\n \"Internal: PreToolUse guard enforcing the fast-profile native Task/Agent ACL.\",\n },\n args: {\n allowBrowse: {\n type: \"boolean\",\n description: \"Whether worker-browse is allowed as a target.\",\n default: false,\n },\n allowedTargets: {\n type: \"string\",\n description: \"Comma-separated list of allowed target subagents.\",\n },\n },\n run({ args }) {\n const allowedTargets = args.allowedTargets\n ? args.allowedTargets.split(\",\").map((s) => s.trim()).filter(Boolean)\n : undefined\n const allowBrowse = args.allowBrowse === true || (allowedTargets ? allowedTargets.includes(\"worker-browse\") : false)\n const decision = decideFastDispatchGuard(readStdinSync(), {\n allowedTargets,\n allowBrowse,\n })\n if (!decision.allowed && decision.reason) {\n process.stdout.write(fastDispatchDenyOutput(decision.reason))\n } else if (decision.verdict === \"allow\" && decision.updatedInput) {\n process.stdout.write(fastDispatchAllowOutput(decision.updatedInput))\n }\n // Natural exit is required on Windows. Claude Code consumes the JSON\n // permission decision; an exit code is not used to deny this hook.\n process.exitCode = 0\n },\n})\n\n/** Build the persisted command for the fast-profile PreToolUse hook. */\nexport function buildFastDispatchGuardHookCommand(\n invocation: SelfInvocation,\n opts?: { allowBrowse?: boolean; allowedTargets?: ReadonlyArray<string> },\n): string {\n const flags: string[] = []\n if (opts?.allowBrowse) flags.push(\"--allowBrowse\")\n if (opts?.allowedTargets && opts.allowedTargets.length > 0) {\n flags.push(`--allowedTargets \"${opts.allowedTargets.join(\",\")}\"`)\n }\n const args = [\"internal-fast-dispatch-guard\", ...flags].join(\" \")\n return buildSelfCommand(invocation, args)\n}\n"],"mappings":"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"}
|
package/dist/{internal-fast-dispatch-guard-2QjUlhqO.js → internal-fast-dispatch-guard-D5kozMpo.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as internalFastDispatchGuard } from "./internal-fast-dispatch-guard-
|
|
1
|
+
import { n as internalFastDispatchGuard } from "./internal-fast-dispatch-guard-CZ4BZuAG.js";
|
|
2
2
|
export { internalFastDispatchGuard };
|
package/dist/{internal-first-mate-guard-VgnHBp0s.js → internal-first-mate-guard-B_or0eN3.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { r as internalFirstMateGuard } from "./internal-first-mate-guard-
|
|
1
|
+
import { r as internalFirstMateGuard } from "./internal-first-mate-guard-CiFH6X1H.js";
|
|
2
2
|
export { internalFirstMateGuard };
|
package/dist/{internal-first-mate-guard-B0rbLb0L.js → internal-first-mate-guard-CiFH6X1H.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as buildSelfCommand } from "./self-invocation-
|
|
2
|
-
import { a as dispatcherAgentName, t as ALL_WORKER_DISPATCH_MODES } from "./worker-dispatch-
|
|
1
|
+
import { t as buildSelfCommand } from "./self-invocation-DahN1gw9.js";
|
|
2
|
+
import { a as dispatcherAgentName, t as ALL_WORKER_DISPATCH_MODES } from "./worker-dispatch-B-OA7tvU.js";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
//#region src/lib/first-mate/operator-shaping.ts
|
|
@@ -124,4 +124,4 @@ function buildFirstMateGuardHookCommand(invocation) {
|
|
|
124
124
|
//#endregion
|
|
125
125
|
export { assertShapingInstalled as i, buildFirstMateGuardHookCommand as n, internalFirstMateGuard as r, FIRST_MATE_GUARD_MATCHER as t };
|
|
126
126
|
|
|
127
|
-
//# sourceMappingURL=internal-first-mate-guard-
|
|
127
|
+
//# sourceMappingURL=internal-first-mate-guard-CiFH6X1H.js.map
|
package/dist/{internal-first-mate-guard-B0rbLb0L.js.map → internal-first-mate-guard-CiFH6X1H.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-first-mate-guard-
|
|
1
|
+
{"version":3,"file":"internal-first-mate-guard-CiFH6X1H.js","names":[],"sources":["../src/lib/first-mate/operator-shaping.ts","../src/internal-first-mate-guard.ts"],"sourcesContent":["/**\n * Operator-mode steering for github-router `--agents` sessions.\n *\n * In first-mate/operator mode the spawned Claude is the cloud-agent operator, not\n * the product implementer. Product implementation is steered to GitHub cloud\n * agents by first-mate guidance and the gh-first-mate skill. This module no\n * longer hard-blocks local file writes or shell commands: Edit/Write/NotebookEdit\n * and Bash remain available as escape hatches when needed.\n *\n * The only enforced main-operator boundary is that local worker/orchestrate MCP\n * tools stay subagent-only. The main operator should invoke the worker-* Agent\n * subagents or delegate to GitHub cloud agents rather than calling\n * `mcp__workers__*` / `mcp__orchestrate__*` directly.\n */\n\nimport {\n ALL_WORKER_DISPATCH_MODES,\n dispatcherAgentName,\n type WorkerDispatchMode,\n} from \"../worker-dispatch\"\n\n/** Tools denied to the main operator by exact name. */\nexport const OPERATOR_DENIED_TOOLS = [] as const\n\n/** MCP prefixes denied to the main operator; these remain subagent-only. */\nexport const OPERATOR_DENIED_MCP_PREFIXES = [\"mcp__workers__\", \"mcp__orchestrate__\"] as const\n\n/** Tools explicitly preserved (documented; used by assertion tests). */\nexport const OPERATOR_KEPT_TOOLS = [\n \"Agent\",\n \"Task\",\n \"Edit\",\n \"Write\",\n \"NotebookEdit\",\n \"Bash\",\n \"Read\",\n \"Grep\",\n \"Glob\",\n \"mcp__first-mate__\",\n] as const\n\n/**\n * Least-privilege note for what the cloud agents themselves may touch — the\n * operator scopes missions to specific repos and forbids irreversible actions\n * without human approval (see first-mate house rules). This constant documents\n * the intent for the launcher/README; enforcement of the cloud-agent scope is\n * the GitHub agent-token scope (repo/workflow) + the mission house_rules.\n */\nexport const CLOUD_AGENT_SCOPE_NOTE =\n \"Cloud agents operate only within mission-listed repos; merges, releases, \" +\n \"Pages, and deletes require explicit human approval (first-mate merge gate).\"\n\nexport interface OperatorToolInput extends Record<string, unknown> {\n agent_type?: unknown\n}\n\nexport interface PreToolUseDecision {\n block: boolean\n reason?: string\n}\n\n/**\n * Decide whether a tool call must be blocked for the main operator. Pure — used\n * both by the PreToolUse hook handler and by config-assertion tests. When\n * operator mode is off, nothing is blocked (normal sessions unaffected).\n */\nfunction parseWorkerMode(toolName: string): WorkerDispatchMode | null {\n const workersPrefix = \"mcp__workers__\"\n if (!toolName.startsWith(workersPrefix)) return null\n const mode = toolName.slice(workersPrefix.length)\n return (ALL_WORKER_DISPATCH_MODES as ReadonlyArray<string>).includes(mode) ? (mode as WorkerDispatchMode) : null\n}\n\nfunction isMatchingWorkerDispatcher(toolName: string, input?: OperatorToolInput): boolean {\n const mode = parseWorkerMode(toolName)\n if (mode === null) return false\n return input?.agent_type === dispatcherAgentName(mode)\n}\n\nexport function shouldDenyOperatorTool(\n toolName: string,\n operatorMode: boolean,\n input?: OperatorToolInput,\n): boolean {\n if (!operatorMode) return false\n if (isMatchingWorkerDispatcher(toolName, input)) return false\n return OPERATOR_DENIED_MCP_PREFIXES.some((prefix) => toolName.startsWith(prefix))\n}\n\n/**\n * M4 — fail-CLOSED. If operator/`--agents` mode is active but the reduced\n * worker/orchestrate guard could NOT be installed (e.g. settings.json unwritable),\n * the operator session must NOT start with direct main-thread worker/orchestrate\n * MCP calls available. Non-agents sessions are never affected.\n */\nexport function assertShapingInstalled(agentsMode: boolean, injectionSucceeded: boolean): void {\n if (agentsMode && !injectionSucceeded) {\n throw new Error(\n \"cloud-agent operator mode requires the worker/orchestrate PreToolUse hook, \" +\n \"but it could not be installed — refusing to start an unguarded operator session.\",\n )\n }\n}\n\n/**\n * The PreToolUse hook decision for a given tool name in operator mode.\n *\n * Bash and file-authoring tools are always allowed. Only direct main-operator\n * calls to local worker/orchestrate MCP tools are blocked so those backends stay\n * reachable through the worker-* Agent subagents rather than the lead context.\n */\nexport function operatorPreToolUse(\n toolName: string,\n operatorMode: boolean,\n input?: OperatorToolInput,\n): PreToolUseDecision {\n if (!operatorMode) return { block: false }\n if (shouldDenyOperatorTool(toolName, operatorMode, input)) {\n return {\n block: true,\n reason: `${toolName} is subagent-only in cloud-agent operator mode — use the worker-* Agent subagents or delegate implementation to a GitHub cloud agent via the first-mate MCP instead of calling local worker/orchestrate MCP tools from the main operator.`,\n }\n }\n return { block: false }\n}\n","/**\n * The internal `internal-first-mate-guard` subcommand: a PreToolUse hook that\n * keeps local worker/orchestrate MCP tools subagent-only in cloud-agent OPERATOR\n * mode. It is injected into the spawned Claude session's settings ONLY in\n * `--agents`/operator mode, with a matcher scoped to those MCP tool prefixes.\n *\n * Reads the PreToolUse payload from stdin (`{tool_name, tool_input, ...}`) and,\n * if the tool is denied in operator mode, blocks via exit code 2 with a reason\n * on stderr (the repo's hook convention). Bash and file-authoring tools are not\n * matched by this guard and are not blocked.\n */\nimport { defineCommand } from \"citty\"\n\nimport { readFileSync } from \"node:fs\"\n\nimport { operatorPreToolUse, type OperatorToolInput } from \"./lib/first-mate/operator-shaping\"\nimport { buildSelfCommand, type SelfInvocation } from \"./lib/hook-launcher/self-invocation\"\n\nfunction readStdinSync(): string {\n try {\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nexport const internalFirstMateGuard = defineCommand({\n meta: {\n name: \"internal-first-mate-guard\",\n description: \"PreToolUse guard enforcing cloud-agent operator capability shaping.\",\n },\n run() {\n let toolName = \"\"\n let hookInput: OperatorToolInput | undefined\n let parsed: boolean\n try {\n const payload = JSON.parse(readStdinSync()) as {\n tool_name?: unknown\n tool_input?: unknown\n agent_type?: unknown\n }\n if (typeof payload.tool_name === \"string\") toolName = payload.tool_name\n if (typeof payload.agent_type === \"string\") hookInput = { agent_type: payload.agent_type }\n parsed = true\n } catch {\n parsed = false // fail-closed below for a named-but-unparseable payload\n }\n if (toolName.length === 0) {\n // Nothing to guard (empty/absent tool name) → allow. But if the payload\n // did not even parse we cannot know it was empty; the matcher only routes\n // the denied tools here, so a parse failure means one of THOSE arrived\n // unreadable → fail-closed.\n if (!parsed) {\n process.stderr.write(\"operator guard: unparseable PreToolUse payload — blocking (fail-closed)\")\n process.exit(2)\n }\n process.exit(0)\n }\n // This hook is only injected in operator mode, so operatorMode = true.\n const decision = operatorPreToolUse(toolName, true, hookInput)\n if (decision.block) {\n process.stderr.write(decision.reason ?? `${toolName} is disabled in operator mode`)\n process.exit(2) // exit 2 blocks the tool call (Claude Code hook convention)\n }\n process.exit(0)\n },\n})\n\n/** The regex matcher scoping the guard hook to exactly the denied tools. */\nexport const FIRST_MATE_GUARD_MATCHER = \"mcp__workers__.*|mcp__orchestrate__.*\"\n\n/** Build the hook command string that runs this subcommand. */\nexport function buildFirstMateGuardHookCommand(invocation: SelfInvocation): string {\n return buildSelfCommand(invocation, \"internal-first-mate-guard\")\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,+BAA+B,CAAC,kBAAkB,oBAAoB;;;;;;AAyCnF,SAAS,gBAAgB,UAA6C;CAEpE,IAAI,CAAC,SAAS,WAAW,gBAAa,GAAG,OAAO;CAChD,MAAM,OAAO,SAAS,MAAM,EAAoB;CAChD,OAAQ,0BAAoD,SAAS,IAAI,IAAK,OAA8B;AAC9G;AAEA,SAAS,2BAA2B,UAAkB,OAAoC;CACxF,MAAM,OAAO,gBAAgB,QAAQ;CACrC,IAAI,SAAS,MAAM,OAAO;CAC1B,OAAO,OAAO,eAAe,oBAAoB,IAAI;AACvD;AAEA,SAAgB,uBACd,UACA,cACA,OACS;CACT,IAAI,CAAC,cAAc,OAAO;CAC1B,IAAI,2BAA2B,UAAU,KAAK,GAAG,OAAO;CACxD,OAAO,6BAA6B,MAAM,WAAW,SAAS,WAAW,MAAM,CAAC;AAClF;;;;;;;AAQA,SAAgB,uBAAuB,YAAqB,oBAAmC;CAC7F,IAAI,cAAc,CAAC,oBACjB,MAAM,IAAI,MACR,6JAEF;AAEJ;;;;;;;;AASA,SAAgB,mBACd,UACA,cACA,OACoB;CACpB,IAAI,CAAC,cAAc,OAAO,EAAE,OAAO,MAAM;CACzC,IAAI,uBAAuB,UAAU,cAAc,KAAK,GACtD,OAAO;EACL,OAAO;EACP,QAAQ,GAAG,SAAS;CACtB;CAEF,OAAO,EAAE,OAAO,MAAM;AACxB;;;;;;;;;;;;;;AC1GA,SAAS,gBAAwB;CAC/B,IAAI;EACF,OAAO,aAAa,GAAG,MAAM;CAC/B,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAa,yBAAyB,cAAc;CAClD,MAAM;EACJ,MAAM;EACN,aAAa;CACf;CACA,MAAM;EACJ,IAAI,WAAW;EACf,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,UAAU,KAAK,MAAM,cAAc,CAAC;GAK1C,IAAI,OAAO,QAAQ,cAAc,UAAU,WAAW,QAAQ;GAC9D,IAAI,OAAO,QAAQ,eAAe,UAAU,YAAY,EAAE,YAAY,QAAQ,WAAW;GACzF,SAAS;EACX,QAAQ;GACN,SAAS;EACX;EACA,IAAI,SAAS,WAAW,GAAG;GAKzB,IAAI,CAAC,QAAQ;IACX,QAAQ,OAAO,MAAM,yEAAyE;IAC9F,QAAQ,KAAK,CAAC;GAChB;GACA,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,WAAW,mBAAmB,UAAU,MAAM,SAAS;EAC7D,IAAI,SAAS,OAAO;GAClB,QAAQ,OAAO,MAAM,SAAS,UAAU,GAAG,SAAS,8BAA8B;GAClF,QAAQ,KAAK,CAAC;EAChB;EACA,QAAQ,KAAK,CAAC;CAChB;AACF,CAAC;;AAGD,MAAa,2BAA2B;;AAGxC,SAAgB,+BAA+B,YAAoC;CACjF,OAAO,iBAAiB,YAAY,2BAA2B;AACjE"}
|
package/dist/{internal-max-dispatch-guard-Bz7a2Ntf.js → internal-max-dispatch-guard-BGOHRYRt.js}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as internalMaxDispatchGuard } from "./internal-max-dispatch-guard-
|
|
1
|
+
import { n as internalMaxDispatchGuard } from "./internal-max-dispatch-guard-CnBZxo5l.js";
|
|
2
2
|
export { internalMaxDispatchGuard };
|
package/dist/{internal-max-dispatch-guard-BNqQ2wMc.js → internal-max-dispatch-guard-CnBZxo5l.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as MAX_PROFILE_NATIVE_AGENT_NAMES, o as MAX_PROFILE_NATIVE_EFFORTS, s as MAX_PROFILE_NATIVE_MODELS } from "./max-profile-contract-Bl7I_EOC.js";
|
|
2
|
-
import { t as buildSelfCommand } from "./self-invocation-
|
|
2
|
+
import { t as buildSelfCommand } from "./self-invocation-DahN1gw9.js";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
//#region src/lib/max-dispatch-acl.ts
|
|
@@ -219,4 +219,4 @@ function buildMaxDispatchGuardHookCommand(invocation, opts = {}) {
|
|
|
219
219
|
//#endregion
|
|
220
220
|
export { assertMaxDispatchGuardInstalled as i, internalMaxDispatchGuard as n, MAX_DISPATCH_TOOL_MATCHER as r, buildMaxDispatchGuardHookCommand as t };
|
|
221
221
|
|
|
222
|
-
//# sourceMappingURL=internal-max-dispatch-guard-
|
|
222
|
+
//# sourceMappingURL=internal-max-dispatch-guard-CnBZxo5l.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"internal-max-dispatch-guard-BNqQ2wMc.js","names":[],"sources":["../src/lib/max-dispatch-acl.ts","../src/internal-max-dispatch-guard.ts"],"sourcesContent":["import {\n MAX_PROFILE_NATIVE_AGENT_NAMES,\n MAX_PROFILE_NATIVE_EFFORTS,\n MAX_PROFILE_NATIVE_MODELS,\n type MaxProfileNativeAgentName,\n} from \"./max-profile-contract\"\nimport type { Effort } from \"./reasoning-effort\"\n\nexport const MAX_DISPATCH_TOOL_MATCHER = \"^(Task|Agent)$\"\n\nexport const MAX_BROWSE_DISPATCH_AGENT = \"worker-browse\" as const\n\nconst MAX_NATIVE_AGENT_SET = new Set<string>([\n ...MAX_PROFILE_NATIVE_AGENT_NAMES,\n MAX_BROWSE_DISPATCH_AGENT,\n])\n\n/** Assert max launches never start without their native Task/Agent ACL. */\nexport class MaxDispatchGuardInstallError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"MaxDispatchGuardInstallError\"\n }\n}\n\nexport function assertMaxDispatchGuardInstalled(\n maxProfile: boolean,\n installationSucceeded: boolean,\n): void {\n if (maxProfile && !installationSucceeded) {\n throw new MaxDispatchGuardInstallError(\n \"max profile requires the native Task/Agent ACL hook, but it could not be installed; refusing to start an unguarded max session.\",\n )\n }\n}\n\nconst MAX_ALLOWED_MODEL_BASES = new Set([\"gpt-5.6-luna\", \"gemini-3.8-flash\", \"grok-4.6\"])\n\n/**\n * Claude Code's public Agent schema currently requires one of these built-in\n * model aliases even when the selected custom agent already owns its model in\n * frontmatter. They are transport placeholders in max, not model overrides.\n * Strip them so the role's catalog-validated max model remains authoritative.\n * Case and a trailing `[1m]` are ignored deliberately: neither can turn a\n * client-owned placeholder into an Opus override in the closed max roster.\n */\nexport const MAX_AGENT_SCHEMA_MODEL_ALIASES = Object.freeze([\n \"sonnet\",\n \"opus\",\n \"haiku\",\n \"fable\",\n] as const)\n\nconst MAX_AGENT_SCHEMA_MODEL_ALIAS_SET = new Set<string>(\n MAX_AGENT_SCHEMA_MODEL_ALIASES,\n)\n\nfunction isMaxAgentSchemaModelAlias(model: unknown): boolean {\n if (typeof model !== \"string\") return false\n return MAX_AGENT_SCHEMA_MODEL_ALIAS_SET.has(baseModel(model.trim()).toLowerCase())\n}\n\nexport interface MaxDispatchGuardOptions {\n /** Catalog-resolved reviewer model for this launch (Sonnet 5 1M/xhigh). */\n reviewerModel?: string\n reviewerEffort?: Extract<Effort, \"high\" | \"xhigh\" | \"max\">\n}\n\nexport interface MaxDispatchDecision {\n allowed: boolean\n reason?: string\n updatedInput?: Record<string, unknown>\n target?: string\n effort?: Effort\n verdict: \"allow\" | \"deny\" | \"allow-non-dispatch\"\n}\n\nfunction stringField(input: Record<string, unknown>, ...keys: string[]): string | undefined {\n const values = keys\n .filter((key) => Object.prototype.hasOwnProperty.call(input, key))\n .map((key) => input[key])\n .filter((value) => value !== undefined && value !== null)\n if (values.length === 0) return undefined\n if (values.some((value) => typeof value !== \"string\" || value.trim().length === 0)) return undefined\n const distinct = [...new Set((values as string[]).map((value) => value.trim()))]\n return distinct.length === 1 ? distinct[0] : undefined\n}\n\nfunction baseModel(model: string): string {\n return model.replace(/(?:\\[1m\\])+$/i, \"\")\n}\n\nexport function normalizeMaxDispatchModel(model: unknown): string | undefined {\n if (typeof model !== \"string\" || model.trim().length === 0) return undefined\n const trimmed = model.trim()\n const base = baseModel(trimmed)\n return MAX_ALLOWED_MODEL_BASES.has(base)\n ? base === \"grok-4.6\" ? base : `${base}[1m]`\n : undefined\n}\n\nexport function normalizeMaxDispatchEffort(\n model: string,\n effort: unknown,\n): Effort | undefined {\n if (effort === undefined || effort === null) return undefined\n if (typeof effort !== \"string\") return undefined\n const allowed: ReadonlyArray<Effort> = model === \"gpt-5.6-sol\"\n ? [\"high\", \"xhigh\", \"max\"]\n : model === \"gpt-5.6-luna\"\n ? [\"none\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]\n : model === \"claude-sonnet-5\" || model === \"claude-opus-5\"\n ? [\"low\", \"medium\", \"high\", \"xhigh\", \"max\"]\n : model === \"grok-4.6\" || model === \"gemini-3.8-flash\"\n ? [\"low\", \"medium\", \"high\"]\n : []\n if (allowed.includes(effort as Effort)) return effort as Effort\n return undefined\n}\n\n/**\n * Native max Task/Agent ACL. Role frontmatter owns the fixed model. Required\n * public-schema aliases are stripped as transport placeholders; a client that\n * can express custom ids may override only to Luna, Gemini, or Grok with a\n * live-ladder effort. Sol/Codex/unknown/private ids are rejected. The lead may\n * target any emitted max role; role-to-role edges are unrestricted here.\n */\nexport function decideMaxDispatchGuard(\n stdin: string | unknown,\n opts: MaxDispatchGuardOptions = {},\n): MaxDispatchDecision {\n let parsed: unknown\n try {\n parsed = typeof stdin === \"string\" ? JSON.parse(stdin) : stdin\n } catch {\n return { allowed: false, reason: \"max dispatch denied: malformed hook payload\", verdict: \"deny\" }\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return { allowed: false, reason: \"max dispatch denied: hook payload must be an object\", verdict: \"deny\" }\n }\n const payload = parsed as Record<string, unknown>\n if (typeof payload.tool_name !== \"string\") {\n return { allowed: false, reason: \"max dispatch denied: hook payload has no tool_name\", verdict: \"deny\" }\n }\n if (!/^(Task|Agent)$/.test(payload.tool_name)) {\n return { allowed: true, verdict: \"allow-non-dispatch\" }\n }\n if (!payload.tool_input || typeof payload.tool_input !== \"object\" || Array.isArray(payload.tool_input)) {\n return { allowed: false, reason: `max dispatch denied: ${payload.tool_name} payload has malformed tool_input`, verdict: \"deny\" }\n }\n const toolInput = payload.tool_input as Record<string, unknown>\n const target = stringField(toolInput, \"subagent_type\", \"subagentType\")\n if (!target || !MAX_NATIVE_AGENT_SET.has(target)) {\n return { allowed: false, reason: `max dispatch denied: target must be one of ${[...MAX_PROFILE_NATIVE_AGENT_NAMES, MAX_BROWSE_DISPATCH_AGENT].join(\", \")}`, verdict: \"deny\" }\n }\n const nativeTarget = target as MaxProfileNativeAgentName | typeof MAX_BROWSE_DISPATCH_AGENT\n const modelValue = toolInput.model\n const schemaAlias = isMaxAgentSchemaModelAlias(modelValue)\n const normalizedModel = modelValue === undefined || schemaAlias\n ? undefined\n : normalizeMaxDispatchModel(modelValue)\n if (modelValue !== undefined && !schemaAlias && !normalizedModel) {\n return { allowed: false, reason: \"max dispatch denied: model must be a Claude Code Agent schema alias, Luna[1m], Gemini 3.8 Flash[1m], or bare Grok 4.6\", target, verdict: \"deny\" }\n }\n const requestedEffort = toolInput.thinking ?? toolInput.effort\n const effectiveModel = normalizedModel\n ? baseModel(normalizedModel)\n : nativeTarget === MAX_BROWSE_DISPATCH_AGENT\n // Keep this aligned with BROWSE_DEFAULT_MODEL. Importing worker-agent/engine\n // here would pull the worker runtime into the small hook bundle.\n ? \"gpt-5.6-luna\"\n : nativeTarget === \"reviewer\" && opts.reviewerModel\n ? baseModel(opts.reviewerModel)\n : MAX_PROFILE_NATIVE_MODELS[nativeTarget]\n const effort = normalizeMaxDispatchEffort(effectiveModel, requestedEffort)\n if ((toolInput.thinking !== undefined || toolInput.effort !== undefined) && !effort) {\n return { allowed: false, reason: \"max dispatch denied: reasoning override is not supported by the selected model\", target, verdict: \"deny\" }\n }\n const fixedReviewerEffort = nativeTarget === \"reviewer\"\n && !normalizedModel\n && opts.reviewerModel\n ? opts.reviewerEffort ?? MAX_PROFILE_NATIVE_EFFORTS.reviewer\n : undefined\n const effectiveEffort = fixedReviewerEffort ?? effort\n const updatedInput = { ...toolInput }\n if (normalizedModel) updatedInput.model = normalizedModel\n else delete updatedInput.model\n if (effectiveEffort) {\n // Claude Code's native Agent payload uses `model` and `effort`; preserve\n // an explicit field when possible, and emit the launch-resolved reviewer\n // effort so the dispatch cannot drift from the generated frontmatter.\n if (toolInput.thinking !== undefined) updatedInput.thinking = effectiveEffort\n else updatedInput.effort = effectiveEffort\n }\n if (toolInput.thinking !== undefined && toolInput.effort !== undefined) {\n delete updatedInput.effort\n }\n if (toolInput.model === undefined && normalizedModel === undefined) delete updatedInput.model\n return { allowed: true, target, effort: effectiveEffort, updatedInput, verdict: \"allow\" }\n}\n\nexport function maxDispatchAllowOutput(updatedInput: Record<string, unknown>): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"allow\",\n updatedInput,\n },\n })\n}\n\nexport function maxDispatchDenyOutput(reason: string): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: reason,\n },\n })\n}\n","import { defineCommand } from \"citty\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n decideMaxDispatchGuard,\n maxDispatchAllowOutput,\n maxDispatchDenyOutput,\n} from \"./lib/max-dispatch-acl\"\nimport { buildSelfCommand, type SelfInvocation } from \"./lib/hook-launcher/self-invocation\"\n\nfunction readStdinSync(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nexport const internalMaxDispatchGuard = defineCommand({\n meta: {\n name: \"internal-max-dispatch-guard\",\n description: \"Internal: PreToolUse guard enforcing the max-profile native Task/Agent policy.\",\n },\n args: {\n reviewerModel: {\n type: \"string\",\n description: \"Catalog-resolved Max reviewer model for this launch.\",\n },\n reviewerEffort: {\n type: \"string\",\n description: \"Fixed Max reviewer effort for this launch.\",\n },\n },\n run({ args }) {\n const reviewerEffort = args.reviewerEffort === \"max\"\n ? \"max\"\n : args.reviewerEffort === \"xhigh\"\n ? \"xhigh\"\n : \"high\"\n const decision = decideMaxDispatchGuard(readStdinSync(), {\n reviewerModel: args.reviewerModel,\n reviewerEffort,\n })\n if (!decision.allowed && decision.reason) {\n process.stdout.write(maxDispatchDenyOutput(decision.reason))\n } else if (decision.verdict === \"allow\" && decision.updatedInput) {\n process.stdout.write(maxDispatchAllowOutput(decision.updatedInput))\n }\n process.exitCode = 0\n },\n})\n\nexport function buildMaxDispatchGuardHookCommand(\n invocation: SelfInvocation,\n opts: { reviewerModel?: string; reviewerEffort?: \"high\" | \"xhigh\" | \"max\" } = {},\n): string {\n const flags: string[] = []\n if (opts.reviewerModel) flags.push(`--reviewerModel \"${opts.reviewerModel}\"`)\n if (opts.reviewerEffort) flags.push(`--reviewerEffort ${opts.reviewerEffort}`)\n return buildSelfCommand(\n invocation,\n [\"internal-max-dispatch-guard\", ...flags].join(\" \"),\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"internal-max-dispatch-guard-CnBZxo5l.js","names":[],"sources":["../src/lib/max-dispatch-acl.ts","../src/internal-max-dispatch-guard.ts"],"sourcesContent":["import {\n MAX_PROFILE_NATIVE_AGENT_NAMES,\n MAX_PROFILE_NATIVE_EFFORTS,\n MAX_PROFILE_NATIVE_MODELS,\n type MaxProfileNativeAgentName,\n} from \"./max-profile-contract\"\nimport type { Effort } from \"./reasoning-effort\"\n\nexport const MAX_DISPATCH_TOOL_MATCHER = \"^(Task|Agent)$\"\n\nexport const MAX_BROWSE_DISPATCH_AGENT = \"worker-browse\" as const\n\nconst MAX_NATIVE_AGENT_SET = new Set<string>([\n ...MAX_PROFILE_NATIVE_AGENT_NAMES,\n MAX_BROWSE_DISPATCH_AGENT,\n])\n\n/** Assert max launches never start without their native Task/Agent ACL. */\nexport class MaxDispatchGuardInstallError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"MaxDispatchGuardInstallError\"\n }\n}\n\nexport function assertMaxDispatchGuardInstalled(\n maxProfile: boolean,\n installationSucceeded: boolean,\n): void {\n if (maxProfile && !installationSucceeded) {\n throw new MaxDispatchGuardInstallError(\n \"max profile requires the native Task/Agent ACL hook, but it could not be installed; refusing to start an unguarded max session.\",\n )\n }\n}\n\nconst MAX_ALLOWED_MODEL_BASES = new Set([\"gpt-5.6-luna\", \"gemini-3.8-flash\", \"grok-4.6\"])\n\n/**\n * Claude Code's public Agent schema currently requires one of these built-in\n * model aliases even when the selected custom agent already owns its model in\n * frontmatter. They are transport placeholders in max, not model overrides.\n * Strip them so the role's catalog-validated max model remains authoritative.\n * Case and a trailing `[1m]` are ignored deliberately: neither can turn a\n * client-owned placeholder into an Opus override in the closed max roster.\n */\nexport const MAX_AGENT_SCHEMA_MODEL_ALIASES = Object.freeze([\n \"sonnet\",\n \"opus\",\n \"haiku\",\n \"fable\",\n] as const)\n\nconst MAX_AGENT_SCHEMA_MODEL_ALIAS_SET = new Set<string>(\n MAX_AGENT_SCHEMA_MODEL_ALIASES,\n)\n\nfunction isMaxAgentSchemaModelAlias(model: unknown): boolean {\n if (typeof model !== \"string\") return false\n return MAX_AGENT_SCHEMA_MODEL_ALIAS_SET.has(baseModel(model.trim()).toLowerCase())\n}\n\nexport interface MaxDispatchGuardOptions {\n /** Catalog-resolved reviewer model for this launch (Sonnet 5 1M/xhigh). */\n reviewerModel?: string\n reviewerEffort?: Extract<Effort, \"high\" | \"xhigh\" | \"max\">\n}\n\nexport interface MaxDispatchDecision {\n allowed: boolean\n reason?: string\n updatedInput?: Record<string, unknown>\n target?: string\n effort?: Effort\n verdict: \"allow\" | \"deny\" | \"allow-non-dispatch\"\n}\n\nfunction stringField(input: Record<string, unknown>, ...keys: string[]): string | undefined {\n const values = keys\n .filter((key) => Object.prototype.hasOwnProperty.call(input, key))\n .map((key) => input[key])\n .filter((value) => value !== undefined && value !== null)\n if (values.length === 0) return undefined\n if (values.some((value) => typeof value !== \"string\" || value.trim().length === 0)) return undefined\n const distinct = [...new Set((values as string[]).map((value) => value.trim()))]\n return distinct.length === 1 ? distinct[0] : undefined\n}\n\nfunction baseModel(model: string): string {\n return model.replace(/(?:\\[1m\\])+$/i, \"\")\n}\n\nexport function normalizeMaxDispatchModel(model: unknown): string | undefined {\n if (typeof model !== \"string\" || model.trim().length === 0) return undefined\n const trimmed = model.trim()\n const base = baseModel(trimmed)\n return MAX_ALLOWED_MODEL_BASES.has(base)\n ? base === \"grok-4.6\" ? base : `${base}[1m]`\n : undefined\n}\n\nexport function normalizeMaxDispatchEffort(\n model: string,\n effort: unknown,\n): Effort | undefined {\n if (effort === undefined || effort === null) return undefined\n if (typeof effort !== \"string\") return undefined\n const allowed: ReadonlyArray<Effort> = model === \"gpt-5.6-sol\"\n ? [\"high\", \"xhigh\", \"max\"]\n : model === \"gpt-5.6-luna\"\n ? [\"none\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]\n : model === \"claude-sonnet-5\" || model === \"claude-opus-5\"\n ? [\"low\", \"medium\", \"high\", \"xhigh\", \"max\"]\n : model === \"grok-4.6\" || model === \"gemini-3.8-flash\"\n ? [\"low\", \"medium\", \"high\"]\n : []\n if (allowed.includes(effort as Effort)) return effort as Effort\n return undefined\n}\n\n/**\n * Native max Task/Agent ACL. Role frontmatter owns the fixed model. Required\n * public-schema aliases are stripped as transport placeholders; a client that\n * can express custom ids may override only to Luna, Gemini, or Grok with a\n * live-ladder effort. Sol/Codex/unknown/private ids are rejected. The lead may\n * target any emitted max role; role-to-role edges are unrestricted here.\n */\nexport function decideMaxDispatchGuard(\n stdin: string | unknown,\n opts: MaxDispatchGuardOptions = {},\n): MaxDispatchDecision {\n let parsed: unknown\n try {\n parsed = typeof stdin === \"string\" ? JSON.parse(stdin) : stdin\n } catch {\n return { allowed: false, reason: \"max dispatch denied: malformed hook payload\", verdict: \"deny\" }\n }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return { allowed: false, reason: \"max dispatch denied: hook payload must be an object\", verdict: \"deny\" }\n }\n const payload = parsed as Record<string, unknown>\n if (typeof payload.tool_name !== \"string\") {\n return { allowed: false, reason: \"max dispatch denied: hook payload has no tool_name\", verdict: \"deny\" }\n }\n if (!/^(Task|Agent)$/.test(payload.tool_name)) {\n return { allowed: true, verdict: \"allow-non-dispatch\" }\n }\n if (!payload.tool_input || typeof payload.tool_input !== \"object\" || Array.isArray(payload.tool_input)) {\n return { allowed: false, reason: `max dispatch denied: ${payload.tool_name} payload has malformed tool_input`, verdict: \"deny\" }\n }\n const toolInput = payload.tool_input as Record<string, unknown>\n const target = stringField(toolInput, \"subagent_type\", \"subagentType\")\n if (!target || !MAX_NATIVE_AGENT_SET.has(target)) {\n return { allowed: false, reason: `max dispatch denied: target must be one of ${[...MAX_PROFILE_NATIVE_AGENT_NAMES, MAX_BROWSE_DISPATCH_AGENT].join(\", \")}`, verdict: \"deny\" }\n }\n const nativeTarget = target as MaxProfileNativeAgentName | typeof MAX_BROWSE_DISPATCH_AGENT\n const modelValue = toolInput.model\n const schemaAlias = isMaxAgentSchemaModelAlias(modelValue)\n const normalizedModel = modelValue === undefined || schemaAlias\n ? undefined\n : normalizeMaxDispatchModel(modelValue)\n if (modelValue !== undefined && !schemaAlias && !normalizedModel) {\n return { allowed: false, reason: \"max dispatch denied: model must be a Claude Code Agent schema alias, Luna[1m], Gemini 3.8 Flash[1m], or bare Grok 4.6\", target, verdict: \"deny\" }\n }\n const requestedEffort = toolInput.thinking ?? toolInput.effort\n const effectiveModel = normalizedModel\n ? baseModel(normalizedModel)\n : nativeTarget === MAX_BROWSE_DISPATCH_AGENT\n // Keep this aligned with BROWSE_DEFAULT_MODEL. Importing worker-agent/engine\n // here would pull the worker runtime into the small hook bundle.\n ? \"gpt-5.6-luna\"\n : nativeTarget === \"reviewer\" && opts.reviewerModel\n ? baseModel(opts.reviewerModel)\n : MAX_PROFILE_NATIVE_MODELS[nativeTarget]\n const effort = normalizeMaxDispatchEffort(effectiveModel, requestedEffort)\n if ((toolInput.thinking !== undefined || toolInput.effort !== undefined) && !effort) {\n return { allowed: false, reason: \"max dispatch denied: reasoning override is not supported by the selected model\", target, verdict: \"deny\" }\n }\n const fixedReviewerEffort = nativeTarget === \"reviewer\"\n && !normalizedModel\n && opts.reviewerModel\n ? opts.reviewerEffort ?? MAX_PROFILE_NATIVE_EFFORTS.reviewer\n : undefined\n const effectiveEffort = fixedReviewerEffort ?? effort\n const updatedInput = { ...toolInput }\n if (normalizedModel) updatedInput.model = normalizedModel\n else delete updatedInput.model\n if (effectiveEffort) {\n // Claude Code's native Agent payload uses `model` and `effort`; preserve\n // an explicit field when possible, and emit the launch-resolved reviewer\n // effort so the dispatch cannot drift from the generated frontmatter.\n if (toolInput.thinking !== undefined) updatedInput.thinking = effectiveEffort\n else updatedInput.effort = effectiveEffort\n }\n if (toolInput.thinking !== undefined && toolInput.effort !== undefined) {\n delete updatedInput.effort\n }\n if (toolInput.model === undefined && normalizedModel === undefined) delete updatedInput.model\n return { allowed: true, target, effort: effectiveEffort, updatedInput, verdict: \"allow\" }\n}\n\nexport function maxDispatchAllowOutput(updatedInput: Record<string, unknown>): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"allow\",\n updatedInput,\n },\n })\n}\n\nexport function maxDispatchDenyOutput(reason: string): string {\n return JSON.stringify({\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: reason,\n },\n })\n}\n","import { defineCommand } from \"citty\"\nimport { readFileSync } from \"node:fs\"\n\nimport {\n decideMaxDispatchGuard,\n maxDispatchAllowOutput,\n maxDispatchDenyOutput,\n} from \"./lib/max-dispatch-acl\"\nimport { buildSelfCommand, type SelfInvocation } from \"./lib/hook-launcher/self-invocation\"\n\nfunction readStdinSync(): string {\n try {\n if (process.stdin.isTTY) return \"\"\n return readFileSync(0, \"utf8\")\n } catch {\n return \"\"\n }\n}\n\nexport const internalMaxDispatchGuard = defineCommand({\n meta: {\n name: \"internal-max-dispatch-guard\",\n description: \"Internal: PreToolUse guard enforcing the max-profile native Task/Agent policy.\",\n },\n args: {\n reviewerModel: {\n type: \"string\",\n description: \"Catalog-resolved Max reviewer model for this launch.\",\n },\n reviewerEffort: {\n type: \"string\",\n description: \"Fixed Max reviewer effort for this launch.\",\n },\n },\n run({ args }) {\n const reviewerEffort = args.reviewerEffort === \"max\"\n ? \"max\"\n : args.reviewerEffort === \"xhigh\"\n ? \"xhigh\"\n : \"high\"\n const decision = decideMaxDispatchGuard(readStdinSync(), {\n reviewerModel: args.reviewerModel,\n reviewerEffort,\n })\n if (!decision.allowed && decision.reason) {\n process.stdout.write(maxDispatchDenyOutput(decision.reason))\n } else if (decision.verdict === \"allow\" && decision.updatedInput) {\n process.stdout.write(maxDispatchAllowOutput(decision.updatedInput))\n }\n process.exitCode = 0\n },\n})\n\nexport function buildMaxDispatchGuardHookCommand(\n invocation: SelfInvocation,\n opts: { reviewerModel?: string; reviewerEffort?: \"high\" | \"xhigh\" | \"max\" } = {},\n): string {\n const flags: string[] = []\n if (opts.reviewerModel) flags.push(`--reviewerModel \"${opts.reviewerModel}\"`)\n if (opts.reviewerEffort) flags.push(`--reviewerEffort ${opts.reviewerEffort}`)\n return buildSelfCommand(\n invocation,\n [\"internal-max-dispatch-guard\", ...flags].join(\" \"),\n )\n}\n"],"mappings":"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"}
|