@tekmidian/pai 0.65.2 → 0.66.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{chain-CT3SI-3z.mjs → chain-CZfKIEq9.mjs} +4 -4
  2. package/dist/{chain-CT3SI-3z.mjs.map → chain-CZfKIEq9.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +9 -9
  4. package/dist/cli/program.mjs +9 -9
  5. package/dist/{context-handover-cache-CRSFdkNi.mjs → context-handover-cache-pkHzmL3c.mjs} +3 -3
  6. package/dist/{context-handover-cache-CRSFdkNi.mjs.map → context-handover-cache-pkHzmL3c.mjs.map} +1 -1
  7. package/dist/daemon/index.mjs +7 -7
  8. package/dist/{daemon-SaPnmoyg.mjs → daemon-DXlaVCkw.mjs} +9 -9
  9. package/dist/{daemon-SaPnmoyg.mjs.map → daemon-DXlaVCkw.mjs.map} +1 -1
  10. package/dist/{daemon-DoTVK9z6.mjs → daemon-DyORmxZu.mjs} +7 -7
  11. package/dist/daemon-mcp/index.mjs +8 -6
  12. package/dist/daemon-mcp/index.mjs.map +1 -1
  13. package/dist/{fallback-vHjFZP0Y.mjs → fallback-CIiq2Raw.mjs} +18 -8
  14. package/dist/fallback-CIiq2Raw.mjs.map +1 -0
  15. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  16. package/dist/hooks/load-project-context.mjs +16 -0
  17. package/dist/hooks/load-project-context.mjs.map +2 -2
  18. package/dist/hooks/route-agents-to-worker.mjs +16 -0
  19. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  20. package/dist/hooks/security-validator.mjs +16 -0
  21. package/dist/hooks/security-validator.mjs.map +2 -2
  22. package/dist/hooks/worker-guard.mjs +15 -0
  23. package/dist/hooks/worker-guard.mjs.map +3 -3
  24. package/dist/hooks/worker-proxy.mjs +16 -0
  25. package/dist/hooks/worker-proxy.mjs.map +2 -2
  26. package/dist/hooks/worker-status-line.mjs +17 -0
  27. package/dist/hooks/worker-status-line.mjs.map +2 -2
  28. package/dist/hooks/worker-supervision.mjs +16 -0
  29. package/dist/hooks/worker-supervision.mjs.map +2 -2
  30. package/dist/{main-resolver-VSM5ytnA.mjs → main-resolver-BKz_OuEh.mjs} +2 -2
  31. package/dist/{main-resolver-CKNGux1B.mjs → main-resolver-DPPwHNtn.mjs} +2 -2
  32. package/dist/{main-resolver-CKNGux1B.mjs.map → main-resolver-DPPwHNtn.mjs.map} +1 -1
  33. package/dist/{planner-Dzs9-8AS.mjs → planner-Dm5Acyo1.mjs} +5 -5
  34. package/dist/{planner-Dzs9-8AS.mjs.map → planner-Dm5Acyo1.mjs.map} +1 -1
  35. package/dist/{program-DK0mRRlf.mjs → program-JJ0TwSHI.mjs} +59 -16
  36. package/dist/program-JJ0TwSHI.mjs.map +1 -0
  37. package/dist/{run-DMaCpvMW.mjs → run-BCpbxqA5.mjs} +490 -26
  38. package/dist/run-BCpbxqA5.mjs.map +1 -0
  39. package/dist/{run-env-BHWnXqle.mjs → run-env-DRcp7A8K.mjs} +16 -2
  40. package/dist/run-env-DRcp7A8K.mjs.map +1 -0
  41. package/dist/{server-DlL1QI2k.mjs → server-DgmAHyFK.mjs} +2 -2
  42. package/dist/{server-DlL1QI2k.mjs.map → server-DgmAHyFK.mjs.map} +1 -1
  43. package/dist/{session-keepalive-DVZ9-7qg.mjs → session-keepalive-DxjTHGcK.mjs} +4 -4
  44. package/dist/{session-keepalive-DVZ9-7qg.mjs.map → session-keepalive-DxjTHGcK.mjs.map} +1 -1
  45. package/dist/{work-queue-worker-DZiEMvBQ.mjs → work-queue-worker-4tLa5gpQ.mjs} +2 -2
  46. package/dist/{work-queue-worker-DZiEMvBQ.mjs.map → work-queue-worker-4tLa5gpQ.mjs.map} +1 -1
  47. package/dist/{work-queue-worker-D7GHuj2I.mjs → work-queue-worker-rFZvTO3p.mjs} +4 -4
  48. package/docs/commands/README.md +1 -0
  49. package/docs/commands/worker.md +30 -0
  50. package/package.json +1 -1
  51. package/src/hooks/ts/lib/worker-guard.test.ts +12 -0
  52. package/src/hooks/ts/lib/worker-guard.ts +13 -0
  53. package/dist/fallback-vHjFZP0Y.mjs.map +0 -1
  54. package/dist/program-DK0mRRlf.mjs.map +0 -1
  55. package/dist/run-DMaCpvMW.mjs.map +0 -1
  56. package/dist/run-env-BHWnXqle.mjs.map +0 -1
@@ -777,6 +777,7 @@ const DEFAULT_TREE = {
777
777
  * "cacheKeepaliveSecs": 60).
778
778
  */
779
779
  const DEFAULT_CACHE_KEEPALIVE_SECS = 0;
780
+ const DEFAULT_MAX_MINUTES = 60;
780
781
  /**
781
782
  * The default MCP sets every config starts from. `desktop` names the clickr
782
783
  * server so `--mcp desktop` hands a worker the machine controls (read-only
@@ -801,6 +802,8 @@ function defaultWorkersConfig() {
801
802
  },
802
803
  tree: { ...DEFAULT_TREE },
803
804
  cacheKeepaliveSecs: DEFAULT_CACHE_KEEPALIVE_SECS,
805
+ defaultMaxMinutes: DEFAULT_MAX_MINUTES,
806
+ noBrowserByDefault: false,
804
807
  caveman: false,
805
808
  fallback: null,
806
809
  nativeModels: { ...NATIVE_ANTHROPIC_MODELS }
@@ -1090,6 +1093,13 @@ function parseWorkersConfig(raw) {
1090
1093
  if (typeof w.cacheKeepaliveSecs !== "number" || !Number.isInteger(w.cacheKeepaliveSecs) || w.cacheKeepaliveSecs < 0) bad(".cacheKeepaliveSecs", "must be a non-negative integer (seconds, 0 = off)");
1091
1094
  cacheKeepaliveSecs = w.cacheKeepaliveSecs;
1092
1095
  }
1096
+ let defaultMaxMinutes = DEFAULT_MAX_MINUTES;
1097
+ if (w.defaultMaxMinutes !== void 0) {
1098
+ if (typeof w.defaultMaxMinutes !== "number" || !Number.isInteger(w.defaultMaxMinutes) || w.defaultMaxMinutes < 0) bad(".defaultMaxMinutes", "must be a non-negative integer (minutes, 0 = no limit)");
1099
+ defaultMaxMinutes = w.defaultMaxMinutes;
1100
+ }
1101
+ if (w.noBrowserByDefault !== void 0 && typeof w.noBrowserByDefault !== "boolean") bad(".noBrowserByDefault", "must be true or false");
1102
+ const noBrowserByDefault = w.noBrowserByDefault === true;
1093
1103
  if (w.caveman !== void 0 && typeof w.caveman !== "boolean") bad(".caveman", "must be true or false");
1094
1104
  const caveman = w.caveman === true;
1095
1105
  const active = w.active === void 0 || w.active === null ? null : str(w.active);
@@ -1129,6 +1139,8 @@ function parseWorkersConfig(raw) {
1129
1139
  routing,
1130
1140
  tree,
1131
1141
  cacheKeepaliveSecs,
1142
+ defaultMaxMinutes,
1143
+ noBrowserByDefault,
1132
1144
  caveman,
1133
1145
  fallback,
1134
1146
  nativeModels: { ...NATIVE_ANTHROPIC_MODELS }
@@ -1194,6 +1206,8 @@ function writeWorkersSection(raw, workers, path = CONFIG_FILE) {
1194
1206
  routing: workers.routing,
1195
1207
  tree: workers.tree,
1196
1208
  cacheKeepaliveSecs: workers.cacheKeepaliveSecs,
1209
+ defaultMaxMinutes: workers.defaultMaxMinutes,
1210
+ noBrowserByDefault: workers.noBrowserByDefault,
1197
1211
  caveman: workers.caveman,
1198
1212
  ...workers.fallback ? { fallback: workers.fallback } : {}
1199
1213
  } : workers.fallback ? workers : {
@@ -1447,5 +1461,5 @@ function onPath(bin, env) {
1447
1461
  }
1448
1462
 
1449
1463
  //#endregion
1450
- export { writeWorkersSection as A, contextWindowFromModelId as B, nativeAnthropicProvider as C, resolveCapability as D, readWorkersSection as E, parseWorkersYamlDocument as F, relocateWorkersYaml as I, workersYamlLegacyNotice as L, inlineWorkersYamlKeys as M, migrateWorkersToYaml as N, resolveModelCapability as O, needsWorkersYamlRelocation as P, workersYamlPath as R, maskKey as S, providerKeyPath as T, stripModelVariant as V, defaultWorkersConfig as _, DEFAULT_CACHE_KEEPALIVE_SECS as a, isModelCapability as b, DEFAULT_ROUTING as c, NATIVE_ANTHROPIC_MODELS as d, PROVIDER_TAGS as f, classModelCapability as g, assertProviderRunnable as h, CAPABILITY_NAME_RE as i, initWorkersYaml as j, resolveProviderKey as k, DEFAULT_TREE as l, WorkersConfigError as m, claudeCommand as n, DEFAULT_LOG_DIR as o, WORKER_CLASSES as p, ANTHROPIC_NATIVE as r, DEFAULT_PANE as s, buildRunEnv as t, MODEL_CAPABILITIES as u, expandHome as v, providerCostTier as w, keysDir as x, getProviderOrNative as y, DEFAULT_CONTEXT_WINDOW as z };
1451
- //# sourceMappingURL=run-env-BHWnXqle.mjs.map
1464
+ export { resolveProviderKey as A, DEFAULT_CONTEXT_WINDOW as B, maskKey as C, readWorkersSection as D, providerKeyPath as E, needsWorkersYamlRelocation as F, stripModelVariant as H, parseWorkersYamlDocument as I, relocateWorkersYaml as L, initWorkersYaml as M, inlineWorkersYamlKeys as N, resolveCapability as O, migrateWorkersToYaml as P, workersYamlLegacyNotice as R, keysDir as S, providerCostTier as T, contextWindowFromModelId as V, classModelCapability as _, DEFAULT_CACHE_KEEPALIVE_SECS as a, getProviderOrNative as b, DEFAULT_PANE as c, MODEL_CAPABILITIES as d, NATIVE_ANTHROPIC_MODELS as f, assertProviderRunnable as g, WorkersConfigError as h, CAPABILITY_NAME_RE as i, writeWorkersSection as j, resolveModelCapability as k, DEFAULT_ROUTING as l, WORKER_CLASSES as m, claudeCommand as n, DEFAULT_LOG_DIR as o, PROVIDER_TAGS as p, ANTHROPIC_NATIVE as r, DEFAULT_MAX_MINUTES as s, buildRunEnv as t, DEFAULT_TREE as u, defaultWorkersConfig as v, nativeAnthropicProvider as w, isModelCapability as x, expandHome as y, workersYamlPath as z };
1465
+ //# sourceMappingURL=run-env-DRcp7A8K.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-env-DRcp7A8K.mjs","names":[],"sources":["../src/utils/model-window.ts","../src/workers/workers-config.ts","../src/workers/config.ts","../src/workers/run-env.ts"],"sourcesContent":["/**\n * model-window.ts — facts derivable from a bare model id.\n *\n * Shared by the worker stack (provider registry) and the session hooks\n * (context-fill). Zero imports on purpose: this leaf is bundled into hooks\n * (scripts/build-hooks.mjs) and must never drag config or path machinery\n * into a hook process.\n */\n\n/** Last-resort window when nothing on hand reports one and the model id\n * carries no window information. */\nexport const DEFAULT_CONTEXT_WINDOW = 200_000;\n\n/** Strip a bracketed variant suffix: \"glm-5.3[1m]\" → \"glm-5.3\". The suffix\n * selects a variant of the same base model (a context-window tier, a\n * quantization), so the prefix is the id's family. */\nexport function stripModelVariant(model: string): string {\n const stripped = model.replace(/\\[[^\\]]*\\]\\s*$/, \"\").trim();\n return stripped || model;\n}\n\n/**\n * The context window a model id itself declares, or null when the id carries\n * no window information. Only the bracketed variant suffix is read\n * (\"[1m]\" → 1,000,000); a bare id says nothing and stays null so callers can\n * fall back to whatever they measured themselves.\n */\nexport function contextWindowFromModelId(model: string | null | undefined): number | null {\n if (typeof model !== \"string\" || model === \"\") return null;\n const m = /\\[(\\d+)m\\]$/i.exec(model.trim());\n if (!m) return null;\n const millions = Number(m[1]);\n if (!Number.isFinite(millions) || millions < 1) return null;\n return millions * 1_000_000;\n}\n","/**\n * workers-config.ts — workers.yaml: providers, model roles, class routing,\n * mcp_sets.\n *\n * config.ts's readWorkersSection/writeWorkersSection are the only callers —\n * every other module keeps talking to the WorkersConfig shape from config.ts\n * and never touches this file. That is deliberate: adding a provider used to\n * mean editing JSON inside an unrelated config blob; this module makes\n * providers/classes/mcp_sets/active a single human-editable file, documented\n * in docs/workers-config.md, while everything downstream is unchanged.\n *\n * Writes go through the `yaml` package's Document API and only touch the\n * entries that actually changed, so hand-written comments elsewhere in the\n * file — including a comment directly above an untouched provider — survive\n * byte-for-byte across `add`, `use`, `disable`, and every other mutation.\n */\n\nimport { existsSync, readFileSync, writeFileSync, chmodSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { Document, Scalar, parseDocument, type Node } from \"yaml\";\nimport { readJsonStrict, writeJsonAtomic } from \"../config/json-store.js\";\nimport { writeYamlFileAtomic, YamlStoreError } from \"../config/yaml-store.js\";\nimport { paiHomePath, resolvePaiFile, migratePaiFile, type MigrateFileResult } from \"../config/pai-home.js\";\nimport {\n ANTHROPIC_NATIVE,\n NATIVE_ANTHROPIC_MODELS,\n WorkersConfigError,\n expandHome,\n isModelCapability,\n parseCapabilitiesValue,\n parseClassesValue,\n parseMcpSetsValue,\n parseModelsBlock,\n parseProvider,\n parseWorkersConfig,\n type ClassTarget,\n type WorkerProvider,\n} from \"./config.js\";\n\nexport const WORKERS_YAML_FILENAME = \"workers.yaml\";\n\n/**\n * Where workers.yaml lives since 2026-09-19: under the PAI_HOME namespace\n * dir (~/.claude/pai by default) — this file is per-user state that can\n * carry API keys and must never be committed, same as the rest of PAI_HOME.\n */\nfunction defaultWorkersYamlPath(): string {\n return paiHomePath(WORKERS_YAML_FILENAME);\n}\n\n/** Briefly the canonical location between 2026-09-19's two migrations —\n * read during the transition, never written to again. */\nfunction oldWorkersYamlPath(): string {\n return join(homedir(), \".claude\", WORKERS_YAML_FILENAME);\n}\n\n/** Where workers.yaml lived before 2026-09-19 — read during the transition,\n * never written to (see `pai worker config migrate`). */\nfunction legacyWorkersYamlPath(): string {\n return join(homedir(), \".config\", \"pai\", WORKERS_YAML_FILENAME);\n}\n\n/**\n * The path any read/write of workers.yaml actually uses: PAI_WORKERS_YAML\n * (tests, power users) first, else the new PAI_HOME location if it exists,\n * else the most recent old location that is actually on disk (printing a\n * one-time notice), else the new location (the target a first write creates).\n */\nexport function workersYamlPath(): string {\n const override = process.env.PAI_WORKERS_YAML;\n if (override) return override;\n return resolvePaiFile(\n defaultWorkersYamlPath(),\n [oldWorkersYamlPath(), legacyWorkersYamlPath()],\n \"pai worker config migrate\"\n );\n}\n\n/** Where a fresh write (init, migrate, relocate's destination) always\n * targets — the new location, or PAI_WORKERS_YAML for test isolation.\n * Never an old path: nothing is ever written there again. */\nfunction writeTargetWorkersYamlPath(): string {\n return process.env.PAI_WORKERS_YAML ?? defaultWorkersYamlPath();\n}\n\n/**\n * One-line notice for `pai worker config check` and `pai worker providers`\n * when workers.yaml is still sitting at an old location. Null once it has\n * moved (or PAI_WORKERS_YAML is set — that always wins, nothing to migrate).\n */\nexport function workersYamlLegacyNotice(): string | null {\n if (process.env.PAI_WORKERS_YAML) return null;\n if (existsSync(defaultWorkersYamlPath())) return null;\n const found = [oldWorkersYamlPath(), legacyWorkersYamlPath()].find((p) => existsSync(p));\n if (!found) return null;\n return `workers.yaml is still at the old location (${found}) — run \\`pai worker config migrate\\` to move it to ${defaultWorkersYamlPath()}`;\n}\n\nexport interface WorkersYamlData {\n active: string | null;\n providers: Record<string, WorkerProvider>;\n classes: Record<string, ClassTarget>;\n capabilities: Record<string, string[]>;\n mcpSets: Record<string, string[]>;\n nativeModels: WorkerProvider[\"models\"];\n}\n\n// ---------------------------------------------------------------------------\n// Friendly (on-disk) provider shape ↔ internal WorkerProvider\n// ---------------------------------------------------------------------------\n\n/** snake_case on-disk keys → the camelCase raw shape parseProvider expects. */\nfunction yamlProviderToRaw(y: Record<string, unknown>): Record<string, unknown> {\n const raw: Record<string, unknown> = {};\n if (y.enabled !== undefined) raw.enabled = y.enabled;\n if (y.url !== undefined) raw.baseUrl = y.url;\n if (y.key_file !== undefined) raw.keyFile = y.key_file;\n if (y.key !== undefined) raw.key = y.key;\n if (y.tier !== undefined) raw.costTier = y.tier;\n if (y.models !== undefined) raw.models = y.models;\n if (y.protocol !== undefined) raw.protocol = y.protocol;\n if (y.engine !== undefined) raw.engine = y.engine;\n if (y.upstream_url !== undefined) raw.upstreamUrl = y.upstream_url;\n if (y.env !== undefined) raw.env = y.env;\n if (y.note !== undefined) raw.note = y.note;\n if (y.quota_probe !== undefined) raw.quotaProbe = y.quota_probe;\n if (y.quota_skip_at !== undefined) raw.quotaSkipAt = y.quota_skip_at;\n if (y.context_window !== undefined) raw.contextWindow = y.context_window;\n if (y.tags !== undefined) raw.tags = y.tags;\n if (y.usage !== undefined) raw.usage = y.usage;\n if (y.model_tiers !== undefined) raw.modelTiers = y.model_tiers;\n return raw;\n}\n\n/** Internal WorkerProvider → the friendly on-disk plain object for writing. */\nfunction providerToYamlPlain(p: WorkerProvider): Record<string, unknown> {\n const y: Record<string, unknown> = {};\n if (!p.enabled) y.enabled = false;\n if (p.baseUrl) y.url = p.baseUrl;\n if (p.keyFile) y.key_file = p.keyFile;\n if (p.key) y.key = p.key;\n if (p.costTier !== undefined) y.tier = p.costTier;\n y.models = { ...p.models };\n if (p.protocol && p.protocol !== \"anthropic\") y.protocol = p.protocol;\n if (p.engine && p.engine !== \"claude\") y.engine = p.engine;\n if (p.upstreamUrl) y.upstream_url = p.upstreamUrl;\n if (p.env && Object.keys(p.env).length) y.env = { ...p.env };\n if (p.note) y.note = p.note;\n if (p.quotaProbe) y.quota_probe = p.quotaProbe;\n if (p.quotaSkipAt !== undefined) y.quota_skip_at = p.quotaSkipAt;\n if (p.contextWindow !== undefined) y.context_window = p.contextWindow;\n if (p.tags?.length) y.tags = [...p.tags];\n if (p.usage) y.usage = p.usage;\n if (p.modelTiers) y.model_tiers = { ...p.modelTiers };\n return y;\n}\n\n/** The builtin `anthropic` block only ever carries `builtin: true` + `models`. */\nfunction parseBuiltinAnthropic(raw: unknown, pathPrefix: string): WorkerProvider[\"models\"] {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n throw new WorkersConfigError(`${pathPrefix}: must be an object`);\n }\n const p = raw as Record<string, unknown>;\n if (p.builtin !== true) {\n throw new WorkersConfigError(\n `${pathPrefix}: \"${ANTHROPIC_NATIVE}\" is reserved for the built-in Claude Code login — ` +\n `set \"builtin: true\" (optionally with a \"models\" override) or rename this provider`\n );\n }\n const disallowed = [\"url\", \"key_file\", \"key\", \"engine\", \"protocol\", \"upstream_url\"].filter(\n (k) => p[k] !== undefined\n );\n if (disallowed.length) {\n throw new WorkersConfigError(\n `${pathPrefix}: a builtin provider cannot set ${disallowed.join(\", \")} — it runs on Claude Code's own login`\n );\n }\n if (p.models === undefined) return { ...NATIVE_ANTHROPIC_MODELS };\n return { ...NATIVE_ANTHROPIC_MODELS, ...parseModelsBlock(`${pathPrefix}.models`, p.models) };\n}\n\n// ---------------------------------------------------------------------------\n// Line-numbered errors\n// ---------------------------------------------------------------------------\n\nfunction lineOf(doc: Document, path: (string | number)[]): number | null {\n try {\n const node = doc.getIn(path, true) as Node | undefined;\n const range = node && typeof node === \"object\" && \"range\" in node ? node.range : undefined;\n if (!range) return null;\n const text = String(doc);\n return text.slice(0, range[0]).split(\"\\n\").length;\n } catch {\n return null;\n }\n}\n\nfunction withLine(doc: Document, path: (string | number)[], yamlPath: string, e: unknown): never {\n const msg = e instanceof Error ? e.message : String(e);\n const line = lineOf(doc, path);\n throw new WorkersConfigError(line ? `${yamlPath}:${line}: ${msg}` : `${yamlPath}: ${msg}`);\n}\n\n// ---------------------------------------------------------------------------\n// Load + validate\n// ---------------------------------------------------------------------------\n\n/**\n * Parse and fully validate a workers.yaml Document. Throws WorkersConfigError\n * naming `<path>:<line>` for anything wrong — an unknown provider referenced\n * by a class is a load error here, not a spawn-time surprise.\n */\nexport function parseWorkersYamlDocument(doc: Document, yamlPath: string): WorkersYamlData {\n const root = doc.toJS() ?? {};\n if (typeof root !== \"object\" || Array.isArray(root)) {\n throw new WorkersConfigError(`${yamlPath}: top level must be a mapping`);\n }\n const r = root as Record<string, unknown>;\n\n const providers: Record<string, WorkerProvider> = {};\n let nativeModels: WorkerProvider[\"models\"] = { ...NATIVE_ANTHROPIC_MODELS };\n const providersRaw = r.providers;\n if (providersRaw !== undefined) {\n if (typeof providersRaw !== \"object\" || providersRaw === null || Array.isArray(providersRaw)) {\n withLine(doc, [\"providers\"], yamlPath, new WorkersConfigError(\"providers: must be a mapping of name → provider\"));\n }\n for (const [name, raw] of Object.entries(providersRaw as Record<string, unknown>)) {\n try {\n if (name === ANTHROPIC_NATIVE) {\n nativeModels = parseBuiltinAnthropic(raw, `providers.${name}`);\n } else {\n providers[name] = parseProvider(name, yamlProviderToRaw(raw as Record<string, unknown>));\n }\n } catch (e) {\n withLine(doc, [\"providers\", name], yamlPath, e);\n }\n }\n }\n\n let classes: Record<string, ClassTarget> = {};\n if (r.classes !== undefined) {\n try {\n classes = parseClassesValue(r.classes, \"classes\");\n } catch (e) {\n withLine(doc, [\"classes\"], yamlPath, e);\n }\n // Cross-validate: every class must name a known provider (or \"anthropic\")\n // and, if it names a role, a capability that provider actually declares.\n // Unlike the JSON path (where a provider can be removed out from under a\n // class and the error surfaces at spawn time), workers.yaml catches this\n // at load time.\n for (const [cls, target] of Object.entries(classes)) {\n const provider = typeof target === \"string\" ? target.split(\"/\")[0] : target.provider;\n const alias = typeof target === \"string\" ? target.split(\"/\")[1] : undefined;\n if (provider !== undefined) {\n const native = provider === ANTHROPIC_NATIVE;\n if (!native && !providers[provider]) {\n withLine(\n doc,\n [\"classes\", cls],\n yamlPath,\n new WorkersConfigError(\n `classes.${cls}: no provider named \"${provider}\" (configured: ${Object.keys(providers).join(\", \") || \"(none)\"})`\n )\n );\n }\n if (alias !== undefined) {\n if (!isModelCapability(alias)) {\n withLine(\n doc,\n [\"classes\", cls],\n yamlPath,\n new WorkersConfigError(\n `classes.${cls}: \"${alias}\" is not a valid capability name (must match ^[a-z][a-z0-9-]*$)`\n )\n );\n } else if (!native && providers[provider] && alias !== \"default\" && !providers[provider].models[alias]) {\n // the native provider has no configurable model slots — any\n // alias resolves through resolveModelCapability's own fallback\n withLine(\n doc,\n [\"classes\", cls],\n yamlPath,\n new WorkersConfigError(`classes.${cls}: provider \"${provider}\" has no \"${alias}\" model configured`)\n );\n }\n }\n }\n }\n }\n\n let capabilities: Record<string, string[]> = {};\n if (r.capabilities !== undefined) {\n try {\n capabilities = parseCapabilitiesValue(r.capabilities, \"capabilities\");\n } catch (e) {\n withLine(doc, [\"capabilities\"], yamlPath, e);\n }\n }\n\n let mcpSets: Record<string, string[]> = {};\n if (r.mcp_sets !== undefined) {\n try {\n mcpSets = parseMcpSetsValue(r.mcp_sets, \"mcp_sets\");\n } catch (e) {\n withLine(doc, [\"mcp_sets\"], yamlPath, e);\n }\n } else {\n mcpSets = parseMcpSetsValue(undefined, \"mcp_sets\");\n }\n\n const active = r.active === undefined || r.active === null ? null : String(r.active);\n if (active !== null && active !== ANTHROPIC_NATIVE && active !== \"auto\" && !providers[active]) {\n withLine(\n doc,\n [\"active\"],\n yamlPath,\n new WorkersConfigError(\n `active: no provider named \"${active}\" (configured: ${Object.keys(providers).join(\", \") || \"(none)\"})`\n )\n );\n }\n\n return { active, providers, classes, capabilities, mcpSets, nativeModels };\n}\n\n/** Read + parse workers.yaml. Returns null if the file does not exist. */\nexport function readWorkersYaml(yamlPath: string): { doc: Document; data: WorkersYamlData } | null {\n if (!existsSync(yamlPath)) return null;\n let text: string;\n try {\n text = readFileSync(yamlPath, \"utf8\");\n } catch (e) {\n throw new WorkersConfigError(\n `Could not read ${yamlPath}: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n const doc = parseDocument(text);\n if (doc.errors.length) {\n throw new WorkersConfigError(`${yamlPath}: ${doc.errors[0].message}`);\n }\n const data = parseWorkersYamlDocument(doc, yamlPath);\n return { doc, data };\n}\n\n// ---------------------------------------------------------------------------\n// Write (diff-based, comment-preserving)\n// ---------------------------------------------------------------------------\n\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * A `key:` value as an explicit double-quoted YAML scalar. A key is an\n * opaque token, not YAML-authored text — plain-scalar auto-styling could\n * read one back as a number or boolean (an all-digit token, \"true\", \"no\", …)\n * if it were ever left unquoted, so every write forces the quoted form\n * regardless of what the plain style would otherwise pick.\n */\nfunction quotedKeyNode(value: string): Scalar {\n const s = new Scalar(value);\n s.type = Scalar.QUOTE_DOUBLE;\n return s;\n}\n\n/**\n * Apply the changes between `before` and `after` onto `doc`, touching only\n * the providers/classes/mcp_sets/active entries that actually differ. This is\n * what makes comment preservation possible: an untouched provider's node\n * (and any comment above it) is never revisited.\n */\nfunction syncWorkersYamlDocument(doc: Document, before: WorkersYamlData, after: WorkersYamlData): void {\n // providers: add/update changed, remove gone (never touch the builtin anthropic block)\n for (const [name, p] of Object.entries(after.providers)) {\n const beforeP = before.providers[name];\n const changed = !beforeP || !shallowEqual(providerToYamlPlain(beforeP), providerToYamlPlain(p));\n if (changed) {\n // a fresh (or wholesale-replaced) provider entry is set in one shot as\n // a plain object — the yaml lib only turns it into real Map/Scalar\n // nodes lazily at stringify time, so a follow-up setIn/deleteIn into\n // the same not-yet-a-collection value would fail. Embedding the `key`\n // as an actual Scalar node here rides along: stringify leaves already-\n // Node values alone (see stringifyPair.js), quoting only that field.\n const y: Record<string, unknown> = providerToYamlPlain(p);\n if (typeof y.key === \"string\") y.key = quotedKeyNode(y.key);\n doc.setIn([\"providers\", name], y);\n }\n }\n for (const name of Object.keys(before.providers)) {\n if (!(name in after.providers)) doc.deleteIn([\"providers\", name]);\n }\n\n // builtin anthropic models: only the capabilities that changed; the entry\n // is created (with `builtin: true`) when the file has none yet\n if (!shallowEqual(before.nativeModels, after.nativeModels)) {\n if (!doc.hasIn([\"providers\", ANTHROPIC_NATIVE])) {\n doc.setIn([\"providers\", ANTHROPIC_NATIVE], { builtin: true, models: { ...after.nativeModels } });\n } else {\n for (const [cap, id] of Object.entries(after.nativeModels)) {\n if (before.nativeModels[cap] !== id) doc.setIn([\"providers\", ANTHROPIC_NATIVE, \"models\", cap], id);\n }\n }\n }\n\n // classes: same add/update/remove diff\n for (const [cls, target] of Object.entries(after.classes)) {\n if (!shallowEqual(before.classes[cls], target)) {\n doc.setIn([\"classes\", cls], target);\n }\n }\n for (const cls of Object.keys(before.classes)) {\n if (!(cls in after.classes)) doc.deleteIn([\"classes\", cls]);\n }\n\n // capabilities: same add/update/remove diff\n for (const [cap, prefs] of Object.entries(after.capabilities)) {\n if (!shallowEqual(before.capabilities[cap], prefs)) {\n doc.setIn([\"capabilities\", cap], [...prefs]);\n }\n }\n for (const cap of Object.keys(before.capabilities)) {\n if (!(cap in after.capabilities)) doc.deleteIn([\"capabilities\", cap]);\n }\n\n // mcp_sets: same add/update/remove diff\n for (const [set, servers] of Object.entries(after.mcpSets)) {\n if (!shallowEqual(before.mcpSets[set], servers)) {\n doc.setIn([\"mcp_sets\", set], [...servers]);\n }\n }\n for (const set of Object.keys(before.mcpSets)) {\n if (!(set in after.mcpSets)) doc.deleteIn([\"mcp_sets\", set]);\n }\n\n // active\n if (before.active !== after.active) {\n if (after.active === null) doc.deleteIn([\"active\"]);\n else doc.setIn([\"active\"], after.active);\n }\n}\n\n/**\n * Write `after` into workers.yaml, preserving comments on everything that did\n * not change. Re-reads the file fresh (so the diff is against what is really\n * on disk, not a stale in-memory copy) and re-validates the result before\n * committing; on failure the previous bytes are left untouched.\n */\nexport function writeWorkersYaml(yamlPath: string, after: WorkersYamlData): void {\n const existing = readWorkersYaml(yamlPath);\n const doc = existing ? existing.doc : new Document({});\n const before: WorkersYamlData = existing\n ? existing.data\n : {\n active: null,\n providers: {},\n classes: {},\n capabilities: {},\n mcpSets: {},\n nativeModels: { ...NATIVE_ANTHROPIC_MODELS },\n };\n syncWorkersYamlDocument(doc, before, after);\n writeWorkersYamlText(yamlPath, String(doc));\n}\n\n/** Atomic write with a .bak-pai backup and a re-validate-or-restore guard.\n * The file-I/O tail (backup, temp write at 0600, rename, chmod) is shared\n * with every other PAI YAML file via writeYamlFileAtomic; only the\n * workers.yaml-specific validation (parseWorkersYamlDocument) lives here. */\nexport function writeWorkersYamlText(yamlPath: string, text: string): void {\n // Validate before committing anything to disk.\n const probe = parseDocument(text);\n if (probe.errors.length) {\n throw new WorkersConfigError(`refusing to write ${yamlPath}: ${probe.errors[0].message} (previous file left unchanged)`);\n }\n try {\n parseWorkersYamlDocument(probe, yamlPath);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n throw new WorkersConfigError(`refusing to write ${yamlPath}: ${msg} (previous file left unchanged)`);\n }\n\n try {\n writeYamlFileAtomic(yamlPath, text, { label: yamlPath });\n } catch (e) {\n throw new WorkersConfigError(e instanceof YamlStoreError ? e.message : String(e instanceof Error ? e.message : e));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Starter template, init, migrate\n// ---------------------------------------------------------------------------\n\n/** The exact commented starter shipped as examples/workers.yaml and by `init`. */\nexport function starterWorkersYamlText(): string {\n return `# PAI worker configuration.\n# Providers PAI can run workers on, the model each role uses, and which\n# provider every --class goes to. Edit by hand; \\`pai worker providers\\` shows\n# the effective result. Comments are preserved when PAI writes this file.\n#\n# Per-user state, kept 0600: this file can hold API keys (\\`key:\\`, below).\n# Never commit it or share it. \\`key_file: <path>\\` keeps a secret in its own\n# 0600 file instead, if you'd rather not put it here.\n\nactive: anthropic # provider for \\`pai worker run\\` without --provider or --class\n\nproviders:\n anthropic:\n builtin: true # Claude Code's own login: no url, no key file\n models:\n default: ${NATIVE_ANTHROPIC_MODELS.default}\n fast: ${NATIVE_ANTHROPIC_MODELS.fast}\n glm:\n url: https://api.z.ai/api/anthropic\n key: \"<your-api-key>\" # or key_file: <path to a 0600 file>\n tier: 3\n models:\n default: glm-5.3[1m]\n fast: glm-5.3-flash\n image: example-paint\n kimi:\n url: https://api.kimi.ai/coding/\n key: \"<your-api-key>\" # or key_file: <path to a 0600 file>\n tier: 3\n models:\n default: k3[1m]\n fast: kimi-for-coding[1m]\n\n# An \\`engine: image\\` provider does not spawn Claude — \\`pai worker run\n# --capability image\\` POSTs straight to its OpenAI-compatible images API and\n# writes the PNG it gets back. Uncomment and point it at a real provider\n# (still nested under providers:, above) to enable \\`--capability image\\`:\n# pictures:\n# engine: image\n# url: https://api.example.com/v1\n# key: \"<your-api-key>\"\n# models:\n# default: example-image-model\n# image: example-image-model\n\n# A class names a provider, or provider/role to pick a non-default model.\n# Workers exist to parallelise and to save cost: the default is Sonnet,\n# Haiku for the mechanical classes, and nothing here inherits the\n# orchestrating session's model.\nclasses:\n implement: anthropic\n draft: anthropic\n research: anthropic\n complex: anthropic\n plan: anthropic\n review: anthropic\n spotcheck: anthropic/fast\n simple: anthropic/fast\n image: glm/image\n\n# Cross-provider capability preference: which provider serves a --capability\n# request, in order, when more than one declares it. Unlisted capabilities\n# fall back to the active provider, then any provider that declares them.\n# capabilities:\n# image: [pictures, glm]\n# fast: [anthropic]\n\nmcp_sets:\n desktop: [clickr]\n`;\n}\n\n/**\n * Build a workers.yaml document from real (migrated) data, keeping the same\n * header/section comments as the starter. Used by `pai worker config\n * migrate` — `init` writes the literal starter instead, since it has no data\n * to carry over.\n */\nexport function buildWorkersYamlText(data: WorkersYamlData): string {\n const lines: string[] = [];\n lines.push(\"# PAI worker configuration.\");\n lines.push(\"# Providers PAI can run workers on, the model each role uses, and which\");\n lines.push(\"# provider every --class goes to. Edit by hand; `pai worker providers` shows\");\n lines.push(\"# the effective result. Comments are preserved when PAI writes this file.\");\n lines.push(\"\");\n lines.push(\n `active: ${data.active ?? \"null\"}` +\n \" # provider for `pai worker run` without --provider or --class\"\n );\n lines.push(\"\");\n lines.push(\"providers:\");\n lines.push(\" anthropic:\");\n lines.push(\" builtin: true # Claude Code's own login: no url, no key file\");\n lines.push(\" models:\");\n lines.push(` default: ${data.nativeModels.default}`);\n if (data.nativeModels.fast) lines.push(` fast: ${data.nativeModels.fast}`);\n if (data.nativeModels.image) lines.push(` image: ${data.nativeModels.image}`);\n for (const [name, p] of Object.entries(data.providers)) {\n const y = providerToYamlPlain(p);\n lines.push(` ${name}:`);\n for (const [k, v] of Object.entries(y)) {\n if (k === \"models\") continue;\n // a key is an opaque token, not YAML-authored text — always quoted so\n // it never round-trips as a number or boolean (see quotedKeyNode).\n lines.push(k === \"key\" ? ` key: ${JSON.stringify(String(v))}` : ` ${k}: ${yamlScalar(v)}`);\n }\n lines.push(\" models:\");\n for (const [k, v] of Object.entries(p.models)) lines.push(` ${k}: ${yamlScalar(v)}`);\n }\n lines.push(\"\");\n lines.push(\"# A class names a provider, or provider/role to pick a non-default model.\");\n lines.push(\"# Workers exist to parallelise and to save cost: the default is Sonnet,\");\n lines.push(\"# Haiku for the mechanical classes, and nothing here inherits the\");\n lines.push(\"# orchestrating session's model.\");\n const classEntries = Object.entries(data.classes);\n lines.push(classEntries.length ? \"classes:\" : \"classes: {}\");\n for (const [cls, target] of classEntries) {\n lines.push(` ${cls}: ${typeof target === \"string\" ? target : yamlScalar(target)}`);\n }\n lines.push(\"\");\n const capEntries = Object.entries(data.capabilities);\n if (capEntries.length) {\n lines.push(\"capabilities:\");\n for (const [cap, prefs] of capEntries) lines.push(` ${cap}: [${prefs.join(\", \")}]`);\n lines.push(\"\");\n }\n const mcpEntries = Object.entries(data.mcpSets);\n lines.push(mcpEntries.length ? \"mcp_sets:\" : \"mcp_sets: {}\");\n for (const [set, servers] of mcpEntries) {\n lines.push(` ${set}: [${servers.join(\", \")}]`);\n }\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** Minimal scalar formatter for buildWorkersYamlText's flat key: value lines. */\nfunction yamlScalar(v: unknown): string {\n if (typeof v === \"string\") {\n // quote only when the plain form would be ambiguous YAML\n return /^[\\w./~\\[\\]-]+$/.test(v) ? v : JSON.stringify(v);\n }\n if (Array.isArray(v)) return `[${v.map(yamlScalar).join(\", \")}]`;\n if (v && typeof v === \"object\") return JSON.stringify(v);\n return String(v);\n}\n\n// ---------------------------------------------------------------------------\n// Migration from the JSON `workers` section\n// ---------------------------------------------------------------------------\n\nexport interface MigrateResult {\n yamlPath: string;\n yamlText: string;\n /** Path the pre-migration JSON `workers` section was backed up to; null on a dry run. */\n backupPath: string | null;\n dryRun: boolean;\n}\n\n/**\n * `pai worker config migrate`: reads the JSON `workers` section, writes\n * workers.yaml with the starter's comments, backs the JSON section up to\n * `workers.json.migrated-<date>` next to it, and strips providers/classes\n * (and the legacy `roles`)/mcpSets/active from the JSON. Idempotent: refuses\n * when workers.yaml already exists unless `force`. `dryRun` computes and\n * returns the would-be YAML text without touching either file.\n */\nexport function migrateWorkersToYaml(\n jsonPath: string,\n opts: { force?: boolean; dryRun?: boolean } = {}\n): MigrateResult {\n const existing = workersYamlPath();\n if (existsSync(existing) && !opts.force) {\n throw new WorkersConfigError(\n `${existing} already exists — refusing to overwrite without --force`\n );\n }\n const yamlPath = writeTargetWorkersYamlPath();\n const raw = readJsonStrict(jsonPath, jsonPath);\n const workers = parseWorkersConfig(raw.workers);\n const data: WorkersYamlData = {\n active: workers.active,\n providers: workers.providers,\n classes: workers.classes,\n capabilities: workers.capabilities,\n mcpSets: workers.mcpSets,\n nativeModels: workers.nativeModels,\n };\n const yamlText = buildWorkersYamlText(data);\n\n if (opts.dryRun) {\n return { yamlPath, yamlText, backupPath: null, dryRun: true };\n }\n\n writeWorkersYamlText(yamlPath, yamlText);\n\n const stamp = new Date().toISOString().slice(0, 10);\n const backupPath = join(dirname(jsonPath), `workers.json.migrated-${stamp}`);\n writeFileSync(backupPath, JSON.stringify(raw.workers ?? {}, null, 2) + \"\\n\", \"utf8\");\n\n const priorWorkers = (raw.workers as Record<string, unknown>) ?? {};\n const { providers: _p, classes: _c, roles: _r, mcpSets: _m, active: _a, ...rest } = priorWorkers;\n raw.workers = rest;\n writeJsonAtomic(jsonPath, raw, { label: jsonPath });\n\n return { yamlPath, yamlText, backupPath, dryRun: false };\n}\n\n/**\n * `pai worker config init`: write the literal commented starter (no data to\n * carry over — that is what `migrate` is for). Refuses if workers.yaml\n * already exists.\n */\nexport function initWorkersYaml(): string {\n const existing = workersYamlPath();\n if (existsSync(existing)) {\n throw new WorkersConfigError(\n `${existing} already exists — edit it directly, or run \\`pai worker config migrate --force\\` to regenerate it from the JSON config`\n );\n }\n const yamlPath = writeTargetWorkersYamlPath();\n writeWorkersYamlText(yamlPath, starterWorkersYamlText());\n return yamlPath;\n}\n\n// ---------------------------------------------------------------------------\n// Relocating from an old location\n// ---------------------------------------------------------------------------\n\nexport type RelocateResult = MigrateFileResult;\n\n/** True when an old-location workers.yaml exists and nothing is at the new one yet. */\nexport function needsWorkersYamlRelocation(): boolean {\n return (\n !process.env.PAI_WORKERS_YAML &&\n !existsSync(defaultWorkersYamlPath()) &&\n [oldWorkersYamlPath(), legacyWorkersYamlPath()].some((p) => existsSync(p))\n );\n}\n\n/**\n * `pai worker config migrate` when workers.yaml is still at an old location\n * (~/.claude/workers.yaml or, older still, ~/.config/pai/workers.yaml): a\n * byte-for-byte copy to the new path (comments, keys, everything — this only\n * changes where the file lives), verified, then the old file is renamed\n * aside as workers.yaml.migrated-<YYYYMMDD> (never deleted). No JSON\n * involved, and no secrets are read or transformed beyond copying bytes.\n */\nexport function relocateWorkersYaml(opts: { force?: boolean; dryRun?: boolean } = {}): RelocateResult {\n const toPath = writeTargetWorkersYamlPath();\n const result = migratePaiFile(toPath, [oldWorkersYamlPath(), legacyWorkersYamlPath()], opts);\n if (!result.dryRun && result.fromPath) chmodSync(toPath, 0o600);\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Inlining key_file contents as `key:`\n// ---------------------------------------------------------------------------\n\nexport interface InlineKeysResult {\n yamlPath: string;\n inlined: { provider: string; keyFilePath: string }[];\n dryRun: boolean;\n}\n\n/**\n * `pai worker config inline-keys`: for every provider with a `key_file` and\n * no `key`, read the file, write its trimmed contents as a quoted `key:`,\n * and remove `key_file`. The key file itself is left on disk — this only\n * changes what workers.yaml reads from, never deletes a credential the\n * operator might still want. `dryRun` computes the plan (provider names and\n * key file paths only — never the key values) without writing anything.\n */\nexport function inlineWorkersYamlKeys(opts: { dryRun?: boolean } = {}): InlineKeysResult {\n const yamlPath = workersYamlPath();\n const existing = readWorkersYaml(yamlPath);\n if (!existing) {\n throw new WorkersConfigError(`${yamlPath} does not exist — run \\`pai worker config init\\` first`);\n }\n const { doc, data } = existing;\n const inlined: { provider: string; keyFilePath: string }[] = [];\n for (const [name, p] of Object.entries(data.providers)) {\n if (p.key || !p.keyFile) continue;\n const keyPath = expandHome(p.keyFile);\n let content: string;\n try {\n content = readFileSync(keyPath, \"utf8\");\n } catch (e) {\n throw new WorkersConfigError(\n `providers.${name}.key_file: could not read ${keyPath}: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n const token = content.trim();\n if (!token) throw new WorkersConfigError(`providers.${name}.key_file: ${keyPath} is empty`);\n inlined.push({ provider: name, keyFilePath: p.keyFile });\n if (!opts.dryRun) {\n doc.setIn([\"providers\", name, \"key\"], quotedKeyNode(token));\n doc.deleteIn([\"providers\", name, \"key_file\"]);\n }\n }\n if (!opts.dryRun && inlined.length) {\n writeWorkersYamlText(yamlPath, String(doc));\n }\n return { yamlPath, inlined, dryRun: !!opts.dryRun };\n}\n","/**\n * config.ts — the `workers` section of the PAI config file (CONFIG_FILE,\n * see src/daemon/config.ts's paiConfigFilePath — ~/.claude/pai/config.yaml\n * today, config.json until `pai config yaml` runs).\n *\n * Everything that knows about worker providers reads this module: the CLI\n * (`pai worker …`), the MCP tools (worker_*), and the Agent-routing hook.\n * Keep it free of commander/MCP imports so all three layers stay thin over it.\n *\n * Keys are never stored here — only paths to 0600 files. A provider without a\n * keyFile is a local server and gets the placeholder token \"local\".\n *\n * `protocol: \"openai\"` providers run through the PAI proxy (src/workers/proxy):\n * `upstreamUrl` is their Chat Completions base, and `run` points\n * ANTHROPIC_BASE_URL at the local proxy with the provider name in the path.\n * `engine: \"codex\"` providers run through the Codex CLI instead of Claude\n * Code.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { CONFIG_FILE, readMainConfigRaw, writeMainConfigRaw } from \"../daemon/config.js\";\nimport { paiHomePath } from \"../config/pai-home.js\";\nimport { contextWindowFromModelId, DEFAULT_CONTEXT_WINDOW } from \"../utils/model-window.js\";\nimport { readWorkersYaml, workersYamlPath, writeWorkersYaml, type WorkersYamlData } from \"./workers-config.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Wire protocol a provider speaks: \"anthropic\" natively, \"openai\" through the\n * PAI proxy (which translates the Anthropic Messages API to Chat Completions).\n */\nexport type WorkerProtocol = \"anthropic\" | \"openai\";\n\n/**\n * Runner executable behind a provider: Claude Code, the Codex CLI, or the\n * \"image\" engine (src/workers/engines/image.ts — an OpenAI-compatible\n * images API, not a claude/codex spawn).\n */\nexport type WorkerEngine = \"claude\" | \"codex\" | \"image\";\n\n/** One quota window the statusline renders for a provider (e.g. \"5h\", \"7d\"). */\nexport interface UsageWindow {\n /** Window label shown in the statusline, e.g. \"5h\". */\n name: string;\n /** jq expression against the usage response yielding a percent 0–100. */\n percent: string;\n /** jq expression against the usage response yielding the reset time. */\n resetAt?: string;\n /** How resetAt is interpreted: epoch ms (default), epoch s, or ISO string. */\n resetUnit?: \"ms\" | \"s\" | \"iso\";\n}\n\n/**\n * How the statusline fetches and renders a provider's plan quota. The block\n * names a JSON GET endpoint plus per-window jq expressions, so a new provider\n * is config, not code in statusline-command.sh.\n */\nexport interface ProviderUsage {\n /** GET endpoint returning the usage JSON. */\n url: string;\n /**\n * Auth header template, default \"Authorization: Bearer\"; the key from\n * keyFile is appended after a space. A value without a space (e.g.\n * \"x-api-key\") is sent as \"<authHeader>: <key>\".\n */\n authHeader?: string;\n /** Display label for the usage segments, default = provider name. */\n label?: string;\n windows: UsageWindow[];\n /** Statusline cache TTL in seconds, default 60. */\n ttlSeconds?: number;\n}\n\nexport interface WorkerProvider {\n enabled: boolean;\n /** Wire protocol the baseUrl speaks. */\n protocol: WorkerProtocol;\n /** Anthropic-compatible Messages API base. */\n baseUrl: string;\n /** 0600 file holding the auth token; null for local servers (\"local\"). */\n keyFile: string | null;\n /**\n * The auth token itself, inline in workers.yaml. Wins over keyFile when\n * both are set (`pai worker config check` notices this). Keeping the key\n * out of a separate file is the operator's explicit call — the file this\n * lives in must be 0600 (writeWorkersYamlText enforces that) and is never\n * committed. See resolveProviderKey for the one place this is read.\n */\n key?: string;\n /** Model id per capability; see MODEL_CAPABILITIES. Only default is required. */\n models: { default: string } & Partial<Record<ModelCapability, string>>;\n /**\n * Model id → tier alias, overriding the built-in mapping (models.fast →\n * haiku tier, models.default → sonnet tier). Lets a provider pin an extra\n * model id to a tier, e.g. a heavyweight default to the opus tier.\n */\n modelTiers?: Record<string, ModelTier>;\n /** Extra environment variables for runs through this provider (string values). */\n env: Record<string, string>;\n note?: string;\n /**\n * OpenAI Chat Completions base (e.g. \"https://api.openai.com/v1\"). Required\n * for protocol \"openai\"; read by the PAI proxy, never by the runner.\n */\n upstreamUrl?: string;\n /** Runner for this provider; \"codex\" goes through the Codex CLI. */\n engine?: WorkerEngine;\n /** Optional shell command printing 0–100 (percent of quota used). */\n quotaProbe?: string;\n /** Auto-routing skips the provider at or above this percentage. Default 95. */\n quotaSkipAt?: number;\n /**\n * Context window of the provider's model, for the context meter. No\n * default: the meter only shows a window the init event announced (or\n * this explicit value, for engines without one, e.g. codex).\n */\n contextWindow?: number;\n /** Cost tier 1 (cheapest) … 5 (most expensive); classes cap it via maxCostTier. */\n costTier?: number;\n /** Capability tags; classes filter auto-routing via requireTags. */\n tags?: ProviderTag[];\n /** Statusline plan-quota block; see ProviderUsage. Absent = \"usage n/a\". */\n usage?: ProviderUsage;\n /**\n * True only for the synthetic built-in \"anthropic\" provider (see\n * ANTHROPIC_NATIVE): plain Claude Code, on its own OAuth/Max-plan login —\n * no baseUrl, no key file, no config entry. Never set by parseProvider.\n */\n native?: boolean;\n}\n\n/**\n * Reserved provider name for plain Anthropic: Claude Code's own OAuth/Max-plan\n * login, no base URL override and no API key. It never needs (or accepts) a\n * `providers.anthropic` config entry — resolveTarget/mustExist synthesize it\n * on demand via `nativeAnthropicProvider()`.\n */\nexport const ANTHROPIC_NATIVE = \"anthropic\";\n\n/**\n * Models the built-in provider runs on. This is the only place a model id is\n * named for it: run.ts resolves the class capability against this table like\n * it does for any configured provider and passes `--model` explicitly.\n *\n * Workers exist to parallelise and to save cost, so they must never inherit\n * the orchestrator's model. A bare headless `claude` takes the interactive\n * session default, and one probe came up on the most expensive tier because\n * the chat session had been switched to it. Sonnet is the working tier;\n * haiku serves the fast classes (spotcheck, simple). These are the Claude\n * Code CLI's own tier aliases, not pinned ids: the CLI resolves each to its\n * newest model on every run, so the tier tracks a release without a code\n * change here (adoptInitModel in run.ts swaps the alias for the init event's\n * resolved id once the run announces one).\n */\nexport const NATIVE_ANTHROPIC_MODELS: WorkerProvider[\"models\"] = {\n default: \"sonnet\",\n fast: \"haiku\",\n};\n\n/**\n * The synthetic provider object for ANTHROPIC_NATIVE. `baseUrl` is empty and\n * there is no key file, so buildRunEnv strips every provider env override;\n * the models come from NATIVE_ANTHROPIC_MODELS so a run always names its\n * model on the command line.\n */\nexport function nativeAnthropicProvider(\n models: WorkerProvider[\"models\"] = NATIVE_ANTHROPIC_MODELS\n): WorkerProvider {\n return {\n enabled: true,\n protocol: \"anthropic\",\n baseUrl: \"\",\n keyFile: null,\n models: { ...models },\n env: {},\n native: true,\n };\n}\n\n/**\n * Provider by name, synthesizing ANTHROPIC_NATIVE when it is asked for. Reads\n * `config.nativeModels` (workers.yaml's documented `providers.anthropic.models`\n * override, if any) so a config that pins a different default/fast id for the\n * built-in provider is honored everywhere a provider is resolved.\n */\nexport function getProviderOrNative(\n config: { providers: Record<string, WorkerProvider>; nativeModels?: WorkerProvider[\"models\"] },\n name: string\n): WorkerProvider | undefined {\n if (name === ANTHROPIC_NATIVE) return nativeAnthropicProvider(config.nativeModels);\n return config.providers[name];\n}\n\nexport interface WorkersPaneConfig {\n enabled: boolean;\n /** Font size (points) of the follow-pane profile's font. */\n fontSize: number;\n /** Seconds a pane lingers after its worker goes quiet. */\n autoExitSecs: number;\n}\n\nexport interface WorkersRoutingConfig {\n /** Provider names in preference order; first usable one wins. */\n order: string[];\n /** Minutes a provider sits in cooldown after a quota/rate failure. */\n cooldownMinutes: number;\n /** Reroute a failed-before-first-tool run to the next provider. */\n retryOnQuota: boolean;\n}\n\n/** Sub-worker caps: how deep the worker tree may grow, how wide per parent. */\nexport interface WorkersTreeConfig {\n /** Maximum nesting depth of sub-workers (top-level = 0). */\n maxDepth: number;\n /** Maximum concurrently running children per parent. */\n maxChildren: number;\n}\n\n/** Cost/quality tier of a provider's model, 1 (cheapest) … 5 (most expensive). */\nexport type CostTier = 1 | 2 | 3 | 4 | 5;\n\n/** The tier aliases the CLI and daemon tables understand — class proxies, not\n * Anthropic model names: any provider's model maps onto one of these. */\nexport type ModelTier = \"haiku\" | \"sonnet\" | \"opus\";\n\nconst MODEL_TIERS: readonly ModelTier[] = [\"haiku\", \"sonnet\", \"opus\"];\n\nexport const DEFAULT_COST_TIER = 3;\n\n/** Tags a provider may carry; classes filter auto-routing on them. */\nexport const PROVIDER_TAGS = [\n \"code\",\n \"vision\",\n \"image-gen\",\n \"long-context\",\n \"fast\",\n \"reasoning\",\n] as const;\n\nexport type ProviderTag = (typeof PROVIDER_TAGS)[number];\n\n/** The standard task classes; `workers.classes` maps each to a target. */\nexport const WORKER_CLASSES = [\n \"draft\",\n \"plan\",\n \"implement\",\n \"review\",\n \"research\",\n \"spotcheck\",\n \"simple\",\n \"complex\",\n \"image\",\n] as const;\n\nexport type WorkerClassName = (typeof WORKER_CLASSES)[number];\n\n/**\n * The well-known model capabilities — documented, and what the starter\n * workers.yaml and `pai worker model`'s listing show by name. The set is\n * open, though: a provider's `models` block may carry any capability name\n * matching CAPABILITY_NAME_RE (see isModelCapability), e.g. \"vision\" or\n * \"longcontext\" for a provider that serves them. \"default\" is the required\n * catch-all; the others name what a model is *for* — \"fast\" the cheap tier\n * (spotcheck, haiku-tier spawns), \"image\" the image class/capability.\n * Everything resolves through resolveModelCapability, falling back to\n * default; cross-provider preference for a capability is `capabilities:` in\n * workers.yaml (see resolveCapability).\n */\nexport const MODEL_CAPABILITIES = [\"default\", \"fast\", \"image\"] as const;\n\nexport type ModelCapability = string;\n\nexport const CAPABILITY_NAME_RE = /^[a-z][a-z0-9-]*$/;\n\n/** Is this string a syntactically valid capability name (open set)? */\nexport function isModelCapability(v: string): v is ModelCapability {\n return CAPABILITY_NAME_RE.test(v);\n}\n\n/**\n * Which model capability a class runs on when its target names no alias: the\n * image class uses the image model, the cheap classes (spotcheck, simple) the\n * fast model, every other class the provider default. A provider without a\n * fast model falls back to its default (resolveModelCapability).\n */\nconst CLASS_MODEL_CAPABILITY: Record<WorkerClassName, ModelCapability> = {\n draft: \"default\",\n plan: \"default\",\n implement: \"default\",\n review: \"default\",\n research: \"default\",\n spotcheck: \"fast\",\n simple: \"fast\",\n complex: \"default\",\n image: \"image\",\n};\n\n/** Capability for a run's class (default when unset or not a standard class). */\nexport function classModelCapability(className?: string): ModelCapability {\n return (className && CLASS_MODEL_CAPABILITY[className as WorkerClassName]) || \"default\";\n}\n\n/**\n * A class target: \"<provider>\", \"<provider>/<modelAlias>\", or an object. The\n * object either pins a `provider` (plus optional `mcp` allowlist) or only\n * constrains auto-routing (`maxCostTier`, `requireTags`, per-class `order`).\n */\nexport type ClassTarget =\n | string\n | {\n provider?: string;\n mcp?: string[];\n /** Auto-routing may only use providers at or below this cost tier. */\n maxCostTier?: number;\n /** Auto-routing may only use providers carrying all these tags. */\n requireTags?: string[];\n /** Provider order for this class; defaults to routing.order. */\n order?: string[];\n };\n\n/**\n * State of the machine-wide Claude Code fallback (`pai worker fallback on`):\n * every new Claude Code process runs on `provider` until `fallback off`.\n * `saved` holds what ~/.claude/settings.json carried before the switch so\n * `off` can restore it exactly.\n */\nexport interface WorkersFallback {\n /** Provider every new Claude Code process is pointed at. */\n provider: string;\n /**\n * settings.json before the switch. `env` records the previous value of\n * every env key fallback touched (null = the key was absent), `model` the\n * previous top-level model pin (null = none), `envExisted` whether an env\n * block existed at all.\n */\n saved: {\n env: Record<string, string | null>;\n model: string | null;\n envExisted: boolean;\n };\n /** ISO stamp of the switch (shown by `fallback status`). */\n on: string;\n}\n\nexport interface WorkersConfig {\n enabled: boolean;\n /** Provider name, or \"auto\" for routing.order resolution. */\n active: string | null;\n providers: Record<string, WorkerProvider>;\n /** Class → target; see ClassTarget. (Reads the legacy `roles` key.) */\n classes: Record<string, ClassTarget>;\n /** MCP set name → server names; `--mcp <set>` and class `mcp` expand these. */\n mcpSets: Record<string, string[]>;\n /**\n * Capability → provider names in preference order (workers.yaml\n * `capabilities:`), e.g. `{ image: [\"pictures\", \"glm\"] }`. Cross-provider,\n * unlike a provider's own `models` table: this is what lets `--capability\n * image` find the one provider configured to serve it. See resolveCapability.\n */\n capabilities: Record<string, string[]>;\n pane: WorkersPaneConfig;\n logDir: string;\n routing: WorkersRoutingConfig;\n /** Sub-worker caps (workers.tree). */\n tree: WorkersTreeConfig;\n /**\n * Daemon cache-keepalive cadence in seconds: how often a trivial\n * single-turn heartbeat worker re-arms the provider prompt cache\n * (src/workers/keepalive.ts). 0 = off.\n */\n cacheKeepaliveSecs: number;\n /** Default time limit of every run in minutes (workers.defaultMaxMinutes); 0 = none. */\n defaultMaxMinutes: number;\n /** Runs strip browser tools unless --browser is passed (workers.noBrowserByDefault). */\n noBrowserByDefault: boolean;\n /**\n * Run native-Anthropic workers through `caveman claude` (workers.caveman).\n * Off pins them to api.anthropic.com even when the user settings route\n * every claude through a proxy; see claudeCommand in run-env.ts.\n */\n caveman: boolean;\n /** Machine-wide Claude Code fallback; null = off. */\n fallback: WorkersFallback | null;\n /**\n * Model ids for the built-in `anthropic` provider, when workers.yaml\n * documents an override under `providers.anthropic.models`. Defaults to\n * NATIVE_ANTHROPIC_MODELS.\n */\n nativeModels: WorkerProvider[\"models\"];\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_LOG_DIR = \"~/.claude/logs/workers\";\n\nexport const DEFAULT_PANE: WorkersPaneConfig = {\n enabled: true,\n fontSize: 13,\n autoExitSecs: 60,\n};\n\nexport const DEFAULT_ROUTING: WorkersRoutingConfig = {\n order: [],\n cooldownMinutes: 30,\n retryOnQuota: true,\n};\n\nexport const DEFAULT_TREE: WorkersTreeConfig = {\n maxDepth: 2,\n maxChildren: 4,\n};\n\n/**\n * Cache-keepalive cadence when the config names none. Measured 2026-09-18\n * (pair probes through the real worker spawn path, numbers in\n * docs/cache-keepalive.md): the implicit provider cache only serves a fresh\n * worker warm within roughly the first minute (positive back-to-back, gone\n * at 2 min), and it covers only ~2.6k of the ~18k-token prefix. Holding it\n * needs a beat every <2 min — a continuous bill for a small saving — so the\n * default is OFF and arming it is the operator's explicit call (set e.g.\n * \"cacheKeepaliveSecs\": 60).\n */\nexport const DEFAULT_CACHE_KEEPALIVE_SECS = 0;\nexport const DEFAULT_MAX_MINUTES = 60;\n\n/**\n * The default MCP sets every config starts from. `desktop` names the clickr\n * server so `--mcp desktop` hands a worker the machine controls (read-only\n * tools always; actuating ones after the operator hands the controls over,\n * see `pai worker controls`). A user's mcpSets section is merged over this,\n * so `desktop: []` removes the set deliberately.\n */\nexport const DEFAULT_MCP_SETS: Record<string, string[]> = {\n desktop: [\"clickr\"],\n};\n\nexport function defaultWorkersConfig(): WorkersConfig {\n return {\n enabled: false,\n active: null,\n providers: {},\n classes: {},\n mcpSets: { ...DEFAULT_MCP_SETS },\n capabilities: {},\n pane: { ...DEFAULT_PANE },\n logDir: DEFAULT_LOG_DIR,\n routing: { ...DEFAULT_ROUTING, order: [] },\n tree: { ...DEFAULT_TREE },\n cacheKeepaliveSecs: DEFAULT_CACHE_KEEPALIVE_SECS,\n defaultMaxMinutes: DEFAULT_MAX_MINUTES,\n noBrowserByDefault: false,\n caveman: false,\n fallback: null,\n nativeModels: { ...NATIVE_ANTHROPIC_MODELS },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nexport class WorkersConfigError extends Error {}\n\nfunction bad(path: string, why: string): never {\n throw new WorkersConfigError(`workers${path}: ${why}`);\n}\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v : \"\";\n}\n\n/**\n * Parse a `models` block (capability → model id, \"default\" required). Shared\n * by parseProvider and the YAML loader's builtin-anthropic override, which\n * has no other provider fields to validate.\n */\nexport function parseModelsBlock(pathPrefix: string, modelsRaw: unknown): WorkerProvider[\"models\"] {\n const raw = modelsRaw === undefined ? {} : modelsRaw;\n if (typeof raw !== \"object\" || raw === null) bad(pathPrefix, \"must be an object\");\n const m = raw as Record<string, unknown>;\n const defaultModel = str(m.default);\n if (!defaultModel) bad(`${pathPrefix}.default`, \"is required\");\n const models: WorkerProvider[\"models\"] = { default: defaultModel };\n for (const key of Object.keys(m)) {\n if (key === \"default\") continue;\n if (!isModelCapability(key)) {\n bad(\n `${pathPrefix}.${key}`,\n `\"${key}\" is not a valid capability name (must match ^[a-z][a-z0-9-]*$; well-known: ${MODEL_CAPABILITIES.join(\", \")}, but any name in that shape is accepted)`\n );\n }\n const id = str(m[key]);\n if (!id) bad(`${pathPrefix}.${key}`, \"must be a non-empty model id\");\n models[key] = id;\n }\n return models;\n}\n\n/** Parse and validate one provider entry (shared by JSON and YAML loading). */\nexport function parseProvider(name: string, raw: unknown): WorkerProvider {\n if (name === ANTHROPIC_NATIVE) {\n bad(\n `.providers.${name}`,\n `\"${ANTHROPIC_NATIVE}\" is reserved for plain Anthropic (Claude Code's own OAuth/Max-plan login) and cannot be configured here — remove this entry; use --provider ${ANTHROPIC_NATIVE} or \"pai worker providers use ${ANTHROPIC_NATIVE}\" instead`\n );\n }\n if (typeof raw !== \"object\" || raw === null) bad(`.providers.${name}`, \"must be an object\");\n const p = raw as Record<string, unknown>;\n\n const protocol = p.protocol === undefined ? \"anthropic\" : str(p.protocol);\n if (protocol !== \"anthropic\" && protocol !== \"openai\") {\n bad(`.providers.${name}.protocol`, `\"${str(p.protocol)}\" is neither \"anthropic\" nor \"openai\"`);\n }\n const engine = p.engine === undefined ? \"claude\" : str(p.engine);\n if (engine !== \"claude\" && engine !== \"codex\" && engine !== \"image\") {\n bad(`.providers.${name}.engine`, `\"${str(p.engine)}\" is none of \"claude\", \"codex\", \"image\"`);\n }\n\n const baseUrl = str(p.baseUrl);\n // openai providers reach the model through the PAI proxy; their baseUrl is\n // the proxy URL, filled in by the runner — only anthropic needs one here.\n if (!baseUrl && protocol !== \"openai\") bad(`.providers.${name}.baseUrl`, \"is required\");\n\n const keyFile =\n p.keyFile === undefined || p.keyFile === null || str(p.keyFile) === \"\"\n ? null\n : str(p.keyFile);\n const key = str(p.key) || undefined;\n\n const models = parseModelsBlock(`.providers.${name}.models`, p.models);\n\n let modelTiers: Record<string, ModelTier> | undefined;\n if (p.modelTiers !== undefined) {\n if (typeof p.modelTiers !== \"object\" || p.modelTiers === null || Array.isArray(p.modelTiers)) {\n bad(`.providers.${name}.modelTiers`, \"must be an object of model id → haiku | sonnet | opus\");\n }\n modelTiers = {};\n for (const [id, tier] of Object.entries(p.modelTiers as Record<string, unknown>)) {\n if (typeof tier !== \"string\" || !MODEL_TIERS.includes(tier as ModelTier)) {\n bad(\n `.providers.${name}.modelTiers.${id}`,\n `must be \"haiku\", \"sonnet\" or \"opus\" (got ${JSON.stringify(tier)})`\n );\n }\n modelTiers[id] = tier as ModelTier;\n }\n }\n\n const env: Record<string, string> = {};\n if (p.env !== undefined) {\n if (typeof p.env !== \"object\" || p.env === null || Array.isArray(p.env)) {\n bad(`.providers.${name}.env`, \"must be an object of string values\");\n }\n for (const [k, v] of Object.entries(p.env as Record<string, unknown>)) {\n if (typeof v !== \"string\") bad(`.providers.${name}.env.${k}`, \"must be a string\");\n env[k] = v;\n }\n }\n\n const quotaSkipAt = p.quotaSkipAt === undefined ? undefined : p.quotaSkipAt;\n if (quotaSkipAt !== undefined) {\n if (typeof quotaSkipAt !== \"number\" || quotaSkipAt < 0 || quotaSkipAt > 100) {\n bad(`.providers.${name}.quotaSkipAt`, \"must be a number between 0 and 100\");\n }\n }\n\n const contextWindow = p.contextWindow === undefined ? undefined : p.contextWindow;\n if (contextWindow !== undefined) {\n if (typeof contextWindow !== \"number\" || contextWindow <= 0) {\n bad(`.providers.${name}.contextWindow`, \"must be a positive number of tokens\");\n }\n }\n\n const upstreamUrl = str(p.upstreamUrl);\n if (protocol === \"openai\" && !upstreamUrl) {\n bad(`.providers.${name}.upstreamUrl`, `is required for protocol \"openai\" (the Chat Completions base, e.g. \"https://api.openai.com/v1\")`);\n }\n\n const costTier = p.costTier === undefined ? undefined : p.costTier;\n if (costTier !== undefined) {\n if (typeof costTier !== \"number\" || !Number.isInteger(costTier) || costTier < 1 || costTier > 5) {\n bad(`.providers.${name}.costTier`, \"must be an integer 1 (cheapest) … 5 (most expensive)\");\n }\n }\n\n let tags: ProviderTag[] | undefined;\n if (p.tags !== undefined) {\n if (!Array.isArray(p.tags) || p.tags.some((x) => typeof x !== \"string\")) {\n bad(`.providers.${name}.tags`, `must be an array of tags from: ${PROVIDER_TAGS.join(\", \")}`);\n }\n for (const t of p.tags as string[]) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n bad(`.providers.${name}.tags`, `\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n }\n tags = p.tags as ProviderTag[];\n }\n\n let usage: ProviderUsage | undefined;\n if (p.usage !== undefined) {\n if (typeof p.usage !== \"object\" || p.usage === null || Array.isArray(p.usage)) {\n bad(`.providers.${name}.usage`, \"must be an object\");\n }\n const u = p.usage as Record<string, unknown>;\n const usageUrl = str(u.url);\n if (!usageUrl) bad(`.providers.${name}.usage.url`, \"is required\");\n if (!Array.isArray(u.windows) || u.windows.length === 0) {\n bad(`.providers.${name}.usage.windows`, \"must be a non-empty array\");\n }\n const windows: UsageWindow[] = [];\n for (const [i, wRaw] of (u.windows as unknown[]).entries()) {\n const wpath = `.providers.${name}.usage.windows[${i}]`;\n if (typeof wRaw !== \"object\" || wRaw === null || Array.isArray(wRaw)) {\n bad(wpath, \"must be an object\");\n }\n const w = wRaw as Record<string, unknown>;\n const wname = str(w.name);\n if (!wname) bad(`${wpath}.name`, \"is required\");\n const percent = str(w.percent);\n if (!percent) bad(`${wpath}.percent`, \"is required (a jq expression yielding 0–100)\");\n const resetUnit = w.resetUnit === undefined ? undefined : str(w.resetUnit);\n if (resetUnit !== undefined && resetUnit !== \"ms\" && resetUnit !== \"s\" && resetUnit !== \"iso\") {\n bad(`${wpath}.resetUnit`, `must be \"ms\", \"s\" or \"iso\"`);\n }\n windows.push({\n name: wname,\n percent,\n ...(str(w.resetAt) ? { resetAt: str(w.resetAt) } : {}),\n ...(resetUnit ? { resetUnit } : {}),\n });\n }\n const ttlSeconds = u.ttlSeconds === undefined ? undefined : u.ttlSeconds;\n if (ttlSeconds !== undefined && (typeof ttlSeconds !== \"number\" || ttlSeconds <= 0)) {\n bad(`.providers.${name}.usage.ttlSeconds`, \"must be a positive number of seconds\");\n }\n usage = {\n url: usageUrl,\n ...(str(u.authHeader) ? { authHeader: str(u.authHeader) } : {}),\n ...(str(u.label) ? { label: str(u.label) } : {}),\n windows,\n ...(ttlSeconds !== undefined ? { ttlSeconds } : {}),\n };\n }\n\n return {\n enabled: p.enabled === undefined ? true : p.enabled === true,\n protocol,\n baseUrl,\n keyFile,\n ...(key ? { key } : {}),\n models,\n ...(modelTiers ? { modelTiers } : {}),\n env,\n ...(str(p.note) ? { note: str(p.note) } : {}),\n ...(upstreamUrl ? { upstreamUrl } : {}),\n ...(engine !== \"claude\" ? { engine } : {}),\n ...(str(p.quotaProbe) ? { quotaProbe: str(p.quotaProbe) } : {}),\n ...(quotaSkipAt !== undefined ? { quotaSkipAt } : {}),\n ...(contextWindow !== undefined ? { contextWindow } : {}),\n ...(costTier !== undefined ? { costTier } : {}),\n ...(tags ? { tags } : {}),\n ...(usage ? { usage } : {}),\n };\n}\n\n/**\n * Parse a `classes` (or legacy `roles`) value shared by JSON and YAML\n * loading. `badPathPrefix` names the field in error messages, e.g. \".classes\".\n */\nexport function parseClassesValue(\n classesRaw: unknown,\n badPathPrefix: string\n): Record<string, ClassTarget> {\n const classes: Record<string, ClassTarget> = {};\n if (classesRaw === undefined) return classes;\n if (typeof classesRaw !== \"object\" || classesRaw === null || Array.isArray(classesRaw)) {\n bad(badPathPrefix, \"must be an object of class → provider[/alias] or {provider, mcp, maxCostTier, requireTags, order}\");\n }\n for (const [cls, target] of Object.entries(classesRaw as Record<string, unknown>)) {\n if (typeof target === \"object\" && target !== null && !Array.isArray(target)) {\n const o = target as Record<string, unknown>;\n const provider = str(o.provider);\n if (o.provider !== undefined && (!provider || provider.includes(\" \"))) {\n bad(`${badPathPrefix}.${cls}.provider`, `invalid provider \"${provider}\"`);\n }\n let mcp: string[] | undefined;\n if (o.mcp !== undefined) {\n if (!Array.isArray(o.mcp) || o.mcp.some((x) => typeof x !== \"string\")) {\n bad(`${badPathPrefix}.${cls}.mcp`, \"must be an array of MCP server or set names\");\n }\n mcp = o.mcp as string[];\n }\n let maxCostTier: number | undefined;\n if (o.maxCostTier !== undefined) {\n if (\n typeof o.maxCostTier !== \"number\" ||\n !Number.isInteger(o.maxCostTier) ||\n o.maxCostTier < 1 ||\n o.maxCostTier > 5\n ) {\n bad(`${badPathPrefix}.${cls}.maxCostTier`, \"must be an integer 1 … 5\");\n }\n maxCostTier = o.maxCostTier;\n }\n let requireTags: string[] | undefined;\n if (o.requireTags !== undefined) {\n if (!Array.isArray(o.requireTags) || o.requireTags.some((x) => typeof x !== \"string\")) {\n bad(`${badPathPrefix}.${cls}.requireTags`, `must be an array of tags from: ${PROVIDER_TAGS.join(\", \")}`);\n }\n for (const t of o.requireTags as string[]) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n bad(`${badPathPrefix}.${cls}.requireTags`, `\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n }\n requireTags = o.requireTags as string[];\n }\n let order: string[] | undefined;\n if (o.order !== undefined) {\n if (!Array.isArray(o.order) || o.order.some((x) => typeof x !== \"string\")) {\n bad(`${badPathPrefix}.${cls}.order`, \"must be an array of provider names\");\n }\n order = o.order as string[];\n }\n const obj: ClassTarget = {\n ...(provider ? { provider } : {}),\n ...(mcp ? { mcp } : {}),\n ...(maxCostTier !== undefined ? { maxCostTier } : {}),\n ...(requireTags ? { requireTags } : {}),\n ...(order ? { order } : {}),\n };\n classes[cls] = Object.keys(obj).length ? obj : {};\n } else {\n const t = str(target);\n if (!t || t.includes(\" \")) bad(`${badPathPrefix}.${cls}`, `invalid target \"${t}\"`);\n classes[cls] = t;\n }\n }\n return classes;\n}\n\n/**\n * Parse an `mcpSets` (or `mcp_sets`) value shared by JSON and YAML loading.\n * Always merged over DEFAULT_MCP_SETS, so an omitted `desktop` key keeps the\n * built-in clickr set (see DEFAULT_MCP_SETS).\n */\nexport function parseMcpSetsValue(raw: unknown, badPathPrefix: string): Record<string, string[]> {\n const mcpSets: Record<string, string[]> = { ...DEFAULT_MCP_SETS };\n if (raw === undefined) return mcpSets;\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n bad(badPathPrefix, \"must be an object of set name → [server names]\");\n }\n for (const [setName, servers] of Object.entries(raw as Record<string, unknown>)) {\n if (!Array.isArray(servers) || servers.some((x) => typeof x !== \"string\")) {\n bad(`${badPathPrefix}.${setName}`, \"must be an array of MCP server names\");\n }\n mcpSets[setName] = servers as string[];\n }\n return mcpSets;\n}\n\n/**\n * Parse a `capabilities` value shared by JSON and YAML loading: capability\n * name (open set, see isModelCapability) → provider names in preference\n * order. Provider names are not cross-checked here — a name that stops\n * existing is simply skipped by resolveCapability at resolve time, the same\n * way routing.order tolerates a removed provider.\n */\nexport function parseCapabilitiesValue(raw: unknown, badPathPrefix: string): Record<string, string[]> {\n const capabilities: Record<string, string[]> = {};\n if (raw === undefined) return capabilities;\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n bad(badPathPrefix, \"must be an object of capability name → [provider names]\");\n }\n for (const [name, order] of Object.entries(raw as Record<string, unknown>)) {\n if (!isModelCapability(name)) {\n bad(`${badPathPrefix}.${name}`, `\"${name}\" is not a valid capability name (must match ^[a-z][a-z0-9-]*$)`);\n }\n if (!Array.isArray(order) || order.length === 0 || order.some((x) => typeof x !== \"string\" || !x)) {\n bad(`${badPathPrefix}.${name}`, \"must be a non-empty array of provider names\");\n }\n capabilities[name] = order as string[];\n }\n return capabilities;\n}\n\n/**\n * Parse and validate a raw `workers` value. Missing section → defaults.\n * Unknown-but-typed garbage → WorkersConfigError naming the offending field.\n */\nexport function parseWorkersConfig(raw: unknown): WorkersConfig {\n const d = defaultWorkersConfig();\n if (raw === undefined || raw === null) return d;\n if (typeof raw !== \"object\" || Array.isArray(raw)) {\n bad(\"\", \"section must be an object\");\n }\n const w = raw as Record<string, unknown>;\n\n const providers: Record<string, WorkerProvider> = {};\n if (w.providers !== undefined) {\n if (typeof w.providers !== \"object\" || w.providers === null || Array.isArray(w.providers)) {\n bad(\".providers\", \"must be an object keyed by provider name\");\n }\n for (const [name, p] of Object.entries(w.providers)) {\n providers[name] = parseProvider(name, p);\n }\n }\n\n // classes is canonical; a config that still carries the pre-classes `roles`\n // key is migrated by reading it here — the next write stores only `classes`.\n const classesRaw = w.classes !== undefined ? w.classes : w.roles;\n const classes = parseClassesValue(classesRaw, w.classes !== undefined ? \".classes\" : \".roles\");\n\n const mcpSets = parseMcpSetsValue(w.mcpSets, \".mcpSets\");\n const capabilities = parseCapabilitiesValue(w.capabilities, \".capabilities\");\n\n let pane = { ...DEFAULT_PANE };\n if (w.pane !== undefined) {\n if (typeof w.pane !== \"object\" || w.pane === null) bad(\".pane\", \"must be an object\");\n const pc = w.pane as Record<string, unknown>;\n if (pc.enabled !== undefined && typeof pc.enabled !== \"boolean\") bad(\".pane.enabled\", \"must be boolean\");\n if (pc.fontSize !== undefined && (typeof pc.fontSize !== \"number\" || pc.fontSize <= 0)) {\n bad(\".pane.fontSize\", \"must be a positive number of points\");\n }\n if (pc.autoExitSecs !== undefined && typeof pc.autoExitSecs !== \"number\") {\n bad(\".pane.autoExitSecs\", \"must be a number\");\n }\n // legacy fontScale (a relative scale, superseded by fontSize) is tolerated and ignored\n pane = {\n enabled: pc.enabled === undefined ? DEFAULT_PANE.enabled : pc.enabled === true,\n fontSize: pc.fontSize === undefined ? DEFAULT_PANE.fontSize : pc.fontSize,\n autoExitSecs: pc.autoExitSecs === undefined ? DEFAULT_PANE.autoExitSecs : pc.autoExitSecs,\n };\n }\n\n let routing = { ...DEFAULT_ROUTING, order: [] as string[] };\n if (w.routing !== undefined) {\n if (typeof w.routing !== \"object\" || w.routing === null) bad(\".routing\", \"must be an object\");\n const r = w.routing as Record<string, unknown>;\n if (r.order !== undefined) {\n if (!Array.isArray(r.order) || r.order.some((x) => typeof x !== \"string\")) {\n bad(\".routing.order\", \"must be an array of provider names\");\n }\n routing.order = r.order as string[];\n }\n if (r.cooldownMinutes !== undefined && typeof r.cooldownMinutes !== \"number\") {\n bad(\".routing.cooldownMinutes\", \"must be a number\");\n }\n if (r.retryOnQuota !== undefined && typeof r.retryOnQuota !== \"boolean\") {\n bad(\".routing.retryOnQuota\", \"must be boolean\");\n }\n routing = {\n order: routing.order,\n cooldownMinutes: r.cooldownMinutes === undefined ? DEFAULT_ROUTING.cooldownMinutes : r.cooldownMinutes,\n retryOnQuota: r.retryOnQuota === undefined ? DEFAULT_ROUTING.retryOnQuota : r.retryOnQuota === true,\n };\n }\n\n let tree = { ...DEFAULT_TREE };\n if (w.tree !== undefined) {\n if (typeof w.tree !== \"object\" || w.tree === null || Array.isArray(w.tree)) {\n bad(\".tree\", \"must be an object\");\n }\n const t = w.tree as Record<string, unknown>;\n if (t.maxDepth !== undefined) {\n if (typeof t.maxDepth !== \"number\" || !Number.isInteger(t.maxDepth) || t.maxDepth < 0) {\n bad(\".tree.maxDepth\", \"must be a non-negative integer\");\n }\n }\n if (t.maxChildren !== undefined) {\n if (typeof t.maxChildren !== \"number\" || !Number.isInteger(t.maxChildren) || t.maxChildren < 1) {\n bad(\".tree.maxChildren\", \"must be a positive integer\");\n }\n }\n tree = {\n maxDepth: t.maxDepth === undefined ? DEFAULT_TREE.maxDepth : t.maxDepth,\n maxChildren: t.maxChildren === undefined ? DEFAULT_TREE.maxChildren : t.maxChildren,\n };\n }\n\n let cacheKeepaliveSecs = DEFAULT_CACHE_KEEPALIVE_SECS;\n if (w.cacheKeepaliveSecs !== undefined) {\n if (\n typeof w.cacheKeepaliveSecs !== \"number\" ||\n !Number.isInteger(w.cacheKeepaliveSecs) ||\n w.cacheKeepaliveSecs < 0\n ) {\n bad(\".cacheKeepaliveSecs\", \"must be a non-negative integer (seconds, 0 = off)\");\n }\n cacheKeepaliveSecs = w.cacheKeepaliveSecs;\n }\n\n let defaultMaxMinutes = DEFAULT_MAX_MINUTES;\n if (w.defaultMaxMinutes !== undefined) {\n if (typeof w.defaultMaxMinutes !== \"number\" || !Number.isInteger(w.defaultMaxMinutes) || w.defaultMaxMinutes < 0) {\n bad(\".defaultMaxMinutes\", \"must be a non-negative integer (minutes, 0 = no limit)\");\n }\n defaultMaxMinutes = w.defaultMaxMinutes;\n }\n if (w.noBrowserByDefault !== undefined && typeof w.noBrowserByDefault !== \"boolean\") {\n bad(\".noBrowserByDefault\", \"must be true or false\");\n }\n const noBrowserByDefault = w.noBrowserByDefault === true;\n\n if (w.caveman !== undefined && typeof w.caveman !== \"boolean\") {\n bad(\".caveman\", \"must be true or false\");\n }\n const caveman = w.caveman === true;\n\n const active = w.active === undefined || w.active === null ? null : str(w.active);\n if (active !== null && active !== \"auto\" && !(active in providers)) {\n // Tolerated at parse time (a provider may have been removed while active\n // still names it) but every consumer resolves it to a clear error.\n }\n\n let fallback: WorkersFallback | null = null;\n if (w.fallback !== undefined && w.fallback !== null) {\n if (typeof w.fallback !== \"object\" || Array.isArray(w.fallback)) {\n bad(\".fallback\", \"must be an object (written by `pai worker fallback on`)\");\n }\n const f = w.fallback as Record<string, unknown>;\n const provider = str(f.provider);\n if (!provider) bad(\".fallback.provider\", \"is required\");\n const savedRaw = f.saved;\n if (typeof savedRaw !== \"object\" || savedRaw === null || Array.isArray(savedRaw)) {\n bad(\".fallback.saved\", \"must be an object\");\n }\n const s = savedRaw as Record<string, unknown>;\n if (typeof s.env !== \"object\" || s.env === null || Array.isArray(s.env)) {\n bad(\".fallback.saved.env\", \"must be an object of env key → previous value or null\");\n }\n const env: Record<string, string | null> = {};\n for (const [k, v] of Object.entries(s.env as Record<string, unknown>)) {\n env[k] = v === null || typeof v === \"string\" ? (v as string | null) : null;\n }\n if (s.model !== null && s.model !== undefined && typeof s.model !== \"string\") {\n bad(\".fallback.saved.model\", \"must be a string or null\");\n }\n fallback = {\n provider,\n saved: {\n env,\n model: typeof s.model === \"string\" ? s.model : null,\n envExisted: s.envExisted === true,\n },\n on: str(f.on) || new Date(0).toISOString(),\n };\n }\n\n return {\n enabled: w.enabled === undefined ? d.enabled : w.enabled === true,\n active,\n providers,\n classes,\n mcpSets,\n capabilities,\n pane,\n logDir: str(w.logDir) || d.logDir,\n routing,\n tree,\n cacheKeepaliveSecs,\n defaultMaxMinutes,\n noBrowserByDefault,\n caveman,\n fallback,\n nativeModels: { ...NATIVE_ANTHROPIC_MODELS },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Read / write\n// ---------------------------------------------------------------------------\n\nfunction workersYamlDataOf(workers: WorkersConfig): WorkersYamlData {\n return {\n active: workers.active,\n providers: workers.providers,\n classes: workers.classes,\n mcpSets: workers.mcpSets,\n capabilities: workers.capabilities,\n nativeModels: workers.nativeModels,\n };\n}\n\n/**\n * Read the whole config file and return (raw, workers) — the raw record so\n * callers can rewrite it preserving every other section, the parsed+validated\n * workers section. Unreadable config throws, missing is fine. `path`\n * overrides the config location (tests, CLAUDE_SETTINGS_PATH-style dry runs);\n * default CONFIG_FILE (see paiConfigFilePath). Reads via readMainConfigRaw,\n * so a config.yaml next to `path` is preferred over JSON, same as every\n * other main-config reader.\n *\n * Providers, classes, mcpSets and active load from workers.yaml (next to\n * `path`) when it exists; otherwise they fall back to the JSON `workers`\n * section, and then to built-in defaults — nothing breaks before migration\n * (`pai worker config migrate`). Everything else (pane, routing, tree,\n * cacheKeepaliveSecs, fallback, enabled, logDir) always comes from the main\n * config (JSON or YAML).\n */\nexport function readWorkersSection(path: string = CONFIG_FILE): {\n raw: Record<string, unknown>;\n workers: WorkersConfig;\n} {\n const raw = readMainConfigRaw(path);\n const workers = parseWorkersConfig(raw.workers);\n const yaml = readWorkersYaml(workersYamlPath());\n if (yaml) {\n workers.active = yaml.data.active;\n workers.providers = yaml.data.providers;\n workers.classes = yaml.data.classes;\n workers.mcpSets = yaml.data.mcpSets;\n workers.capabilities = yaml.data.capabilities;\n workers.nativeModels = yaml.data.nativeModels;\n }\n return { raw, workers };\n}\n\n/**\n * Write the workers section back, atomically. When workers.yaml exists, its\n * providers/classes/mcpSets/active are synced there (comment-preserving,\n * only the entries that changed are touched) and stripped from the JSON\n * `workers` section; everything else still writes to JSON as before.\n */\nexport function writeWorkersSection(\n raw: Record<string, unknown>,\n workers: WorkersConfig,\n path: string = CONFIG_FILE\n): void {\n const yamlPath = workersYamlPath();\n const usingYaml = existsSync(yamlPath);\n if (usingYaml) {\n writeWorkersYaml(yamlPath, workersYamlDataOf(workers));\n }\n const jsonWorkers = usingYaml\n ? {\n enabled: workers.enabled,\n pane: workers.pane,\n logDir: workers.logDir,\n routing: workers.routing,\n tree: workers.tree,\n cacheKeepaliveSecs: workers.cacheKeepaliveSecs,\n defaultMaxMinutes: workers.defaultMaxMinutes,\n noBrowserByDefault: workers.noBrowserByDefault,\n caveman: workers.caveman,\n ...(workers.fallback ? { fallback: workers.fallback } : {}),\n }\n : workers.fallback\n ? workers\n : { ...workers, fallback: undefined };\n raw.workers = jsonWorkers;\n writeMainConfigRaw(raw, path);\n}\n\n/** Expand a leading ~ (config values are written with `~` to stay portable). */\nexport function expandHome(p: string): string {\n if (p === \"~\" || p.startsWith(\"~/\")) return join(homedir(), p.slice(1));\n return p;\n}\n\n// ---------------------------------------------------------------------------\n// Runnability checks (proxy / codex providers included)\n// ---------------------------------------------------------------------------\n\nexport function assertProviderRunnable(name: string, p: WorkerProvider): void {\n if (p.protocol === \"openai\" && !p.upstreamUrl) {\n throw new WorkersConfigError(\n `provider \"${name}\" uses protocol \"openai\" but has no upstreamUrl — ` +\n `set its Chat Completions base (e.g. \"https://api.openai.com/v1\") ` +\n `so the PAI proxy knows where to translate to.`\n );\n }\n}\n\n/** Where a provider's key file lives, or null. Shared by run + test + add. */\nexport function providerKeyPath(p: WorkerProvider): string | null {\n return p.keyFile ? expandHome(p.keyFile) : null;\n}\n\n/**\n * The provider's auth token: inline `key` wins when set, else `key_file`\n * read and trimmed, else null (native/local providers use the \"local\"\n * placeholder). The one place a provider's key value is resolved — every\n * spawn path (buildRunEnv, the codex runner, the openai proxy, the\n * machine-wide fallback) reads through this instead of its own\n * readFileSync, so inline keys work everywhere a key file already did.\n */\nexport function resolveProviderKey(p: WorkerProvider): string | null {\n if (p.key) return p.key;\n const keyPath = providerKeyPath(p);\n if (!keyPath) return null;\n let content: string;\n try {\n content = readFileSync(keyPath, \"utf8\");\n } catch {\n throw new WorkersConfigError(`key file not readable: ${keyPath}`);\n }\n const token = content.trim();\n if (!token) throw new WorkersConfigError(`key file is empty: ${keyPath}`);\n return token;\n}\n\n/** `key: ****` + last 4 chars — never the full value. Used everywhere a\n * provider's key would otherwise be echoed (`providers`, `config check`). */\nexport function maskKey(key: string): string {\n return `****${key.slice(-4)}`;\n}\n\nexport { DEFAULT_CONTEXT_WINDOW } from \"../utils/model-window.js\";\n\n/** Cost tier of a provider for class filtering (unset = 3, the middle). */\nexport function providerCostTier(p: WorkerProvider): number {\n return p.costTier ?? DEFAULT_COST_TIER;\n}\n\n/**\n * The model id for a capability: the provider's preference for it, else its\n * default model — the one resolution rule every consumer (image class, fast\n * tier spawns, env pins) goes through.\n */\nexport function resolveModelCapability(p: WorkerProvider, capability: ModelCapability): string {\n return p.models[capability] ?? p.models.default;\n}\n\n/** Runner behind a provider, defaulting to the Claude Code harness. */\nfunction providerEngine(p: WorkerProvider): WorkerEngine {\n return p.engine ?? \"claude\";\n}\n\n/** Does this provider name a model for the capability (\"default\" always counts)? */\nfunction providerDeclaresCapability(p: WorkerProvider, capability: string): boolean {\n return capability === \"default\" || p.models[capability] !== undefined;\n}\n\n/** A provider usable for this capability: declares it, enabled, and passes assertProviderRunnable. */\nfunction providerUsableForCapability(p: WorkerProvider | undefined, capability: string): p is WorkerProvider {\n if (!p || !p.enabled || !providerDeclaresCapability(p, capability)) return false;\n try {\n assertProviderRunnable(\"\", p);\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface ResolvedCapability {\n /** Provider name the capability resolved to (\"anthropic\" for the built-in). */\n provider: string;\n model: string;\n engine: WorkerEngine;\n /**\n * True when nothing declared this capability anywhere and the result is\n * just the active provider's default model — a caller should treat this as\n * \"no real image/vision/… provider is configured\", not a genuine match.\n */\n fellBack: boolean;\n}\n\n/**\n * Cross-provider capability resolution (`workers.yaml`'s `capabilities:`\n * map): which provider+model serves a named capability (e.g. \"image\"),\n * independent of any one provider's own `models` table.\n *\n * Resolution order:\n * 1. `capabilities.<name>` (explicit preference list) — first provider\n * that declares the capability, is enabled, and passes\n * assertProviderRunnable.\n * 2. No preference list: the active provider, if it declares the\n * capability.\n * 3. Still nothing: any enabled provider that declares the capability\n * (stable order — provider names sorted, \"anthropic\" included).\n * 4. Nothing anywhere: the active provider's default model, `fellBack: true`.\n */\nexport function resolveCapability(\n workers: Pick<WorkersConfig, \"providers\" | \"active\" | \"capabilities\" | \"nativeModels\">,\n capability: string\n): ResolvedCapability {\n const order = workers.capabilities[capability];\n if (order?.length) {\n for (const name of order) {\n const p = getProviderOrNative(workers, name);\n if (providerUsableForCapability(p, capability)) {\n return { provider: name, model: resolveModelCapability(p, capability), engine: providerEngine(p), fellBack: false };\n }\n }\n throw new WorkersConfigError(\n `no configured provider for capability \"${capability}\" is usable right now ` +\n `(checked, in order: ${order.join(\", \")}) — set one with: pai worker capability ${capability} <provider>`\n );\n }\n\n const activeName = workers.active && workers.active !== \"auto\" ? workers.active : null;\n if (activeName) {\n const p = getProviderOrNative(workers, activeName);\n if (providerUsableForCapability(p, capability)) {\n return { provider: activeName, model: resolveModelCapability(p, capability), engine: providerEngine(p), fellBack: false };\n }\n }\n\n const candidates: Record<string, WorkerProvider> = {\n [ANTHROPIC_NATIVE]: nativeAnthropicProvider(workers.nativeModels),\n ...workers.providers,\n };\n for (const name of Object.keys(candidates).sort()) {\n const p = candidates[name];\n if (providerUsableForCapability(p, capability)) {\n return { provider: name, model: resolveModelCapability(p, capability), engine: providerEngine(p), fellBack: false };\n }\n }\n\n const fallbackName = activeName ?? ANTHROPIC_NATIVE;\n const fallback = getProviderOrNative(workers, fallbackName) ?? nativeAnthropicProvider(workers.nativeModels);\n return {\n provider: fallbackName,\n model: fallback.models.default,\n engine: providerEngine(fallback),\n fellBack: true,\n };\n}\n\n/**\n * Context window used by the meter when the run reports none: an explicit\n * `contextWindow` first, then whatever the default model's id declares\n * (\"[1m]\" → 1,000,000), then the last-resort default.\n */\nexport function providerContextWindow(p: WorkerProvider): number {\n return (\n p.contextWindow ??\n contextWindowFromModelId(p.models.default) ??\n DEFAULT_CONTEXT_WINDOW\n );\n}\n\n/** Directory under which inline keys (MCP `key` field) are stored, 0600. */\nexport function keysDir(): string {\n return paiHomePath(\"keys\");\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 { 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 */\nconst HEADLESS_STRIP_VARS = [\"ENABLE_TOOL_SEARCH\"] 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): 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 }\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 } 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 } 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): 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 return [\"claude\", \"--settings\", JSON.stringify({ env: { ANTHROPIC_BASE_URL: route || ANTHROPIC_FIRST_PARTY_URL } })];\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,5 +1,5 @@
1
1
  import { a as paiHomePath, o as resolvePaiFile } from "./pai-home-Cm9rcJgX.mjs";
2
- import { D as resolveCapability, g as classModelCapability, m as WorkersConfigError, o as DEFAULT_LOG_DIR, p as WORKER_CLASSES, r as ANTHROPIC_NATIVE, v as expandHome, w as providerCostTier, y as getProviderOrNative } from "./run-env-BHWnXqle.mjs";
2
+ import { O as resolveCapability, T as providerCostTier, _ as classModelCapability, b as getProviderOrNative, h as WorkersConfigError, m as WORKER_CLASSES, o as DEFAULT_LOG_DIR, r as ANTHROPIC_NATIVE, y as expandHome } from "./run-env-DRcp7A8K.mjs";
3
3
  import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -400,4 +400,4 @@ function stopProxy(logDir) {
400
400
 
401
401
  //#endregion
402
402
  export { statusPath as _, clearCooldown as a, probeQuota as c, resolveTarget as d, setCooldown as f, panesDir as g, ledgerPath as h, capabilityForRun as i, quotaSkipThreshold as l, eventsPath as m, ensureProxyRunning as n, isQuotaFailure as o, ensureNoMcpConfig as p, stopProxy as r, nextAutoProvider as s, DEFAULT_PROXY_PORT as t, resolveCapabilityRun as u, workersLogDir as v };
403
- //# sourceMappingURL=server-DlL1QI2k.mjs.map
403
+ //# sourceMappingURL=server-DgmAHyFK.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-DlL1QI2k.mjs","names":[],"sources":["../src/workers/paths.ts","../src/workers/routing.ts","../src/workers/proxy/server.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 * routing.ts — provider selection: flag > class > active (possibly \"auto\").\n *\n * Auto-routing walks `workers.routing.order` — or the class's own `order` —\n * and takes the first provider that is enabled, out of cooldown, (when it\n * defines a quotaProbe) under its quotaSkipAt threshold, and — when the class\n * constrains it — within `maxCostTier` and carrying all `requireTags`. A run\n * that dies of a quota/rate error puts its provider in cooldown for\n * cooldownMinutes; when that happens before the first tool call and\n * retryOnQuota is set, the runner restarts the same task on the next provider\n * (ledger: WORKER-REROUTE).\n *\n * An explicit --provider or a class mapping that pins a provider always\n * bypasses all of this.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport {\n type WorkerEngine,\n type WorkerProvider,\n type WorkersConfig,\n type ProviderTag,\n WORKER_CLASSES,\n ANTHROPIC_NATIVE,\n WorkersConfigError,\n classModelCapability,\n providerCostTier,\n getProviderOrNative,\n resolveCapability,\n} from \"./config.js\";\nimport { routingStatePath } from \"./paths.js\";\n\nconst QUOTA_SKIP_DEFAULT = 95;\nconst PROBE_TIMEOUT_MS = 10_000;\n\nexport interface RoutingState {\n /** provider name → ISO timestamp when its cooldown ends */\n cooldowns: Record<string, string>;\n}\n\nexport function readRoutingState(logDir: string): RoutingState {\n const path = routingStatePath(logDir);\n if (!existsSync(path)) return { cooldowns: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as RoutingState;\n return { cooldowns: parsed.cooldowns ?? {} };\n } catch {\n return { cooldowns: {} };\n }\n}\n\nexport function writeRoutingState(logDir: string, state: RoutingState): void {\n const path = routingStatePath(logDir);\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(state, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nexport function cooldownRemaining(\n state: RoutingState,\n provider: string,\n now: Date = new Date()\n): number {\n const end = state.cooldowns[provider];\n if (!end) return 0;\n const ms = Date.parse(end) - now.getTime();\n return ms > 0 ? ms : 0;\n}\n\nexport function setCooldown(\n logDir: string,\n provider: string,\n minutes: number,\n now: Date = new Date()\n): void {\n const state = readRoutingState(logDir);\n state.cooldowns[provider] = new Date(now.getTime() + minutes * 60_000).toISOString();\n writeRoutingState(logDir, state);\n}\n\nexport function clearCooldown(logDir: string, provider: string): void {\n const state = readRoutingState(logDir);\n if (!(provider in state.cooldowns)) return;\n delete state.cooldowns[provider];\n writeRoutingState(logDir, state);\n}\n\n/**\n * Run a provider's quotaProbe and return its percentage (0–100), or null when\n * there is no probe or it printed nothing usable. A probe must never break\n * routing: failures read as \"unknown\", not as \"full\".\n */\nexport function probeQuota(provider: WorkerProvider): number | null {\n if (!provider.quotaProbe) return null;\n try {\n const out = execFileSync(\"/bin/sh\", [\"-c\", provider.quotaProbe], {\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n const m = out.match(/(\\d+(?:\\.\\d+)?)/);\n if (!m) return null;\n return Math.min(100, Math.max(0, Math.round(parseFloat(m[1]))));\n } catch {\n return null;\n }\n}\n\nexport function quotaSkipThreshold(provider: WorkerProvider): number {\n return provider.quotaSkipAt ?? QUOTA_SKIP_DEFAULT;\n}\n\n/** Does this provider exceed its quota threshold right now? */\nexport function quotaExceeded(provider: WorkerProvider): boolean {\n const used = probeQuota(provider);\n return used !== null && used >= quotaSkipThreshold(provider);\n}\n\nexport interface ResolvedTarget {\n providerName: string;\n provider: WorkerProvider;\n /** Model alias from the class (\"glm/fast\" → \"fast\"), null = provider default. */\n modelAlias: string | null;\n /** MCP servers (or set names) from the class target, null when it sets none. */\n classMcp: string[] | null;\n /** How the provider was chosen — names the bypass rule for rerouting. */\n via: \"flag\" | \"class\" | \"active\" | \"auto\";\n}\n\nexport class NoProviderError extends WorkersConfigError {}\n\nfunction mustExist(config: WorkersConfig, name: string): WorkerProvider {\n const p = getProviderOrNative(config, name);\n if (!p) {\n throw new NoProviderError(\n `no worker provider named \"${name}\". Configured: ` +\n `${Object.keys(config.providers).join(\", \") || \"(none)\"}.` +\n `\\nAdd one with: pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`\n );\n }\n return p;\n}\n\nfunction mustBeRunnable(name: string, p: WorkerProvider): WorkerProvider {\n if (!p.enabled) {\n throw new NoProviderError(\n `provider \"${name}\" is disabled. Enable it with: pai worker providers enable ${name}`\n );\n }\n return p;\n}\n\n/** Why one provider of a routing order did not qualify (message fragment). */\nfunction exclusionReason(\n config: WorkersConfig,\n state: RoutingState,\n name: string,\n cls?: { maxCostTier?: number; requireTags?: string[] }\n): string | null {\n const p = getProviderOrNative(config, name);\n if (!p) return \"not configured\";\n if (!p.enabled) return \"disabled\";\n if (cooldownRemaining(state, name) > 0) return \"cooldown\";\n if (quotaExceeded(p)) return \"quota\";\n const tier = providerCostTier(p);\n if (cls?.maxCostTier !== undefined && tier > cls.maxCostTier) {\n return `cost tier ${tier} > max ${cls.maxCostTier}`;\n }\n if (cls?.requireTags?.length) {\n const have = p.tags ?? [];\n const missing = cls.requireTags.filter((t) => !have.includes(t as ProviderTag));\n if (missing.length) return `missing tags: ${missing.join(\", \")}`;\n }\n return null;\n}\n\n/**\n * Resolve which provider (and model alias) a run uses.\n *\n * @param flagProvider --provider value, highest precedence\n * @param className --class value (the old --role), looked up in workers.classes\n */\nexport function resolveTarget(\n config: WorkersConfig,\n logDir: string,\n opts: { flagProvider?: string; className?: string } = {}\n): ResolvedTarget {\n if (opts.flagProvider) {\n return {\n providerName: opts.flagProvider,\n provider: mustBeRunnable(opts.flagProvider, mustExist(config, opts.flagProvider)),\n modelAlias: null,\n classMcp: null,\n via: \"flag\",\n };\n }\n\n // class constraints (present whether or not the target pins a provider)\n const clsTarget = opts.className ? config.classes[opts.className] : undefined;\n const cls =\n typeof clsTarget === \"object\" && clsTarget !== null ? clsTarget : undefined;\n\n if (opts.className && clsTarget !== undefined && typeof clsTarget !== \"object\") {\n const [name, alias] = clsTarget.split(\"/\");\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: alias ?? null,\n classMcp: null,\n via: \"class\",\n };\n }\n if (opts.className && cls?.provider) {\n const provider = mustBeRunnable(cls.provider, mustExist(config, cls.provider));\n return {\n providerName: cls.provider,\n provider,\n modelAlias: null,\n classMcp: cls.mcp ?? null,\n via: \"class\",\n };\n }\n if (opts.className && clsTarget === undefined) {\n // a standard class may simply be unconfigured (it then routes like a run\n // without a class); anything else is a typo and must not pass silently\n if (!(WORKER_CLASSES as readonly string[]).includes(opts.className)) {\n throw new NoProviderError(\n `no class named \"${opts.className}\". Standard classes: ${WORKER_CLASSES.join(\", \")}.` +\n `Defined: ${Object.keys(config.classes).join(\", \") || \"(none)\"}.` +\n `\\nSet one with: pai worker classes set ${opts.className}=<provider[/alias]>`\n );\n }\n }\n\n if (config.active !== \"auto\") {\n // nothing active and no class entry: the built-in provider, no config written\n const name = config.active ?? ANTHROPIC_NATIVE;\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: null,\n classMcp: null,\n via: \"active\",\n };\n }\n\n // auto: first provider in (class or global) order that is enabled,\n // cooled-down-free, under quota and within the class constraints\n const state = readRoutingState(logDir);\n const order = cls?.order ?? config.routing.order;\n const excluded: string[] = [];\n for (const name of order) {\n const p = getProviderOrNative(config, name);\n const why = exclusionReason(config, state, name, cls);\n if (why) {\n excluded.push(`${name}: ${why}`);\n continue;\n }\n // exclusionReason already returned null (no exclusion) above, which is\n // only possible when getProviderOrNative found a real provider\n return { providerName: name, provider: p!, modelAlias: null, classMcp: cls?.mcp ?? null, via: \"auto\" };\n }\n throw new NoProviderError(\n `auto-routing found no usable provider${opts.className ? ` for class \"${opts.className}\"` : \"\"} ` +\n `(order: [${order.join(\", \")}]).` +\n `${excluded.length ? `\\nExcluded: ${excluded.join(\"; \")}.` : \"\"}` +\n `\\nClear a cooldown with: pai worker providers enable <name>; widen the class with: pai worker classes set ${opts.className ?? \"<class>\"}=<target>`\n );\n}\n\n/**\n * Next provider after `from` in auto order, applying the same filters.\n * Used by rerouting; null when the order is exhausted.\n */\nexport function nextAutoProvider(\n config: WorkersConfig,\n logDir: string,\n from: string,\n now: Date = new Date()\n): string | null {\n const state = readRoutingState(logDir);\n const order = config.routing.order;\n const start = order.indexOf(from);\n for (let i = start + 1; i < order.length; i++) {\n const name = order[i];\n const p = getProviderOrNative(config, name);\n if (!p || !p.enabled) continue;\n const end = state.cooldowns[name];\n if (end && Date.parse(end) > now.getTime()) continue;\n if (quotaExceeded(p)) continue;\n return name;\n }\n return null;\n}\n\n/**\n * Was this failure a quota/rate failure? Detected from the result text the\n * endpoint produced (HTTP status is not visible in the stream events).\n */\nexport function isQuotaFailure(resultText: string): boolean {\n return /usage limit reached|rate limit|quota/i.test(resultText);\n}\n\nexport interface CapabilityRunResolution {\n providerName: string;\n provider: WorkerProvider;\n model: string;\n engine: WorkerEngine;\n fellBack: boolean;\n capability: string;\n}\n\n/**\n * Whether a run should resolve through `resolveCapability` instead of\n * `resolveTarget`: an explicit `--capability` always does. A `--class`\n * whose implied capability is not \"default\" (e.g. `image`) does too, but\n * only when the class itself names no provider — a class already pinned to\n * one (`image: glm/image`) always wins unchanged, same as any other class.\n */\nexport function capabilityForRun(\n config: WorkersConfig,\n opts: { capabilityFlag?: string; className?: string }\n): string | null {\n if (opts.capabilityFlag) return opts.capabilityFlag;\n if (!opts.className) return null;\n const capability = classModelCapability(opts.className);\n if (capability === \"default\") return null;\n const clsTarget = config.classes[opts.className];\n const namesProvider = typeof clsTarget === \"string\" ? true : Boolean(clsTarget?.provider);\n return namesProvider ? null : capability;\n}\n\n/**\n * resolveCapability, turned into a runnable provider (NoProviderError-shaped\n * on failure). Unlike resolveCapability itself — which falls back to the\n * active provider's default model so `describeCapabilities` has something to\n * report — an actual run refuses the fallback: silently running a normal\n * model on a prompt written for a capability it does not have (an image\n * prompt fed to a text model, say) produces a confusing non-answer instead\n * of a clear error naming the config key to set.\n */\nexport function resolveCapabilityRun(config: WorkersConfig, capability: string): CapabilityRunResolution {\n const r = resolveCapability(config, capability);\n if (r.fellBack) {\n throw new NoProviderError(\n `no provider declares the \"${capability}\" capability — configure one with: ` +\n `pai worker capability ${capability} <provider>[,<provider>…], or ` +\n `pai worker model ${capability} <model-id> --provider <name>`\n );\n }\n // \"image\" specifically promises real pixel output — a provider whose only\n // claim to it is a `models.image` entry from the classes-based idiom\n // (`image: glm/image`, a plain model choice on an unchanged claude/codex\n // engine) cannot deliver that through this cross-provider run path, so a\n // run refuses it rather than spawning claude on a prompt it cannot answer.\n if (capability === \"image\" && r.engine !== \"image\") {\n throw new NoProviderError(\n `provider \"${r.provider}\" has no image engine (it would run \"${r.model}\" on its normal ` +\n `${r.engine} engine, not generate an image) — configure an engine=image provider with: ` +\n `pai worker providers add <name> --engine image --url <url> --key <key> --model <id>, then ` +\n `pai worker capability image <name>`\n );\n }\n const provider = getProviderOrNative(config, r.provider);\n if (!provider) {\n throw new NoProviderError(`capability \"${capability}\" resolved to unknown provider \"${r.provider}\"`);\n }\n return {\n providerName: r.provider,\n provider,\n model: r.model,\n engine: r.engine,\n fellBack: r.fellBack,\n capability,\n };\n}\n","/**\n * server.ts — the PAI worker proxy: Anthropic Messages API on the front,\n * OpenAI Chat Completions on the back, loopback only.\n *\n * One proxy serves every OpenAI-protocol provider: Claude Code points\n * ANTHROPIC_BASE_URL at `http://127.0.0.1:8797/<provider>` and the provider\n * name in the path selects the upstream (`upstreamUrl` + `keyFile` from the\n * workers config, re-read per request so config edits apply without a\n * restart). `run` starts the proxy on demand (detached, pid file under the\n * logDir) and `pai worker proxy stop` stops it again.\n *\n * All translation lives in translate.ts; this file is only HTTP plumbing.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createServer, type Server } from \"node:http\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { connect } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readWorkersSection, resolveProviderKey, type WorkerProvider } from \"../config.js\";\nimport { workersLogDir } from \"../paths.js\";\nimport {\n anthropicError,\n anthropicToOpenAi,\n openAiToAnthropic,\n OpenAiStreamTranslator,\n type AnthropicRequest,\n} from \"./translate.js\";\n\nexport const DEFAULT_PROXY_PORT = 8797;\nconst HOST = \"127.0.0.1\";\n\n/** How providers are looked up — swapped out by the tests. */\nexport type ProviderResolver = () => Record<string, WorkerProvider>;\n\nconst configProviders: ProviderResolver = () => readWorkersSection().workers.providers;\n\nexport function proxyPidPath(logDir: string): string {\n return join(logDir, \"proxy.pid\");\n}\n\n// ---------------------------------------------------------------------------\n// HTTP server\n// ---------------------------------------------------------------------------\n\ninterface ProxyServerOptions {\n port?: number;\n resolveProvider?: ProviderResolver;\n /** Test hook: called with every (translated) upstream request body. */\n onUpstreamRequest?: (url: string, body: unknown) => void;\n}\n\n/**\n * Create (but not start) the proxy server. Routes:\n * GET /healthz → 200 ok\n * POST /<provider>/v1/messages (and /<provider>/messages) → translated call\n */\nexport function createProxyServer(opts: ProxyServerOptions = {}): Server {\n const resolveProvider = opts.resolveProvider ?? configProviders;\n return createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${HOST}`);\n if (req.method === \"GET\" && url.pathname === \"/healthz\") {\n res.writeHead(200, { \"content-type\": \"text/plain\" });\n res.end(\"ok\");\n return;\n }\n const m = url.pathname.match(/^\\/([a-zA-Z0-9_-]+)\\/(v1\\/)?messages$/);\n if (req.method !== \"POST\" || !m) {\n res.writeHead(404, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(404, `no such route: ${req.method} ${url.pathname}`)));\n return;\n }\n const providerName = m[1];\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n void handleMessages(providerName, Buffer.concat(chunks).toString(\"utf8\"), resolveProvider, res, opts);\n });\n req.on(\"error\", () => {\n res.writeHead(400, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(400, \"request body read failed\")));\n });\n });\n}\n\nasync function handleMessages(\n providerName: string,\n bodyText: string,\n resolveProvider: ProviderResolver,\n res: import(\"node:http\").ServerResponse,\n opts: ProxyServerOptions\n): Promise<void> {\n const fail = (status: number, message: string) => {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(status, JSON.stringify({ message }))));\n };\n let reqBody: AnthropicRequest;\n try {\n reqBody = JSON.parse(bodyText) as AnthropicRequest;\n } catch {\n fail(400, \"request body is not valid JSON\");\n return;\n }\n const provider = resolveProvider()[providerName];\n if (!provider) {\n fail(404, `no worker provider named \"${providerName}\"`);\n return;\n }\n if (provider.protocol !== \"openai\" || !provider.upstreamUrl) {\n fail(400, `provider \"${providerName}\" is not an openai-protocol provider`);\n return;\n }\n\n const model = provider.models.default;\n const openaiBody = anthropicToOpenAi(reqBody, model);\n opts.onUpstreamRequest?.(`${provider.upstreamUrl}/chat/completions`, openaiBody);\n\n let upstream: Response;\n try {\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n let token: string | null;\n try {\n token = resolveProviderKey(provider);\n } catch (e) {\n fail(500, e instanceof Error ? e.message : String(e));\n return;\n }\n if (token) headers.authorization = `Bearer ${token}`;\n upstream = await fetch(`${provider.upstreamUrl}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(openaiBody),\n });\n } catch (e) {\n fail(502, `upstream unreachable: ${e instanceof Error ? e.message : String(e)}`);\n return;\n }\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n res.writeHead(upstream.status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(upstream.status, text)));\n return;\n }\n\n if (reqBody.stream) {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n const translator = new OpenAiStreamTranslator(model);\n let buf = \"\";\n if (!upstream.body) {\n res.end(translator.finish());\n return;\n }\n const reader = upstream.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += new TextDecoder().decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line.startsWith(\"data:\")) continue;\n const payload = line.slice(5).trim();\n if (payload === \"[DONE]\") {\n res.write(translator.finish());\n } else {\n try {\n res.write(translator.feed(JSON.parse(payload) as Parameters<typeof translator.feed>[0]));\n } catch {\n // skip a malformed chunk rather than kill the stream\n }\n }\n }\n }\n } finally {\n res.write(translator.finish()); // no-op when [DONE] already finished it\n res.end();\n }\n return;\n }\n\n const json = (await upstream.json().catch(() => null)) as unknown;\n if (!json) {\n fail(502, \"upstream returned a non-JSON body\");\n return;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(openAiToAnthropic(json as Parameters<typeof openAiToAnthropic>[0], model)));\n}\n\n// ---------------------------------------------------------------------------\n// Start / stop\n// ---------------------------------------------------------------------------\n\n/** Listen on host:port (loopback). Resolves with the bound port. */\nexport function listenProxy(server: Server, port = DEFAULT_PROXY_PORT): Promise<number> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, HOST, () => {\n const addr = server.address();\n resolve(typeof addr === \"object\" && addr ? addr.port : port);\n });\n });\n}\n\n/** True when something already accepts connections on the port. */\nexport function proxyListening(port = DEFAULT_PROXY_PORT, timeoutMs = 400): Promise<boolean> {\n return new Promise((resolve) => {\n const sock = connect({ port, host: HOST });\n const done = (ok: boolean) => {\n sock.removeAllListeners();\n sock.destroy();\n resolve(ok);\n };\n sock.setTimeout(timeoutMs, () => done(false));\n sock.once(\"connect\", () => done(true));\n sock.once(\"error\", () => done(false));\n });\n}\n\n/**\n * Where the standalone proxy artefact lives, found from this module's own\n * location (dist/cli bundle → ../hooks/, dev checkout → <repo>/dist/hooks).\n */\nexport function standaloneProxyPath(): string | null {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 6; i++) {\n for (const cand of [\n join(dir, \"worker-proxy.mjs\"),\n join(dir, \"hooks\", \"worker-proxy.mjs\"),\n join(dir, \"dist\", \"hooks\", \"worker-proxy.mjs\"),\n ]) {\n if (existsSync(cand)) return cand;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/**\n * Ensure a proxy is listening on the port; start the detached standalone when\n * nothing answers. Returns the base URL (`http://127.0.0.1:<port>`).\n */\nexport async function ensureProxyRunning(port: number, logDir: string): Promise<string> {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n const script = standaloneProxyPath();\n if (!script) {\n throw new Error(\n `the PAI worker proxy is not running and its build was not found — ` +\n `run \\`bun run build\\` (or start one with: pai worker proxy --port ${port})`\n );\n }\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n const child = spawn(process.execPath, [script, \"--port\", String(port)], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n writeFileSync(proxyPidPath(logDir), `${child.pid}\\n`, \"utf8\");\n for (let i = 0; i < 40; i++) {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n await new Promise((r) => setTimeout(r, 250));\n }\n throw new Error(`the PAI worker proxy did not come up on port ${port} (see ${proxyPidPath(logDir)})`);\n}\n\n/** `pai worker proxy stop`: SIGTERM the pid from the pid file. */\nexport function stopProxy(logDir: string): string {\n const path = proxyPidPath(logDir);\n if (!existsSync(path)) return \"no proxy pid file — nothing to stop\";\n const pid = parseInt(readFileSync(path, \"utf8\").trim(), 10);\n unlinkSync(path);\n if (!Number.isFinite(pid)) return \"stale proxy pid file removed\";\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return `proxy pid ${pid} was not running (pid file removed)`;\n }\n return `proxy pid ${pid} stopped`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"server-DgmAHyFK.mjs","names":[],"sources":["../src/workers/paths.ts","../src/workers/routing.ts","../src/workers/proxy/server.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 * routing.ts — provider selection: flag > class > active (possibly \"auto\").\n *\n * Auto-routing walks `workers.routing.order` — or the class's own `order` —\n * and takes the first provider that is enabled, out of cooldown, (when it\n * defines a quotaProbe) under its quotaSkipAt threshold, and — when the class\n * constrains it — within `maxCostTier` and carrying all `requireTags`. A run\n * that dies of a quota/rate error puts its provider in cooldown for\n * cooldownMinutes; when that happens before the first tool call and\n * retryOnQuota is set, the runner restarts the same task on the next provider\n * (ledger: WORKER-REROUTE).\n *\n * An explicit --provider or a class mapping that pins a provider always\n * bypasses all of this.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport {\n type WorkerEngine,\n type WorkerProvider,\n type WorkersConfig,\n type ProviderTag,\n WORKER_CLASSES,\n ANTHROPIC_NATIVE,\n WorkersConfigError,\n classModelCapability,\n providerCostTier,\n getProviderOrNative,\n resolveCapability,\n} from \"./config.js\";\nimport { routingStatePath } from \"./paths.js\";\n\nconst QUOTA_SKIP_DEFAULT = 95;\nconst PROBE_TIMEOUT_MS = 10_000;\n\nexport interface RoutingState {\n /** provider name → ISO timestamp when its cooldown ends */\n cooldowns: Record<string, string>;\n}\n\nexport function readRoutingState(logDir: string): RoutingState {\n const path = routingStatePath(logDir);\n if (!existsSync(path)) return { cooldowns: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as RoutingState;\n return { cooldowns: parsed.cooldowns ?? {} };\n } catch {\n return { cooldowns: {} };\n }\n}\n\nexport function writeRoutingState(logDir: string, state: RoutingState): void {\n const path = routingStatePath(logDir);\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(state, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nexport function cooldownRemaining(\n state: RoutingState,\n provider: string,\n now: Date = new Date()\n): number {\n const end = state.cooldowns[provider];\n if (!end) return 0;\n const ms = Date.parse(end) - now.getTime();\n return ms > 0 ? ms : 0;\n}\n\nexport function setCooldown(\n logDir: string,\n provider: string,\n minutes: number,\n now: Date = new Date()\n): void {\n const state = readRoutingState(logDir);\n state.cooldowns[provider] = new Date(now.getTime() + minutes * 60_000).toISOString();\n writeRoutingState(logDir, state);\n}\n\nexport function clearCooldown(logDir: string, provider: string): void {\n const state = readRoutingState(logDir);\n if (!(provider in state.cooldowns)) return;\n delete state.cooldowns[provider];\n writeRoutingState(logDir, state);\n}\n\n/**\n * Run a provider's quotaProbe and return its percentage (0–100), or null when\n * there is no probe or it printed nothing usable. A probe must never break\n * routing: failures read as \"unknown\", not as \"full\".\n */\nexport function probeQuota(provider: WorkerProvider): number | null {\n if (!provider.quotaProbe) return null;\n try {\n const out = execFileSync(\"/bin/sh\", [\"-c\", provider.quotaProbe], {\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n const m = out.match(/(\\d+(?:\\.\\d+)?)/);\n if (!m) return null;\n return Math.min(100, Math.max(0, Math.round(parseFloat(m[1]))));\n } catch {\n return null;\n }\n}\n\nexport function quotaSkipThreshold(provider: WorkerProvider): number {\n return provider.quotaSkipAt ?? QUOTA_SKIP_DEFAULT;\n}\n\n/** Does this provider exceed its quota threshold right now? */\nexport function quotaExceeded(provider: WorkerProvider): boolean {\n const used = probeQuota(provider);\n return used !== null && used >= quotaSkipThreshold(provider);\n}\n\nexport interface ResolvedTarget {\n providerName: string;\n provider: WorkerProvider;\n /** Model alias from the class (\"glm/fast\" → \"fast\"), null = provider default. */\n modelAlias: string | null;\n /** MCP servers (or set names) from the class target, null when it sets none. */\n classMcp: string[] | null;\n /** How the provider was chosen — names the bypass rule for rerouting. */\n via: \"flag\" | \"class\" | \"active\" | \"auto\";\n}\n\nexport class NoProviderError extends WorkersConfigError {}\n\nfunction mustExist(config: WorkersConfig, name: string): WorkerProvider {\n const p = getProviderOrNative(config, name);\n if (!p) {\n throw new NoProviderError(\n `no worker provider named \"${name}\". Configured: ` +\n `${Object.keys(config.providers).join(\", \") || \"(none)\"}.` +\n `\\nAdd one with: pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`\n );\n }\n return p;\n}\n\nfunction mustBeRunnable(name: string, p: WorkerProvider): WorkerProvider {\n if (!p.enabled) {\n throw new NoProviderError(\n `provider \"${name}\" is disabled. Enable it with: pai worker providers enable ${name}`\n );\n }\n return p;\n}\n\n/** Why one provider of a routing order did not qualify (message fragment). */\nfunction exclusionReason(\n config: WorkersConfig,\n state: RoutingState,\n name: string,\n cls?: { maxCostTier?: number; requireTags?: string[] }\n): string | null {\n const p = getProviderOrNative(config, name);\n if (!p) return \"not configured\";\n if (!p.enabled) return \"disabled\";\n if (cooldownRemaining(state, name) > 0) return \"cooldown\";\n if (quotaExceeded(p)) return \"quota\";\n const tier = providerCostTier(p);\n if (cls?.maxCostTier !== undefined && tier > cls.maxCostTier) {\n return `cost tier ${tier} > max ${cls.maxCostTier}`;\n }\n if (cls?.requireTags?.length) {\n const have = p.tags ?? [];\n const missing = cls.requireTags.filter((t) => !have.includes(t as ProviderTag));\n if (missing.length) return `missing tags: ${missing.join(\", \")}`;\n }\n return null;\n}\n\n/**\n * Resolve which provider (and model alias) a run uses.\n *\n * @param flagProvider --provider value, highest precedence\n * @param className --class value (the old --role), looked up in workers.classes\n */\nexport function resolveTarget(\n config: WorkersConfig,\n logDir: string,\n opts: { flagProvider?: string; className?: string } = {}\n): ResolvedTarget {\n if (opts.flagProvider) {\n return {\n providerName: opts.flagProvider,\n provider: mustBeRunnable(opts.flagProvider, mustExist(config, opts.flagProvider)),\n modelAlias: null,\n classMcp: null,\n via: \"flag\",\n };\n }\n\n // class constraints (present whether or not the target pins a provider)\n const clsTarget = opts.className ? config.classes[opts.className] : undefined;\n const cls =\n typeof clsTarget === \"object\" && clsTarget !== null ? clsTarget : undefined;\n\n if (opts.className && clsTarget !== undefined && typeof clsTarget !== \"object\") {\n const [name, alias] = clsTarget.split(\"/\");\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: alias ?? null,\n classMcp: null,\n via: \"class\",\n };\n }\n if (opts.className && cls?.provider) {\n const provider = mustBeRunnable(cls.provider, mustExist(config, cls.provider));\n return {\n providerName: cls.provider,\n provider,\n modelAlias: null,\n classMcp: cls.mcp ?? null,\n via: \"class\",\n };\n }\n if (opts.className && clsTarget === undefined) {\n // a standard class may simply be unconfigured (it then routes like a run\n // without a class); anything else is a typo and must not pass silently\n if (!(WORKER_CLASSES as readonly string[]).includes(opts.className)) {\n throw new NoProviderError(\n `no class named \"${opts.className}\". Standard classes: ${WORKER_CLASSES.join(\", \")}.` +\n `Defined: ${Object.keys(config.classes).join(\", \") || \"(none)\"}.` +\n `\\nSet one with: pai worker classes set ${opts.className}=<provider[/alias]>`\n );\n }\n }\n\n if (config.active !== \"auto\") {\n // nothing active and no class entry: the built-in provider, no config written\n const name = config.active ?? ANTHROPIC_NATIVE;\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: null,\n classMcp: null,\n via: \"active\",\n };\n }\n\n // auto: first provider in (class or global) order that is enabled,\n // cooled-down-free, under quota and within the class constraints\n const state = readRoutingState(logDir);\n const order = cls?.order ?? config.routing.order;\n const excluded: string[] = [];\n for (const name of order) {\n const p = getProviderOrNative(config, name);\n const why = exclusionReason(config, state, name, cls);\n if (why) {\n excluded.push(`${name}: ${why}`);\n continue;\n }\n // exclusionReason already returned null (no exclusion) above, which is\n // only possible when getProviderOrNative found a real provider\n return { providerName: name, provider: p!, modelAlias: null, classMcp: cls?.mcp ?? null, via: \"auto\" };\n }\n throw new NoProviderError(\n `auto-routing found no usable provider${opts.className ? ` for class \"${opts.className}\"` : \"\"} ` +\n `(order: [${order.join(\", \")}]).` +\n `${excluded.length ? `\\nExcluded: ${excluded.join(\"; \")}.` : \"\"}` +\n `\\nClear a cooldown with: pai worker providers enable <name>; widen the class with: pai worker classes set ${opts.className ?? \"<class>\"}=<target>`\n );\n}\n\n/**\n * Next provider after `from` in auto order, applying the same filters.\n * Used by rerouting; null when the order is exhausted.\n */\nexport function nextAutoProvider(\n config: WorkersConfig,\n logDir: string,\n from: string,\n now: Date = new Date()\n): string | null {\n const state = readRoutingState(logDir);\n const order = config.routing.order;\n const start = order.indexOf(from);\n for (let i = start + 1; i < order.length; i++) {\n const name = order[i];\n const p = getProviderOrNative(config, name);\n if (!p || !p.enabled) continue;\n const end = state.cooldowns[name];\n if (end && Date.parse(end) > now.getTime()) continue;\n if (quotaExceeded(p)) continue;\n return name;\n }\n return null;\n}\n\n/**\n * Was this failure a quota/rate failure? Detected from the result text the\n * endpoint produced (HTTP status is not visible in the stream events).\n */\nexport function isQuotaFailure(resultText: string): boolean {\n return /usage limit reached|rate limit|quota/i.test(resultText);\n}\n\nexport interface CapabilityRunResolution {\n providerName: string;\n provider: WorkerProvider;\n model: string;\n engine: WorkerEngine;\n fellBack: boolean;\n capability: string;\n}\n\n/**\n * Whether a run should resolve through `resolveCapability` instead of\n * `resolveTarget`: an explicit `--capability` always does. A `--class`\n * whose implied capability is not \"default\" (e.g. `image`) does too, but\n * only when the class itself names no provider — a class already pinned to\n * one (`image: glm/image`) always wins unchanged, same as any other class.\n */\nexport function capabilityForRun(\n config: WorkersConfig,\n opts: { capabilityFlag?: string; className?: string }\n): string | null {\n if (opts.capabilityFlag) return opts.capabilityFlag;\n if (!opts.className) return null;\n const capability = classModelCapability(opts.className);\n if (capability === \"default\") return null;\n const clsTarget = config.classes[opts.className];\n const namesProvider = typeof clsTarget === \"string\" ? true : Boolean(clsTarget?.provider);\n return namesProvider ? null : capability;\n}\n\n/**\n * resolveCapability, turned into a runnable provider (NoProviderError-shaped\n * on failure). Unlike resolveCapability itself — which falls back to the\n * active provider's default model so `describeCapabilities` has something to\n * report — an actual run refuses the fallback: silently running a normal\n * model on a prompt written for a capability it does not have (an image\n * prompt fed to a text model, say) produces a confusing non-answer instead\n * of a clear error naming the config key to set.\n */\nexport function resolveCapabilityRun(config: WorkersConfig, capability: string): CapabilityRunResolution {\n const r = resolveCapability(config, capability);\n if (r.fellBack) {\n throw new NoProviderError(\n `no provider declares the \"${capability}\" capability — configure one with: ` +\n `pai worker capability ${capability} <provider>[,<provider>…], or ` +\n `pai worker model ${capability} <model-id> --provider <name>`\n );\n }\n // \"image\" specifically promises real pixel output — a provider whose only\n // claim to it is a `models.image` entry from the classes-based idiom\n // (`image: glm/image`, a plain model choice on an unchanged claude/codex\n // engine) cannot deliver that through this cross-provider run path, so a\n // run refuses it rather than spawning claude on a prompt it cannot answer.\n if (capability === \"image\" && r.engine !== \"image\") {\n throw new NoProviderError(\n `provider \"${r.provider}\" has no image engine (it would run \"${r.model}\" on its normal ` +\n `${r.engine} engine, not generate an image) — configure an engine=image provider with: ` +\n `pai worker providers add <name> --engine image --url <url> --key <key> --model <id>, then ` +\n `pai worker capability image <name>`\n );\n }\n const provider = getProviderOrNative(config, r.provider);\n if (!provider) {\n throw new NoProviderError(`capability \"${capability}\" resolved to unknown provider \"${r.provider}\"`);\n }\n return {\n providerName: r.provider,\n provider,\n model: r.model,\n engine: r.engine,\n fellBack: r.fellBack,\n capability,\n };\n}\n","/**\n * server.ts — the PAI worker proxy: Anthropic Messages API on the front,\n * OpenAI Chat Completions on the back, loopback only.\n *\n * One proxy serves every OpenAI-protocol provider: Claude Code points\n * ANTHROPIC_BASE_URL at `http://127.0.0.1:8797/<provider>` and the provider\n * name in the path selects the upstream (`upstreamUrl` + `keyFile` from the\n * workers config, re-read per request so config edits apply without a\n * restart). `run` starts the proxy on demand (detached, pid file under the\n * logDir) and `pai worker proxy stop` stops it again.\n *\n * All translation lives in translate.ts; this file is only HTTP plumbing.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createServer, type Server } from \"node:http\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { connect } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readWorkersSection, resolveProviderKey, type WorkerProvider } from \"../config.js\";\nimport { workersLogDir } from \"../paths.js\";\nimport {\n anthropicError,\n anthropicToOpenAi,\n openAiToAnthropic,\n OpenAiStreamTranslator,\n type AnthropicRequest,\n} from \"./translate.js\";\n\nexport const DEFAULT_PROXY_PORT = 8797;\nconst HOST = \"127.0.0.1\";\n\n/** How providers are looked up — swapped out by the tests. */\nexport type ProviderResolver = () => Record<string, WorkerProvider>;\n\nconst configProviders: ProviderResolver = () => readWorkersSection().workers.providers;\n\nexport function proxyPidPath(logDir: string): string {\n return join(logDir, \"proxy.pid\");\n}\n\n// ---------------------------------------------------------------------------\n// HTTP server\n// ---------------------------------------------------------------------------\n\ninterface ProxyServerOptions {\n port?: number;\n resolveProvider?: ProviderResolver;\n /** Test hook: called with every (translated) upstream request body. */\n onUpstreamRequest?: (url: string, body: unknown) => void;\n}\n\n/**\n * Create (but not start) the proxy server. Routes:\n * GET /healthz → 200 ok\n * POST /<provider>/v1/messages (and /<provider>/messages) → translated call\n */\nexport function createProxyServer(opts: ProxyServerOptions = {}): Server {\n const resolveProvider = opts.resolveProvider ?? configProviders;\n return createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${HOST}`);\n if (req.method === \"GET\" && url.pathname === \"/healthz\") {\n res.writeHead(200, { \"content-type\": \"text/plain\" });\n res.end(\"ok\");\n return;\n }\n const m = url.pathname.match(/^\\/([a-zA-Z0-9_-]+)\\/(v1\\/)?messages$/);\n if (req.method !== \"POST\" || !m) {\n res.writeHead(404, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(404, `no such route: ${req.method} ${url.pathname}`)));\n return;\n }\n const providerName = m[1];\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n void handleMessages(providerName, Buffer.concat(chunks).toString(\"utf8\"), resolveProvider, res, opts);\n });\n req.on(\"error\", () => {\n res.writeHead(400, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(400, \"request body read failed\")));\n });\n });\n}\n\nasync function handleMessages(\n providerName: string,\n bodyText: string,\n resolveProvider: ProviderResolver,\n res: import(\"node:http\").ServerResponse,\n opts: ProxyServerOptions\n): Promise<void> {\n const fail = (status: number, message: string) => {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(status, JSON.stringify({ message }))));\n };\n let reqBody: AnthropicRequest;\n try {\n reqBody = JSON.parse(bodyText) as AnthropicRequest;\n } catch {\n fail(400, \"request body is not valid JSON\");\n return;\n }\n const provider = resolveProvider()[providerName];\n if (!provider) {\n fail(404, `no worker provider named \"${providerName}\"`);\n return;\n }\n if (provider.protocol !== \"openai\" || !provider.upstreamUrl) {\n fail(400, `provider \"${providerName}\" is not an openai-protocol provider`);\n return;\n }\n\n const model = provider.models.default;\n const openaiBody = anthropicToOpenAi(reqBody, model);\n opts.onUpstreamRequest?.(`${provider.upstreamUrl}/chat/completions`, openaiBody);\n\n let upstream: Response;\n try {\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n let token: string | null;\n try {\n token = resolveProviderKey(provider);\n } catch (e) {\n fail(500, e instanceof Error ? e.message : String(e));\n return;\n }\n if (token) headers.authorization = `Bearer ${token}`;\n upstream = await fetch(`${provider.upstreamUrl}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(openaiBody),\n });\n } catch (e) {\n fail(502, `upstream unreachable: ${e instanceof Error ? e.message : String(e)}`);\n return;\n }\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n res.writeHead(upstream.status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(upstream.status, text)));\n return;\n }\n\n if (reqBody.stream) {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n const translator = new OpenAiStreamTranslator(model);\n let buf = \"\";\n if (!upstream.body) {\n res.end(translator.finish());\n return;\n }\n const reader = upstream.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += new TextDecoder().decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line.startsWith(\"data:\")) continue;\n const payload = line.slice(5).trim();\n if (payload === \"[DONE]\") {\n res.write(translator.finish());\n } else {\n try {\n res.write(translator.feed(JSON.parse(payload) as Parameters<typeof translator.feed>[0]));\n } catch {\n // skip a malformed chunk rather than kill the stream\n }\n }\n }\n }\n } finally {\n res.write(translator.finish()); // no-op when [DONE] already finished it\n res.end();\n }\n return;\n }\n\n const json = (await upstream.json().catch(() => null)) as unknown;\n if (!json) {\n fail(502, \"upstream returned a non-JSON body\");\n return;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(openAiToAnthropic(json as Parameters<typeof openAiToAnthropic>[0], model)));\n}\n\n// ---------------------------------------------------------------------------\n// Start / stop\n// ---------------------------------------------------------------------------\n\n/** Listen on host:port (loopback). Resolves with the bound port. */\nexport function listenProxy(server: Server, port = DEFAULT_PROXY_PORT): Promise<number> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, HOST, () => {\n const addr = server.address();\n resolve(typeof addr === \"object\" && addr ? addr.port : port);\n });\n });\n}\n\n/** True when something already accepts connections on the port. */\nexport function proxyListening(port = DEFAULT_PROXY_PORT, timeoutMs = 400): Promise<boolean> {\n return new Promise((resolve) => {\n const sock = connect({ port, host: HOST });\n const done = (ok: boolean) => {\n sock.removeAllListeners();\n sock.destroy();\n resolve(ok);\n };\n sock.setTimeout(timeoutMs, () => done(false));\n sock.once(\"connect\", () => done(true));\n sock.once(\"error\", () => done(false));\n });\n}\n\n/**\n * Where the standalone proxy artefact lives, found from this module's own\n * location (dist/cli bundle → ../hooks/, dev checkout → <repo>/dist/hooks).\n */\nexport function standaloneProxyPath(): string | null {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 6; i++) {\n for (const cand of [\n join(dir, \"worker-proxy.mjs\"),\n join(dir, \"hooks\", \"worker-proxy.mjs\"),\n join(dir, \"dist\", \"hooks\", \"worker-proxy.mjs\"),\n ]) {\n if (existsSync(cand)) return cand;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/**\n * Ensure a proxy is listening on the port; start the detached standalone when\n * nothing answers. Returns the base URL (`http://127.0.0.1:<port>`).\n */\nexport async function ensureProxyRunning(port: number, logDir: string): Promise<string> {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n const script = standaloneProxyPath();\n if (!script) {\n throw new Error(\n `the PAI worker proxy is not running and its build was not found — ` +\n `run \\`bun run build\\` (or start one with: pai worker proxy --port ${port})`\n );\n }\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n const child = spawn(process.execPath, [script, \"--port\", String(port)], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n writeFileSync(proxyPidPath(logDir), `${child.pid}\\n`, \"utf8\");\n for (let i = 0; i < 40; i++) {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n await new Promise((r) => setTimeout(r, 250));\n }\n throw new Error(`the PAI worker proxy did not come up on port ${port} (see ${proxyPidPath(logDir)})`);\n}\n\n/** `pai worker proxy stop`: SIGTERM the pid from the pid file. */\nexport function stopProxy(logDir: string): string {\n const path = proxyPidPath(logDir);\n if (!existsSync(path)) return \"no proxy pid file — nothing to stop\";\n const pid = parseInt(readFileSync(path, \"utf8\").trim(), 10);\n unlinkSync(path);\n if (!Number.isFinite(pid)) return \"stale proxy pid file removed\";\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return `proxy pid ${pid} was not running (pid file removed)`;\n }\n return `proxy pid ${pid} stopped`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,QAA+B;CAC3D,MAAM,WAAW,WAAW,OAAO,OAAO;AAC1C,KAAI,OAAO,WAAW,gBAAiB,QAAO;AAC9C,QAAO,eAAe,YAAY,QAAQ,UAAU,EAAE,CAAC,SAAS,EAAE,4BAA4B;;;AAIhG,SAAgB,kBAAkB,QAAwB;CACxD,MAAM,OAAO,gBAAgB,OAAO;AACpC,KAAI,CAAC,WAAW,KAAK,EAAE;AACrB,YAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AACtC,gBAAc,MAAM,4BAA0B,OAAO;;AAEvD,QAAO;;AAGT,SAAgB,WAAW,QAAgB,IAAoB;AAC7D,QAAO,KAAK,QAAQ,GAAG,GAAG,SAAS;;AAGrC,SAAgB,WAAW,QAAgB,IAAoB;AAC7D,QAAO,KAAK,QAAQ,GAAG,GAAG,QAAQ;;AAGpC,SAAgB,WAAW,QAAwB;AACjD,QAAO,KAAK,QAAQ,aAAa;;AAGnC,SAAgB,iBAAiB,QAAwB;AACvD,QAAO,KAAK,QAAQ,qBAAqB;;AAG3C,SAAgB,SAAS,QAAwB;AAC/C,QAAO,KAAK,QAAQ,QAAQ;;;;;;;AAQ9B,SAAgB,gBAAgB,QAAwB;AACtD,QAAO,KAAK,QAAQ,cAAc;;;;;;;;;;;;;;;;;;;;AC3BpC,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAOzB,SAAgB,iBAAiB,QAA8B;CAC7D,MAAM,OAAO,iBAAiB,OAAO;AACrC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE,WAAW,EAAE,EAAE;AAC/C,KAAI;AAEF,SAAO,EAAE,WADM,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC,CAC1B,aAAa,EAAE,EAAE;SACtC;AACN,SAAO,EAAE,WAAW,EAAE,EAAE;;;AAI5B,SAAgB,kBAAkB,QAAgB,OAA2B;CAC3E,MAAM,OAAO,iBAAiB,OAAO;CACrC,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,OAAO,MAAM,EAAE,GAAG,MAAM,OAAO;AACjE,YAAW,KAAK,KAAK;;AAGvB,SAAgB,kBACd,OACA,UACA,sBAAY,IAAI,MAAM,EACd;CACR,MAAM,MAAM,MAAM,UAAU;AAC5B,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,KAAK,KAAK,MAAM,IAAI,GAAG,IAAI,SAAS;AAC1C,QAAO,KAAK,IAAI,KAAK;;AAGvB,SAAgB,YACd,QACA,UACA,SACA,sBAAY,IAAI,MAAM,EAChB;CACN,MAAM,QAAQ,iBAAiB,OAAO;AACtC,OAAM,UAAU,YAAY,IAAI,KAAK,IAAI,SAAS,GAAG,UAAU,IAAO,CAAC,aAAa;AACpF,mBAAkB,QAAQ,MAAM;;AAGlC,SAAgB,cAAc,QAAgB,UAAwB;CACpE,MAAM,QAAQ,iBAAiB,OAAO;AACtC,KAAI,EAAE,YAAY,MAAM,WAAY;AACpC,QAAO,MAAM,UAAU;AACvB,mBAAkB,QAAQ,MAAM;;;;;;;AAQlC,SAAgB,WAAW,UAAyC;AAClE,KAAI,CAAC,SAAS,WAAY,QAAO;AACjC,KAAI;EAMF,MAAM,IALM,aAAa,WAAW,CAAC,MAAM,SAAS,WAAW,EAAE;GAC/D,SAAS;GACT,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CACY,MAAM,kBAAkB;AACtC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;SACzD;AACN,SAAO;;;AAIX,SAAgB,mBAAmB,UAAkC;AACnE,QAAO,SAAS,eAAe;;;AAIjC,SAAgB,cAAc,UAAmC;CAC/D,MAAM,OAAO,WAAW,SAAS;AACjC,QAAO,SAAS,QAAQ,QAAQ,mBAAmB,SAAS;;AAc9D,IAAa,kBAAb,cAAqC,mBAAmB;AAExD,SAAS,UAAU,QAAuB,MAA8B;CACtE,MAAM,IAAI,oBAAoB,QAAQ,KAAK;AAC3C,KAAI,CAAC,EACH,OAAM,IAAI,gBACR,6BAA6B,KAAK,iBAC7B,OAAO,KAAK,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,SAAS,qGAE3D;AAEH,QAAO;;AAGT,SAAS,eAAe,MAAc,GAAmC;AACvE,KAAI,CAAC,EAAE,QACL,OAAM,IAAI,gBACR,aAAa,KAAK,6DAA6D,OAChF;AAEH,QAAO;;;AAIT,SAAS,gBACP,QACA,OACA,MACA,KACe;CACf,MAAM,IAAI,oBAAoB,QAAQ,KAAK;AAC3C,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,CAAC,EAAE,QAAS,QAAO;AACvB,KAAI,kBAAkB,OAAO,KAAK,GAAG,EAAG,QAAO;AAC/C,KAAI,cAAc,EAAE,CAAE,QAAO;CAC7B,MAAM,OAAO,iBAAiB,EAAE;AAChC,KAAI,KAAK,gBAAgB,UAAa,OAAO,IAAI,YAC/C,QAAO,aAAa,KAAK,SAAS,IAAI;AAExC,KAAI,KAAK,aAAa,QAAQ;EAC5B,MAAM,OAAO,EAAE,QAAQ,EAAE;EACzB,MAAM,UAAU,IAAI,YAAY,QAAQ,MAAM,CAAC,KAAK,SAAS,EAAiB,CAAC;AAC/E,MAAI,QAAQ,OAAQ,QAAO,iBAAiB,QAAQ,KAAK,KAAK;;AAEhE,QAAO;;;;;;;;AAST,SAAgB,cACd,QACA,QACA,OAAsD,EAAE,EACxC;AAChB,KAAI,KAAK,aACP,QAAO;EACL,cAAc,KAAK;EACnB,UAAU,eAAe,KAAK,cAAc,UAAU,QAAQ,KAAK,aAAa,CAAC;EACjF,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CAIH,MAAM,YAAY,KAAK,YAAY,OAAO,QAAQ,KAAK,aAAa;CACpE,MAAM,MACJ,OAAO,cAAc,YAAY,cAAc,OAAO,YAAY;AAEpE,KAAI,KAAK,aAAa,cAAc,UAAa,OAAO,cAAc,UAAU;EAC9E,MAAM,CAAC,MAAM,SAAS,UAAU,MAAM,IAAI;AAC1C,SAAO;GACL,cAAc;GACd,UAAU,eAAe,MAAM,UAAU,QAAQ,KAAK,CAAC;GACvD,YAAY,SAAS;GACrB,UAAU;GACV,KAAK;GACN;;AAEH,KAAI,KAAK,aAAa,KAAK,UAAU;EACnC,MAAM,WAAW,eAAe,IAAI,UAAU,UAAU,QAAQ,IAAI,SAAS,CAAC;AAC9E,SAAO;GACL,cAAc,IAAI;GAClB;GACA,YAAY;GACZ,UAAU,IAAI,OAAO;GACrB,KAAK;GACN;;AAEH,KAAI,KAAK,aAAa,cAAc,QAGlC;MAAI,CAAE,eAAqC,SAAS,KAAK,UAAU,CACjE,OAAM,IAAI,gBACR,mBAAmB,KAAK,UAAU,uBAAuB,eAAe,KAAK,KAAK,CAAC,YACrE,OAAO,KAAK,OAAO,QAAQ,CAAC,KAAK,KAAK,IAAI,SAAS,0CACrB,KAAK,UAAU,qBAC5D;;AAIL,KAAI,OAAO,WAAW,QAAQ;EAE5B,MAAM,OAAO,OAAO,UAAU;AAC9B,SAAO;GACL,cAAc;GACd,UAAU,eAAe,MAAM,UAAU,QAAQ,KAAK,CAAC;GACvD,YAAY;GACZ,UAAU;GACV,KAAK;GACN;;CAKH,MAAM,QAAQ,iBAAiB,OAAO;CACtC,MAAM,QAAQ,KAAK,SAAS,OAAO,QAAQ;CAC3C,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,IAAI,oBAAoB,QAAQ,KAAK;EAC3C,MAAM,MAAM,gBAAgB,QAAQ,OAAO,MAAM,IAAI;AACrD,MAAI,KAAK;AACP,YAAS,KAAK,GAAG,KAAK,IAAI,MAAM;AAChC;;AAIF,SAAO;GAAE,cAAc;GAAM,UAAU;GAAI,YAAY;GAAM,UAAU,KAAK,OAAO;GAAM,KAAK;GAAQ;;AAExG,OAAM,IAAI,gBACR,wCAAwC,KAAK,YAAY,eAAe,KAAK,UAAU,KAAK,GAAG,YACjF,MAAM,KAAK,KAAK,CAAC,KAC1B,SAAS,SAAS,eAAe,SAAS,KAAK,KAAK,CAAC,KAAK,+GACgD,KAAK,aAAa,UAAU,WAC5I;;;;;;AAOH,SAAgB,iBACd,QACA,QACA,MACA,sBAAY,IAAI,MAAM,EACP;CACf,MAAM,QAAQ,iBAAiB,OAAO;CACtC,MAAM,QAAQ,OAAO,QAAQ;CAC7B,MAAM,QAAQ,MAAM,QAAQ,KAAK;AACjC,MAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,MAAM,QAAQ,KAAK;EAC7C,MAAM,OAAO,MAAM;EACnB,MAAM,IAAI,oBAAoB,QAAQ,KAAK;AAC3C,MAAI,CAAC,KAAK,CAAC,EAAE,QAAS;EACtB,MAAM,MAAM,MAAM,UAAU;AAC5B,MAAI,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI,SAAS,CAAE;AAC5C,MAAI,cAAc,EAAE,CAAE;AACtB,SAAO;;AAET,QAAO;;;;;;AAOT,SAAgB,eAAe,YAA6B;AAC1D,QAAO,wCAAwC,KAAK,WAAW;;;;;;;;;AAmBjE,SAAgB,iBACd,QACA,MACe;AACf,KAAI,KAAK,eAAgB,QAAO,KAAK;AACrC,KAAI,CAAC,KAAK,UAAW,QAAO;CAC5B,MAAM,aAAa,qBAAqB,KAAK,UAAU;AACvD,KAAI,eAAe,UAAW,QAAO;CACrC,MAAM,YAAY,OAAO,QAAQ,KAAK;AAEtC,SADsB,OAAO,cAAc,WAAW,OAAO,QAAQ,WAAW,SAAS,IAClE,OAAO;;;;;;;;;;;AAYhC,SAAgB,qBAAqB,QAAuB,YAA6C;CACvG,MAAM,IAAI,kBAAkB,QAAQ,WAAW;AAC/C,KAAI,EAAE,SACJ,OAAM,IAAI,gBACR,6BAA6B,WAAW,2DACb,WAAW,iDAChB,WAAW,+BAClC;AAOH,KAAI,eAAe,WAAW,EAAE,WAAW,QACzC,OAAM,IAAI,gBACR,aAAa,EAAE,SAAS,uCAAuC,EAAE,MAAM,kBAClE,EAAE,OAAO,yMAGf;CAEH,MAAM,WAAW,oBAAoB,QAAQ,EAAE,SAAS;AACxD,KAAI,CAAC,SACH,OAAM,IAAI,gBAAgB,eAAe,WAAW,kCAAkC,EAAE,SAAS,GAAG;AAEtG,QAAO;EACL,cAAc,EAAE;EAChB;EACA,OAAO,EAAE;EACT,QAAQ,EAAE;EACV,UAAU,EAAE;EACZ;EACD;;;;;;;;;;;;;;;;;;AC5VH,MAAa,qBAAqB;AAClC,MAAM,OAAO;AAOb,SAAgB,aAAa,QAAwB;AACnD,QAAO,KAAK,QAAQ,YAAY;;;AA8KlC,SAAgB,eAAe,OAAO,oBAAoB,YAAY,KAAuB;AAC3F,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,OAAO,QAAQ;GAAE;GAAM,MAAM;GAAM,CAAC;EAC1C,MAAM,QAAQ,OAAgB;AAC5B,QAAK,oBAAoB;AACzB,QAAK,SAAS;AACd,WAAQ,GAAG;;AAEb,OAAK,WAAW,iBAAiB,KAAK,MAAM,CAAC;AAC7C,OAAK,KAAK,iBAAiB,KAAK,KAAK,CAAC;AACtC,OAAK,KAAK,eAAe,KAAK,MAAM,CAAC;GACrC;;;;;;AAOJ,SAAgB,sBAAqC;CACnD,IAAI,MAAM,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AACjD,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,OAAK,MAAM,QAAQ;GACjB,KAAK,KAAK,mBAAmB;GAC7B,KAAK,KAAK,SAAS,mBAAmB;GACtC,KAAK,KAAK,QAAQ,SAAS,mBAAmB;GAC/C,CACC,KAAI,WAAW,KAAK,CAAE,QAAO;EAE/B,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,QAAO;;;;;;AAOT,eAAsB,mBAAmB,MAAc,QAAiC;AACtF,KAAI,MAAM,eAAe,KAAK,CAAE,QAAO,UAAU,KAAK,GAAG;CACzD,MAAM,SAAS,qBAAqB;AACpC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uIACuE,KAAK,GAC7E;AAEH,KAAI,CAAC,WAAW,OAAO,CAAE,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;CAC/D,MAAM,QAAQ,MAAM,QAAQ,UAAU;EAAC;EAAQ;EAAU,OAAO,KAAK;EAAC,EAAE;EACtE,UAAU;EACV,OAAO;EACR,CAAC;AACF,OAAM,OAAO;AACb,eAAc,aAAa,OAAO,EAAE,GAAG,MAAM,IAAI,KAAK,OAAO;AAC7D,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,MAAI,MAAM,eAAe,KAAK,CAAE,QAAO,UAAU,KAAK,GAAG;AACzD,QAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAI,CAAC;;AAE9C,OAAM,IAAI,MAAM,gDAAgD,KAAK,QAAQ,aAAa,OAAO,CAAC,GAAG;;;AAIvG,SAAgB,UAAU,QAAwB;CAChD,MAAM,OAAO,aAAa,OAAO;AACjC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;CAC9B,MAAM,MAAM,SAAS,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,GAAG;AAC3D,YAAW,KAAK;AAChB,KAAI,CAAC,OAAO,SAAS,IAAI,CAAE,QAAO;AAClC,KAAI;AACF,UAAQ,KAAK,KAAK,UAAU;SACtB;AACN,SAAO,aAAa,IAAI;;AAE1B,QAAO,aAAa,IAAI"}
@@ -1,8 +1,8 @@
1
1
  import { a as paiHomePath } from "./pai-home-Cm9rcJgX.mjs";
2
2
  import { y as writeJsonAtomic } from "./config-BbLFD7Uf.mjs";
3
- import { E as readWorkersSection } from "./run-env-BHWnXqle.mjs";
4
- import { O as worktreesDir, b as sessionMapPath, k as appendLedger, v as itermUuid } from "./run-DMaCpvMW.mjs";
5
- import { v as workersLogDir } from "./server-DlL1QI2k.mjs";
3
+ import { D as readWorkersSection } from "./run-env-DRcp7A8K.mjs";
4
+ import { N as worktreesDir, P as appendLedger, S as itermUuid, w as sessionMapPath } from "./run-BCpbxqA5.mjs";
5
+ import { v as workersLogDir } from "./server-DgmAHyFK.mjs";
6
6
  import { i as sendToSession, n as fetchLiveSessions } from "./aibroker-client-C5Fw7DNz.mjs";
7
7
  import { createReadStream, existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
8
  import { homedir } from "node:os";
@@ -579,4 +579,4 @@ async function runSessionKeepaliveTick(config, overrides = {}) {
579
579
 
580
580
  //#endregion
581
581
  export { totalUsageTokens as a, parseSessionUsage as i, runSessionKeepaliveTick as n, sessionKeepaliveLedgerSummary as r, loadSessionKeepaliveState as t };
582
- //# sourceMappingURL=session-keepalive-DVZ9-7qg.mjs.map
582
+ //# sourceMappingURL=session-keepalive-DxjTHGcK.mjs.map