@tekmidian/pai 0.66.48 → 0.66.49
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/auto-route-CMARQbQ4.mjs +8 -0
- package/dist/{auto-route-DrncbMaz.mjs → auto-route-Dpm6nZ6X.mjs} +2 -2
- package/dist/{auto-route-DrncbMaz.mjs.map → auto-route-Dpm6nZ6X.mjs.map} +1 -1
- package/dist/{chain-Bba4Y8A8.mjs → chain-xSc3-u0X.mjs} +5 -5
- package/dist/{chain-Bba4Y8A8.mjs.map → chain-xSc3-u0X.mjs.map} +1 -1
- package/dist/cli/index.mjs +14 -14
- package/dist/cli/program.mjs +14 -14
- package/dist/{config-2pR0RqHM.mjs → config-CYjjGSWq.mjs} +41 -4
- package/dist/config-CYjjGSWq.mjs.map +1 -0
- package/dist/config-IuOG1W92.mjs +4 -0
- package/dist/daemon/index.mjs +10 -10
- package/dist/{daemon-C25MKGD8.mjs → daemon-BIG7XcqE.mjs} +10 -10
- package/dist/{daemon-C6K3kikA.mjs → daemon-CiSmbylJ.mjs} +7 -7
- package/dist/{daemon-C6K3kikA.mjs.map → daemon-CiSmbylJ.mjs.map} +1 -1
- package/dist/daemon-mcp/index.mjs +8 -8
- package/dist/{fallback-DyhAZAOe.mjs → fallback-BgAukP8r.mjs} +6 -5
- package/dist/fallback-BgAukP8r.mjs.map +1 -0
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +26 -2
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/route-agents-to-worker.mjs +26 -2
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/security-validator.mjs +26 -2
- package/dist/hooks/security-validator.mjs.map +2 -2
- package/dist/hooks/worker-guard.mjs +26 -2
- package/dist/hooks/worker-guard.mjs.map +2 -2
- package/dist/hooks/worker-pool-host.mjs +242 -14
- package/dist/hooks/worker-pool-host.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +26 -2
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +26 -2
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/hooks/worker-supervision.mjs +26 -2
- package/dist/hooks/worker-supervision.mjs.map +2 -2
- package/dist/index.mjs +4 -4
- package/dist/{local-EmOjw5jq.mjs → local-Bt-7L0oB.mjs} +3 -3
- package/dist/{local-EmOjw5jq.mjs.map → local-Bt-7L0oB.mjs.map} +1 -1
- package/dist/local-oqPQEmhb.mjs +7 -0
- package/dist/{local-tiers-BIINbhkY.mjs → local-tiers-C0QSQcP1.mjs} +2 -2
- package/dist/{local-tiers-8fmjiySw.mjs → local-tiers-_ZOTbjeP.mjs} +2 -2
- package/dist/{local-tiers-8fmjiySw.mjs.map → local-tiers-_ZOTbjeP.mjs.map} +1 -1
- package/dist/{main-resolver-DBXvnYKp.mjs → main-resolver-BwjexuSA.mjs} +3 -3
- package/dist/{main-resolver-B10-lsU0.mjs → main-resolver-CVo0ab7j.mjs} +3 -3
- package/dist/{main-resolver-B10-lsU0.mjs.map → main-resolver-CVo0ab7j.mjs.map} +1 -1
- package/dist/{pai-marker-CDwQOpQ8.mjs → pai-marker-CJ6WMt05.mjs} +4 -4
- package/dist/{pai-marker-CDwQOpQ8.mjs.map → pai-marker-CJ6WMt05.mjs.map} +1 -1
- package/dist/{planner-dO-3PLjD.mjs → planner-ZKXZf9JS.mjs} +8 -8
- package/dist/{planner-dO-3PLjD.mjs.map → planner-ZKXZf9JS.mjs.map} +1 -1
- package/dist/{pool-DLhqOY3P.mjs → pool-CgsnP3La.mjs} +3 -3
- package/dist/{pool-DLhqOY3P.mjs.map → pool-CgsnP3La.mjs.map} +1 -1
- package/dist/{pool-cli-DerrWWpP.mjs → pool-cli-kGGuZCzJ.mjs} +4 -4
- package/dist/{pool-cli-DerrWWpP.mjs.map → pool-cli-kGGuZCzJ.mjs.map} +1 -1
- package/dist/{program-B7gVKc-u.mjs → program-DgcwJN6Z.mjs} +25 -463
- package/dist/program-DgcwJN6Z.mjs.map +1 -0
- package/dist/{run-a_JzcVwv.mjs → run-CsrBbtj_.mjs} +686 -21
- package/dist/run-CsrBbtj_.mjs.map +1 -0
- package/dist/{run-env-DyFZ8C1s.mjs → run-env-Cyw2iwBh.mjs} +15 -5
- package/dist/{run-env-DyFZ8C1s.mjs.map → run-env-Cyw2iwBh.mjs.map} +1 -1
- package/dist/{status-C3fBJKU_.mjs → status-B0br9zV0.mjs} +2 -2
- package/dist/status-B0br9zV0.mjs.map +1 -0
- package/dist/{supervisor-B_2JKYxe.mjs → supervisor-BZA0KICt.mjs} +7 -7
- package/dist/{supervisor-B_2JKYxe.mjs.map → supervisor-BZA0KICt.mjs.map} +1 -1
- package/dist/workers-config-BifEz8ns.mjs +4 -0
- package/dist/{worktree-lnmAOFQv.mjs → worktree-B6d-1uIn.mjs} +2 -2
- package/dist/{worktree-lnmAOFQv.mjs.map → worktree-B6d-1uIn.mjs.map} +1 -1
- package/docs/worker.md +22 -0
- package/package.json +1 -1
- package/dist/auto-route-DYuVfXwc.mjs +0 -8
- package/dist/config-2pR0RqHM.mjs.map +0 -1
- package/dist/config-CQWpEdXb.mjs +0 -4
- package/dist/fallback-DyhAZAOe.mjs.map +0 -1
- package/dist/local-B5SekWOC.mjs +0 -7
- package/dist/program-B7gVKc-u.mjs.map +0 -1
- package/dist/run-a_JzcVwv.mjs.map +0 -1
- package/dist/status-C3fBJKU_.mjs.map +0 -1
- package/dist/workers-config-nXSiW07z.mjs +0 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as loadConfig } from "./config-DSdCEcHS.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { B as resolveModelCapability, R as readWorkersSection, V as resolveProviderKey } from "./config-CYjjGSWq.mjs";
|
|
3
3
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { delimiter, dirname, join } from "node:path";
|
|
@@ -802,8 +802,13 @@ const NATIVE_STRIP_VARS = [
|
|
|
802
802
|
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
803
803
|
"ANTHROPIC_DEFAULT_OPUS_MODEL"
|
|
804
804
|
];
|
|
805
|
-
/**
|
|
806
|
-
|
|
805
|
+
/**
|
|
806
|
+
* Environment for a run through `provider`. Caller's env minus the Anthropic
|
|
807
|
+
* key. `routerEnabled` routes the run through the local model router instead
|
|
808
|
+
* of the provider's own URL (the openai-protocol proxy route always wins);
|
|
809
|
+
* unset keeps the provider-direct route.
|
|
810
|
+
*/
|
|
811
|
+
function buildRunEnv(provider, headless, proxyUrl, providerName, routerEnabled) {
|
|
807
812
|
const env = { ...process.env };
|
|
808
813
|
delete env.ANTHROPIC_API_KEY;
|
|
809
814
|
if (headless) {
|
|
@@ -822,6 +827,7 @@ function buildRunEnv(provider, headless, proxyUrl, providerName) {
|
|
|
822
827
|
}
|
|
823
828
|
let token = "local";
|
|
824
829
|
if (proxyUrl) env.ANTHROPIC_BASE_URL = proxyUrl;
|
|
830
|
+
else if (routerEnabled) env.ANTHROPIC_BASE_URL = routerUrl();
|
|
825
831
|
else {
|
|
826
832
|
const resolved = resolveProviderKey(provider);
|
|
827
833
|
if (resolved) token = resolved;
|
|
@@ -843,6 +849,10 @@ function buildRunEnv(provider, headless, proxyUrl, providerName) {
|
|
|
843
849
|
}
|
|
844
850
|
/** First-party route a native run is pinned to when caveman is off. */
|
|
845
851
|
const ANTHROPIC_FIRST_PARTY_URL = "https://api.anthropic.com";
|
|
852
|
+
/** Loopback URL of the local model router — the route every accounted run takes. */
|
|
853
|
+
function routerUrl() {
|
|
854
|
+
return `http://127.0.0.1:${ROUTER_PORT}`;
|
|
855
|
+
}
|
|
846
856
|
/**
|
|
847
857
|
* The argv head every Claude Code spawn starts from, for an env built by
|
|
848
858
|
* buildRunEnv. The user settings' env block outranks the process env
|
|
@@ -879,7 +889,7 @@ const PINNED_KEYS = [
|
|
|
879
889
|
* operator output style for a headless non-anthropic worker).
|
|
880
890
|
*/
|
|
881
891
|
function pinnedSettingsArgs(env, routerEnabled = loadConfig().router.enabled, extra) {
|
|
882
|
-
const native = routerEnabled ?
|
|
892
|
+
const native = routerEnabled ? routerUrl() : ANTHROPIC_FIRST_PARTY_URL;
|
|
883
893
|
const pinned = { ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL || native };
|
|
884
894
|
if (env.ANTHROPIC_BASE_URL) {
|
|
885
895
|
for (const k of PINNED_KEYS) if (env[k]) pinned[k] = env[k];
|
|
@@ -895,4 +905,4 @@ function onPath(bin, env) {
|
|
|
895
905
|
|
|
896
906
|
//#endregion
|
|
897
907
|
export { routerHandler as a, aggregateBySession as c, formatStats as d, parseSince as f, ROUTER_PORT as i, appendUsage as l, splitBySessionKind as m, claudeCommand as n, USAGE_LOG_FILE as o, readUsage as p, pinnedSettingsArgs as r, aggregate as s, buildRunEnv as t, formatSessionStats as u };
|
|
898
|
-
//# sourceMappingURL=run-env-
|
|
908
|
+
//# sourceMappingURL=run-env-Cyw2iwBh.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-env-DyFZ8C1s.mjs","names":[],"sources":["../src/router/usage.ts","../src/router/router.ts","../src/workers/run-env.ts"],"sourcesContent":["/**\n * usage.ts — per-request token and cache accounting for the router.\n *\n * Usage is parsed from the response as it streams (SSE message_start /\n * message_delta) or from the JSON body; only numbers and hashes are stored,\n * never bodies, keys or content.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const USAGE_LOG_FILE = join(homedir(), \".claude\", \"pai\", \"logs\", \"router-usage.jsonl\");\n\nexport type CacheVerdict = \"hit\" | \"write\" | \"miss\";\n\nexport interface Usage {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n}\n\nexport interface UsageRow extends Usage {\n ts: string;\n model: string;\n provider: string;\n status: number;\n ms: number;\n bytes: number;\n cache: CacheVerdict;\n /** short hash of x-claude-code-session-id, when sent */\n session?: string;\n /** x-claude-code-session-id as sent (an identifier, not a credential) */\n session_id?: string;\n /** model as the client asked for it, before routing */\n requested_model?: string;\n /** model the request was routed to (same value as `model`) */\n routed_model?: string;\n /** seconds since the previous request of the same session */\n gap_s?: number;\n /** non-sensitive client identification headers */\n user_agent?: string;\n x_app?: string;\n /** which prompt part changed inside a session: system, tools, messages */\n bust?: string[];\n}\n\nconst KEYS: Array<keyof Usage> = [\n \"input_tokens\",\n \"output_tokens\",\n \"cache_creation_input_tokens\",\n \"cache_read_input_tokens\",\n];\n\nexport function emptyUsage(): Usage {\n return { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };\n}\n\nfunction mergeUsage(into: Usage, u: unknown): void {\n if (!u || typeof u !== \"object\") return;\n for (const k of KEYS) {\n const v = (u as Record<string, unknown>)[k];\n if (typeof v === \"number\" && Number.isFinite(v)) into[k] = v;\n }\n}\n\nexport function verdict(u: Usage): CacheVerdict {\n if (u.cache_read_input_tokens > 0) return \"hit\";\n if (u.cache_creation_input_tokens > 0) return \"write\";\n return \"miss\";\n}\n\n/** Usage object of a non-streaming /v1/messages JSON response. */\nexport function usageFromJson(text: string): Usage {\n const u = emptyUsage();\n try {\n mergeUsage(u, (JSON.parse(text) as { usage?: unknown }).usage);\n } catch {\n /* not JSON (error page): zero usage */\n }\n return u;\n}\n\nconst MAX_LINE = 256 * 1024;\nconst MAX_JSON = 4 * 1024 * 1024;\n\n/**\n * Incremental response sniffer. Feed every chunk that is piped to the client.\n * SSE: only the current partial line is held, usage events are parsed as they\n * complete. JSON: the body is held (capped) and parsed at finish().\n */\nexport class UsageSniffer {\n private usage = emptyUsage();\n private line = \"\";\n private skipLine = false;\n private json = \"\";\n private jsonOverflow = false;\n private mode: \"sse\" | \"json\" | undefined;\n\n constructor(contentType: string | undefined) {\n if (contentType) this.mode = /text\\/event-stream/i.test(contentType) ? \"sse\" : \"json\";\n }\n\n push(chunk: Buffer): void {\n const text = chunk.toString(\"utf8\");\n if (!this.mode) this.mode = /^\\s*(event:|data:|:)/.test(text) ? \"sse\" : \"json\";\n if (this.mode === \"json\") {\n if (this.json.length + text.length > MAX_JSON) this.jsonOverflow = true;\n else this.json += text;\n return;\n }\n let rest = text;\n for (;;) {\n const nl = rest.indexOf(\"\\n\");\n if (nl < 0) {\n if (!this.skipLine) {\n this.line += rest;\n if (this.line.length > MAX_LINE) {\n this.line = \"\";\n this.skipLine = true;\n }\n }\n return;\n }\n if (!this.skipLine) this.handleLine(this.line + rest.slice(0, nl));\n this.line = \"\";\n this.skipLine = false;\n rest = rest.slice(nl + 1);\n }\n }\n\n private handleLine(raw: string): void {\n const line = raw.replace(/\\r$/, \"\");\n if (!line.startsWith(\"data:\")) return;\n const data = line.slice(5).trim();\n // cheap prefilter: only usage-bearing events are parsed\n if (!data.includes('\"usage\"')) return;\n try {\n const ev = JSON.parse(data) as { type?: string; usage?: unknown; message?: { usage?: unknown } };\n if (ev.type === \"message_start\") mergeUsage(this.usage, ev.message?.usage);\n else if (ev.type === \"message_delta\") mergeUsage(this.usage, ev.usage);\n } catch {\n /* malformed event: ignore */\n }\n }\n\n finish(): Usage {\n if (this.mode === \"sse\") {\n if (this.line && !this.skipLine) this.handleLine(this.line);\n this.line = \"\";\n } else if (!this.jsonOverflow && this.json) {\n mergeUsage(this.usage, (() => {\n try {\n return (JSON.parse(this.json) as { usage?: unknown }).usage;\n } catch {\n return undefined;\n }\n })());\n this.json = \"\";\n }\n return this.usage;\n }\n}\n\n// ---- cache-bust detector -------------------------------------------------\n\nconst PREFIX_MESSAGES = 2;\n\nfunction sha(v: unknown): string {\n return createHash(\"sha256\").update(JSON.stringify(v) ?? \"\").digest(\"hex\").slice(0, 16);\n}\n\n/** cache_control placement moves every turn; the provider caches content, not markers. */\nfunction stripCacheControl(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripCacheControl);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v)) if (k !== \"cache_control\") out[k] = stripCacheControl(val);\n return out;\n }\n return v;\n}\n\n/** Claude Code per-request telemetry block; changes every request, the cache survives it. */\nconst BILLING_PREFIX = \"x-anthropic-billing-header:\";\n\nfunction isBillingBlock(b: unknown): boolean {\n const text = typeof b === \"string\" ? b : b && typeof b === \"object\" ? (b as { text?: unknown }).text : undefined;\n return typeof text === \"string\" && text.startsWith(BILLING_PREFIX);\n}\n\n/** Fingerprint view of the system prompt without the billing block (the forwarded body is never touched). */\nfunction systemForHash(system: unknown): unknown {\n return Array.isArray(system) ? system.filter((b) => !isBillingBlock(b)) : isBillingBlock(system) ? undefined : system;\n}\n\nexport interface PromptFingerprint {\n system: string;\n tools: string;\n messages: string;\n}\n\nexport function fingerprint(body: Record<string, unknown>): PromptFingerprint {\n const msgs = Array.isArray(body.messages) ? body.messages.slice(0, PREFIX_MESSAGES) : [];\n return {\n system: sha(stripCacheControl(systemForHash(body.system))),\n tools: sha(stripCacheControl(body.tools)),\n messages: sha(stripCacheControl(msgs)),\n };\n}\n\nexport class BustDetector {\n private seen = new Map<string, PromptFingerprint>();\n busts = 0;\n\n constructor(private readonly maxSessions = 500) {}\n\n /** Changed parts vs the previous request of this session (empty when stable or first). */\n check(session: string | undefined, fp: PromptFingerprint): string[] {\n if (!session) return [];\n const prev = this.seen.get(session);\n this.seen.delete(session);\n this.seen.set(session, fp);\n if (this.seen.size > this.maxSessions) this.seen.delete(this.seen.keys().next().value as string);\n if (!prev) return [];\n const changed = ([\"system\", \"tools\", \"messages\"] as const).filter((k) => prev[k] !== fp[k]);\n if (changed.length) this.busts++;\n return changed;\n }\n}\n\n/** Seconds since the previous request of the same session (start to start). */\nexport class GapTracker {\n private last = new Map<string, number>();\n\n constructor(private readonly maxSessions = 2000) {}\n\n gap(session: string | undefined, nowMs: number): number | undefined {\n if (!session) return undefined;\n const prev = this.last.get(session);\n this.last.delete(session);\n this.last.set(session, nowMs);\n if (this.last.size > this.maxSessions) this.last.delete(this.last.keys().next().value as string);\n return prev === undefined ? undefined : Math.round((nowMs - prev) / 100) / 10;\n }\n}\n\nexport function headerValue(h: string | string[] | undefined, max = 120): string | undefined {\n const v = Array.isArray(h) ? h[0] : h;\n return v ? v.slice(0, max) : undefined;\n}\n\nexport function sessionTag(header: string | string[] | undefined): string | undefined {\n const v = Array.isArray(header) ? header[0] : header;\n return v ? createHash(\"sha256\").update(v).digest(\"hex\").slice(0, 8) : undefined;\n}\n\n// ---- storage + stats -----------------------------------------------------\n\nexport function appendUsage(row: UsageRow, file: string = USAGE_LOG_FILE): void {\n try {\n mkdirSync(dirname(file), { recursive: true });\n appendFileSync(file, JSON.stringify(row) + \"\\n\");\n } catch {\n /* accounting must never break routing */\n }\n}\n\nexport function readUsage(file: string = USAGE_LOG_FILE): UsageRow[] {\n if (!existsSync(file)) return [];\n const rows: UsageRow[] = [];\n for (const l of readFileSync(file, \"utf8\").split(\"\\n\")) {\n if (!l) continue;\n try {\n rows.push(JSON.parse(l) as UsageRow);\n } catch {\n /* skip torn line */\n }\n }\n return rows;\n}\n\nexport function parseSince(s: string): number {\n const m = /^(\\d+)\\s*([smhd])$/.exec(s.trim());\n if (!m) throw new Error(`bad --since \"${s}\" (use e.g. 30m, 1h, 2d)`);\n return Number(m[1]) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2] as \"s\" | \"m\" | \"h\" | \"d\"];\n}\n\nexport interface StatsGroup extends Usage {\n provider: string;\n model: string;\n requests: number;\n hit: number;\n write: number;\n miss: number;\n busts: number;\n bytes: number;\n /** cache_read / (cache_read + cache_creation + input) */\n hitRatio: number;\n}\n\nexport function aggregate(rows: UsageRow[], sinceMs?: number, now: number = Date.now()): StatsGroup[] {\n const groups = new Map<string, StatsGroup>();\n for (const r of rows) {\n if (sinceMs !== undefined && Date.parse(r.ts) < now - sinceMs) continue;\n const key = `${r.provider}\\u0000${r.model}`;\n let g = groups.get(key);\n if (!g) {\n g = { ...emptyUsage(), provider: r.provider, model: r.model, requests: 0, hit: 0, write: 0, miss: 0, busts: 0, bytes: 0, hitRatio: 0 };\n groups.set(key, g);\n }\n g.requests++;\n g[r.cache]++;\n if (r.bust?.length) g.busts++;\n g.bytes += r.bytes ?? 0;\n for (const k of KEYS) g[k] += r[k] ?? 0;\n }\n for (const g of groups.values()) {\n const denom = g.cache_read_input_tokens + g.cache_creation_input_tokens + g.input_tokens;\n g.hitRatio = denom ? g.cache_read_input_tokens / denom : 0;\n }\n return [...groups.values()].sort((a, b) => a.provider.localeCompare(b.provider) || a.model.localeCompare(b.model));\n}\n\nexport interface SessionStats extends Usage {\n session: string;\n /** display name, null when PAI does not know the session */\n name: string | null;\n requests: number;\n busts: number;\n firstTs: string;\n lastTs: string;\n hitRatio: number;\n}\n\n/** Totals per session (raw session_id, else the hash tag, else \"-\"). */\nexport function aggregateBySession(\n rows: UsageRow[],\n sinceMs?: number,\n now: number = Date.now(),\n nameOf: (id: string) => string | null = () => null\n): SessionStats[] {\n const groups = new Map<string, SessionStats>();\n for (const r of rows) {\n if (sinceMs !== undefined && Date.parse(r.ts) < now - sinceMs) continue;\n const key = r.session_id ?? r.session ?? \"-\";\n let g = groups.get(key);\n if (!g) {\n g = { ...emptyUsage(), session: key, name: r.session_id ? nameOf(r.session_id) : null, requests: 0, busts: 0, firstTs: r.ts, lastTs: r.ts, hitRatio: 0 };\n groups.set(key, g);\n }\n g.requests++;\n if (r.bust?.length) g.busts++;\n if (r.ts < g.firstTs) g.firstTs = r.ts;\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n for (const k of KEYS) g[k] += r[k] ?? 0;\n }\n for (const g of groups.values()) {\n const denom = g.cache_read_input_tokens + g.cache_creation_input_tokens + g.input_tokens;\n g.hitRatio = denom ? g.cache_read_input_tokens / denom : 0;\n }\n return [...groups.values()].sort((a, b) => b.lastTs.localeCompare(a.lastTs));\n}\n\n// ---- session-kind split: continuous vs one-shot --------------------------\n\n/** Token totals + hit ratio of one group of sessions. */\nexport interface SessionKindStats extends Usage {\n /** Sessions in the group. */\n sessions: number;\n requests: number;\n hitRatio: number;\n}\n\n/** Continuous (multi-request) vs one-shot (exactly one request) sessions. */\nexport interface SessionKindSplit {\n /** A provider-side prompt cache can serve these across requests. */\n continuous: SessionKindStats;\n /** One request, then the session is gone — every request is a cold cache. */\n oneShot: SessionKindStats;\n}\n\nconst emptyKindStats = (): SessionKindStats => ({\n ...emptyUsage(),\n sessions: 0,\n requests: 0,\n hitRatio: 0,\n});\n\n/**\n * Split usage rows by how many requests each session made: a continuous\n * session carries the provider prompt cache from request to request, a\n * one-shot session (the shape of today's `pai worker run` traffic — 158 of\n * 161 glm sessions carry exactly one request) is a fresh spawn each time and\n * shows what the cache is worth. The overall hitRatio alone averages these\n * two populations into one number that predicts neither.\n */\nexport function splitBySessionKind(\n rows: UsageRow[],\n sinceMs?: number,\n now: number = Date.now()\n): SessionKindSplit {\n interface PerSession extends Usage {\n requests: number;\n }\n const bySession = new Map<string, PerSession>();\n for (const r of rows) {\n if (sinceMs !== undefined && Date.parse(r.ts) < now - sinceMs) continue;\n const key = r.session_id ?? r.session ?? \"-\";\n let g = bySession.get(key);\n if (!g) {\n g = { ...emptyUsage(), requests: 0 };\n bySession.set(key, g);\n }\n g.requests++;\n for (const k of KEYS) g[k] += r[k] ?? 0;\n }\n const out: SessionKindSplit = { continuous: emptyKindStats(), oneShot: emptyKindStats() };\n const ratio = (g: SessionKindStats): void => {\n const denom = g.cache_read_input_tokens + g.cache_creation_input_tokens + g.input_tokens;\n g.hitRatio = denom ? g.cache_read_input_tokens / denom : 0;\n };\n for (const g of bySession.values()) {\n const bucket = g.requests > 1 ? out.continuous : out.oneShot;\n bucket.sessions++;\n bucket.requests += g.requests;\n for (const k of KEYS) bucket[k] += g[k];\n }\n ratio(out.continuous);\n ratio(out.oneShot);\n return out;\n}\n\nexport function formatSessionStats(groups: SessionStats[]): string {\n if (!groups.length) return \"no router requests recorded\";\n const head = [\"session\", \"name\", \"req\", \"in\", \"out\", \"cache_w\", \"cache_r\", \"hit%\", \"busts\", \"last\"];\n const lines = groups.map((g) => [\n g.session.slice(0, 8),\n g.name ?? \"-\",\n String(g.requests),\n String(g.input_tokens),\n String(g.output_tokens),\n String(g.cache_creation_input_tokens),\n String(g.cache_read_input_tokens),\n (g.hitRatio * 100).toFixed(1),\n String(g.busts),\n g.lastTs,\n ]);\n const widths = head.map((h, i) => Math.max(h.length, ...lines.map((l) => l[i]!.length)));\n const fmt = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i]!)).join(\" \").trimEnd();\n return [fmt(head), ...lines.map(fmt)].join(\"\\n\");\n}\n\nexport function formatStats(groups: StatsGroup[]): string {\n if (!groups.length) return \"no router requests recorded\";\n const head = [\"provider\", \"model\", \"req\", \"in\", \"out\", \"cache_w\", \"cache_r\", \"hit/write/miss\", \"hit%\", \"busts\"];\n const lines = groups.map((g) => [\n g.provider,\n g.model,\n String(g.requests),\n String(g.input_tokens),\n String(g.output_tokens),\n String(g.cache_creation_input_tokens),\n String(g.cache_read_input_tokens),\n `${g.hit}/${g.write}/${g.miss}`,\n (g.hitRatio * 100).toFixed(1),\n String(g.busts),\n ]);\n const widths = head.map((h, i) => Math.max(h.length, ...lines.map((l) => l[i]!.length)));\n const fmt = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i]!)).join(\" \").trimEnd();\n return [fmt(head), ...lines.map(fmt)].join(\"\\n\");\n}\n","/**\n * router.ts — local model router. One Claude Code session points its\n * ANTHROPIC_BASE_URL here and /model picks the provider: each POST\n * /v1/messages (and count_tokens) is routed by the body's `model`.\n *\n * claude ids → caveman (CLAUDE_UPSTREAM), inbound auth passed through\n * other ids → the workers.yaml provider that lists the model; inbound auth\n * dropped, the provider key injected as Authorization: Bearer\n *\n * Bodies and keys are never logged.\n */\n\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport {\n BustDetector,\n emptyUsage,\n fingerprint,\n GapTracker,\n headerValue,\n sessionTag,\n UsageSniffer,\n verdict,\n type Usage,\n type UsageRow,\n} from \"./usage.js\";\nimport { readWorkersSection, resolveProviderKey, type WorkerProvider } from \"../workers/config.js\";\n\nexport const ROUTER_PORT = 8790;\nexport const CLAUDE_UPSTREAM = \"http://127.0.0.1:8787/w/claude\";\n\nexport type Target =\n | { kind: \"claude\"; model: string; base: string }\n | { kind: \"provider\"; model: string; name: string; provider: WorkerProvider };\n\nexport type Resolution = Target | { kind: \"unknown\"; model: string; routable: string[] };\n\n/** Claude Code appends \"[1m]\" for the 1M-context variant; upstreams reject it. */\nexport function stripContextSuffix(model: string): string {\n return model.replace(/\\[1m\\]$/i, \"\");\n}\n\nfunction isClaudeId(model: string): boolean {\n return /^claude-/.test(model) || [\"sonnet\", \"opus\", \"haiku\", \"default\", \"opusplan\"].includes(model);\n}\n\n/** model id (suffix-free) → provider name, from enabled non-native providers. */\nexport function routingTable(providers: Record<string, WorkerProvider>): Map<string, string> {\n const table = new Map<string, string>();\n for (const [name, p] of Object.entries(providers)) {\n if (p.native || p.enabled === false || !p.baseUrl || p.protocol !== \"anthropic\" || (p.engine ?? \"claude\") !== \"claude\") continue;\n for (const id of Object.values(p.models)) {\n if (!id) continue;\n const bare = stripContextSuffix(id);\n if (!table.has(bare)) table.set(bare, name);\n }\n }\n return table;\n}\n\nexport function resolveModel(\n rawModel: string,\n providers: Record<string, WorkerProvider>,\n claudeBase: string = CLAUDE_UPSTREAM\n): Resolution {\n const model = stripContextSuffix(rawModel);\n if (isClaudeId(model)) return { kind: \"claude\", model, base: claudeBase };\n const table = routingTable(providers);\n let name = table.get(model);\n if (!name) {\n // family prefixes: glm-* → glm, k3/kimi-* → kimi\n if (/^glm-/.test(model) && providers.glm) name = \"glm\";\n else if (/^(k3|kimi-)/.test(model) && providers.kimi) name = \"kimi\";\n }\n const p = name ? providers[name] : undefined;\n if (!name || !p) return { kind: \"unknown\", model, routable: [\"claude-*\", ...table.keys()] };\n return { kind: \"provider\", model, name, provider: p };\n}\n\n/** thinking blocks carry Anthropic signatures other providers reject. */\nexport function stripThinking(body: Record<string, unknown>): Record<string, unknown> {\n const messages = body.messages;\n if (!Array.isArray(messages)) return body;\n const cleaned = messages.map((m) => {\n if (!m || typeof m !== \"object\" || !Array.isArray((m as { content?: unknown }).content)) return m;\n const content = (m as { content: Array<{ type?: string }> }).content.filter(\n (b) => !(b && (b.type === \"thinking\" || b.type === \"redacted_thinking\"))\n );\n return { ...(m as object), content };\n });\n return { ...body, messages: cleaned };\n}\n\nexport interface Upstream {\n url: URL;\n headers: Record<string, string>;\n body: Buffer;\n}\n\nexport interface RouterOptions {\n /**\n * Remote-address gate for the /v1 routes, set whenever the router listens\n * beyond loopback (router.bind): a request whose socket address fails it is\n * answered 403 before any routing — the inbound auth pass-through must not\n * leave the loopback+tailnet perimeter. Cockpit routes are handled before\n * this (they carry their own token, see src/router/cockpit-auth.ts).\n */\n remoteAllowed?: (remoteAddress: string | undefined) => boolean;\n}\n\n// accept-encoding dropped: usage is parsed from the plain response as it streams\nconst HOP = new Set([\"host\", \"connection\", \"content-length\", \"transfer-encoding\", \"keep-alive\", \"accept-encoding\"]);\nconst AUTH = new Set([\"authorization\", \"x-api-key\"]);\n\nfunction joinUrl(base: string, path: string): URL {\n return new URL(base.replace(/\\/+$/, \"\") + path);\n}\n\n/** Build the upstream request, or an error status+message. */\nexport function prepare(\n path: string,\n inHeaders: http.IncomingHttpHeaders,\n rawBody: Buffer,\n providers: Record<string, WorkerProvider>,\n claudeBase: string = CLAUDE_UPSTREAM\n): { ok: true; up: Upstream; target: Target; requested: string } | { ok: false; status: number; message: string } {\n let body: Record<string, unknown>;\n try {\n body = JSON.parse(rawBody.toString(\"utf8\"));\n } catch {\n return { ok: false, status: 400, message: \"invalid JSON body\" };\n }\n if (typeof body.model !== \"string\") return { ok: false, status: 400, message: \"missing model\" };\n const res = resolveModel(body.model, providers, claudeBase);\n if (res.kind === \"unknown\") {\n return {\n ok: false,\n status: 400,\n message: `unknown model \"${res.model}\"; routable: ${res.routable.join(\", \")}`,\n };\n }\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(inHeaders)) {\n const lk = k.toLowerCase();\n if (HOP.has(lk) || v === undefined) continue;\n if (res.kind === \"provider\" && AUTH.has(lk)) continue;\n headers[lk] = Array.isArray(v) ? v.join(\", \") : v;\n }\n let out: Record<string, unknown> = { ...body, model: res.model };\n let base: string;\n if (res.kind === \"claude\") {\n base = res.base;\n } else {\n out = stripThinking(out);\n base = res.provider.baseUrl;\n let token: string | null;\n try {\n token = resolveProviderKey(res.provider);\n } catch (e) {\n return { ok: false, status: 502, message: (e as Error).message };\n }\n headers.authorization = `Bearer ${token ?? \"local\"}`;\n }\n const buf = Buffer.from(JSON.stringify(out));\n headers[\"content-length\"] = String(buf.length);\n return { ok: true, up: { url: joinUrl(base, path), headers, body: buf }, target: res, requested: body.model };\n}\n\nconst MODELS_PATH = /^\\/v1\\/models(?:\\/([^/?]+))?(\\?.*)?$/;\n\n/** Anthropic models-API entry for a routed id. */\nexport function modelEntry(id: string): { id: string; type: \"model\"; display_name: string; created_at: string } {\n return { id, type: \"model\", display_name: id, created_at: \"2025-01-01T00:00:00Z\" };\n}\n\n/** GET /v1/models[/id] for non-claude ids, from the routing table; null = not ours. */\nexport function modelsReply(\n path: string,\n providers: Record<string, WorkerProvider>\n): { status: number; body: unknown } | null {\n const m = MODELS_PATH.exec(path);\n if (!m) return null;\n if (!m[1]) {\n const data = [...routingTable(providers).keys()].map(modelEntry);\n return { status: 200, body: { data, has_more: false, first_id: data[0]?.id ?? null, last_id: data.at(-1)?.id ?? null } };\n }\n const res = resolveModel(decodeURIComponent(m[1]), providers);\n if (res.kind === \"provider\") return { status: 200, body: modelEntry(res.model) };\n if (res.kind === \"unknown\") {\n return { status: 404, body: { type: \"error\", error: { type: \"not_found_error\", message: `model \"${res.model}\" not found` } } };\n }\n return null;\n}\n\n/** chars/4 over the request body, for providers without count_tokens. */\nexport function estimateTokens(rawBody: Buffer): number {\n return Math.ceil(rawBody.length / 4);\n}\n\nfunction isLocal(u: URL): boolean {\n return u.hostname === \"127.0.0.1\" || u.hostname === \"localhost\";\n}\n\nexport function createRouter(\n loadProviders: () => Record<string, WorkerProvider> = () => readWorkersSection().workers.providers,\n claudeBase: string = CLAUDE_UPSTREAM,\n log: (line: string) => void = (l) => process.stderr.write(l + \"\\n\"),\n onUsage?: (row: UsageRow) => void,\n /** serves GET pages (the cockpit) before routing; true = handled */\n extra?: (req: http.IncomingMessage, res: http.ServerResponse) => boolean,\n options?: RouterOptions\n): http.Server {\n return http.createServer(routerHandler(loadProviders, claudeBase, log, onUsage, extra, options));\n}\n\n/**\n * The router's request handler, built separately from the server so a\n * non-loopback bind can put a second http.Server on the same handler (Node\n * refuses a second listen() on a listening server — ERR_SERVER_ALREADY_LISTEN)\n * while every entry point shares one BustDetector/GapTracker.\n */\nexport function routerHandler(\n loadProviders: () => Record<string, WorkerProvider> = () => readWorkersSection().workers.providers,\n claudeBase: string = CLAUDE_UPSTREAM,\n log: (line: string) => void = (l) => process.stderr.write(l + \"\\n\"),\n onUsage?: (row: UsageRow) => void,\n /** serves GET pages (the cockpit) before routing; true = handled */\n extra?: (req: http.IncomingMessage, res: http.ServerResponse) => boolean,\n options?: RouterOptions\n): (req: http.IncomingMessage, res: http.ServerResponse) => void {\n const busts = new BustDetector();\n const gaps = new GapTracker();\n return (req, res) => {\n if (extra?.(req, res)) return;\n const guard = options?.remoteAllowed;\n if (guard && !guard(req.socket.remoteAddress ?? undefined)) {\n res.writeHead(403, { \"content-type\": \"application/json\" });\n res.end(\n JSON.stringify({\n type: \"error\",\n error: { type: \"permission_error\", message: \"pai router: /v1 is limited to loopback and the tailnet (100.64.0.0/10)\" },\n })\n );\n log(`${new Date().toISOString()} ${req.method ?? \"-\"} ${(req.url ?? \"/\").split(\"?\")[0]} 403 remote-not-allowed`);\n return;\n }\n const started = Date.now();\n const path = req.url ?? \"/\";\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n const fail = (status: number, message: string, model = \"-\", prov = \"-\") => {\n if (!res.headersSent) {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ type: \"error\", error: { type: \"invalid_request_error\", message } }));\n } else res.end();\n log(`${new Date().toISOString()} ${model} ${prov} ${status} ${Date.now() - started}ms`);\n };\n const isModels = req.method === \"GET\" && MODELS_PATH.test(path);\n const isMessages = req.method === \"POST\" && /^\\/v1\\/messages(\\/count_tokens)?(\\?.*)?$/.test(path);\n if (!isModels && !isMessages) {\n return fail(404, `pai router: no route for ${req.method} ${path.split(\"?\")[0]}`, `${req.method} ${path.split(\"?\")[0]}`);\n }\n let providers: Record<string, WorkerProvider>;\n try {\n providers = loadProviders();\n } catch (e) {\n return fail(500, `workers config: ${(e as Error).message}`);\n }\n if (isModels) {\n const local = modelsReply(path, providers);\n if (local) {\n res.writeHead(local.status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(local.body));\n log(`${new Date().toISOString()} GET ${path.split(\"?\")[0]} local ${local.status} ${Date.now() - started}ms`);\n return;\n }\n // claude ids (and the list) pass to the anthropic upstream unchanged\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (!HOP.has(k.toLowerCase()) && v !== undefined) headers[k.toLowerCase()] = Array.isArray(v) ? v.join(\", \") : v;\n }\n const url = joinUrl(claudeBase, path);\n const ureq = (url.protocol === \"https:\" ? https : http).request(url, { method: \"GET\", headers }, (ures) => {\n const rh = { ...ures.headers };\n delete rh[\"transfer-encoding\"];\n delete rh.connection;\n res.writeHead(ures.statusCode ?? 502, rh);\n ures.pipe(res);\n ures.on(\"end\", () => log(`${new Date().toISOString()} GET ${path.split(\"?\")[0]} anthropic ${ures.statusCode} ${Date.now() - started}ms`));\n });\n ureq.on(\"error\", (e: NodeJS.ErrnoException) => fail(502, `upstream error: ${e.code ?? e.message}`, \"GET models\", \"anthropic\"));\n res.on(\"close\", () => ureq.destroy());\n ureq.end();\n return;\n }\n const prep = prepare(path, req.headers, Buffer.concat(chunks), providers, claudeBase);\n if (!prep.ok) return fail(prep.status, prep.message);\n const { up, target, requested } = prep;\n const prov = target.kind === \"claude\" ? \"anthropic\" : target.name;\n const isCount = path.includes(\"/count_tokens\");\n const session = sessionTag(req.headers[\"x-claude-code-session-id\"]);\n const sessionId = headerValue(req.headers[\"x-claude-code-session-id\"], 64);\n const gap = onUsage && !isCount ? gaps.gap(sessionId, started) : undefined;\n const changed =\n onUsage && !isCount ? busts.check(session, fingerprint(JSON.parse(up.body.toString(\"utf8\")))) : [];\n const record = (status: number, usage: Usage) => {\n if (!onUsage || isCount) return;\n const row: UsageRow = {\n ts: new Date().toISOString(),\n model: target.model,\n provider: prov,\n status,\n ms: Date.now() - started,\n ...usage,\n bytes: up.body.length,\n cache: verdict(usage),\n };\n if (session) row.session = session;\n if (sessionId) row.session_id = sessionId;\n row.requested_model = requested;\n row.routed_model = target.model;\n if (gap !== undefined) row.gap_s = gap;\n const ua = headerValue(req.headers[\"user-agent\"]);\n const app = headerValue(req.headers[\"x-app\"], 40);\n if (ua) row.user_agent = ua;\n if (app) row.x_app = app;\n if (changed.length) row.bust = changed;\n onUsage(row);\n };\n const mod = up.url.protocol === \"https:\" ? https : http;\n const ureq = mod.request(\n up.url,\n { method: \"POST\", headers: up.headers },\n (ures) => {\n if (isCount && target.kind === \"provider\" && (ures.statusCode ?? 0) >= 400) {\n // provider has no count_tokens: estimate instead of failing the client\n ures.resume();\n const raw = Buffer.concat(chunks);\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ input_tokens: estimateTokens(raw) }));\n log(`${new Date().toISOString()} ${target.model} ${prov} count_tokens-estimate ${Date.now() - started}ms`);\n return;\n }\n const rh = { ...ures.headers };\n delete rh[\"transfer-encoding\"];\n delete rh.connection;\n delete rh[\"keep-alive\"];\n res.writeHead(ures.statusCode ?? 502, rh);\n const sniff = onUsage && !isCount ? new UsageSniffer(ures.headers[\"content-type\"]) : undefined;\n if (sniff) ures.on(\"data\", (c: Buffer) => sniff.push(c));\n ures.pipe(res);\n ures.on(\"end\", () => {\n log(`${new Date().toISOString()} ${target.model} ${prov} ${ures.statusCode} ${Date.now() - started}ms`);\n record(ures.statusCode ?? 502, sniff ? sniff.finish() : emptyUsage());\n });\n }\n );\n ureq.on(\"error\", (e: NodeJS.ErrnoException) => {\n record(e.code === \"ECONNREFUSED\" ? 503 : 502, emptyUsage());\n if (target.kind === \"provider\" && isLocal(up.url) && e.code === \"ECONNREFUSED\") {\n return fail(\n 503,\n `local model down — start it with pai worker local <tier>`,\n target.model,\n prov\n );\n }\n fail(502, `upstream error: ${e.code ?? e.message}`, target.model, prov);\n });\n res.on(\"close\", () => ureq.destroy());\n ureq.end(up.body);\n });\n };\n}\n","/**\n * run-env.ts — the provider environment every Claude Code spawn must run with.\n *\n * Extracted from run.ts so daemon-side background spawns (session summaries,\n * context handovers, KG extraction) go through the exact same env the worker\n * runner uses, instead of importing the whole runner into the daemon.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { delimiter, join } from \"node:path\";\n\nimport { loadConfig } from \"../daemon/config.js\";\nimport { ROUTER_PORT } from \"../router/router.js\";\nimport { resolveModelCapability, resolveProviderKey, type WorkerProvider } from \"./config.js\";\n\n/**\n * Session-identity variables the SPAWNING session leaves in the environment.\n * Inherited, they make a headless child attach to the spawner's messaging\n * socket instead of standing on its own — and a worktree-mode child then\n * starts with its core tools (Bash/Read/…) deferred out of its reach, left\n * with nothing but the tool-registry search (2026-09-18). The child is its\n * own session, so none of these may cross the spawn boundary.\n */\nconst SESSION_IDENTITY_VARS = [\n \"CLAUDE_CODE_MESSAGING_SOCKET\",\n \"CLAUDE_CODE_MESSAGING_TOKEN\",\n \"CLAUDE_CODE_SESSION_ID\",\n \"CLAUDE_CODE_CHILD_SESSION\",\n \"CLAUDE_PID\",\n \"CLAUDECODE\",\n] as const;\n\n/**\n * Harness settings the spawner's environment carries that a headless worker\n * must not keep. ENABLE_TOOL_SEARCH reaches every pai process through the\n * user settings' env block, so a pai spawned from inside a Claude session\n * passes it on to worker children; inherited, the headless child starts with\n * every core tool deferred out of reach — only ToolSearch remains callable,\n * granted tools included (2026-09-18, verified by stripping exactly this one\n * var). Interactive runs set it deliberately below.\n *\n * PAI_WORKER_ALLOW_HIDDEN is the operator's own consent to run a worker with\n * no pane. It must die at the spawn boundary like the session identities\n * above: a worker (or a daemon born of an operator shell) never passes the\n * consent on, so \"allowed hidden\" applies only to the process it was set for.\n */\nconst HEADLESS_STRIP_VARS = [\"ENABLE_TOOL_SEARCH\", \"PAI_WORKER_ALLOW_HIDDEN\"] as const;\n\n/**\n * Provider-specific env vars a native-Anthropic run must never carry — not\n * set by this function, and stripped even when a parent shell exported them\n * (e.g. this worker was itself spawned from a GLM-configured environment).\n */\nconst NATIVE_STRIP_VARS = [\n \"ANTHROPIC_BASE_URL\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\",\n] as const;\n\n/** Environment for a run through `provider`. Caller's env minus the Anthropic key. */\nexport function buildRunEnv(\n provider: WorkerProvider,\n headless: boolean,\n proxyUrl?: string,\n providerName?: string\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n delete env.ANTHROPIC_API_KEY;\n if (headless) {\n for (const k of SESSION_IDENTITY_VARS) delete env[k];\n for (const k of HEADLESS_STRIP_VARS) delete env[k];\n // workers have no memory to recall; the auto-memory prompt only sends\n // them to read a MEMORY.md that does not exist\n env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = \"1\";\n }\n\n if (provider.native) {\n // Plain Claude Code on its own OAuth/Max-plan login: no base URL, no\n // token, no provider-specific model pins — and none inherited from a\n // shell that had a different provider exported into it either.\n for (const k of NATIVE_STRIP_VARS) delete env[k];\n env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = \"1\";\n if (headless) {\n env.PAI_WORKER = \"1\";\n env.PAI_WORKER_PROVIDER = \"anthropic\";\n } else {\n env.ENABLE_TOOL_SEARCH = \"true\";\n }\n return env;\n }\n\n let token = \"local\";\n if (proxyUrl) {\n // openai-protocol provider: the proxy holds the real key; the runner only\n // needs a placeholder so Claude Code sends an auth header at all\n env.ANTHROPIC_BASE_URL = proxyUrl;\n } else {\n const resolved = resolveProviderKey(provider);\n if (resolved) token = resolved;\n env.ANTHROPIC_BASE_URL = provider.baseUrl;\n }\n\n env.ANTHROPIC_AUTH_TOKEN = token;\n env.ANTHROPIC_DEFAULT_HAIKU_MODEL = resolveModelCapability(provider, \"fast\");\n env.ANTHROPIC_DEFAULT_SONNET_MODEL = provider.models.default;\n env.ANTHROPIC_DEFAULT_OPUS_MODEL = provider.models.default;\n for (const [k, v] of Object.entries(provider.env)) env[k] = v;\n env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = \"1\";\n if (headless) {\n env.PAI_WORKER = \"1\";\n // read by the whisper-rules hook: a non-anthropic worker gets no operator rules\n if (providerName) env.PAI_WORKER_PROVIDER = providerName;\n // A local model gets the task, not the operator's interactive-session\n // overhead (user+project CLAUDE.md, memory index): it echoed that text\n // instead of working (2026-10-15). The two switches cannot separate user\n // from project CLAUDE.md, so both go. The output style is dropped in\n // claudeCommand via --settings.\n env.CLAUDE_CODE_DISABLE_CLAUDE_MDS = \"1\";\n env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = \"1\";\n } else {\n env.ENABLE_TOOL_SEARCH = \"true\";\n }\n return env;\n}\n\n/** First-party route a native run is pinned to when caveman is off. */\nexport const ANTHROPIC_FIRST_PARTY_URL = \"https://api.anthropic.com\";\n\n/**\n * The argv head every Claude Code spawn starts from, for an env built by\n * buildRunEnv. The user settings' env block outranks the process env\n * (measured 2026-09-22: an unreachable ANTHROPIC_BASE_URL in the process env\n * was ignored while settings.json carried a caveman proxy route, and a glm\n * worker's bearer token ended at Anthropic with a 401), so a route set in\n * `env` is not enough once a machine-wide proxy is installed — it is pinned\n * again with `--settings`, which outranks user settings. A native run goes\n * through `caveman claude` when workers.caveman is on and the CLI is on\n * PATH; off, it is pinned to api.anthropic.com so a global route cannot\n * pull it in either.\n */\nexport function claudeCommand(env: NodeJS.ProcessEnv, caveman: boolean, routerEnabled?: boolean): string[] {\n const route = env.ANTHROPIC_BASE_URL;\n if (!route && caveman) {\n if (onPath(\"caveman\", env)) return [\"caveman\", \"claude\"];\n process.stderr.write(\"pai: workers.caveman is on but `caveman` is not on PATH; running claude directly\\n\");\n }\n // set by buildRunEnv for a headless non-anthropic worker: no operator output style\n const extra = env.CLAUDE_CODE_DISABLE_CLAUDE_MDS === \"1\" ? { outputStyle: \"default\" } : undefined;\n return [\"claude\", ...pinnedSettingsArgs(env, routerEnabled, extra)];\n}\n\nconst PINNED_KEYS = [\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\",\n];\n\n/**\n * `--settings` argv pinning the provider's route, token and tier models: the\n * user settings' env outranks the process env, so a provider launch would\n * otherwise authenticate its token against the user's proxy (401). Shared by\n * workers and interactive `pai launch`. A native run (no provider route) is\n * pinned to the local model router when router.enabled, so `/model` can switch\n * provider and every request is accounted; else to api.anthropic.com.\n * `extra` spreads over the settings object (claudeCommand uses it to drop the\n * operator output style for a headless non-anthropic worker).\n */\nexport function pinnedSettingsArgs(\n env: NodeJS.ProcessEnv,\n routerEnabled: boolean = loadConfig().router.enabled,\n extra?: Record<string, unknown>\n): string[] {\n const native = routerEnabled ? `http://127.0.0.1:${ROUTER_PORT}` : ANTHROPIC_FIRST_PARTY_URL;\n const pinned: Record<string, string> = { ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL || native };\n if (env.ANTHROPIC_BASE_URL) for (const k of PINNED_KEYS) if (env[k]) pinned[k] = env[k] as string;\n return [\"--settings\", JSON.stringify({ env: pinned, ...extra })];\n}\n\nfunction onPath(bin: string, env: NodeJS.ProcessEnv): boolean {\n return (env.PATH ?? \"\").split(delimiter).some((d) => d && existsSync(join(d, bin)));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run-env-Cyw2iwBh.mjs","names":[],"sources":["../src/router/usage.ts","../src/router/router.ts","../src/workers/run-env.ts"],"sourcesContent":["/**\n * usage.ts — per-request token and cache accounting for the router.\n *\n * Usage is parsed from the response as it streams (SSE message_start /\n * message_delta) or from the JSON body; only numbers and hashes are stored,\n * never bodies, keys or content.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const USAGE_LOG_FILE = join(homedir(), \".claude\", \"pai\", \"logs\", \"router-usage.jsonl\");\n\nexport type CacheVerdict = \"hit\" | \"write\" | \"miss\";\n\nexport interface Usage {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n}\n\nexport interface UsageRow extends Usage {\n ts: string;\n model: string;\n provider: string;\n status: number;\n ms: number;\n bytes: number;\n cache: CacheVerdict;\n /** short hash of x-claude-code-session-id, when sent */\n session?: string;\n /** x-claude-code-session-id as sent (an identifier, not a credential) */\n session_id?: string;\n /** model as the client asked for it, before routing */\n requested_model?: string;\n /** model the request was routed to (same value as `model`) */\n routed_model?: string;\n /** seconds since the previous request of the same session */\n gap_s?: number;\n /** non-sensitive client identification headers */\n user_agent?: string;\n x_app?: string;\n /** which prompt part changed inside a session: system, tools, messages */\n bust?: string[];\n}\n\nconst KEYS: Array<keyof Usage> = [\n \"input_tokens\",\n \"output_tokens\",\n \"cache_creation_input_tokens\",\n \"cache_read_input_tokens\",\n];\n\nexport function emptyUsage(): Usage {\n return { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 };\n}\n\nfunction mergeUsage(into: Usage, u: unknown): void {\n if (!u || typeof u !== \"object\") return;\n for (const k of KEYS) {\n const v = (u as Record<string, unknown>)[k];\n if (typeof v === \"number\" && Number.isFinite(v)) into[k] = v;\n }\n}\n\nexport function verdict(u: Usage): CacheVerdict {\n if (u.cache_read_input_tokens > 0) return \"hit\";\n if (u.cache_creation_input_tokens > 0) return \"write\";\n return \"miss\";\n}\n\n/** Usage object of a non-streaming /v1/messages JSON response. */\nexport function usageFromJson(text: string): Usage {\n const u = emptyUsage();\n try {\n mergeUsage(u, (JSON.parse(text) as { usage?: unknown }).usage);\n } catch {\n /* not JSON (error page): zero usage */\n }\n return u;\n}\n\nconst MAX_LINE = 256 * 1024;\nconst MAX_JSON = 4 * 1024 * 1024;\n\n/**\n * Incremental response sniffer. Feed every chunk that is piped to the client.\n * SSE: only the current partial line is held, usage events are parsed as they\n * complete. JSON: the body is held (capped) and parsed at finish().\n */\nexport class UsageSniffer {\n private usage = emptyUsage();\n private line = \"\";\n private skipLine = false;\n private json = \"\";\n private jsonOverflow = false;\n private mode: \"sse\" | \"json\" | undefined;\n\n constructor(contentType: string | undefined) {\n if (contentType) this.mode = /text\\/event-stream/i.test(contentType) ? \"sse\" : \"json\";\n }\n\n push(chunk: Buffer): void {\n const text = chunk.toString(\"utf8\");\n if (!this.mode) this.mode = /^\\s*(event:|data:|:)/.test(text) ? \"sse\" : \"json\";\n if (this.mode === \"json\") {\n if (this.json.length + text.length > MAX_JSON) this.jsonOverflow = true;\n else this.json += text;\n return;\n }\n let rest = text;\n for (;;) {\n const nl = rest.indexOf(\"\\n\");\n if (nl < 0) {\n if (!this.skipLine) {\n this.line += rest;\n if (this.line.length > MAX_LINE) {\n this.line = \"\";\n this.skipLine = true;\n }\n }\n return;\n }\n if (!this.skipLine) this.handleLine(this.line + rest.slice(0, nl));\n this.line = \"\";\n this.skipLine = false;\n rest = rest.slice(nl + 1);\n }\n }\n\n private handleLine(raw: string): void {\n const line = raw.replace(/\\r$/, \"\");\n if (!line.startsWith(\"data:\")) return;\n const data = line.slice(5).trim();\n // cheap prefilter: only usage-bearing events are parsed\n if (!data.includes('\"usage\"')) return;\n try {\n const ev = JSON.parse(data) as { type?: string; usage?: unknown; message?: { usage?: unknown } };\n if (ev.type === \"message_start\") mergeUsage(this.usage, ev.message?.usage);\n else if (ev.type === \"message_delta\") mergeUsage(this.usage, ev.usage);\n } catch {\n /* malformed event: ignore */\n }\n }\n\n finish(): Usage {\n if (this.mode === \"sse\") {\n if (this.line && !this.skipLine) this.handleLine(this.line);\n this.line = \"\";\n } else if (!this.jsonOverflow && this.json) {\n mergeUsage(this.usage, (() => {\n try {\n return (JSON.parse(this.json) as { usage?: unknown }).usage;\n } catch {\n return undefined;\n }\n })());\n this.json = \"\";\n }\n return this.usage;\n }\n}\n\n// ---- cache-bust detector -------------------------------------------------\n\nconst PREFIX_MESSAGES = 2;\n\nfunction sha(v: unknown): string {\n return createHash(\"sha256\").update(JSON.stringify(v) ?? \"\").digest(\"hex\").slice(0, 16);\n}\n\n/** cache_control placement moves every turn; the provider caches content, not markers. */\nfunction stripCacheControl(v: unknown): unknown {\n if (Array.isArray(v)) return v.map(stripCacheControl);\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v)) if (k !== \"cache_control\") out[k] = stripCacheControl(val);\n return out;\n }\n return v;\n}\n\n/** Claude Code per-request telemetry block; changes every request, the cache survives it. */\nconst BILLING_PREFIX = \"x-anthropic-billing-header:\";\n\nfunction isBillingBlock(b: unknown): boolean {\n const text = typeof b === \"string\" ? b : b && typeof b === \"object\" ? (b as { text?: unknown }).text : undefined;\n return typeof text === \"string\" && text.startsWith(BILLING_PREFIX);\n}\n\n/** Fingerprint view of the system prompt without the billing block (the forwarded body is never touched). */\nfunction systemForHash(system: unknown): unknown {\n return Array.isArray(system) ? system.filter((b) => !isBillingBlock(b)) : isBillingBlock(system) ? undefined : system;\n}\n\nexport interface PromptFingerprint {\n system: string;\n tools: string;\n messages: string;\n}\n\nexport function fingerprint(body: Record<string, unknown>): PromptFingerprint {\n const msgs = Array.isArray(body.messages) ? body.messages.slice(0, PREFIX_MESSAGES) : [];\n return {\n system: sha(stripCacheControl(systemForHash(body.system))),\n tools: sha(stripCacheControl(body.tools)),\n messages: sha(stripCacheControl(msgs)),\n };\n}\n\nexport class BustDetector {\n private seen = new Map<string, PromptFingerprint>();\n busts = 0;\n\n constructor(private readonly maxSessions = 500) {}\n\n /** Changed parts vs the previous request of this session (empty when stable or first). */\n check(session: string | undefined, fp: PromptFingerprint): string[] {\n if (!session) return [];\n const prev = this.seen.get(session);\n this.seen.delete(session);\n this.seen.set(session, fp);\n if (this.seen.size > this.maxSessions) this.seen.delete(this.seen.keys().next().value as string);\n if (!prev) return [];\n const changed = ([\"system\", \"tools\", \"messages\"] as const).filter((k) => prev[k] !== fp[k]);\n if (changed.length) this.busts++;\n return changed;\n }\n}\n\n/** Seconds since the previous request of the same session (start to start). */\nexport class GapTracker {\n private last = new Map<string, number>();\n\n constructor(private readonly maxSessions = 2000) {}\n\n gap(session: string | undefined, nowMs: number): number | undefined {\n if (!session) return undefined;\n const prev = this.last.get(session);\n this.last.delete(session);\n this.last.set(session, nowMs);\n if (this.last.size > this.maxSessions) this.last.delete(this.last.keys().next().value as string);\n return prev === undefined ? undefined : Math.round((nowMs - prev) / 100) / 10;\n }\n}\n\nexport function headerValue(h: string | string[] | undefined, max = 120): string | undefined {\n const v = Array.isArray(h) ? h[0] : h;\n return v ? v.slice(0, max) : undefined;\n}\n\nexport function sessionTag(header: string | string[] | undefined): string | undefined {\n const v = Array.isArray(header) ? header[0] : header;\n return v ? createHash(\"sha256\").update(v).digest(\"hex\").slice(0, 8) : undefined;\n}\n\n// ---- storage + stats -----------------------------------------------------\n\nexport function appendUsage(row: UsageRow, file: string = USAGE_LOG_FILE): void {\n try {\n mkdirSync(dirname(file), { recursive: true });\n appendFileSync(file, JSON.stringify(row) + \"\\n\");\n } catch {\n /* accounting must never break routing */\n }\n}\n\nexport function readUsage(file: string = USAGE_LOG_FILE): UsageRow[] {\n if (!existsSync(file)) return [];\n const rows: UsageRow[] = [];\n for (const l of readFileSync(file, \"utf8\").split(\"\\n\")) {\n if (!l) continue;\n try {\n rows.push(JSON.parse(l) as UsageRow);\n } catch {\n /* skip torn line */\n }\n }\n return rows;\n}\n\nexport function parseSince(s: string): number {\n const m = /^(\\d+)\\s*([smhd])$/.exec(s.trim());\n if (!m) throw new Error(`bad --since \"${s}\" (use e.g. 30m, 1h, 2d)`);\n return Number(m[1]) * { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2] as \"s\" | \"m\" | \"h\" | \"d\"];\n}\n\nexport interface StatsGroup extends Usage {\n provider: string;\n model: string;\n requests: number;\n hit: number;\n write: number;\n miss: number;\n busts: number;\n bytes: number;\n /** cache_read / (cache_read + cache_creation + input) */\n hitRatio: number;\n}\n\nexport function aggregate(rows: UsageRow[], sinceMs?: number, now: number = Date.now()): StatsGroup[] {\n const groups = new Map<string, StatsGroup>();\n for (const r of rows) {\n if (sinceMs !== undefined && Date.parse(r.ts) < now - sinceMs) continue;\n const key = `${r.provider}\\u0000${r.model}`;\n let g = groups.get(key);\n if (!g) {\n g = { ...emptyUsage(), provider: r.provider, model: r.model, requests: 0, hit: 0, write: 0, miss: 0, busts: 0, bytes: 0, hitRatio: 0 };\n groups.set(key, g);\n }\n g.requests++;\n g[r.cache]++;\n if (r.bust?.length) g.busts++;\n g.bytes += r.bytes ?? 0;\n for (const k of KEYS) g[k] += r[k] ?? 0;\n }\n for (const g of groups.values()) {\n const denom = g.cache_read_input_tokens + g.cache_creation_input_tokens + g.input_tokens;\n g.hitRatio = denom ? g.cache_read_input_tokens / denom : 0;\n }\n return [...groups.values()].sort((a, b) => a.provider.localeCompare(b.provider) || a.model.localeCompare(b.model));\n}\n\nexport interface SessionStats extends Usage {\n session: string;\n /** display name, null when PAI does not know the session */\n name: string | null;\n requests: number;\n busts: number;\n firstTs: string;\n lastTs: string;\n hitRatio: number;\n}\n\n/** Totals per session (raw session_id, else the hash tag, else \"-\"). */\nexport function aggregateBySession(\n rows: UsageRow[],\n sinceMs?: number,\n now: number = Date.now(),\n nameOf: (id: string) => string | null = () => null\n): SessionStats[] {\n const groups = new Map<string, SessionStats>();\n for (const r of rows) {\n if (sinceMs !== undefined && Date.parse(r.ts) < now - sinceMs) continue;\n const key = r.session_id ?? r.session ?? \"-\";\n let g = groups.get(key);\n if (!g) {\n g = { ...emptyUsage(), session: key, name: r.session_id ? nameOf(r.session_id) : null, requests: 0, busts: 0, firstTs: r.ts, lastTs: r.ts, hitRatio: 0 };\n groups.set(key, g);\n }\n g.requests++;\n if (r.bust?.length) g.busts++;\n if (r.ts < g.firstTs) g.firstTs = r.ts;\n if (r.ts > g.lastTs) g.lastTs = r.ts;\n for (const k of KEYS) g[k] += r[k] ?? 0;\n }\n for (const g of groups.values()) {\n const denom = g.cache_read_input_tokens + g.cache_creation_input_tokens + g.input_tokens;\n g.hitRatio = denom ? g.cache_read_input_tokens / denom : 0;\n }\n return [...groups.values()].sort((a, b) => b.lastTs.localeCompare(a.lastTs));\n}\n\n// ---- session-kind split: continuous vs one-shot --------------------------\n\n/** Token totals + hit ratio of one group of sessions. */\nexport interface SessionKindStats extends Usage {\n /** Sessions in the group. */\n sessions: number;\n requests: number;\n hitRatio: number;\n}\n\n/** Continuous (multi-request) vs one-shot (exactly one request) sessions. */\nexport interface SessionKindSplit {\n /** A provider-side prompt cache can serve these across requests. */\n continuous: SessionKindStats;\n /** One request, then the session is gone — every request is a cold cache. */\n oneShot: SessionKindStats;\n}\n\nconst emptyKindStats = (): SessionKindStats => ({\n ...emptyUsage(),\n sessions: 0,\n requests: 0,\n hitRatio: 0,\n});\n\n/**\n * Split usage rows by how many requests each session made: a continuous\n * session carries the provider prompt cache from request to request, a\n * one-shot session (the shape of today's `pai worker run` traffic — 158 of\n * 161 glm sessions carry exactly one request) is a fresh spawn each time and\n * shows what the cache is worth. The overall hitRatio alone averages these\n * two populations into one number that predicts neither.\n */\nexport function splitBySessionKind(\n rows: UsageRow[],\n sinceMs?: number,\n now: number = Date.now()\n): SessionKindSplit {\n interface PerSession extends Usage {\n requests: number;\n }\n const bySession = new Map<string, PerSession>();\n for (const r of rows) {\n if (sinceMs !== undefined && Date.parse(r.ts) < now - sinceMs) continue;\n const key = r.session_id ?? r.session ?? \"-\";\n let g = bySession.get(key);\n if (!g) {\n g = { ...emptyUsage(), requests: 0 };\n bySession.set(key, g);\n }\n g.requests++;\n for (const k of KEYS) g[k] += r[k] ?? 0;\n }\n const out: SessionKindSplit = { continuous: emptyKindStats(), oneShot: emptyKindStats() };\n const ratio = (g: SessionKindStats): void => {\n const denom = g.cache_read_input_tokens + g.cache_creation_input_tokens + g.input_tokens;\n g.hitRatio = denom ? g.cache_read_input_tokens / denom : 0;\n };\n for (const g of bySession.values()) {\n const bucket = g.requests > 1 ? out.continuous : out.oneShot;\n bucket.sessions++;\n bucket.requests += g.requests;\n for (const k of KEYS) bucket[k] += g[k];\n }\n ratio(out.continuous);\n ratio(out.oneShot);\n return out;\n}\n\nexport function formatSessionStats(groups: SessionStats[]): string {\n if (!groups.length) return \"no router requests recorded\";\n const head = [\"session\", \"name\", \"req\", \"in\", \"out\", \"cache_w\", \"cache_r\", \"hit%\", \"busts\", \"last\"];\n const lines = groups.map((g) => [\n g.session.slice(0, 8),\n g.name ?? \"-\",\n String(g.requests),\n String(g.input_tokens),\n String(g.output_tokens),\n String(g.cache_creation_input_tokens),\n String(g.cache_read_input_tokens),\n (g.hitRatio * 100).toFixed(1),\n String(g.busts),\n g.lastTs,\n ]);\n const widths = head.map((h, i) => Math.max(h.length, ...lines.map((l) => l[i]!.length)));\n const fmt = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i]!)).join(\" \").trimEnd();\n return [fmt(head), ...lines.map(fmt)].join(\"\\n\");\n}\n\nexport function formatStats(groups: StatsGroup[]): string {\n if (!groups.length) return \"no router requests recorded\";\n const head = [\"provider\", \"model\", \"req\", \"in\", \"out\", \"cache_w\", \"cache_r\", \"hit/write/miss\", \"hit%\", \"busts\"];\n const lines = groups.map((g) => [\n g.provider,\n g.model,\n String(g.requests),\n String(g.input_tokens),\n String(g.output_tokens),\n String(g.cache_creation_input_tokens),\n String(g.cache_read_input_tokens),\n `${g.hit}/${g.write}/${g.miss}`,\n (g.hitRatio * 100).toFixed(1),\n String(g.busts),\n ]);\n const widths = head.map((h, i) => Math.max(h.length, ...lines.map((l) => l[i]!.length)));\n const fmt = (cols: string[]) => cols.map((c, i) => c.padEnd(widths[i]!)).join(\" \").trimEnd();\n return [fmt(head), ...lines.map(fmt)].join(\"\\n\");\n}\n","/**\n * router.ts — local model router. One Claude Code session points its\n * ANTHROPIC_BASE_URL here and /model picks the provider: each POST\n * /v1/messages (and count_tokens) is routed by the body's `model`.\n *\n * claude ids → caveman (CLAUDE_UPSTREAM), inbound auth passed through\n * other ids → the workers.yaml provider that lists the model; inbound auth\n * dropped, the provider key injected as Authorization: Bearer\n *\n * Bodies and keys are never logged.\n */\n\nimport http from \"node:http\";\nimport https from \"node:https\";\nimport {\n BustDetector,\n emptyUsage,\n fingerprint,\n GapTracker,\n headerValue,\n sessionTag,\n UsageSniffer,\n verdict,\n type Usage,\n type UsageRow,\n} from \"./usage.js\";\nimport { readWorkersSection, resolveProviderKey, type WorkerProvider } from \"../workers/config.js\";\n\nexport const ROUTER_PORT = 8790;\nexport const CLAUDE_UPSTREAM = \"http://127.0.0.1:8787/w/claude\";\n\nexport type Target =\n | { kind: \"claude\"; model: string; base: string }\n | { kind: \"provider\"; model: string; name: string; provider: WorkerProvider };\n\nexport type Resolution = Target | { kind: \"unknown\"; model: string; routable: string[] };\n\n/** Claude Code appends \"[1m]\" for the 1M-context variant; upstreams reject it. */\nexport function stripContextSuffix(model: string): string {\n return model.replace(/\\[1m\\]$/i, \"\");\n}\n\nfunction isClaudeId(model: string): boolean {\n return /^claude-/.test(model) || [\"sonnet\", \"opus\", \"haiku\", \"default\", \"opusplan\"].includes(model);\n}\n\n/** model id (suffix-free) → provider name, from enabled non-native providers. */\nexport function routingTable(providers: Record<string, WorkerProvider>): Map<string, string> {\n const table = new Map<string, string>();\n for (const [name, p] of Object.entries(providers)) {\n if (p.native || p.enabled === false || !p.baseUrl || p.protocol !== \"anthropic\" || (p.engine ?? \"claude\") !== \"claude\") continue;\n for (const id of Object.values(p.models)) {\n if (!id) continue;\n const bare = stripContextSuffix(id);\n if (!table.has(bare)) table.set(bare, name);\n }\n }\n return table;\n}\n\nexport function resolveModel(\n rawModel: string,\n providers: Record<string, WorkerProvider>,\n claudeBase: string = CLAUDE_UPSTREAM\n): Resolution {\n const model = stripContextSuffix(rawModel);\n if (isClaudeId(model)) return { kind: \"claude\", model, base: claudeBase };\n const table = routingTable(providers);\n let name = table.get(model);\n if (!name) {\n // family prefixes: glm-* → glm, k3/kimi-* → kimi\n if (/^glm-/.test(model) && providers.glm) name = \"glm\";\n else if (/^(k3|kimi-)/.test(model) && providers.kimi) name = \"kimi\";\n }\n const p = name ? providers[name] : undefined;\n if (!name || !p) return { kind: \"unknown\", model, routable: [\"claude-*\", ...table.keys()] };\n return { kind: \"provider\", model, name, provider: p };\n}\n\n/** thinking blocks carry Anthropic signatures other providers reject. */\nexport function stripThinking(body: Record<string, unknown>): Record<string, unknown> {\n const messages = body.messages;\n if (!Array.isArray(messages)) return body;\n const cleaned = messages.map((m) => {\n if (!m || typeof m !== \"object\" || !Array.isArray((m as { content?: unknown }).content)) return m;\n const content = (m as { content: Array<{ type?: string }> }).content.filter(\n (b) => !(b && (b.type === \"thinking\" || b.type === \"redacted_thinking\"))\n );\n return { ...(m as object), content };\n });\n return { ...body, messages: cleaned };\n}\n\nexport interface Upstream {\n url: URL;\n headers: Record<string, string>;\n body: Buffer;\n}\n\nexport interface RouterOptions {\n /**\n * Remote-address gate for the /v1 routes, set whenever the router listens\n * beyond loopback (router.bind): a request whose socket address fails it is\n * answered 403 before any routing — the inbound auth pass-through must not\n * leave the loopback+tailnet perimeter. Cockpit routes are handled before\n * this (they carry their own token, see src/router/cockpit-auth.ts).\n */\n remoteAllowed?: (remoteAddress: string | undefined) => boolean;\n}\n\n// accept-encoding dropped: usage is parsed from the plain response as it streams\nconst HOP = new Set([\"host\", \"connection\", \"content-length\", \"transfer-encoding\", \"keep-alive\", \"accept-encoding\"]);\nconst AUTH = new Set([\"authorization\", \"x-api-key\"]);\n\nfunction joinUrl(base: string, path: string): URL {\n return new URL(base.replace(/\\/+$/, \"\") + path);\n}\n\n/** Build the upstream request, or an error status+message. */\nexport function prepare(\n path: string,\n inHeaders: http.IncomingHttpHeaders,\n rawBody: Buffer,\n providers: Record<string, WorkerProvider>,\n claudeBase: string = CLAUDE_UPSTREAM\n): { ok: true; up: Upstream; target: Target; requested: string } | { ok: false; status: number; message: string } {\n let body: Record<string, unknown>;\n try {\n body = JSON.parse(rawBody.toString(\"utf8\"));\n } catch {\n return { ok: false, status: 400, message: \"invalid JSON body\" };\n }\n if (typeof body.model !== \"string\") return { ok: false, status: 400, message: \"missing model\" };\n const res = resolveModel(body.model, providers, claudeBase);\n if (res.kind === \"unknown\") {\n return {\n ok: false,\n status: 400,\n message: `unknown model \"${res.model}\"; routable: ${res.routable.join(\", \")}`,\n };\n }\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(inHeaders)) {\n const lk = k.toLowerCase();\n if (HOP.has(lk) || v === undefined) continue;\n if (res.kind === \"provider\" && AUTH.has(lk)) continue;\n headers[lk] = Array.isArray(v) ? v.join(\", \") : v;\n }\n let out: Record<string, unknown> = { ...body, model: res.model };\n let base: string;\n if (res.kind === \"claude\") {\n base = res.base;\n } else {\n out = stripThinking(out);\n base = res.provider.baseUrl;\n let token: string | null;\n try {\n token = resolveProviderKey(res.provider);\n } catch (e) {\n return { ok: false, status: 502, message: (e as Error).message };\n }\n headers.authorization = `Bearer ${token ?? \"local\"}`;\n }\n const buf = Buffer.from(JSON.stringify(out));\n headers[\"content-length\"] = String(buf.length);\n return { ok: true, up: { url: joinUrl(base, path), headers, body: buf }, target: res, requested: body.model };\n}\n\nconst MODELS_PATH = /^\\/v1\\/models(?:\\/([^/?]+))?(\\?.*)?$/;\n\n/** Anthropic models-API entry for a routed id. */\nexport function modelEntry(id: string): { id: string; type: \"model\"; display_name: string; created_at: string } {\n return { id, type: \"model\", display_name: id, created_at: \"2025-01-01T00:00:00Z\" };\n}\n\n/** GET /v1/models[/id] for non-claude ids, from the routing table; null = not ours. */\nexport function modelsReply(\n path: string,\n providers: Record<string, WorkerProvider>\n): { status: number; body: unknown } | null {\n const m = MODELS_PATH.exec(path);\n if (!m) return null;\n if (!m[1]) {\n const data = [...routingTable(providers).keys()].map(modelEntry);\n return { status: 200, body: { data, has_more: false, first_id: data[0]?.id ?? null, last_id: data.at(-1)?.id ?? null } };\n }\n const res = resolveModel(decodeURIComponent(m[1]), providers);\n if (res.kind === \"provider\") return { status: 200, body: modelEntry(res.model) };\n if (res.kind === \"unknown\") {\n return { status: 404, body: { type: \"error\", error: { type: \"not_found_error\", message: `model \"${res.model}\" not found` } } };\n }\n return null;\n}\n\n/** chars/4 over the request body, for providers without count_tokens. */\nexport function estimateTokens(rawBody: Buffer): number {\n return Math.ceil(rawBody.length / 4);\n}\n\nfunction isLocal(u: URL): boolean {\n return u.hostname === \"127.0.0.1\" || u.hostname === \"localhost\";\n}\n\nexport function createRouter(\n loadProviders: () => Record<string, WorkerProvider> = () => readWorkersSection().workers.providers,\n claudeBase: string = CLAUDE_UPSTREAM,\n log: (line: string) => void = (l) => process.stderr.write(l + \"\\n\"),\n onUsage?: (row: UsageRow) => void,\n /** serves GET pages (the cockpit) before routing; true = handled */\n extra?: (req: http.IncomingMessage, res: http.ServerResponse) => boolean,\n options?: RouterOptions\n): http.Server {\n return http.createServer(routerHandler(loadProviders, claudeBase, log, onUsage, extra, options));\n}\n\n/**\n * The router's request handler, built separately from the server so a\n * non-loopback bind can put a second http.Server on the same handler (Node\n * refuses a second listen() on a listening server — ERR_SERVER_ALREADY_LISTEN)\n * while every entry point shares one BustDetector/GapTracker.\n */\nexport function routerHandler(\n loadProviders: () => Record<string, WorkerProvider> = () => readWorkersSection().workers.providers,\n claudeBase: string = CLAUDE_UPSTREAM,\n log: (line: string) => void = (l) => process.stderr.write(l + \"\\n\"),\n onUsage?: (row: UsageRow) => void,\n /** serves GET pages (the cockpit) before routing; true = handled */\n extra?: (req: http.IncomingMessage, res: http.ServerResponse) => boolean,\n options?: RouterOptions\n): (req: http.IncomingMessage, res: http.ServerResponse) => void {\n const busts = new BustDetector();\n const gaps = new GapTracker();\n return (req, res) => {\n if (extra?.(req, res)) return;\n const guard = options?.remoteAllowed;\n if (guard && !guard(req.socket.remoteAddress ?? undefined)) {\n res.writeHead(403, { \"content-type\": \"application/json\" });\n res.end(\n JSON.stringify({\n type: \"error\",\n error: { type: \"permission_error\", message: \"pai router: /v1 is limited to loopback and the tailnet (100.64.0.0/10)\" },\n })\n );\n log(`${new Date().toISOString()} ${req.method ?? \"-\"} ${(req.url ?? \"/\").split(\"?\")[0]} 403 remote-not-allowed`);\n return;\n }\n const started = Date.now();\n const path = req.url ?? \"/\";\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n const fail = (status: number, message: string, model = \"-\", prov = \"-\") => {\n if (!res.headersSent) {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ type: \"error\", error: { type: \"invalid_request_error\", message } }));\n } else res.end();\n log(`${new Date().toISOString()} ${model} ${prov} ${status} ${Date.now() - started}ms`);\n };\n const isModels = req.method === \"GET\" && MODELS_PATH.test(path);\n const isMessages = req.method === \"POST\" && /^\\/v1\\/messages(\\/count_tokens)?(\\?.*)?$/.test(path);\n if (!isModels && !isMessages) {\n return fail(404, `pai router: no route for ${req.method} ${path.split(\"?\")[0]}`, `${req.method} ${path.split(\"?\")[0]}`);\n }\n let providers: Record<string, WorkerProvider>;\n try {\n providers = loadProviders();\n } catch (e) {\n return fail(500, `workers config: ${(e as Error).message}`);\n }\n if (isModels) {\n const local = modelsReply(path, providers);\n if (local) {\n res.writeHead(local.status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(local.body));\n log(`${new Date().toISOString()} GET ${path.split(\"?\")[0]} local ${local.status} ${Date.now() - started}ms`);\n return;\n }\n // claude ids (and the list) pass to the anthropic upstream unchanged\n const headers: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.headers)) {\n if (!HOP.has(k.toLowerCase()) && v !== undefined) headers[k.toLowerCase()] = Array.isArray(v) ? v.join(\", \") : v;\n }\n const url = joinUrl(claudeBase, path);\n const ureq = (url.protocol === \"https:\" ? https : http).request(url, { method: \"GET\", headers }, (ures) => {\n const rh = { ...ures.headers };\n delete rh[\"transfer-encoding\"];\n delete rh.connection;\n res.writeHead(ures.statusCode ?? 502, rh);\n ures.pipe(res);\n ures.on(\"end\", () => log(`${new Date().toISOString()} GET ${path.split(\"?\")[0]} anthropic ${ures.statusCode} ${Date.now() - started}ms`));\n });\n ureq.on(\"error\", (e: NodeJS.ErrnoException) => fail(502, `upstream error: ${e.code ?? e.message}`, \"GET models\", \"anthropic\"));\n res.on(\"close\", () => ureq.destroy());\n ureq.end();\n return;\n }\n const prep = prepare(path, req.headers, Buffer.concat(chunks), providers, claudeBase);\n if (!prep.ok) return fail(prep.status, prep.message);\n const { up, target, requested } = prep;\n const prov = target.kind === \"claude\" ? \"anthropic\" : target.name;\n const isCount = path.includes(\"/count_tokens\");\n const session = sessionTag(req.headers[\"x-claude-code-session-id\"]);\n const sessionId = headerValue(req.headers[\"x-claude-code-session-id\"], 64);\n const gap = onUsage && !isCount ? gaps.gap(sessionId, started) : undefined;\n const changed =\n onUsage && !isCount ? busts.check(session, fingerprint(JSON.parse(up.body.toString(\"utf8\")))) : [];\n const record = (status: number, usage: Usage) => {\n if (!onUsage || isCount) return;\n const row: UsageRow = {\n ts: new Date().toISOString(),\n model: target.model,\n provider: prov,\n status,\n ms: Date.now() - started,\n ...usage,\n bytes: up.body.length,\n cache: verdict(usage),\n };\n if (session) row.session = session;\n if (sessionId) row.session_id = sessionId;\n row.requested_model = requested;\n row.routed_model = target.model;\n if (gap !== undefined) row.gap_s = gap;\n const ua = headerValue(req.headers[\"user-agent\"]);\n const app = headerValue(req.headers[\"x-app\"], 40);\n if (ua) row.user_agent = ua;\n if (app) row.x_app = app;\n if (changed.length) row.bust = changed;\n onUsage(row);\n };\n const mod = up.url.protocol === \"https:\" ? https : http;\n const ureq = mod.request(\n up.url,\n { method: \"POST\", headers: up.headers },\n (ures) => {\n if (isCount && target.kind === \"provider\" && (ures.statusCode ?? 0) >= 400) {\n // provider has no count_tokens: estimate instead of failing the client\n ures.resume();\n const raw = Buffer.concat(chunks);\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify({ input_tokens: estimateTokens(raw) }));\n log(`${new Date().toISOString()} ${target.model} ${prov} count_tokens-estimate ${Date.now() - started}ms`);\n return;\n }\n const rh = { ...ures.headers };\n delete rh[\"transfer-encoding\"];\n delete rh.connection;\n delete rh[\"keep-alive\"];\n res.writeHead(ures.statusCode ?? 502, rh);\n const sniff = onUsage && !isCount ? new UsageSniffer(ures.headers[\"content-type\"]) : undefined;\n if (sniff) ures.on(\"data\", (c: Buffer) => sniff.push(c));\n ures.pipe(res);\n ures.on(\"end\", () => {\n log(`${new Date().toISOString()} ${target.model} ${prov} ${ures.statusCode} ${Date.now() - started}ms`);\n record(ures.statusCode ?? 502, sniff ? sniff.finish() : emptyUsage());\n });\n }\n );\n ureq.on(\"error\", (e: NodeJS.ErrnoException) => {\n record(e.code === \"ECONNREFUSED\" ? 503 : 502, emptyUsage());\n if (target.kind === \"provider\" && isLocal(up.url) && e.code === \"ECONNREFUSED\") {\n return fail(\n 503,\n `local model down — start it with pai worker local <tier>`,\n target.model,\n prov\n );\n }\n fail(502, `upstream error: ${e.code ?? e.message}`, target.model, prov);\n });\n res.on(\"close\", () => ureq.destroy());\n ureq.end(up.body);\n });\n };\n}\n","/**\n * run-env.ts — the provider environment every Claude Code spawn must run with.\n *\n * Extracted from run.ts so daemon-side background spawns (session summaries,\n * context handovers, KG extraction) go through the exact same env the worker\n * runner uses, instead of importing the whole runner into the daemon.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { delimiter, join } from \"node:path\";\n\nimport { loadConfig } from \"../daemon/config.js\";\nimport { ROUTER_PORT } from \"../router/router.js\";\nimport { resolveModelCapability, resolveProviderKey, type WorkerProvider } from \"./config.js\";\n\n/**\n * Session-identity variables the SPAWNING session leaves in the environment.\n * Inherited, they make a headless child attach to the spawner's messaging\n * socket instead of standing on its own — and a worktree-mode child then\n * starts with its core tools (Bash/Read/…) deferred out of its reach, left\n * with nothing but the tool-registry search (2026-09-18). The child is its\n * own session, so none of these may cross the spawn boundary.\n */\nconst SESSION_IDENTITY_VARS = [\n \"CLAUDE_CODE_MESSAGING_SOCKET\",\n \"CLAUDE_CODE_MESSAGING_TOKEN\",\n \"CLAUDE_CODE_SESSION_ID\",\n \"CLAUDE_CODE_CHILD_SESSION\",\n \"CLAUDE_PID\",\n \"CLAUDECODE\",\n] as const;\n\n/**\n * Harness settings the spawner's environment carries that a headless worker\n * must not keep. ENABLE_TOOL_SEARCH reaches every pai process through the\n * user settings' env block, so a pai spawned from inside a Claude session\n * passes it on to worker children; inherited, the headless child starts with\n * every core tool deferred out of reach — only ToolSearch remains callable,\n * granted tools included (2026-09-18, verified by stripping exactly this one\n * var). Interactive runs set it deliberately below.\n *\n * PAI_WORKER_ALLOW_HIDDEN is the operator's own consent to run a worker with\n * no pane. It must die at the spawn boundary like the session identities\n * above: a worker (or a daemon born of an operator shell) never passes the\n * consent on, so \"allowed hidden\" applies only to the process it was set for.\n */\nconst HEADLESS_STRIP_VARS = [\"ENABLE_TOOL_SEARCH\", \"PAI_WORKER_ALLOW_HIDDEN\"] as const;\n\n/**\n * Provider-specific env vars a native-Anthropic run must never carry — not\n * set by this function, and stripped even when a parent shell exported them\n * (e.g. this worker was itself spawned from a GLM-configured environment).\n */\nconst NATIVE_STRIP_VARS = [\n \"ANTHROPIC_BASE_URL\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\",\n] as const;\n\n/**\n * Environment for a run through `provider`. Caller's env minus the Anthropic\n * key. `routerEnabled` routes the run through the local model router instead\n * of the provider's own URL (the openai-protocol proxy route always wins);\n * unset keeps the provider-direct route.\n */\nexport function buildRunEnv(\n provider: WorkerProvider,\n headless: boolean,\n proxyUrl?: string,\n providerName?: string,\n routerEnabled?: boolean\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n delete env.ANTHROPIC_API_KEY;\n if (headless) {\n for (const k of SESSION_IDENTITY_VARS) delete env[k];\n for (const k of HEADLESS_STRIP_VARS) delete env[k];\n // workers have no memory to recall; the auto-memory prompt only sends\n // them to read a MEMORY.md that does not exist\n env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = \"1\";\n }\n\n if (provider.native) {\n // Plain Claude Code on its own OAuth/Max-plan login: no base URL, no\n // token, no provider-specific model pins — and none inherited from a\n // shell that had a different provider exported into it either.\n for (const k of NATIVE_STRIP_VARS) delete env[k];\n env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = \"1\";\n if (headless) {\n env.PAI_WORKER = \"1\";\n env.PAI_WORKER_PROVIDER = \"anthropic\";\n } else {\n env.ENABLE_TOOL_SEARCH = \"true\";\n }\n return env;\n }\n\n let token = \"local\";\n if (proxyUrl) {\n // openai-protocol provider: the proxy holds the real key; the runner only\n // needs a placeholder so Claude Code sends an auth header at all\n env.ANTHROPIC_BASE_URL = proxyUrl;\n } else if (routerEnabled) {\n // Router on: the run goes through the local model router, which resolves\n // the requested model to this provider, injects the key itself and logs\n // usage+cache per session — the same settings.json pin an interactive\n // session uses (5f870a4). A provider-direct route would send the same\n // traffic past that accounting entirely (measured 2026-10-17: pooled\n // slots carried 87% cache while the router log had zero rows for them),\n // so there is no provider-direct fallback while the router is enabled.\n env.ANTHROPIC_BASE_URL = routerUrl();\n } else {\n const resolved = resolveProviderKey(provider);\n if (resolved) token = resolved;\n env.ANTHROPIC_BASE_URL = provider.baseUrl;\n }\n\n env.ANTHROPIC_AUTH_TOKEN = token;\n env.ANTHROPIC_DEFAULT_HAIKU_MODEL = resolveModelCapability(provider, \"fast\");\n env.ANTHROPIC_DEFAULT_SONNET_MODEL = provider.models.default;\n env.ANTHROPIC_DEFAULT_OPUS_MODEL = provider.models.default;\n for (const [k, v] of Object.entries(provider.env)) env[k] = v;\n env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = \"1\";\n if (headless) {\n env.PAI_WORKER = \"1\";\n // read by the whisper-rules hook: a non-anthropic worker gets no operator rules\n if (providerName) env.PAI_WORKER_PROVIDER = providerName;\n // A local model gets the task, not the operator's interactive-session\n // overhead (user+project CLAUDE.md, memory index): it echoed that text\n // instead of working (2026-10-15). The two switches cannot separate user\n // from project CLAUDE.md, so both go. The output style is dropped in\n // claudeCommand via --settings.\n env.CLAUDE_CODE_DISABLE_CLAUDE_MDS = \"1\";\n env.CLAUDE_CODE_DISABLE_AUTO_MEMORY = \"1\";\n } else {\n env.ENABLE_TOOL_SEARCH = \"true\";\n }\n return env;\n}\n\n/** First-party route a native run is pinned to when caveman is off. */\nexport const ANTHROPIC_FIRST_PARTY_URL = \"https://api.anthropic.com\";\n\n/** Loopback URL of the local model router — the route every accounted run takes. */\nexport function routerUrl(): string {\n return `http://127.0.0.1:${ROUTER_PORT}`;\n}\n\n/**\n * The argv head every Claude Code spawn starts from, for an env built by\n * buildRunEnv. The user settings' env block outranks the process env\n * (measured 2026-09-22: an unreachable ANTHROPIC_BASE_URL in the process env\n * was ignored while settings.json carried a caveman proxy route, and a glm\n * worker's bearer token ended at Anthropic with a 401), so a route set in\n * `env` is not enough once a machine-wide proxy is installed — it is pinned\n * again with `--settings`, which outranks user settings. A native run goes\n * through `caveman claude` when workers.caveman is on and the CLI is on\n * PATH; off, it is pinned to api.anthropic.com so a global route cannot\n * pull it in either.\n */\nexport function claudeCommand(env: NodeJS.ProcessEnv, caveman: boolean, routerEnabled?: boolean): string[] {\n const route = env.ANTHROPIC_BASE_URL;\n if (!route && caveman) {\n if (onPath(\"caveman\", env)) return [\"caveman\", \"claude\"];\n process.stderr.write(\"pai: workers.caveman is on but `caveman` is not on PATH; running claude directly\\n\");\n }\n // set by buildRunEnv for a headless non-anthropic worker: no operator output style\n const extra = env.CLAUDE_CODE_DISABLE_CLAUDE_MDS === \"1\" ? { outputStyle: \"default\" } : undefined;\n return [\"claude\", ...pinnedSettingsArgs(env, routerEnabled, extra)];\n}\n\nconst PINNED_KEYS = [\n \"ANTHROPIC_AUTH_TOKEN\",\n \"ANTHROPIC_DEFAULT_HAIKU_MODEL\",\n \"ANTHROPIC_DEFAULT_SONNET_MODEL\",\n \"ANTHROPIC_DEFAULT_OPUS_MODEL\",\n];\n\n/**\n * `--settings` argv pinning the provider's route, token and tier models: the\n * user settings' env outranks the process env, so a provider launch would\n * otherwise authenticate its token against the user's proxy (401). Shared by\n * workers and interactive `pai launch`. A native run (no provider route) is\n * pinned to the local model router when router.enabled, so `/model` can switch\n * provider and every request is accounted; else to api.anthropic.com.\n * `extra` spreads over the settings object (claudeCommand uses it to drop the\n * operator output style for a headless non-anthropic worker).\n */\nexport function pinnedSettingsArgs(\n env: NodeJS.ProcessEnv,\n routerEnabled: boolean = loadConfig().router.enabled,\n extra?: Record<string, unknown>\n): string[] {\n const native = routerEnabled ? routerUrl() : ANTHROPIC_FIRST_PARTY_URL;\n const pinned: Record<string, string> = { ANTHROPIC_BASE_URL: env.ANTHROPIC_BASE_URL || native };\n if (env.ANTHROPIC_BASE_URL) for (const k of PINNED_KEYS) if (env[k]) pinned[k] = env[k] as string;\n return [\"--settings\", JSON.stringify({ env: pinned, ...extra })];\n}\n\nfunction onPath(bin: string, env: NodeJS.ProcessEnv): boolean {\n return (env.PATH ?? \"\").split(delimiter).some((d) => d && existsSync(join(d, bin)));\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { f as readMainConfigRaw } from "./config-DSdCEcHS.mjs";
|
|
2
2
|
import { d as resolvePaiFile, u as paiHomePath } from "./runtime-paths-DkZoxofH.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { C as expandHome, c as DEFAULT_LOG_DIR } from "./config-CYjjGSWq.mjs";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
@@ -820,4 +820,4 @@ function describeTool(name, inp) {
|
|
|
820
820
|
|
|
821
821
|
//#endregion
|
|
822
822
|
export { writeContinueToForge as A, parseRunnerArgs as B, formatStale as C, parseStatusConfig as D, loadStatusConfig as E, preambleBlock as F, ledgerPath as G, stripPromptValues as H, preambleLine as I, statusPath as J, panesDir as K, projectRoot as L, PREAMBLE_MAX_BYTES as M, PREAMBLE_REL as N, setClaim as O, loadPreamble as P, appendSystemPromptArgs as R, PROJECT_CONFIG_REL as S, listStale as T, ensureNoMcpConfig as U, shortText as V, eventsPath as W, workersLogDir as Y, parseSleepSecs as _, contextLabel as a, waitForTerminalStatus as b, elapsedOf as c, isLive as d, loadStatus as f, ownsPid as g, nowStamp as h, contextFooter as i, writeSectionToForge as j, upsertSection as k, fmtTokens as l, newWorkerId as m, ageOf as n, contextPercent as o, loadStatuses as p, routingStatePath as q, alive as r, describeTool as s, UNLABELED as t, isChatPane as u, saveStatus as v, listClaims as w, ForgeError as x, setWorkerLabel as y, longInlinePromptHint as z };
|
|
823
|
-
//# sourceMappingURL=status-
|
|
823
|
+
//# sourceMappingURL=status-B0br9zV0.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-B0br9zV0.mjs","names":[],"sources":["../src/workers/paths.ts","../src/workers/args.ts","../src/workers/preamble.ts","../src/session/forge-sink.ts","../src/workers/status.ts"],"sourcesContent":["/**\n * paths.ts — where worker run artefacts live.\n *\n * Everything a run writes (event mirror, status file, pane registry, routing\n * state, the empty MCP config) sits under one logDir so the whole tree is\n * disposable and configurable: `workers.logDir`, default ~/.claude/logs/workers.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { expandHome, DEFAULT_LOG_DIR, type WorkersConfig } from \"./config.js\";\nimport { paiHomePath, resolvePaiFile } from \"../config/pai-home.js\";\n\n/**\n * Absolute logDir for the given config. An explicit `logDir` in workers.yaml\n * is honored verbatim (the user chose it). Otherwise this resolves like every\n * other PAI_HOME file: PAI_HOME/logs/workers if it exists, else the old\n * ~/.claude/logs/workers (with a one-time notice), else the new path.\n */\nexport function workersLogDir(config: WorkersConfig): string {\n const expanded = expandHome(config.logDir);\n if (config.logDir !== DEFAULT_LOG_DIR) return expanded;\n return resolvePaiFile(paiHomePath(\"logs\", \"workers\"), [expanded], \"pai config migrate --logs\");\n}\n\n/** The strict empty MCP config for headless runs, written on demand. */\nexport function ensureNoMcpConfig(logDir: string): string {\n const path = noMcpConfigPath(logDir);\n if (!existsSync(path)) {\n mkdirSync(logDir, { recursive: true });\n writeFileSync(path, '{ \"mcpServers\": {} }\\n', \"utf8\");\n }\n return path;\n}\n\nexport function statusPath(logDir: string, id: string): string {\n return join(logDir, `${id}.status`);\n}\n\nexport function eventsPath(logDir: string, id: string): string {\n return join(logDir, `${id}.jsonl`);\n}\n\nexport function ledgerPath(logDir: string): string {\n return join(logDir, \"ledger.log\");\n}\n\nexport function routingStatePath(logDir: string): string {\n return join(logDir, \"routing-state.json\");\n}\n\nexport function panesDir(logDir: string): string {\n return join(logDir, \"panes\");\n}\n\n/**\n * The strict empty MCP config handed to headless workers. Written into the\n * logDir on demand (never into the user's vendor config directory — this is\n * PAI state, not vendor state).\n */\nexport function noMcpConfigPath(logDir: string): string {\n return join(logDir, \"no-mcp.json\");\n}\n","/**\n * args.ts — parse the claude-args tail that `pai worker run` receives.\n *\n * The runner needs a few things out of the caller's argument vector: the\n * prompt (for the default label), the requested --output-format (so the final\n * print matches what plain `claude -p` would have produced), whether the\n * caller already chose a --model or an --mcp-config (both suppress the\n * defaults the runner would otherwise force), any --mcp allowlist names, any\n * --allowedTools grants (their mcp__server__… entries decide which servers\n * load), and whether they appended their own system prompt (the worker\n * contract is then added alongside, not instead). Everything is passed\n * through untouched — the runner never rewrites the caller's task.\n */\n\nexport interface ParsedRunnerArgs {\n /** Prompt string after -p/--print, when the run is headless. */\n prompt: string | null;\n /** Caller's --output-format: text (default), json, or stream-json. */\n outputFormat: \"text\" | \"json\" | \"stream-json\";\n /** Args to hand to claude (minus --output-format/--verbose, which we add). */\n rest: string[];\n /** Headless: a -p/--print flag is present. */\n headless: boolean;\n /** Caller passed --model (or --model=…): do not force the provider model. */\n callerModel: boolean;\n /** Caller passed --mcp-config (or --mcp-config=…): keep their MCP setup. */\n callerMcpConfig: boolean;\n /** Caller passed --append-system-prompt: the contract is added alongside. */\n callerSystemPrompt: boolean;\n /** The caller's --append-system-prompt texts, pulled out of `rest` (claude keeps only the last flag). */\n callerSystemPrompts: string[];\n /** Caller passed --tools (or --tools=…): a project's `pai project tools` pin does not apply. */\n callerTools: boolean;\n /** --mcp values (repeatable, comma-separated inside one flag). */\n mcp: string[];\n /** --allowedTools values (repeatable, comma-separated inside one flag). */\n allowedTools: string[];\n}\n\nexport function parseRunnerArgs(argv: string[]): ParsedRunnerArgs {\n let prompt: string | null = null;\n let outputFormat: ParsedRunnerArgs[\"outputFormat\"] = \"text\";\n const rest: string[] = [];\n let headless = false;\n let callerModel = false;\n let callerMcpConfig = false;\n let callerSystemPrompt = false;\n const callerSystemPrompts: string[] = [];\n let callerTools = false;\n const mcp: string[] = [];\n const allowedTools: string[] = [];\n\n let i = 0;\n while (i < argv.length) {\n const a = argv[i];\n if (a === \"-p\" || a === \"--print\") {\n headless = true;\n rest.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n prompt = argv[i + 1];\n rest.push(prompt);\n i += 1;\n }\n } else if (a === \"--output-format\") {\n const v = argv[i + 1];\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n i += 1; // dropped; the runner prints the result in this format itself\n } else if (a.startsWith(\"--output-format=\")) {\n const v = a.slice(\"--output-format=\".length);\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n } else if (a === \"--verbose\") {\n // dropped; re-added by the runner\n } else if (a === \"--append-system-prompt\") {\n callerSystemPrompt = true;\n if (i + 1 < argv.length) callerSystemPrompts.push(argv[++i]);\n } else if (a.startsWith(\"--append-system-prompt=\")) {\n callerSystemPrompt = true;\n callerSystemPrompts.push(a.slice(\"--append-system-prompt=\".length));\n } else if (a === \"--mcp\") {\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n mcp.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--mcp=\")) {\n mcp.push(a.slice(\"--mcp=\".length));\n } else if (a === \"--allowedTools\") {\n // captured for the MCP derivation, but still claude's flag: it passes through\n rest.push(a);\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n allowedTools.push(v);\n rest.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--allowedTools=\")) {\n allowedTools.push(a.slice(\"--allowedTools=\".length));\n rest.push(a);\n } else {\n if (a === \"--model\") callerModel = true;\n if (a.startsWith(\"--model=\")) callerModel = true;\n if (a === \"--mcp-config\") callerMcpConfig = true;\n if (a.startsWith(\"--mcp-config=\")) callerMcpConfig = true;\n if (a === \"--tools\") callerTools = true;\n if (a.startsWith(\"--tools=\")) callerTools = true;\n if (\n prompt === null && !a.startsWith(\"-\") && rest.length > 0 &&\n (rest[rest.length - 1] === \"-p\" || rest[rest.length - 1] === \"--print\")\n ) {\n prompt = a;\n }\n rest.push(a);\n }\n i += 1;\n }\n\n return {\n prompt,\n outputFormat,\n rest,\n headless,\n callerModel,\n callerMcpConfig,\n callerSystemPrompt,\n callerSystemPrompts,\n callerTools,\n mcp,\n allowedTools,\n };\n}\n\n/**\n * The one --append-system-prompt flag for a spawn: claude keeps only the LAST\n * occurrence, so every piece is joined (blank line between) into a single flag.\n * Empty parts are dropped; no parts, no flag.\n */\nexport function appendSystemPromptArgs(parts: string[]): string[] {\n const text = parts.filter((p) => p.trim()).join(\"\\n\\n\");\n return text ? [\"--append-system-prompt\", text] : [];\n}\n\n/**\n * Turn `-p \"<prompt>\"` into bare `-p` (same for --print): for stream-json\n * stdin runs the prompt moves to the first user message on stdin, so the\n * value must not stay on the command line. Only prompt values directly\n * following the flag are touched; everything else passes through.\n */\nexport function stripPromptValues(argv: string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n const isPrint = a === \"-p\" || a === \"--print\";\n out.push(a);\n if (isPrint && i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n i += 1; // drop the prompt value; the flag itself stays\n }\n }\n return out;\n}\n\n/**\n * A one-line stderr hint (never blocking) for an inline -p prompt long or\n * multi-line enough to be a shell-quoting risk — write it to a file and pass\n * --spec instead. null when the prompt is short/simple enough to be fine.\n */\nexport function longInlinePromptHint(prompt: string | null): string | null {\n if (!prompt) return null;\n const newlines = (prompt.match(/\\n/g) ?? []).length;\n if (prompt.length > 600 || newlines > 3) {\n return \"hint: long inline prompts break on shell quoting — write the spec to a file and use --spec <file>\";\n }\n return null;\n}\n\n/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */\nexport function shortText(s: unknown, n: number): string {\n const t = String(s ?? \"\").split(/\\s+/).filter(Boolean).join(\" \");\n return t.length <= n ? t : t.slice(0, n - 1) + \"…\";\n}\n","/**\n * Per-project worker preamble: `.pai/worker.md` at the project root (git\n * toplevel of the cwd; the cwd itself outside git). Its text is appended to the\n * worker's system prompt right after the contract, so a project's standing\n * rules ride on every worker started there.\n */\nimport { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport const PREAMBLE_REL = \".pai/worker.md\";\nexport const PREAMBLE_MAX_BYTES = 16 * 1024;\n\nexport interface Preamble {\n path: string;\n text: string;\n lines: number;\n hash: string;\n}\n\nexport type PreambleResult = { preamble: Preamble | null } | { refused: string };\n\nexport function projectRoot(cwd: string): string {\n try {\n return execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return cwd;\n }\n}\n\n/** Missing or empty file: no preamble. Over PREAMBLE_MAX_BYTES: refused, never truncated. */\nexport function loadPreamble(cwd: string): PreambleResult {\n const path = join(projectRoot(cwd), PREAMBLE_REL);\n if (!existsSync(path)) return { preamble: null };\n const size = statSync(path).size;\n if (size > PREAMBLE_MAX_BYTES) {\n return { refused: `${path} is ${size} bytes, over the ${PREAMBLE_MAX_BYTES} byte limit — shorten it` };\n }\n const text = readFileSync(path, \"utf8\");\n if (!text.trim()) return { preamble: null };\n const hash = createHash(\"sha256\").update(text).digest(\"hex\").slice(0, 8);\n return { preamble: { path, text: text.trim(), lines: text.trimEnd().split(\"\\n\").length, hash } };\n}\n\n/**\n * The system-prompt block: a fixed header plus the raw file text, nothing run-specific\n * (no ids, paths, hashes), so every worker of a project shares one cacheable prefix.\n */\nexport function preambleBlock(p: Preamble): string {\n return `## Project worker rules\\n\\n${p.text}`;\n}\n\nexport function preambleLine(p: Preamble | null): string {\n return p ? `preamble: ${PREAMBLE_REL} (${p.lines} lines)` : \"preamble: none\";\n}\n","/**\n * Status sink: write the session handover (\"## Continue\") into a forge issue\n * instead of Notes/TODO.md. Opt-in per project via `.pai/project.yaml`:\n *\n * status:\n * sink: forge # todo (default) | forge\n * forge:\n * anchor: 283 # issue number of the anchor\n * marker: CASELEAF-HANDOVER-ANCHOR-V1 # must occur in the anchor body\n * repo: OWNER/NAME # optional; default from `git remote get-url origin`\n *\n * Anchor format: the anchor body carries the line `handover: #<n>`, naming the\n * day's handover issue. If no such line exists, the first `#<n>` after the\n * marker is used. The handover issue body holds the section\n * `<!-- pai:continue --> ... <!-- /pai:continue -->`, replaced or appended.\n * Never comments, never closes. Token: config `forge.token`, else env\n * FORGE_TOKEN, else AIBROKER_FORGE_TOKEN; never printed.\n *\n * Synchronous (callers are hooks) via curl, 5 s budget for the whole write.\n */\nimport { execFileSync, spawnSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { parse } from \"yaml\";\nimport { projectRoot } from \"../workers/preamble.js\";\nimport { readMainConfigRaw } from \"../daemon/config.js\";\n\nexport const PROJECT_CONFIG_REL = \".pai/project.yaml\";\nexport const SECTION_OPEN = \"<!-- pai:continue -->\";\nexport const SECTION_CLOSE = \"<!-- /pai:continue -->\";\nexport const FORGE_BUDGET_MS = 5000;\n\nexport interface StatusConfig {\n sink: \"todo\" | \"forge\";\n anchor?: number;\n marker?: string;\n repo?: string;\n}\n\nexport interface HttpResult { status: number; text: string }\n/** Injected in tests. Throws on transport failure. */\nexport type Http = (method: \"GET\" | \"PATCH\" | \"POST\" | \"DELETE\", url: string, token: string, body: string | undefined, timeoutMs: number) => HttpResult;\n\nexport class ForgeError extends Error {}\n\nexport function parseStatusConfig(text: string): StatusConfig {\n const doc = (parse(text) ?? {}) as { status?: { sink?: unknown; forge?: Record<string, unknown> } };\n const forge = doc.status?.forge ?? {};\n const anchor = Number(forge.anchor);\n return {\n sink: doc.status?.sink === \"forge\" ? \"forge\" : \"todo\",\n anchor: Number.isInteger(anchor) && anchor > 0 ? anchor : undefined,\n marker: typeof forge.marker === \"string\" && forge.marker ? forge.marker : undefined,\n repo: typeof forge.repo === \"string\" && forge.repo ? forge.repo : undefined,\n };\n}\n\nexport function loadStatusConfig(cwd: string): StatusConfig {\n const path = join(projectRoot(cwd), PROJECT_CONFIG_REL);\n if (!existsSync(path)) return { sink: \"todo\" };\n try {\n return parseStatusConfig(readFileSync(path, \"utf8\"));\n } catch (e) {\n throw new ForgeError(`${PROJECT_CONFIG_REL} unreadable (${(e as Error).message.split(\"\\n\")[0]})`);\n }\n}\n\n/** https://host/git/OWNER/REPO.git -> API base https://host/git/api/v1 + \"OWNER/REPO\". */\nexport function deriveRepo(originUrl: string): { apiBase: string; repo: string } {\n const m = originUrl.trim().match(/^(https?):\\/\\/(?:[^@/]+@)?([^/]+)\\/(.*?)\\/?$/);\n if (m) {\n const parts = m[3].replace(/\\.git$/, \"\").split(\"/\");\n if (parts.length >= 2) {\n const prefix = parts.slice(0, -2).join(\"/\");\n return { apiBase: `${m[1]}://${m[2]}${prefix ? `/${prefix}` : \"\"}/api/v1`, repo: parts.slice(-2).join(\"/\") };\n }\n }\n throw new ForgeError(\"cannot derive forge API from origin URL\");\n}\n\nfunction resolveToken(): string | undefined {\n try {\n const t = (readMainConfigRaw().forge as { token?: unknown } | undefined)?.token;\n if (typeof t === \"string\" && t) return t;\n } catch { /* unreadable config: fall through to env */ }\n return process.env.FORGE_TOKEN || process.env.AIBROKER_FORGE_TOKEN || undefined;\n}\n\n/** Real transport: curl; the token goes in on stdin so it never shows in ps. */\nexport const curlHttp: Http = (method, url, token, body, timeoutMs) => {\n const cfg = [\n `url = \"${url}\"`,\n `request = \"${method}\"`,\n `header = \"Authorization: token ${token}\"`,\n `header = \"Content-Type: application/json\"`,\n `max-time = ${Math.max(0.5, timeoutMs / 1000)}`,\n `silent`,\n `write-out = \"\\\\n%{http_code}\"`,\n ].join(\"\\n\");\n const argv = body !== undefined ? [\"-K\", \"-\", \"--data-raw\", body] : [\"-K\", \"-\"];\n const r = spawnSync(\"curl\", argv, { input: cfg, encoding: \"utf8\", timeout: timeoutMs + 500, maxBuffer: 16 * 1024 * 1024 });\n if (r.error || r.status !== 0) throw new ForgeError(`curl failed (${r.error?.message ?? `exit ${r.status}`})`);\n const at = r.stdout.lastIndexOf(\"\\n\");\n return { status: Number(r.stdout.slice(at + 1)), text: r.stdout.slice(0, at) };\n};\n\n/** `handover: #<n>` line wins; else the first `#<n>` after the marker. */\nexport function extractHandoverNumber(anchorBody: string, marker: string): number | undefined {\n const line = anchorBody.match(/^\\s*handover:\\s*#(\\d+)/im);\n if (line) return Number(line[1]);\n const after = anchorBody.slice(anchorBody.indexOf(marker) + marker.length).match(/#(\\d+)/);\n return after ? Number(after[1]) : undefined;\n}\n\n/** Replace the delimited section, or append it; everything else stays byte-identical. */\nexport function upsertSection(body: string, markdown: string, tag = \"continue\"): string {\n const open = tag === \"continue\" ? SECTION_OPEN : `<!-- pai:${tag} -->`;\n const close = tag === \"continue\" ? SECTION_CLOSE : `<!-- /pai:${tag} -->`;\n const section = `${open}\\n${markdown.trim()}\\n${close}`;\n const a = body.indexOf(open);\n const b = a === -1 ? -1 : body.indexOf(close, a);\n if (b !== -1) return body.slice(0, a) + section + body.slice(b + close.length);\n const sep = body === \"\" || body.endsWith(\"\\n\\n\") ? \"\" : body.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n return body + sep + section + \"\\n\";\n}\n\nexport interface ForgeDeps { http?: Http; token?: string; originUrl?: string; dryRun?: boolean }\n\ntype Method = \"GET\" | \"PATCH\" | \"POST\" | \"DELETE\";\n\n/** One forge repo + token + 5 s budget; dry run records mutating calls in `planned` instead of sending them. */\nfunction forgeClient(cwd: string, cfg: StatusConfig, deps: ForgeDeps) {\n const token = deps.token ?? resolveToken();\n if (!token) throw new ForgeError(\"no forge token\");\n const http = deps.http ?? curlHttp;\n let origin = deps.originUrl;\n if (!origin) {\n try {\n origin = execFileSync(\"git\", [\"remote\", \"get-url\", \"origin\"], { cwd, encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"] });\n } catch { throw new ForgeError(\"no origin remote\"); }\n }\n const derived = deriveRepo(origin);\n const repo = cfg.repo ?? derived.repo;\n const deadline = Date.now() + FORGE_BUDGET_MS;\n const planned: string[] = [];\n const call = (method: Method, path: string, body?: string): HttpResult => {\n if (deps.dryRun && method !== \"GET\") {\n planned.push(`${method} ${derived.apiBase}/repos/${repo}/${path}${body ? ` ${body}` : \"\"}`);\n return { status: 200, text: \"{}\" };\n }\n const left = deadline - Date.now();\n if (left < 300) throw new ForgeError(\"time budget exhausted\");\n let r: HttpResult;\n try { r = http(method, `${derived.apiBase}/repos/${repo}/${path}`, token, body, left); }\n catch (e) { throw e instanceof ForgeError ? e : new ForgeError(`request failed (${(e as Error).message})`); }\n if (r.status < 200 || r.status >= 300) throw new ForgeError(`HTTP ${r.status} on ${method} ${path}`);\n return r;\n };\n return { repo, call, planned };\n}\n\nfunction json<T>(r: HttpResult): T {\n try { return JSON.parse(r.text) as T; } catch { throw new ForgeError(\"unparseable forge response\"); }\n}\n\n/** Returns the issue number written (or, dry run, that would be). Throws ForgeError with the reason. */\nexport function writeContinueToForge(cwd: string, cfg: StatusConfig, markdown: string, deps: ForgeDeps = {}): { issue: number; body: string } {\n return writeSectionToForge(cwd, cfg, markdown, \"continue\", deps);\n}\n\n/** Replace/append section `tag` in the day's handover issue; the rest of the body is untouched. */\nexport function writeSectionToForge(cwd: string, cfg: StatusConfig, markdown: string, tag: string, deps: ForgeDeps = {}): { issue: number; body: string } {\n if (!cfg.anchor || !cfg.marker) throw new ForgeError(\"status.forge.anchor/marker missing\");\n const { call } = forgeClient(cwd, cfg, deps);\n const bodyOf = (n: number): string => String(json<{ body?: unknown }>(call(\"GET\", `issues/${n}`)).body ?? \"\");\n const anchorBody = bodyOf(cfg.anchor);\n if (!anchorBody.includes(cfg.marker)) throw new ForgeError(`anchor #${cfg.anchor} lacks marker`);\n const handover = extractHandoverNumber(anchorBody, cfg.marker);\n if (!handover) throw new ForgeError(`no handover number in anchor #${cfg.anchor}`);\n const next = upsertSection(bodyOf(handover), markdown, tag);\n if (!deps.dryRun) call(\"PATCH\", `issues/${handover}`, JSON.stringify({ body: next }));\n return { issue: handover, body: next };\n}\n\n// Claim convention (identical on the AIBroker side): assignee CLAIM_ASSIGNEE + label CLAIM_LABEL.\n// PAI never closes issues.\nexport const CLAIM_ASSIGNEE = \"claude\";\nexport const CLAIM_LABEL = \"in-progress\";\n\nexport interface ClaimResult { repo: string; issue: number; message: string; planned: string[] }\nexport interface ClaimRecord { issue: number; repo: string; at: string }\n\ninterface IssueView { assignees?: Array<{ login: string }> | null; labels?: Array<{ id: number; name: string }> | null }\n\n/** Claim (add=true) or release (add=false) issue `n`; other assignees and labels are left alone. */\nexport function setClaim(cwd: string, cfg: StatusConfig, n: number, add: boolean, deps: ForgeDeps = {}): ClaimResult {\n const { repo, call, planned } = forgeClient(cwd, cfg, deps);\n const issue = json<IssueView>(call(\"GET\", `issues/${n}`));\n const logins = (issue.assignees ?? []).map((a) => a.login);\n const next = add ? [...new Set([...logins, CLAIM_ASSIGNEE])] : logins.filter((l) => l !== CLAIM_ASSIGNEE);\n if (next.length !== logins.length || next.some((l, i) => l !== logins[i])) {\n call(\"PATCH\", `issues/${n}`, JSON.stringify({ assignees: next }));\n }\n const verb = add ? \"claimed\" : \"released\";\n let message = `${verb} ${repo}#${n}`;\n const has = (issue.labels ?? []).find((l) => l.name === CLAIM_LABEL);\n if (add && !has) {\n const label = json<Array<{ id: number; name: string }>>(call(\"GET\", \"labels?limit=100\")).find((l) => l.name === CLAIM_LABEL);\n if (label) call(\"POST\", `issues/${n}/labels`, JSON.stringify({ labels: [label.id] }));\n else message += ` (assignee only: label \"${CLAIM_LABEL}\" does not exist in ${repo})`;\n } else if (!add && has) {\n call(\"DELETE\", `issues/${n}/labels/${has.id}`);\n }\n return { repo, issue: n, message, planned };\n}\n\n/** Open issues in the repo currently carrying the claim label. */\nexport function listClaims(cwd: string, cfg: StatusConfig, deps: ForgeDeps = {}): Array<{ issue: number; title: string; assignees: string[] }> {\n const { call } = forgeClient(cwd, cfg, deps);\n const rows = json<Array<{ number: number; title: string; pull_request?: unknown } & IssueView>>(\n call(\"GET\", `issues?state=open&type=issues&labels=${CLAIM_LABEL}&limit=50`),\n );\n return rows.map((r) => ({ issue: r.number, title: r.title, assignees: (r.assignees ?? []).map((a) => a.login) }));\n}\n\n/** Release every claim best-effort; returns the per-issue errors (never throws). */\nexport function releaseClaims(cwd: string, claims: ClaimRecord[], deps: ForgeDeps = {}): string[] {\n const errors: string[] = [];\n for (const c of claims) {\n try { setClaim(cwd, { ...loadStatusConfig(cwd), repo: c.repo }, c.issue, false, deps); }\n catch (e) { errors.push(`${c.repo}#${c.issue}: ${(e as Error).message}`); }\n }\n return errors;\n}\n\n// ---- stale report: open issues that landed in the integration branch or went idle ----\n\nexport type Git = (args: string[]) => string;\nexport interface StaleRow { issue: number; title: string; kind: \"landed\" | \"idle\"; via?: string; idleDays: number; claim: string[] }\nexport interface StaleDeps extends ForgeDeps { git?: Git; now?: number }\n\n/** One shared sink for the CLI/MCP report and the daemon section. */\nexport function formatStale(rows: StaleRow[]): string {\n if (!rows.length) return \"No stale issues.\";\n return rows\n .map((r) => `- #${r.issue} ${r.title} - ${r.kind === \"landed\" ? `landed via ${r.via}` : `idle ${r.idleDays} days`}${r.claim.length ? ` [${r.claim.join(\", \")}]` : \"\"}`)\n .join(\"\\n\");\n}\n\n/**\n * Open issues that either landed or idle >= days. Landed first, then idle by age.\n * Landed (deterministic; a plain mention like \"see #283\" never counts):\n * (a) a branch merged into the integration branch whose name has <n> as a whole token (split on / - _ .), or\n * (b) an integration-branch commit whose subject has a closing reference (fixes|closes|resolves #<n>).\n * A trailing \"(#<n>)\" is a plain reference and does not count. The configured anchor issue is never listed.\n */\nexport function listStale(cwd: string, cfg: StatusConfig, days = 7, deps: StaleDeps = {}): StaleRow[] {\n const { call } = forgeClient(cwd, cfg, deps);\n const git: Git = deps.git ?? ((a) => execFileSync(\"git\", a, { cwd, encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: 64 * 1024 * 1024 }));\n const gitTry = (a: string[]): string => { try { return git(a); } catch { return \"\"; } };\n type Row = { number: number; title: string; updated_at: string } & IssueView;\n const rows: Row[] = [];\n for (let page = 1; page <= 10; page++) {\n const chunk = json<Row[]>(call(\"GET\", `issues?state=open&type=issues&limit=50&page=${page}`));\n rows.push(...chunk);\n if (chunk.length < 50) break;\n }\n const refs = new Set(gitTry([\"for-each-ref\", \"--format=%(refname:short)\"]).split(\"\\n\"));\n let integ = [\"develop\", \"origin/develop\"].find((b) => refs.has(b));\n if (!integ) {\n let def = \"main\";\n try { def = json<{ default_branch?: string }>(call(\"GET\", \"\")).default_branch || \"main\"; } catch { /* repo info unavailable: main */ }\n integ = refs.has(def) ? def : refs.has(`origin/${def}`) ? `origin/${def}` : def;\n }\n const tip = gitTry([\"rev-parse\", integ]).trim();\n const merged = gitTry([\"branch\", \"-a\", \"--merged\", integ, \"--format=%(refname:short) %(objectname)\"])\n .split(\"\\n\").map((l) => l.trim().split(\" \")).filter(([n, sha]) => n && sha && sha !== tip && n !== integ && !n.endsWith(\"/HEAD\") && n !== `origin/${integ}`)\n .map(([n]) => n);\n const now = deps.now ?? Date.now();\n const out: StaleRow[] = [];\n for (const r of rows) {\n const n = r.number;\n if (n === cfg.anchor) continue;\n const idleDays = Math.floor((now - Date.parse(r.updated_at)) / 86_400_000);\n const claim = [...((r.assignees ?? []).some((a) => a.login === CLAIM_ASSIGNEE) ? [`assignee ${CLAIM_ASSIGNEE}`] : []), ...((r.labels ?? []).some((l) => l.name === CLAIM_LABEL) ? [CLAIM_LABEL] : [])];\n const branch = merged.find((b) => b.split(/[/\\-_.]/).includes(String(n)));\n const closing = new RegExp(`\\\\b(fixes|fix|closes|close|resolves|resolve)\\\\s+#${n}\\\\b`, \"i\");\n const commit = branch ? undefined : gitTry([\"log\", integ, \"-E\", `--grep=#${n}([^0-9]|$)`, \"--format=%h %s\"]).split(\"\\n\").find((l) => closing.test(l.slice(l.indexOf(\" \") + 1)));\n if (branch || commit) out.push({ issue: n, title: r.title, kind: \"landed\", via: branch ?? `commit ${commit}`, idleDays, claim });\n else if (idleDays >= days) out.push({ issue: n, title: r.title, kind: \"idle\", idleDays, claim });\n }\n return out.sort((a, b) => (a.kind === b.kind ? b.idleDays - a.idleDays : a.kind === \"landed\" ? -1 : 1));\n}\n","/**\n * status.ts — the live per-worker status file.\n *\n * One JSON file per run, rewritten atomically on every turn, read by `ps`,\n * `follow`, the status line and the pane logic. Same field set the Python\n * runner wrote, plus `provider` (routing is multi-provider now) and `session`\n * (the AIBroker identity of the launching session, see scope.ts).\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport { statusPath } from \"./paths.js\";\nimport { releaseClaims, type ClaimRecord } from \"../session/forge-sink.js\";\nimport type { WaitingOn } from \"./waiting.js\";\nimport type { Denial } from \"./denials.js\";\n\nexport interface WorkerSessionRef {\n /** AIBroker/iTerm session id (the iTerm UUID). */\n id: string;\n /** AIBroker name of that session, when it has one. */\n name: string;\n}\n\nexport interface WorkerStatus {\n id: string;\n pid: number;\n label: string;\n cwd: string;\n /** iTerm session id of the launching terminal (\"\" outside iTerm). */\n term: string;\n provider: string;\n model: string;\n state: \"running\" | \"done\" | \"failed\" | \"killed\" | \"lost\";\n started: string;\n updated: string;\n turns: number;\n tools: number;\n last: string;\n rc: number | null;\n secs: number | null;\n /** Launching session resolved through AIBroker, when it was. */\n session?: WorkerSessionRef | null;\n /** Claude Code session id (system/init) — what `resume` continues. */\n claudeSession?: string | null;\n /**\n * Claude Code session id of the orchestrator whose Bash launched this run,\n * when the status line's session map knew it (see scope.ts). Distinct from\n * claudeSession, which is the run's own session.\n */\n spawnerSession?: string | null;\n /** Follow-pane outcome of the run start: opened, or why not. */\n pane?: { opened: boolean; reason?: string; session?: string } | null;\n /** Context meter: tokens of the last assistant turn (input+cache+output). */\n contextTokens?: number | null;\n /** Current context fill: input+cache_read+cache_creation of the latest assistant turn. */\n contextUsed?: number | null;\n /** Context meter: window size (init model info or provider default). */\n contextWindow?: number | null;\n /** Path the prompt was read from via --spec (\"-\" for stdin), when it was. */\n spec?: string | null;\n /**\n * Caller's --output-format, when the run was headless. A json/stream-json\n * caller reads its own result and is notified on process exit by the\n * harness — supervision.ts uses this to skip the duplicate relay send.\n */\n outputFormat?: \"text\" | \"json\" | \"stream-json\";\n /** Chain this stage belongs to (the chain id), when it is a chain stage. */\n parent?: string;\n /** Class name of the chain stage (\"draft\", \"implement\", …). */\n stage?: string;\n /** Worktree this run executed in, when it got one (see worktree.ts). */\n worktreeDir?: string | null;\n /** Branch the worker committed on (worker/<id>), set on worktree runs. */\n branch?: string | null;\n /** Set on a resumed worker: the id whose worktree it took over (see worktree.ts adoptTree). */\n resumedFrom?: string;\n /** Set on the old id when a resume took its worktree: that id owns the tree now. */\n resumedBy?: string;\n /** Commit the branch started from (worktree base) for the commits count. */\n worktreeBase?: string | null;\n /** Whether `worktreeBase` is a snapshot of uncommitted checkout state, not HEAD. */\n worktreeSnapshot?: boolean | null;\n /** Commits the worker made on its branch; set with `branch` on success. */\n commits?: number | null;\n /** Set by `pai worker merge` once the branch landed in the original checkout. */\n merged?: boolean;\n /** Set by `pai worker gc`: the hidden ref (refs/pai-archive/<id>) holding the leftover worktree's state. */\n archived?: string | null;\n /**\n * How the run came to be: \"spawn\" (a `pai worker run` subagent) or \"chat\"\n * (the terminal's interactive pane itself, tracked like a worker). Absent\n * on statuses written before the flag existed — read as a spawn.\n */\n origin?: \"spawn\" | \"chat\";\n /** Where the routing policy sent this run and why (policy.ts); absent without a policy. */\n route?: { policy: string; reason: string; local: boolean };\n /**\n * Why budget routing put this run on its provider instead of a fuller one\n * ahead of it (\"glm: anthropic 96% until 08:02\"); absent when no switch\n * happened. Resolved once at spawn — a running worker is never switched.\n */\n switchReason?: string;\n /** Fall-through link: the local run this cloud run repeats, and (on the local run) its repeat. */\n fallThroughOf?: string;\n fallThroughTo?: string;\n /** Which contract/parser this run used for its final report (see report.ts). */\n reportFormat?: \"json\" | \"ag2\";\n /** Whether the final AG2 report passed `aibroker agentish check`; unset for json reports or when no validator ran. */\n reportValid?: boolean;\n /** Validation error messages, when reportValid is false. */\n reportErrors?: string[];\n /** Whether the headless prompt got the end-of-turn \"final message must be…\" trailer line. */\n promptTrailer?: boolean;\n /** The project preamble (.pai/worker.md) appended to this run's prompt: path + short content hash. */\n preamble?: { path: string; hash: string };\n /** The provider contract addendum appended to this run's prompt: path + sha256 prefix. */\n contractAddendum?: { path: string; sha: string };\n /** Whether an invalid AG2 final message triggered the one bounded re-ask (see run.ts). */\n reportRetried?: boolean;\n /**\n * Consecutive failed tool executions in this run, maintained by the runner's\n * stream loop (0 after any success). Absent on old status files — every\n * reader must treat that as \"no signal\".\n */\n consecFails?: number | null;\n /** Hook/permission denials the run hit (capped list, see denials.ts) and their true total. */\n denials?: Denial[];\n denialCount?: number;\n /**\n * Seconds of the most recent single long sleep observed in a Bash tool_use\n * (see parseSleepSecs), cleared when the command completes. Absent on old\n * status files.\n */\n sleepSec?: number | null;\n /** Epoch ms the run is stopped at (deadline.ts); absent = no limit. */\n deadlineAt?: number | null;\n /** Limit in minutes the deadline was derived from. */\n maxMinutes?: number | null;\n /** The deadline stopped this run (state failed, `last` says why). */\n timedOut?: boolean;\n /** Launching process (the parent of `pai worker run`) at start, and its command name. */\n parentPid?: number;\n parentComm?: string;\n parentBackground?: boolean;\n /** Why the run ended (endreason.ts): exit, deadline, pai-worker-kill, parent-gone, external-signal. */\n endReason?: string;\n /** Who asked for the stop, when known (kill caller, daemon:<reason>). */\n endBy?: string;\n /** Detail for the reason (signal, sender note). */\n endNote?: string;\n /**\n * What the worker is blocked on right now: written by `pai worker wait-on`\n * while it waits (null once it returns), rendered by waiting.ts.\n */\n waitingOn?: WaitingOn | null;\n /** Forge issues this worker claimed (`pai issue claim` inside it); released when it ends unless claimsContinue. */\n claims?: ClaimRecord[];\n /** The final report said the work continues (r=~): claims stay. Set by finaliseRun before the end write. */\n claimsContinue?: boolean;\n /** Per-issue errors from the best-effort release at worker end. */\n claimReleaseErrors?: string[];\n /** Ever granted clickr desktop controls (see controls.ts) — the runner returns them best-effort on exit. */\n controlsHeld?: boolean;\n /**\n * Set when the task ran through the worker session pool (pool.ts) instead\n * of a fresh claude spawn: task index within its slot, the slot's claude\n * session id (shared across tasks), and the host pid. `pid` names the slot\n * process — killing it kills the whole slot, not just this task.\n */\n pool?: { task: number; slotSession: string | null; hostPid: number };\n}\n\n/** Label a worker gets when launched with neither --label nor a prompt. */\nexport const UNLABELED = \"unlabeled\";\n\n/**\n * Whether a status is the terminal's interactive chat pane, not a spawned\n * task worker. `origin` is the real discriminator; the fallback catches\n * running entries from before the flag existed (unlabeled, no turns yet).\n * Removable once every pane runs code that writes `origin`.\n */\nexport function isChatPane(s: Pick<WorkerStatus, \"origin\" | \"label\" | \"turns\">): boolean {\n return (\n s.origin === \"chat\" ||\n (!s.origin && (s.label === UNLABELED || s.label === \"(no prompt)\") && s.turns === 0)\n );\n}\n\n/** Context-meter percentage 0–100, null when the numbers are missing. */\nexport function contextPercent(s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">): number | null {\n if (!s.contextTokens || !s.contextWindow) return null;\n return Math.round((s.contextTokens / s.contextWindow) * 100);\n}\n\n/** Compact token count: 84k, 200k, 900. */\nexport function fmtContextK(n: number): string {\n return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);\n}\n\n/** Footer token count: 48k, 1M, 1.5M. */\nexport function fmtTokens(n: number): string {\n if (n >= 1_000_000) return `${Number((n / 1_000_000).toFixed(1))}M`;\n return fmtContextK(n);\n}\n\n/** `<used> / <window>`, or just `<used>` when the window is unknown; \"\" with no reading. */\nexport function contextFooter(s: Pick<WorkerStatus, \"contextUsed\" | \"contextWindow\">): string {\n if (!s.contextUsed) return \"\";\n return s.contextWindow ? `${fmtTokens(s.contextUsed)} / ${fmtTokens(s.contextWindow)}` : fmtTokens(s.contextUsed);\n}\n\n/**\n * The one context-meter label, shared by every renderer so identical\n * numbers always render identically: `ctx 84k/200k (42%)` — used-style\n * percent. Empty when the numbers are missing (callers drop the part).\n */\nexport function contextLabel(\n s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">\n): string {\n const pct = contextPercent(s);\n if (pct === null) return \"\";\n return `ctx ${fmtContextK(s.contextTokens ?? 0)}/${fmtContextK(s.contextWindow ?? 0)} (${pct}%)`;\n}\n\n/** Timestamp format shared by status files and the ledger. */\nexport function nowStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\n// second-resolution ids repeat when two workers/chains start together in one\n// process; a repeat gets a monotonic suffix so files never collide\nlet lastId = \"\";\nlet idSeq = 0;\n\nexport function newWorkerId(d: Date = new Date(), pid = process.pid): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n const base = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${pid}`;\n if (base === lastId) {\n idSeq += 1;\n return `${base}-${String(idSeq).padStart(2, \"0\")}`;\n }\n lastId = base;\n idSeq = 0;\n return base;\n}\n\n// Temp names must be unique per writer, not just per worker: two processes\n// legitimately write the same id at the same moment (`pai worker kill` marking\n// a run killed while the run's own SIGTERM handler writes its terminal state).\n// With one shared `<id>.status.tmp` they clobber each other's temp file and the\n// loser's rename fails with ENOENT — observed live on 2026-09-19.\nlet tmpSeq = 0;\n\n/** A temp path only this call owns — never the same string twice. */\nexport function statusTmpPath(logDir: string, id: string): string {\n return `${statusPath(logDir, id)}.${process.pid}.${tmpSeq++}.tmp`;\n}\n\n// The label this process first wrote for a given worker id: run.ts holds one\n// unchanging label in memory for the whole run and passes it to every\n// periodic saveStatus call, so a later call whose label still equals this\n// baseline is that unmodified write, never an intentional change — on-disk\n// then wins, so an external `pai worker goal` relabel between two of a\n// worker's own writes survives instead of being reverted by the next one. A\n// call whose label differs from the baseline (or the first call for an id in\n// this process, which has no baseline yet) IS the intentional change and\n// always wins — exactly the one-shot `pai worker goal` process itself.\nconst firstWrittenLabel = new Map<string, string>();\n\n/** Write status atomically (temp + rename) and stamp `updated`. */\nexport function saveStatus(logDir: string, status: WorkerStatus, d: Date = new Date()): void {\n status.updated = nowStamp(d);\n const path = statusPath(logDir, status.id);\n const baseline = firstWrittenLabel.get(status.id);\n if (baseline === undefined) {\n firstWrittenLabel.set(status.id, status.label);\n }\n const onDisk = baseline === undefined ? undefined : loadStatus(logDir, status.id);\n if (onDisk) {\n if (status.label === baseline && onDisk.label !== status.label) status.label = onDisk.label;\n // Set by another process (`pai worker controls`); a true flag is never dropped by a stale in-memory copy.\n if (onDisk.controlsHeld) status.controlsHeld = true;\n // Written by `pai worker wait-on` (another process); the runner's in-memory copy never knows it.\n if (status.waitingOn === undefined && onDisk.waitingOn) status.waitingOn = onDisk.waitingOn;\n // Recorded by `pai issue claim` (another process).\n if (status.claims === undefined && onDisk.claims) status.claims = onDisk.claims;\n }\n // The one owner of \"an ended worker holds no claims\" (like its pane, below): every end path saves through here.\n if (status.state !== \"running\" && status.claims?.length && !status.claimsContinue) {\n const errors = releaseClaims(status.cwd, status.claims);\n status.claims = [];\n if (errors.length) {\n status.claimReleaseErrors = errors;\n process.stderr.write(`claim release failed: ${errors.join(\"; \")}\\n`);\n }\n }\n const tmp = statusTmpPath(logDir, status.id);\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n try {\n writeFileSync(tmp, JSON.stringify(status), \"utf8\");\n renameSync(tmp, path);\n } catch (e) {\n try {\n rmSync(tmp, { force: true });\n } catch {\n // nothing to clean up\n }\n throw e;\n }\n}\n\n/**\n * Set a worker's goal (the operator's `--label`), atomically, through the\n * same saveStatus path everything else writes with. Used by `pai worker\n * goal` and the `--goal` option of `say`: a fresh call in a fresh process, so\n * it always carries no baseline yet and always wins over the worker's own\n * next periodic write (see saveStatus above).\n */\nexport function setWorkerLabel(logDir: string, id: string, label: string): WorkerStatus {\n const status = loadStatus(logDir, id);\n if (!status) throw new Error(`no worker named \"${id}\"`);\n status.label = label;\n saveStatus(logDir, status);\n return status;\n}\n\n/** Load every status file in the logDir, oldest id first, skipping damage. */\nexport function loadStatuses(logDir: string): WorkerStatus[] {\n if (!existsSync(logDir)) return [];\n const out: WorkerStatus[] = [];\n for (const name of readdirSync(logDir).sort()) {\n if (!name.endsWith(\".status\")) continue;\n try {\n out.push(JSON.parse(readFileSync(join(logDir, name), \"utf8\")) as WorkerStatus);\n } catch {\n // a half-written or damaged status file is not worth a crash\n }\n }\n return out;\n}\n\nexport function loadStatus(logDir: string, id: string): WorkerStatus | null {\n const path = statusPath(logDir, id);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as WorkerStatus;\n } catch {\n return null;\n }\n}\n\n/**\n * Wait for a worker to record its own terminal state (the run's signal\n * handler writes killed/rc/secs). Returns that status, or null when it is\n * still \"running\" after `timeoutMs` — the caller then writes the state itself.\n */\nexport async function waitForTerminalStatus(\n logDir: string,\n id: string,\n timeoutMs = 2000,\n stepMs = 50\n): Promise<WorkerStatus | null> {\n const deadline = Date.now() + timeoutMs;\n for (;;) {\n const s = loadStatus(logDir, id);\n if (s && s.state !== \"running\") return s;\n if (Date.now() >= deadline) return null;\n await new Promise((r) => setTimeout(r, stepMs));\n }\n}\n\nexport function alive(pid: number | null | undefined): boolean {\n if (!pid || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Whether `status.pid` still names the process that was started as this\n * worker, not a later, unrelated process the OS handed the same pid to after\n * the worker exited. `alive()` alone can't tell these apart — a pid observed\n * live on 2026-09-19 (worker 20260919-083549-90913) had been reused, and\n * `pai worker kill` signalled the wrong, unrelated process. `ps -o lstart=`\n * reads the running process's actual start time and compares it against the\n * status file's own `started` stamp; a reused pid almost never starts within\n * 120s of the original, so a wider drift means \"different process\".\n */\nexport function ownsPid(status: Pick<WorkerStatus, \"pid\" | \"started\">): boolean {\n if (status.pid <= 0 || !alive(status.pid)) return false;\n try {\n const out = execFileSync(\"ps\", [\"-o\", \"lstart=\", \"-p\", String(status.pid)], {\n encoding: \"utf8\",\n env: { ...process.env, LC_ALL: \"C\" },\n }).trim();\n const psStart = Date.parse(out);\n const started = Date.parse(status.started.replace(\" \", \"T\"));\n if (Number.isNaN(psStart) || Number.isNaN(started)) return alive(status.pid);\n return Math.abs(psStart - started) <= 120_000;\n } catch {\n // pid exited between the alive() check and the ps call, or ps failed for\n // another reason — the pid-existence check is still better than nothing.\n return alive(status.pid);\n }\n}\n\n/** Whether a status is both marked \"running\" and still owns its recorded pid. */\nexport function isLive(status: Pick<WorkerStatus, \"pid\" | \"started\" | \"state\">): boolean {\n return status.state === \"running\" && ownsPid(status);\n}\n\n/** \"42s\" under 90s, \"7m\" above — the coarse age the table and bar show. */\nexport function ageOf(ts: string, now: Date = new Date()): string {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return \"?\";\n const s = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;\n}\n\n/** Precise elapsed time: \"45s\", \"1m 23s\", \"12m 05s\", \"1h 02m 05s\" — seconds zero-padded after a larger unit. */\nexport function elapsedOf(ts: string, now: Date = new Date()): string {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return \"?\";\n const secs = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n const hours = Math.floor(secs / 3600);\n const mins = Math.floor((secs % 3600) / 60);\n const sec = secs % 60;\n if (hours > 0) return `${hours}h ${String(mins).padStart(2, \"0\")}m ${String(sec).padStart(2, \"0\")}s`;\n if (mins > 0) return `${mins}m ${String(sec).padStart(2, \"0\")}s`;\n return `${sec}s`;\n}\n\n/** Shorter sleeps are legitimate polling — not worth a supervision event. */\nexport const SLEEP_FLOOR_SECS = 60;\n\nconst SLEEP_RE = /^\\s*sleep\\s+([0-9]+(?:\\.[0-9]+)?)([smhd]?)\\s*(?:$|[;&|])/;\n\n/** s/m/h/d suffix to seconds multiplier, shared by every `sleep N<unit>` parse below. */\nfunction sleepUnitMultiplier(unit: string): number {\n return unit === \"m\" ? 60 : unit === \"h\" ? 3600 : unit === \"d\" ? 86_400 : 1;\n}\n\n/**\n * Seconds of a `sleep N` (N plain or with an s/m/h/d suffix) that stands alone\n * or leads a compound command (`sleep 150; cat f`, `sleep 2m && tail f`,\n * `sleep 90 || true`, `sleep 60 &`), or null when no long sleep leads: anchored\n * to the start of the command, so a filename containing \"sleep\" or an\n * `echo sleep 500` never trips it, and floored at SLEEP_FLOOR_SECS so ordinary\n * polling stays invisible.\n */\nexport function parseSleepSecs(command: string): number | null {\n const m = command.match(SLEEP_RE);\n if (!m) return null;\n const secs = Number(m[1]) * sleepUnitMultiplier(m[2]);\n return Number.isFinite(secs) && secs >= SLEEP_FLOOR_SECS ? secs : null;\n}\n\n/**\n * Every command-segment boundary a shell recognises before a new simple\n * command can start: the very start of the string, `;`, `&`/`&&`, `|`/`||`,\n * a newline, an opening `(` or `{` (also covers `$(...)`, which ends in `(`),\n * or the keywords `do`/`then`. Segment end is the mirror image plus a closing\n * `)`, so `sleep 90` inside `$(sleep 90)` or `(sleep 90)` still counts.\n */\n// The terminator is a lookahead, not a consumed match: two sleeps sharing one\n// separator (`sleep 65; sleep 590`) both need that `;` available as their own\n// boundary — matchAll never revisits a character a previous match consumed.\nconst SLEEP_SEGMENT_RE =\n /(?:^|[;&|\\n(){]|\\bdo\\b|\\bthen\\b)\\s*sleep\\s+([0-9]+(?:\\.[0-9]+)?)([smhd]?)(?=\\s*(?:$|[;&|)\\n]))/g;\n\n/**\n * The longest `sleep N` found as its OWN command segment anywhere in\n * `command` — not just leading it, unlike {@link parseSleepSecs} — or null\n * when none reaches SLEEP_FLOOR_SECS. Used by the PreToolUse Bash gate\n * (block-sleep-poll.ts), which must catch a sleep buried after `&&`, inside a\n * `do ... done` loop, or in a subshell, not only one that leads the command.\n */\nexport function longestSleepSecs(command: string): number | null {\n let longest: number | null = null;\n for (const m of command.matchAll(SLEEP_SEGMENT_RE)) {\n const secs = Number(m[1]) * sleepUnitMultiplier(m[2]);\n if (!Number.isFinite(secs) || secs < SLEEP_FLOOR_SECS) continue;\n if (longest === null || secs > longest) longest = secs;\n }\n return longest;\n}\n\nconst LOOP_RE = /\\b(for|while|until)\\b([^;\\n]*)[;\\n]\\s*do\\b([\\s\\S]*?)\\bdone\\b/g;\n\nfunction sleepSum(text: string): number {\n let sum = 0;\n for (const m of text.matchAll(SLEEP_SEGMENT_RE)) {\n const secs = Number(m[1]) * sleepUnitMultiplier(m[2]);\n if (Number.isFinite(secs)) sum += secs;\n }\n return sum;\n}\n\n/** Iterations of a `for x in <words>` header, or null when not provably small. */\nfunction forIterations(header: string): number | null {\n const words = header.replace(/^\\s*\\w+\\s+in\\s+/, \"\").trim();\n if (words === header.trim() || /[$`*?]/.test(words)) return null;\n const range = /^\\{(-?\\d+)\\.\\.(-?\\d+)\\}$/.exec(words);\n if (range) return Math.abs(Number(range[2]) - Number(range[1])) + 1;\n return words ? words.split(/\\s+/).length : 0;\n}\n\n/**\n * Total sleep of one Bash command: every `sleep N` segment counted once, plus\n * loop repetition — `for x in a b c` multiplies its body by the word count;\n * `while`/`until` and any unbounded `for` count as at least SLEEP_FLOOR_SECS,\n * except a `kill -0` job-wait (the sanctioned wait-for-a-job loop). Used by\n * the PreToolUse gate so `sleep 58; sleep 58` is caught as the 116 s it is.\n */\n// ponytail: loops are not nested-aware (body ends at the first `done`); upgrade to a real shell parse if that bites.\nexport function totalSleepSecs(command: string): number {\n let total = sleepSum(command);\n for (const m of command.matchAll(LOOP_RE)) {\n const body = sleepSum(m[3]);\n if (body === 0) continue;\n if (m[1] === \"for\") {\n const n = forIterations(m[2]);\n total += n === null ? Math.max(SLEEP_FLOOR_SECS, body) : body * (n - 1);\n } else if (!/\\bkill\\s+-0\\b/.test(m[2])) {\n total += SLEEP_FLOOR_SECS;\n }\n }\n return total;\n}\n\n/** One-line description of a tool call, e.g. \"Bash: npm test\". */\nexport function describeTool(name: string, inp: unknown): string {\n if (typeof inp !== \"object\" || inp === null) return name;\n const i = inp as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Bash\") return `Bash: ${shortText(get(\"command\"), 70)}`;\n if (name === \"Read\" || name === \"Edit\" || name === \"Write\" || name === \"MultiEdit\") {\n const file = get(\"file_path\").split(\"/\").pop() ?? \"\";\n return `${name}: ${file}`;\n }\n if (name === \"Grep\" || name === \"Glob\") return `${name}: ${shortText(get(\"pattern\"), 50)}`;\n if (name === \"WebSearch\") return `${name}: ${shortText(get(\"query\"), 50)}`;\n if (name === \"WebFetch\") return `${name}: ${shortText(get(\"url\"), 60)}`;\n return name;\n}\n"],"mappings":"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"}
|
|
@@ -2,13 +2,13 @@ import { c as ok, g as warn, i as err, n as dim, r as encodeDir, s as now, t as
|
|
|
2
2
|
import { a as slugify, i as parseSessionFilename, n as decodeEncodedDir, t as buildEncodedDirMap } from "./migrate-CYccXTAA.mjs";
|
|
3
3
|
import { b as writeJsonAtomic } from "./config-DSdCEcHS.mjs";
|
|
4
4
|
import { c as migratePaiFile, d as resolvePaiFile, r as daemonPidPath, u as paiHomePath } from "./runtime-paths-DkZoxofH.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import { A as writeContinueToForge, E as loadStatusConfig, U as ensureNoMcpConfig, Y as workersLogDir } from "./status-
|
|
7
|
-
import { b as appendLedger, g as worktreesDir } from "./worktree-
|
|
8
|
-
import { a as unregistrableReason, i as pruneWorktreeProjects, n as ensurePaiMarker, t as discoverPaiMarkers } from "./pai-marker-
|
|
5
|
+
import { B as resolveModelCapability, R as readWorkersSection, S as defaultWorkersConfig } from "./config-CYjjGSWq.mjs";
|
|
6
|
+
import { A as writeContinueToForge, E as loadStatusConfig, U as ensureNoMcpConfig, Y as workersLogDir } from "./status-B0br9zV0.mjs";
|
|
7
|
+
import { b as appendLedger, g as worktreesDir } from "./worktree-B6d-1uIn.mjs";
|
|
8
|
+
import { a as unregistrableReason, i as pruneWorktreeProjects, n as ensurePaiMarker, t as discoverPaiMarkers } from "./pai-marker-CJ6WMt05.mjs";
|
|
9
9
|
import { i as getRegistryBackend } from "./factory-DRAEDNKV.mjs";
|
|
10
|
-
import { $ as ensureProxyRunning, Q as DEFAULT_PROXY_PORT,
|
|
11
|
-
import { i as ROUTER_PORT, t as buildRunEnv } from "./run-env-
|
|
10
|
+
import { $ as ensureProxyRunning, Q as DEFAULT_PROXY_PORT, St as sessionMapPath, it as resolveTarget, yt as itermUuid } from "./run-CsrBbtj_.mjs";
|
|
11
|
+
import { i as ROUTER_PORT, t as buildRunEnv } from "./run-env-Cyw2iwBh.mjs";
|
|
12
12
|
import { o as sendToSession, r as fetchLiveSessions } from "./aibroker-client-Dome-XV2.mjs";
|
|
13
13
|
import { closeSync, createReadStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { homedir, setPriority, tmpdir, userInfo } from "node:os";
|
|
@@ -4545,4 +4545,4 @@ async function runRouterTick(deps) {
|
|
|
4545
4545
|
|
|
4546
4546
|
//#endregion
|
|
4547
4547
|
export { setLastIndexTime as $, getGitContext as A, runPassChild as At, lastEmbedTime as B, findMovedProjects as Bt, hasPendingOrProcessingOfType as C, archiveSessionFilesToSessionsDir as Ct, migrateWorkQueue as D, PassSpawnError as Dt, markFailed as E, migrateIdentityFile as Et, daemonConfig as F, resolveHome as Ft, registryBackend as G, formatDetectionJson as Gt, lastVaultIndexTime as H, transcriptFiles as Ht, embedInProgress as I, saveScanConfig as It, setEmbedInProgress as J, setCacheKeepaliveTimer as K, embedSchedulerTimer as L, upsertProject as Lt, migrateSummaryCooldowns as M, loadScanConfig as Mt, spawnSummarizer as N, migrateScanConfig as Nt, contentToText as O, cliEntry as Ot, cacheKeepaliveTimer as P, performScan as Pt, setLastEmbedTime as Q, indexInProgress as R, upsertSession as Rt, getStats as S, getCurrentNotePath as St, markCompleted as T, buildWakeupContext as Tt, notificationConfig as U, detectProject as Ut, lastIndexTime as V, scanTranscriptFolders as Vt, passFailures as W, formatDetection as Wt, setIndexInProgress as X, setEmbedSchedulerTimer as Y, setIndexSchedulerTimer as Z, resolveDaemonPid as _, findNotesDir as _t, routerRunningPid as a, setStorageBackend as at, dequeue as b, appendCheckpoint as bt, stopRouter as c, startTime as ct, loadSessionKeepaliveState as d, extractAndStoreTriples$1 as dt, setLastVaultIndexTime as et, runSessionKeepaliveTick as f, sessionIdFromTranscript as ft, removeDaemonPidfile as g, findLatestNote as gt, totalUsageTokens as h, applyContinue as ht, routerIsStale as i, setStartTime as it, handleSessionSummary as j, cmdScan as jt, findLatestJsonl as k, reexecBackground as kt, readContextHandoverCache as l, storageBackend as lt, parseSessionUsage as m, appendCheckpointToNote as mt, ROUTER_PID_FILE as n, setRegistryBackend as nt, runRouterTick as o, setVaultIndexInProgress as ot, sessionKeepaliveLedgerSummary as p, updateTodoContinue as pt, setDaemonConfig as q, probeRouter as r, setShutdownRequested as rt, spawnRouter as s, shutdownRequested as st, ROUTER_LOG_FILE as t, setNotificationConfig as tt, writeContextHandoverCache as u, vaultIndexInProgress as ut, writeDaemonPidfile as v, readBodyFile as vt, loadQueue as w, findNotesDir$1 as wt, enqueue as x, finalizeSessionNote as xt, cleanup as y, addWorkToSessionNote as yt, indexSchedulerTimer as z, claudeProjectsDir as zt };
|
|
4548
|
-
//# sourceMappingURL=supervisor-
|
|
4548
|
+
//# sourceMappingURL=supervisor-BZA0KICt.mjs.map
|