@tekmidian/pai 0.41.0 → 0.42.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.
- package/dist/aibroker-client-BP74gGwD.mjs +215 -0
- package/dist/aibroker-client-BP74gGwD.mjs.map +1 -0
- package/dist/aibroker-client-C6m71p6O.mjs +3 -0
- package/dist/{auto-route-BwzmlW3W.mjs → auto-route-_5zzJz5q.mjs} +2 -2
- package/dist/{auto-route-BwzmlW3W.mjs.map → auto-route-_5zzJz5q.mjs.map} +1 -1
- package/dist/{chain-zMjCDW-R.mjs → chain-DsvSHnO9.mjs} +133 -361
- package/dist/chain-DsvSHnO9.mjs.map +1 -0
- package/dist/cli/index.mjs +12 -10
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +12 -10
- package/dist/{clusters-DRHhfli9.mjs → clusters-j0ZuNYp9.mjs} +1 -1
- package/dist/{clusters-DRHhfli9.mjs.map → clusters-j0ZuNYp9.mjs.map} +1 -1
- package/dist/{config-D1G9IFpn.mjs → config-B64vFg14.mjs} +3 -50
- package/dist/config-B64vFg14.mjs.map +1 -0
- package/dist/{config-Dl8lT4Lu.mjs → config-Bi_F6lXf.mjs} +32 -3
- package/dist/config-Bi_F6lXf.mjs.map +1 -0
- package/dist/{config-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
- package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache--bm6Siim.mjs} +28 -9
- package/dist/context-handover-cache--bm6Siim.mjs.map +1 -0
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BOAPyL_b.mjs +23 -0
- package/dist/{daemon-CpdVUnl_.mjs → daemon-DvXih78C.mjs} +359 -27
- package/dist/daemon-DvXih78C.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +10 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/detector-C-7KRi42.mjs +5 -0
- package/dist/{detector-smpCm7ku.mjs → detector-C73Nxm94.mjs} +1 -1
- package/dist/{detector-smpCm7ku.mjs.map → detector-C73Nxm94.mjs.map} +1 -1
- package/dist/{factory-Bc5EyN54.mjs → factory-DEb9NmHJ.mjs} +7 -7
- package/dist/{factory-Bc5EyN54.mjs.map → factory-DEb9NmHJ.mjs.map} +1 -1
- package/dist/factory-Dbo8yNPi.mjs +3 -0
- package/dist/{fallback-CgQ_x4I7.mjs → fallback-8bzmg6No.mjs} +6 -5
- package/dist/fallback-8bzmg6No.mjs.map +1 -0
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +13 -5
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +593 -0
- package/dist/hooks/worker-supervision.mjs.map +7 -0
- package/dist/{indexer-backend-rohVVKrA.mjs → indexer-backend-BPTzKFZU.mjs} +1 -1
- package/dist/{indexer-backend-rohVVKrA.mjs.map → indexer-backend-BPTzKFZU.mjs.map} +1 -1
- package/dist/{ipc-client-C7AVpsyv.mjs → ipc-client-BmypMNYk.mjs} +2 -2
- package/dist/{ipc-client-C7AVpsyv.mjs.map → ipc-client-BmypMNYk.mjs.map} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs → kg-entity-r8duqhi9.mjs} +1 -1
- package/dist/{kg-entity-DbOMPdF9.mjs.map → kg-entity-r8duqhi9.mjs.map} +1 -1
- package/dist/{latent-ideas-iNp5s55R.mjs → latent-ideas-CtSca4qG.mjs} +2 -2
- package/dist/{latent-ideas-iNp5s55R.mjs.map → latent-ideas-CtSca4qG.mjs.map} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs → link-boost-Bucnz3n8.mjs} +1 -1
- package/dist/{link-boost-NNzADHX_.mjs.map → link-boost-Bucnz3n8.mjs.map} +1 -1
- package/dist/main-resolver-CqppP_Bw.mjs +7 -0
- package/dist/{main-resolver-CM1IHbuu.mjs → main-resolver-Dip2VLB5.mjs} +4 -214
- package/dist/main-resolver-Dip2VLB5.mjs.map +1 -0
- package/dist/{neighborhood-AGL7oXQ2.mjs → neighborhood-CM7Jgb51.mjs} +1 -1
- package/dist/{neighborhood-AGL7oXQ2.mjs.map → neighborhood-CM7Jgb51.mjs.map} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs → note-context-DCwE2A-M.mjs} +1 -1
- package/dist/{note-context-BHwYpSFf.mjs.map → note-context-DCwE2A-M.mjs.map} +1 -1
- package/dist/{planner-DsmMRIZR.mjs → planner-ogy-Zy4O.mjs} +6 -5
- package/dist/{planner-DsmMRIZR.mjs.map → planner-ogy-Zy4O.mjs.map} +1 -1
- package/dist/{postgres-Dwxe290H.mjs → postgres-CtOzfoQU.mjs} +1 -1
- package/dist/{postgres-Dwxe290H.mjs.map → postgres-CtOzfoQU.mjs.map} +1 -1
- package/dist/{program-Bed-Pd-8.mjs → program-B7N5OIJR.mjs} +36 -33
- package/dist/{program-Bed-Pd-8.mjs.map → program-B7N5OIJR.mjs.map} +1 -1
- package/dist/query-feedback-DGoVRaWD.mjs +3 -0
- package/dist/{query-feedback-DZfjI34w.mjs → query-feedback-Dqz-HC__.mjs} +1 -1
- package/dist/{query-feedback-DZfjI34w.mjs.map → query-feedback-Dqz-HC__.mjs.map} +1 -1
- package/dist/router-BTIGZR2_.mjs +3 -0
- package/dist/{router-DHracTUC.mjs → router-DD6D4j31.mjs} +1 -1
- package/dist/{router-DHracTUC.mjs.map → router-DD6D4j31.mjs.map} +1 -1
- package/dist/runtime-paths-rni52zHX.mjs +50 -0
- package/dist/runtime-paths-rni52zHX.mjs.map +1 -0
- package/dist/scope-CfYnjy5R.mjs +360 -0
- package/dist/scope-CfYnjy5R.mjs.map +1 -0
- package/dist/{server-BOuAOj9b.mjs → server-DvN-IFIV.mjs} +42 -3
- package/dist/server-DvN-IFIV.mjs.map +1 -0
- package/dist/skills/Worker/SKILL.md +3 -0
- package/dist/{sources-ju6AA7lL.mjs → sources-CS43CpuS.mjs} +1 -1
- package/dist/{sources-ju6AA7lL.mjs.map → sources-CS43CpuS.mjs.map} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs → sqlite-Cq3gJcq9.mjs} +1 -1
- package/dist/{sqlite-kFZeWCJ2.mjs.map → sqlite-Cq3gJcq9.mjs.map} +1 -1
- package/dist/{state-ahFAbD8b.mjs → state-BTa10SOM.mjs} +1 -1
- package/dist/{state-ahFAbD8b.mjs.map → state-BTa10SOM.mjs.map} +1 -1
- package/dist/{state-DH_mp5Xe.mjs → state-RXepvbnO.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs → themes-B7h9YDiy.mjs} +1 -1
- package/dist/{themes-DqhNUUQk.mjs.map → themes-B7h9YDiy.mjs.map} +1 -1
- package/dist/{tools-COna9vZR.mjs → tools-BW3NLMPk.mjs} +1 -1
- package/dist/{tools-CMlHa9lZ.mjs → tools-LlzcV_eK.mjs} +14 -14
- package/dist/{tools-CMlHa9lZ.mjs.map → tools-LlzcV_eK.mjs.map} +1 -1
- package/dist/{trace-DTP6k4B_.mjs → trace-u4hT-pLD.mjs} +1 -1
- package/dist/{trace-DTP6k4B_.mjs.map → trace-u4hT-pLD.mjs.map} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs → vault-indexer-mata9S7b.mjs} +1 -1
- package/dist/{vault-indexer-YMH22mLU.mjs.map → vault-indexer-mata9S7b.mjs.map} +1 -1
- package/dist/work-queue-worker-19r0cpH4.mjs +14 -0
- package/dist/{work-queue-worker-B3nl8yyn.mjs → work-queue-worker-DXh_FnT5.mjs} +4 -4
- package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-DXh_FnT5.mjs.map} +1 -1
- package/dist/{zettelkasten-DDvKRm2V.mjs → zettelkasten-F0D_XfQ4.mjs} +3 -3
- package/dist/{zettelkasten-DDvKRm2V.mjs.map → zettelkasten-F0D_XfQ4.mjs.map} +1 -1
- package/docs/commands/worker.md +1 -1
- package/docs/merge-demo.md +1 -0
- package/docs/worker.md +40 -0
- package/package.json +1 -1
- package/plugins/productivity/hooks/hooks.json +5 -0
- package/src/hooks/ts/user-prompt/worker-supervision.ts +123 -0
- package/dist/chain-zMjCDW-R.mjs.map +0 -1
- package/dist/config-D1G9IFpn.mjs.map +0 -1
- package/dist/config-Dl8lT4Lu.mjs.map +0 -1
- package/dist/context-handover-cache-C6_rs7JT.mjs.map +0 -1
- package/dist/daemon-BSL9sXxm.mjs +0 -22
- package/dist/daemon-CpdVUnl_.mjs.map +0 -1
- package/dist/detector-BLVOCiJ_.mjs +0 -5
- package/dist/factory-C7ua_3ik.mjs +0 -3
- package/dist/fallback-CgQ_x4I7.mjs.map +0 -1
- package/dist/main-resolver-CM1IHbuu.mjs.map +0 -1
- package/dist/main-resolver-E5WlilhM.mjs +0 -6
- package/dist/query-feedback-CGcEiGtY.mjs +0 -3
- package/dist/router-CAtyVQud.mjs +0 -3
- package/dist/server-BOuAOj9b.mjs.map +0 -1
- package/dist/work-queue-worker-BM1GcAAq.mjs +0 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector-
|
|
1
|
+
{"version":3,"file":"detector-C73Nxm94.mjs","names":[],"sources":["../src/topics/detector.ts"],"sourcesContent":["/**\n * Topic shift detection engine.\n *\n * Accepts a context summary (recent conversation text) and determines whether\n * the conversation has drifted away from the currently-routed project.\n *\n * Algorithm:\n * 1. Run keyword memory_search against the context text (no project filter)\n * 2. Score results by project — sum of BM25 scores per project\n * 3. Compare the top-scoring project against the current project\n * 4. If a different project dominates by more than the confidence threshold,\n * report a topic shift.\n *\n * Design decisions:\n * - Keyword search only (no semantic) — fast, no embedding requirement\n * - Works with or without an active daemon (direct DB access path)\n * - Stateless: callers supply currentProject; detector has no session memory\n * - Minimal: returns a plain result object, not MCP content arrays\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport { searchMemory, populateSlugs } from \"../memory/search.js\";\nimport type { SearchResult } from \"../memory/search.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TopicCheckParams {\n /** Recent conversation context (a few sentences or tool call summaries) */\n context: string;\n /** The project slug the session is currently routed to. May be null/empty. */\n currentProject?: string;\n /**\n * Minimum confidence [0,1] to declare a shift. Default: 0.6.\n * Higher = less sensitive, fewer false positives.\n */\n threshold?: number;\n /**\n * Maximum results to draw from memory search (candidates). Default: 20.\n * More candidates = more accurate scoring, slightly slower.\n */\n candidates?: number;\n}\n\nexport interface TopicCheckResult {\n /** Whether a significant topic shift was detected. */\n shifted: boolean;\n /** The project slug the session is currently routed to (echoed from input). */\n currentProject: string | null;\n /** The project slug that best matches the context, or null if no clear match. */\n suggestedProject: string | null;\n /**\n * Confidence score for the suggested project [0,1].\n * Represents the fraction of total score mass held by the top project.\n * 1.0 = all matching chunks belong to one project.\n * 0.5 = two projects are equally matched.\n */\n confidence: number;\n /** Number of memory chunks that contributed to scoring. */\n chunkCount: number;\n /** Top-3 scoring projects with their normalised scores (for debugging). */\n topProjects: Array<{ slug: string; score: number }>;\n}\n\n// ---------------------------------------------------------------------------\n// Core algorithm\n// ---------------------------------------------------------------------------\n\n/**\n * Detect whether the provided context text best matches a different project\n * than the session's current routing.\n *\n * Works with either a raw SQLite Database or a StorageBackend.\n * For the StorageBackend path, keyword search is used.\n * For the raw Database path (legacy/direct), searchMemory() is called.\n */\nexport async function detectTopicShift(\n registryDb: Database,\n federation: Database | StorageBackend,\n params: TopicCheckParams\n): Promise<TopicCheckResult> {\n const threshold = params.threshold ?? 0.6;\n const candidates = params.candidates ?? 20;\n const currentProject = params.currentProject?.trim() || null;\n\n if (!params.context || params.context.trim().length === 0) {\n return {\n shifted: false,\n currentProject,\n suggestedProject: null,\n confidence: 0,\n chunkCount: 0,\n topProjects: [],\n };\n }\n\n // -------------------------------------------------------------------------\n // Run memory search across ALL projects (no project filter)\n // -------------------------------------------------------------------------\n\n let results: SearchResult[];\n\n const isBackend = (x: Database | StorageBackend): x is StorageBackend =>\n \"backendType\" in x;\n\n if (isBackend(federation)) {\n results = await federation.searchKeyword(params.context, {\n maxResults: candidates,\n });\n } else {\n results = searchMemory(federation, params.context, {\n maxResults: candidates,\n });\n }\n\n if (results.length === 0) {\n return {\n shifted: false,\n currentProject,\n suggestedProject: null,\n confidence: 0,\n chunkCount: 0,\n topProjects: [],\n };\n }\n\n // Populate project slugs from the registry\n const withSlugs = populateSlugs(results, registryDb);\n\n // -------------------------------------------------------------------------\n // Score projects by summing BM25 scores of matching chunks\n // -------------------------------------------------------------------------\n\n const projectScores = new Map<string, number>();\n\n for (const r of withSlugs) {\n const slug = r.projectSlug;\n if (!slug) continue;\n projectScores.set(slug, (projectScores.get(slug) ?? 0) + r.score);\n }\n\n if (projectScores.size === 0) {\n return {\n shifted: false,\n currentProject,\n suggestedProject: null,\n confidence: 0,\n chunkCount: withSlugs.length,\n topProjects: [],\n };\n }\n\n // Sort by total score descending\n const ranked = Array.from(projectScores.entries())\n .sort((a, b) => b[1] - a[1]);\n\n const totalScore = ranked.reduce((sum, [, s]) => sum + s, 0);\n\n // Top-3 for reporting (normalised to [0,1] fraction of total mass)\n const topProjects = ranked.slice(0, 3).map(([slug, score]) => ({\n slug,\n score: totalScore > 0 ? score / totalScore : 0,\n }));\n\n const topSlug = ranked[0][0];\n const topRawScore = ranked[0][1];\n const confidence = totalScore > 0 ? topRawScore / totalScore : 0;\n\n // -------------------------------------------------------------------------\n // Determine if a shift occurred\n // -------------------------------------------------------------------------\n\n // A shift is detected when:\n // 1. confidence >= threshold (the top project dominates)\n // 2. The top project is different from currentProject\n // 3. There is a currentProject to compare against\n // (if no current project, we still return the best match but no \"shift\")\n\n const isDifferent =\n currentProject !== null &&\n topSlug !== currentProject;\n\n const shifted = isDifferent && confidence >= threshold;\n\n return {\n shifted,\n currentProject,\n suggestedProject: topSlug,\n confidence,\n chunkCount: withSlugs.length,\n topProjects,\n };\n}\n"],"mappings":";;;;;;;;;;;AA8EA,eAAsB,iBACpB,YACA,YACA,QAC2B;CAC3B,MAAM,YAAY,OAAO,aAAa;CACtC,MAAM,aAAa,OAAO,cAAc;CACxC,MAAM,iBAAiB,OAAO,gBAAgB,MAAM,IAAI;AAExD,KAAI,CAAC,OAAO,WAAW,OAAO,QAAQ,MAAM,CAAC,WAAW,EACtD,QAAO;EACL,SAAS;EACT;EACA,kBAAkB;EAClB,YAAY;EACZ,YAAY;EACZ,aAAa,EAAE;EAChB;CAOH,IAAI;CAEJ,MAAM,aAAa,MACjB,iBAAiB;AAEnB,KAAI,UAAU,WAAW,CACvB,WAAU,MAAM,WAAW,cAAc,OAAO,SAAS,EACvD,YAAY,YACb,CAAC;KAEF,WAAU,aAAa,YAAY,OAAO,SAAS,EACjD,YAAY,YACb,CAAC;AAGJ,KAAI,QAAQ,WAAW,EACrB,QAAO;EACL,SAAS;EACT;EACA,kBAAkB;EAClB,YAAY;EACZ,YAAY;EACZ,aAAa,EAAE;EAChB;CAIH,MAAM,YAAY,cAAc,SAAS,WAAW;CAMpD,MAAM,gCAAgB,IAAI,KAAqB;AAE/C,MAAK,MAAM,KAAK,WAAW;EACzB,MAAM,OAAO,EAAE;AACf,MAAI,CAAC,KAAM;AACX,gBAAc,IAAI,OAAO,cAAc,IAAI,KAAK,IAAI,KAAK,EAAE,MAAM;;AAGnE,KAAI,cAAc,SAAS,EACzB,QAAO;EACL,SAAS;EACT;EACA,kBAAkB;EAClB,YAAY;EACZ,YAAY,UAAU;EACtB,aAAa,EAAE;EAChB;CAIH,MAAM,SAAS,MAAM,KAAK,cAAc,SAAS,CAAC,CAC/C,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;CAE9B,MAAM,aAAa,OAAO,QAAQ,KAAK,GAAG,OAAO,MAAM,GAAG,EAAE;CAG5D,MAAM,cAAc,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,YAAY;EAC7D;EACA,OAAO,aAAa,IAAI,QAAQ,aAAa;EAC9C,EAAE;CAEH,MAAM,UAAU,OAAO,GAAG;CAC1B,MAAM,cAAc,OAAO,GAAG;CAC9B,MAAM,aAAa,aAAa,IAAI,cAAc,aAAa;AAkB/D,QAAO;EACL,SANA,mBAAmB,QACnB,YAAY,kBAEiB,cAAc;EAI3C;EACA,kBAAkB;EAClB;EACA,YAAY,UAAU;EACtB;EACD"}
|
|
@@ -56,7 +56,7 @@ async function createStorageBackend(config, opts = {}) {
|
|
|
56
56
|
* backend on success, or an error string describing why it failed.
|
|
57
57
|
*/
|
|
58
58
|
async function attemptPostgres(config) {
|
|
59
|
-
const { PostgresBackend } = await import("./postgres-
|
|
59
|
+
const { PostgresBackend } = await import("./postgres-CtOzfoQU.mjs");
|
|
60
60
|
const pgConfig = config.postgres ?? {};
|
|
61
61
|
let backend = null;
|
|
62
62
|
try {
|
|
@@ -85,8 +85,8 @@ const ESCALATE_AFTER_ATTEMPTS = 5;
|
|
|
85
85
|
/** Tell the user the backend is down, through whatever channels are configured. */
|
|
86
86
|
async function notifyBackendDown(attempts, lastError) {
|
|
87
87
|
try {
|
|
88
|
-
const { routeNotification } = await import("./router-
|
|
89
|
-
const { loadConfig } = await import("./config-
|
|
88
|
+
const { routeNotification } = await import("./router-BTIGZR2_.mjs");
|
|
89
|
+
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
90
90
|
await routeNotification({
|
|
91
91
|
event: "error",
|
|
92
92
|
title: "PAI: storage backend unreachable",
|
|
@@ -97,8 +97,8 @@ async function notifyBackendDown(attempts, lastError) {
|
|
|
97
97
|
/** And say when it comes back, so the alert is not left hanging. */
|
|
98
98
|
async function notifyBackendRecovered(attempts, since) {
|
|
99
99
|
try {
|
|
100
|
-
const { routeNotification } = await import("./router-
|
|
101
|
-
const { loadConfig } = await import("./config-
|
|
100
|
+
const { routeNotification } = await import("./router-BTIGZR2_.mjs");
|
|
101
|
+
const { loadConfig } = await import("./config-C_ErGddD.mjs");
|
|
102
102
|
const mins = since ? Math.max(1, Math.round((Date.now() - since) / 6e4)) : null;
|
|
103
103
|
await routeNotification({
|
|
104
104
|
event: "completion",
|
|
@@ -141,10 +141,10 @@ async function connectPostgres(config, waitForever) {
|
|
|
141
141
|
}
|
|
142
142
|
async function createSQLiteBackend() {
|
|
143
143
|
const { openFederation } = await import("./db-O-cyAPfS.mjs");
|
|
144
|
-
const { SQLiteBackend } = await import("./sqlite-
|
|
144
|
+
const { SQLiteBackend } = await import("./sqlite-Cq3gJcq9.mjs");
|
|
145
145
|
return new SQLiteBackend(openFederation());
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
//#endregion
|
|
149
149
|
export { getBackendOutage as n, humanDuration as r, createStorageBackend as t };
|
|
150
|
-
//# sourceMappingURL=factory-
|
|
150
|
+
//# sourceMappingURL=factory-DEb9NmHJ.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-Bc5EyN54.mjs","names":[],"sources":["../src/storage/outage.ts","../src/storage/factory.ts"],"sourcesContent":["/**\n * outage.ts — is the storage backend actually reachable right now?\n *\n * The daemon retries a dead backend forever, which is correct: a Postgres\n * container that is down will usually come back, and giving up would lose the\n * work queue. What was wrong is that it did so in complete silence.\n *\n * Observed 2026-07-26: the container was down for roughly two days. The daemon\n * logged \"Postgres unavailable\" 144 times over 36 minutes, the work queue backed\n * up, session notes for the whole period were never written — and\n * `pai daemon status` reported \"Index: idle\" throughout. The one command anyone\n * would run to check said everything was fine.\n *\n * So the outage is recorded where the status command can see it, and escalated\n * once through the notification channels that were already configured and\n * already unused for this.\n */\n\nexport interface BackendOutage {\n backend: string;\n /** When the current run of failures began. */\n since: number;\n /** Consecutive failed attempts so far. */\n attempts: number;\n lastError: string;\n}\n\nlet current: BackendOutage | null = null;\n\n/** Record that the backend is currently unreachable. */\nexport function setBackendOutage(outage: BackendOutage): void {\n current = outage;\n}\n\n/**\n * Record that the backend answered.\n *\n * Called on every successful connection, including the first — so a daemon that\n * never had a problem reports none, and one that recovered stops reporting an\n * outage that has ended.\n */\nexport function clearBackendOutage(): void {\n current = null;\n}\n\n/** The current outage, or null when the backend is answering. */\nexport function getBackendOutage(): BackendOutage | null {\n return current;\n}\n\n/**\n * Human-readable elapsed time, for a status line rather than a log.\n *\n * Exported because the status command needs the duration on its own, to colour\n * it separately from the rest of the sentence. Without this it kept a second\n * copy of the same rounding, which is how two renderings of one outage drift.\n */\nexport function humanDuration(ms: number): string {\n const mins = Math.max(1, Math.round(ms / 60_000));\n return mins < 60 ? `${mins} min` : `${(mins / 60).toFixed(1)} h`;\n}\n\n/** Human-readable duration, for a status line rather than a log. */\nexport function describeOutage(o: BackendOutage, now = Date.now()): string {\n return `${o.backend} unreachable for ${humanDuration(now - o.since)}, ${o.attempts} attempts — ${o.lastError}`;\n}\n","/**\n * Storage backend factory.\n *\n * Reads the daemon config and returns the appropriate StorageBackend.\n *\n * When Postgres is the configured backend we NEVER silently fall back to\n * SQLite — doing so would split the corpus across two databases. Instead:\n * - Daemon (waitForPostgres: true) retries Postgres forever with capped\n * backoff until it comes up (handles the boot race where launchd starts\n * the daemon before Docker Desktop / Postgres is ready).\n * - CLI / one-shot callers (default) retry a few times, then throw a clear\n * error rather than returning a wrong/empty SQLite database.\n */\n\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport type { StorageBackend } from \"./interface.js\";\nimport { setBackendOutage, clearBackendOutage } from \"./outage.js\";\n\nexport interface StorageBackendOptions {\n /**\n * When true, retry Postgres indefinitely instead of giving up. Used by the\n * long-lived daemon so a not-yet-ready Postgres at boot is tolerated.\n * Defaults to false (one-shot CLI behaviour: bounded retries, then throw).\n */\n waitForPostgres?: boolean;\n}\n\n/** Backoff schedule (ms) for the bounded CLI retry path. */\nconst CLI_RETRY_DELAYS_MS = [500, 1_000, 2_000];\n\n/** Backoff cap (ms) for the daemon's infinite retry path. */\nconst DAEMON_RETRY_CAP_MS = 15_000;\n\n/**\n * Create and return the configured StorageBackend.\n *\n * Auto-behaviour:\n * - storageBackend = \"sqlite\" → SQLiteBackend always\n * - storageBackend = \"postgres\" → PostgresBackend (retried; never falls back)\n */\nexport async function createStorageBackend(\n config: PaiDaemonConfig,\n opts: StorageBackendOptions = {}\n): Promise<StorageBackend> {\n if (config.storageBackend === \"postgres\") {\n return await connectPostgres(config, opts.waitForPostgres ?? false);\n }\n\n // Default: SQLite\n return createSQLiteBackend();\n}\n\n/**\n * Attempt a single Postgres connection (ensure DB + test). Returns the live\n * backend on success, or an error string describing why it failed.\n */\nasync function attemptPostgres(\n config: PaiDaemonConfig\n): Promise<{ backend: StorageBackend } | { error: string }> {\n const { PostgresBackend } = await import(\"./postgres.js\");\n const pgConfig = config.postgres ?? {};\n\n let backend: InstanceType<typeof PostgresBackend> | null = null;\n try {\n // Ensure the per-user database exists and has the schema applied.\n await PostgresBackend.ensureDatabase(pgConfig);\n\n backend = new PostgresBackend(pgConfig);\n const err = await backend.testConnection();\n if (err) {\n await backend.close().catch(() => {});\n return { error: err };\n }\n return { backend };\n } catch (e) {\n if (backend) await backend.close().catch(() => {});\n return { error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/**\n * Consecutive failures before the outage is escalated to the user.\n *\n * Not the first failure: a container restarting, or the daemon starting before\n * Docker is up, recovers within a few seconds and is not worth a notification.\n * By the fifth attempt the backoff has already spent tens of seconds, which is\n * long enough that something is actually wrong.\n */\nconst ESCALATE_AFTER_ATTEMPTS = 5;\n\n/** Tell the user the backend is down, through whatever channels are configured. */\nasync function notifyBackendDown(attempts: number, lastError: string): Promise<void> {\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n await routeNotification(\n {\n event: \"error\",\n title: \"PAI: storage backend unreachable\",\n message:\n `Postgres has not answered in ${attempts} attempts (${lastError}). ` +\n `Indexing, session notes and the work queue are stalled until it returns. ` +\n `Check the container, then \\`pai daemon status\\`.`,\n },\n loadConfig().notifications\n );\n } catch {\n // A notification that cannot be sent must never take the daemon down with\n // it — the daemon retrying is still the useful behaviour here.\n }\n}\n\n/** And say when it comes back, so the alert is not left hanging. */\nasync function notifyBackendRecovered(\n attempts: number,\n since: number | null\n): Promise<void> {\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n const mins = since ? Math.max(1, Math.round((Date.now() - since) / 60_000)) : null;\n await routeNotification(\n {\n event: \"completion\",\n title: \"PAI: storage backend back\",\n message:\n `Postgres answered after ${attempts} attempts` +\n (mins ? `, ${mins} min down` : \"\") +\n `. The queue will drain on its own.`,\n },\n loadConfig().notifications\n );\n } catch {\n /* same reasoning as above */\n }\n}\n\nasync function connectPostgres(\n config: PaiDaemonConfig,\n waitForever: boolean\n): Promise<StorageBackend> {\n let attempt = 0;\n let lastError = \"unknown error\";\n let outageSince: number | null = null;\n let escalated = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n attempt++;\n const result = await attemptPostgres(config);\n if (\"backend\" in result) {\n if (attempt > 1) {\n process.stderr.write(\n `[pai-daemon] Connected to PostgreSQL backend (after ${attempt} attempts).\\n`\n );\n } else {\n process.stderr.write(\"[pai-daemon] Connected to PostgreSQL backend.\\n\");\n }\n // An outage that ended must stop being reported, or the status command\n // trades one wrong answer for another.\n clearBackendOutage();\n if (escalated) void notifyBackendRecovered(attempt, outageSince);\n return result.backend;\n }\n\n lastError = result.error;\n\n if (!waitForever && attempt > CLI_RETRY_DELAYS_MS.length) {\n // Bounded CLI path exhausted — fail loudly, never silently use SQLite.\n throw new Error(\n `Postgres backend unreachable after ${attempt} attempts: ${lastError}. ` +\n `Is Docker Desktop / Postgres running? Refusing to fall back to SQLite ` +\n `(would split the corpus). Start Postgres and retry.`\n );\n }\n\n const delayMs = waitForever\n ? Math.min(DAEMON_RETRY_CAP_MS, 1_000 * 2 ** Math.min(attempt - 1, 4))\n : CLI_RETRY_DELAYS_MS[attempt - 1];\n\n process.stderr.write(\n `[pai-daemon] Postgres unavailable (${lastError}). ` +\n `Retry ${attempt}${waitForever ? \"\" : `/${CLI_RETRY_DELAYS_MS.length + 1}`} ` +\n `in ${delayMs}ms...\\n`\n );\n\n // Publish the outage so `pai daemon status` can report it. Without this the\n // daemon retries silently forever and status still reads \"idle\" — which is\n // what happened for two days in July: 144 retries over 36 minutes, session\n // notes never written, and the one command anyone would run to check\n // reporting that everything was fine.\n setBackendOutage({\n backend: \"postgres\",\n since: outageSince ?? (outageSince = Date.now()),\n attempts: attempt,\n lastError: String(lastError),\n });\n\n // And escalate once, out loud, rather than only into a log nobody tails.\n // Once — not per retry — because a notification that repeats every few\n // seconds is filtered within a minute and stops being a signal at all.\n if (waitForever && attempt === ESCALATE_AFTER_ATTEMPTS && !escalated) {\n escalated = true;\n void notifyBackendDown(attempt, String(lastError));\n }\n\n await new Promise((r) => setTimeout(r, delayMs));\n }\n}\n\nasync function createSQLiteBackend(): Promise<StorageBackend> {\n const { openFederation } = await import(\"../memory/db.js\");\n const { SQLiteBackend } = await import(\"./sqlite.js\");\n const db = openFederation();\n return new SQLiteBackend(db);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"factory-DEb9NmHJ.mjs","names":[],"sources":["../src/storage/outage.ts","../src/storage/factory.ts"],"sourcesContent":["/**\n * outage.ts — is the storage backend actually reachable right now?\n *\n * The daemon retries a dead backend forever, which is correct: a Postgres\n * container that is down will usually come back, and giving up would lose the\n * work queue. What was wrong is that it did so in complete silence.\n *\n * Observed 2026-07-26: the container was down for roughly two days. The daemon\n * logged \"Postgres unavailable\" 144 times over 36 minutes, the work queue backed\n * up, session notes for the whole period were never written — and\n * `pai daemon status` reported \"Index: idle\" throughout. The one command anyone\n * would run to check said everything was fine.\n *\n * So the outage is recorded where the status command can see it, and escalated\n * once through the notification channels that were already configured and\n * already unused for this.\n */\n\nexport interface BackendOutage {\n backend: string;\n /** When the current run of failures began. */\n since: number;\n /** Consecutive failed attempts so far. */\n attempts: number;\n lastError: string;\n}\n\nlet current: BackendOutage | null = null;\n\n/** Record that the backend is currently unreachable. */\nexport function setBackendOutage(outage: BackendOutage): void {\n current = outage;\n}\n\n/**\n * Record that the backend answered.\n *\n * Called on every successful connection, including the first — so a daemon that\n * never had a problem reports none, and one that recovered stops reporting an\n * outage that has ended.\n */\nexport function clearBackendOutage(): void {\n current = null;\n}\n\n/** The current outage, or null when the backend is answering. */\nexport function getBackendOutage(): BackendOutage | null {\n return current;\n}\n\n/**\n * Human-readable elapsed time, for a status line rather than a log.\n *\n * Exported because the status command needs the duration on its own, to colour\n * it separately from the rest of the sentence. Without this it kept a second\n * copy of the same rounding, which is how two renderings of one outage drift.\n */\nexport function humanDuration(ms: number): string {\n const mins = Math.max(1, Math.round(ms / 60_000));\n return mins < 60 ? `${mins} min` : `${(mins / 60).toFixed(1)} h`;\n}\n\n/** Human-readable duration, for a status line rather than a log. */\nexport function describeOutage(o: BackendOutage, now = Date.now()): string {\n return `${o.backend} unreachable for ${humanDuration(now - o.since)}, ${o.attempts} attempts — ${o.lastError}`;\n}\n","/**\n * Storage backend factory.\n *\n * Reads the daemon config and returns the appropriate StorageBackend.\n *\n * When Postgres is the configured backend we NEVER silently fall back to\n * SQLite — doing so would split the corpus across two databases. Instead:\n * - Daemon (waitForPostgres: true) retries Postgres forever with capped\n * backoff until it comes up (handles the boot race where launchd starts\n * the daemon before Docker Desktop / Postgres is ready).\n * - CLI / one-shot callers (default) retry a few times, then throw a clear\n * error rather than returning a wrong/empty SQLite database.\n */\n\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport type { StorageBackend } from \"./interface.js\";\nimport { setBackendOutage, clearBackendOutage } from \"./outage.js\";\n\nexport interface StorageBackendOptions {\n /**\n * When true, retry Postgres indefinitely instead of giving up. Used by the\n * long-lived daemon so a not-yet-ready Postgres at boot is tolerated.\n * Defaults to false (one-shot CLI behaviour: bounded retries, then throw).\n */\n waitForPostgres?: boolean;\n}\n\n/** Backoff schedule (ms) for the bounded CLI retry path. */\nconst CLI_RETRY_DELAYS_MS = [500, 1_000, 2_000];\n\n/** Backoff cap (ms) for the daemon's infinite retry path. */\nconst DAEMON_RETRY_CAP_MS = 15_000;\n\n/**\n * Create and return the configured StorageBackend.\n *\n * Auto-behaviour:\n * - storageBackend = \"sqlite\" → SQLiteBackend always\n * - storageBackend = \"postgres\" → PostgresBackend (retried; never falls back)\n */\nexport async function createStorageBackend(\n config: PaiDaemonConfig,\n opts: StorageBackendOptions = {}\n): Promise<StorageBackend> {\n if (config.storageBackend === \"postgres\") {\n return await connectPostgres(config, opts.waitForPostgres ?? false);\n }\n\n // Default: SQLite\n return createSQLiteBackend();\n}\n\n/**\n * Attempt a single Postgres connection (ensure DB + test). Returns the live\n * backend on success, or an error string describing why it failed.\n */\nasync function attemptPostgres(\n config: PaiDaemonConfig\n): Promise<{ backend: StorageBackend } | { error: string }> {\n const { PostgresBackend } = await import(\"./postgres.js\");\n const pgConfig = config.postgres ?? {};\n\n let backend: InstanceType<typeof PostgresBackend> | null = null;\n try {\n // Ensure the per-user database exists and has the schema applied.\n await PostgresBackend.ensureDatabase(pgConfig);\n\n backend = new PostgresBackend(pgConfig);\n const err = await backend.testConnection();\n if (err) {\n await backend.close().catch(() => {});\n return { error: err };\n }\n return { backend };\n } catch (e) {\n if (backend) await backend.close().catch(() => {});\n return { error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/**\n * Consecutive failures before the outage is escalated to the user.\n *\n * Not the first failure: a container restarting, or the daemon starting before\n * Docker is up, recovers within a few seconds and is not worth a notification.\n * By the fifth attempt the backoff has already spent tens of seconds, which is\n * long enough that something is actually wrong.\n */\nconst ESCALATE_AFTER_ATTEMPTS = 5;\n\n/** Tell the user the backend is down, through whatever channels are configured. */\nasync function notifyBackendDown(attempts: number, lastError: string): Promise<void> {\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n await routeNotification(\n {\n event: \"error\",\n title: \"PAI: storage backend unreachable\",\n message:\n `Postgres has not answered in ${attempts} attempts (${lastError}). ` +\n `Indexing, session notes and the work queue are stalled until it returns. ` +\n `Check the container, then \\`pai daemon status\\`.`,\n },\n loadConfig().notifications\n );\n } catch {\n // A notification that cannot be sent must never take the daemon down with\n // it — the daemon retrying is still the useful behaviour here.\n }\n}\n\n/** And say when it comes back, so the alert is not left hanging. */\nasync function notifyBackendRecovered(\n attempts: number,\n since: number | null\n): Promise<void> {\n try {\n const { routeNotification } = await import(\"../notifications/router.js\");\n const { loadConfig } = await import(\"../daemon/config.js\");\n const mins = since ? Math.max(1, Math.round((Date.now() - since) / 60_000)) : null;\n await routeNotification(\n {\n event: \"completion\",\n title: \"PAI: storage backend back\",\n message:\n `Postgres answered after ${attempts} attempts` +\n (mins ? `, ${mins} min down` : \"\") +\n `. The queue will drain on its own.`,\n },\n loadConfig().notifications\n );\n } catch {\n /* same reasoning as above */\n }\n}\n\nasync function connectPostgres(\n config: PaiDaemonConfig,\n waitForever: boolean\n): Promise<StorageBackend> {\n let attempt = 0;\n let lastError = \"unknown error\";\n let outageSince: number | null = null;\n let escalated = false;\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n attempt++;\n const result = await attemptPostgres(config);\n if (\"backend\" in result) {\n if (attempt > 1) {\n process.stderr.write(\n `[pai-daemon] Connected to PostgreSQL backend (after ${attempt} attempts).\\n`\n );\n } else {\n process.stderr.write(\"[pai-daemon] Connected to PostgreSQL backend.\\n\");\n }\n // An outage that ended must stop being reported, or the status command\n // trades one wrong answer for another.\n clearBackendOutage();\n if (escalated) void notifyBackendRecovered(attempt, outageSince);\n return result.backend;\n }\n\n lastError = result.error;\n\n if (!waitForever && attempt > CLI_RETRY_DELAYS_MS.length) {\n // Bounded CLI path exhausted — fail loudly, never silently use SQLite.\n throw new Error(\n `Postgres backend unreachable after ${attempt} attempts: ${lastError}. ` +\n `Is Docker Desktop / Postgres running? Refusing to fall back to SQLite ` +\n `(would split the corpus). Start Postgres and retry.`\n );\n }\n\n const delayMs = waitForever\n ? Math.min(DAEMON_RETRY_CAP_MS, 1_000 * 2 ** Math.min(attempt - 1, 4))\n : CLI_RETRY_DELAYS_MS[attempt - 1];\n\n process.stderr.write(\n `[pai-daemon] Postgres unavailable (${lastError}). ` +\n `Retry ${attempt}${waitForever ? \"\" : `/${CLI_RETRY_DELAYS_MS.length + 1}`} ` +\n `in ${delayMs}ms...\\n`\n );\n\n // Publish the outage so `pai daemon status` can report it. Without this the\n // daemon retries silently forever and status still reads \"idle\" — which is\n // what happened for two days in July: 144 retries over 36 minutes, session\n // notes never written, and the one command anyone would run to check\n // reporting that everything was fine.\n setBackendOutage({\n backend: \"postgres\",\n since: outageSince ?? (outageSince = Date.now()),\n attempts: attempt,\n lastError: String(lastError),\n });\n\n // And escalate once, out loud, rather than only into a log nobody tails.\n // Once — not per retry — because a notification that repeats every few\n // seconds is filtered within a minute and stops being a signal at all.\n if (waitForever && attempt === ESCALATE_AFTER_ATTEMPTS && !escalated) {\n escalated = true;\n void notifyBackendDown(attempt, String(lastError));\n }\n\n await new Promise((r) => setTimeout(r, delayMs));\n }\n}\n\nasync function createSQLiteBackend(): Promise<StorageBackend> {\n const { openFederation } = await import(\"../memory/db.js\");\n const { SQLiteBackend } = await import(\"./sqlite.js\");\n const db = openFederation();\n return new SQLiteBackend(db);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as workersLogDir, a as clearCooldown, c as probeQuota,
|
|
3
|
-
import {
|
|
1
|
+
import { S as writeJsonAtomic, _ as writeWorkersSection, a as WorkersConfigError, d as keysDir, f as parseWorkersConfig, g as resolveModelCapability, h as readWorkersSection, l as expandHome, m as providerKeyPath, n as MODEL_CAPABILITIES, p as providerCostTier, r as PROVIDER_TAGS, t as DEFAULT_LOG_DIR, u as isModelCapability, x as readJsonStrict } from "./config-Bi_F6lXf.mjs";
|
|
2
|
+
import { _ as workersLogDir, a as clearCooldown, c as probeQuota, l as quotaSkipThreshold, p as eventsPath, u as resolveTarget } from "./server-DvN-IFIV.mjs";
|
|
3
|
+
import { S as shortText, _ as loadStatuses, c as workerInScope, d as alive, f as contextLabel, h as isChatPane, i as resolveSession, p as contextPercent, s as sessionTag, t as currentTabKey, u as ageOf } from "./scope-CfYnjy5R.mjs";
|
|
4
|
+
import { c as renderReport, f as readInbox, m as workerDepth, p as sayToWorker, s as parseWorkerReport } from "./chain-DsvSHnO9.mjs";
|
|
4
5
|
import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
7
|
import { basename, join, relative } from "node:path";
|
|
@@ -583,7 +584,7 @@ function stepOf(last) {
|
|
|
583
584
|
*/
|
|
584
585
|
function renderStatusLine(mine, now = /* @__PURE__ */ new Date()) {
|
|
585
586
|
if (!mine.length) return "";
|
|
586
|
-
const isChat =
|
|
587
|
+
const isChat = isChatPane;
|
|
587
588
|
const chat = mine.find((s) => isChat(s) && s.state === "running" && alive(s.pid));
|
|
588
589
|
const running = mine.filter((s) => !isChat(s) && s.state === "running" && alive(s.pid)).sort((a, b) => a.started.localeCompare(b.started));
|
|
589
590
|
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
@@ -1762,4 +1763,4 @@ function fallbackStatusText(s) {
|
|
|
1762
1763
|
|
|
1763
1764
|
//#endregion
|
|
1764
1765
|
export { statusLineOutput as S, updateProvider as _, addProvider as a, psOutput as b, describeProviders as c, resolveProviderName as d, setClass as f, unsetClass as g, setWorkersEnabled as h, fallbackStatusText as i, modelPrefsText as l, setProviderModel as m, fallbackOn as n, classTargetText as o, setProviderEnabled as p, fallbackStatus as r, describeModels as s, fallbackOff as t, removeProvider as u, useProvider as v, replayOutput as x, followWorkers as y };
|
|
1765
|
-
//# sourceMappingURL=fallback-
|
|
1766
|
+
//# sourceMappingURL=fallback-8bzmg6No.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback-8bzmg6No.mjs","names":[],"sources":["../src/workers/chatui.ts","../src/workers/render.ts","../src/workers/viewer.ts","../src/workers/providers.ts","../src/workers/fallback.ts"],"sourcesContent":["/**\n * chatui.ts — the chat line of a `follow` pane.\n *\n * A follow pane with a target behaves like a small chat, Claude Code style:\n * the transcript lives in a terminal scroll region that ends three rows above\n * the pane's bottom; below it a separator rule row, then the two fixed rows —\n * the prompt row (`› `, readline line editing) and the ticker row. A transcript\n * line is inserted above the fixed rows with a save-cursor / scroll-region /\n * restore-cursor write that never touches them; the scroll region makes the\n * transcript roll inside itself. Everything here builds strings (or parses one\n * line), so the tests assert exact byte sequences — no terminal needed.\n */\n\n/** The prompt marker of the chat row. */\nexport const CHAT_PROMPT = \"› \";\n\n/** Dim hint shown once behind the cursor until the first line is typed. */\nexport const CHAT_HINT = \"type here and press Enter · /help for commands\";\n\n/** What `/help` prints (one command per line, dim). */\nexport const CHAT_HELP = [\n \"/quit close this pane\",\n \"/resume <text> continue the finished worker with <text>\",\n \"/status one-line worker status\",\n \"anything else is sent to the worker — said while it runs, resumed after\",\n];\n\n// ---------------------------------------------------------------------------\n// visible width & wrapping (the gutter must stay the leftmost column)\n// ---------------------------------------------------------------------------\n\n/** Index just past the escape starting at `i` (CSI, OSC or a two-char one). */\nfunction endOfEscape(s: string, i: number): number {\n const n = s[i + 1];\n if (n === \"[\") {\n let j = i + 2;\n while (j < s.length && !(s[j]! >= \"@\" && s[j]! <= \"~\")) j++;\n return Math.min(s.length, j + 1);\n }\n if (n === \"]\") {\n let j = i + 2;\n while (j < s.length && s[j] !== \"\\x07\") j++;\n return Math.min(s.length, j + 1);\n }\n return i + 2;\n}\n\n/** Printable columns of `s` — ANSI escape sequences measure zero. */\nexport function visibleWidth(s: string): number {\n let w = 0;\n let i = 0;\n while (i < s.length) {\n if (s[i] === \"\\x1b\") {\n i = endOfEscape(s, i);\n continue;\n }\n w += 1;\n i += 1;\n }\n return w;\n}\n\n/**\n * The SGR sequences in effect at `upto`: everything opened since the last\n * reset, in order. Anything that is not an SGR escape is ignored (it does\n * not change colour state).\n */\nfunction sgrStateAt(text: string, upto: number): string[] {\n const open: string[] = [];\n let i = 0;\n while (i < Math.min(upto, text.length)) {\n if (text[i] === \"\\x1b\") {\n const end = endOfEscape(text, i);\n const esc = text.slice(i, end);\n if (/^\\x1b\\[[0-9;]*m$/.test(esc)) {\n const params = esc.slice(2, -1);\n const resets = params === \"\" || params.split(\";\").includes(\"0\");\n if (resets) open.length = 0;\n if (!(params === \"\" || params === \"0\")) open.push(esc);\n }\n i = end;\n continue;\n }\n i += 1;\n }\n return open;\n}\n\n/**\n * Wrap one rendered row to `width` printable columns. Breaks on whitespace\n * where possible, hard-wraps words longer than the width, never splits an\n * ANSI escape, and re-opens the colours it wraps inside of, so a diff row\n * keeps its `-`/`+` colour on every continuation row.\n */\nexport function wrapText(text: string, width: number): string[] {\n if (width < 1 || visibleWidth(text) <= width) return [text];\n\n // visible characters by their index in `text`\n const chars: number[] = [];\n for (let i = 0; i < text.length; i++) {\n if (text[i] === \"\\x1b\") {\n i = endOfEscape(text, i) - 1;\n continue;\n }\n chars.push(i);\n }\n\n // words as [first, last] indexes into `chars` (escapes ride along later)\n const words: Array<{ s: number; e: number; w: number }> = [];\n {\n let s = -1;\n let w = 0;\n for (let k = 0; k <= chars.length; k++) {\n const ch = k === chars.length ? \" \" : text[chars[k]!]!;\n if (ch === \" \") {\n if (s >= 0) {\n words.push({ s, e: k - 1, w });\n s = -1;\n w = 0;\n }\n } else {\n if (s < 0) s = k;\n w++;\n }\n }\n }\n // a leading indent (diff rows) belongs to the first word, width counted\n if (words.length && words[0]!.s > 0) {\n words[0] = { s: 0, e: words[0]!.e, w: words[0]!.w + words[0]!.s };\n }\n\n // chunk spans [start, end] over `chars`, greedy on visible width — the\n // width of a chunk is simply its span in `chars`, inner spaces included\n const spans: Array<[number, number]> = [];\n let cs = -1;\n let ce = -1;\n for (const word of words) {\n if (word.w > width) {\n // oversized word: fill the rest of the line, then full-width rows.\n // `take` is the word's chars that still fit — the gap before the word\n // eats into the room, and when it eats all of it the word starts fresh\n const room = cs < 0 ? 0 : width - (ce - cs + 1);\n const take = room > 0 ? ce + room - word.s + 1 : 0;\n if (take > 0) spans.push([cs, ce + room]);\n else if (cs >= 0) spans.push([cs, ce]);\n let pos = word.s + Math.max(0, take);\n let remaining = word.w - Math.max(0, take);\n while (remaining > width) {\n spans.push([pos, pos + width - 1]);\n pos += width;\n remaining -= width;\n }\n cs = pos;\n ce = pos + remaining - 1;\n continue;\n }\n if (cs < 0) {\n cs = word.s;\n ce = word.e;\n continue;\n }\n if (word.e - cs + 1 <= width) {\n ce = word.e;\n continue;\n }\n spans.push([cs, ce]);\n cs = word.s;\n ce = word.e;\n }\n if (cs >= 0) spans.push([cs, ce]);\n\n const out: string[] = [];\n for (const [a, b] of spans) {\n const from = chars[a]!;\n // take trailing escapes up to the next visible char with the chunk\n let end = chars[b]! + 1;\n while (end < text.length && text[end] === \"\\x1b\") end = endOfEscape(text, end);\n let piece = text.slice(from, end);\n const reopen = sgrStateAt(text, from);\n if (reopen.length) piece = reopen.join(\"\") + piece;\n if (sgrStateAt(text, end).length) piece += \"\\x1b[0m\";\n out.push(piece);\n }\n return out.length ? out : [\"\"];\n}\n\n// ---------------------------------------------------------------------------\n// the layout: scroll region + two fixed rows\n// ---------------------------------------------------------------------------\n\n/** Restrict scrolling to the transcript region (rows 1 … rows-3). */\nexport function chatScrollRegion(rows: number): string {\n return `\\x1b[1;${Math.max(1, rows - 3)}r`;\n}\n\n/**\n * The separator row between the transcript and the prompt: cleared, then —\n * when the pane's width is known — a dim rule of `cols` box-drawing `─`\n * filling the row. `cols` 0 (unknown) keeps the row blank; `dim` is the\n * pane's colour helper, identity when colours are off.\n */\nexport function chatBlankRow(\n rows: number,\n cols = 0,\n dim: (s: string) => string = (s) => s\n): string {\n const rule = cols > 0 ? dim(\"─\".repeat(cols)) : \"\";\n return `\\x1b[${Math.max(1, rows - 2)};1H\\x1b[K` + rule;\n}\n\n/**\n * Enter the chat layout: clear the pane, set the scroll region, draw the\n * separator row and park the cursor at column 3 of the prompt row (rows-1,\n * right after `› `). The ticker owns row `rows`.\n */\nexport function chatEnter(\n rows: number,\n cols = 0,\n dim: (s: string) => string = (s) => s\n): string {\n return (\n \"\\x1b[2J\" + chatScrollRegion(rows) + chatBlankRow(rows, cols, dim) +\n `\\x1b[${Math.max(1, rows - 1)};3H`\n );\n}\n\n/** Leave it: reset the scroll region, show the cursor, drop to the last row. */\nexport function chatLeave(rows: number): string {\n return \"\\x1b[r\\x1b[?25h\" + `\\x1b[${Math.max(1, rows)};1H`;\n}\n\n/**\n * Redraw the ticker on its own row and park the cursor back on the prompt\n * row at `parkCol` (default right after `› `): a ticker refresh never leaves\n * the cursor on the bottom row.\n */\nexport function chatTickerRow(text: string, rows: number, parkCol = 3): string {\n return (\n \"\\x1b7\" +\n `\\x1b[${Math.max(1, rows)};1H\\x1b[K` + text +\n `\\x1b[${Math.max(1, rows - 1)};${Math.max(1, parkCol)}H`\n );\n}\n\n/**\n * The prompt row: the fixed `› ` prefix, then the input buffer — or, while\n * the buffer is empty, the dim placeholder behind the same prefix. The\n * cursor parks right after the buffer (`cursorAt` chars in, for a cursor\n * mid-draft), so typed text always starts at the column the placeholder\n * occupied, never glued to a hint.\n */\nexport function chatPromptRow(\n rows: number,\n buffer = \"\",\n dim: (s: string) => string = (s) => s,\n cursorAt?: number\n): string {\n const rowN = Math.max(1, rows - 1);\n const body = buffer !== \"\" ? buffer : dim(CHAT_HINT);\n const col = 3 + Math.max(0, Math.min(cursorAt ?? buffer.length, buffer.length));\n return `\\x1b[${rowN};1H\\x1b[K` + CHAT_PROMPT + body + `\\x1b[${rowN};${col}H`;\n}\n\nexport interface ChatInsert {\n seq: string;\n /** rows filled after this one (caps at regionRows, then it always scrolls). */\n fill: number;\n}\n\n/**\n * Insert one transcript row above the fixed prompt/ticker rows. While the\n * region is still filling (`fill < regionRows`) the row is placed top-down;\n * once full, the cursor moves to the region's bottom row and a newline\n * scrolls the region up by one — the two fixed rows are never touched. The\n * user's cursor is saved before and restored after, so readline keeps its\n * position on the prompt row.\n */\nexport function chatInsertLine(line: string, fill: number, regionRows: number): ChatInsert {\n const growing = fill < regionRows;\n const seq =\n \"\\x1b7\" +\n (growing\n ? `\\x1b[${fill + 1};1H${line}\\x1b[K`\n : `\\x1b[${regionRows};1H\\n${line}\\x1b[K`) +\n \"\\x1b8\";\n return { seq, fill: Math.min(fill + 1, regionRows) };\n}\n\n// ---------------------------------------------------------------------------\n// prompt command parsing\n// ---------------------------------------------------------------------------\n\nexport type ChatAction =\n | { kind: \"message\"; text: string }\n | { kind: \"help\" }\n | { kind: \"quit\" }\n | { kind: \"status\" }\n | { kind: \"resume\"; text: string };\n\n/**\n * One submitted prompt line → what to do with it. `/help`, `/quit`,\n * `/status` and `/resume <text>` are commands (a bare `/resume` comes back\n * with empty text so the caller can print its usage); anything else,\n * including any other `/word`, is a message for the worker.\n */\nexport function parseChatLine(raw: string): ChatAction {\n const text = raw.trim();\n if (text === \"/help\") return { kind: \"help\" };\n if (text === \"/quit\") return { kind: \"quit\" };\n if (text === \"/status\") return { kind: \"status\" };\n if (text.startsWith(\"/resume\")) return { kind: \"resume\", text: text.slice(\"/resume\".length).trim() };\n return { kind: \"message\", text };\n}\n\n/**\n * The auto-exit countdown must not fire while the prompt holds unsent text:\n * true while it does. null/undefined (no prompt wired) never holds.\n */\nexport function holdAutoExit(promptText: string | null | undefined): boolean {\n return typeof promptText === \"string\" && promptText.trim() !== \"\";\n}\n","/**\n * render.ts — turn worker events into the lines a human reads.\n *\n * Ports the glm-ps transcript rendering: the gutter rules (dim `>` for reads\n * and searches, `$` for shell, magenta `~` + red/green diff for edits, green\n * `+` for writes), Read-result trimming (first 3 lines then a count), and the\n * result footer. Colors are applied only when the output is a TTY, so MCP\n * tool output stays plain text.\n */\n\nimport { relative, basename } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport { ageOf, contextLabel, contextPercent, isChatPane, type WorkerStatus, alive } from \"./status.js\";\nimport { workerDepth } from \"./tree.js\";\nimport { sessionTag } from \"./scope.js\";\nimport { parseWorkerReport, renderReport } from \"./report.js\";\n\nexport type ColorEnabled = boolean;\n\nconst CODES = {\n dim: \"2\",\n bold: \"1\",\n red: \"31\",\n green: \"32\",\n yellow: \"33\",\n blue: \"34\",\n mag: \"35\",\n cyan: \"36\",\n} as const;\n\nexport type ColorName = keyof typeof CODES;\n\nexport function makeColor(enabled: ColorEnabled) {\n return (name: ColorName, s: string): string =>\n enabled ? `\\x1b[${CODES[name]}m${s}\\x1b[0m` : s;\n}\n\nexport type Paint = ReturnType<typeof makeColor>;\n\n/** Path relative to cwd when it lies inside it, otherwise unchanged. */\nexport function relPath(path: string, cwd: string): string {\n if (!path || !cwd) return path;\n let r: string;\n try {\n r = relative(cwd, path);\n } catch {\n return path;\n }\n return r.startsWith(\"..\") ? path : r;\n}\n\n/** Minimal line diff for Edit previews: common prefix/suffix, one hunk. */\nexport function unifiedDiffLines(oldStr: string, newStr: string): string[] {\n const oldL = oldStr.split(\"\\n\");\n const newL = newStr.split(\"\\n\");\n let start = 0;\n while (start < oldL.length && start < newL.length && oldL[start] === newL[start]) start++;\n let endOld = oldL.length;\n let endNew = newL.length;\n while (endOld > start && endNew > start && oldL[endOld - 1] === newL[endNew - 1]) {\n endOld--;\n endNew--;\n }\n const out: string[] = [];\n for (let i = start; i < endOld; i++) out.push(\"-\" + oldL[i]);\n for (let i = start; i < endNew; i++) out.push(\"+\" + newL[i]);\n return out;\n}\n\nexport interface ToolUseBlock {\n type?: string;\n text?: string;\n name?: string;\n id?: string;\n input?: unknown;\n /** tool_result blocks only: the call this answers. */\n tool_use_id?: string;\n is_error?: boolean;\n content?: unknown;\n}\n\nexport interface StreamEventLike {\n type?: string;\n subtype?: string;\n model?: string;\n cwd?: string;\n message?: { content?: ToolUseBlock[] };\n result?: string;\n is_error?: boolean;\n num_turns?: number;\n duration_ms?: number;\n /** ISO stamp the runner attaches to every mirrored event (2g). */\n _ts?: string;\n /** Operator text (type: \"operator\"). */\n text?: string;\n /** Operator mirror of a handoff delivery — shown only as the ◆ inbox line. */\n handoff?: boolean;\n /** Handoff fields (type: \"handoff\", from the inbox tail). */\n from?: string;\n kind?: string;\n}\n\n/**\n * `HH:MM:SS` at an offset east of UTC in minutes (Date.getTimezoneOffset()\n * negated), from any ISO stamp the runner wrote — `Z` or a local `+HH:MM`.\n * null when the stamp cannot be parsed. Offsets make this testable without\n * depending on the machine's zone; the default is this machine's.\n */\nexport function clockOf(ts: string, offMin = -new Date().getTimezoneOffset()): string | null {\n const t = Date.parse(ts);\n if (Number.isNaN(t)) return null;\n return new Date(t + offMin * 60_000).toISOString().slice(11, 19);\n}\n\n/** `YYYY-MM-DD` at the same offset — the local day a date separator shows. */\nexport function dayOf(ts: string, offMin = -new Date().getTimezoneOffset()): string | null {\n const t = Date.parse(ts);\n if (Number.isNaN(t)) return null;\n return new Date(t + offMin * 60_000).toISOString().slice(0, 10);\n}\n\n/** The gutter of one rendered event: its three shapes and its width. */\nexport interface Gutter {\n /** first row: `HH:MM:SS │ ` (dim). */\n first: string;\n /** later rows of one event, no wrapping: blanks of the same width. */\n cont: string;\n /** wrapped continuation rows: blank time, the `│` bar kept (dim). */\n barCont: string;\n /** printable columns first/cont/barCont occupy. */\n width: number;\n}\n\n/**\n * The transcript gutter (2g): `HH:MM:SS │ ` from the event's `_ts`, dim, with\n * the worker tag in front when several run at once. Continuation lines get\n * blanks of the same width so wrapped text stays aligned; when the viewer\n * wraps lines itself, continuation rows carry the `│` bar instead (barCont)\n * so the bar runs unbroken down the pane. null when the event carries no\n * stamp (logs from before 2g render with the plain prefix). The time is the\n * stamp's wall clock at `offMin` — the default renders local time, whatever\n * zone stamped the log (old logs were stamped in UTC).\n */\nexport function gutterFor(\n c: Paint,\n e: { _ts?: string },\n tag?: string,\n offMin?: number\n): Gutter | null {\n if (!e._ts) return null;\n const time = clockOf(e._ts, offMin) ?? (e._ts.length >= 19 ? e._ts.slice(11, 19) : e._ts);\n const head = tag ? `${tag} ${time}` : time;\n const width = head.length + 3; // + \" │ \"\n return {\n first: c(\"dim\", `${head} │ `),\n cont: \" \".repeat(width),\n barCont: c(\"dim\", `${\" \".repeat(head.length)} │ `),\n width,\n };\n}\n\n/**\n * The ticker's tool part: `$ <command>` for Bash (first 60 chars), the file\n * basename for the file tools, the bare name for everything else.\n */\nexport function tickerTool(name: string, inp: unknown): string {\n const i = (typeof inp === \"object\" && inp !== null ? inp : {}) as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Bash\") return `$ ${shortText(get(\"command\").replace(/\\s+/g, \" \").trim(), 60)}`;\n if (name === \"Read\" || name === \"Edit\" || name === \"Write\" || name === \"MultiEdit\") {\n const base = get(\"file_path\").split(\"/\").pop() ?? \"\";\n return base || name;\n }\n return name;\n}\n\n/**\n * The liveness line: `⋯ 12s · run tests before the fix · $ bun run test` —\n * seconds since the last *rendered* event, the worker's last stated intent\n * (its last assistant text, ≤60 chars) and the tool it is currently running.\n * Parts that are empty drop out.\n */\nexport function tickerText(secs: number, intent: string, tool: string, meter?: string | null): string {\n const head = `⋯ ${secs}s`;\n const parts = [intent, tool].map((p) => p.trim()).filter(Boolean);\n const tail = parts.join(\" · \");\n const line = tail ? `${head} · ${tail}` : head;\n return meter ? `${line} · ${meter}` : line;\n}\n\n/** What the chat pane's status row shows (chatStatusRow formats it). */\nexport interface StatusRow {\n provider: string;\n model: string;\n contextTokens?: number | null;\n contextWindow?: number | null;\n turns: number;\n tools: number;\n /** runtime so far in seconds; the finished row freezes its final value. */\n elapsed: number;\n /** seconds since the last rendered event (running only). */\n idle?: number;\n intent?: string;\n tool?: string;\n /** a finished state freezes the row with ✓/✗ instead of the ticker part. */\n state?: string | null;\n}\n\n/** `4m12s` — the runtime shape the status row shows. */\nexport function fmtElapsed(secs: number): string {\n const s = Math.max(0, Math.floor(secs));\n return `${Math.floor(s / 60)}m${String(s % 60).padStart(2, \"0\")}s`;\n}\n\n/**\n * The chat pane's status row (always visible while following):\n * `[glm/glm-5.3] ctx 84k/200k (42%) · turns 12 · tools 7 · 4m12s · ⋯ 7s · <intent> · <tool>`\n * — context yellow past 70 %, red past 85 %, dropped when unknown; once the\n * worker finished, the ticker part is replaced by `✓ done` / `✗ failed`.\n */\nexport function chatStatusRow(c: Paint, s: StatusRow): string {\n const parts: string[] = [];\n const pct = contextPercent(s);\n if (pct !== null) {\n const label = contextLabel(s);\n parts.push(pct > 85 ? c(\"red\", label) : pct > 70 ? c(\"yellow\", label) : label);\n }\n parts.push(`turns ${s.turns}`, `tools ${s.tools}`, fmtElapsed(s.elapsed));\n if (s.state && s.state !== \"running\") {\n parts.push(s.state === \"done\" ? c(\"green\", \"✓ done\") : c(\"red\", \"✗ failed\"));\n } else {\n parts.push(`⋯ ${Math.max(0, Math.floor(s.idle ?? 0))}s`);\n for (const p of [s.intent, s.tool]) if (p && p.trim()) parts.push(p.trim());\n }\n return `[${s.provider}/${s.model}] ${parts.join(\" · \")}`;\n}\n\n/**\n * One blank line between turns, none inside one: a blank goes before an\n * assistant message that follows a tool result or an operator message (the\n * worker starting to speak again after its tools were answered / it was told\n * something), not between the text, tool calls and results of one turn.\n */\nexport function blankBetween(prev: { type?: string } | null, e: { type?: string }): boolean {\n if (!prev) return false;\n if (e.type !== \"assistant\") return false;\n return prev.type === \"user\" || prev.type === \"operator\" || prev.type === \"handoff\";\n}\n\n/** The worker's last stated intent: the first line of its last text, ≤60. */\nexport function intentOf(text: string): string {\n const first = text.trim().split(\"\\n\").find((l) => l.trim()) ?? \"\";\n return shortText(first.trim().replace(/\\s+/g, \" \"), 60);\n}\n\n/**\n * The context meter `ctx 84k/200k (42%)` (contextLabel, shared with the\n * chat status row), yellow from 70 %, red from 85 %. null when the numbers\n * are missing or below `minPct` (the table only shows it past 60; the pane\n * liveness line always shows it).\n */\nexport function contextMeter(\n c: Paint,\n s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">,\n minPct = 0\n): string | null {\n const pct = contextPercent(s);\n if (pct === null || pct <= minPct) return null;\n const label = contextLabel(s);\n if (pct > 85) return c(\"red\", label);\n if (pct > 70) return c(\"yellow\", label);\n return label;\n}\n\nfunction renderToolUse(\n c: Paint,\n prefix: string,\n name: string,\n inp: unknown,\n cwd: string\n): string[] {\n const i = (typeof inp === \"object\" && inp !== null ? inp : {}) as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Read\") {\n return [`${prefix}${c(\"dim\", \">\")} Reading ${relPath(get(\"file_path\"), cwd)}`];\n }\n if (name === \"Grep\" || name === \"Glob\") {\n return [\n `${prefix}${c(\"dim\", \">\")} Searching ${c(\"cyan\", get(\"pattern\"))} in ${relPath(get(\"path\") || \".\", cwd)}`,\n ];\n }\n if (name === \"Bash\") {\n return [`${prefix}${c(\"dim\", \"$\")} ${shortText(get(\"command\"), 160)}`];\n }\n if (name === \"Edit\") {\n const out = [`${prefix}${c(\"mag\", \"~\")} Editing ${relPath(get(\"file_path\"), cwd)}`];\n for (const line of unifiedDiffLines(get(\"old_string\"), get(\"new_string\"))) {\n if (line.startsWith(\"-\")) out.push(`${prefix} ${c(\"red\", line)}`);\n else if (line.startsWith(\"+\")) out.push(`${prefix} ${c(\"green\", line)}`);\n else out.push(`${prefix} ${c(\"dim\", line)}`);\n }\n return out;\n }\n if (name === \"Write\") {\n const n = get(\"content\").split(\"\\n\").length;\n return [`${prefix}${c(\"green\", \"+\")} Writing ${relPath(get(\"file_path\"), cwd)} (${n} lines)`];\n }\n if (name === \"WebSearch\" || name === \"WebFetch\") {\n return [`${prefix}${c(\"dim\", \">\")} ${name} ${shortText(get(\"query\") || get(\"url\"), 100)}`];\n }\n return [`${prefix}${c(\"dim\", \">\")} ${name} ${shortText(JSON.stringify(i), 120)}`];\n}\n\n/** Render one stream-json event; `tools` maps tool_use_id → tool name. */\nexport function renderEvent(\n c: Paint,\n prefix: string,\n e: StreamEventLike,\n cwd: string,\n tools: Record<string, string>,\n gutter?: { first: string; cont: string } | null\n): string[] {\n const out: string[] = [];\n if (e.type === \"system\" && e.subtype === \"init\") {\n out.push(\n `${prefix}${c(\"dim\", `worker started · model ${e.model ?? \"?\"} · cwd ${basename(e.cwd || cwd || \"\")}`)}`\n );\n } else if (e.type === \"operator\") {\n // a handoff delivery is said to the worker AND tailed from the inbox —\n // the transcript shows only the ◆ line, its mirror renders nothing\n if (e.handoff) return out;\n // split per line so the gutter continuation pads wrapped text\n for (const ln of String(e.text ?? \"\").split(\"\\n\")) {\n out.push(`${prefix}${c(\"cyan\", \"» \" + ln)}`);\n }\n } else if (e.type === \"handoff\") {\n // a child's message from the inbox: ◆ from <id> · <kind>: <text>\n for (const ln of String(e.text ?? \"\").split(\"\\n\")) {\n out.push(`${prefix}${c(\"mag\", `◆ from ${e.from ?? \"?\"} · ${e.kind ?? \"?\"}: ${ln}`)}`);\n }\n } else if (e.type === \"assistant\") {\n for (const b of e.message?.content ?? []) {\n if (b.type === \"text\" && (b.text ?? \"\").trim()) {\n for (const ln of (b.text ?? \"\").trim().split(\"\\n\")) out.push(`${prefix}${ln}`);\n } else if (b.type === \"tool_use\") {\n out.push(...renderToolUse(c, prefix, b.name ?? \"?\", b.input, cwd));\n }\n }\n } else if (e.type === \"user\") {\n for (const b of e.message?.content ?? []) {\n if (b.type !== \"tool_result\") continue;\n let content: unknown = (b as { content?: unknown }).content ?? \"\";\n if (Array.isArray(content)) {\n content = content\n .map((x) => (typeof x === \"object\" && x !== null && \"text\" in x ? String((x as { text?: string }).text ?? \"\") : \"\"))\n .join(\"\\n\");\n }\n const text = String(content);\n const isError = (b as { is_error?: boolean }).is_error === true;\n if (!isError && tools[(b as { tool_use_id?: string }).tool_use_id ?? \"\"] === \"Read\") {\n // keep Read results as the tool returned them (`<lineno>\\t<code>`),\n // only their count is summarised\n const lines = text.split(\"\\n\");\n for (const ln of lines.slice(0, 3)) out.push(`${prefix}${c(\"dim\", ln)}`);\n if (lines.length > 3) {\n out.push(`${prefix}${c(\"dim\", ` … ${lines.length} lines`)}`);\n }\n } else if (isError) {\n out.push(`${prefix} ${c(\"red\", \"! \" + shortText(text, 200))}`);\n } else if (text.trim()) {\n out.push(`${prefix} ${c(\"dim\", shortText(text, 120))}`);\n }\n }\n } else if (e.type === \"result\") {\n const ok = !e.is_error;\n const mark = ok ? c(\"green\", \"✓ done\") : c(\"red\", \"✗ failed\");\n out.push(`${prefix}${mark} · ${e.num_turns ?? \"?\"} turns · ${Math.floor((e.duration_ms ?? 0) / 1000)}s`);\n // a contract-compliant final message renders as the compact report block\n const report = parseWorkerReport(String(e.result ?? \"\"));\n if (report) {\n out.push(...renderReport(c, prefix, report, cwd));\n } else {\n for (const ln of String(e.result ?? \"\").trim().split(\"\\n\")) {\n out.push(`${prefix} ${ln}`);\n }\n }\n }\n if (!gutter) return out;\n return out.map((ln, i) => (i === 0 ? gutter.first : gutter.cont) + ln);\n}\n\n/** Transcript header: id, provider, label, session name, project dir. */\nexport function headerLine(\n c: Paint,\n s: { id: string; label: string; cwd: string; provider?: string; session?: { name?: string } | null }\n): string {\n const bits = [s.provider ? `[${s.provider}]` : \"\", sessionTag(s)].filter(Boolean).join(\" \");\n const sep = bits ? ` ${bits}` : \"\";\n return c(\"bold\", `━━ ${s.id}${sep} ${s.label} (${basename(s.cwd)})`);\n}\n\n/** The chain label behind a stage label: strip the trailing \" · <stage>\". */\nfunction chainLabelOf(stages: WorkerStatus[]): string {\n const first = stages[0];\n if (!first) return \"\";\n const suffix = first.stage ? ` · ${first.stage}` : \"\";\n return first.label.endsWith(suffix) && suffix\n ? first.label.slice(0, first.label.length - suffix.length)\n : first.label;\n}\n\n/**\n * The ps table (RUNNING + FINISHED last 8) as a forest: a worker whose parent\n * is another worker renders indented under it (`├`/`└` connectors); a parent\n * that is not a worker is a chain id and gets its old `chain <id>` header.\n * `⎇` marks an unmerged worktree branch, `◆N` an inbox with N handoffs.\n */\nexport function renderTable(\n c: Paint,\n statuses: WorkerStatus[],\n scopeLabel: string,\n now: Date = new Date(),\n inbox: Record<string, number> = {}\n): string {\n const running: WorkerStatus[] = [];\n const done: WorkerStatus[] = [];\n for (const s of statuses) {\n if (s.state === \"running\" && alive(s.pid)) running.push(s);\n else {\n if (s.state === \"running\") s.state = \"lost\";\n done.push(s);\n }\n }\n const isWorker = (id: string): boolean => statuses.some((s) => s.id === id);\n // a chain id (no status file) groups its stages under a header instead\n const chainOf = (s: WorkerStatus): string | null => (s.parent && !isWorker(s.parent) ? s.parent : null);\n const treeLine = (line: string, chain: string | null, last: boolean): string => {\n if (chain === null) return line;\n const mark = last ? \"└\" : \"├\";\n const bar = last ? \" \" : \"│\";\n return line.startsWith(\" \")\n ? ` ${bar} ${line.slice(6)}`\n : ` ${mark} ${line.slice(2)}`;\n };\n // sub-workers: one connector level per depth under their worker parent\n const rowPrefix = (depth: number, last: boolean): string =>\n depth <= 0 ? \" \" : \" \" + \"│ \".repeat(depth - 1) + (last ? \"└ \" : \"├ \");\n const rowCont = (depth: number, last: boolean): string =>\n depth <= 0 ? \" \" : \" \" + \"│ \".repeat(depth - 1) + (last ? \" \" : \"│ \");\n const branchMark = (s: WorkerStatus): string =>\n s.branch && !s.merged ? \" \" + c(\"yellow\", \"⎇\" + (s.commits ? String(s.commits) : \"\")) : \"\";\n const inboxMark = (s: WorkerStatus): string =>\n inbox[s.id] ? \" \" + c(\"mag\", `◆${inbox[s.id]}`) : \"\";\n\n const clock = `${String(now.getHours()).padStart(2, \"0\")}:${String(now.getMinutes()).padStart(2, \"0\")}:${String(now.getSeconds()).padStart(2, \"0\")}`;\n const lines: string[] = [c(\"bold\", `Workers ${clock}`), \"\"];\n lines.push(c(\"bold\", `RUNNING (${running.length})`));\n if (!running.length) lines.push(\" none\");\n for (let i = 0; i < running.length; i++) {\n const s = running[i];\n const chain = chainOf(s);\n if (chain) {\n const prev = running[i - 1];\n if (!prev || chainOf(prev) !== chain) {\n const stages = running.filter((x) => chainOf(x) === chain);\n lines.push(` ${c(\"bold\", `chain ${chain}`)} ${chainLabelOf(stages)}`);\n }\n }\n const last =\n !!chain && (i + 1 >= running.length || chainOf(running[i + 1]) !== chain);\n const depth = workerDepth(statuses, s.id);\n const subLast =\n !chain && (i + 1 >= running.length || running[i + 1].parent !== s.parent);\n const meter = contextMeter(c, s, 60);\n const p = chain ? \" \" : rowPrefix(depth, subLast);\n const q = chain ? \" \" : rowCont(depth, subLast);\n lines.push(\n treeLine(\n `${p}${c(\"cyan\", s.id)}${inboxMark(s)} [${s.provider}] ${ageOf(s.started, now).padStart(4)} old turns ${String(s.turns).padStart(2)} tools ${String(s.tools).padStart(2)} ${basename(s.cwd)}${branchMark(s)}${meter ? \" \" + meter : \"\"}`,\n chain,\n last\n )\n );\n lines.push(treeLine(`${q}task: ${s.label}`, chain, last));\n lines.push(\n treeLine(`${q}now: ${c(\"yellow\", s.last)} (${ageOf(s.updated, now)} ago)`, chain, last)\n );\n }\n lines.push(\"\");\n lines.push(c(\"bold\", \"FINISHED (last 8)\"));\n const doneSlice = done.slice(-8);\n for (let i = 0; i < doneSlice.length; i++) {\n const s = doneSlice[i];\n const chain = chainOf(s);\n if (chain) {\n const prev = doneSlice[i - 1];\n if (!prev || chainOf(prev) !== chain) {\n const stages = doneSlice.filter((x) => chainOf(x) === chain);\n lines.push(` ${c(\"bold\", `chain ${chain}`)} ${chainLabelOf(stages)}`);\n }\n }\n const last =\n !!chain && (i + 1 >= doneSlice.length || chainOf(doneSlice[i + 1]) !== chain);\n const depth = workerDepth(statuses, s.id);\n const subLast =\n !chain && (i + 1 >= doneSlice.length || doneSlice[i + 1].parent !== s.parent);\n const col = s.state === \"done\" ? \"green\" : \"red\";\n const tag = sessionTag(s);\n lines.push(\n treeLine(\n `${chain ? \" \" : rowPrefix(depth, subLast)}${s.id}${inboxMark(s)} [${s.provider}]${tag ? \" \" + tag : \"\"} ${c(col, s.state.padEnd(6))} rc=${s.rc} ${String(s.secs ?? \"?\").padStart(4)}s turns ${String(s.turns).padStart(2)} tools ${String(s.tools).padStart(2)} ${basename(s.cwd)}${branchMark(s)} ${s.label}`,\n chain,\n last\n )\n );\n }\n lines.push(\"\");\n lines.push(c(\"dim\", \"worker follow live transcript of running workers\"));\n lines.push(c(\"dim\", \"worker <id> replay one worker\"));\n lines.push(c(\"dim\", scopeLabel));\n return lines.join(\"\\n\");\n}\n\n/**\n * The step of a statusline row: what the worker is doing as a verb, never a\n * raw command. A Bash line trims to its first word (`grep -n -A4 \"x\" src/` →\n * `grep …` — no `Bash:` prefix, no flags, no quotes); the file tools keep\n * their `Edit: button.ts` shape; free text (what the worker last said) is\n * only shortened. Always ≤ 24 chars.\n */\nexport function stepOf(last: string): string {\n const s = String(last ?? \"\").trim();\n if (s.startsWith(\"Bash: \")) {\n const cmd = s.slice(6).trim();\n const verb = cmd.split(/\\s+/)[0] ?? \"\";\n return shortText(cmd.length > verb.length ? `${verb} …` : verb, 24);\n }\n return shortText(s, 24);\n}\n\n/**\n * The statusline bar: `provider ▶N` then one row per running spawned worker —\n * `name · age · step` — and today's ✓/✗ tail. The chat pane contributes\n * nothing but its provider: its age, state and inbox live in `pai worker ps`,\n * not here. Rows are flat (the ↳ depth is ps territory) and oldest first;\n * `#id` joins a name only when two running workers share one label, so ids\n * stay rare enough to read.\n */\nexport function renderStatusLine(mine: WorkerStatus[], now: Date = new Date()): string {\n if (!mine.length) return \"\";\n // isChatPane carries the migration shim for pre-`origin` entries (see\n // status.ts): the chat pane is not a worker row and not counted in ▶N.\n const isChat = isChatPane;\n const chat = mine.find((s) => isChat(s) && s.state === \"running\" && alive(s.pid));\n const running = mine\n .filter((s) => !isChat(s) && s.state === \"running\" && alive(s.pid))\n .sort((a, b) => a.started.localeCompare(b.started));\n const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}`;\n const doneToday = mine.filter((s) => s.state !== \"running\" && s.started.startsWith(today));\n const ok = doneToday.filter((s) => s.state === \"done\").length;\n const bad = doneToday.length - ok;\n const rows = running.slice(0, 3).map((s) => {\n const twin = running.some((o) => o !== s && o.label === s.label);\n const name = `${twin ? `#${s.id.slice(-4)} ` : \"\"}${s.label.slice(0, 26)}`;\n return `${name} · ${ageOf(s.started, now)} · ${stepOf(s.last)}`;\n });\n let head: string;\n if (chat) head = chat.provider;\n else {\n const providers = new Set(running.map((s) => s.provider));\n head = providers.size === 1 ? [...providers][0] : \"workers\";\n }\n if (running.length) head += ` ▶${running.length}`;\n if (rows.length) head += \" | \" + rows.join(\" | \");\n if (doneToday.length) head += ` ✓${ok} ✗${bad} today`;\n return head;\n}\n","/**\n * viewer.ts — ps / follow / replay / status line over the worker logDir.\n *\n * Scoping: workers launched from this terminal's AIBroker session (or, as the\n * fallback, its iTerm tab) unless --all or an explicit worker id is given.\n * Outside iTerm, everything degrades to \"all workers\" — the Python behaviour.\n *\n * Transcripts render with a `HH:MM:SS │ ` gutter (2g): dim, taken from the\n * `_ts` stamp on every mirrored event (local wall clock — stamps carry a local\n * offset and old UTC stamps are converted), the worker tag in front when\n * several run at once, a date separator when the day changes, and — on a TTY —\n * a liveness line (`⋯ 12s · run tests before the fix · $ bun run test`) that\n * is rewritten in place between events. Attaching to a worker that is already\n * running first replays its last events (backfill), then continues live.\n *\n * Following one worker turns the pane into a small chat (see chatui.ts): the\n * transcript scrolls in a region that ends two rows above the bottom, the\n * prompt row (`› `, readline editing) and the ticker row stay fixed, and every\n * submitted line is said to the worker while it runs and resumes it (same\n * Claude session) once it has finished. Lines the pane wraps itself keep the\n * `│` bar on continuation rows, so no content ever lands left of the bar.\n * Non-TTY output keeps the plain scrolling behaviour.\n */\n\nimport { existsSync, openSync, readSync, closeSync, readFileSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { spawn, type SpawnOptions } from \"node:child_process\";\nimport { eventsPath } from \"./paths.js\";\nimport { alive, loadStatuses, type WorkerStatus } from \"./status.js\";\nimport { currentTabKey, resolveSession, workerInScope } from \"./scope.js\";\nimport { readInbox } from \"./handoff.js\";\nimport { sayToWorker } from \"./operator.js\";\nimport {\n CHAT_HELP,\n chatBlankRow,\n chatEnter,\n chatInsertLine,\n chatLeave,\n chatPromptRow,\n chatScrollRegion,\n chatTickerRow,\n holdAutoExit,\n parseChatLine,\n wrapText,\n} from \"./chatui.js\";\nimport {\n blankBetween,\n chatStatusRow,\n contextMeter,\n dayOf,\n gutterFor,\n headerLine,\n intentOf,\n makeColor,\n renderEvent,\n renderStatusLine,\n renderTable,\n tickerText,\n tickerTool,\n type Gutter,\n type Paint,\n type StatusRow,\n type StreamEventLike,\n} from \"./render.js\";\n\n/** How many existing events a fresh follow pane replays before going live. */\nexport const BACKFILL_EVENTS = 200;\n\n// ---------------------------------------------------------------------------\n// ps\n// ---------------------------------------------------------------------------\n\nexport function psOutput(\n logDir: string,\n showAll: boolean,\n env: NodeJS.ProcessEnv = process.env,\n color = process.stdout.isTTY === true\n): string {\n const c = makeColor(color);\n const term = env.ITERM_SESSION_ID ?? \"\";\n const statuses = loadStatuses(logDir);\n const scoped = showAll || !term ? statuses : statuses.filter((s) => workerInScope(s, term));\n const scopeLabel =\n showAll || !term\n ? \"scope: all workers\"\n : resolveSession(term)\n ? `scope: session ${resolveSession(term)!.name}`\n : `scope: tab ${currentTabKey(env)} (this iTerm tab)`;\n const inbox = inboxCounts(logDir, scoped);\n return renderTable(c, scoped, scopeLabel, new Date(), inbox);\n}\n\n/** Handoffs waiting in each listed worker's inbox: id → count. */\nexport function inboxCounts(logDir: string, statuses: WorkerStatus[]): Record<string, number> {\n const out: Record<string, number> = {};\n for (const s of statuses) {\n const n = readInbox(logDir, s.id).length;\n if (n) out[s.id] = n;\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// one event → rendered lines (shared by replay, backfill and the live tail)\n// ---------------------------------------------------------------------------\n\n/** What applyEvent() carries between the events of one transcript. */\nexport interface FollowState {\n /** day separator already printed (\"\" before the first stamped event). */\n lastDay: string;\n /** tool_use id → tool name (Edit previews and Read trimming need it). */\n tools: Record<string, string>;\n /** the worker's last stated intent — its last assistant text, ≤60 chars. */\n intent: string;\n /** the ticker's tool part of the last tool_use (\"$ bun run test\"). */\n tool: string;\n /** the last event seen, for the blank line between turns. */\n prev: StreamEventLike | null;\n}\n\nexport function initialFollowState(intent = \"waiting for first event\"): FollowState {\n return { lastDay: \"\", tools: {}, intent, tool: \"\", prev: null };\n}\n\n/** One applied event: what to print, and how the ticker changes. */\nexport interface FollowStep {\n /** rendered lines (\"\" among them marks the blank line between turns). */\n lines: string[];\n /** day separator to print first, when the stamp's local day changed. */\n day: string | null;\n /** the event produced visible output — the ticker clock restarts. */\n activity: boolean;\n state: FollowState;\n}\n\n/**\n * Gutter the rendered body of one event, wrapping when the pane width is\n * known. Unwrapped (null `wrapWidth`, e.g. piped output): the first row gets\n * the stamped gutter, later rows of the event blanks — exactly the pre-chat\n * rendering. Wrapped: every row is folded at `wrapWidth` columns and each\n * continuation row carries the blank gutter with the `│` bar, so the bar runs\n * unbroken down the pane and no content ever lands left of it.\n */\nexport function gutterBody(\n body: string[],\n gutter: Gutter | null,\n wrapWidth: number | null\n): string[] {\n if (!gutter) return body;\n if (wrapWidth === null || wrapWidth <= gutter.width) {\n return body.map((ln, i) => (i === 0 ? gutter.first : gutter.cont) + ln);\n }\n const out: string[] = [];\n for (const ln of body) {\n for (const piece of wrapText(ln, wrapWidth - gutter.width)) {\n out.push((out.length === 0 ? gutter.first : gutter.barCont) + piece);\n }\n }\n return out;\n}\n\n/**\n * Render one event and advance the follow state. Everything the viewer shows\n * between events of one worker comes from here: replay, the backfill on\n * attach and the live tail all use it, so they space identically — events\n * back to back, one blank line between turns. `activity` is false for events\n * that render nothing (stream noise, empty tool results): they leave the\n * ticker's \"since last event\" clock running. `wrapWidth` (the pane's column\n * count, re-read on resize) makes the viewer wrap rows itself; null keeps\n * the terminal's own wrapping.\n */\nexport function applyEvent(\n c: Paint,\n s: FollowState,\n e: StreamEventLike,\n cwd: string,\n tag?: string,\n offMin?: number,\n wrapWidth?: number | null\n): FollowStep {\n const tools = { ...s.tools };\n if (e.type === \"assistant\") {\n for (const b of e.message?.content ?? []) {\n if (b.type === \"tool_use\" && b.id) tools[b.id] = b.name ?? \"?\";\n }\n }\n const day = typeof e._ts === \"string\" ? (dayOf(e._ts, offMin) ?? rawDay(e._ts)) : \"\";\n const state: FollowState = {\n lastDay: day && day !== s.lastDay ? day : s.lastDay,\n tools,\n intent: s.intent,\n tool: s.tool,\n prev: e,\n };\n if (e.type === \"assistant\") {\n for (const b of e.message?.content ?? []) {\n if (b.type === \"text\" && (b.text ?? \"\").trim()) state.intent = intentOf(b.text ?? \"\");\n else if (b.type === \"tool_use\") state.tool = tickerTool(b.name ?? \"?\", b.input);\n }\n }\n const gutter = gutterFor(c, e, tag, offMin);\n const prefix = \"\";\n const body = gutterBody(renderEvent(c, prefix, e, cwd, tools), gutter, wrapWidth ?? null);\n // the turn separator carries the gutter's bar (barCont) so the │ runs\n // unbroken down the pane; \"\" only when the event has no stamp/gutter\n const lines = blankBetween(s.prev, e) ? [gutter ? gutter.barCont : \"\", ...body] : body;\n return {\n lines,\n day: day && day !== s.lastDay ? day : null,\n activity: lines.some((ln) => ln !== \"\"),\n state,\n };\n}\n\n/** `YYYY-MM-DD` straight out of an unparsable stamp, \"\" when it has none. */\nfunction rawDay(ts: string): string {\n return /^\\d{4}-\\d{2}-\\d{2}/.test(ts) ? ts.slice(0, 10) : \"\";\n}\n\n/**\n * The events a fresh follow replays before going live: the last `cap`\n * non-empty log lines, oldest first.\n */\nexport function backfillLines(raw: string, cap = BACKFILL_EVENTS): string[] {\n return raw.split(\"\\n\").filter((l) => l.trim()).slice(-cap);\n}\n\n// ---------------------------------------------------------------------------\n// replay\n// ---------------------------------------------------------------------------\n\n/** The rendered transcript of one worker, from the start, as one string. */\nexport function replayOutput(\n logDir: string,\n wid: string,\n color = process.stdout.isTTY === true,\n tailLines?: number\n): string {\n const path = eventsPath(logDir, wid);\n if (!existsSync(path)) {\n throw new Error(`no event log for ${wid}`);\n }\n const c = makeColor(color);\n const st =\n loadStatuses(logDir).find((s) => s.id === wid) ??\n ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n const wrapWidth = typeof process.stdout.columns === \"number\" ? process.stdout.columns : null;\n const out: string[] = [headerLine(c, st)];\n const raw = readFileSync(path, \"utf8\");\n const lines = tailLines !== undefined ? raw.split(\"\\n\").slice(-tailLines) : raw.split(\"\\n\");\n // the worker's inbox handoffs join the transcript where they happened (_ts)\n const handoffs: StreamEventLike[] = readInbox(logDir, wid).map((h) => ({\n type: \"handoff\",\n from: h.from,\n kind: h.kind,\n text: h.text,\n _ts: h._ts,\n }));\n let state = initialFollowState(\"\");\n const events: StreamEventLike[] = [];\n for (const line of lines) {\n if (!line.trim()) continue;\n let e: StreamEventLike;\n try {\n e = JSON.parse(line) as StreamEventLike;\n } catch {\n continue;\n }\n events.push(e);\n }\n const stamp = (e: StreamEventLike): number => {\n const t = e._ts ? Date.parse(e._ts) : NaN;\n return Number.isNaN(t) ? 0 : t;\n };\n const merged = [...events, ...handoffs].sort((a, b) => stamp(a) - stamp(b));\n for (const e of merged) {\n const step = applyEvent(c, state, e, st.cwd ?? \"\", undefined, undefined, wrapWidth);\n if (step.day) out.push(c(\"dim\", `── ${step.day} ──`));\n out.push(...step.lines);\n state = step.state;\n }\n return out.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// follow\n// ---------------------------------------------------------------------------\n\ninterface FollowHandle {\n fd: number;\n buf: string;\n}\n\n/**\n * The follow exit decision for one worker: it is over once its result event\n * was rendered, or once its status left \"running\" while its pid is gone — a\n * worker killed without writing a result still ends. A status never read\n * (undefined) means \"not over\": follow keeps waiting for the first event.\n */\nexport function workerEnded(\n resultRendered: boolean,\n state: WorkerStatus[\"state\"] | undefined,\n pidAlive: boolean\n): boolean {\n return resultRendered || (state !== undefined && state !== \"running\" && !pidAlive);\n}\n\n// ---------------------------------------------------------------------------\n// operator input (2i): this pane's stdin drives say / resume\n// ---------------------------------------------------------------------------\n\n/** What makeOperatorInput() needs from the follow around it. */\nexport interface OperatorInputDeps {\n /** the worker typed lines go to right now (null: none, lines are ignored). */\n target: () => string | null;\n /** forwards one message to a running worker. */\n say: (id: string, text: string) => Promise<string>;\n /** whether a status file exists for the id (say failed → resume, or note). */\n workerKnown: (id: string) => boolean;\n /** continues a finished worker (same Claude session). */\n resume: (text: string, id: string) => void;\n /** one line of feedback in the pane. */\n note: (s: string) => void;\n paint: Paint;\n /** chat mode only: the echoed line replaces the \"» sent\" note. */\n sent?: (id: string) => void;\n}\n\n/**\n * One typed stdin line → say (worker running) or resume (worker finished):\n * the operator channel of a `follow <id>` pane. Trimmed; empty lines and a\n * missing target are ignored.\n */\nexport function makeOperatorInput(d: OperatorInputDeps): (raw: string) => void {\n return (raw: string) => {\n const text = raw.trim();\n if (!text) return;\n const id = d.target();\n if (id === null) return;\n d.say(id, text).then(\n () => (d.sent ? d.sent(id) : d.note(d.paint(\"dim\", `» sent to ${id}`))),\n (e: Error) => {\n if (d.workerKnown(id)) d.resume(text, id);\n else d.note(d.paint(\"red\", `» ${e.message}`));\n }\n );\n };\n}\n\n/** What followWorkers writes to (process.stdout, or a fake in tests). */\nexport interface FollowStream {\n write(s: string): boolean;\n isTTY?: boolean;\n columns?: number;\n rows?: number;\n on?(event: \"resize\", fn: () => void): unknown;\n removeListener?(event: \"resize\", fn: () => void): unknown;\n}\n\n/** The child of a `pai worker resume` spawn (tests inject a fake). */\nexport interface ResumeChild {\n stdout?: { on(event: \"data\", cb: (chunk: Buffer) => void): unknown };\n on(event: \"close\", cb: (code: number | null) => void): unknown;\n}\n\n/** Test seams for followWorkers: the streams, the resume spawn, the prompt. */\nexport interface FollowIO {\n stdin?: NodeJS.ReadableStream;\n stdout?: FollowStream;\n /** replaces the `pai worker resume` spawn (tests record instead of run). */\n spawnResume?: (id: string, text: string) => ResumeChild;\n /** current unsent prompt text (tests force a draft to hold the countdown). */\n promptLine?: () => string;\n}\n\n/**\n * Tail one worker (target) or the running workers of this scope, live.\n * Workers whose event log already exists are first replayed (last\n * BACKFILL_EVENTS events), then tailed. Auto-exit: with a target, wait\n * `autoExit` seconds after its end; without one, exit once no worker in\n * scope has run for that many seconds in a row, never within the first 30 s.\n *\n * With a target on a TTY the pane becomes a chat (chatui.ts): transcript in\n * a scroll region, fixed prompt and ticker rows, submitted lines said to the\n * worker (or resuming it after it finished) and echoed as `»` rows. A draft\n * in the prompt holds the auto-exit countdown. Non-TTY output keeps the\n * plain scrolling behaviour; FORCE_TTY=1 emits the chat layout over a pipe.\n */\nexport async function followWorkers(\n logDir: string,\n target: string | null,\n showAll: boolean,\n autoExit: number,\n env: NodeJS.ProcessEnv = process.env,\n color = process.stdout.isTTY === true,\n io?: FollowIO\n): Promise<void> {\n const c = makeColor(color);\n const out_ = io?.stdout ?? process.stdout;\n const in_ = io?.stdin ?? process.stdin;\n // FORCE_TTY=1: the TTY layout over a pipe (tests, recorded panes)\n const tty = out_.isTTY === true || env.FORCE_TTY === \"1\";\n const term = env.ITERM_SESSION_ID ?? \"\";\n const scopeTab = target || showAll ? \"\" : currentTabKey(env);\n const handles = new Map<string, FollowHandle>();\n const seenHeader = new Set<string>();\n const finished = new Set<string>();\n // handoffs already rendered per worker (id → inbox lines shown so far)\n const inboxSeen = new Map<string, number>();\n const states = new Map<string, FollowState>();\n const started = Date.now();\n let idleSince: number | null = null;\n let aborted = false;\n // liveness state: rewritten in place between events, TTY only\n let lastEventAt = Date.now();\n let meterStatus: WorkerStatus | null = null;\n const onInt = () => {\n aborted = true;\n };\n process.once(\"SIGINT\", onInt);\n\n // --- the chat layout (target + TTY): transcript region, a blank separator\n // row, then the two fixed rows (prompt, ticker)\n const chat = tty && target !== null;\n let rows = out_.rows ?? 24;\n const columns = (): number | null => (typeof out_.columns === \"number\" ? out_.columns : null);\n let fill = 0; // transcript rows filled since the region was (re)set\n const regionRows = () => Math.max(1, rows - 3);\n // the rendered transcript, kept so a resize can replay it onto the cleared\n // pane (chat mode only; capped so a long run cannot grow without bound)\n const retained: string[] = [];\n const RETAIN_CAP = 500;\n /** Every line the pane shows goes through here: plain newline, or a row\n * inserted above the fixed prompt/ticker rows (chatui.chatInsertLine). */\n const out = (line: string, keep = true) => {\n if (!chat) {\n out_.write(line + \"\\n\");\n return;\n }\n if (keep) {\n retained.push(line);\n if (retained.length > RETAIN_CAP) retained.splice(0, retained.length - RETAIN_CAP);\n }\n const r = chatInsertLine(line, fill, regionRows());\n out_.write(r.seq);\n fill = r.fill;\n };\n\n // The ticker redraws its line in place: in the chat layout that is its own\n // bottom row (save-cursor, draw, restore-cursor); the plain mode writes\n // CR + erase-to-end-of-line, never a newline. The control bytes live in\n // their own string literals — bundling them onto a template literal makes\n // the bundler fold them in as raw chars, and a raw CR inside a template\n // literal is normalised to LF by the language, which is how the ticker\n // once scrolled a blank line per tick.\n const eraseLiveness = () => {\n if (chat) return;\n if (tty) out_.write(\"\\r\\x1b[K\");\n };\n let ticker = initialFollowState();\n // the chat ticker row doubles as the worker's status line: provider/model,\n // context meter, turns, tools, runtime — frozen with ✓/✗ once it finished\n const statusRowOf = (st: WorkerStatus, secs: number): StatusRow => {\n const fin = finished.has(st.id);\n const startedAt = Date.parse((st.started ?? \"\").replace(\" \", \"T\"));\n const elapsed =\n fin && st.secs !== null\n ? st.secs\n : Number.isNaN(startedAt)\n ? 0\n : Math.floor((Date.now() - startedAt) / 1000);\n return {\n provider: st.provider,\n model: st.model,\n contextTokens: st.contextTokens,\n contextWindow: st.contextWindow,\n turns: st.turns,\n tools: st.tools,\n elapsed,\n idle: secs,\n intent: ticker.intent,\n tool: ticker.tool,\n state: fin ? (st.state === \"running\" ? \"done\" : st.state) : null,\n };\n };\n const writeLiveness = () => {\n if (!tty) return;\n const secs = Math.max(0, Math.floor((Date.now() - lastEventAt) / 1000));\n if (chat) {\n const text = meterStatus\n ? chatStatusRow(c, statusRowOf(meterStatus, secs))\n : tickerText(secs, ticker.intent, ticker.tool);\n out_.write(chatTickerRow(text, rows, promptCursorCol()));\n } else {\n const meter = meterStatus ? contextMeter(c, meterStatus) : null;\n out_.write(\"\\r\\x1b[K\");\n out_.write(tickerText(secs, ticker.intent, ticker.tool, meter));\n }\n };\n\n const runningIds = (): string[] =>\n loadStatuses(logDir)\n .filter(\n (s) =>\n s.state === \"running\" &&\n alive(s.pid) &&\n (target !== null || showAll || (scopeTab ? workerInScope(s, term) : true))\n )\n .map((s) => s.id);\n\n // --- one rendered event, shared by the backfill and the live tail\n const emitEvent = (e: StreamEventLike, wid: string, st: WorkerStatus, multi: boolean) => {\n if (e.type === \"operator\" && chat && suppressMirror(String(e.text ?? \"\"))) return;\n if (!seenHeader.has(wid)) {\n eraseLiveness();\n out(headerLine(c, st));\n seenHeader.add(wid);\n }\n const state = states.get(wid) ?? initialFollowState();\n const step = applyEvent(\n c,\n state,\n e,\n st.cwd ?? \"\",\n multi ? c(\"cyan\", wid.slice(-4)) : undefined,\n undefined,\n columns()\n );\n if (step.day) {\n out(c(\"dim\", `── ${step.day} ──`));\n }\n states.set(wid, step.state);\n if (wid === target) ticker = step.state;\n eraseLiveness();\n for (const ln of step.lines) {\n out(ln);\n }\n if (step.activity) lastEventAt = Date.now();\n if (e.type === \"result\") {\n meterStatus = st;\n finished.add(wid);\n } else if (wid === target || target === null) {\n meterStatus = st;\n }\n };\n\n // --- attach: replay what is already in the log, then tail from its end\n const attachHandle = (wid: string, path: string, st: WorkerStatus, multi: boolean): FollowHandle => {\n const handle = { fd: openSync(path, \"r\"), buf: \"\" };\n try {\n const existing = readFileSync(path, \"utf8\");\n if (existing.trim()) {\n for (const line of backfillLines(existing)) {\n let e: StreamEventLike;\n try {\n e = JSON.parse(line) as StreamEventLike;\n } catch {\n continue;\n }\n emitEvent(e, wid, st, multi);\n }\n } else {\n // no event yet: show the header now, not at the first event\n if (!seenHeader.has(wid)) {\n out(headerLine(c, st));\n seenHeader.add(wid);\n }\n }\n // the backfill already rendered the file; the live tail starts at EOF\n const sink = Buffer.alloc(65536);\n for (;;) {\n let n: number;\n try {\n n = readSync(handle.fd, sink, 0, sink.length, null);\n } catch {\n break;\n }\n if (n <= 0) break;\n }\n } catch {\n // unreadable log: tail from wherever the fd happens to be\n }\n return handle;\n };\n\n // --- say / resume from this pane's stdin\n const noteLine = (s: string) => {\n eraseLiveness();\n out(s);\n };\n // SpawnOptions (not the stdio-tuple overload): we only read stdout and\n // want the plain ChildProcess shape; tests inject a fake that records\n const spawnResume =\n io?.spawnResume ??\n ((id: string, text: string): ResumeChild =>\n spawn(\"pai\", [\"worker\", \"resume\", id, text, \"--print-id\", \"--no-pane\"], {\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n } as SpawnOptions) as unknown as ResumeChild);\n const resumeTarget = (text: string, id: string) => {\n noteLine(c(\"dim\", `» resuming ${id} …`));\n const child = spawnResume(id, text);\n let idOut = \"\";\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n idOut += chunk.toString(\"utf8\");\n });\n child.on(\"close\", (rc: number | null) => {\n const newId = idOut.trim().split(\"\\n\").pop() ?? \"\";\n if (rc === 0 && /^\\d{8}-\\d{6}-\\d+$/.test(newId)) {\n noteLine(c(\"dim\", `» resumed as ${newId}`));\n target = newId;\n finished.delete(newId);\n seenHeader.delete(newId);\n states.delete(newId);\n ticker = initialFollowState(\"resumed\");\n lastEventAt = Date.now();\n } else {\n noteLine(c(\"red\", `» resume failed (rc=${rc})`));\n }\n });\n };\n const handleOperatorLine = makeOperatorInput({\n target: () => target,\n say: (id, text) => sayToWorker(logDir, id, text),\n workerKnown: (id) => loadStatuses(logDir).some((s) => s.id === id),\n resume: resumeTarget,\n note: noteLine,\n paint: c,\n ...(chat ? { sent: () => undefined } : {}), // the echo replaces the note\n });\n\n // --- the chat line: readline editing on the prompt row\n // the readline interface keeps stdin flowing — without a close, the process\n // outlives follow itself (a pane then never closes, however long ago the\n // worker ended), so it is closed in the finally block below\n const terminalIn = (in_ as { isTTY?: boolean }).isTTY === true;\n let rlIn: ReturnType<typeof createInterface> | null = null;\n let onResize: (() => void) | null = null;\n // an echoed line comes back as a mirrored operator event within moments —\n // remember what was echoed and swallow the twin, so the pane shows what\n // was typed exactly once\n const echoed = new Map<string, { n: number; until: number }>();\n const suppressMirror = (text: string): boolean => {\n const g = echoed.get(text);\n if (!g || Date.now() > g.until) return false;\n g.n -= 1;\n if (g.n <= 0) echoed.delete(text);\n return true;\n };\n /** Column the prompt row's cursor parks at: right after the draft's cursor. */\n const promptCursorCol = (): number => {\n const line = rlIn?.line ?? \"\";\n const cur = (rlIn as unknown as { cursor?: number } | null)?.cursor;\n return 3 + Math.max(0, Math.min(typeof cur === \"number\" ? cur : line.length, line.length));\n };\n /**\n * The pane owns the prompt row's rendering: `› `, the buffer — or the dim\n * placeholder while the buffer is empty — and the cursor parked after it.\n */\n const drawPrompt = () => {\n if (!chat) return;\n const line = rlIn?.line ?? \"\";\n const cur = (rlIn as unknown as { cursor?: number } | null)?.cursor;\n out_.write(chatPromptRow(rows, line, (s) => c(\"dim\", s), cur));\n };\n if (chat) {\n const echoOperator = (text: string) => {\n const id = target;\n if (id === null) return;\n echoed.set(text, { n: 1, until: Date.now() + 10_000 });\n const st = states.get(id) ?? initialFollowState();\n const step = applyEvent(\n c,\n st,\n { type: \"operator\", _ts: new Date().toISOString(), text },\n \"\",\n undefined,\n undefined,\n columns()\n );\n if (step.day) out(c(\"dim\", `── ${step.day} ──`));\n for (const ln of step.lines) out(ln);\n states.set(id, step.state);\n };\n const handleChatLine = (raw: string) => {\n const act = parseChatLine(raw);\n let redrew = false;\n switch (act.kind) {\n case \"message\":\n if (!act.text) break;\n handleOperatorLine(act.text); // send the line …\n drawPrompt(); // … placeholder back before the » echo is written\n redrew = true;\n echoOperator(act.text);\n break;\n case \"resume\":\n if (!act.text) {\n out(c(\"dim\", \"usage: /resume <text>\"));\n break;\n }\n if (target !== null) resumeTarget(act.text, target);\n drawPrompt();\n redrew = true;\n echoOperator(act.text);\n break;\n case \"help\":\n for (const ln of CHAT_HELP) out(c(\"dim\", ln));\n break;\n case \"status\": {\n const s =\n target !== null ? loadStatuses(logDir).find((x) => x.id === target) : undefined;\n out(c(\"dim\", s ? `${s.id} · ${s.state} · ${s.last}` : `${target ?? \"?\"} · no status`));\n break;\n }\n case \"quit\":\n aborted = true;\n break;\n }\n if (!redrew) drawPrompt(); // on a pipe readline does not repaint the row\n };\n // readline edits silently (its output is a mute stream); the pane draws\n // the prompt row itself from the live buffer, so the placeholder yields\n // to the first keystroke and returns when the buffer empties again.\n // The emitter no-ops matter: with terminal:true readline attaches a\n // resize listener on its output, and a bare { write } object crashes\n // follow in a real terminal (\"output.on is not a function\").\n const silent = {\n write: () => true,\n on: () => silent,\n once: () => silent,\n off: () => silent,\n removeListener: () => silent,\n emit: () => false,\n } as unknown as NodeJS.WriteStream;\n rlIn = createInterface({ input: in_, output: silent, terminal: terminalIn });\n rlIn.on(\"line\", handleChatLine);\n // Ctrl-C: an empty prompt leaves, a draft clears; Ctrl-D (close) leaves\n rlIn.on(\"SIGINT\", () => {\n if ((rlIn?.line ?? \"\").trim() === \"\") aborted = true;\n else {\n rlIn?.write(null, { ctrl: true, name: \"u\" });\n drawPrompt();\n }\n });\n rlIn.on(\"close\", () => {\n aborted = true;\n });\n // the separator rule above the prompt row needs the pane's width and the\n // pane's dim colour — chatEnter without cols leaves the row blank\n out_.write(chatEnter(rows, columns() ?? 0, (s) => c(\"dim\", s)));\n drawPrompt();\n if (terminalIn) {\n // every keystroke re-renders the row (and re-parks the cursor) from\n // the buffer readline now holds\n (in_ as NodeJS.ReadableStream).on(\"keypress\", () => drawPrompt());\n }\n // resize: re-read the geometry, rebuild the region, fill it afresh\n onResize = () => {\n if (typeof out_.rows === \"number\") rows = out_.rows;\n fill = 0;\n // clear first: the refill lands rows top-down and must not overwrite\n // the stale transcript left under the old geometry\n out_.write(\n \"\\x1b[2J\" + chatScrollRegion(rows) + chatBlankRow(rows, columns() ?? 0, (s) => c(\"dim\", s))\n );\n // the retained transcript replays in order, newest regionRows() lines\n // only - a shrunken pane shows its newest rows, not a scroll replay\n for (const ln of retained.slice(-regionRows())) out(ln, false);\n drawPrompt();\n };\n out_.on?.(\"resize\", onResize);\n } else if (target !== null && (in_ as { isTTY?: boolean }).isTTY) {\n // plain operator channel: TTY stdin, non-TTY stdout\n rlIn = createInterface({ input: in_ });\n rlIn.on(\"line\", handleOperatorLine);\n }\n\n /** The prompt's unsent text — a draft holds the auto-exit countdown. */\n const promptText = () => (io?.promptLine ? io.promptLine() : (rlIn?.line ?? \"\"));\n\n try {\n for (;;) {\n if (aborted) return;\n const statuses = new Map(loadStatuses(logDir).map((s) => [s.id, s]));\n const wanted = target ? [target] : runningIds();\n for (const wid of wanted) {\n if (handles.has(wid) || finished.has(wid)) continue;\n const path = eventsPath(logDir, wid);\n const st = statuses.get(wid) ?? ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n if (existsSync(path)) {\n const multi = target === null || handles.size > 0;\n handles.set(wid, attachHandle(wid, path, st, multi));\n states.set(wid, states.get(wid) ?? initialFollowState());\n } else if (!seenHeader.has(wid)) {\n // worker just started, no event yet: name it instead of a blank pane\n out(headerLine(c, st));\n seenHeader.add(wid);\n }\n }\n const multi = handles.size > 1 || target === null;\n let progressed = false;\n\n for (const [wid, h] of [...handles.entries()]) {\n const st = statuses.get(wid) ?? ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n // read everything appended since the last poll (fd position advances)\n const buffer = Buffer.alloc(65536);\n for (;;) {\n let n: number;\n try {\n n = readSync(h.fd, buffer, 0, buffer.length, null);\n } catch {\n n = 0;\n }\n if (n <= 0) break;\n h.buf += buffer.toString(\"utf8\", 0, n);\n }\n const lines = h.buf.split(\"\\n\");\n h.buf = lines.pop() ?? \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n progressed = true;\n let e: StreamEventLike;\n try {\n e = JSON.parse(line) as StreamEventLike;\n } catch {\n continue;\n }\n emitEvent(e, wid, st, multi);\n }\n const ended = workerEnded(finished.has(wid), st.state, alive(st.pid));\n if (ended) {\n if (!finished.has(wid)) {\n eraseLiveness();\n out(\n `${multi ? c(\"cyan\", wid.slice(-4)) + c(\"dim\", \" ┃ \") : \" \"}${c(\"red\", \"✗ \" + (st.state || \"ended\"))} · ${st.last ?? \"\"}`\n );\n finished.add(wid);\n }\n closeSync(h.fd);\n handles.delete(wid);\n }\n }\n\n // inbox tail: new handoffs render as ◆ lines in the recipient's pane\n // (they may also arrive via the say mirror — the durable copy is here)\n for (const wid of [...handles.keys()]) {\n const st = statuses.get(wid) ?? ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n const msgs = readInbox(logDir, wid);\n const seenN = inboxSeen.get(wid) ?? 0;\n if (msgs.length > seenN) {\n for (const m of msgs.slice(seenN)) {\n emitEvent(\n { type: \"handoff\", from: m.from, kind: m.kind, text: m.text, _ts: m._ts },\n wid,\n st,\n multi\n );\n }\n inboxSeen.set(wid, msgs.length);\n progressed = true;\n }\n }\n\n const lingerOn = target; // resume swaps `target` under us (see above)\n if (lingerOn !== null && finished.has(lingerOn)) {\n if (autoExit) {\n // a draft in the prompt holds the countdown: the operator may be\n // about to say or resume something\n if (holdAutoExit(promptText())) {\n writeLiveness();\n await sleep(250);\n continue;\n }\n const until = Date.now() + autoExit * 1000;\n while (\n Date.now() < until &&\n !aborted &&\n target === lingerOn &&\n !holdAutoExit(promptText())\n ) {\n writeLiveness();\n await sleep(250);\n }\n // interrupted, resumed inside the window, or a draft appeared: follow on\n if (aborted || target !== lingerOn || holdAutoExit(promptText())) continue;\n eraseLiveness();\n out(c(\"dim\", \"closing\"));\n return;\n }\n // no auto-exit: only a terminal follow with no wired stdin is done —\n // an interactive one stays up for say / resume input\n if (rlIn === null) return;\n }\n if (autoExit && !target) {\n if (runningIds().length) {\n idleSince = null;\n } else if (idleSince === null) {\n idleSince = Date.now();\n } else if (Date.now() - started >= 30_000 && Date.now() - idleSince >= autoExit * 1000) {\n eraseLiveness();\n out(c(\"dim\", \"closing\"));\n return;\n }\n }\n if (!progressed) {\n writeLiveness();\n await sleep(500);\n }\n }\n } finally {\n // close before anything else: an open readline keeps the process (and the\n // iTerm pane running it) alive long after follow has decided to end\n rlIn?.close();\n process.removeListener(\"SIGINT\", onInt);\n if (onResize) out_.removeListener?.(\"resize\", onResize);\n if (chat) out_.write(chatLeave(rows));\n for (const h of handles.values()) {\n try {\n closeSync(h.fd);\n } catch {\n /* already closed */\n }\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n// ---------------------------------------------------------------------------\n// status line\n// ---------------------------------------------------------------------------\n\n/** Workers of this terminal for the status bar; \"\" when there are none. */\nexport function statusLineOutput(\n logDir: string,\n term: string,\n cwd: string,\n claudeSession: string = \"\",\n now: Date = new Date()\n): string {\n const statuses = loadStatuses(logDir);\n const mine = statuses.filter((s) => {\n const sameScope = term && workerInScope(s, term);\n // orchestrator Bash spawns have no terminal identity; their spawner\n // session (the claude session rendering this bar) claims them instead\n const spawnedHere = claudeSession && s.spawnerSession === claudeSession;\n const sameDir = cwd && s.cwd.startsWith(cwd);\n return sameScope || spawnedHere || (!term && sameDir);\n });\n return renderStatusLine(mine, now);\n}\n","/**\n * providers.ts — provider, class and switch management over the workers config.\n *\n * One layer under both `pai worker providers …` and the MCP worker_providers\n * tool. Every mutation re-reads the config file, changes only the workers\n * section, and writes it back atomically — the file is shared with everything\n * else PAI runs, so a torn write is not an option.\n *\n * Keys are never stored in the config and never accepted inline from the CLI:\n * only file paths. (The MCP `add` tool additionally accepts a raw `key`, which\n * it immediately parks in ~/.config/pai/keys/<name>, mode 0600, storing only\n * the path — a chat is not a place to leave a credential lying around.)\n */\n\nimport { existsSync, writeFileSync, chmodSync, mkdirSync } from \"node:fs\";\nimport {\n DEFAULT_LOG_DIR,\n MODEL_CAPABILITIES,\n PROVIDER_TAGS,\n WorkersConfigError,\n expandHome,\n isModelCapability,\n keysDir,\n parseWorkersConfig,\n providerCostTier,\n readWorkersSection,\n writeWorkersSection,\n type ClassTarget,\n type ModelCapability,\n type WorkerProvider,\n type WorkersConfig,\n} from \"./config.js\";\nimport { clearCooldown, probeQuota, quotaSkipThreshold } from \"./routing.js\";\nimport { workersLogDir } from \"./paths.js\";\n\nexport interface AddProviderInput {\n name: string;\n baseUrl: string;\n keyFile?: string | null;\n key?: string;\n model: string;\n fastModel?: string;\n env?: Record<string, string>;\n note?: string;\n protocol?: \"anthropic\" | \"openai\";\n upstreamUrl?: string;\n engine?: \"claude\" | \"codex\";\n quotaProbe?: string;\n contextWindow?: number;\n costTier?: number;\n tags?: string[];\n}\n\nexport function addProvider(input: AddProviderInput): WorkersConfig {\n if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(input.name)) {\n throw new WorkersConfigError(\n `provider name \"${input.name}\" may only contain letters, digits, - and _`\n );\n }\n if (input.protocol === \"openai\" && !input.upstreamUrl) {\n throw new WorkersConfigError(\n `protocol \"openai\" needs the upstreamUrl (the Chat Completions base, ` +\n `e.g. \"https://api.openai.com/v1\") — the PAI proxy translates to it.`\n );\n }\n if (!input.baseUrl && input.protocol !== \"openai\") {\n throw new WorkersConfigError(\"baseUrl is required\");\n }\n\n let keyFile = input.keyFile ?? null;\n if (input.key !== undefined) {\n const dir = keysDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const path = `${dir}/${input.name}`;\n writeFileSync(path, input.key.trim() + \"\\n\", { encoding: \"utf8\", mode: 0o600 });\n try {\n chmodSync(path, 0o600);\n } catch {\n /* mode above already applied on create where supported */\n }\n keyFile = path;\n }\n\n const { raw, workers } = readWorkersSection();\n const provider: WorkerProvider = {\n enabled: true,\n protocol: input.protocol ?? \"anthropic\",\n baseUrl: input.baseUrl,\n keyFile,\n models: input.fastModel\n ? { default: input.model, fast: input.fastModel }\n : { default: input.model },\n env: input.env ?? {},\n ...(input.note ? { note: input.note } : {}),\n ...(input.upstreamUrl ? { upstreamUrl: input.upstreamUrl } : {}),\n ...(input.engine && input.engine !== \"claude\" ? { engine: input.engine } : {}),\n ...(input.quotaProbe ? { quotaProbe: input.quotaProbe } : {}),\n ...(input.contextWindow ? { contextWindow: input.contextWindow } : {}),\n ...(input.costTier ? { costTier: input.costTier } : {}),\n ...(input.tags?.length ? { tags: input.tags as WorkerProvider[\"tags\"] } : {}),\n };\n workers.providers[input.name] = provider;\n\n // First provider takes over the whole section: active, default classes, pane.\n const first = Object.keys(workers.providers).length === 1;\n if (first) {\n workers.enabled = true;\n workers.active = input.name;\n workers.logDir = workers.logDir || DEFAULT_LOG_DIR;\n const fast = input.fastModel ? `${input.name}/fast` : input.name;\n workers.classes = {\n draft: fast,\n plan: input.name,\n implement: input.name,\n review: input.name,\n research: input.name,\n spotcheck: fast,\n simple: fast,\n complex: input.name,\n image: input.name,\n };\n }\n\n writeWorkersSection(raw, workers);\n return workers;\n}\n\n/** Change costTier / tags on an existing provider (MCP action \"update\"). */\nexport function updateProvider(\n name: string,\n changes: { costTier?: number; tags?: string[] }\n): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n const p = workers.providers[name];\n if (!p) {\n throw new WorkersConfigError(\n `no provider named \"${name}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n if (changes.costTier !== undefined) {\n if (!Number.isInteger(changes.costTier) || changes.costTier < 1 || changes.costTier > 5) {\n throw new WorkersConfigError(\"costTier must be an integer 1 (cheapest) … 5 (most expensive)\");\n }\n p.costTier = changes.costTier;\n }\n if (changes.tags !== undefined) {\n for (const t of changes.tags) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n throw new WorkersConfigError(`\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n }\n p.tags = changes.tags as WorkerProvider[\"tags\"];\n }\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function removeProvider(name: string): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (!workers.providers[name]) {\n throw new WorkersConfigError(`no provider named \"${name}\"`);\n }\n delete workers.providers[name];\n for (const [cls, target] of Object.entries(workers.classes)) {\n const targetProvider = typeof target === \"string\" ? target.split(\"/\")[0] : target.provider;\n if (targetProvider === name) delete workers.classes[cls];\n }\n if (workers.active === name) workers.active = null;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function useProvider(name: string): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (!workers.providers[name]) {\n throw new WorkersConfigError(\n `no provider named \"${name}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n workers.active = name;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\n/** Which model capability of a provider a set touches (MODEL_CAPABILITIES). */\nexport type ModelSlot = ModelCapability;\n\n/**\n * The provider a model command targets: the named one, else the active one.\n * \"auto\" and an unset active both ask for an explicit name.\n */\nexport function resolveProviderName(workers: WorkersConfig, name?: string): string {\n const target = name ?? workers.active ?? \"\";\n if (workers.providers[target]) return target;\n if (!target) throw new WorkersConfigError(\"no active provider — name one explicitly\");\n if (target === \"auto\") {\n throw new WorkersConfigError('active is \"auto\" — name a provider explicitly');\n }\n throw new WorkersConfigError(\n `no provider named \"${target}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n}\n\n/**\n * Set a provider's model id for a capability — default, fast, image, …\n * (`pai worker model`, MCP worker_model).\n */\nexport function setProviderModel(\n name: string,\n capability: ModelCapability,\n model: string,\n configPath?: string\n): WorkersConfig {\n if (!isModelCapability(capability)) {\n throw new WorkersConfigError(\n `\"${capability}\" is not a model capability (from: ${MODEL_CAPABILITIES.join(\", \")})`\n );\n }\n const id = model.trim();\n if (!id) throw new WorkersConfigError(`a ${capability} model id must not be empty`);\n const { raw, workers } = readWorkersSection(configPath);\n const p = workers.providers[name];\n if (!p) {\n throw new WorkersConfigError(\n `no provider named \"${name}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n p.models[capability] = id;\n writeWorkersSection(raw, workers, configPath);\n return workers;\n}\n\nexport function setProviderEnabled(name: string, enabled: boolean): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n const p = workers.providers[name];\n if (!p) throw new WorkersConfigError(`no provider named \"${name}\"`);\n p.enabled = enabled;\n writeWorkersSection(raw, workers);\n // `enable` is also the manual cooldown-clear (spec: routing)\n if (enabled) clearCooldown(workersLogDir(parseWorkersConfig(raw.workers)), name);\n return workers;\n}\n\n/**\n * Point a class at a target (\"provider\", \"provider/fast\", or an object with\n * provider + optional mcp/maxCostTier/requireTags/order). An object without\n * a provider only constrains auto-routing for that class.\n */\nexport function setClass(name: string, target: ClassTarget): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (typeof target === \"string\") {\n const [provider, alias] = target.split(\"/\");\n const p = workers.providers[provider];\n if (!p) {\n throw new WorkersConfigError(\n `no provider named \"${provider}\" in \"${target}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n if (alias) {\n if (!isModelCapability(alias)) {\n throw new WorkersConfigError(\n `unknown model alias \"${alias}\" — providers expose: ${MODEL_CAPABILITIES.join(\", \")}`\n );\n }\n if (alias !== \"default\" && !p.models[alias]) {\n throw new WorkersConfigError(`provider \"${provider}\" has no ${alias} model configured`);\n }\n }\n } else if (target.provider) {\n if (!workers.providers[target.provider]) {\n throw new WorkersConfigError(\n `no provider named \"${target.provider}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n }\n workers.classes[name] = target;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function unsetClass(name: string): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (!(name in workers.classes)) {\n throw new WorkersConfigError(`no class named \"${name}\"`);\n }\n delete workers.classes[name];\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function setWorkersEnabled(enabled: boolean): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n workers.enabled = enabled;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\n/** `glm/fast` | `{provider, mcp, …}` → one printable target line. */\nexport function classTargetText(target: ClassTarget): string {\n if (typeof target === \"string\") return target;\n const bits = [\n target.provider ?? \"(routing)\",\n ...(target.mcp?.length ? [`mcp(${target.mcp.join(\",\")})`] : []),\n ...(target.maxCostTier !== undefined ? [`max tier ${target.maxCostTier}`] : []),\n ...(target.requireTags?.length ? [`needs ${target.requireTags.join(\",\")}`] : []),\n ...(target.order?.length ? [`order [${target.order.join(\",\")}]`] : []),\n ];\n return bits.join(\" \");\n}\n\n/**\n * `default X fast Y image Z` — every model capability on one line, unset\n * ones shown as \"(none)\" (they resolve to the default model).\n */\nexport function modelPrefsText(p: WorkerProvider): string {\n return MODEL_CAPABILITIES.map((c) => `${c} ${p.models[c] ?? \"(none)\"}`).join(\" \");\n}\n\n/**\n * Compact model listing for `pai worker model` / worker_model get: the active\n * provider, then one line per provider with all its capability preferences.\n */\nexport function describeModels(workers: WorkersConfig): string[] {\n const names = Object.keys(workers.providers);\n if (!names.length) {\n return [\"no providers configured — add one with `pai worker providers add <name> …`\"];\n }\n const lines = [`active provider: ${workers.active ?? \"(none)\"}`];\n for (const name of names) {\n const p = workers.providers[name];\n const active = workers.active === name ? \" [active]\" : \"\";\n lines.push(`${name}${active} ${modelPrefsText(p)}`);\n }\n return lines;\n}\n\n/** Human-readable provider listing (quota probe included when configured). */\nexport function describeProviders(workers: WorkersConfig): string[] {\n const lines: string[] = [];\n const names = Object.keys(workers.providers);\n if (!names.length) {\n lines.push(`no providers configured. Add one with:`);\n lines.push(\n ` pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`\n );\n return lines;\n }\n for (const name of names) {\n const p = workers.providers[name];\n const flags = [\n p.enabled ? \"enabled\" : \"disabled\",\n workers.active === name ? \"active\" : null,\n ].filter(Boolean);\n const quota = p.quotaProbe ? probeQuota(p) : null;\n const quotaNote = quota === null ? \"\" : ` quota ${quota}% (skip at ${quotaSkipThreshold(p)})`;\n const tierTags = [`tier ${providerCostTier(p)}`, ...(p.tags ?? [])].join(\", \");\n lines.push(`${name} [${flags.join(\", \")}] ${p.baseUrl}`);\n lines.push(` ${modelPrefsText(p)}${quotaNote}`);\n lines.push(` ${tierTags}`);\n if (p.keyFile) lines.push(` key file ${expandHome(p.keyFile)}`);\n else lines.push(` no key file (token \"local\")`);\n if (p.note) lines.push(` ${p.note}`);\n if (p.protocol === \"openai\") {\n lines.push(` via PAI proxy ← ${p.upstreamUrl ?? \"(upstreamUrl missing)\"}`);\n }\n if (p.engine === \"codex\") {\n lines.push(` engine codex (runs through the Codex CLI)`);\n }\n }\n const setNames = Object.keys(workers.mcpSets);\n if (setNames.length) {\n lines.push(`mcp sets: ${setNames.map((s) => `${s}=[${workers.mcpSets[s].join(\",\")}]`).join(\" \")}`);\n }\n const classNames = Object.keys(workers.classes);\n if (classNames.length) {\n lines.push(`classes: ${classNames.map((cl) => `${cl}=${classTargetText(workers.classes[cl])}`).join(\" \")}`);\n }\n if (workers.active === \"auto\") {\n lines.push(`routing: auto — order [${workers.routing.order.join(\", \")}], cooldown ${workers.routing.cooldownMinutes}m`);\n }\n return lines;\n}\n","/**\n * fallback.ts — machine-wide Claude Code fallback to a worker provider.\n *\n * When the Anthropic plan runs out, `pai worker fallback on <provider>`\n * points EVERY new Claude Code process on this machine at that provider:\n * interactive sessions, task-bus sessions, the daemon's headless summarizer.\n * It writes the provider's base URL, token (read from its key file at switch\n * time — so the token then sits in settings.json until `off`), the three\n * DEFAULT_*_MODEL pins, its extra env (API_TIMEOUT_MS …) plus tool search on\n * and nonessential traffic off into the `env` block of ~/.claude/settings.json,\n * and pins the top-level `model` to the provider's default model.\n *\n * What settings.json carried before the switch is saved under\n * `workers.fallback.saved` in ~/.config/pai/config.json; `fallback off`\n * restores it exactly and removes the added keys. Both files are written\n * atomically; nothing else in them is touched. While fallback is on the\n * Agent hook keeps routing subagents to workers, unchanged — those runs set\n * their own env per provider and are unaffected.\n *\n * Running Claude Code processes keep the provider they started with until\n * restarted; `fallback status` lists them and points at the note `on` leaves\n * in the workers log dir (FALLBACK-ACTIVE.md).\n *\n * CLAUDE_SETTINGS_PATH overrides the settings.json location (dry runs against\n * a copy); the config path is injectable for tests the same way.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../config/json-store.js\";\nimport {\n WorkersConfigError,\n providerKeyPath,\n readWorkersSection,\n resolveModelCapability,\n writeWorkersSection,\n type WorkerProvider,\n type WorkersFallback,\n} from \"./config.js\";\nimport { resolveTarget } from \"./routing.js\";\nimport { workersLogDir } from \"./paths.js\";\n\n/** settings.json location; CLAUDE_SETTINGS_PATH points it at a copy for dry runs. */\nexport function fallbackSettingsPath(): string {\n return process.env.CLAUDE_SETTINGS_PATH ?? join(homedir(), \".claude\", \"settings.json\");\n}\n\n/** The note `on` writes so a human landing in the log dir sees the switch. */\nexport function fallbackNotePath(logDir: string): string {\n return join(logDir, \"FALLBACK-ACTIVE.md\");\n}\n\nexport interface FallbackPaths {\n /** settings.json to switch (default: fallbackSettingsPath()). */\n settingsPath?: string;\n /** pai config.json holding the workers section (default: the real one). */\n configPath?: string;\n}\n\nexport interface FallbackOnResult {\n provider: string;\n /** True when fallback was already on for this provider (env re-applied, saved state kept). */\n alreadyOn: boolean;\n /** Env keys written into settings.json. */\n envKeys: string[];\n /** Model the top-level pin was set to. */\n model: string;\n}\n\nexport interface FallbackOffResult {\n provider: string;\n /** Env keys removed or restored in settings.json. */\n envKeys: string[];\n}\n\n/**\n * The env block fallback writes: the provider's endpoint and token, model\n * pins (fast model as haiku, default as sonnet and opus — the same mapping\n * buildRunEnv uses), the provider's own env, and the two fallback constants.\n */\nexport function fallbackEnv(provider: WorkerProvider): Record<string, string> {\n let token = \"local\";\n const keyPath = providerKeyPath(provider);\n if (keyPath) {\n token = readFileSync(keyPath, \"utf8\").trim();\n if (!token) throw new WorkersConfigError(`key file is empty: ${keyPath}`);\n }\n return {\n ANTHROPIC_BASE_URL: provider.baseUrl,\n ANTHROPIC_AUTH_TOKEN: token,\n ANTHROPIC_DEFAULT_OPUS_MODEL: provider.models.default,\n ANTHROPIC_DEFAULT_SONNET_MODEL: provider.models.default,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: resolveModelCapability(provider, \"fast\"),\n ...provider.env,\n ENABLE_TOOL_SEARCH: \"true\",\n CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: \"1\",\n };\n}\n\nfunction assertFallbackCapable(name: string, p: WorkerProvider): void {\n if (p.protocol === \"openai\") {\n throw new WorkersConfigError(\n `provider \"${name}\" speaks the openai protocol and only works through the live PAI proxy — ` +\n `fallback needs an anthropic-protocol provider every process can reach directly.`\n );\n }\n if (p.engine === \"codex\") {\n throw new WorkersConfigError(\n `provider \"${name}\" runs through the Codex CLI — fallback switches Claude Code itself and needs a claude-engine provider.`\n );\n }\n if (!p.baseUrl) {\n throw new WorkersConfigError(`provider \"${name}\" has no baseUrl — fallback cannot point at it.`);\n }\n}\n\n/** The env block of a parsed settings.json record, or null when absent. */\nfunction envBlockOf(settings: Record<string, unknown>): Record<string, unknown> | null {\n return typeof settings.env === \"object\" && settings.env !== null\n ? (settings.env as Record<string, unknown>)\n : null;\n}\n\n/** Apply a saved state to a parsed settings.json record (inverse of the switch). */\nfunction restoreSettings(\n settings: Record<string, unknown>,\n saved: WorkersFallback[\"saved\"]\n): string[] {\n const keys = Object.keys(saved.env);\n if (saved.envExisted) {\n const env = { ...(typeof settings.env === \"object\" && settings.env !== null ? (settings.env as Record<string, unknown>) : {}) };\n for (const k of keys) {\n const v = saved.env[k];\n if (v === null) delete env[k];\n else env[k] = v;\n }\n settings.env = env;\n } else {\n delete settings.env;\n }\n if (saved.model === null) delete settings.model;\n else settings.model = saved.model;\n return keys;\n}\n\nfunction writeFallbackNote(logDir: string, r: FallbackOnResult): string {\n const path = fallbackNotePath(logDir);\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n writeFileSync(\n path,\n [\n `# Fallback active`,\n ``,\n `- switched: ${new Date().toISOString().slice(0, 19).replace(\"T\", \" \")} UTC`,\n `- provider: ${r.provider} — every NEW Claude Code process on this machine runs on it`,\n `- settings.json: env got ${r.envKeys.join(\", \")}; top-level model pinned to ${r.model}`,\n `- turn off: pai worker fallback off (restores settings.json exactly)`,\n ``,\n `## Running sessions`,\n ``,\n `restart this session in its project directory; the new process uses ${r.provider}; keep the same AIBroker name`,\n ``,\n ].join(\"\\n\"),\n \"utf8\"\n );\n return path;\n}\n\n/**\n * Switch every new Claude Code process to a worker provider.\n *\n * Write order: the saved state lands in the pai config BEFORE settings.json\n * is touched, so a crash between the two writes leaves `off` able to restore\n * (and `on` re-applies idempotently, healing the gap).\n */\nexport function fallbackOn(providerFlag: string | undefined, paths: FallbackPaths = {}): FallbackOnResult {\n const settingsPath = paths.settingsPath ?? fallbackSettingsPath();\n const { raw, workers } = readWorkersSection(paths.configPath);\n const logDir = workersLogDir(workers);\n const target = resolveTarget(workers, logDir, { flagProvider: providerFlag });\n const { providerName, provider } = target;\n assertFallbackCapable(providerName, provider);\n\n const env = fallbackEnv(provider);\n const envKeys = Object.keys(env);\n const settings = readJsonStrict(settingsPath, settingsPath);\n\n const prev = workers.fallback;\n if (prev && prev.provider === providerName) {\n // Already on for this provider: re-apply (heals a crash between the two\n // writes) but keep the ORIGINAL saved state and stamp — that is what `off`\n // must restore to.\n const eb = envBlockOf(settings);\n settings.env = { ...(eb ?? {}), ...env };\n settings.model = provider.models.default;\n writeJsonAtomic(settingsPath, settings, { label: settingsPath });\n return { provider: providerName, alreadyOn: true, envKeys, model: provider.models.default };\n }\n if (prev) {\n // Switching providers while on: restore what the first switch saved, then\n // save fresh from the restored state.\n restoreSettings(settings, prev.saved);\n }\n const envBlock = envBlockOf(settings);\n\n const saved: WorkersFallback[\"saved\"] = {\n env: {},\n model: typeof settings.model === \"string\" ? settings.model : null,\n envExisted: envBlock !== null,\n };\n for (const k of envKeys) {\n saved.env[k] = envBlock && typeof envBlock[k] === \"string\" ? (envBlock[k] as string) : null;\n }\n\n workers.fallback = { provider: providerName, saved, on: new Date().toISOString() };\n writeWorkersSection(raw, workers, paths.configPath);\n\n settings.env = { ...(envBlock ?? {}), ...env };\n settings.model = provider.models.default;\n writeJsonAtomic(settingsPath, settings, { label: settingsPath });\n\n const r: FallbackOnResult = { provider: providerName, alreadyOn: false, envKeys, model: provider.models.default };\n writeFallbackNote(logDir, r);\n return r;\n}\n\n/** Restore settings.json exactly and clear the switch. Errors when not on. */\nexport function fallbackOff(paths: FallbackPaths = {}): FallbackOffResult {\n const settingsPath = paths.settingsPath ?? fallbackSettingsPath();\n const { raw, workers } = readWorkersSection(paths.configPath);\n const fb = workers.fallback;\n if (!fb) throw new WorkersConfigError(\"fallback is not on — nothing to restore\");\n\n const settings = readJsonStrict(settingsPath, settingsPath);\n const envKeys = restoreSettings(settings, fb.saved);\n writeJsonAtomic(settingsPath, settings, { label: settingsPath });\n\n workers.fallback = null;\n writeWorkersSection(raw, workers, paths.configPath);\n\n try {\n const note = fallbackNotePath(workersLogDir(workers));\n if (existsSync(note)) unlinkSync(note);\n } catch {\n /* the note is advisory; its removal must never fail the restore */\n }\n return { provider: fb.provider, envKeys };\n}\n\nexport interface FallbackStatus {\n on: boolean;\n provider: string | null;\n /** ISO stamp of the switch, when on. */\n onAt: string | null;\n /** FALLBACK-ACTIVE.md path, when on. */\n notePath: string | null;\n /** Where the running-session list came from. */\n sessionsSource: \"aibroker\" | \"ps\";\n /** One line per running Claude Code process/session. */\n sessions: string[];\n}\n\n/** Running Claude Code processes: the AIBroker managers registry when it has\n * entries, else `ps`. Informational — sessions keep their provider until\n * restarted whatever this shows. */\nexport function listClaudeSessions(): { source: \"aibroker\" | \"ps\"; lines: string[] } {\n // AIBroker registry (~/.aibroker/managers.json): manager name → session\n // record. Best-effort — any parse problem falls through to ps.\n try {\n const regPath = join(homedir(), \".aibroker\", \"managers.json\");\n if (existsSync(regPath)) {\n const reg = JSON.parse(readFileSync(regPath, \"utf8\")) as Record<string, unknown>;\n const names = Object.keys(reg);\n if (names.length) {\n const lines = names.map((name) => {\n const rec = typeof reg[name] === \"object\" && reg[name] !== null ? (reg[name] as Record<string, unknown>) : {};\n const bits = [name];\n for (const field of [\"session\", \"cwd\", \"pid\"] as const) {\n const v = rec[field];\n if (typeof v === \"string\" || typeof v === \"number\") bits.push(`${field} ${v}`);\n }\n return bits.join(\" \");\n });\n return { source: \"aibroker\", lines };\n }\n }\n } catch {\n /* fall through to ps */\n }\n try {\n const out = execFileSync(\"/bin/ps\", [\"-eo\", \"pid=,etime=,command=\"], {\n encoding: \"utf8\",\n timeout: 10_000,\n });\n // pid, etime, then the command; keep only lines whose command is a\n // `claude` executable (the `cli.js` shape also matches other daemons)\n const lines = out\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter(Boolean)\n .map((l) => {\n const m = l.match(/^\\d+\\s+\\S+\\s+(.*)$/);\n return m ? m[1] : \"\";\n })\n .filter((cmd) => {\n const first = cmd.split(/\\s+/)[0] ?? \"\";\n return first === \"claude\" || first.endsWith(\"/claude\");\n })\n .map((cmd) => (cmd.length > 100 ? cmd.slice(0, 97) + \"…\" : cmd))\n .slice(0, 20);\n return { source: \"ps\", lines };\n } catch {\n return { source: \"ps\", lines: [] };\n }\n}\n\nexport function fallbackStatus(paths: FallbackPaths = {}): FallbackStatus {\n const { workers } = readWorkersSection(paths.configPath);\n const fb = workers.fallback;\n const sessions = listClaudeSessions();\n if (!fb) {\n return {\n on: false,\n provider: null,\n onAt: null,\n notePath: null,\n sessionsSource: sessions.source,\n sessions: sessions.lines,\n };\n }\n const notePath = fallbackNotePath(workersLogDir(workers));\n return {\n on: true,\n provider: fb.provider,\n onAt: fb.on,\n notePath,\n sessionsSource: sessions.source,\n sessions: sessions.lines,\n };\n}\n\n/** Human-readable status block for the CLI and the MCP tool. */\nexport function fallbackStatusText(s: FallbackStatus): string[] {\n const lines: string[] = [];\n if (!s.on) {\n lines.push(\"fallback: off — new Claude Code processes run on the Anthropic login\");\n lines.push(\"turn on with: pai worker fallback on [provider]\");\n } else {\n lines.push(`fallback: on — provider ${s.provider} (since ${s.onAt})`);\n lines.push(\"every NEW Claude Code process runs on it; turn off with: pai worker fallback off\");\n if (s.notePath) {\n lines.push(`note: ${s.notePath}${existsSync(s.notePath) ? \"\" : \" (missing)\"}`);\n }\n }\n lines.push(\"\");\n lines.push(\n s.sessions.length\n ? `running Claude Code sessions (${s.sessionsSource}; they keep their current provider until restarted):`\n : `no running Claude Code sessions detected (${s.sessionsSource})`\n );\n lines.push(...(s.sessions.length ? s.sessions.map((l) => ` ${l}`) : []));\n return lines;\n}\n"],"mappings":"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"}
|