github-router 0.3.249 → 0.3.255

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/attribution-settings-CegtE0TY.js +2347 -0
  2. package/dist/attribution-settings-CegtE0TY.js.map +1 -0
  3. package/dist/auth-CP4q2CjO.js +44 -0
  4. package/dist/auth-CP4q2CjO.js.map +1 -0
  5. package/dist/browser-ext/background.js +8 -1
  6. package/dist/browser-ext/manifest.json +1 -1
  7. package/dist/check-usage-6PB5IUFb.js +43 -0
  8. package/dist/check-usage-6PB5IUFb.js.map +1 -0
  9. package/dist/claude-BiZxoNWq.js +593 -0
  10. package/dist/claude-BiZxoNWq.js.map +1 -0
  11. package/dist/client-CQMfroGV.js +413 -0
  12. package/dist/client-CQMfroGV.js.map +1 -0
  13. package/dist/codex-ST21X3WF.js +85 -0
  14. package/dist/codex-ST21X3WF.js.map +1 -0
  15. package/dist/debug-CjmE9sgG.js +81 -0
  16. package/dist/debug-CjmE9sgG.js.map +1 -0
  17. package/dist/engine-DNCX0Hfd.js +2 -0
  18. package/dist/{lifecycle-DHZsVN8j.js → exec-DTO2oin0.js} +3 -130
  19. package/dist/exec-DTO2oin0.js.map +1 -0
  20. package/dist/file-log-reporter-IFQcL5IK.js +158 -0
  21. package/dist/file-log-reporter-IFQcL5IK.js.map +1 -0
  22. package/dist/gate-discovery-CepmbZnc.js +635 -0
  23. package/dist/gate-discovery-CepmbZnc.js.map +1 -0
  24. package/dist/get-copilot-usage-DD1N0Xlh.js +11 -0
  25. package/dist/get-copilot-usage-DD1N0Xlh.js.map +1 -0
  26. package/dist/hook-mcp-client-DBvm9608.js +136 -0
  27. package/dist/hook-mcp-client-DBvm9608.js.map +1 -0
  28. package/dist/internal-artifact-open-D2cQ2QCw.js +319 -0
  29. package/dist/internal-artifact-open-D2cQ2QCw.js.map +1 -0
  30. package/dist/internal-first-mate-guard-7v2CtMA6.js +2 -0
  31. package/dist/internal-first-mate-guard-La0tIjTU.js +126 -0
  32. package/dist/internal-first-mate-guard-La0tIjTU.js.map +1 -0
  33. package/dist/internal-plan-review-ipnT3bhj.js +114 -0
  34. package/dist/internal-plan-review-ipnT3bhj.js.map +1 -0
  35. package/dist/internal-prompt-submit-DOj75_hh.js +121 -0
  36. package/dist/internal-prompt-submit-DOj75_hh.js.map +1 -0
  37. package/dist/internal-session-bind-DwK2iGW0.js +133 -0
  38. package/dist/internal-session-bind-DwK2iGW0.js.map +1 -0
  39. package/dist/internal-stop-hook-CNPGJm63.js +212 -0
  40. package/dist/internal-stop-hook-CNPGJm63.js.map +1 -0
  41. package/dist/internal-stop-review-AUcFssrW.js +122 -0
  42. package/dist/internal-stop-review-AUcFssrW.js.map +1 -0
  43. package/dist/internal-worker-guard-Lu8VHj5k.js +75 -0
  44. package/dist/internal-worker-guard-Lu8VHj5k.js.map +1 -0
  45. package/dist/internal-workspace-header-BRMz0Yql.js +24 -0
  46. package/dist/internal-workspace-header-BRMz0Yql.js.map +1 -0
  47. package/dist/lifecycle-Byw5s1G8.js +134 -0
  48. package/dist/lifecycle-Byw5s1G8.js.map +1 -0
  49. package/dist/lifecycle-D80CaWie.js +2 -0
  50. package/dist/{lifecycle-CUf1UDrH.js → lifecycle-DD88ka3f.js} +3 -3
  51. package/dist/{lifecycle-CUf1UDrH.js.map → lifecycle-DD88ka3f.js.map} +1 -1
  52. package/dist/lifecycle-DzqCqDlM.js +2 -0
  53. package/dist/main.js +43 -12884
  54. package/dist/main.js.map +1 -1
  55. package/dist/mcp-workspace-header-CGJbNeHb.js +21 -0
  56. package/dist/mcp-workspace-header-CGJbNeHb.js.map +1 -0
  57. package/dist/models-B6UnGyRo.js +158 -0
  58. package/dist/models-B6UnGyRo.js.map +1 -0
  59. package/dist/orchestration-hRU4TdWr.js +39 -0
  60. package/dist/orchestration-hRU4TdWr.js.map +1 -0
  61. package/dist/{paths-BjTMI_xK.js → paths-C1cLZ7Ga.js} +52 -20
  62. package/dist/paths-C1cLZ7Ga.js.map +1 -0
  63. package/dist/paths-CwKwc2pi.js +2 -0
  64. package/dist/{peer-mcp-personas-DJzLpfDJ.js → peer-mcp-personas-BDet588n.js} +1006 -2488
  65. package/dist/peer-mcp-personas-BDet588n.js.map +1 -0
  66. package/dist/plan-review-hook-C8x0uKPf.js +159 -0
  67. package/dist/plan-review-hook-C8x0uKPf.js.map +1 -0
  68. package/dist/prompt-submit-hook-BrkkLAAB.js +153 -0
  69. package/dist/prompt-submit-hook-BrkkLAAB.js.map +1 -0
  70. package/dist/provision-DSkPa5hY.js +656 -0
  71. package/dist/provision-DSkPa5hY.js.map +1 -0
  72. package/dist/serve-sa_7c-yD.js +1511 -0
  73. package/dist/serve-sa_7c-yD.js.map +1 -0
  74. package/dist/server-setup-DcO9qvN3.js +4776 -0
  75. package/dist/server-setup-DcO9qvN3.js.map +1 -0
  76. package/dist/start-siGxuZhO.js +129 -0
  77. package/dist/start-siGxuZhO.js.map +1 -0
  78. package/dist/stop-gate-hook-DQc2KV7B.js +686 -0
  79. package/dist/stop-gate-hook-DQc2KV7B.js.map +1 -0
  80. package/dist/stop-gate-policy-CHwWbuF_.js +268 -0
  81. package/dist/stop-gate-policy-CHwWbuF_.js.map +1 -0
  82. package/dist/token-R2ocFC3M.js +1054 -0
  83. package/dist/token-R2ocFC3M.js.map +1 -0
  84. package/dist/version-_Q1WpsQp.js +32 -0
  85. package/dist/version-_Q1WpsQp.js.map +1 -0
  86. package/dist/worker-dispatch-Bj1uYyG9.js +222 -0
  87. package/dist/worker-dispatch-Bj1uYyG9.js.map +1 -0
  88. package/package.json +1 -1
  89. package/dist/engine-BuuHUb4t.js +0 -2
  90. package/dist/lifecycle-0rXrwjWk.js +0 -2
  91. package/dist/lifecycle-DHZsVN8j.js.map +0 -1
  92. package/dist/lifecycle-hzPwXc3I.js +0 -2
  93. package/dist/paths-BjTMI_xK.js.map +0 -1
  94. package/dist/paths-D3XgmTcQ.js +0 -2
  95. package/dist/peer-mcp-personas-DJzLpfDJ.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-CQMfroGV.js","names":[],"sources":["../src/lib/insecure-tls.ts","../src/lib/artifact/client.ts"],"sourcesContent":["/**\n * Runtime-aware \"skip TLS verification\" for a self-signed direct-HTTPS instance.\n * The mechanism differs by runtime and getting it wrong is silent: the BUILT\n * proxy runs under Node (bin shebang `#!/usr/bin/env node`), whose fetch (undici)\n * IGNORES a per-request `tls` option — only a `dispatcher` relaxes verification.\n * `bun run dev` and the test suite run under Bun, whose fetch honors `tls`. So we\n * detect the runtime and attach the field it actually understands.\n *\n * Shared by the fleet control-plane client and the artifact-review client — both\n * talk to a self-signed ai-or-die instance on loopback. Per-request scope (not a\n * global TLS posture change) keeps Copilot upstream verification untouched.\n */\nimport { Agent } from \"undici\"\n\nconst IS_BUN = typeof (globalThis as { Bun?: unknown }).Bun !== \"undefined\"\n\n// One lazily-created insecure dispatcher (Node path), shared across instances. It\n// is a SEPARATE connection pool from the global/verified dispatcher, so an\n// unverified socket can never be reused by a verified request.\nlet sharedInsecureDispatcher: Agent | undefined\nfunction insecureDispatcher(): Agent {\n return (sharedInsecureDispatcher ??= new Agent({ connect: { rejectUnauthorized: false } }))\n}\n\n/**\n * Attach the runtime-correct TLS-verification-off mechanism to a fetch init for a\n * single self-signed direct-HTTPS instance: Bun → `tls`, Node → an undici\n * `dispatcher`. Exported so BOTH runtime branches are unit-testable under one\n * interpreter (the untested Node branch is exactly what shipped broken).\n */\nexport function applyInsecureTls(init: Record<string, unknown>, isBun: boolean = IS_BUN): void {\n if (isBun) {\n init.tls = { rejectUnauthorized: false }\n } else {\n init.dispatcher = insecureDispatcher()\n }\n}\n\nexport { IS_BUN }\n","import { randomUUID } from \"node:crypto\"\n\nimport { applyInsecureTls } from \"../insecure-tls\"\n\nexport type ArtifactErrorCode =\n | \"UNREACHABLE\"\n | \"AUTH_FAILED\"\n | \"NOT_FOUND\"\n | \"TIMEOUT\"\n | \"UPSTREAM_ERROR\"\n | \"INVALID_RESPONSE\"\n | \"INVALID_REQUEST\"\n\nexport class ArtifactError extends Error {\n code: ArtifactErrorCode\n retryable: boolean\n status?: number\n detail?: unknown\n\n constructor(args: {\n code: ArtifactErrorCode\n message: string\n retryable: boolean\n status?: number\n detail?: unknown\n }) {\n super(args.message)\n this.name = \"ArtifactError\"\n this.code = args.code\n this.retryable = args.retryable\n this.status = args.status\n this.detail = args.detail\n }\n}\n\nexport interface ArtifactClientOptions {\n baseUrl: string\n token: string\n sessionId: string\n fetchFn?: typeof fetch\n /**\n * Disable TLS certificate verification for this client's requests (ai-or-die\n * serves the artifact API over a SELF-SIGNED cert on loopback). Applied\n * per-request via the runtime-correct mechanism (Bun `tls` / Node undici\n * `dispatcher`); the global TLS posture is untouched. Off unless explicitly true.\n */\n insecureTLS?: boolean\n /**\n * Base backoff (ms) between transient retries. The per-attempt delay is\n * `retryBaseMs * 2^(attempt-1)` with jitter. Tests pass 0 for instant retries.\n */\n retryBaseMs?: number\n}\n\nexport interface ArtifactOpenResponse {\n sessionId: string\n key: string\n viewUrl: string\n}\n\nexport interface ArtifactUpdateResponse {\n ok: boolean\n viewUrl?: string\n [key: string]: unknown\n}\n\nexport interface ArtifactSimpleResponse {\n ok: boolean\n [key: string]: unknown\n}\n\nexport interface ArtifactEndResponse {\n ok: boolean\n status?: string\n [key: string]: unknown\n}\n\nexport interface ArtifactPollResponse {\n status: string\n prompts?: unknown\n layout_warnings?: unknown\n dom_snapshot?: unknown\n next_step?: string\n [key: string]: unknown\n}\n\n/**\n * Typed drain events (contract v2.2 §2). The union is deliberately open-ended:\n * an unknown `kind` is preserved (never rejected) so a future event type flows\n * through to the model, which ignores what it does not understand (the\n * forward-compat rule). Server assigns a per-session, gap-free monotonic `id`.\n */\nexport interface ArtifactCommentEvent {\n kind: \"comment\"\n id: string\n prompt: string\n text: string\n selector: string\n sourceLine?: number\n target?: unknown\n}\nexport interface ArtifactActionEvent {\n kind: \"action\"\n id: string\n action: string\n value?: string\n elementId: string\n selector?: string\n sourceLine?: number\n}\nexport interface ArtifactEndedEvent {\n kind: \"ended\"\n id: string\n}\nexport type ArtifactEvent =\n | ArtifactCommentEvent\n | ArtifactActionEvent\n | ArtifactEndedEvent\n | { kind: string; id: string; [key: string]: unknown }\n\nexport type ReviewStatus = \"open\" | \"ended\" | \"missing\" | (string & {})\n\nexport interface ArtifactAwaitResponse {\n events: ArtifactEvent[]\n status: ReviewStatus\n cursor?: string\n [key: string]: unknown\n}\n\nexport interface ArtifactAgentReplyResponse {\n [key: string]: unknown\n}\n\n// Long-hold budget for /await. The server long-polls up to ~25s; we abort a\n// little above that so a healthy long-hold is never severed, only a real stall.\nconst AWAIT_DEFAULT_TIMEOUT_MS = 25_000\nconst AWAIT_ABORT_MARGIN_MS = 5_000\nconst DEFAULT_RETRY_BASE_MS = 250\n// Retryable ops (open/update/end/await) get this many additional attempts on a\n// transient UNREACHABLE/TIMEOUT. Single-shot ops (reply/refresh/dismiss) get 0.\nconst TRANSIENT_RETRIES = 2\n// Which error codes withRetry re-attempts. Default covers a network stall and a\n// connection failure. `awaitEvents` narrows to UNREACHABLE only (a long-hold\n// TIMEOUT is a normal empty-drain, not a fault to re-hold).\nconst DEFAULT_RETRYABLE_CODES: ReadonlySet<ArtifactErrorCode> = new Set([\"UNREACHABLE\", \"TIMEOUT\"])\nconst AWAIT_RETRYABLE_CODES: ReadonlySet<ArtifactErrorCode> = new Set([\"UNREACHABLE\"])\n\nexport class ArtifactClient {\n private readonly baseUrl: string\n private readonly token: string\n private readonly sessionId: string\n private readonly fetchFn: typeof fetch\n private readonly insecureTLS: boolean\n private readonly retryBaseMs: number\n\n constructor(options: ArtifactClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, \"\")\n this.token = options.token\n this.sessionId = options.sessionId\n this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis)\n this.insecureTLS = options.insecureTLS ?? false\n this.retryBaseMs = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS\n }\n\n /**\n * Open (or replace) the review for this session. `mode` is advisory metadata\n * forwarded to the server (forward-compat: ignored if unknown); the actual\n * interactivity comes from the served HTML's `data-aod-*` markup. Retried on a\n * transient failure with a STABLE idempotency key so a retry-after-success does\n * not double-open.\n */\n open(\n file: string,\n opts: { mode?: string; idempotencyKey?: string; signal?: AbortSignal } = {},\n ): Promise<ArtifactOpenResponse> {\n const idempotencyKey = opts.idempotencyKey ?? randomUUID()\n const body: Record<string, unknown> = { file }\n if (opts.mode) body.mode = opts.mode\n return this.withRetry(\n () =>\n this.requestOnce<ArtifactOpenResponse>({\n method: \"POST\",\n pathname: this.path(\"/open\"),\n body,\n signal: opts.signal,\n idempotencyKey,\n }),\n TRANSIENT_RETRIES,\n opts.signal,\n )\n }\n\n /**\n * Replace the current review's content. Exactly one of `file` | `html` (the\n * caller-facing tool enforces that). `html` is written by the SERVER to the\n * review's existing sandboxed file, so `html` with no existing review is an\n * INVALID_REQUEST from the server. Retried with a stable idempotency key.\n */\n update(opts: {\n file?: string\n html?: string\n idempotencyKey?: string\n signal?: AbortSignal\n }): Promise<ArtifactUpdateResponse> {\n const idempotencyKey = opts.idempotencyKey ?? randomUUID()\n const body: Record<string, unknown> = {}\n if (opts.file !== undefined) body.file = opts.file\n if (opts.html !== undefined) body.html = opts.html\n return this.withRetry(\n () =>\n this.requestOnce<ArtifactUpdateResponse>({\n method: \"POST\",\n pathname: this.path(\"/update\"),\n body,\n signal: opts.signal,\n idempotencyKey,\n allowEmptyJson: true,\n }),\n TRANSIENT_RETRIES,\n opts.signal,\n )\n }\n\n /** Force a reload from disk (no content change). Single-shot (idempotent, cheap). */\n refresh(signal?: AbortSignal): Promise<ArtifactSimpleResponse> {\n return this.requestOnce<ArtifactSimpleResponse>({\n method: \"POST\",\n pathname: this.path(\"/refresh\"),\n signal,\n allowEmptyJson: true,\n })\n }\n\n /** Hide the panel UI while keeping the review alive. Single-shot; server idempotent. */\n dismiss(signal?: AbortSignal): Promise<ArtifactSimpleResponse> {\n return this.requestOnce<ArtifactSimpleResponse>({\n method: \"POST\",\n pathname: this.path(\"/dismiss\"),\n signal,\n allowEmptyJson: true,\n })\n }\n\n /**\n * Typed drain (contract v2.2 §1/§2). Long-holds up to the server cap, returns\n * events with `id > cursor` plus the new high-water `cursor`. Idempotent by\n * (cursor, event.id): re-calling with the same cursor replays the same window\n * from the server's bounded buffer, so a lost cursor (compaction) is\n * recoverable. Retried on a transient network failure (the cursor makes retry\n * safe — no double-consumption).\n */\n awaitEvents(\n opts: { cursor?: string; timeoutMs?: number; signal?: AbortSignal } = {},\n ): Promise<ArtifactAwaitResponse> {\n const serverTimeoutMs =\n typeof opts.timeoutMs === \"number\" && Number.isFinite(opts.timeoutMs) && opts.timeoutMs > 0\n ? opts.timeoutMs\n : AWAIT_DEFAULT_TIMEOUT_MS\n // Abort a little ABOVE the server's own long-hold cap so a healthy hold is\n // never severed — only a genuine stall trips the client timeout.\n const clientTimeoutMs = serverTimeoutMs + AWAIT_ABORT_MARGIN_MS\n return this.withRetry(\n () =>\n this.requestOnce<ArtifactAwaitResponse>({\n method: \"GET\",\n pathname: this.path(\"/await\"),\n query: {\n cursor: opts.cursor,\n timeoutMs: String(serverTimeoutMs),\n },\n signal: opts.signal,\n timeoutMsHint: clientTimeoutMs,\n }),\n TRANSIENT_RETRIES,\n opts.signal,\n // A long-hold TIMEOUT is the EXPECTED empty-drain outcome, not a fault, so\n // do not silently re-hold (which would multiply the wait); the caller\n // re-awaits with the cursor. Only a real connection failure is retried.\n AWAIT_RETRYABLE_CODES,\n )\n }\n\n /** Free-text agent->human reply. SINGLE-SHOT (not retried): a retry-after-success\n * would render a duplicate chat bubble server-side. */\n agentReply(text: string, signal?: AbortSignal): Promise<ArtifactAgentReplyResponse> {\n return this.requestOnce<ArtifactAgentReplyResponse>({\n method: \"POST\",\n pathname: this.path(\"/agent-reply\"),\n body: { text },\n signal,\n allowEmptyJson: true,\n })\n }\n\n /**\n * End the review. Retried with a stable idempotency key; a NOT_FOUND (the\n * review is already ended — whether by a prior call or by a retry landing after\n * the first attempt succeeded) is mapped to a successful `{ ok, status:\"ended\" }`\n * rather than surfaced as a 404 (contract v2.2 §1.1).\n */\n async end(signal?: AbortSignal): Promise<ArtifactEndResponse> {\n const idempotencyKey = randomUUID()\n try {\n return await this.withRetry(\n () =>\n this.requestOnce<ArtifactEndResponse>({\n method: \"POST\",\n pathname: this.path(\"/end\"),\n signal,\n idempotencyKey,\n allowEmptyJson: true,\n }),\n TRANSIENT_RETRIES,\n signal,\n )\n } catch (err) {\n if (err instanceof ArtifactError && err.code === \"NOT_FOUND\") {\n return { ok: true, status: \"ended\" }\n }\n throw err\n }\n }\n\n /**\n * FROZEN legacy long-poll (contract v2.2 §1). Old payload shape,\n * comment-equivalent only. New agents use `awaitEvents`. Single request per\n * call (the caller-facing tool owns the bounded re-poll budget).\n */\n poll(timeoutMsHint?: number, signal?: AbortSignal): Promise<ArtifactPollResponse> {\n return this.requestOnce<ArtifactPollResponse>({\n method: \"GET\",\n pathname: this.path(\"/poll\"),\n signal,\n timeoutMsHint,\n })\n }\n\n private path(suffix: string): string {\n return `/api/artifact/${encodeURIComponent(this.sessionId)}${suffix}`\n }\n\n private async withRetry<T>(\n fn: () => Promise<T>,\n retries: number,\n signal?: AbortSignal,\n retryableCodes: ReadonlySet<ArtifactErrorCode> = DEFAULT_RETRYABLE_CODES,\n ): Promise<T> {\n let attempt = 0\n for (;;) {\n try {\n return await fn()\n } catch (err) {\n attempt += 1\n const transient =\n err instanceof ArtifactError && err.retryable && retryableCodes.has(err.code)\n // Never retry once the caller cancelled, and never exceed the budget.\n if (!transient || attempt > retries || signal?.aborted) throw err\n const base = this.retryBaseMs\n const delay = base <= 0 ? 0 : Math.round(base * 2 ** (attempt - 1) * (0.5 + Math.random() * 0.5))\n await sleep(delay, signal)\n }\n }\n }\n\n private async requestOnce<T>(o: {\n method: \"GET\" | \"POST\"\n pathname: string\n body?: unknown\n signal?: AbortSignal\n timeoutMsHint?: number\n allowEmptyJson?: boolean\n idempotencyKey?: string\n query?: Record<string, string | undefined>\n }): Promise<T> {\n let url: URL\n try {\n url = new URL(o.pathname, `${this.baseUrl}/`)\n } catch (err) {\n throw new ArtifactError({\n code: \"UNREACHABLE\",\n message: \"artifact API base URL is invalid\",\n retryable: false,\n detail: err,\n })\n }\n if (o.query) {\n for (const [key, value] of Object.entries(o.query)) {\n if (value !== undefined) url.searchParams.set(key, value)\n }\n }\n const timeout = combineSignalAndTimeout(o.signal, o.timeoutMsHint)\n\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n }\n if (o.body !== undefined) headers[\"Content-Type\"] = \"application/json\"\n // A stable idempotency key survives across retries so the server can dedupe\n // a retry that lands after the first attempt already succeeded.\n if (o.idempotencyKey) headers[\"Idempotency-Key\"] = o.idempotencyKey\n const init: RequestInit = {\n method: o.method,\n headers,\n body: o.body === undefined ? undefined : JSON.stringify(o.body),\n redirect: \"error\",\n signal: timeout.signal,\n }\n if (this.insecureTLS) {\n // Self-signed direct-HTTPS ai-or-die on loopback: relax verification for\n // THIS request only (Bun `tls` / Node undici `dispatcher`). Bearer +\n // `redirect:\"error\"` still bound the token to the pinned loopback origin.\n applyInsecureTls(init as unknown as Record<string, unknown>)\n }\n const response = await this.fetchFn(url.toString(), init)\n\n if (!response.ok) {\n throw await mapHttpError(response)\n }\n\n // Read the body INSIDE the timeout scope (cleanup is in the finally below),\n // so a server that sends headers then hangs on the body is bounded by the\n // same deadline as the connect/headers phase — not left to block forever.\n let text: string\n try {\n text = await response.text()\n } catch (err) {\n if (isAbortLike(err)) throw mapNetworkError(err)\n throw new ArtifactError({\n code: \"INVALID_RESPONSE\",\n message: \"artifact API response body could not be read\",\n retryable: false,\n detail: err,\n })\n }\n if (!text && o.allowEmptyJson) return {} as T\n try {\n return JSON.parse(text) as T\n } catch (err) {\n throw new ArtifactError({\n code: \"INVALID_RESPONSE\",\n message: \"artifact API returned a non-JSON response\",\n retryable: false,\n detail: err,\n })\n }\n } catch (err) {\n // ArtifactErrors (HTTP status, parse) are already classified — pass through.\n // Anything else is a transport failure (connect/abort/body stall) → map it.\n if (err instanceof ArtifactError) throw err\n throw mapNetworkError(err)\n } finally {\n timeout.cleanup()\n }\n }\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve, reject) => {\n const onAbort = (): void => {\n clearTimeout(timer)\n reject(new DOMException(\"retry backoff aborted\", \"AbortError\"))\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort)\n resolve()\n }, ms)\n if (signal?.aborted) {\n onAbort()\n return\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true })\n })\n}\n\nfunction combineSignalAndTimeout(\n signal: AbortSignal | undefined,\n timeoutMsHint: number | undefined,\n): { signal?: AbortSignal; cleanup: () => void } {\n const timeoutMs =\n typeof timeoutMsHint === \"number\" && Number.isFinite(timeoutMsHint) && timeoutMsHint > 0\n ? timeoutMsHint\n : undefined\n if (timeoutMs === undefined) return { signal, cleanup: () => {} }\n\n const controller = new AbortController()\n const abortFromCaller = (): void => {\n try {\n controller.abort(signal?.reason)\n } catch {\n controller.abort()\n }\n }\n if (signal?.aborted) abortFromCaller()\n signal?.addEventListener(\"abort\", abortFromCaller, { once: true })\n const timer = setTimeout(() => {\n try {\n controller.abort(new DOMException(\"artifact API request timed out\", \"TimeoutError\"))\n } catch {\n controller.abort()\n }\n }, timeoutMs)\n\n return {\n signal: controller.signal,\n cleanup: () => {\n clearTimeout(timer)\n signal?.removeEventListener(\"abort\", abortFromCaller)\n },\n }\n}\n\nasync function mapHttpError(response: Response): Promise<ArtifactError> {\n const detail = await readErrorDetail(response)\n const upstreamMessage = detailToMessage(detail)\n const suffix = upstreamMessage ? `: ${upstreamMessage}` : \"\"\n if (response.status === 401 || response.status === 403) {\n return new ArtifactError({\n code: \"AUTH_FAILED\",\n message: `artifact API authentication failed (${response.status})${suffix}`,\n retryable: false,\n status: response.status,\n detail,\n })\n }\n if (response.status === 404) {\n return new ArtifactError({\n code: \"NOT_FOUND\",\n message: `artifact session or resource not found (404)${suffix}`,\n retryable: false,\n status: response.status,\n detail,\n })\n }\n if (response.status === 408 || response.status === 504) {\n return new ArtifactError({\n code: \"TIMEOUT\",\n message: `artifact API request timed out (${response.status})${suffix}`,\n retryable: true,\n status: response.status,\n detail,\n })\n }\n // A 400 tagged with an INVALID_REQUEST code (e.g. update html-without-file) is a\n // distinct, non-retryable client error — not the generic upstream bucket.\n if (response.status === 400 && detailToCode(detail)?.toUpperCase() === \"INVALID_REQUEST\") {\n return new ArtifactError({\n code: \"INVALID_REQUEST\",\n message: `artifact API rejected the request (400)${suffix}`,\n retryable: false,\n status: response.status,\n detail,\n })\n }\n return new ArtifactError({\n code: \"UPSTREAM_ERROR\",\n message: `artifact API returned HTTP ${response.status}${suffix}`,\n retryable: response.status === 429 || response.status >= 500,\n status: response.status,\n detail,\n })\n}\n\nfunction mapNetworkError(err: unknown): ArtifactError {\n if (isAbortLike(err)) {\n return new ArtifactError({\n code: \"TIMEOUT\",\n message: \"artifact API request timed out or was aborted\",\n retryable: true,\n detail: err,\n })\n }\n const message = err instanceof Error ? err.message : String(err)\n return new ArtifactError({\n code: \"UNREACHABLE\",\n message: `artifact API unreachable: ${message}`,\n retryable: true,\n detail: err,\n })\n}\n\nasync function readErrorDetail(response: Response): Promise<unknown> {\n const text = await response.text().catch(() => \"\")\n if (!text) return undefined\n try {\n return JSON.parse(text) as unknown\n } catch {\n return text\n }\n}\n\nfunction detailToMessage(detail: unknown): string | undefined {\n if (typeof detail === \"string\") return detail\n if (typeof detail !== \"object\" || detail === null) return undefined\n const record = detail as Record<string, unknown>\n const error = record.error\n if (typeof error === \"string\") return error\n if (typeof error === \"object\" && error !== null) {\n const errorRecord = error as Record<string, unknown>\n if (typeof errorRecord.message === \"string\") return errorRecord.message\n if (typeof errorRecord.code === \"string\") return errorRecord.code\n }\n if (typeof record.message === \"string\") return record.message\n return undefined\n}\n\n/** Extract a machine `code` from an upstream error body (`{error:{code}}` or\n * `{code}`) so a tagged 400 can be classified (INVALID_REQUEST). */\nfunction detailToCode(detail: unknown): string | undefined {\n if (typeof detail !== \"object\" || detail === null) return undefined\n const record = detail as Record<string, unknown>\n const error = record.error\n if (typeof error === \"object\" && error !== null) {\n const code = (error as Record<string, unknown>).code\n if (typeof code === \"string\") return code\n }\n if (typeof record.code === \"string\") return record.code\n return undefined\n}\n\nfunction isAbortLike(err: unknown): boolean {\n return err instanceof Error && (err.name === \"AbortError\" || err.name === \"TimeoutError\")\n}\n"],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import { Dt as DEFAULT_CODEX_MODEL, Ot as DEFAULT_CODEX_MODEL_FALLBACKS, _t as provisionAndIndexColbert, bt as warmTreeSitterPool, gt as hasSupportedBrowserInstalled, ht as provisionBrowserAssets, w as toolbeltEnabled } from "./peer-mcp-personas-BDet588n.js";
