@tekmidian/pai 0.41.0 → 0.42.0
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-DGXNoTwc.mjs} +70 -329
- package/dist/chain-DGXNoTwc.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-DD52BPB-.mjs → config-C_ErGddD.mjs} +1 -1
- package/dist/{config-Dl8lT4Lu.mjs → config-DgzR5sfb.mjs} +2 -2
- package/dist/{config-Dl8lT4Lu.mjs.map → config-DgzR5sfb.mjs.map} +1 -1
- package/dist/{context-handover-cache-C6_rs7JT.mjs → context-handover-cache-dUnSZwaA.mjs} +4 -4
- package/dist/{context-handover-cache-C6_rs7JT.mjs.map → context-handover-cache-dUnSZwaA.mjs.map} +1 -1
- package/dist/daemon/index.mjs +13 -12
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-CKBkhOsZ.mjs +23 -0
- package/dist/{daemon-CpdVUnl_.mjs → daemon-DsvP8KMy.mjs} +357 -27
- package/dist/daemon-DsvP8KMy.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-5UJO-RyX.mjs} +5 -4
- package/dist/{fallback-CgQ_x4I7.mjs.map → fallback-5UJO-RyX.mjs.map} +1 -1
- package/dist/hooks/worker-status-line.mjs +9 -4
- 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-CM1IHbuu.mjs → main-resolver-BgIrC-D9.mjs} +4 -214
- package/dist/main-resolver-BgIrC-D9.mjs.map +1 -0
- package/dist/main-resolver-DXGuygFG.mjs +7 -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-BLDJt3ZS.mjs} +6 -5
- package/dist/{planner-DsmMRIZR.mjs.map → planner-BLDJt3ZS.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-CruRnLtc.mjs} +36 -33
- package/dist/{program-Bed-Pd-8.mjs.map → program-CruRnLtc.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-NIwCGxRI.mjs +351 -0
- package/dist/scope-NIwCGxRI.mjs.map +1 -0
- package/dist/{server-BOuAOj9b.mjs → server-Cp7tNzNC.mjs} +19 -2
- package/dist/server-Cp7tNzNC.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-B3nl8yyn.mjs → work-queue-worker-7Vkli4Yh.mjs} +4 -4
- package/dist/{work-queue-worker-B3nl8yyn.mjs.map → work-queue-worker-7Vkli4Yh.mjs.map} +1 -1
- package/dist/work-queue-worker-D1p2V-ae.mjs +14 -0
- 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/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/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/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,106 +1,13 @@
|
|
|
1
|
-
import { a as WorkersConfigError, h as resolveModelCapability, l as isModelCapability, m as readWorkersSection, o as assertProviderRunnable, p as providerKeyPath, s as classModelCapability } from "./config-
|
|
2
|
-
import { _ as workersLogDir, d as setCooldown, f as eventsPath, g as statusPath, h as panesDir, i as buildRunEnv, m as noMcpConfigPath, n as ensureProxyRunning, o as isQuotaFailure, p as ledgerPath, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveTarget } from "./server-
|
|
3
|
-
import {
|
|
1
|
+
import { a as WorkersConfigError, h as resolveModelCapability, l as isModelCapability, m as readWorkersSection, o as assertProviderRunnable, p as providerKeyPath, s as classModelCapability } from "./config-DgzR5sfb.mjs";
|
|
2
|
+
import { _ as workersLogDir, d as setCooldown, f as eventsPath, g as statusPath, h as panesDir, i as buildRunEnv, m as noMcpConfigPath, n as ensureProxyRunning, o as isQuotaFailure, p as ledgerPath, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveTarget } from "./server-Cp7tNzNC.mjs";
|
|
3
|
+
import { S as stripPromptValues, _ as newWorkerId, a as resolveSpawnerSession, b as parseRunnerArgs, d as alive, g as loadStatuses, h as loadStatus, i as resolveSession, l as UNLABELED, m as describeTool, o as scopeKey, r as itermUuid, v as nowStamp, x as shortText, y as saveStatus } from "./scope-NIwCGxRI.mjs";
|
|
4
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
4
5
|
import { homedir, tmpdir } from "node:os";
|
|
5
|
-
import { dirname, join, relative } from "node:path";
|
|
6
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
7
|
import { connect, createServer } from "node:net";
|
|
7
8
|
import { execFileSync, spawn } from "node:child_process";
|
|
8
9
|
import { createInterface } from "node:readline";
|
|
9
10
|
|
|
10
|
-
//#region src/workers/args.ts
|
|
11
|
-
function parseRunnerArgs(argv) {
|
|
12
|
-
let prompt = null;
|
|
13
|
-
let outputFormat = "text";
|
|
14
|
-
const rest = [];
|
|
15
|
-
let headless = false;
|
|
16
|
-
let callerModel = false;
|
|
17
|
-
let callerMcpConfig = false;
|
|
18
|
-
let callerSystemPrompt = false;
|
|
19
|
-
const mcp = [];
|
|
20
|
-
const allowedTools = [];
|
|
21
|
-
let i = 0;
|
|
22
|
-
while (i < argv.length) {
|
|
23
|
-
const a = argv[i];
|
|
24
|
-
if (a === "-p" || a === "--print") {
|
|
25
|
-
headless = true;
|
|
26
|
-
rest.push(a);
|
|
27
|
-
if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
28
|
-
prompt = argv[i + 1];
|
|
29
|
-
rest.push(prompt);
|
|
30
|
-
i += 1;
|
|
31
|
-
}
|
|
32
|
-
} else if (a === "--output-format") {
|
|
33
|
-
const v = argv[i + 1];
|
|
34
|
-
if (v === "json" || v === "stream-json") outputFormat = v;
|
|
35
|
-
i += 1;
|
|
36
|
-
} else if (a.startsWith("--output-format=")) {
|
|
37
|
-
const v = a.slice(16);
|
|
38
|
-
if (v === "json" || v === "stream-json") outputFormat = v;
|
|
39
|
-
} else if (a === "--verbose") {} else if (a === "--mcp") {
|
|
40
|
-
const v = argv[i + 1];
|
|
41
|
-
if (v !== void 0 && !v.startsWith("-")) {
|
|
42
|
-
mcp.push(v);
|
|
43
|
-
i += 1;
|
|
44
|
-
}
|
|
45
|
-
} else if (a.startsWith("--mcp=")) mcp.push(a.slice(6));
|
|
46
|
-
else if (a === "--allowedTools") {
|
|
47
|
-
rest.push(a);
|
|
48
|
-
const v = argv[i + 1];
|
|
49
|
-
if (v !== void 0 && !v.startsWith("-")) {
|
|
50
|
-
allowedTools.push(v);
|
|
51
|
-
rest.push(v);
|
|
52
|
-
i += 1;
|
|
53
|
-
}
|
|
54
|
-
} else if (a.startsWith("--allowedTools=")) {
|
|
55
|
-
allowedTools.push(a.slice(15));
|
|
56
|
-
rest.push(a);
|
|
57
|
-
} else {
|
|
58
|
-
if (a === "--model") callerModel = true;
|
|
59
|
-
if (a.startsWith("--model=")) callerModel = true;
|
|
60
|
-
if (a === "--mcp-config") callerMcpConfig = true;
|
|
61
|
-
if (a.startsWith("--mcp-config=")) callerMcpConfig = true;
|
|
62
|
-
if (a === "--append-system-prompt") callerSystemPrompt = true;
|
|
63
|
-
if (a.startsWith("--append-system-prompt=")) callerSystemPrompt = true;
|
|
64
|
-
if (prompt === null && !a.startsWith("-") && rest.length > 0 && (rest[rest.length - 1] === "-p" || rest[rest.length - 1] === "--print")) prompt = a;
|
|
65
|
-
rest.push(a);
|
|
66
|
-
}
|
|
67
|
-
i += 1;
|
|
68
|
-
}
|
|
69
|
-
return {
|
|
70
|
-
prompt,
|
|
71
|
-
outputFormat,
|
|
72
|
-
rest,
|
|
73
|
-
headless,
|
|
74
|
-
callerModel,
|
|
75
|
-
callerMcpConfig,
|
|
76
|
-
callerSystemPrompt,
|
|
77
|
-
mcp,
|
|
78
|
-
allowedTools
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Turn `-p "<prompt>"` into bare `-p` (same for --print): for stream-json
|
|
83
|
-
* stdin runs the prompt moves to the first user message on stdin, so the
|
|
84
|
-
* value must not stay on the command line. Only prompt values directly
|
|
85
|
-
* following the flag are touched; everything else passes through.
|
|
86
|
-
*/
|
|
87
|
-
function stripPromptValues(argv) {
|
|
88
|
-
const out = [];
|
|
89
|
-
for (let i = 0; i < argv.length; i++) {
|
|
90
|
-
const a = argv[i];
|
|
91
|
-
const isPrint = a === "-p" || a === "--print";
|
|
92
|
-
out.push(a);
|
|
93
|
-
if (isPrint && i + 1 < argv.length && !argv[i + 1].startsWith("-")) i += 1;
|
|
94
|
-
}
|
|
95
|
-
return out;
|
|
96
|
-
}
|
|
97
|
-
/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */
|
|
98
|
-
function shortText(s, n) {
|
|
99
|
-
const t = String(s ?? "").split(/\s+/).filter(Boolean).join(" ");
|
|
100
|
-
return t.length <= n ? t : t.slice(0, n - 1) + "…";
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
//#endregion
|
|
104
11
|
//#region src/workers/ledger.ts
|
|
105
12
|
/**
|
|
106
13
|
* ledger.ts — the routing ledger, one append-only log for every worker event.
|
|
@@ -170,231 +77,6 @@ function ledgerSummary(path, scope, lastN = 15, now = /* @__PURE__ */ new Date()
|
|
|
170
77
|
};
|
|
171
78
|
}
|
|
172
79
|
|
|
173
|
-
//#endregion
|
|
174
|
-
//#region src/workers/status.ts
|
|
175
|
-
/**
|
|
176
|
-
* status.ts — the live per-worker status file.
|
|
177
|
-
*
|
|
178
|
-
* One JSON file per run, rewritten atomically on every turn, read by `ps`,
|
|
179
|
-
* `follow`, the status line and the pane logic. Same field set the Python
|
|
180
|
-
* runner wrote, plus `provider` (routing is multi-provider now) and `session`
|
|
181
|
-
* (the AIBroker identity of the launching session, see scope.ts).
|
|
182
|
-
*/
|
|
183
|
-
/** Label a worker gets when launched with neither --label nor a prompt. */
|
|
184
|
-
const UNLABELED = "unlabeled";
|
|
185
|
-
/** Context-meter percentage 0–100, null when the numbers are missing. */
|
|
186
|
-
function contextPercent(s) {
|
|
187
|
-
if (!s.contextTokens || !s.contextWindow) return null;
|
|
188
|
-
return Math.round(s.contextTokens / s.contextWindow * 100);
|
|
189
|
-
}
|
|
190
|
-
/** Compact token count: 84k, 200k, 900. */
|
|
191
|
-
function fmtContextK(n) {
|
|
192
|
-
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* The one context-meter label, shared by every renderer so identical
|
|
196
|
-
* numbers always render identically: `ctx 84k/200k (42%)` — used-style
|
|
197
|
-
* percent. Empty when the numbers are missing (callers drop the part).
|
|
198
|
-
*/
|
|
199
|
-
function contextLabel(s) {
|
|
200
|
-
const pct = contextPercent(s);
|
|
201
|
-
if (pct === null) return "";
|
|
202
|
-
return `ctx ${fmtContextK(s.contextTokens ?? 0)}/${fmtContextK(s.contextWindow ?? 0)} (${pct}%)`;
|
|
203
|
-
}
|
|
204
|
-
/** Timestamp format shared by status files and the ledger. */
|
|
205
|
-
function nowStamp(d = /* @__PURE__ */ new Date()) {
|
|
206
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
207
|
-
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
|
|
208
|
-
}
|
|
209
|
-
let lastId = "";
|
|
210
|
-
let idSeq = 0;
|
|
211
|
-
function newWorkerId(d = /* @__PURE__ */ new Date(), pid = process.pid) {
|
|
212
|
-
const p = (n) => String(n).padStart(2, "0");
|
|
213
|
-
const base = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${pid}`;
|
|
214
|
-
if (base === lastId) {
|
|
215
|
-
idSeq += 1;
|
|
216
|
-
return `${base}-${String(idSeq).padStart(2, "0")}`;
|
|
217
|
-
}
|
|
218
|
-
lastId = base;
|
|
219
|
-
idSeq = 0;
|
|
220
|
-
return base;
|
|
221
|
-
}
|
|
222
|
-
/** Write status atomically (temp + rename) and stamp `updated`. */
|
|
223
|
-
function saveStatus(logDir, status, d = /* @__PURE__ */ new Date()) {
|
|
224
|
-
status.updated = nowStamp(d);
|
|
225
|
-
const path = statusPath(logDir, status.id);
|
|
226
|
-
const tmp = `${path}.tmp`;
|
|
227
|
-
writeFileSync(tmp, JSON.stringify(status), "utf8");
|
|
228
|
-
renameSync(tmp, path);
|
|
229
|
-
}
|
|
230
|
-
/** Load every status file in the logDir, oldest id first, skipping damage. */
|
|
231
|
-
function loadStatuses(logDir) {
|
|
232
|
-
if (!existsSync(logDir)) return [];
|
|
233
|
-
const out = [];
|
|
234
|
-
for (const name of readdirSync(logDir).sort()) {
|
|
235
|
-
if (!name.endsWith(".status")) continue;
|
|
236
|
-
try {
|
|
237
|
-
out.push(JSON.parse(readFileSync(join(logDir, name), "utf8")));
|
|
238
|
-
} catch {}
|
|
239
|
-
}
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
function loadStatus(logDir, id) {
|
|
243
|
-
const path = statusPath(logDir, id);
|
|
244
|
-
if (!existsSync(path)) return null;
|
|
245
|
-
try {
|
|
246
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
247
|
-
} catch {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function alive(pid) {
|
|
252
|
-
if (!pid || pid <= 0) return false;
|
|
253
|
-
try {
|
|
254
|
-
process.kill(pid, 0);
|
|
255
|
-
return true;
|
|
256
|
-
} catch {
|
|
257
|
-
return false;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
/** "42s" under 90s, "7m" above — the coarse age the table and bar show. */
|
|
261
|
-
function ageOf(ts, now = /* @__PURE__ */ new Date()) {
|
|
262
|
-
const t = Date.parse(ts.replace(" ", "T"));
|
|
263
|
-
if (Number.isNaN(t)) return "?";
|
|
264
|
-
const s = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
265
|
-
return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;
|
|
266
|
-
}
|
|
267
|
-
/** One-line description of a tool call, e.g. "Bash: npm test". */
|
|
268
|
-
function describeTool(name, inp) {
|
|
269
|
-
if (typeof inp !== "object" || inp === null) return name;
|
|
270
|
-
const i = inp;
|
|
271
|
-
const get = (k) => typeof i[k] === "string" ? i[k] : "";
|
|
272
|
-
if (name === "Bash") return `Bash: ${shortText(get("command"), 70)}`;
|
|
273
|
-
if (name === "Read" || name === "Edit" || name === "Write" || name === "MultiEdit") return `${name}: ${get("file_path").split("/").pop() ?? ""}`;
|
|
274
|
-
if (name === "Grep" || name === "Glob") return `${name}: ${shortText(get("pattern"), 50)}`;
|
|
275
|
-
if (name === "WebSearch") return `${name}: ${shortText(get("query"), 50)}`;
|
|
276
|
-
if (name === "WebFetch") return `${name}: ${shortText(get("url"), 60)}`;
|
|
277
|
-
return name;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
//#endregion
|
|
281
|
-
//#region src/workers/scope.ts
|
|
282
|
-
/**
|
|
283
|
-
* scope.ts — which workers "belong" to the terminal asking about them.
|
|
284
|
-
*
|
|
285
|
-
* Two identities, in priority order:
|
|
286
|
-
*
|
|
287
|
-
* 1. AIBroker's session registry (~/.aibroker/session-names.json): iTerm
|
|
288
|
-
* session UUID → session name. The launching session's UUID is stored in
|
|
289
|
-
* each worker's status file as `session.id`, so every pane of a named
|
|
290
|
-
* session sees its workers — regardless of tab layout.
|
|
291
|
-
* 2. The iTerm tab key (`w<window>t<tab>` prefix of ITERM_SESSION_ID): the
|
|
292
|
-
* original heuristic, kept as the fallback when no registry entry
|
|
293
|
-
* matches (AIBroker absent, unnamed session, non-iTerm terminal).
|
|
294
|
-
*
|
|
295
|
-
* If neither is available, viewers fall back to "all workers".
|
|
296
|
-
*
|
|
297
|
-
* A third case: workers spawned from a Claude Code Bash tool (the
|
|
298
|
-
* orchestrator pattern) have neither — Claude Code exports no terminal or
|
|
299
|
-
* session identity to its Bash children. Those record `spawnerSession`, the
|
|
300
|
-
* orchestrator's claude session id bridged through the status line's
|
|
301
|
-
* session map (see below), so their tab still claims them.
|
|
302
|
-
*/
|
|
303
|
-
const AIBROKER_REGISTRY = join(homedir(), ".aibroker", "session-names.json");
|
|
304
|
-
/**
|
|
305
|
-
* `w<window>t<tab>` prefix of an iTerm session id, "" if empty or malformed.
|
|
306
|
-
* Panes split from the same tab share this prefix and differ only in `p<n>`.
|
|
307
|
-
*/
|
|
308
|
-
function tabKey(term) {
|
|
309
|
-
if (!term) return "";
|
|
310
|
-
const head = term.split("p", 1)[0];
|
|
311
|
-
const parts = head.slice(1).split("t");
|
|
312
|
-
if (head.startsWith("w") && parts.length === 2 && parts.every((p) => /^\d+$/.test(p))) return head;
|
|
313
|
-
return "";
|
|
314
|
-
}
|
|
315
|
-
/** Tab key of the iTerm tab this process runs in, "" if not inside iTerm2. */
|
|
316
|
-
function currentTabKey(env = process.env) {
|
|
317
|
-
return tabKey(env.ITERM_SESSION_ID ?? "");
|
|
318
|
-
}
|
|
319
|
-
/** The iTerm UUID part of an ITERM_SESSION_ID (after the last colon). */
|
|
320
|
-
function itermUuid(term) {
|
|
321
|
-
if (!term) return "";
|
|
322
|
-
return term.split(":").pop() ?? "";
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Resolve the AIBroker session for an ITERM_SESSION_ID. Reads the persistent
|
|
326
|
-
* name registry (the same store `aibroker_rename` writes); returns null when
|
|
327
|
-
* AIBroker is absent or the session is not in it — callers then use the tab
|
|
328
|
-
* key, which is the pre-AIBroker behaviour.
|
|
329
|
-
*/
|
|
330
|
-
function resolveSession(term, registryPath = AIBROKER_REGISTRY) {
|
|
331
|
-
const uuid = itermUuid(term);
|
|
332
|
-
if (!uuid) return null;
|
|
333
|
-
let names;
|
|
334
|
-
try {
|
|
335
|
-
if (!existsSync(registryPath)) return null;
|
|
336
|
-
names = JSON.parse(readFileSync(registryPath, "utf8"));
|
|
337
|
-
} catch {
|
|
338
|
-
return null;
|
|
339
|
-
}
|
|
340
|
-
const name = names[uuid];
|
|
341
|
-
if (typeof name !== "string" || !name) return null;
|
|
342
|
-
return {
|
|
343
|
-
id: uuid,
|
|
344
|
-
name
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* True when `worker` was launched from `term` (the terminal asking about it).
|
|
349
|
-
* Session-id match first; the tab key only when no registry entry matched —
|
|
350
|
-
* a session without a name keeps the tab-scoped behaviour it always had.
|
|
351
|
-
*/
|
|
352
|
-
function workerInScope(worker, term) {
|
|
353
|
-
if (!term) return false;
|
|
354
|
-
const uuid = itermUuid(term);
|
|
355
|
-
if (worker.session?.id && uuid) return worker.session.id === uuid;
|
|
356
|
-
return tabKey(worker.term) === tabKey(term) && tabKey(term) !== "";
|
|
357
|
-
}
|
|
358
|
-
/**
|
|
359
|
-
* Registry key for pane files: the session id when AIBroker knows this
|
|
360
|
-
* terminal, else the tab key, else the raw iTerm UUID.
|
|
361
|
-
*/
|
|
362
|
-
function scopeKey(term) {
|
|
363
|
-
const session = resolveSession(term);
|
|
364
|
-
if (session) return session.id;
|
|
365
|
-
return currentTabKey() || itermUuid(term);
|
|
366
|
-
}
|
|
367
|
-
/** `[Name]` when the worker has an AIBroker session name, else "". */
|
|
368
|
-
function sessionTag(worker) {
|
|
369
|
-
return worker.session?.name ? `[${worker.session.name}]` : "";
|
|
370
|
-
}
|
|
371
|
-
/** How old a map entry may be for a spawn to adopt it (status lines refresh constantly while a session lives). */
|
|
372
|
-
const SPAWNER_SESSION_TTL_MS = 10 * 6e4;
|
|
373
|
-
/** Map entries not refreshed within this window are pruned on write. */
|
|
374
|
-
const SESSION_MAP_PRUNE_MS = 60 * 6e4;
|
|
375
|
-
function sessionMapPath(logDir) {
|
|
376
|
-
return join(logDir, "claude-session-map.json");
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* The claude session a new run was spawned by, when it can be known: a run
|
|
380
|
-
* inside another worker inherits its spawner (chain stages keep the
|
|
381
|
-
* orchestrator's tab that way); otherwise a fresh map entry for `cwd`.
|
|
382
|
-
*/
|
|
383
|
-
function resolveSpawnerSession(logDir, cwd, env = process.env, now = Date.now()) {
|
|
384
|
-
const parentId = env.PAI_WORKER_ID;
|
|
385
|
-
if (parentId) {
|
|
386
|
-
const inherited = loadStatus(logDir, parentId)?.spawnerSession;
|
|
387
|
-
if (inherited) return inherited;
|
|
388
|
-
}
|
|
389
|
-
const path = sessionMapPath(logDir);
|
|
390
|
-
if (!existsSync(path) || !cwd) return null;
|
|
391
|
-
try {
|
|
392
|
-
const entry = JSON.parse(readFileSync(path, "utf8"))[cwd];
|
|
393
|
-
if (entry && entry.session && now - entry.ts < SPAWNER_SESSION_TTL_MS) return entry.session;
|
|
394
|
-
} catch {}
|
|
395
|
-
return null;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
80
|
//#endregion
|
|
399
81
|
//#region src/workers/pane.ts
|
|
400
82
|
/**
|
|
@@ -1232,6 +914,11 @@ function worktreeWanted(flag, opts) {
|
|
|
1232
914
|
* degrade to an in-place run.
|
|
1233
915
|
*/
|
|
1234
916
|
function addWorktree(logDir, id, cwd) {
|
|
917
|
+
const swept = sweepOrphanWorktrees(logDir);
|
|
918
|
+
if (swept.length) appendLedger(ledgerPath(logDir), "WORKER-NOTE", {
|
|
919
|
+
id,
|
|
920
|
+
note: `swept orphan worktree(s): ${swept.join(", ")}`
|
|
921
|
+
});
|
|
1235
922
|
const dir = worktreePath(logDir, id);
|
|
1236
923
|
const branch = worktreeBranch(id);
|
|
1237
924
|
const base = git(cwd, ["rev-parse", "HEAD"]);
|
|
@@ -1248,6 +935,46 @@ function addWorktree(logDir, id, cwd) {
|
|
|
1248
935
|
base
|
|
1249
936
|
};
|
|
1250
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Remove worktrees (and their `worker/<id>` branches) whose worker no longer
|
|
940
|
+
* exists — no status file left in the log dir. Killed and failed runs clean
|
|
941
|
+
* up after themselves, but a `kill -9` or a crash strands a directory and a
|
|
942
|
+
* branch; every worktree creation sweeps first so they cannot accumulate.
|
|
943
|
+
* Directories younger than `minAgeMin` minutes are left alone: a run that is
|
|
944
|
+
* just starting owns its worktree a moment before its status file exists.
|
|
945
|
+
*/
|
|
946
|
+
function sweepOrphanWorktrees(logDir, minAgeMin = 10) {
|
|
947
|
+
const root = worktreesDir(logDir);
|
|
948
|
+
if (!existsSync(root)) return [];
|
|
949
|
+
const swept = [];
|
|
950
|
+
for (const ent of readdirSync(root, { withFileTypes: true })) {
|
|
951
|
+
if (!ent.isDirectory()) continue;
|
|
952
|
+
const id = ent.name;
|
|
953
|
+
const dir = join(root, id);
|
|
954
|
+
if (existsSync(statusPath(logDir, id))) continue;
|
|
955
|
+
try {
|
|
956
|
+
if ((Date.now() - statSync(dir).mtimeMs) / 6e4 < minAgeMin) continue;
|
|
957
|
+
} catch {}
|
|
958
|
+
let gitDir = null;
|
|
959
|
+
try {
|
|
960
|
+
const raw = git(dir, ["rev-parse", "--git-common-dir"]);
|
|
961
|
+
gitDir = isAbsolute(raw) ? raw : resolve(dir, raw);
|
|
962
|
+
} catch {
|
|
963
|
+
gitDir = null;
|
|
964
|
+
}
|
|
965
|
+
removeWorktree(gitDir ?? dir, dir, true);
|
|
966
|
+
if (gitDir) try {
|
|
967
|
+
git(gitDir, ["worktree", "prune"]);
|
|
968
|
+
git(gitDir, [
|
|
969
|
+
"branch",
|
|
970
|
+
"-D",
|
|
971
|
+
worktreeBranch(id)
|
|
972
|
+
]);
|
|
973
|
+
} catch {}
|
|
974
|
+
swept.push(id);
|
|
975
|
+
}
|
|
976
|
+
return swept;
|
|
977
|
+
}
|
|
1251
978
|
/** Commits the branch collected on top of its base. */
|
|
1252
979
|
function commitsSince(dir, base) {
|
|
1253
980
|
try {
|
|
@@ -1533,6 +1260,9 @@ const WORKER_CONTRACT_PROMPT = [
|
|
|
1533
1260
|
"notes: one line, the headline a reviewer reads first.",
|
|
1534
1261
|
"Never write shell heredocs — write the script to a file with the Write tool, then run it",
|
|
1535
1262
|
"(heredocs fail silently or get swallowed).",
|
|
1263
|
+
"Never inline code one-liners (python3 -c, node -e, ruby -e and friends) — the permission layer",
|
|
1264
|
+
"denies them (\"ztk: command denied by permission rules\"); write the script to a file with the",
|
|
1265
|
+
"Write tool and run it (python3 script.py), or use jq for JSON inspection.",
|
|
1536
1266
|
"",
|
|
1537
1267
|
`Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
|
|
1538
1268
|
"run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines —",
|
|
@@ -1890,9 +1620,12 @@ function codexInstalled() {
|
|
|
1890
1620
|
* - env: ANTHROPIC_BASE_URL/AUTH_TOKEN from the provider (token from its
|
|
1891
1621
|
* key file, never from the environment), the three DEFAULT_*_MODEL vars,
|
|
1892
1622
|
* the provider's extra env, nonessential traffic off, and ANTHROPIC_API_KEY
|
|
1893
|
-
* stripped so nothing can fall back to Anthropic billing.
|
|
1894
|
-
*
|
|
1895
|
-
*
|
|
1623
|
+
* stripped so nothing can fall back to Anthropic billing. Headless runs
|
|
1624
|
+
* also drop the spawner's session identity (messaging socket, session id,
|
|
1625
|
+
* nesting markers) — inherited, a worktree child starts tool-blind.
|
|
1626
|
+
* OpenAI-protocol providers point at the PAI proxy instead (started on
|
|
1627
|
+
* demand, the provider name in the URL path); codex-engine providers run
|
|
1628
|
+
* the Codex CLI.
|
|
1896
1629
|
* - headless (-p): strict empty MCP config unless the caller brings one or
|
|
1897
1630
|
* names servers via --mcp / a role (then a filtered <id>.mcp.json),
|
|
1898
1631
|
* PAI_WORKER=1 so PAI's per-session hooks leave it alone, the worker
|
|
@@ -1992,7 +1725,7 @@ async function runWorker(opts) {
|
|
|
1992
1725
|
const logDir = workersLogDir(config);
|
|
1993
1726
|
mkdirSync(logDir, { recursive: true });
|
|
1994
1727
|
if (opts.className === "plan" && !opts._planner) {
|
|
1995
|
-
const { runPlanner } = await import("./planner-
|
|
1728
|
+
const { runPlanner } = await import("./planner-BLDJt3ZS.mjs");
|
|
1996
1729
|
return runPlanner(opts);
|
|
1997
1730
|
}
|
|
1998
1731
|
const parent = launchParent(opts.parent);
|
|
@@ -2214,6 +1947,9 @@ async function executeRun(a) {
|
|
|
2214
1947
|
killed: 1,
|
|
2215
1948
|
label
|
|
2216
1949
|
});
|
|
1950
|
+
if (worktree) try {
|
|
1951
|
+
recordWorktree(logDir, status, worktree, false);
|
|
1952
|
+
} catch {}
|
|
2217
1953
|
cleanup();
|
|
2218
1954
|
try {
|
|
2219
1955
|
proc.kill();
|
|
@@ -2222,6 +1958,7 @@ async function executeRun(a) {
|
|
|
2222
1958
|
};
|
|
2223
1959
|
process.once("SIGTERM", () => onSignal("SIGTERM"));
|
|
2224
1960
|
process.once("SIGINT", () => onSignal("SIGINT"));
|
|
1961
|
+
process.once("SIGHUP", () => onSignal("SIGHUP"));
|
|
2225
1962
|
const ctx = {
|
|
2226
1963
|
resultEvent: null,
|
|
2227
1964
|
resultReport: null
|
|
@@ -2457,6 +2194,7 @@ async function executeCodexRun(a) {
|
|
|
2457
2194
|
let killed = false;
|
|
2458
2195
|
process.once("SIGTERM", onCodexSignal("SIGTERM"));
|
|
2459
2196
|
process.once("SIGINT", onCodexSignal("SIGINT"));
|
|
2197
|
+
process.once("SIGHUP", onCodexSignal("SIGHUP"));
|
|
2460
2198
|
function onCodexSignal(sig) {
|
|
2461
2199
|
return () => {
|
|
2462
2200
|
killed = true;
|
|
@@ -2465,6 +2203,9 @@ async function executeCodexRun(a) {
|
|
|
2465
2203
|
status.secs = Math.floor((Date.now() - t0) / 1e3);
|
|
2466
2204
|
status.last = `killed by signal ${sig}`;
|
|
2467
2205
|
saveStatus(logDir, status);
|
|
2206
|
+
if (worktree) try {
|
|
2207
|
+
recordWorktree(logDir, status, worktree, false);
|
|
2208
|
+
} catch {}
|
|
2468
2209
|
try {
|
|
2469
2210
|
proc.kill();
|
|
2470
2211
|
} catch {}
|
|
@@ -2825,5 +2566,5 @@ async function runChain(opts, deps = {}) {
|
|
|
2825
2566
|
}
|
|
2826
2567
|
|
|
2827
2568
|
//#endregion
|
|
2828
|
-
export {
|
|
2829
|
-
//# sourceMappingURL=chain-
|
|
2569
|
+
export { openPaneForWorker as _, testProvider as a, renderReport as c, handoffFromInside as d, readInbox as f, openFollowPane as g, checkPaneForWorker as h, runWorker as i, discardWorker as l, workerDepth as m, swapPromptArg as n, describeMcp as o, sayToWorker as p, printResult as r, parseWorkerReport as s, runChain as t, mergeWorker as u, appendLedger as v, ledgerSummary as y };
|
|
2570
|
+
//# sourceMappingURL=chain-DGXNoTwc.mjs.map
|