2
+ import { O as state, d as resolveModel, u as resolveCodexModel } from "./token-R2ocFC3M.js";
3
+ import { a as sharedServerArgs, c as listModelsForEndpoint, i as setupAndServe, l as runSelfUpdate, n as getCodexEnvVars, o as startKeepAwake, r as parseSharedArgs } from "./server-setup-DcO9qvN3.js";
4
+ import { i as launchChild, t as provisionToolbelt } from "./provision-DSkPa5hY.js";
5
+ import { t as enableFileLogging } from "./file-log-reporter-IFQcL5IK.js";
6
+ import { defineCommand } from "citty";
7
+ import consola from "consola";
8
+ import process from "node:process";
9
+ //#region src/codex.ts
10
+ const codex = defineCommand({
11
+ meta: {
12
+ name: "codex",
13
+ description: "Start the proxy server and launch Codex CLI"
14
+ },
15
+ args: {
16
+ ...sharedServerArgs,
17
+ model: {
18
+ alias: "m",
19
+ type: "string",
20
+ description: "Override the default model for Codex CLI"
21
+ }
22
+ },
23
+ async run({ args }) {
24
+ if (!process.stdout.isTTY) {
25
+ consola.error("The codex subcommand requires a TTY (interactive terminal).");
26
+ process.exit(1);
27
+ }
28
+ const parsed = parseSharedArgs(args);
29
+ let server;
30
+ let serverUrl;
31
+ try {
32
+ const result = await setupAndServe({
33
+ ...parsed,
34
+ port: parsed.port,
35
+ silent: true
36
+ });
37
+ server = result.server;
38
+ serverUrl = result.serverUrl;
39
+ } catch (error) {
40
+ consola.error("Failed to start server:", error instanceof Error ? error.message : error);
41
+ process.exit(1);
42
+ }
43
+ runSelfUpdate({ selfUpdate: args["self-update"] !== false });
44
+ if (toolbeltEnabled()) provisionToolbelt().catch(() => {});
45
+ provisionAndIndexColbert();
46
+ warmTreeSitterPool();
47
+ startKeepAwake();
48
+ if ((state.browseEnabled || process.env.GH_ROUTER_ENABLE_BROWSE === "1") && hasSupportedBrowserInstalled()) provisionBrowserAssets().catch((err) => consola.debug("Browser extension provisioning failed:", err));
49
+ const usingDefault = !args.model;
50
+ const requestedModel = args.model ?? "gpt-5.6-sol";
51
+ enableFileLogging();
52
+ let codexModel = resolveCodexModel(requestedModel);
53
+ if (codexModel !== requestedModel) consola.info(`Model "${requestedModel}" resolved to "${codexModel}"`);
54
+ if (usingDefault && state.models) {
55
+ const inCache = (id) => state.models?.data.some((m) => m.id === id) ?? false;
56
+ const firstPresent = [DEFAULT_CODEX_MODEL, ...DEFAULT_CODEX_MODEL_FALLBACKS].map((id) => resolveModel(id)).find((id) => inCache(id));
57
+ if (firstPresent) {
58
+ if (firstPresent !== codexModel) consola.info(`Default model "${DEFAULT_CODEX_MODEL}" not in your Copilot model list; falling back to "${firstPresent}".`);
59
+ codexModel = firstPresent;
60
+ }
61
+ }
62
+ const modelEntry = state.models?.data.find((m) => m.id === codexModel);
63
+ if (!modelEntry) {
64
+ const available = listModelsForEndpoint("/responses");
65
+ consola.warn(`Model "${codexModel}" not found. Available codex models: ${available.join(", ")}`);
66
+ } else {
67
+ const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens;
68
+ if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`);
69
+ }
70
+ process.stderr.write(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...\n`);
71
+ const envVars = getCodexEnvVars(serverUrl);
72
+ const extraArgs = args._ ?? [];
73
+ launchChild({
74
+ kind: "codex",
75
+ envVars,
76
+ extraArgs,
77
+ model: codexModel,
78
+ serverUrl
79
+ }, server);
80
+ }
81
+ });
82
+ //#endregion
83
+ export { codex };
84
+
85
+ //# sourceMappingURL=codex-ST21X3WF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-ST21X3WF.js","names":[],"sources":["../src/codex.ts"],"sourcesContent":["import process from \"node:process\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { enableFileLogging } from \"./lib/file-log-reporter\"\nimport { launchChild } from \"./lib/launch\"\nimport { listModelsForEndpoint } from \"./lib/model-validation\"\nimport {\n DEFAULT_CODEX_MODEL,\n DEFAULT_CODEX_MODEL_FALLBACKS,\n} from \"./lib/port\"\nimport {\n getCodexEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport { runSelfUpdate } from \"./lib/self-update\"\nimport { state } from \"./lib/state\"\nimport { toolbeltEnabled } from \"./lib/toolbelt\"\nimport { provisionToolbelt } from \"./lib/toolbelt/provision\"\nimport { provisionAndIndexColbert } from \"./lib/colbert\"\nimport { startKeepAwake } from \"./lib/keep-awake\"\nimport { warmTreeSitterPool } from \"./lib/tree-sitter-pool/pool\"\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport { hasSupportedBrowserInstalled } from \"./lib/browser-mcp/browser-detect\"\nimport { resolveCodexModel, resolveModel } from \"./lib/utils\"\n\nexport const codex = defineCommand({\n meta: {\n name: \"codex\",\n description: \"Start the proxy server and launch Codex CLI\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default model for Codex CLI\",\n },\n },\n async run({ args }) {\n if (!process.stdout.isTTY) {\n consola.error(\"The codex subcommand requires a TTY (interactive terminal).\")\n process.exit(1)\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n let server: Awaited<ReturnType<typeof setupAndServe>>[\"server\"]\n let serverUrl: string\n try {\n const result = await setupAndServe({\n ...parsed,\n port: parsed.port, // undefined = random port\n silent: true,\n })\n server = result.server\n serverUrl = result.serverUrl\n } catch (error) {\n consola.error(\"Failed to start server:\", error instanceof Error ? error.message : error)\n process.exit(1)\n }\n\n // Best-effort self-update (detached, applies next launch).\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Materialize the LLM toolbelt in the background (PATH prepend is in\n // getCodexEnvVars). Best-effort; never blocks launch.\n if (toolbeltEnabled()) {\n void provisionToolbelt().catch(() => {})\n }\n\n // Best-effort ColBERT semantic-search provision + background index of\n // the launch cwd. ON by default; never blocks launch, never throws.\n void provisionAndIndexColbert()\n\n // Best-effort, bounded launch warm-up: ready one tree-sitter worker.\n // Opt out with GH_ROUTER_DISABLE_TS_POOL_WARMUP=1.\n void warmTreeSitterPool()\n\n // Best-effort: keep the machine awake while the proxy/Codex session\n // runs (win32 default-on; opt out GH_ROUTER_DISABLE_KEEP_AWAKE=1).\n // Self-registered SIGINT/SIGTERM/exit reaper releases the assertion.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the\n // stable app-dir and stamp the running version. Gated inline (browse\n // opt-in + a supported browser) rather than via browserToolsEnabled()\n // so the codex command path doesn't eagerly pull the compressor /\n // worker-agent graph. Never blocks launch, never throws.\n const browseOptIn =\n state.browseEnabled || process.env.GH_ROUTER_ENABLE_BROWSE === \"1\"\n if (browseOptIn && hasSupportedBrowserInstalled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n const usingDefault = !args.model\n const requestedModel = args.model ?? DEFAULT_CODEX_MODEL\n\n // Resolve model before printing success message (so we show the actual model)\n // but enable file logging first so resolution warnings go to file, not terminal\n enableFileLogging()\n\n let codexModel = resolveCodexModel(requestedModel)\n if (codexModel !== requestedModel) {\n consola.info(`Model \"${requestedModel}\" resolved to \"${codexModel}\"`)\n }\n\n // For the implicit-default path only: honor the explicit fallback order\n // (DEFAULT_CODEX_MODEL, then DEFAULT_CODEX_MODEL_FALLBACKS led by gpt-5.5)\n // by cache presence BEFORE resolveCodexModel's codex-slug-preferring\n // \"best /responses model\" safety net. `resolveModel` (NOT resolveCodexModel)\n // walks the chain, so the codex-slug preference can't shadow an\n // earlier-in-chain named fallback (e.g. gpt-5.5) that IS present. The\n // resolveCodexModel result above stays as the FINAL safety net, used only\n // when none of the named chain entries is in the catalog.\n if (usingDefault && state.models) {\n const inCache = (id: string) =>\n state.models?.data.some((m) => m.id === id) ?? false\n const firstPresent = [DEFAULT_CODEX_MODEL, ...DEFAULT_CODEX_MODEL_FALLBACKS]\n .map((id) => resolveModel(id))\n .find((id) => inCache(id))\n if (firstPresent) {\n if (firstPresent !== codexModel) {\n consola.info(\n `Default model \"${DEFAULT_CODEX_MODEL}\" not in your Copilot model list; falling back to \"${firstPresent}\".`,\n )\n }\n codexModel = firstPresent\n }\n }\n\n // Validate model exists in Copilot model list\n const modelEntry = state.models?.data.find((m) => m.id === codexModel)\n if (!modelEntry) {\n const available = listModelsForEndpoint(\"/responses\")\n consola.warn(\n `Model \"${codexModel}\" not found. Available codex models: ${available.join(\", \")}`,\n )\n } else {\n const ctx = modelEntry.capabilities?.limits?.max_context_window_tokens\n if (ctx) consola.info(`Model context window: ${ctx.toLocaleString()} tokens`)\n }\n\n // Print to stderr directly — consola's terminal reporter is already gone\n process.stderr.write(`Server ready on ${serverUrl}, launching Codex CLI (${codexModel})...\\n`)\n\n const envVars = getCodexEnvVars(serverUrl)\n const extraArgs = ((args as unknown as Record<string, unknown>)._ as string[]) ?? []\n\n launchChild(\n {\n kind: \"codex\",\n envVars,\n extraArgs,\n model: codexModel,\n serverUrl,\n },\n server,\n )\n },\n})\n"],"mappings":";;;;;;;;;AA6BA,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;CACf;CACA,MAAM;EACJ,GAAG;EACH,OAAO;GACL,OAAO;GACP,MAAM;GACN,aAAa;EACf;CACF;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,IAAI,CAAC,QAAQ,OAAO,OAAO;GACzB,QAAQ,MAAM,6DAA6D;GAC3E,QAAQ,KAAK,CAAC;EAChB;EAEA,MAAM,SAAS,gBAAgB,IAA0C;EAEzE,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,MAAM,cAAc;IACjC,GAAG;IACH,MAAM,OAAO;IACb,QAAQ;GACV,CAAC;GACD,SAAS,OAAO;GAChB,YAAY,OAAO;EACrB,SAAS,OAAO;GACd,QAAQ,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,KAAK;GACvF,QAAQ,KAAK,CAAC;EAChB;EAGA,cAAmB,EAAE,YAAY,KAAK,mBAAmB,MAAM,CAAC;EAIhE,IAAI,gBAAgB,GAClB,kBAAuB,CAAC,CAAC,YAAY,CAAC,CAAC;EAKzC,yBAA8B;EAI9B,mBAAwB;EAKxB,eAAe;EASf,KADE,MAAM,iBAAiB,QAAQ,IAAI,4BAA4B,QAC9C,6BAA6B,GAC9C,uBAA4B,CAAC,CAAC,OAAO,QACnC,QAAQ,MAAM,0CAA0C,GAAG,CAC7D;EAGF,MAAM,eAAe,CAAC,KAAK;EAC3B,MAAM,iBAAiB,KAAK,SAAA;EAI5B,kBAAkB;EAElB,IAAI,aAAa,kBAAkB,cAAc;EACjD,IAAI,eAAe,gBACjB,QAAQ,KAAK,UAAU,eAAe,iBAAiB,WAAW,EAAE;EAWtE,IAAI,gBAAgB,MAAM,QAAQ;GAChC,MAAM,WAAW,OACf,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,EAAE,KAAK;GACjD,MAAM,eAAe,CAAC,qBAAqB,GAAG,6BAA6B,CAAC,CACzE,KAAK,OAAO,aAAa,EAAE,CAAC,CAAC,CAC7B,MAAM,OAAO,QAAQ,EAAE,CAAC;GAC3B,IAAI,cAAc;IAChB,IAAI,iBAAiB,YACnB,QAAQ,KACN,kBAAkB,oBAAoB,qDAAqD,aAAa,GAC1G;IAEF,aAAa;GACf;EACF;EAGA,MAAM,aAAa,MAAM,QAAQ,KAAK,MAAM,MAAM,EAAE,OAAO,UAAU;EACrE,IAAI,CAAC,YAAY;GACf,MAAM,YAAY,sBAAsB,YAAY;GACpD,QAAQ,KACN,UAAU,WAAW,uCAAuC,UAAU,KAAK,IAAI,GACjF;EACF,OAAO;GACL,MAAM,MAAM,WAAW,cAAc,QAAQ;GAC7C,IAAI,KAAK,QAAQ,KAAK,yBAAyB,IAAI,eAAe,EAAE,QAAQ;EAC9E;EAGA,QAAQ,OAAO,MAAM,mBAAmB,UAAU,yBAAyB,WAAW,OAAO;EAE7F,MAAM,UAAU,gBAAgB,SAAS;EACzC,MAAM,YAAc,KAA4C,KAAkB,CAAC;EAEnF,YACE;GACE,MAAM;GACN;GACA;GACA,OAAO;GACP;EACF,GACA,MACF;CACF;AACF,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { t as PATHS } from "./paths-C1cLZ7Ga.js";
2
+ import { defineCommand } from "citty";
3
+ import consola from "consola";
4
+ import fs from "node:fs/promises";
5
+ import os from "node:os";
6
+ //#region src/debug.ts
7
+ async function getPackageVersion() {
8
+ try {
9
+ const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
10
+ return JSON.parse(await fs.readFile(packageJsonPath)).version;
11
+ } catch {
12
+ return "unknown";
13
+ }
14
+ }
15
+ function getRuntimeInfo() {
16
+ const isBun = typeof Bun !== "undefined";
17
+ return {
18
+ name: isBun ? "bun" : "node",
19
+ version: isBun ? Bun.version : process.version.slice(1),
20
+ platform: os.platform(),
21
+ arch: os.arch()
22
+ };
23
+ }
24
+ async function checkTokenExists() {
25
+ try {
26
+ if (!(await fs.stat(PATHS.GITHUB_TOKEN_PATH)).isFile()) return false;
27
+ return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ async function getDebugInfo() {
33
+ const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
34
+ return {
35
+ version,
36
+ runtime: getRuntimeInfo(),
37
+ paths: {
38
+ APP_DIR: PATHS.APP_DIR,
39
+ GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
40
+ },
41
+ tokenExists
42
+ };
43
+ }
44
+ function printDebugInfoPlain(info) {
45
+ consola.info(`github-router debug
46
+
47
+ Version: ${info.version}
48
+ Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
49
+
50
+ Paths:
51
+ - APP_DIR: ${info.paths.APP_DIR}
52
+ - GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
53
+
54
+ Token exists: ${info.tokenExists ? "Yes" : "No"}`);
55
+ }
56
+ function printDebugInfoJson(info) {
57
+ console.log(JSON.stringify(info, null, 2));
58
+ }
59
+ async function runDebug(options) {
60
+ const debugInfo = await getDebugInfo();
61
+ if (options.json) printDebugInfoJson(debugInfo);
62
+ else printDebugInfoPlain(debugInfo);
63
+ }
64
+ const debug = defineCommand({
65
+ meta: {
66
+ name: "debug",
67
+ description: "Print debug information about the application"
68
+ },
69
+ args: { json: {
70
+ type: "boolean",
71
+ default: false,
72
+ description: "Output debug information as JSON"
73
+ } },
74
+ run({ args }) {
75
+ return runDebug({ json: args.json });
76
+ }
77
+ });
78
+ //#endregion
79
+ export { debug };
80
+
81
+ //# sourceMappingURL=debug-CjmE9sgG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-CjmE9sgG.js","names":[],"sources":["../src/debug.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\n\nimport { PATHS } from \"./lib/paths\"\n\ninterface DebugInfo {\n version: string\n runtime: {\n name: string\n version: string\n platform: string\n arch: string\n }\n paths: {\n APP_DIR: string\n GITHUB_TOKEN_PATH: string\n }\n tokenExists: boolean\n}\n\ninterface RunDebugOptions {\n json: boolean\n}\n\nasync function getPackageVersion(): Promise<string> {\n try {\n const packageJsonPath = new URL(\"../package.json\", import.meta.url).pathname\n // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md\n // JSON.parse() can actually parse buffers\n const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as {\n version: string\n }\n return packageJson.version\n } catch {\n return \"unknown\"\n }\n}\n\nfunction getRuntimeInfo() {\n const isBun = typeof Bun !== \"undefined\"\n\n return {\n name: isBun ? \"bun\" : \"node\",\n version: isBun ? Bun.version : process.version.slice(1),\n platform: os.platform(),\n arch: os.arch(),\n }\n}\n\nasync function checkTokenExists(): Promise<boolean> {\n try {\n const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH)\n if (!stats.isFile()) return false\n\n const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, \"utf8\")\n return content.trim().length > 0\n } catch {\n return false\n }\n}\n\nasync function getDebugInfo(): Promise<DebugInfo> {\n const [version, tokenExists] = await Promise.all([\n getPackageVersion(),\n checkTokenExists(),\n ])\n\n return {\n version,\n runtime: getRuntimeInfo(),\n paths: {\n APP_DIR: PATHS.APP_DIR,\n GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,\n },\n tokenExists,\n }\n}\n\nfunction printDebugInfoPlain(info: DebugInfo): void {\n consola.info(`github-router debug\n\nVersion: ${info.version}\nRuntime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})\n\nPaths:\n- APP_DIR: ${info.paths.APP_DIR}\n- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}\n\nToken exists: ${info.tokenExists ? \"Yes\" : \"No\"}`)\n}\n\nfunction printDebugInfoJson(info: DebugInfo): void {\n console.log(JSON.stringify(info, null, 2))\n}\n\nexport async function runDebug(options: RunDebugOptions): Promise<void> {\n const debugInfo = await getDebugInfo()\n\n if (options.json) {\n printDebugInfoJson(debugInfo)\n } else {\n printDebugInfoPlain(debugInfo)\n }\n}\n\nexport const debug = defineCommand({\n meta: {\n name: \"debug\",\n description: \"Print debug information about the application\",\n },\n args: {\n json: {\n type: \"boolean\",\n default: false,\n description: \"Output debug information as JSON\",\n },\n },\n run({ args }) {\n return runDebug({\n json: args.json,\n })\n },\n})\n"],"mappings":";;;;;;AA4BA,eAAe,oBAAqC;CAClD,IAAI;EACF,MAAM,kBAAkB,IAAI,IAAI,mBAAmB,YAAY,GAAG,CAAC,CAAC;EAMpE,OAHoB,KAAK,MAAM,MAAM,GAAG,SAAS,eAAe,CAG/C,CAAC,CAAC;CACrB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,iBAAiB;CACxB,MAAM,QAAQ,OAAO,QAAQ;CAE7B,OAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,SAAS,QAAQ,IAAI,UAAU,QAAQ,QAAQ,MAAM,CAAC;EACtD,UAAU,GAAG,SAAS;EACtB,MAAM,GAAG,KAAK;CAChB;AACF;AAEA,eAAe,mBAAqC;CAClD,IAAI;EAEF,IAAI,EAAC,MADe,GAAG,KAAK,MAAM,iBAAiB,EAAA,CACxC,OAAO,GAAG,OAAO;EAG5B,QAAO,MADe,GAAG,SAAS,MAAM,mBAAmB,MAAM,EAAA,CAClD,KAAK,CAAC,CAAC,SAAS;CACjC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,eAAe,eAAmC;CAChD,MAAM,CAAC,SAAS,eAAe,MAAM,QAAQ,IAAI,CAC/C,kBAAkB,GAClB,iBAAiB,CACnB,CAAC;CAED,OAAO;EACL;EACA,SAAS,eAAe;EACxB,OAAO;GACL,SAAS,MAAM;GACf,mBAAmB,MAAM;EAC3B;EACA;CACF;AACF;AAEA,SAAS,oBAAoB,MAAuB;CAClD,QAAQ,KAAK;;WAEJ,KAAK,QAAQ;WACb,KAAK,QAAQ,KAAK,GAAG,KAAK,QAAQ,QAAQ,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,QAAQ,KAAK;;;aAGvF,KAAK,MAAM,QAAQ;uBACT,KAAK,MAAM,kBAAkB;;gBAEpC,KAAK,cAAc,QAAQ,MAAM;AACjD;AAEA,SAAS,mBAAmB,MAAuB;CACjD,QAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAC3C;AAEA,eAAsB,SAAS,SAAyC;CACtE,MAAM,YAAY,MAAM,aAAa;CAErC,IAAI,QAAQ,MACV,mBAAmB,SAAS;MAE5B,oBAAoB,SAAS;AAEjC;AAEA,MAAa,QAAQ,cAAc;CACjC,MAAM;EACJ,MAAM;EACN,aAAa;CACf;CACA,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aAAa;CACf,EACF;CACA,IAAI,EAAE,QAAQ;EACZ,OAAO,SAAS,EACd,MAAM,KAAK,KACb,CAAC;CACH;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { b as runWorkerAgent } from "./peer-mcp-personas-BDet588n.js";
2
+ export { runWorkerAgent };
@@ -1,10 +1,7 @@
1
- import { t as PATHS } from "./paths-BjTMI_xK.js";
2
- import { randomUUID } from "node:crypto";
3
- import fs from "node:fs/promises";
1
+ import { existsSync } from "node:fs";
4
2
  import path from "node:path";
5
3
  import process from "node:process";
6
4
  import { spawn, spawnSync } from "node:child_process";
7
- import { existsSync } from "node:fs";
8
5
  //#region src/lib/exec.ts
9
6
  /**
10
7
  * Windows-safe, injection-safe command execution helpers.
@@ -477,130 +474,6 @@ function killChildProcessTree(child, opts = { detachedGroup: false }) {
477
474
  }, graceMs).unref?.();
478
475
  }
479
476
  //#endregion
480
- //#region src/lib/colbert/lifecycle.ts
481
- let _instanceUuid = null;
482
- /**
483
- * Stable UUID4 generated once per proxy process. Written into the
484
- * sidecar metadata `ownerInstanceId` so the boot sweep can tell "this
485
- * proxy's still-live build" from "a stranded `building` entry from a
486
- * prior proxy whose PID got recycled" (Docker PID-1 across restarts).
487
- */
488
- function getColbertInstanceUuid() {
489
- if (_instanceUuid === null) _instanceUuid = randomUUID();
490
- return _instanceUuid;
491
- }
492
- const _liveChildren = /* @__PURE__ */ new Set();
493
- /**
494
- * Register a freshly-spawned colgrep child so the exit sweep can reap
495
- * it. The runner also removes it on natural close via `untrackChild`.
496
- */
497
- function trackChild(child) {
498
- _liveChildren.add(child);
499
- child.once("close", () => _liveChildren.delete(child));
500
- child.once("error", () => _liveChildren.delete(child));
501
- }
502
- /**
503
- * Synchronous best-effort tree-kill of every tracked child. Called from
504
- * the signal/exit handlers. After killing, the set is cleared so a
505
- * second call is a no-op.
506
- */
507
- function sweepLiveChildren() {
508
- const isWin = process.platform === "win32";
509
- for (const child of _liveChildren) try {
510
- killManagedTree(child, isWin);
511
- } catch {}
512
- _liveChildren.clear();
513
- }
514
- let _registered = false;
515
- let _exitHandler = null;
516
- let _sigintHandler = null;
517
- let _sigtermHandler = null;
518
- /**
519
- * Wire SIGINT/SIGTERM/exit handlers that tree-kill every tracked
520
- * colgrep child. Idempotent — subsequent calls are a no-op (we never
521
- * leak listeners). The signal handlers re-raise after sweeping so Node's
522
- * default terminate-on-signal behavior is restored (otherwise attaching
523
- * a listener cancels the default and Ctrl-C would clean but not exit).
524
- */
525
- function registerColbertExitHandlers() {
526
- if (_registered) return;
527
- _registered = true;
528
- _exitHandler = () => sweepLiveChildren();
529
- _sigintHandler = () => {
530
- sweepLiveChildren();
531
- if (_sigintHandler) process.off("SIGINT", _sigintHandler);
532
- process.kill(process.pid, "SIGINT");
533
- };
534
- _sigtermHandler = () => {
535
- sweepLiveChildren();
536
- if (_sigtermHandler) process.off("SIGTERM", _sigtermHandler);
537
- process.kill(process.pid, "SIGTERM");
538
- };
539
- process.on("SIGINT", _sigintHandler);
540
- process.on("SIGTERM", _sigtermHandler);
541
- process.on("exit", _exitHandler);
542
- }
543
- /**
544
- * True iff `pid` names a live process. `process.kill(pid, 0)` probes
545
- * existence without signalling; `EPERM` means the process exists but is
546
- * owned by another user (still alive). Exported so the per-query freshness
547
- * verdict can mirror the boot sweep's liveness check.
548
- */
549
- function isPidAlive(pid) {
550
- if (!Number.isInteger(pid) || pid <= 0) return false;
551
- try {
552
- process.kill(pid, 0);
553
- return true;
554
- } catch (err) {
555
- if (err.code === "EPERM") return true;
556
- return false;
557
- }
558
- }
559
- /**
560
- * Boot-time sweep. Walks `.gh-router-meta/*.json`; any entry stuck in
561
- * `status:"building"` whose `buildPid` is DEAD is a crashed-build
562
- * escapee → reset to `status:"failed"` so the next search re-kicks a
563
- * build instead of routing to a never-finishing one.
564
- *
565
- * It NEVER kills anything: a live PID matching a stale `buildPid` from a
566
- * prior boot may be a recycled PID belonging to an unrelated process, so
567
- * the boot sweep only RECLASSIFIES metadata. The in-memory ledger (this
568
- * run's spawns) is the only thing the SIGINT/SIGTERM handler ever kills.
569
- *
570
- * Best-effort; never throws (wrapped by the caller in `ensurePaths`).
571
- */
572
- async function sweepStaleColbertMetaAtBoot() {
573
- const metaDir = PATHS.COLBERT_META_DIR;
574
- let names;
575
- try {
576
- names = await fs.readdir(metaDir);
577
- } catch {
578
- return;
579
- }
580
- for (const name of names) {
581
- if (!name.endsWith(".json")) continue;
582
- const file = path.join(metaDir, name);
583
- let meta;
584
- try {
585
- meta = JSON.parse(await fs.readFile(file, "utf8"));
586
- } catch {
587
- continue;
588
- }
589
- if (meta.status !== "building") continue;
590
- const buildPid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
591
- if (buildPid > 0 && isPidAlive(buildPid)) continue;
592
- meta.status = "failed";
593
- meta.failureClass = "crashed";
594
- const tmp = `${file}.${process.pid}.tmp`;
595
- try {
596
- await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
597
- await fs.rename(tmp, file);
598
- } catch {
599
- await fs.rm(tmp, { force: true }).catch(() => {});
600
- }
601
- }
602
- }
603
- //#endregion
604
- export { sweepStaleColbertMetaAtBoot as a, killManagedTree as c, resolveExecutable as d, runCommandCapture as f, sweepLiveChildren as i, parseBoolEnv as l, runManagedExeCapture as m, isPidAlive as n, trackChild as o, runCommandVoid as p, registerColbertExitHandlers as r, killChildProcessTree as s, getColbertInstanceUuid as t, parseIntEnv as u };
477
+ export { resolveExecutable as a, runManagedExeCapture as c, parseIntEnv as i, killManagedTree as n, runCommandCapture as o, parseBoolEnv as r, runCommandVoid as s, killChildProcessTree as t };
605
478
 
606
- //# sourceMappingURL=lifecycle-DHZsVN8j.js.map
479
+ //# sourceMappingURL=exec-DTO2oin0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-DTO2oin0.js","names":[],"sources":["../src/lib/exec.ts"],"sourcesContent":["/**\n * Windows-safe, injection-safe command execution helpers.\n *\n * Why this module exists: on Windows, npm-installed CLIs (`claude`,\n * `npm`, `codex`) are `.cmd`/`.bat` shims. Node's `execFile`/`spawn`\n * with `shell:false` cannot launch them (CreateProcess only resolves\n * `.exe`), so callers must go through `cmd.exe` (`shell:true`). That in\n * turn opens two hazards this module closes:\n *\n * 1. **Metacharacter injection** (`& | < > ^ ( ) ! %`). A naive\n * \"quote only tokens with spaces\" scheme lets `pkg@latest&calc`\n * run `calc` as a second command. `buildExecInvocation` applies\n * real cmd.exe quoting (argv-quote + caret-escape) and fails\n * closed on `%` (which cannot be reliably escaped on the cmd\n * command line).\n *\n * 2. **CWD shadowing.** `cmd.exe` resolves a bare `npm` from the\n * current directory before PATH, so an untrusted repo can plant\n * `npm.cmd`. `resolveExecutable` resolves to an absolute path\n * against PATH only (honoring PATHEXT), never the cwd, so callers\n * spawn the real binary by absolute path.\n *\n * All runners are best-effort and timeout-bounded; callers wrap in\n * try/catch and never let an update/probe failure block launch.\n */\n\nimport { spawn, spawnSync, type ChildProcess } from \"node:child_process\"\nimport { existsSync } from \"node:fs\"\nimport path from \"node:path\"\nimport process from \"node:process\"\n\n/**\n * Parse a boolean-ish env value. Returns `undefined` when unset or\n * unrecognized so callers can apply their own default. Accepts\n * `1|true|yes|on` (true) and `0|false|no|off|<empty>` (false),\n * case-insensitive. The single shared parser for all new `GH_ROUTER_*`\n * flags so on/off semantics don't drift per call site.\n */\nexport function parseBoolEnv(value: string | undefined): boolean | undefined {\n if (value === undefined) return undefined\n const v = value.trim().toLowerCase()\n if (v === \"1\" || v === \"true\" || v === \"yes\" || v === \"on\") return true\n if (v === \"0\" || v === \"false\" || v === \"no\" || v === \"off\" || v === \"\") {\n return false\n }\n return undefined\n}\n\n/**\n * Parse a positive-integer env value. Returns `undefined` when unset, empty, or\n * not a clean positive integer, so callers apply their own default.\n *\n * The single shared parser for numeric `GH_ROUTER_*` knobs, and it deliberately\n * uses `Number()` rather than `parseInt()`. `parseInt` stops at the first\n * character it cannot consume, so it silently truncates instead of rejecting:\n * `\"3e5\"` becomes 3, `\"300_000\"` becomes 300, `\"60000ms\"` becomes 60000. For a\n * timeout or a concurrency cap that is the worst possible failure — a user who\n * writes `GH_ROUTER_STOP_GATE_TIMEOUT_MS=3e5` intending five minutes gets three\n * MILLISECONDS, and the resulting instant failure looks like a product bug\n * rather than a typo. Falling back to the documented default is strictly safer\n * than honouring a number the user never meant.\n *\n * Found by a cross-lab reviewer during a capability audit, at three shipped call\n * sites, while three separate local helpers in this repo already parsed the same\n * shape correctly and one of them documented this exact hazard. That is the\n * argument for one shared parser rather than a fourth copy.\n */\nexport function parseIntEnv(value: string | undefined): number | undefined {\n if (value === undefined) return undefined\n const raw = value.trim()\n if (raw === \"\") return undefined\n const n = Number(raw)\n if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return undefined\n return n\n}\n\n/** Read the PATH value from an env object, case-insensitively. */\nfunction pathValueOf(env: NodeJS.ProcessEnv): string {\n for (const key of Object.keys(env)) {\n if (key.toLowerCase() === \"path\") return env[key] ?? \"\"\n }\n return \"\"\n}\n\nexport interface ResolveExecutableOpts {\n env?: NodeJS.ProcessEnv\n platform?: NodeJS.Platform\n /** Directory to treat as \"current dir\" and exclude from resolution. */\n cwd?: string\n}\n\n/**\n * Resolve an executable name to an absolute path against PATH, honoring\n * `PATHEXT` on Windows and **excluding the current working directory**.\n *\n * Returns `null` when unresolved — callers treat that as \"tool absent\"\n * and skip (best-effort). Spawning the returned absolute path means\n * `cmd.exe`'s implicit cwd-first lookup never applies, closing the\n * planted-`npm.cmd` vector.\n */\nexport function resolveExecutable(\n name: string,\n opts: ResolveExecutableOpts = {},\n): string | null {\n const platform = opts.platform ?? process.platform\n const env = opts.env ?? process.env\n // Defensive: some test harnesses mock `node:process` without `cwd`.\n // Absent a usable cwd we simply skip the cwd-exclusion guard (the\n // returned absolute path already bypasses cmd.exe's cwd-first lookup).\n const cwdRaw =\n opts.cwd ?? (typeof process.cwd === \"function\" ? process.cwd() : undefined)\n const resolvedCwd = cwdRaw ? path.resolve(cwdRaw) : null\n\n const dirs = pathValueOf(env)\n .split(path.delimiter)\n // Drop empty entries and a literal \".\" — both denote the cwd on\n // Windows, which we explicitly refuse to resolve against.\n .filter((d) => d.length > 0 && d !== \".\")\n\n const isWin = platform === \"win32\"\n\n // POSIX: an explicit path component → resolve directly.\n if (!isWin && name.includes(\"/\")) {\n return existsSync(name) ? path.resolve(name) : null\n }\n // Windows: an explicit path component (with a separator) → direct.\n if (isWin && (name.includes(\"\\\\\") || name.includes(\"/\"))) {\n return existsSync(name) ? path.resolve(name) : null\n }\n\n const exts =\n isWin && path.extname(name) === \"\"\n ? (env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\")\n .split(\";\")\n .map((e) => e.trim())\n .filter(Boolean)\n : [\"\"]\n\n for (const dir of dirs) {\n // Belt-and-suspenders: never resolve against the cwd even if it is\n // listed explicitly in PATH.\n if (resolvedCwd && path.resolve(dir) === resolvedCwd) continue\n for (const ext of exts) {\n const candidate = path.join(dir, name + ext)\n if (existsSync(candidate)) return candidate\n }\n }\n return null\n}\n\n/**\n * Quote one argument for a `cmd.exe /c \"<line>\"` command line so the\n * target program receives it verbatim and no `cmd.exe` metacharacter\n * retains shell meaning.\n *\n * Two phases (the canonical Windows approach — Colascione / Rust std):\n * 1. **argv quoting** so `CommandLineToArgvW` in the target parses\n * the token as one argument (double-quote, backslash-escape).\n * 2. **caret-escaping** every `cmd.exe` metacharacter — including the\n * quotes from phase 1 — so `cmd.exe` is never in quote-mode, strips\n * the carets, and hands the argv-quoted string to the program.\n *\n * `%` is special: it cannot be reliably escaped on the `cmd.exe`\n * *command line* (caret does not stop `%VAR%` expansion there). Rather\n * than mis-escape, we **throw** — our callers never pass `%`, so this\n * fails closed on the one unescapable injection vector.\n */\nexport function quoteWinArg(arg: string): string {\n if (arg.includes(\"%\")) {\n throw new Error(\n \"buildExecInvocation: argument contains '%', which cannot be safely \" +\n \"escaped on the Windows command line; refusing to build the command.\",\n )\n }\n\n // Phase 1 — argv quoting.\n let quoted: string\n if (arg.length > 0 && !/[ \\t\\n\\v\"&|<>()^!]/.test(arg)) {\n quoted = arg\n } else {\n let s = '\"'\n let backslashes = 0\n for (const ch of arg) {\n if (ch === \"\\\\\") {\n backslashes++\n } else if (ch === '\"') {\n s += \"\\\\\".repeat(backslashes * 2 + 1) + '\"'\n backslashes = 0\n } else {\n s += \"\\\\\".repeat(backslashes) + ch\n backslashes = 0\n }\n }\n s += \"\\\\\".repeat(backslashes * 2) + '\"'\n quoted = s\n }\n\n // Phase 2 — caret-escape all cmd.exe metacharacters (and the carets\n // themselves). cmd strips these; the program sees the phase-1 string.\n return quoted.replace(/[()!^\"<>&|]/g, \"^$&\")\n}\n\nexport interface ExecInvocation {\n command: string\n args: string[]\n shell: boolean\n}\n\n/**\n * Build the platform-correct `spawn` invocation for a command given as\n * an argv array. Pure / unit-testable (no spawn).\n *\n * - win32 → a single caret/argv-quoted command string + `shell:true`\n * + empty args array (the empty array avoids the DEP0190 warning\n * that fires when args and `shell:true` are combined). `cmd[0]`\n * should already be an absolute path from `resolveExecutable`.\n * - posix → `(cmd[0], cmd.slice(1))` with `shell:false` — no shell,\n * no injection surface.\n */\nexport function buildExecInvocation(\n cmd: string[],\n platform: NodeJS.Platform = process.platform,\n): ExecInvocation {\n if (cmd.length === 0) throw new Error(\"buildExecInvocation: empty command\")\n if (platform === \"win32\") {\n return { command: cmd.map(quoteWinArg).join(\" \"), args: [], shell: true }\n }\n return { command: cmd[0], args: cmd.slice(1), shell: false }\n}\n\nexport interface RunOpts {\n cwd?: string\n /** Hard timeout in ms; the process tree is killed on expiry. */\n timeoutMs?: number\n /** Extra env to merge over the parent env for the child. */\n env?: NodeJS.ProcessEnv\n}\n\nexport interface RunResult {\n stdout: string\n stderr: string\n /** Exit code; `null` when killed by signal/timeout. */\n code: number | null\n timedOut: boolean\n}\n\nfunction runInternal(\n cmd: string[],\n stdoutMode: \"pipe\" | \"inherit\" | \"ignore\",\n opts: RunOpts,\n): Promise<RunResult> {\n const { command, args, shell } = buildExecInvocation(cmd)\n return new Promise<RunResult>((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(command, args, {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n shell,\n windowsHide: true,\n stdio: [\n \"ignore\",\n stdoutMode,\n stdoutMode === \"inherit\" ? \"inherit\" : \"pipe\",\n ],\n })\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n let stdout = \"\"\n let stderr = \"\"\n let timedOut = false\n let settled = false\n\n const timer = opts.timeoutMs\n ? setTimeout(() => {\n timedOut = true\n killTree(child.pid)\n }, opts.timeoutMs)\n : undefined\n timer?.unref?.()\n\n child.stdout?.on(\"data\", (c: Buffer) => {\n stdout += c.toString(\"utf8\")\n })\n child.stderr?.on(\"data\", (c: Buffer) => {\n stderr += c.toString(\"utf8\")\n })\n child.stdout?.on(\"error\", () => {})\n child.stderr?.on(\"error\", () => {})\n\n const finish = (code: number | null): void => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n resolve({ stdout, stderr, code, timedOut })\n }\n\n child.on(\"error\", (err) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n reject(err)\n })\n child.on(\"close\", (code) => finish(code))\n })\n}\n\n/** Kill a process tree best-effort (taskkill /T on Windows). */\nfunction killTree(pid: number | undefined): void {\n if (!pid) return\n try {\n if (process.platform === \"win32\") {\n spawn(\"taskkill\", [\"/T\", \"/F\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n windowsHide: true,\n })\n } else {\n process.kill(pid, \"SIGTERM\")\n }\n } catch {\n // already gone\n }\n}\n\n/** Run a command and capture stdout/stderr. Rejects on spawn error. */\nexport function runCommandCapture(\n cmd: string[],\n opts: RunOpts = {},\n): Promise<RunResult> {\n return runInternal(cmd, \"pipe\", opts)\n}\n\n/** Run a command discarding output (still captures stderr for errors). */\nexport function runCommandVoid(\n cmd: string[],\n opts: RunOpts = {},\n): Promise<RunResult> {\n return runInternal(cmd, \"pipe\", opts)\n}\n\n/** Run a command with the child's stdout/stderr inherited to the user. */\nexport function runCommandInherit(\n cmd: string[],\n opts: RunOpts = {},\n): Promise<RunResult> {\n return runInternal(cmd, \"inherit\", opts)\n}\n\nexport interface ManagedExeOpts extends RunOpts {\n /**\n * Hard cap on captured stdout bytes. On overflow the child is\n * tree-killed and the result carries `stdoutTruncated: true`. Defends\n * against a full-`CodeUnit` colgrep `--json` payload (source + 5\n * analysis layers per hit) bloating memory.\n */\n maxStdoutBytes?: number\n /**\n * When true, exceeding `maxStdoutBytes` does NOT tree-kill the child — it\n * sets `stdoutTruncated` and stops BUFFERING further stdout while still\n * draining the pipe (no backpressure). Use for a child whose kill is unsafe\n * (e.g. colgrep, which writes a non-atomic index — a byte-cap kill during\n * its result output could interrupt a write). The child runs to completion\n * (bounded by `timeoutMs` / `inactivityTimeoutMs`). Default false (kill).\n */\n truncateInsteadOfKill?: boolean\n /**\n * Called synchronously with the spawned child right after spawn\n * succeeds, BEFORE any output arrives. The colbert lifecycle ledger\n * uses this to register the child so a session-exit sweep can\n * tree-kill an orphan. Never throws into the runner.\n */\n onSpawn?: (child: ReturnType<typeof spawn>) => void\n /**\n * Inactivity (stall) watchdog. When set, a timer is armed for\n * `inactivityTimeoutMs` and RESET on every stdout/stderr data chunk. On\n * expiry (no output for the window) it consults `onInactivityCheck`: a\n * `true` return means \"still making progress via an out-of-band signal\"\n * (e.g. the colgrep index dir is still growing on disk even though the\n * process is silent on a non-TTY pipe) and the watchdog re-arms; a\n * `false`/absent return tree-kills the child and sets `stalled: true`.\n * This is the progress-based \"stuck\" detector that lets a long-but-\n * progressing build run to completion while still killing a hung one —\n * independent of the coarse total `timeoutMs` backstop.\n */\n inactivityTimeoutMs?: number\n /** External progress probe consulted when the inactivity timer fires.\n * Return `true` to re-arm (still progressing), `false`/throw to kill.\n * MUST be cheap + synchronous (called on a timer, not awaited). */\n onInactivityCheck?: () => boolean\n}\n\nexport interface ManagedExeResult extends RunResult {\n /** True iff stdout was truncated at `maxStdoutBytes` (child was killed). */\n stdoutTruncated: boolean\n /** True iff the inactivity watchdog killed the child (no progress). */\n stalled: boolean\n}\n\n/**\n * Run a **native executable** (a real `.exe`/Mach-O/ELF, NOT a `.cmd`\n * shim) capturing stdout/stderr, with `shell:false` on EVERY platform.\n *\n * Why a separate runner from `runCommandCapture`: that path routes\n * through `buildExecInvocation`, which on Windows builds a\n * `cmd.exe`-quoted command string and **throws on `%`** (`quoteWinArg`).\n * A workspace path can legally contain `%` (and `&`, `(`, `)`, `!`, …),\n * so the managed colgrep binary — which IS a native `.exe`, not a shim —\n * must bypass cmd.exe entirely. `spawn(absExe, args, {shell:false})`\n * resolves the `.exe` via CreateProcess directly: no cmd.exe, no\n * metacharacter hazard, no `%` refusal. POSIX was already `shell:false`.\n * This is what makes \"ANY absolute workspace\" hold on Windows.\n *\n * Lifecycle:\n * - `timeoutMs` → tree-kill on expiry (`taskkill /T /F` on Windows,\n * POSIX process-group `kill(-pgid)` so colgrep's rayon worker\n * children die too). `timedOut: true` in the result.\n * - `maxStdoutBytes` → tree-kill + `stdoutTruncated: true` once the\n * captured stdout exceeds the cap.\n * - `onSpawn(child)` → register the child with the caller's ledger.\n *\n * `command` MUST be an absolute path to the executable (the caller\n * resolves it; we never search PATH here — there is nothing to inject).\n */\nexport function runManagedExeCapture(\n command: string,\n args: ReadonlyArray<string>,\n opts: ManagedExeOpts = {},\n): Promise<ManagedExeResult> {\n const isWin = process.platform === \"win32\"\n return new Promise<ManagedExeResult>((resolve, reject) => {\n let child: ReturnType<typeof spawn>\n try {\n child = spawn(command, [...args], {\n cwd: opts.cwd,\n env: opts.env ?? process.env,\n shell: false,\n windowsHide: true,\n // POSIX: new process group so we can kill the whole tree\n // (colgrep + rayon workers) with kill(-pgid). Windows uses\n // taskkill /T instead; `detached` there has no group semantics.\n detached: !isWin,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n })\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)))\n return\n }\n\n try {\n opts.onSpawn?.(child)\n } catch {\n // ledger registration must never break the spawn\n }\n\n const chunks: Array<Buffer> = []\n let stdoutBytes = 0\n const stderrChunks: Array<Buffer> = []\n let stderrBytes = 0\n const STDERR_CAP = 64 * 1024\n let timedOut = false\n let stdoutTruncated = false\n let stalled = false\n let settled = false\n\n // Exactly one terminator wins: the first to fire records its reason and\n // tree-kills; later ones no-op. Keeps timedOut / stalled / stdoutTruncated\n // mutually exclusive and avoids a double tree-kill.\n let terminated = false\n const terminate = (reason: \"timeout\" | \"stall\" | \"truncate\"): void => {\n if (terminated) return\n terminated = true\n if (reason === \"timeout\") timedOut = true\n else if (reason === \"stall\") stalled = true\n else stdoutTruncated = true\n if (timer) clearTimeout(timer)\n if (inactivityTimer) clearTimeout(inactivityTimer)\n killManagedTree(child, isWin)\n }\n\n const timer = opts.timeoutMs\n ? setTimeout(() => terminate(\"timeout\"), opts.timeoutMs)\n : undefined\n timer?.unref?.()\n\n // Inactivity (stall) watchdog. Re-arms itself: when the window elapses\n // with no output, consult the external progress probe — re-arm if it\n // says \"still progressing\" (e.g. the index dir grew), else kill + mark\n // stalled. Reset on every data chunk (a chatty process never stalls).\n let inactivityTimer: ReturnType<typeof setTimeout> | undefined\n const armInactivity = (): void => {\n if (opts.inactivityTimeoutMs === undefined || settled || terminated) return\n inactivityTimer = setTimeout(() => {\n if (settled) return\n // No probe → pure output-inactivity kill. A probe that THROWS is\n // inconclusive → don't kill (re-arm); the absolute timeoutMs backstop\n // still catches a genuinely wedged process.\n let progressing = false\n if (opts.onInactivityCheck) {\n try {\n progressing = opts.onInactivityCheck() === true\n } catch {\n progressing = true\n }\n }\n if (progressing) {\n armInactivity()\n return\n }\n terminate(\"stall\")\n }, opts.inactivityTimeoutMs)\n inactivityTimer?.unref?.()\n }\n const resetInactivity = (): void => {\n if (inactivityTimer) clearTimeout(inactivityTimer)\n armInactivity()\n }\n armInactivity()\n\n child.stdout?.on(\"data\", (c: Buffer) => {\n resetInactivity()\n if (stdoutTruncated) return\n stdoutBytes += c.length\n if (\n opts.maxStdoutBytes !== undefined &&\n stdoutBytes > opts.maxStdoutBytes\n ) {\n stdoutTruncated = true\n // Default: tree-kill on overflow. Opt-in: keep the child alive and\n // just stop buffering (the data handler keeps firing + discarding via\n // the `if (stdoutTruncated) return` guard above, so the pipe drains\n // and the child never blocks on a full buffer).\n if (!opts.truncateInsteadOfKill) terminate(\"truncate\")\n return\n }\n chunks.push(c)\n })\n child.stderr?.on(\"data\", (c: Buffer) => {\n resetInactivity()\n // Hard byte cap on stderr — append only the slice that fits so a\n // single huge chunk can't overshoot. Never logged raw (it can\n // embed source code from colgrep).\n if (stderrBytes >= STDERR_CAP) return\n const remaining = STDERR_CAP - stderrBytes\n const slice = c.length > remaining ? c.subarray(0, remaining) : c\n stderrChunks.push(slice)\n stderrBytes += slice.length\n })\n child.stdout?.on(\"error\", () => {})\n child.stderr?.on(\"error\", () => {})\n\n const finish = (code: number | null): void => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n if (inactivityTimer) clearTimeout(inactivityTimer)\n resolve({\n stdout: Buffer.concat(chunks).toString(\"utf8\"),\n stderr: Buffer.concat(stderrChunks).toString(\"utf8\"),\n code,\n timedOut,\n stdoutTruncated,\n stalled,\n })\n }\n\n child.on(\"error\", (err) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n if (inactivityTimer) clearTimeout(inactivityTimer)\n reject(err)\n })\n child.on(\"close\", (code) => finish(code))\n })\n}\n\n/**\n * Tree-kill a managed child. Windows: `taskkill /T /F /PID` (whole\n * tree). POSIX: kill the process GROUP (`-pgid`) so colgrep's rayon\n * worker children die with the parent.\n *\n * `runManagedExeCapture` always spawns POSIX children `detached:true`\n * (their own process group), so the group kill is the correct and\n * sufficient primitive. We deliberately do NOT fall back to a positive-\n * pid `process.kill(pid)` when the group kill fails: by the time a kill\n * fires (timeout / byte-cap race), the child may have already exited and\n * its PID been recycled by an unrelated process — a positive-pid kill\n * would then target the wrong process. `ESRCH` (group already gone) is\n * the success case for our purposes; any other error is swallowed.\n */\nexport function killManagedTree(\n child: ReturnType<typeof spawn>,\n isWin: boolean = process.platform === \"win32\",\n): void {\n const pid = child.pid\n if (!pid) return\n try {\n if (isWin) {\n spawn(\"taskkill\", [\"/T\", \"/F\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n windowsHide: true,\n })\n } else {\n // Negative pid → the process group (we spawned detached). No\n // positive-pid fallback (PID-reuse hazard — see doc comment).\n process.kill(-pid, \"SIGKILL\")\n }\n } catch {\n // ESRCH (already gone) / EPERM — best-effort, nothing more to do.\n }\n}\n\n/**\n * Absolute path to Windows `taskkill.exe`. Bare `\"taskkill\"` would let\n * `CreateProcess`'s search order (which can include the cwd) run a planted\n * `taskkill.exe`; pinning the System32 path closes that hijack.\n */\nfunction taskkillExe(): string {\n const root =\n process.env.SystemRoot || process.env.windir || \"C:\\\\Windows\"\n return path.join(root, \"System32\", \"taskkill.exe\")\n}\n\n/**\n * Tree-kill a child process on a SHUTDOWN/exit path, deterministically.\n *\n * Distinct from `killManagedTree` in two ways that matter when the proxy\n * is about to call `process.exit`:\n *\n * 1. **Windows uses `spawnSync`, not async `spawn`.** An async taskkill\n * may never issue before `process.exit` tears the event loop down;\n * the synchronous form guarantees the kill command has run. It is\n * time-bounded so a wedged taskkill can't hang shutdown.\n * 2. **POSIX honors `detachedGroup`.** The launched CLI is spawned\n * `detached:true` (its own process group) so `kill(-pid)` reaps the\n * whole group; other callers (e.g. ripgrep) are NOT detached, so\n * `child.kill()` on the positive pid is correct. A negative-pid kill\n * on a non-detached child would throw or hit the wrong target.\n *\n * Both branches escalate SIGTERM → SIGKILL after `graceMs` so a CLI that\n * traps SIGTERM still dies. The escalation timer is `.unref()`'d so it\n * never holds the event loop open. Errors are swallowed (already-gone /\n * EBUSY / ESRCH are the success case for our purposes).\n */\nexport function killChildProcessTree(\n child: ChildProcess,\n opts: { detachedGroup: boolean; graceMs?: number } = { detachedGroup: false },\n): void {\n const pid = child.pid\n if (!pid) return\n const graceMs = opts.graceMs ?? 500\n\n if (process.platform === \"win32\") {\n // /T = whole tree, /F = force. taskkill walks the live parent-PID\n // chain, so it reaps grandchildren regardless of the cmd.exe-vs-direct\n // spawn shape. Sync (completes before an imminent process.exit) and\n // time-bounded (a wedged taskkill must not hang shutdown).\n try {\n spawnSync(taskkillExe(), [\"/T\", \"/F\", \"/PID\", String(pid)], {\n stdio: \"ignore\",\n windowsHide: true,\n timeout: 5000,\n })\n } catch {\n // Already gone / EBUSY race — best-effort.\n }\n return\n }\n\n // POSIX: signal the group (detached) or the lone child (non-detached).\n const target = opts.detachedGroup ? -pid : pid\n try {\n process.kill(target, \"SIGTERM\")\n } catch {\n // ESRCH — already gone.\n }\n const t = setTimeout(() => {\n // For a lone (non-detached) child, skip escalation once it has exited\n // — its positive PID could have been recycled. For a process GROUP the\n // leader's exit does NOT mean the group is empty (a SIGTERM-ignoring\n // grandchild may survive), and the group id is only reused once the\n // group is fully gone, so always attempt the group SIGKILL (ESRCH when\n // already empty is the success case).\n if (\n !opts.detachedGroup &&\n (child.exitCode !== null || child.signalCode !== null)\n ) {\n return\n }\n try {\n process.kill(target, \"SIGKILL\")\n } catch {\n // Already dead.\n }\n }, graceMs)\n t.unref?.()\n}\n"],"mappings":"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"}