@wrongstack/core 0.283.1 → 0.284.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/{compactor-BxddyCFu.d.ts → compactor-DQLL4HTo.d.ts} +1 -1
- package/dist/{config-DkCbA3Bw.d.ts → config-MRqn1V-u.d.ts} +130 -22
- package/dist/coordination/index.d.ts +43 -20
- package/dist/coordination/index.js +1744 -912
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-Buho8yge.d.ts → default-config-B79_gJYv.d.ts} +7 -0
- package/dist/defaults/index.d.ts +25 -29
- package/dist/defaults/index.js +2546 -7396
- package/dist/defaults/index.js.map +1 -1
- package/dist/{director-state-B_GPa78Q.d.ts → director-state-CMS_bMs4.d.ts} +2 -0
- package/dist/{events-KmxSmvho.d.ts → events-Bgnh43r9.d.ts} +27 -10
- package/dist/execution/index.d.ts +15 -16
- package/dist/execution/index.js +534 -140
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{global-mailbox-rWVt8YlO.d.ts → global-mailbox-sZwyI6CS.d.ts} +13 -2
- package/dist/{goal-store-8owBXJT2.d.ts → goal-store-Datpp-ar.d.ts} +1 -1
- package/dist/hq/index.d.ts +6 -6
- package/dist/hq/index.js +58 -7
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-BhCteHAF.d.ts → index--Gm41aBJ.d.ts} +26 -11
- package/dist/{index-BEFWNZYs.d.ts → index-CbyuOE5y.d.ts} +5 -1
- package/dist/index.d.ts +50 -1015
- package/dist/index.js +21028 -27775
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +9 -7
- package/dist/infrastructure/index.js +7 -2
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +11 -11
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BUx9A_AQ.d.ts → mcp-servers-DEV4s6Ks.d.ts} +3 -3
- package/dist/models/index.d.ts +4 -4
- package/dist/models/index.js +18 -3
- package/dist/models/index.js.map +1 -1
- package/dist/{agent-subagent-runner-BsuWhB28.d.ts → multi-agent-coordinator-4RCRZKjN.d.ts} +417 -9
- package/dist/{null-fleet-bus-D4e9R7Qc.d.ts → null-fleet-bus-CFoNVQEK.d.ts} +53 -12
- package/dist/observability/index.d.ts +3 -3
- package/dist/{parallel-eternal-engine-iY2uxocj.d.ts → parallel-eternal-engine-HUrtePLd.d.ts} +4 -5
- package/dist/{permission-B6YAtNFz.d.ts → permission-ByDKXEcG.d.ts} +1 -1
- package/dist/{pipeline-ixSFe5jg.d.ts → pipeline-DGDyIohT.d.ts} +28 -7
- package/dist/{provider-model-resolve-DIxKL_T-.d.ts → provider-model-resolve-o2hHLs79.d.ts} +3 -3
- package/dist/{provider-runner-oQhDaZmH.d.ts → provider-runner-ChL-kVg6.d.ts} +3 -3
- package/dist/{retry-policy-V67lBOJz.d.ts → retry-policy-CQ7KwXJa.d.ts} +2 -2
- package/dist/{secret-vault-D7jeyXUQ.d.ts → secret-vault-DxohkUBc.d.ts} +3 -2
- package/dist/security/index.d.ts +4 -4
- package/dist/security/index.js +232 -2
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-Bx2HgUkU.d.ts → selector-D8O6B_Rm.d.ts} +1 -1
- package/dist/{session-event-bridge-Bkp7ORfc.d.ts → session-event-bridge-D_z_mBwk.d.ts} +3 -3
- package/dist/{session-reader-B-Fi9L6I.d.ts → session-reader-7Esxrkpw.d.ts} +1 -1
- package/dist/storage/index.d.ts +77 -39
- package/dist/storage/index.js +1223 -288
- package/dist/storage/index.js.map +1 -1
- package/dist/tasking/index.d.ts +124 -0
- package/dist/tasking/index.js +492 -0
- package/dist/tasking/index.js.map +1 -0
- package/dist/{todos-checkpoint-BwCrj4Cb.d.ts → todos-checkpoint-FY7PjmMx.d.ts} +13 -4
- package/dist/{tool-LVSLiE1s.d.ts → tool-BNlnIJvo.d.ts} +216 -17
- package/dist/{tool-executor-kElSEgO0.d.ts → tool-executor-CckSCM5E.d.ts} +12 -15
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +94 -19
- package/dist/types/index.js +76 -12
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +44 -10
- package/dist/utils/index.js +106 -17
- package/dist/utils/index.js.map +1 -1
- package/dist/{worktree-manager-BjyAW30D.d.ts → worktree-manager-DzwxKE35.d.ts} +1 -1
- package/instructions/coordination/subagent-baseline.md +5 -0
- package/instructions/llm/autonomy-brain.md +3 -2
- package/instructions/system.md +4 -0
- package/package.json +10 -3
- package/dist/multi-agent-coordinator-CrjeTB9i.d.ts +0 -357
- package/dist/sdd/index.d.ts +0 -1993
- package/dist/sdd/index.js +0 -9500
- package/dist/sdd/index.js.map +0 -1
- package/dist/spec-TBi3Jr6T.d.ts +0 -78
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { randomUUID, createHash, randomBytes } from 'crypto';
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as fsp9 from 'fs/promises';
|
|
3
|
+
import * as path9 from 'path';
|
|
4
4
|
import { isAbsolute, resolve } from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
6
6
|
import { hostname } from 'os';
|
|
7
|
-
import { readFileSync, statSync, createReadStream } from 'fs';
|
|
7
|
+
import { readFileSync, statSync, createReadStream, openSync, writeSync, fsyncSync, closeSync } from 'fs';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { EventEmitter } from 'events';
|
|
10
|
+
import { listReadyTasks, claimReadyTask, updateTaskAssignment } from '@wrongstack/kanban';
|
|
10
11
|
import { createInterface } from 'readline';
|
|
11
|
-
import { execFileSync } from 'child_process';
|
|
12
|
+
import { execFileSync, spawn } from 'child_process';
|
|
12
13
|
|
|
13
14
|
// src/coordination/agent-bridge.ts
|
|
14
15
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
15
|
-
const dir =
|
|
16
|
-
await
|
|
17
|
-
const tmp =
|
|
16
|
+
const dir = path9.dirname(targetPath);
|
|
17
|
+
await fsp9.mkdir(dir, { recursive: true });
|
|
18
|
+
const tmp = path9.join(dir, `.${path9.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
18
19
|
try {
|
|
19
20
|
if (typeof content === "string") {
|
|
20
|
-
await
|
|
21
|
+
await fsp9.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
21
22
|
} else {
|
|
22
|
-
await
|
|
23
|
+
await fsp9.writeFile(tmp, content, { flag: "wx" });
|
|
23
24
|
}
|
|
24
25
|
try {
|
|
25
|
-
const fh = await
|
|
26
|
+
const fh = await fsp9.open(tmp, "r+");
|
|
26
27
|
try {
|
|
27
28
|
await fh.sync();
|
|
28
29
|
} finally {
|
|
@@ -32,56 +33,56 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
32
33
|
}
|
|
33
34
|
let mode;
|
|
34
35
|
try {
|
|
35
|
-
const
|
|
36
|
-
mode =
|
|
36
|
+
const stat9 = await fsp9.stat(targetPath);
|
|
37
|
+
mode = stat9.mode & 511;
|
|
37
38
|
} catch {
|
|
38
39
|
mode = opts.mode;
|
|
39
40
|
}
|
|
40
41
|
if (mode !== void 0) {
|
|
41
|
-
await
|
|
42
|
+
await fsp9.chmod(tmp, mode);
|
|
42
43
|
}
|
|
43
44
|
await renameWithRetry(tmp, targetPath);
|
|
44
45
|
if (mode !== void 0 && process.platform === "win32") {
|
|
45
46
|
try {
|
|
46
|
-
await
|
|
47
|
+
await fsp9.chmod(targetPath, mode);
|
|
47
48
|
} catch {
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
} catch (err) {
|
|
51
52
|
try {
|
|
52
|
-
await
|
|
53
|
+
await fsp9.unlink(tmp);
|
|
53
54
|
} catch {
|
|
54
55
|
}
|
|
55
56
|
throw err;
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
async function ensureDir(dir) {
|
|
59
|
-
await
|
|
60
|
+
await fsp9.mkdir(dir, { recursive: true });
|
|
60
61
|
}
|
|
61
62
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
62
|
-
const dir =
|
|
63
|
-
await
|
|
64
|
-
const lockPath =
|
|
63
|
+
const dir = path9.dirname(targetPath);
|
|
64
|
+
await fsp9.mkdir(dir, { recursive: true });
|
|
65
|
+
const lockPath = path9.join(dir, `.${path9.basename(targetPath)}.lock`);
|
|
65
66
|
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
66
67
|
const staleMs = opts.staleMs ?? 3e4;
|
|
67
68
|
const started = Date.now();
|
|
68
69
|
let handle;
|
|
69
70
|
for (; ; ) {
|
|
70
71
|
try {
|
|
71
|
-
handle = await
|
|
72
|
+
handle = await fsp9.open(lockPath, "wx");
|
|
72
73
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
73
74
|
break;
|
|
74
75
|
} catch (err) {
|
|
75
76
|
const code = err.code;
|
|
76
77
|
if (code === "ENOENT") {
|
|
77
|
-
await
|
|
78
|
+
await fsp9.mkdir(dir, { recursive: true });
|
|
78
79
|
continue;
|
|
79
80
|
}
|
|
80
81
|
if (code !== "EEXIST") throw err;
|
|
81
82
|
try {
|
|
82
|
-
const
|
|
83
|
-
if (Date.now() -
|
|
84
|
-
await
|
|
83
|
+
const stat9 = await fsp9.stat(lockPath);
|
|
84
|
+
if (Date.now() - stat9.mtimeMs > staleMs) {
|
|
85
|
+
await fsp9.unlink(lockPath);
|
|
85
86
|
continue;
|
|
86
87
|
}
|
|
87
88
|
} catch {
|
|
@@ -95,7 +96,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
95
96
|
context: { timeoutMs }
|
|
96
97
|
});
|
|
97
98
|
}
|
|
98
|
-
await new Promise((
|
|
99
|
+
await new Promise((resolve9) => setTimeout(resolve9, 25));
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
try {
|
|
@@ -106,7 +107,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
106
107
|
} catch {
|
|
107
108
|
}
|
|
108
109
|
try {
|
|
109
|
-
await
|
|
110
|
+
await fsp9.unlink(lockPath);
|
|
110
111
|
} catch {
|
|
111
112
|
}
|
|
112
113
|
}
|
|
@@ -114,14 +115,14 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
114
115
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
115
116
|
async function renameWithRetry(from, to) {
|
|
116
117
|
if (process.platform !== "win32") {
|
|
117
|
-
await
|
|
118
|
+
await fsp9.rename(from, to);
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
120
121
|
const delays = [10, 25, 60, 120, 250];
|
|
121
122
|
let lastErr;
|
|
122
123
|
for (let i = 0; i <= delays.length; i++) {
|
|
123
124
|
try {
|
|
124
|
-
await
|
|
125
|
+
await fsp9.rename(from, to);
|
|
125
126
|
return;
|
|
126
127
|
} catch (err) {
|
|
127
128
|
lastErr = err;
|
|
@@ -129,12 +130,132 @@ async function renameWithRetry(from, to) {
|
|
|
129
130
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
130
131
|
throw err;
|
|
131
132
|
}
|
|
132
|
-
await new Promise((
|
|
133
|
+
await new Promise((resolve9) => setTimeout(resolve9, delays[i]));
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
throw lastErr;
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
// src/utils/child-env.ts
|
|
140
|
+
var ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
141
|
+
"PATH",
|
|
142
|
+
"HOME",
|
|
143
|
+
"USER",
|
|
144
|
+
"USERNAME",
|
|
145
|
+
"LOGNAME",
|
|
146
|
+
"SHELL",
|
|
147
|
+
"LANG",
|
|
148
|
+
"LC_ALL",
|
|
149
|
+
"LC_CTYPE",
|
|
150
|
+
"TERM",
|
|
151
|
+
"TZ",
|
|
152
|
+
"TMPDIR",
|
|
153
|
+
"TEMP",
|
|
154
|
+
"TMP",
|
|
155
|
+
"PWD",
|
|
156
|
+
"OLDPWD",
|
|
157
|
+
"COMSPEC",
|
|
158
|
+
"SYSTEMROOT",
|
|
159
|
+
"SYSTEMDRIVE",
|
|
160
|
+
"WINDIR",
|
|
161
|
+
"PROGRAMFILES",
|
|
162
|
+
"PROGRAMFILES(X86)",
|
|
163
|
+
"PROGRAMDATA",
|
|
164
|
+
"APPDATA",
|
|
165
|
+
"LOCALAPPDATA",
|
|
166
|
+
"USERPROFILE",
|
|
167
|
+
"PUBLIC",
|
|
168
|
+
"PATHEXT"
|
|
169
|
+
]);
|
|
170
|
+
var SECRET_NAME_PARTS = [
|
|
171
|
+
"TOKEN",
|
|
172
|
+
"SECRET",
|
|
173
|
+
"PASSWORD",
|
|
174
|
+
"PASSWD",
|
|
175
|
+
"AUTH",
|
|
176
|
+
"CRED",
|
|
177
|
+
"BEARER",
|
|
178
|
+
"COOKIE",
|
|
179
|
+
"PRIVATE"
|
|
180
|
+
];
|
|
181
|
+
function looksSecret(name) {
|
|
182
|
+
const upper = name.toUpperCase();
|
|
183
|
+
for (const p of SECRET_NAME_PARTS) {
|
|
184
|
+
if (upper.includes(p)) return true;
|
|
185
|
+
}
|
|
186
|
+
if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
|
|
187
|
+
if (/API[_-]?KEY/i.test(upper)) return true;
|
|
188
|
+
if (/ACCESS[_-]?KEY/i.test(upper)) return true;
|
|
189
|
+
if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
function valueHasEmbeddedCredential(value) {
|
|
195
|
+
return /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]*:[^/\s@]+@/i.test(value);
|
|
196
|
+
}
|
|
197
|
+
var NODE_OPTIONS_INJECTION_FLAG = /^(?:--require|-r|--import|--loader|--experimental-loader)$/;
|
|
198
|
+
var NODE_OPTIONS_INJECTION_FLAG_EQ = /^(?:--require|-r|--import|--loader|--experimental-loader)=/;
|
|
199
|
+
function sanitizeNodeOptions(value) {
|
|
200
|
+
const tokens = value.split(/\s+/).filter(Boolean);
|
|
201
|
+
const kept = [];
|
|
202
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
203
|
+
const tok = tokens[i];
|
|
204
|
+
if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue;
|
|
205
|
+
if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {
|
|
206
|
+
i++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
kept.push(tok);
|
|
210
|
+
}
|
|
211
|
+
return kept.join(" ");
|
|
212
|
+
}
|
|
213
|
+
function buildChildEnv(optsOrSessionId) {
|
|
214
|
+
const opts = {};
|
|
215
|
+
const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
|
|
216
|
+
const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
|
|
217
|
+
const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
|
|
218
|
+
if (passthrough && !process.env["CI"]) {
|
|
219
|
+
console.warn(
|
|
220
|
+
"[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const out = {};
|
|
224
|
+
const nodeEnvDefaulted = process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] === "1";
|
|
225
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
226
|
+
if (v === void 0) continue;
|
|
227
|
+
if (nodeEnvDefaulted && (k === "NODE_ENV" || k === "WRONGSTACK_NODE_ENV_DEFAULTED")) continue;
|
|
228
|
+
if (passthrough) {
|
|
229
|
+
out[k] = v;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const upper = k.toUpperCase();
|
|
233
|
+
if (valueHasEmbeddedCredential(v)) continue;
|
|
234
|
+
if (ALLOWED_KEYS.has(upper)) {
|
|
235
|
+
out[k] = v;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (looksSecret(upper)) continue;
|
|
239
|
+
if (upper === "NODE_OPTIONS") {
|
|
240
|
+
const sanitized = sanitizeNodeOptions(v);
|
|
241
|
+
if (sanitized) out[k] = sanitized;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
|
|
245
|
+
// Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
|
|
246
|
+
// vault). Forwarding keeps child wstack processes — e.g. ones spawned
|
|
247
|
+
// by the test suite — inside the same redirected global root.
|
|
248
|
+
upper.startsWith("WRONGSTACK_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
|
|
249
|
+
out[k] = v;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (opts.extra) {
|
|
253
|
+
Object.assign(out, opts.extra);
|
|
254
|
+
}
|
|
255
|
+
if (opts.sessionId) out["WRONGSTACK_SESSION_ID"] = opts.sessionId;
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
138
259
|
// src/utils/error.ts
|
|
139
260
|
function toErrorMessage(err) {
|
|
140
261
|
return err instanceof Error ? err.message : String(err);
|
|
@@ -214,7 +335,7 @@ async function expandGlob(pattern) {
|
|
|
214
335
|
async function walk(dir, pat) {
|
|
215
336
|
let entries;
|
|
216
337
|
try {
|
|
217
|
-
entries = await
|
|
338
|
+
entries = await fsp9.readdir(dir);
|
|
218
339
|
} catch {
|
|
219
340
|
return;
|
|
220
341
|
}
|
|
@@ -236,8 +357,8 @@ async function expandGlob(pattern) {
|
|
|
236
357
|
for (const e of entries) {
|
|
237
358
|
const full = `${dir}${SEP}${e}`;
|
|
238
359
|
try {
|
|
239
|
-
const
|
|
240
|
-
if (
|
|
360
|
+
const stat9 = await fsp9.stat(full);
|
|
361
|
+
if (stat9.isDirectory()) await walk(full, rest);
|
|
241
362
|
} catch {
|
|
242
363
|
}
|
|
243
364
|
}
|
|
@@ -254,8 +375,8 @@ async function expandGlob(pattern) {
|
|
|
254
375
|
if (entries.includes(seg)) {
|
|
255
376
|
const full = `${dir}${SEP}${seg}`;
|
|
256
377
|
try {
|
|
257
|
-
const
|
|
258
|
-
if (
|
|
378
|
+
const stat9 = await fsp9.stat(full);
|
|
379
|
+
if (stat9.isDirectory()) await walk(full, rest);
|
|
259
380
|
} catch {
|
|
260
381
|
}
|
|
261
382
|
}
|
|
@@ -396,9 +517,9 @@ function sessionScopedPath(dir, sessionId, suffix) {
|
|
|
396
517
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
397
518
|
throw invalid(sessionId);
|
|
398
519
|
}
|
|
399
|
-
const resolved =
|
|
400
|
-
const rel =
|
|
401
|
-
if (rel.startsWith("..") ||
|
|
520
|
+
const resolved = path9.resolve(dir, `${sessionId}${suffix}`);
|
|
521
|
+
const rel = path9.relative(path9.resolve(dir), resolved);
|
|
522
|
+
if (rel.startsWith("..") || path9.isAbsolute(rel)) {
|
|
402
523
|
throw invalid(sessionId);
|
|
403
524
|
}
|
|
404
525
|
return resolved;
|
|
@@ -442,8 +563,8 @@ function truncate(s, max) {
|
|
|
442
563
|
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
443
564
|
}
|
|
444
565
|
function projectSlug(absRoot) {
|
|
445
|
-
const base = slugify(
|
|
446
|
-
const hash = createHash("sha256").update(
|
|
566
|
+
const base = slugify(path9.basename(absRoot));
|
|
567
|
+
const hash = createHash("sha256").update(path9.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
447
568
|
return `${base}-${hash}`;
|
|
448
569
|
}
|
|
449
570
|
function slugify(name) {
|
|
@@ -451,8 +572,8 @@ function slugify(name) {
|
|
|
451
572
|
}
|
|
452
573
|
function wstackGlobalRoot() {
|
|
453
574
|
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
454
|
-
if (fromEnv && fromEnv.trim().length > 0) return
|
|
455
|
-
return
|
|
575
|
+
if (fromEnv && fromEnv.trim().length > 0) return path9.resolve(fromEnv);
|
|
576
|
+
return path9.join(os.homedir(), ".wrongstack");
|
|
456
577
|
}
|
|
457
578
|
|
|
458
579
|
// src/types/errors.ts
|
|
@@ -464,6 +585,7 @@ var ERROR_CODES = {
|
|
|
464
585
|
PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
|
|
465
586
|
PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
|
|
466
587
|
PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
|
|
588
|
+
PROVIDER_CONTEXT_OVERFLOW: "PROVIDER_CONTEXT_OVERFLOW",
|
|
467
589
|
// Agent
|
|
468
590
|
AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
|
|
469
591
|
AGENT_ABORTED: "AGENT_ABORTED",
|
|
@@ -632,7 +754,7 @@ var InMemoryAgentBridge = class {
|
|
|
632
754
|
});
|
|
633
755
|
}
|
|
634
756
|
this.inflightGuards.add(correlationId);
|
|
635
|
-
return new Promise((
|
|
757
|
+
return new Promise((resolve9, reject) => {
|
|
636
758
|
const timer = setTimeout(() => {
|
|
637
759
|
this.inflightGuards.delete(correlationId);
|
|
638
760
|
this.pendingRequests.delete(correlationId);
|
|
@@ -651,7 +773,7 @@ var InMemoryAgentBridge = class {
|
|
|
651
773
|
return;
|
|
652
774
|
}
|
|
653
775
|
this.pendingRequests.set(correlationId, {
|
|
654
|
-
resolve:
|
|
776
|
+
resolve: resolve9,
|
|
655
777
|
reject,
|
|
656
778
|
timer
|
|
657
779
|
});
|
|
@@ -1000,13 +1122,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
1000
1122
|
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
1001
1123
|
return Promise.resolve("stop");
|
|
1002
1124
|
}
|
|
1003
|
-
return new Promise((
|
|
1125
|
+
return new Promise((resolve9) => {
|
|
1004
1126
|
let resolved = false;
|
|
1005
1127
|
const respond = (d) => {
|
|
1006
1128
|
if (resolved) return;
|
|
1007
1129
|
resolved = true;
|
|
1008
1130
|
clearTimeout(fallback);
|
|
1009
|
-
|
|
1131
|
+
resolve9(d);
|
|
1010
1132
|
};
|
|
1011
1133
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
1012
1134
|
const sessionId = this.currentSessionId();
|
|
@@ -1141,6 +1263,152 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
1141
1263
|
}
|
|
1142
1264
|
};
|
|
1143
1265
|
|
|
1266
|
+
// src/security/capabilities.ts
|
|
1267
|
+
var ToolCapabilities = {
|
|
1268
|
+
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
1269
|
+
SHELL_ARBITRARY: "shell.arbitrary",
|
|
1270
|
+
/** Can execute a restricted set of commands (the `exec` tool). */
|
|
1271
|
+
SHELL_RESTRICTED: "shell.restricted",
|
|
1272
|
+
/** Can run a restricted project formatter/linter-style command. */
|
|
1273
|
+
SHELL_EXEC: "shell.exec",
|
|
1274
|
+
/** Can write / modify / delete files inside the project. */
|
|
1275
|
+
FS_WRITE: "fs.write",
|
|
1276
|
+
/** Can write files outside the current project root (very high risk). */
|
|
1277
|
+
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
1278
|
+
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
1279
|
+
SUBAGENT_SPAWN: "subagent.spawn",
|
|
1280
|
+
/** Can inspect fleet/subagent coordination state without mutating it. */
|
|
1281
|
+
COORDINATION_FLEET_READ: "coordination.fleet.read",
|
|
1282
|
+
/** Can publish attributed, schema-checked events onto the fleet bus. */
|
|
1283
|
+
COORDINATION_FLEET_EMIT: "coordination.fleet.emit",
|
|
1284
|
+
/** Can submit a task-local structured result to the parent Director. */
|
|
1285
|
+
COORDINATION_RESULT_SUBMIT: "coordination.result.submit",
|
|
1286
|
+
/** Can read or write inter-agent mailbox messages. */
|
|
1287
|
+
COORDINATION_MAIL: "coordination.mail",
|
|
1288
|
+
/** Can install packages or run package managers with side effects. */
|
|
1289
|
+
PACKAGE_INSTALL: "package.install"
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
// src/coordination/subagent-result-tool.ts
|
|
1293
|
+
var SUBAGENT_STRUCTURED_REPORT_META_KEY = "subagentStructuredReport";
|
|
1294
|
+
var MAX_SUBAGENT_STRUCTURED_REPORT_CHARS = 8e3;
|
|
1295
|
+
var MAX_SUMMARY_CHARS = 1500;
|
|
1296
|
+
var MAX_FINDINGS = 16;
|
|
1297
|
+
var MAX_FILES = 64;
|
|
1298
|
+
var MAX_NEXT_STEPS = 12;
|
|
1299
|
+
var MAX_ITEM_CHARS = 500;
|
|
1300
|
+
function normalizeSubagentStructuredReport(value) {
|
|
1301
|
+
if (!isRecord(value)) return void 0;
|
|
1302
|
+
const summary = nonEmptyString(value["summary"]);
|
|
1303
|
+
const findings = stringArray(value["findings"]);
|
|
1304
|
+
const filesExamined = stringArray(value["files_examined"]);
|
|
1305
|
+
const nextSteps = stringArray(value["suggested_next_steps"]);
|
|
1306
|
+
const confidence = value["confidence"];
|
|
1307
|
+
if (!summary || summary.length > MAX_SUMMARY_CHARS || !findings || findings.length > MAX_FINDINGS || !filesExamined || filesExamined.length > MAX_FILES || !nextSteps || nextSteps.length > MAX_NEXT_STEPS || [...findings, ...filesExamined, ...nextSteps].some(
|
|
1308
|
+
(item) => item.trim().length > MAX_ITEM_CHARS
|
|
1309
|
+
) || typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
|
|
1310
|
+
return void 0;
|
|
1311
|
+
}
|
|
1312
|
+
const report = {
|
|
1313
|
+
summary,
|
|
1314
|
+
findings: findings.map((item) => item.trim()).filter(Boolean),
|
|
1315
|
+
files_examined: filesExamined.map((item) => item.trim()).filter(Boolean),
|
|
1316
|
+
confidence,
|
|
1317
|
+
suggested_next_steps: nextSteps.map((item) => item.trim()).filter(Boolean)
|
|
1318
|
+
};
|
|
1319
|
+
return JSON.stringify(report).length <= MAX_SUBAGENT_STRUCTURED_REPORT_CHARS ? report : void 0;
|
|
1320
|
+
}
|
|
1321
|
+
function readSubagentStructuredReport(ctx) {
|
|
1322
|
+
return normalizeSubagentStructuredReport(ctx?.meta?.[SUBAGENT_STRUCTURED_REPORT_META_KEY]);
|
|
1323
|
+
}
|
|
1324
|
+
function formatSubagentStructuredReport(report) {
|
|
1325
|
+
const findings = report.findings.length ? report.findings.map((item) => `- ${item}`).join("\n") : "- (none)";
|
|
1326
|
+
const files = report.files_examined.length ? report.files_examined.join(", ") : "(none)";
|
|
1327
|
+
const next = report.suggested_next_steps.length ? report.suggested_next_steps.map((item) => `- ${item}`).join("\n") : "- (none)";
|
|
1328
|
+
return [
|
|
1329
|
+
report.summary,
|
|
1330
|
+
`
|
|
1331
|
+
Findings:
|
|
1332
|
+
${findings}`,
|
|
1333
|
+
`
|
|
1334
|
+
Files examined: ${files}`,
|
|
1335
|
+
`Confidence: ${report.confidence.toFixed(2)}`,
|
|
1336
|
+
`
|
|
1337
|
+
Suggested next steps:
|
|
1338
|
+
${next}`
|
|
1339
|
+
].join("\n");
|
|
1340
|
+
}
|
|
1341
|
+
function makeSubagentResultTool() {
|
|
1342
|
+
return {
|
|
1343
|
+
name: "submit_result",
|
|
1344
|
+
description: "Submit the task result as a compact structured report for the Director. Call once near the end, before your final text response.",
|
|
1345
|
+
permission: "auto",
|
|
1346
|
+
mutating: false,
|
|
1347
|
+
capabilities: [ToolCapabilities.COORDINATION_RESULT_SUBMIT],
|
|
1348
|
+
inputSchema: {
|
|
1349
|
+
type: "object",
|
|
1350
|
+
properties: {
|
|
1351
|
+
summary: {
|
|
1352
|
+
type: "string",
|
|
1353
|
+
maxLength: MAX_SUMMARY_CHARS,
|
|
1354
|
+
description: "Concise outcome summary."
|
|
1355
|
+
},
|
|
1356
|
+
findings: {
|
|
1357
|
+
type: "array",
|
|
1358
|
+
maxItems: MAX_FINDINGS,
|
|
1359
|
+
items: { type: "string" },
|
|
1360
|
+
description: "Atomic findings or changes, with evidence when useful."
|
|
1361
|
+
},
|
|
1362
|
+
files_examined: {
|
|
1363
|
+
type: "array",
|
|
1364
|
+
maxItems: MAX_FILES,
|
|
1365
|
+
items: { type: "string" },
|
|
1366
|
+
description: "Project-relative files materially read or changed."
|
|
1367
|
+
},
|
|
1368
|
+
confidence: {
|
|
1369
|
+
type: "number",
|
|
1370
|
+
minimum: 0,
|
|
1371
|
+
maximum: 1,
|
|
1372
|
+
description: "Confidence from 0.0 to 1.0."
|
|
1373
|
+
},
|
|
1374
|
+
suggested_next_steps: {
|
|
1375
|
+
type: "array",
|
|
1376
|
+
maxItems: MAX_NEXT_STEPS,
|
|
1377
|
+
items: { type: "string" },
|
|
1378
|
+
description: "Concrete follow-ups; use an empty array when none remain."
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
required: ["summary", "findings", "files_examined", "confidence", "suggested_next_steps"],
|
|
1382
|
+
additionalProperties: false
|
|
1383
|
+
},
|
|
1384
|
+
async execute(input, ctx) {
|
|
1385
|
+
const report = normalizeSubagentStructuredReport(input);
|
|
1386
|
+
if (!report) {
|
|
1387
|
+
return {
|
|
1388
|
+
ok: false,
|
|
1389
|
+
error: "Invalid report: summary/findings/files_examined/confidence/suggested_next_steps are required and confidence must be 0..1."
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
ctx.meta[SUBAGENT_STRUCTURED_REPORT_META_KEY] = report;
|
|
1393
|
+
return {
|
|
1394
|
+
ok: true,
|
|
1395
|
+
findings: report.findings.length,
|
|
1396
|
+
filesExamined: report.files_examined.length,
|
|
1397
|
+
confidence: report.confidence
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
function isRecord(value) {
|
|
1403
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1404
|
+
}
|
|
1405
|
+
function nonEmptyString(value) {
|
|
1406
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1407
|
+
}
|
|
1408
|
+
function stringArray(value) {
|
|
1409
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1144
1412
|
// src/coordination/agent-subagent-runner.ts
|
|
1145
1413
|
function withDisabledToolFiltering(factory) {
|
|
1146
1414
|
return async (config) => {
|
|
@@ -1162,6 +1430,8 @@ function makeAgentSubagentRunner(opts) {
|
|
|
1162
1430
|
const taskStartedAt = Date.now();
|
|
1163
1431
|
const factoryResult = await opts.factory(ctx.config);
|
|
1164
1432
|
const { agent, events } = factoryResult;
|
|
1433
|
+
const agentContext = agent.ctx;
|
|
1434
|
+
if (agentContext?.meta) agentContext.meta["subagentTaskId"] = task.id;
|
|
1165
1435
|
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
1166
1436
|
const aborter = new AbortController();
|
|
1167
1437
|
ctx.budget._events = events;
|
|
@@ -1257,6 +1527,10 @@ function makeAgentSubagentRunner(opts) {
|
|
|
1257
1527
|
events.on("provider.text_delta", (e) => {
|
|
1258
1528
|
ctx.budget.markActivity();
|
|
1259
1529
|
streamingTextAcc = (streamingTextAcc + e.text).slice(-2e3);
|
|
1530
|
+
const text = streamingTextAcc.trim();
|
|
1531
|
+
if (text) {
|
|
1532
|
+
ctx.reportProgress?.({ text, source: "stream", capturedAt: Date.now() });
|
|
1533
|
+
}
|
|
1260
1534
|
})
|
|
1261
1535
|
);
|
|
1262
1536
|
const onParentAbort = () => aborter.abort();
|
|
@@ -1264,6 +1538,10 @@ function makeAgentSubagentRunner(opts) {
|
|
|
1264
1538
|
let result;
|
|
1265
1539
|
try {
|
|
1266
1540
|
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
1541
|
+
const finalText2 = result.finalText?.trim();
|
|
1542
|
+
if (finalText2) {
|
|
1543
|
+
ctx.reportProgress?.({ text: finalText2, source: "run_result", capturedAt: Date.now() });
|
|
1544
|
+
}
|
|
1267
1545
|
events.emit("subagent.task_completed", {
|
|
1268
1546
|
subagentId: ctx.subagentId,
|
|
1269
1547
|
taskId: task.id,
|
|
@@ -1345,6 +1623,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
1345
1623
|
}
|
|
1346
1624
|
return {
|
|
1347
1625
|
result: result.finalText,
|
|
1626
|
+
report: readSubagentStructuredReport(agentContext),
|
|
1348
1627
|
iterations: result.iterations,
|
|
1349
1628
|
toolCalls: usage.toolCalls
|
|
1350
1629
|
};
|
|
@@ -1417,7 +1696,7 @@ function agentPrompt(id) {
|
|
|
1417
1696
|
let resolved = "";
|
|
1418
1697
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
1419
1698
|
try {
|
|
1420
|
-
resolved = readFileSync(
|
|
1699
|
+
resolved = readFileSync(path9.join(dir, fileName), "utf8").trimEnd();
|
|
1421
1700
|
break;
|
|
1422
1701
|
} catch {
|
|
1423
1702
|
}
|
|
@@ -1426,20 +1705,20 @@ function agentPrompt(id) {
|
|
|
1426
1705
|
return resolved;
|
|
1427
1706
|
}
|
|
1428
1707
|
function agentPromptDirCandidates(envDir) {
|
|
1429
|
-
const globalRoot = process.env["WRONGSTACK_HOME"] ||
|
|
1708
|
+
const globalRoot = process.env["WRONGSTACK_HOME"] || path9.join(os.homedir(), ".wrongstack");
|
|
1430
1709
|
const candKey = `${envDir}\0${globalRoot}`;
|
|
1431
1710
|
const cached = candidateCache.get(candKey);
|
|
1432
1711
|
if (cached !== void 0) return cached;
|
|
1433
|
-
const here =
|
|
1712
|
+
const here = path9.dirname(fileURLToPath(import.meta.url));
|
|
1434
1713
|
const explicitDir = envDir || void 0;
|
|
1435
1714
|
const candidates = [
|
|
1436
|
-
...explicitDir ? [
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1715
|
+
...explicitDir ? [path9.resolve(explicitDir)] : [],
|
|
1716
|
+
path9.join(globalRoot, "instructions", "agents"),
|
|
1717
|
+
path9.resolve(here, "../../../../instructions/agents"),
|
|
1718
|
+
path9.resolve(here, "../../../instructions/agents"),
|
|
1719
|
+
path9.resolve(here, "../../instructions/agents"),
|
|
1720
|
+
path9.resolve(here, "../instructions/agents"),
|
|
1721
|
+
path9.resolve(here, "instructions/agents")
|
|
1443
1722
|
];
|
|
1444
1723
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
1445
1724
|
candidateCache.set(candKey, ordered);
|
|
@@ -3055,12 +3334,12 @@ var BrainDecisionQueue = class {
|
|
|
3055
3334
|
options: request.options,
|
|
3056
3335
|
rationale: "Decision escalated to human authority."
|
|
3057
3336
|
};
|
|
3058
|
-
const pending = new Promise((
|
|
3059
|
-
const entry = { request, resolve:
|
|
3337
|
+
const pending = new Promise((resolve9) => {
|
|
3338
|
+
const entry = { request, resolve: resolve9 };
|
|
3060
3339
|
if (this.opts.timeoutMs && this.opts.timeoutMs > 0) {
|
|
3061
3340
|
entry.timer = setTimeout(() => {
|
|
3062
3341
|
this.pending.delete(request.id);
|
|
3063
|
-
|
|
3342
|
+
resolve9({ type: "deny", reason: "Brain human decision timed out." });
|
|
3064
3343
|
}, this.opts.timeoutMs);
|
|
3065
3344
|
}
|
|
3066
3345
|
this.pending.set(request.id, entry);
|
|
@@ -3285,6 +3564,130 @@ var BrainMonitor = class {
|
|
|
3285
3564
|
}
|
|
3286
3565
|
}
|
|
3287
3566
|
};
|
|
3567
|
+
|
|
3568
|
+
// src/coordination/fleet-event-validation.ts
|
|
3569
|
+
var ROLE_BY_EVENT = {
|
|
3570
|
+
"bug.found": "bug-hunter",
|
|
3571
|
+
"refactor.plan": "refactor-planner",
|
|
3572
|
+
"critic.evaluation": "critic"
|
|
3573
|
+
};
|
|
3574
|
+
function validateFleetEventEmission(type, payload, role) {
|
|
3575
|
+
if (!(type in ROLE_BY_EVENT)) return void 0;
|
|
3576
|
+
const knownType = type;
|
|
3577
|
+
const expectedRole = ROLE_BY_EVENT[knownType];
|
|
3578
|
+
if (role !== expectedRole) {
|
|
3579
|
+
return `${knownType} may only be emitted by role ${expectedRole}; caller role is ${role ?? "unknown"}`;
|
|
3580
|
+
}
|
|
3581
|
+
if (!isRecord2(payload)) return `${knownType} payload must be an object`;
|
|
3582
|
+
switch (knownType) {
|
|
3583
|
+
case "bug.found":
|
|
3584
|
+
return validateBugFound(payload);
|
|
3585
|
+
case "refactor.plan":
|
|
3586
|
+
return validateRefactorPlan(payload);
|
|
3587
|
+
case "critic.evaluation":
|
|
3588
|
+
return validateCriticEvaluation(payload);
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
function validateBugFound(payload) {
|
|
3592
|
+
const finding = payload["finding"];
|
|
3593
|
+
if (!isRecord2(finding)) return "bug.found payload.finding must be an object";
|
|
3594
|
+
if (!isNonEmptyString(finding["id"])) return "bug.found finding.id must be a non-empty string";
|
|
3595
|
+
if (!isNonEmptyString(finding["type"]))
|
|
3596
|
+
return "bug.found finding.type must be a non-empty string";
|
|
3597
|
+
if (!isOneOf(finding["severity"], ["critical", "high", "medium", "low"])) {
|
|
3598
|
+
return "bug.found finding.severity must be critical|high|medium|low";
|
|
3599
|
+
}
|
|
3600
|
+
const location = finding["location"];
|
|
3601
|
+
if (!isRecord2(location) || !isNonEmptyString(location["file"]) || !isPositiveInteger(location["line"])) {
|
|
3602
|
+
return "bug.found finding.location must contain file and a positive integer line";
|
|
3603
|
+
}
|
|
3604
|
+
if (!isNonEmptyString(finding["description"])) {
|
|
3605
|
+
return "bug.found finding.description must be a non-empty string";
|
|
3606
|
+
}
|
|
3607
|
+
if (finding["suggestedFix"] !== void 0 && typeof finding["suggestedFix"] !== "string") {
|
|
3608
|
+
return "bug.found finding.suggestedFix must be a string when provided";
|
|
3609
|
+
}
|
|
3610
|
+
return void 0;
|
|
3611
|
+
}
|
|
3612
|
+
function validateRefactorPlan(payload) {
|
|
3613
|
+
const plan = payload["plan"];
|
|
3614
|
+
if (!isRecord2(plan)) return "refactor.plan payload.plan must be an object";
|
|
3615
|
+
if (!isNonEmptyString(plan["id"])) return "refactor.plan plan.id must be a non-empty string";
|
|
3616
|
+
if (!isStringArray(plan["basedOnBugIds"])) {
|
|
3617
|
+
return "refactor.plan plan.basedOnBugIds must be a string array";
|
|
3618
|
+
}
|
|
3619
|
+
if (!Array.isArray(plan["phases"]) || !plan["phases"].every(isValidPhase)) {
|
|
3620
|
+
return "refactor.plan plan.phases must contain valid numbered phases";
|
|
3621
|
+
}
|
|
3622
|
+
if (!isOneOf(plan["riskScore"], ["low", "medium", "high"])) {
|
|
3623
|
+
return "refactor.plan plan.riskScore must be low|medium|high";
|
|
3624
|
+
}
|
|
3625
|
+
if (!isNonNegativeInteger(plan["estimatedChangeCount"])) {
|
|
3626
|
+
return "refactor.plan plan.estimatedChangeCount must be a non-negative integer";
|
|
3627
|
+
}
|
|
3628
|
+
if (!isNonEmptyString(plan["rollbackStrategy"])) {
|
|
3629
|
+
return "refactor.plan plan.rollbackStrategy must be a non-empty string";
|
|
3630
|
+
}
|
|
3631
|
+
return void 0;
|
|
3632
|
+
}
|
|
3633
|
+
function validateCriticEvaluation(payload) {
|
|
3634
|
+
const evaluation = payload["evaluation"];
|
|
3635
|
+
if (!isRecord2(evaluation)) {
|
|
3636
|
+
return "critic.evaluation payload.evaluation must be an object";
|
|
3637
|
+
}
|
|
3638
|
+
if (!isNonEmptyString(evaluation["id"])) {
|
|
3639
|
+
return "critic.evaluation evaluation.id must be a non-empty string";
|
|
3640
|
+
}
|
|
3641
|
+
if (!isOneOf(evaluation["subjectType"], ["bug_finding", "refactor_plan"])) {
|
|
3642
|
+
return "critic.evaluation evaluation.subjectType must be bug_finding|refactor_plan";
|
|
3643
|
+
}
|
|
3644
|
+
if (!isNonEmptyString(evaluation["subjectId"])) {
|
|
3645
|
+
return "critic.evaluation evaluation.subjectId must be a non-empty string";
|
|
3646
|
+
}
|
|
3647
|
+
if (typeof evaluation["score"] !== "number" || !Number.isFinite(evaluation["score"]) || evaluation["score"] < 0 || evaluation["score"] > 10) {
|
|
3648
|
+
return "critic.evaluation evaluation.score must be between 0 and 10";
|
|
3649
|
+
}
|
|
3650
|
+
if (!isOneOf(evaluation["verdict"], ["approve", "needs_revision", "reject"])) {
|
|
3651
|
+
return "critic.evaluation evaluation.verdict must be approve|needs_revision|reject";
|
|
3652
|
+
}
|
|
3653
|
+
if (!isStringArray(evaluation["strengths"]) || !isStringArray(evaluation["weaknesses"])) {
|
|
3654
|
+
return "critic.evaluation strengths and weaknesses must be string arrays";
|
|
3655
|
+
}
|
|
3656
|
+
if (!Array.isArray(evaluation["concerns"]) || !evaluation["concerns"].every(isValidConcern)) {
|
|
3657
|
+
return "critic.evaluation concerns must contain valid concern objects";
|
|
3658
|
+
}
|
|
3659
|
+
return void 0;
|
|
3660
|
+
}
|
|
3661
|
+
function isValidPhase(value) {
|
|
3662
|
+
return isRecord2(value) && isPositiveInteger(value["number"]) && isNonEmptyString(value["title"]) && isStringArray(value["tasks"]) && isOneOf(value["risk"], ["low", "medium", "high"]);
|
|
3663
|
+
}
|
|
3664
|
+
function isValidConcern(value) {
|
|
3665
|
+
if (!isRecord2(value)) return false;
|
|
3666
|
+
if (!isNonEmptyString(value["description"])) return false;
|
|
3667
|
+
if (!isOneOf(value["severity"], ["blocking", "advisory"])) return false;
|
|
3668
|
+
const location = value["location"];
|
|
3669
|
+
return location === void 0 || isRecord2(location) && (location["file"] === void 0 || typeof location["file"] === "string") && isPositiveInteger(location["line"]);
|
|
3670
|
+
}
|
|
3671
|
+
function isRecord2(value) {
|
|
3672
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3673
|
+
}
|
|
3674
|
+
function isNonEmptyString(value) {
|
|
3675
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
3676
|
+
}
|
|
3677
|
+
function isPositiveInteger(value) {
|
|
3678
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
3679
|
+
}
|
|
3680
|
+
function isNonNegativeInteger(value) {
|
|
3681
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
3682
|
+
}
|
|
3683
|
+
function isStringArray(value) {
|
|
3684
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
3685
|
+
}
|
|
3686
|
+
function isOneOf(value, allowed) {
|
|
3687
|
+
return typeof value === "string" && allowed.includes(value);
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
// src/coordination/collab-debug.ts
|
|
3288
3691
|
var DEFAULT_MAX_TARGET_FILES = 30;
|
|
3289
3692
|
var DirectorAlertLevel = /* @__PURE__ */ ((DirectorAlertLevel2) => {
|
|
3290
3693
|
DirectorAlertLevel2["WARNING"] = "warning";
|
|
@@ -3379,9 +3782,9 @@ var CollabSession = class extends EventEmitter {
|
|
|
3379
3782
|
}
|
|
3380
3783
|
for (const filePath of allFiles) {
|
|
3381
3784
|
try {
|
|
3382
|
-
const [content,
|
|
3383
|
-
|
|
3384
|
-
|
|
3785
|
+
const [content, stat9] = await Promise.all([
|
|
3786
|
+
fsp9.readFile(filePath, "utf8"),
|
|
3787
|
+
fsp9.stat(filePath)
|
|
3385
3788
|
]);
|
|
3386
3789
|
const ext = filePath.split(".").pop() ?? "";
|
|
3387
3790
|
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
@@ -3389,8 +3792,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
3389
3792
|
path: filePath,
|
|
3390
3793
|
content,
|
|
3391
3794
|
language,
|
|
3392
|
-
snapshotMtimeMs:
|
|
3393
|
-
snapshotSizeBytes:
|
|
3795
|
+
snapshotMtimeMs: stat9.mtimeMs,
|
|
3796
|
+
snapshotSizeBytes: stat9.size
|
|
3394
3797
|
});
|
|
3395
3798
|
} catch {
|
|
3396
3799
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
@@ -3484,6 +3887,12 @@ var CollabSession = class extends EventEmitter {
|
|
|
3484
3887
|
for (const obj of this.extractJsonObjects(text)) {
|
|
3485
3888
|
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
3486
3889
|
if (!type) continue;
|
|
3890
|
+
const validationError = validateFleetEventEmission(
|
|
3891
|
+
type,
|
|
3892
|
+
obj,
|
|
3893
|
+
this.roleFromSubagentId(result.subagentId) ?? void 0
|
|
3894
|
+
);
|
|
3895
|
+
if (validationError) continue;
|
|
3487
3896
|
this.fleetBus.emit({
|
|
3488
3897
|
subagentId: result.subagentId,
|
|
3489
3898
|
taskId: result.taskId,
|
|
@@ -3803,9 +4212,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
3803
4212
|
for (const file of this.snapshot.files) {
|
|
3804
4213
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
3805
4214
|
try {
|
|
3806
|
-
const
|
|
3807
|
-
const mtimeChanged = file.snapshotMtimeMs !== void 0 &&
|
|
3808
|
-
const sizeChanged = file.snapshotSizeBytes !== void 0 &&
|
|
4215
|
+
const stat9 = await fsp9.stat(file.path);
|
|
4216
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat9.mtimeMs > file.snapshotMtimeMs + 1;
|
|
4217
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat9.size !== file.snapshotSizeBytes;
|
|
3809
4218
|
if (mtimeChanged || sizeChanged) {
|
|
3810
4219
|
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
3811
4220
|
}
|
|
@@ -4033,28 +4442,6 @@ var FLEET_ROSTER_WITHACP = {
|
|
|
4033
4442
|
...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
|
|
4034
4443
|
};
|
|
4035
4444
|
|
|
4036
|
-
// src/security/capabilities.ts
|
|
4037
|
-
var ToolCapabilities = {
|
|
4038
|
-
/** Can execute arbitrary commands in the user's shell (the `bash` tool). */
|
|
4039
|
-
SHELL_ARBITRARY: "shell.arbitrary",
|
|
4040
|
-
/** Can execute a restricted set of commands (the `exec` tool). */
|
|
4041
|
-
SHELL_RESTRICTED: "shell.restricted",
|
|
4042
|
-
/** Can run a restricted project formatter/linter-style command. */
|
|
4043
|
-
SHELL_EXEC: "shell.exec",
|
|
4044
|
-
/** Can write / modify / delete files inside the project. */
|
|
4045
|
-
FS_WRITE: "fs.write",
|
|
4046
|
-
/** Can write files outside the current project root (very high risk). */
|
|
4047
|
-
FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
|
|
4048
|
-
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
4049
|
-
SUBAGENT_SPAWN: "subagent.spawn",
|
|
4050
|
-
/** Can inspect fleet/subagent coordination state without mutating it. */
|
|
4051
|
-
COORDINATION_FLEET_READ: "coordination.fleet.read",
|
|
4052
|
-
/** Can read or write inter-agent mailbox messages. */
|
|
4053
|
-
COORDINATION_MAIL: "coordination.mail",
|
|
4054
|
-
/** Can install packages or run package managers with side effects. */
|
|
4055
|
-
PACKAGE_INSTALL: "package.install"
|
|
4056
|
-
};
|
|
4057
|
-
|
|
4058
4445
|
// src/coordination/delegate-tool.ts
|
|
4059
4446
|
function createDelegateTool(opts) {
|
|
4060
4447
|
const defaultTimeoutMs = opts.defaultTimeoutMs ?? 30 * 60 * 1e3;
|
|
@@ -4203,7 +4590,7 @@ function createDelegateTool(opts) {
|
|
|
4203
4590
|
subagentId
|
|
4204
4591
|
});
|
|
4205
4592
|
const dir = director;
|
|
4206
|
-
const result = await new Promise((
|
|
4593
|
+
const result = await new Promise((resolve9) => {
|
|
4207
4594
|
let settled = false;
|
|
4208
4595
|
let timer;
|
|
4209
4596
|
const finish = (value) => {
|
|
@@ -4213,7 +4600,7 @@ function createDelegateTool(opts) {
|
|
|
4213
4600
|
offTool();
|
|
4214
4601
|
offIter();
|
|
4215
4602
|
offProgress();
|
|
4216
|
-
|
|
4603
|
+
resolve9(value);
|
|
4217
4604
|
};
|
|
4218
4605
|
const arm = () => {
|
|
4219
4606
|
if (timer) clearTimeout(timer);
|
|
@@ -4277,7 +4664,11 @@ function createDelegateTool(opts) {
|
|
|
4277
4664
|
};
|
|
4278
4665
|
}
|
|
4279
4666
|
const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
4280
|
-
const partial = result.status === "success" ? void 0 :
|
|
4667
|
+
const partial = result.status === "success" ? void 0 : result.partial ? {
|
|
4668
|
+
lastAssistantText: result.partial.text,
|
|
4669
|
+
toolUsesObserved: result.toolCalls,
|
|
4670
|
+
events: result.iterations
|
|
4671
|
+
} : await readSubagentPartial(opts, subagentId);
|
|
4281
4672
|
const errorKind = result.error?.kind;
|
|
4282
4673
|
const retryable = result.error?.retryable;
|
|
4283
4674
|
const backoffMs = result.error?.backoffMs;
|
|
@@ -4311,6 +4702,7 @@ function createDelegateTool(opts) {
|
|
|
4311
4702
|
subagentId: result.subagentId,
|
|
4312
4703
|
taskId: result.taskId,
|
|
4313
4704
|
result: result.result,
|
|
4705
|
+
report: result.report,
|
|
4314
4706
|
error: result.error,
|
|
4315
4707
|
iterations: result.iterations,
|
|
4316
4708
|
toolCalls: result.toolCalls,
|
|
@@ -4413,7 +4805,7 @@ function buildDelegateSummary(role, result) {
|
|
|
4413
4805
|
const ms = result.durationMs;
|
|
4414
4806
|
const duration = ms < 6e4 ? `${Math.round(ms / 1e3)}s` : ms < 36e5 ? `${Math.round(ms / 6e4)}m` : `${(ms / 36e5).toFixed(1)}h`;
|
|
4415
4807
|
if (result.status === "success") {
|
|
4416
|
-
const preview = typeof result.result === "string" ? result.result.trim().slice(0, 120).replace(/\n+/g, " ") : null;
|
|
4808
|
+
const preview = result.report?.summary ? result.report.summary.trim().slice(0, 120).replace(/\n+/g, " ") : typeof result.result === "string" ? result.result.trim().slice(0, 120).replace(/\n+/g, " ") : null;
|
|
4417
4809
|
const tail = preview ? ` \u2014 ${preview}` : "";
|
|
4418
4810
|
return `[${roleLabel}] done in ${duration} (${result.iterations} iter, ${result.toolCalls} tools)${tail}`;
|
|
4419
4811
|
}
|
|
@@ -4424,13 +4816,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
4424
4816
|
if (!opts.sessionsRoot) return void 0;
|
|
4425
4817
|
const candidates = [];
|
|
4426
4818
|
if (opts.directorRunId) {
|
|
4427
|
-
candidates.push(
|
|
4819
|
+
candidates.push(path9.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
4428
4820
|
} else {
|
|
4429
4821
|
try {
|
|
4430
|
-
const entries = await
|
|
4822
|
+
const entries = await fsp9.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
4431
4823
|
for (const entry of entries) {
|
|
4432
4824
|
if (entry.isDirectory()) {
|
|
4433
|
-
candidates.push(
|
|
4825
|
+
candidates.push(path9.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
4434
4826
|
}
|
|
4435
4827
|
}
|
|
4436
4828
|
} catch {
|
|
@@ -4440,7 +4832,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
4440
4832
|
for (const file of candidates) {
|
|
4441
4833
|
let raw;
|
|
4442
4834
|
try {
|
|
4443
|
-
raw = await
|
|
4835
|
+
raw = await fsp9.readFile(file, "utf8");
|
|
4444
4836
|
} catch {
|
|
4445
4837
|
continue;
|
|
4446
4838
|
}
|
|
@@ -4532,13 +4924,13 @@ function makeDependencyWatcherConfig(opts) {
|
|
|
4532
4924
|
const globPatterns = patterns.filter((p) => p.includes("*"));
|
|
4533
4925
|
const plainPatterns = patterns.filter((p) => !p.includes("*"));
|
|
4534
4926
|
function matchesPattern(filePath) {
|
|
4535
|
-
const
|
|
4536
|
-
if (plainPatterns.includes(
|
|
4927
|
+
const basename8 = filePath.split("/").pop()?.split("\\").pop() ?? "";
|
|
4928
|
+
if (plainPatterns.includes(basename8)) return true;
|
|
4537
4929
|
for (const gp of globPatterns) {
|
|
4538
4930
|
const regex = new RegExp(
|
|
4539
4931
|
"^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
|
|
4540
4932
|
);
|
|
4541
|
-
if (regex.test(
|
|
4933
|
+
if (regex.test(basename8)) return true;
|
|
4542
4934
|
}
|
|
4543
4935
|
return false;
|
|
4544
4936
|
}
|
|
@@ -4624,7 +5016,7 @@ function attachDepWatcherBridge(opts) {
|
|
|
4624
5016
|
async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
4625
5017
|
let existing;
|
|
4626
5018
|
try {
|
|
4627
|
-
existing = await
|
|
5019
|
+
existing = await fsp9.readFile(lockPath, "utf8");
|
|
4628
5020
|
} catch {
|
|
4629
5021
|
}
|
|
4630
5022
|
if (existing) {
|
|
@@ -4648,7 +5040,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
|
4648
5040
|
}
|
|
4649
5041
|
async function releaseDirectorStateLock(lockPath) {
|
|
4650
5042
|
try {
|
|
4651
|
-
await
|
|
5043
|
+
await fsp9.unlink(lockPath);
|
|
4652
5044
|
} catch {
|
|
4653
5045
|
}
|
|
4654
5046
|
}
|
|
@@ -4804,6 +5196,14 @@ var DirectorStateCheckpoint = class {
|
|
|
4804
5196
|
}
|
|
4805
5197
|
};
|
|
4806
5198
|
|
|
5199
|
+
// src/coordination/spawn-budget.ts
|
|
5200
|
+
var HARD_MAX_SPAWN_DEPTH = 2;
|
|
5201
|
+
var DEFAULT_MAX_FLEET_SPAWNS = 64;
|
|
5202
|
+
function resolveMaxSpawnDepth(requested) {
|
|
5203
|
+
if (requested === void 0 || !Number.isFinite(requested)) return HARD_MAX_SPAWN_DEPTH;
|
|
5204
|
+
return Math.max(0, Math.min(HARD_MAX_SPAWN_DEPTH, Math.floor(requested)));
|
|
5205
|
+
}
|
|
5206
|
+
|
|
4807
5207
|
// src/coordination/director/director-errors.ts
|
|
4808
5208
|
var FleetSpawnBudgetError = class extends Error {
|
|
4809
5209
|
kind;
|
|
@@ -4832,6 +5232,20 @@ var FleetCostCapError = class extends Error {
|
|
|
4832
5232
|
this.observed = observed;
|
|
4833
5233
|
}
|
|
4834
5234
|
};
|
|
5235
|
+
var FleetTokenCapError = class extends Error {
|
|
5236
|
+
kind;
|
|
5237
|
+
limit;
|
|
5238
|
+
observed;
|
|
5239
|
+
constructor(limit, observed) {
|
|
5240
|
+
super(
|
|
5241
|
+
`Director token cap exceeded: total fleet usage ${Math.round(observed)} tokens meets or exceeds maxTokens ${Math.round(limit)}`
|
|
5242
|
+
);
|
|
5243
|
+
this.name = "FleetTokenCapError";
|
|
5244
|
+
this.kind = "max_tokens";
|
|
5245
|
+
this.limit = limit;
|
|
5246
|
+
this.observed = observed;
|
|
5247
|
+
}
|
|
5248
|
+
};
|
|
4835
5249
|
var FleetContextOverflowError = class extends Error {
|
|
4836
5250
|
kind;
|
|
4837
5251
|
limit;
|
|
@@ -4854,7 +5268,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
4854
5268
|
let resolved = "";
|
|
4855
5269
|
for (const root of instructionRootCandidates()) {
|
|
4856
5270
|
try {
|
|
4857
|
-
resolved = readFileSync(
|
|
5271
|
+
resolved = readFileSync(path9.join(root, relativePath), "utf8").trimEnd();
|
|
4858
5272
|
break;
|
|
4859
5273
|
} catch {
|
|
4860
5274
|
}
|
|
@@ -4870,11 +5284,11 @@ function renderInstructionTemplate(template, values) {
|
|
|
4870
5284
|
}
|
|
4871
5285
|
function instructionRootCandidates() {
|
|
4872
5286
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
4873
|
-
const here =
|
|
5287
|
+
const here = path9.dirname(fileURLToPath(import.meta.url));
|
|
4874
5288
|
const candidates = [
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
5289
|
+
path9.resolve(here, "../../instructions"),
|
|
5290
|
+
path9.resolve(here, "../instructions"),
|
|
5291
|
+
path9.resolve(here, "instructions")
|
|
4878
5292
|
];
|
|
4879
5293
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
|
|
4880
5294
|
return rootCandidates;
|
|
@@ -4948,554 +5362,69 @@ function rosterSummaryFromConfigs(roster) {
|
|
|
4948
5362
|
return lines.join("\n");
|
|
4949
5363
|
}
|
|
4950
5364
|
|
|
4951
|
-
// src/
|
|
4952
|
-
var
|
|
4953
|
-
|
|
4954
|
-
{
|
|
4955
|
-
|
|
4956
|
-
{
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
// src/kanban/storage.ts
|
|
4962
|
-
var KANBANS_DIR = "kanbans";
|
|
4963
|
-
var BOARD_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
4964
|
-
function getKanbanDir(projectRoot) {
|
|
4965
|
-
return path10.join(projectRoot, ".wrongstack", KANBANS_DIR);
|
|
4966
|
-
}
|
|
4967
|
-
function getKanbanPath(projectRoot, boardId) {
|
|
4968
|
-
assertValidBoardId(boardId);
|
|
4969
|
-
const dir = path10.resolve(getKanbanDir(projectRoot));
|
|
4970
|
-
const resolved = path10.resolve(dir, `${boardId}.json`);
|
|
4971
|
-
const rel = path10.relative(dir, resolved);
|
|
4972
|
-
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
4973
|
-
throw invalidBoardId(boardId);
|
|
4974
|
-
}
|
|
4975
|
-
return resolved;
|
|
4976
|
-
}
|
|
4977
|
-
function getKanbanEventsPath(projectRoot, boardId) {
|
|
4978
|
-
assertValidBoardId(boardId);
|
|
4979
|
-
const dir = path10.resolve(getKanbanDir(projectRoot));
|
|
4980
|
-
const resolved = path10.resolve(dir, `${boardId}.events.jsonl`);
|
|
4981
|
-
const rel = path10.relative(dir, resolved);
|
|
4982
|
-
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
4983
|
-
throw invalidBoardId(boardId);
|
|
4984
|
-
}
|
|
4985
|
-
return resolved;
|
|
4986
|
-
}
|
|
4987
|
-
function isValidBoardId(boardId) {
|
|
4988
|
-
return BOARD_ID_RE.test(boardId) && !boardId.includes("..");
|
|
4989
|
-
}
|
|
4990
|
-
function assertValidBoardId(boardId) {
|
|
4991
|
-
if (!isValidBoardId(boardId)) throw invalidBoardId(boardId);
|
|
4992
|
-
}
|
|
4993
|
-
function invalidBoardId(boardId) {
|
|
4994
|
-
return new Error(`Invalid kanban board id: ${boardId}`);
|
|
4995
|
-
}
|
|
4996
|
-
function isEnoent(err) {
|
|
4997
|
-
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
4998
|
-
}
|
|
4999
|
-
async function pathExists(filePath) {
|
|
5000
|
-
try {
|
|
5001
|
-
await fsp7.stat(filePath);
|
|
5002
|
-
return true;
|
|
5003
|
-
} catch (err) {
|
|
5004
|
-
if (isEnoent(err)) return false;
|
|
5005
|
-
throw err;
|
|
5006
|
-
}
|
|
5365
|
+
// src/coordination/dispatcher.ts
|
|
5366
|
+
var DEFAULT_DISPATCH_ROLE = "executor";
|
|
5367
|
+
var FALLBACK_DEFINITION = {
|
|
5368
|
+
config: { role: "unknown", name: "Unknown Agent" },
|
|
5369
|
+
budget: {},
|
|
5370
|
+
capability: { phase: "meta", summary: "", keywords: [] }
|
|
5371
|
+
};
|
|
5372
|
+
function normalize(text) {
|
|
5373
|
+
return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
|
|
5007
5374
|
}
|
|
5008
|
-
|
|
5009
|
-
const
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5375
|
+
function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
5376
|
+
const haySet = new Set(normalize(task).split(/\s+/).filter(Boolean));
|
|
5377
|
+
const out = [];
|
|
5378
|
+
for (const def of Object.values(catalog)) {
|
|
5379
|
+
if (!def?.config?.role) continue;
|
|
5380
|
+
let score = 0;
|
|
5381
|
+
const matched = [];
|
|
5382
|
+
for (const kw of def.capability.keywords) {
|
|
5383
|
+
const needleWords = normalize(kw).split(/\s+/).filter(Boolean);
|
|
5384
|
+
const allPresent = needleWords.every((w) => haySet.has(w));
|
|
5385
|
+
if (allPresent) {
|
|
5386
|
+
score += needleWords.length;
|
|
5387
|
+
matched.push(kw);
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
if (score > 0) {
|
|
5391
|
+
out.push({ role: def.config.role, name: def.config.name, score, matched });
|
|
5392
|
+
}
|
|
5016
5393
|
}
|
|
5394
|
+
out.sort((a, b) => b.score - a.score);
|
|
5395
|
+
return out;
|
|
5017
5396
|
}
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
const
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
}
|
|
5026
|
-
return matches[0] ?? null;
|
|
5397
|
+
function heuristicConfidence(candidates) {
|
|
5398
|
+
if (candidates.length === 0) return 0;
|
|
5399
|
+
const top = candidates[0]?.score ?? 0;
|
|
5400
|
+
const second = candidates[1]?.score ?? 0;
|
|
5401
|
+
const strength = Math.min(1, top / 3);
|
|
5402
|
+
const margin = (top - second + 1) / (top + 1);
|
|
5403
|
+
return Math.min(1, strength * margin);
|
|
5027
5404
|
}
|
|
5028
|
-
async function
|
|
5029
|
-
const
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5405
|
+
async function dispatchAgent(task, opts = {}) {
|
|
5406
|
+
const catalog = opts.catalog ?? AGENT_CATALOG;
|
|
5407
|
+
const threshold = opts.confidenceThreshold ?? 0.4;
|
|
5408
|
+
const maxCandidates = opts.maxCandidates ?? 6;
|
|
5409
|
+
const candidates = scoreAgents(task, catalog);
|
|
5410
|
+
const confidence = heuristicConfidence(candidates);
|
|
5411
|
+
const top = candidates[0];
|
|
5412
|
+
if (top && confidence >= threshold) {
|
|
5413
|
+
return {
|
|
5414
|
+
role: top.role,
|
|
5415
|
+
definition: catalog[top.role] ?? FALLBACK_DEFINITION,
|
|
5416
|
+
confidence,
|
|
5417
|
+
method: "heuristic",
|
|
5418
|
+
reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
|
|
5419
|
+
alternatives: candidates.slice(1, maxCandidates)
|
|
5420
|
+
};
|
|
5037
5421
|
}
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
`, "utf8");
|
|
5045
|
-
});
|
|
5046
|
-
}
|
|
5047
|
-
async function mutateBoard(projectRoot, boardRef, mutator) {
|
|
5048
|
-
const boardId = await resolveBoardRef(projectRoot, boardRef);
|
|
5049
|
-
if (!boardId) return null;
|
|
5050
|
-
const filePath = getKanbanPath(projectRoot, boardId);
|
|
5051
|
-
return withFileLock(filePath, async () => {
|
|
5052
|
-
let board;
|
|
5053
|
-
try {
|
|
5054
|
-
const raw = await fsp7.readFile(filePath, "utf8");
|
|
5055
|
-
board = normalizeBoard(JSON.parse(raw));
|
|
5056
|
-
} catch (err) {
|
|
5057
|
-
if (isEnoent(err)) return null;
|
|
5058
|
-
throw err;
|
|
5059
|
-
}
|
|
5060
|
-
const result = await mutator(board);
|
|
5061
|
-
await writeBoardUnlocked(filePath, normalizeBoard(board));
|
|
5062
|
-
return { board, result };
|
|
5063
|
-
});
|
|
5064
|
-
}
|
|
5065
|
-
function summarizeBoard(board) {
|
|
5066
|
-
const total = board.tasks.length;
|
|
5067
|
-
const completed = board.tasks.filter((task) => task.status === "completed").length;
|
|
5068
|
-
const lastActivity = latestActivity(board);
|
|
5069
|
-
return {
|
|
5070
|
-
id: board.id,
|
|
5071
|
-
title: board.title,
|
|
5072
|
-
createdAt: board.createdAt,
|
|
5073
|
-
updatedAt: board.updatedAt,
|
|
5074
|
-
columnCount: board.columns.length,
|
|
5075
|
-
taskCount: total,
|
|
5076
|
-
completedTaskCount: completed,
|
|
5077
|
-
...board.description !== void 0 ? { description: board.description } : {},
|
|
5078
|
-
...board.tags !== void 0 ? { tags: board.tags } : {},
|
|
5079
|
-
...lastActivity !== void 0 ? { lastActivity } : {}
|
|
5080
|
-
};
|
|
5081
|
-
}
|
|
5082
|
-
async function listBoardSummaries(projectRoot) {
|
|
5083
|
-
const summaries = [];
|
|
5084
|
-
for (const id of await listBoardIds(projectRoot)) {
|
|
5085
|
-
const board = await readBoard(projectRoot, id);
|
|
5086
|
-
if (board) summaries.push(summarizeBoard(board));
|
|
5087
|
-
}
|
|
5088
|
-
return summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
5089
|
-
}
|
|
5090
|
-
function normalizeBoard(board) {
|
|
5091
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5092
|
-
const columns = [...board.columns?.length ? board.columns : DEFAULT_COLUMNS].map((column, index) => ({
|
|
5093
|
-
...column,
|
|
5094
|
-
order: column.order ?? index
|
|
5095
|
-
})).sort((a, b) => a.order - b.order).map((column, index) => ({ ...column, order: index }));
|
|
5096
|
-
const columnIds = new Set(columns.map((column) => column.id));
|
|
5097
|
-
const fallbackColumnId = columns[0]?.id ?? "backlog";
|
|
5098
|
-
const tasks = normalizeTaskOrders(
|
|
5099
|
-
(board.tasks ?? []).map((task, index) => ({
|
|
5100
|
-
...task,
|
|
5101
|
-
columnId: columnIds.has(task.columnId) ? task.columnId : fallbackColumnId,
|
|
5102
|
-
order: task.order ?? index,
|
|
5103
|
-
priority: task.priority ?? "medium",
|
|
5104
|
-
status: task.status ?? "pending",
|
|
5105
|
-
createdAt: task.createdAt ?? now,
|
|
5106
|
-
updatedAt: task.updatedAt ?? now
|
|
5107
|
-
}))
|
|
5108
|
-
);
|
|
5109
|
-
return {
|
|
5110
|
-
...board,
|
|
5111
|
-
version: board.version ?? CURRENT_KANBAN_VERSION,
|
|
5112
|
-
createdAt: board.createdAt ?? now,
|
|
5113
|
-
updatedAt: board.updatedAt ?? now,
|
|
5114
|
-
columns,
|
|
5115
|
-
tasks
|
|
5116
|
-
};
|
|
5117
|
-
}
|
|
5118
|
-
function latestActivity(board) {
|
|
5119
|
-
return [board.updatedAt, ...board.tasks.map((task) => task.updatedAt)].filter(Boolean).sort().reverse()[0];
|
|
5120
|
-
}
|
|
5121
|
-
async function writeBoardUnlocked(filePath, board) {
|
|
5122
|
-
await atomicWrite(filePath, `${JSON.stringify(board, null, 2)}
|
|
5123
|
-
`, { encoding: "utf8" });
|
|
5124
|
-
}
|
|
5125
|
-
function normalizeTaskOrders(tasks) {
|
|
5126
|
-
const byColumn = /* @__PURE__ */ new Map();
|
|
5127
|
-
for (const task of tasks) {
|
|
5128
|
-
const columnTasks = byColumn.get(task.columnId) ?? [];
|
|
5129
|
-
columnTasks.push(task);
|
|
5130
|
-
byColumn.set(task.columnId, columnTasks);
|
|
5131
|
-
}
|
|
5132
|
-
for (const columnTasks of byColumn.values()) {
|
|
5133
|
-
columnTasks.sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt)).forEach((task, index) => {
|
|
5134
|
-
task.order = index;
|
|
5135
|
-
});
|
|
5136
|
-
}
|
|
5137
|
-
return tasks;
|
|
5138
|
-
}
|
|
5139
|
-
|
|
5140
|
-
// src/kanban/manager.ts
|
|
5141
|
-
async function listBoards(projectRoot) {
|
|
5142
|
-
return listBoardSummaries(projectRoot);
|
|
5143
|
-
}
|
|
5144
|
-
async function updateTaskAssignment(projectRoot, boardId, taskId, patch) {
|
|
5145
|
-
let event;
|
|
5146
|
-
const updated = await mutateBoard(projectRoot, boardId, (board) => {
|
|
5147
|
-
const task = findTask(board, taskId);
|
|
5148
|
-
if (!task) return null;
|
|
5149
|
-
const previousColumnId = task.columnId;
|
|
5150
|
-
const beforeAssignment = task.assignment ? { ...task.assignment } : void 0;
|
|
5151
|
-
const nextAssignment = {
|
|
5152
|
-
...task.assignment ?? { status: "assigned" }
|
|
5153
|
-
};
|
|
5154
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
5155
|
-
if (value !== void 0) {
|
|
5156
|
-
nextAssignment[key] = value;
|
|
5157
|
-
}
|
|
5158
|
-
}
|
|
5159
|
-
task.assignment = nextAssignment;
|
|
5160
|
-
if (task.assignment.agentId) task.assignedAgent = task.assignment.agentId;
|
|
5161
|
-
if (task.assignment.status === "completed") {
|
|
5162
|
-
task.assignment.completedAt = task.assignment.completedAt ?? nowIso();
|
|
5163
|
-
task.status = "completed";
|
|
5164
|
-
task.completedAt = task.assignment.completedAt;
|
|
5165
|
-
if (patch.error === void 0) delete task.assignment.error;
|
|
5166
|
-
} else if (task.assignment.status === "running") {
|
|
5167
|
-
delete task.assignment.completedAt;
|
|
5168
|
-
if (patch.error === void 0) delete task.assignment.error;
|
|
5169
|
-
task.status = "in_progress";
|
|
5170
|
-
delete task.completedAt;
|
|
5171
|
-
} else if (task.assignment.status === "failed") {
|
|
5172
|
-
delete task.assignment.completedAt;
|
|
5173
|
-
task.status = "failed";
|
|
5174
|
-
delete task.completedAt;
|
|
5175
|
-
} else if (task.assignment.status === "cancelled") {
|
|
5176
|
-
delete task.assignment.completedAt;
|
|
5177
|
-
if (patch.error === void 0) delete task.assignment.error;
|
|
5178
|
-
task.status = "blocked";
|
|
5179
|
-
delete task.completedAt;
|
|
5180
|
-
} else if (task.assignment.status === "queued" || task.assignment.status === "assigned") {
|
|
5181
|
-
delete task.assignment.completedAt;
|
|
5182
|
-
if (patch.error === void 0) delete task.assignment.error;
|
|
5183
|
-
if (task.status === "completed" || task.status === "failed") {
|
|
5184
|
-
task.status = task.assignment.status === "queued" ? "ready" : "pending";
|
|
5185
|
-
}
|
|
5186
|
-
delete task.completedAt;
|
|
5187
|
-
}
|
|
5188
|
-
syncTaskColumnForStatus(board, task, previousColumnId);
|
|
5189
|
-
task.updatedAt = nowIso();
|
|
5190
|
-
board.updatedAt = task.updatedAt;
|
|
5191
|
-
event = createKanbanEvent(board.id, task, assignmentEventType(task.assignment.status), {
|
|
5192
|
-
before: beforeAssignment,
|
|
5193
|
-
after: { ...task.assignment },
|
|
5194
|
-
note: patch.error ?? patch.lastResult
|
|
5195
|
-
});
|
|
5196
|
-
return task;
|
|
5197
|
-
});
|
|
5198
|
-
if (updated && event) await emitKanbanEvent(projectRoot, event);
|
|
5199
|
-
return updated?.result ? updated.board : null;
|
|
5200
|
-
}
|
|
5201
|
-
async function claimReadyTask(projectRoot, input = {}) {
|
|
5202
|
-
if (input.boardId) return claimReadyTaskOnBoard(projectRoot, input.boardId, input);
|
|
5203
|
-
for (const board of await listBoards(projectRoot)) {
|
|
5204
|
-
const claimed = await claimReadyTaskOnBoard(projectRoot, board.id, input);
|
|
5205
|
-
if (claimed) return claimed;
|
|
5206
|
-
}
|
|
5207
|
-
return null;
|
|
5208
|
-
}
|
|
5209
|
-
async function searchKanban(projectRoot, input = {}) {
|
|
5210
|
-
const boardIds = input.boardId ? [input.boardId] : (await listBoards(projectRoot)).map((b) => b.id);
|
|
5211
|
-
const results = [];
|
|
5212
|
-
for (const boardId of boardIds) {
|
|
5213
|
-
const board = await readBoard(projectRoot, boardId);
|
|
5214
|
-
if (!board) continue;
|
|
5215
|
-
for (const task of board.tasks) {
|
|
5216
|
-
if (!matchesKanbanSearch(board, task, input)) continue;
|
|
5217
|
-
results.push({ board: summarizeBoard(board), task });
|
|
5218
|
-
}
|
|
5219
|
-
}
|
|
5220
|
-
return results;
|
|
5221
|
-
}
|
|
5222
|
-
async function listReadyTasks(projectRoot, input = {}) {
|
|
5223
|
-
const results = await searchKanban(projectRoot, { ...input, readyOnly: true });
|
|
5224
|
-
return input.limit && input.limit > 0 ? results.slice(0, input.limit) : results;
|
|
5225
|
-
}
|
|
5226
|
-
function areDependenciesMet(board, taskId) {
|
|
5227
|
-
const task = findTask(board, taskId);
|
|
5228
|
-
if (!task?.dependsOn?.length) return true;
|
|
5229
|
-
return task.dependsOn.every((depId) => {
|
|
5230
|
-
const depTask = board.tasks.find((candidate) => candidate.id === depId);
|
|
5231
|
-
return depTask?.status === "completed";
|
|
5232
|
-
});
|
|
5233
|
-
}
|
|
5234
|
-
function buildAssignment(input) {
|
|
5235
|
-
return {
|
|
5236
|
-
status: input.status ?? "assigned",
|
|
5237
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
5238
|
-
...input.name !== void 0 ? { name: input.name } : {},
|
|
5239
|
-
...input.role !== void 0 ? { role: input.role } : {},
|
|
5240
|
-
...input.provider !== void 0 ? { provider: input.provider } : {},
|
|
5241
|
-
...input.model !== void 0 ? { model: input.model } : {},
|
|
5242
|
-
...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
|
|
5243
|
-
...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
|
|
5244
|
-
...input.tools !== void 0 ? { tools: input.tools } : {},
|
|
5245
|
-
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
|
|
5246
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
5247
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
5248
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
5249
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
5250
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
5251
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
|
|
5252
|
-
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
5253
|
-
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
5254
|
-
...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {}
|
|
5255
|
-
};
|
|
5256
|
-
}
|
|
5257
|
-
async function claimReadyTaskOnBoard(projectRoot, boardId, input) {
|
|
5258
|
-
let event;
|
|
5259
|
-
const updated = await mutateBoard(projectRoot, boardId, (board) => {
|
|
5260
|
-
const candidates = input.taskId ? [findTask(board, input.taskId)].filter((task2) => Boolean(task2)) : board.tasks.filter((task2) => isTaskReadyForWork(board, task2)).sort(compareTasksForWork);
|
|
5261
|
-
const task = candidates.find((candidate) => isTaskReadyForWork(board, candidate));
|
|
5262
|
-
if (!task) return null;
|
|
5263
|
-
const previousColumnId = task.columnId;
|
|
5264
|
-
const current = task.assignment;
|
|
5265
|
-
const assignment = buildAssignment({
|
|
5266
|
-
...current?.agentId !== void 0 ? { agentId: current.agentId } : {},
|
|
5267
|
-
...current?.name !== void 0 ? { name: current.name } : {},
|
|
5268
|
-
...current?.role !== void 0 ? { role: current.role } : {},
|
|
5269
|
-
...current?.provider !== void 0 ? { provider: current.provider } : {},
|
|
5270
|
-
...current?.model !== void 0 ? { model: current.model } : {},
|
|
5271
|
-
...current?.fallbackProfile !== void 0 ? { fallbackProfile: current.fallbackProfile } : {},
|
|
5272
|
-
...current?.fallbackModels !== void 0 ? { fallbackModels: current.fallbackModels } : {},
|
|
5273
|
-
...current?.tools !== void 0 ? { tools: current.tools } : {},
|
|
5274
|
-
...current?.allowedCapabilities !== void 0 ? { allowedCapabilities: current.allowedCapabilities } : {},
|
|
5275
|
-
...current?.leaseId !== void 0 ? { leaseId: current.leaseId } : {},
|
|
5276
|
-
...current?.claimedAt !== void 0 ? { claimedAt: current.claimedAt } : {},
|
|
5277
|
-
...current?.heartbeatAt !== void 0 ? { heartbeatAt: current.heartbeatAt } : {},
|
|
5278
|
-
...current?.leaseExpiresAt !== void 0 ? { leaseExpiresAt: current.leaseExpiresAt } : {},
|
|
5279
|
-
...current?.attempt !== void 0 ? { attempt: current.attempt } : {},
|
|
5280
|
-
...current?.maxAttempts !== void 0 ? { maxAttempts: current.maxAttempts } : {},
|
|
5281
|
-
...current?.costCeilingUsd !== void 0 ? { costCeilingUsd: current.costCeilingUsd } : {},
|
|
5282
|
-
...current?.retryPolicy !== void 0 ? { retryPolicy: current.retryPolicy } : {},
|
|
5283
|
-
...current?.lastFailureKind !== void 0 ? { lastFailureKind: current.lastFailureKind } : {},
|
|
5284
|
-
...input.agentId !== void 0 ? { agentId: input.agentId } : {},
|
|
5285
|
-
...input.name !== void 0 ? { name: input.name } : {},
|
|
5286
|
-
...input.role !== void 0 ? { role: input.role } : {},
|
|
5287
|
-
...input.provider !== void 0 ? { provider: input.provider } : {},
|
|
5288
|
-
...input.model !== void 0 ? { model: input.model } : {},
|
|
5289
|
-
...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
|
|
5290
|
-
...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
|
|
5291
|
-
...input.tools !== void 0 ? { tools: input.tools } : {},
|
|
5292
|
-
...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
|
|
5293
|
-
...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
|
|
5294
|
-
...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
|
|
5295
|
-
...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
|
|
5296
|
-
...input.leaseExpiresAt !== void 0 ? { leaseExpiresAt: input.leaseExpiresAt } : {},
|
|
5297
|
-
...input.attempt !== void 0 ? { attempt: input.attempt } : {},
|
|
5298
|
-
...input.maxAttempts !== void 0 ? { maxAttempts: input.maxAttempts } : {},
|
|
5299
|
-
...input.costCeilingUsd !== void 0 ? { costCeilingUsd: input.costCeilingUsd } : {},
|
|
5300
|
-
...input.retryPolicy !== void 0 ? { retryPolicy: input.retryPolicy } : {},
|
|
5301
|
-
...input.lastFailureKind !== void 0 ? { lastFailureKind: input.lastFailureKind } : {},
|
|
5302
|
-
...input.assignee !== void 0 ? { assignee: input.assignee } : {},
|
|
5303
|
-
status: input.status ?? "queued"
|
|
5304
|
-
});
|
|
5305
|
-
assignment.claimedAt = assignment.claimedAt ?? nowIso();
|
|
5306
|
-
assignment.dispatchedAt = assignment.dispatchedAt ?? assignment.claimedAt;
|
|
5307
|
-
task.assignment = assignment;
|
|
5308
|
-
if (assignment.agentId ?? assignment.role ?? assignment.name) {
|
|
5309
|
-
task.assignedAgent = assignment.agentId ?? assignment.role ?? assignment.name;
|
|
5310
|
-
}
|
|
5311
|
-
if (input.assignee ?? assignment.name ?? assignment.agentId) {
|
|
5312
|
-
task.assignee = input.assignee ?? assignment.name ?? assignment.agentId;
|
|
5313
|
-
}
|
|
5314
|
-
task.status = assignment.status === "running" ? "in_progress" : "ready";
|
|
5315
|
-
delete task.completedAt;
|
|
5316
|
-
syncTaskColumnForStatus(board, task, previousColumnId);
|
|
5317
|
-
task.updatedAt = nowIso();
|
|
5318
|
-
board.updatedAt = task.updatedAt;
|
|
5319
|
-
event = createKanbanEvent(board.id, task, "task.claimed", {
|
|
5320
|
-
before: current ? { ...current } : void 0,
|
|
5321
|
-
after: { ...assignment }
|
|
5322
|
-
});
|
|
5323
|
-
return task;
|
|
5324
|
-
});
|
|
5325
|
-
if (updated && event) await emitKanbanEvent(projectRoot, event);
|
|
5326
|
-
return updated?.result ? { board: updated.board, task: updated.result } : null;
|
|
5327
|
-
}
|
|
5328
|
-
function compareTasksForWork(a, b) {
|
|
5329
|
-
const priorityRank = {
|
|
5330
|
-
critical: 0,
|
|
5331
|
-
high: 1,
|
|
5332
|
-
medium: 2,
|
|
5333
|
-
low: 3
|
|
5334
|
-
};
|
|
5335
|
-
return priorityRank[a.priority] - priorityRank[b.priority] || a.columnId.localeCompare(b.columnId) || a.order - b.order || a.createdAt.localeCompare(b.createdAt);
|
|
5336
|
-
}
|
|
5337
|
-
function matchesKanbanSearch(board, task, input) {
|
|
5338
|
-
const query = input.query?.trim().toLowerCase();
|
|
5339
|
-
if (input.assignedAgent && task.assignedAgent !== input.assignedAgent) return false;
|
|
5340
|
-
if (input.status && task.status !== input.status) return false;
|
|
5341
|
-
if (input.priority && task.priority !== input.priority) return false;
|
|
5342
|
-
if (input.label && !task.labels?.includes(input.label)) return false;
|
|
5343
|
-
if (input.chainId && task.chain?.chainId !== input.chainId) return false;
|
|
5344
|
-
if (input.readyOnly && !isTaskReadyForWork(board, task)) return false;
|
|
5345
|
-
if (!query) return true;
|
|
5346
|
-
return [task.title, task.description, task.assignedAgent, task.assignee, ...task.labels ?? []].filter(Boolean).some((value) => String(value).toLowerCase().includes(query));
|
|
5347
|
-
}
|
|
5348
|
-
function findTask(board, taskId) {
|
|
5349
|
-
const exact = board.tasks.find((task) => task.id === taskId);
|
|
5350
|
-
if (exact) return exact;
|
|
5351
|
-
const matches = board.tasks.filter((task) => task.id.startsWith(taskId));
|
|
5352
|
-
if (matches.length > 1) {
|
|
5353
|
-
throw new Error(
|
|
5354
|
-
`Ambiguous kanban task id "${taskId}": ${matches.slice(0, 5).map((task) => task.id).join(", ")}`
|
|
5355
|
-
);
|
|
5356
|
-
}
|
|
5357
|
-
return matches[0];
|
|
5358
|
-
}
|
|
5359
|
-
function existingColumnId(board, columnId) {
|
|
5360
|
-
if (!columnId) return void 0;
|
|
5361
|
-
const exact = board.columns.find((column) => column.id === columnId);
|
|
5362
|
-
if (exact) return exact.id;
|
|
5363
|
-
const matches = board.columns.filter((column) => column.id.startsWith(columnId));
|
|
5364
|
-
if (matches.length > 1) {
|
|
5365
|
-
throw new Error(
|
|
5366
|
-
`Ambiguous kanban column id "${columnId}": ${matches.slice(0, 5).map((column) => column.id).join(", ")}`
|
|
5367
|
-
);
|
|
5368
|
-
}
|
|
5369
|
-
return matches[0]?.id;
|
|
5370
|
-
}
|
|
5371
|
-
function isTaskReadyForWork(board, task) {
|
|
5372
|
-
if (!["pending", "ready"].includes(task.status)) return false;
|
|
5373
|
-
if (task.assignment && ["queued", "running"].includes(task.assignment.status)) return false;
|
|
5374
|
-
if (task.mergedIntoTaskId) return false;
|
|
5375
|
-
if (!areDependenciesMet(board, task.id)) return false;
|
|
5376
|
-
return true;
|
|
5377
|
-
}
|
|
5378
|
-
function createKanbanEvent(boardId, task, type, details = {}) {
|
|
5379
|
-
return {
|
|
5380
|
-
id: randomUUID(),
|
|
5381
|
-
boardId,
|
|
5382
|
-
taskId: task.id,
|
|
5383
|
-
type,
|
|
5384
|
-
ts: nowIso(),
|
|
5385
|
-
...task.assignment?.agentId !== void 0 ? { actor: task.assignment.agentId } : {},
|
|
5386
|
-
...task.assignment?.subagentId !== void 0 ? { subagentId: task.assignment.subagentId } : {},
|
|
5387
|
-
...task.assignment?.runTaskId !== void 0 ? { runTaskId: task.assignment.runTaskId } : {},
|
|
5388
|
-
...details
|
|
5389
|
-
};
|
|
5390
|
-
}
|
|
5391
|
-
async function emitKanbanEvent(projectRoot, event) {
|
|
5392
|
-
try {
|
|
5393
|
-
await appendKanbanEvent(projectRoot, event.boardId, event);
|
|
5394
|
-
} catch {
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
function assignmentEventType(status) {
|
|
5398
|
-
return status === "completed" ? "task.assignment.completed" : status === "failed" ? "task.assignment.failed" : status === "running" ? "task.assignment.running" : status === "cancelled" ? "task.assignment.cancelled" : "task.assignment.updated";
|
|
5399
|
-
}
|
|
5400
|
-
function columnIdForKanbanStatus(board, status) {
|
|
5401
|
-
const preferred = status === "completed" ? ["done", "completed"] : status === "in_progress" ? ["in-progress", "progress", "doing"] : status === "review" || status === "failed" ? ["review"] : status === "blocked" ? ["blocked", "backlog"] : status === "ready" ? ["todo", "ready", "backlog"] : status === "archived" ? ["done", "archive", "backlog"] : ["todo", "backlog"];
|
|
5402
|
-
for (const columnRef of preferred) {
|
|
5403
|
-
const columnId = existingColumnId(board, columnRef);
|
|
5404
|
-
if (columnId) return columnId;
|
|
5405
|
-
}
|
|
5406
|
-
return board.columns[0]?.id;
|
|
5407
|
-
}
|
|
5408
|
-
function normalizeColumnTaskOrders(board, columnId) {
|
|
5409
|
-
board.tasks.filter((task) => task.columnId === columnId).sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt)).forEach((task, index) => {
|
|
5410
|
-
task.order = index;
|
|
5411
|
-
});
|
|
5412
|
-
}
|
|
5413
|
-
function syncTaskColumnForStatus(board, task, previousColumnId) {
|
|
5414
|
-
const nextColumnId = columnIdForKanbanStatus(board, task.status);
|
|
5415
|
-
if (!nextColumnId || nextColumnId === task.columnId) return;
|
|
5416
|
-
task.columnId = nextColumnId;
|
|
5417
|
-
normalizeColumnTaskOrders(board, previousColumnId);
|
|
5418
|
-
placeTaskInColumn(board, task, nextColumnId, void 0);
|
|
5419
|
-
}
|
|
5420
|
-
function placeTaskInColumn(board, task, columnId, targetOrder) {
|
|
5421
|
-
const tasks = board.tasks.filter((candidate) => candidate.columnId === columnId && candidate.id !== task.id).sort((a, b) => a.order - b.order || a.createdAt.localeCompare(b.createdAt));
|
|
5422
|
-
const index = clampOrder(targetOrder, tasks.length);
|
|
5423
|
-
tasks.splice(index, 0, task);
|
|
5424
|
-
tasks.forEach((candidate, order) => {
|
|
5425
|
-
candidate.order = order;
|
|
5426
|
-
});
|
|
5427
|
-
}
|
|
5428
|
-
function clampOrder(order, max) {
|
|
5429
|
-
if (!Number.isFinite(order)) return max;
|
|
5430
|
-
return Math.max(0, Math.min(Math.trunc(order), max));
|
|
5431
|
-
}
|
|
5432
|
-
function nowIso() {
|
|
5433
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5434
|
-
}
|
|
5435
|
-
|
|
5436
|
-
// src/coordination/dispatcher.ts
|
|
5437
|
-
var DEFAULT_DISPATCH_ROLE = "executor";
|
|
5438
|
-
var FALLBACK_DEFINITION = {
|
|
5439
|
-
config: { role: "unknown", name: "Unknown Agent" },
|
|
5440
|
-
budget: {},
|
|
5441
|
-
capability: { phase: "meta", summary: "", keywords: [] }
|
|
5442
|
-
};
|
|
5443
|
-
function normalize(text) {
|
|
5444
|
-
return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
|
|
5445
|
-
}
|
|
5446
|
-
function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
5447
|
-
const haySet = new Set(normalize(task).split(/\s+/).filter(Boolean));
|
|
5448
|
-
const out = [];
|
|
5449
|
-
for (const def of Object.values(catalog)) {
|
|
5450
|
-
if (!def?.config?.role) continue;
|
|
5451
|
-
let score = 0;
|
|
5452
|
-
const matched = [];
|
|
5453
|
-
for (const kw of def.capability.keywords) {
|
|
5454
|
-
const needleWords = normalize(kw).split(/\s+/).filter(Boolean);
|
|
5455
|
-
const allPresent = needleWords.every((w) => haySet.has(w));
|
|
5456
|
-
if (allPresent) {
|
|
5457
|
-
score += needleWords.length;
|
|
5458
|
-
matched.push(kw);
|
|
5459
|
-
}
|
|
5460
|
-
}
|
|
5461
|
-
if (score > 0) {
|
|
5462
|
-
out.push({ role: def.config.role, name: def.config.name, score, matched });
|
|
5463
|
-
}
|
|
5464
|
-
}
|
|
5465
|
-
out.sort((a, b) => b.score - a.score);
|
|
5466
|
-
return out;
|
|
5467
|
-
}
|
|
5468
|
-
function heuristicConfidence(candidates) {
|
|
5469
|
-
if (candidates.length === 0) return 0;
|
|
5470
|
-
const top = candidates[0]?.score ?? 0;
|
|
5471
|
-
const second = candidates[1]?.score ?? 0;
|
|
5472
|
-
const strength = Math.min(1, top / 3);
|
|
5473
|
-
const margin = (top - second + 1) / (top + 1);
|
|
5474
|
-
return Math.min(1, strength * margin);
|
|
5475
|
-
}
|
|
5476
|
-
async function dispatchAgent(task, opts = {}) {
|
|
5477
|
-
const catalog = opts.catalog ?? AGENT_CATALOG;
|
|
5478
|
-
const threshold = opts.confidenceThreshold ?? 0.4;
|
|
5479
|
-
const maxCandidates = opts.maxCandidates ?? 6;
|
|
5480
|
-
const candidates = scoreAgents(task, catalog);
|
|
5481
|
-
const confidence = heuristicConfidence(candidates);
|
|
5482
|
-
const top = candidates[0];
|
|
5483
|
-
if (top && confidence >= threshold) {
|
|
5484
|
-
return {
|
|
5485
|
-
role: top.role,
|
|
5486
|
-
definition: catalog[top.role] ?? FALLBACK_DEFINITION,
|
|
5487
|
-
confidence,
|
|
5488
|
-
method: "heuristic",
|
|
5489
|
-
reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
|
|
5490
|
-
alternatives: candidates.slice(1, maxCandidates)
|
|
5491
|
-
};
|
|
5492
|
-
}
|
|
5493
|
-
if (opts.classifier) {
|
|
5494
|
-
const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role] ?? FALLBACK_DEFINITION) : ALL_AGENT_DEFINITIONS).map((d) => ({
|
|
5495
|
-
role: d.config.role,
|
|
5496
|
-
name: d.config.name,
|
|
5497
|
-
summary: d.capability.summary
|
|
5498
|
-
}));
|
|
5422
|
+
if (opts.classifier) {
|
|
5423
|
+
const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role] ?? FALLBACK_DEFINITION) : ALL_AGENT_DEFINITIONS).map((d) => ({
|
|
5424
|
+
role: d.config.role,
|
|
5425
|
+
name: d.config.name,
|
|
5426
|
+
summary: d.capability.summary
|
|
5427
|
+
}));
|
|
5499
5428
|
try {
|
|
5500
5429
|
const choice = await opts.classifier(task, pool);
|
|
5501
5430
|
if (choice && catalog[choice.role]) {
|
|
@@ -5556,7 +5485,7 @@ function makeLLMClassifier(complete) {
|
|
|
5556
5485
|
}
|
|
5557
5486
|
|
|
5558
5487
|
// src/coordination/director-tools.ts
|
|
5559
|
-
function
|
|
5488
|
+
function nowIso() {
|
|
5560
5489
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5561
5490
|
}
|
|
5562
5491
|
function makeSpawnTool(director, roster) {
|
|
@@ -5679,6 +5608,9 @@ function makeSpawnTool(director, roster) {
|
|
|
5679
5608
|
if (err instanceof FleetCostCapError) {
|
|
5680
5609
|
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
5681
5610
|
}
|
|
5611
|
+
if (err instanceof FleetTokenCapError) {
|
|
5612
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
5613
|
+
}
|
|
5682
5614
|
return { error: toErrorMessage(err) };
|
|
5683
5615
|
}
|
|
5684
5616
|
}
|
|
@@ -5860,11 +5792,11 @@ function normalizeQualityGateInput(input) {
|
|
|
5860
5792
|
const raw = input ?? {};
|
|
5861
5793
|
return {
|
|
5862
5794
|
task: typeof raw.task === "string" ? raw.task : void 0,
|
|
5863
|
-
implementerTaskIds:
|
|
5795
|
+
implementerTaskIds: stringArray2(raw.implementerTaskIds),
|
|
5864
5796
|
repairSubagentId: typeof raw.repairSubagentId === "string" && raw.repairSubagentId.trim() ? raw.repairSubagentId.trim() : void 0,
|
|
5865
5797
|
maxRepairAttempts: typeof raw.maxRepairAttempts === "number" ? raw.maxRepairAttempts : void 0,
|
|
5866
|
-
targets:
|
|
5867
|
-
commands:
|
|
5798
|
+
targets: stringArray2(raw.targets),
|
|
5799
|
+
commands: stringArray2(raw.commands),
|
|
5868
5800
|
expected: typeof raw.expected === "string" ? raw.expected : void 0,
|
|
5869
5801
|
evidence: typeof raw.evidence === "string" ? raw.evidence : void 0,
|
|
5870
5802
|
reviewer: typeof raw.reviewer === "boolean" ? raw.reviewer : void 0,
|
|
@@ -5874,7 +5806,7 @@ function normalizeQualityGateInput(input) {
|
|
|
5874
5806
|
verifierWorktree: normalizeWorktreeOverride(raw.verifierWorktree)
|
|
5875
5807
|
};
|
|
5876
5808
|
}
|
|
5877
|
-
function
|
|
5809
|
+
function stringArray2(value) {
|
|
5878
5810
|
if (!Array.isArray(value)) return void 0;
|
|
5879
5811
|
const strings = value.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
5880
5812
|
return strings.length > 0 ? strings : void 0;
|
|
@@ -6181,7 +6113,7 @@ function makeKanbanQueueTool(director, roster) {
|
|
|
6181
6113
|
if (!projectRoot) return { error: "kanban_queue requires ctx.projectRoot." };
|
|
6182
6114
|
const maxTasks = Math.max(1, Math.min(20, Math.floor(i.maxTasks ?? 1)));
|
|
6183
6115
|
const leaseTtlMs = Math.max(1e3, Math.floor(i.leaseTtlMs ?? 5 * 60 * 1e3));
|
|
6184
|
-
const claimedAt =
|
|
6116
|
+
const claimedAt = nowIso();
|
|
6185
6117
|
const leaseSeeding = {
|
|
6186
6118
|
leaseId: randomUUID(),
|
|
6187
6119
|
claimedAt,
|
|
@@ -6339,9 +6271,9 @@ function normalizeKanbanQueueInput(input) {
|
|
|
6339
6271
|
role: typeof raw.role === "string" ? raw.role : void 0,
|
|
6340
6272
|
provider: typeof raw.provider === "string" ? raw.provider : void 0,
|
|
6341
6273
|
model: typeof raw.model === "string" ? raw.model : void 0,
|
|
6342
|
-
fallbackModels:
|
|
6343
|
-
tools:
|
|
6344
|
-
allowedCapabilities:
|
|
6274
|
+
fallbackModels: stringArray2(raw.fallbackModels),
|
|
6275
|
+
tools: stringArray2(raw.tools),
|
|
6276
|
+
allowedCapabilities: stringArray2(raw.allowedCapabilities),
|
|
6345
6277
|
worktree: normalizeWorktreeOverride(raw.worktree),
|
|
6346
6278
|
heartbeatIntervalMs: typeof raw.heartbeatIntervalMs === "number" ? raw.heartbeatIntervalMs : void 0,
|
|
6347
6279
|
leaseTtlMs: typeof raw.leaseTtlMs === "number" ? raw.leaseTtlMs : void 0
|
|
@@ -6819,10 +6751,10 @@ function makeCollabDebugTool(director) {
|
|
|
6819
6751
|
function makeFleetEmitTool(director) {
|
|
6820
6752
|
return {
|
|
6821
6753
|
name: "fleet_emit",
|
|
6822
|
-
description: "Emit a structured event on the FleetBus.
|
|
6754
|
+
description: "Emit a structured event on the FleetBus. Known collaboration events are schema-validated and role-bound; custom event types remain extensible. Use it to stream findings, progress updates, or final results to other agents in real time.",
|
|
6823
6755
|
permission: "auto",
|
|
6824
6756
|
mutating: false,
|
|
6825
|
-
capabilities: [ToolCapabilities.
|
|
6757
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_EMIT],
|
|
6826
6758
|
inputSchema: {
|
|
6827
6759
|
type: "object",
|
|
6828
6760
|
properties: {
|
|
@@ -6837,10 +6769,16 @@ function makeFleetEmitTool(director) {
|
|
|
6837
6769
|
},
|
|
6838
6770
|
required: ["type"]
|
|
6839
6771
|
},
|
|
6840
|
-
async execute(input) {
|
|
6772
|
+
async execute(input, ctx) {
|
|
6841
6773
|
const i = input;
|
|
6774
|
+
const role = ctx.meta["agentRole"];
|
|
6775
|
+
const validationError = validateFleetEventEmission(i.type, i.payload ?? {}, role);
|
|
6776
|
+
if (validationError) return { ok: false, error: validationError };
|
|
6777
|
+
const callerId = ctx.agentId && ctx.agentId !== "unknown" ? ctx.agentId : director.id;
|
|
6778
|
+
const taskId = ctx.meta["subagentTaskId"];
|
|
6842
6779
|
director.fleet.emit({
|
|
6843
|
-
subagentId:
|
|
6780
|
+
subagentId: callerId,
|
|
6781
|
+
taskId,
|
|
6844
6782
|
ts: Date.now(),
|
|
6845
6783
|
type: i.type,
|
|
6846
6784
|
payload: i.payload ?? {}
|
|
@@ -7124,15 +7062,39 @@ function hashStr(s) {
|
|
|
7124
7062
|
}
|
|
7125
7063
|
|
|
7126
7064
|
// src/types/provider.ts
|
|
7065
|
+
var CONTEXT_OVERFLOW_RE = /context.length|context.window|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|context_length_exceeded/i;
|
|
7066
|
+
var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
|
|
7067
|
+
function classifyProviderError(status, body, message) {
|
|
7068
|
+
const type = body?.type;
|
|
7069
|
+
if (status === 0) return "network";
|
|
7070
|
+
if (status === 408) return "timeout";
|
|
7071
|
+
if (status === 599) return "stream_hang";
|
|
7072
|
+
if (type === "rate_limit_error" || status === 429) return "rate_limit";
|
|
7073
|
+
if (type === "overloaded_error" || status === 529) return "overloaded";
|
|
7074
|
+
if (status >= 500) return "server";
|
|
7075
|
+
if (type === "authentication_error" || type === "permission_error" || status === 401 || status === 403) {
|
|
7076
|
+
return "auth";
|
|
7077
|
+
}
|
|
7078
|
+
const text = [message, body?.message, type, body?.raw].filter(Boolean).join("\n");
|
|
7079
|
+
if (type === "content_filter" || CONTENT_FILTER_RE.test(text)) return "content_filter";
|
|
7080
|
+
if (status === 413 || status >= 400 && CONTEXT_OVERFLOW_RE.test(text)) {
|
|
7081
|
+
return "context_overflow";
|
|
7082
|
+
}
|
|
7083
|
+
if (status >= 400) return "invalid_request";
|
|
7084
|
+
return "unknown";
|
|
7085
|
+
}
|
|
7127
7086
|
var ProviderError = class extends WrongStackError {
|
|
7128
7087
|
status;
|
|
7129
7088
|
retryable;
|
|
7130
7089
|
providerId;
|
|
7090
|
+
/** Canonical failure classification — see {@link ProviderErrorKind}. */
|
|
7091
|
+
kind;
|
|
7131
7092
|
body;
|
|
7132
7093
|
constructor(message, status, retryable, providerId, opts = {}) {
|
|
7094
|
+
const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
|
|
7133
7095
|
super({
|
|
7134
7096
|
message,
|
|
7135
|
-
code:
|
|
7097
|
+
code: kindToCode(kind),
|
|
7136
7098
|
subsystem: "provider",
|
|
7137
7099
|
severity: status >= 500 ? "error" : "warning",
|
|
7138
7100
|
recoverable: retryable,
|
|
@@ -7143,6 +7105,7 @@ var ProviderError = class extends WrongStackError {
|
|
|
7143
7105
|
this.status = status;
|
|
7144
7106
|
this.retryable = retryable;
|
|
7145
7107
|
this.providerId = providerId;
|
|
7108
|
+
this.kind = kind;
|
|
7146
7109
|
this.body = opts.body;
|
|
7147
7110
|
}
|
|
7148
7111
|
/**
|
|
@@ -7170,26 +7133,34 @@ var ProviderError = class extends WrongStackError {
|
|
|
7170
7133
|
};
|
|
7171
7134
|
function describeStatus(status, type) {
|
|
7172
7135
|
if (status === 0) return "network error";
|
|
7136
|
+
if (status === 599) return `stream hang (${status})`;
|
|
7173
7137
|
if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
|
|
7174
7138
|
if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
|
|
7175
7139
|
if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
|
|
7176
7140
|
if (type === "permission_error" || status === 403) return `forbidden (${status})`;
|
|
7177
7141
|
if (type === "not_found_error" || status === 404) return `not found (${status})`;
|
|
7142
|
+
if (type === "content_filter") return `content filtered (${status})`;
|
|
7178
7143
|
if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
|
|
7179
7144
|
if (status === 408) return `timeout (${status})`;
|
|
7180
7145
|
if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
|
|
7181
7146
|
if (type) return `${type} (${status})`;
|
|
7182
7147
|
return `HTTP ${status}`;
|
|
7183
7148
|
}
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7149
|
+
var KIND_TO_CODE = {
|
|
7150
|
+
network: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
7151
|
+
timeout: ERROR_CODES.PROVIDER_NETWORK_ERROR,
|
|
7152
|
+
rate_limit: ERROR_CODES.PROVIDER_RATE_LIMITED,
|
|
7153
|
+
auth: ERROR_CODES.PROVIDER_AUTH_FAILED,
|
|
7154
|
+
overloaded: ERROR_CODES.PROVIDER_OVERLOADED,
|
|
7155
|
+
context_overflow: ERROR_CODES.PROVIDER_CONTEXT_OVERFLOW,
|
|
7156
|
+
server: ERROR_CODES.PROVIDER_SERVER_ERROR,
|
|
7157
|
+
stream_hang: ERROR_CODES.PROVIDER_SERVER_ERROR,
|
|
7158
|
+
content_filter: ERROR_CODES.PROVIDER_INVALID_REQUEST,
|
|
7159
|
+
invalid_request: ERROR_CODES.PROVIDER_INVALID_REQUEST,
|
|
7160
|
+
unknown: ERROR_CODES.PROVIDER_INVALID_REQUEST
|
|
7161
|
+
};
|
|
7162
|
+
function kindToCode(kind) {
|
|
7163
|
+
return KIND_TO_CODE[kind];
|
|
7193
7164
|
}
|
|
7194
7165
|
|
|
7195
7166
|
// src/core/fallback-model.ts
|
|
@@ -7438,20 +7409,31 @@ function classifySubagentError(err, hints = {}) {
|
|
|
7438
7409
|
return { kind: "unknown", message: baseMessage, retryable: false, cause };
|
|
7439
7410
|
}
|
|
7440
7411
|
function providerErrorToSubagentError(err, message, cause) {
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7412
|
+
switch (err.kind) {
|
|
7413
|
+
case "rate_limit":
|
|
7414
|
+
return {
|
|
7415
|
+
kind: "provider_rate_limit",
|
|
7416
|
+
message,
|
|
7417
|
+
retryable: true,
|
|
7418
|
+
backoffMs: err.body?.retryAfterMs ?? 5e3,
|
|
7419
|
+
cause
|
|
7420
|
+
};
|
|
7421
|
+
case "auth":
|
|
7422
|
+
return { kind: "provider_auth", message, retryable: false, cause };
|
|
7423
|
+
case "timeout":
|
|
7424
|
+
case "network":
|
|
7425
|
+
return { kind: "provider_timeout", message, retryable: true, cause };
|
|
7426
|
+
case "overloaded":
|
|
7427
|
+
case "server":
|
|
7428
|
+
case "stream_hang":
|
|
7429
|
+
return { kind: "provider_5xx", message, retryable: true, backoffMs: 3e3, cause };
|
|
7430
|
+
case "context_overflow":
|
|
7431
|
+
return { kind: "context_overflow", message, retryable: false, cause };
|
|
7432
|
+
case "invalid_request":
|
|
7433
|
+
case "content_filter":
|
|
7434
|
+
case "unknown":
|
|
7435
|
+
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
7453
7436
|
}
|
|
7454
|
-
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
7455
7437
|
}
|
|
7456
7438
|
|
|
7457
7439
|
// src/coordination/subagent-nicknames.ts
|
|
@@ -7836,7 +7818,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7836
7818
|
taskIds.map((id) => {
|
|
7837
7819
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
7838
7820
|
if (cached) return cached;
|
|
7839
|
-
return new Promise((
|
|
7821
|
+
return new Promise((resolve9, reject) => {
|
|
7840
7822
|
const timeout = setTimeout(() => {
|
|
7841
7823
|
this.off("task.completed", handler);
|
|
7842
7824
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -7845,7 +7827,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7845
7827
|
if (result.taskId === id) {
|
|
7846
7828
|
clearTimeout(timeout);
|
|
7847
7829
|
this.off("task.completed", handler);
|
|
7848
|
-
|
|
7830
|
+
resolve9(result);
|
|
7849
7831
|
}
|
|
7850
7832
|
};
|
|
7851
7833
|
this.on("task.completed", handler);
|
|
@@ -7870,13 +7852,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7870
7852
|
const done = new Set(completed.map((r) => r.taskId));
|
|
7871
7853
|
return { completed, pending: taskIds.filter((id) => !done.has(id)) };
|
|
7872
7854
|
}
|
|
7873
|
-
return new Promise((
|
|
7855
|
+
return new Promise((resolve9) => {
|
|
7874
7856
|
let timer;
|
|
7875
7857
|
const handler = ({ result }) => {
|
|
7876
7858
|
if (!ids.has(result.taskId)) return;
|
|
7877
7859
|
if (timer) clearTimeout(timer);
|
|
7878
7860
|
this.off("task.completed", handler);
|
|
7879
|
-
|
|
7861
|
+
resolve9({
|
|
7880
7862
|
completed: [result],
|
|
7881
7863
|
pending: taskIds.filter((id) => id !== result.taskId)
|
|
7882
7864
|
});
|
|
@@ -7884,7 +7866,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
7884
7866
|
if (opts?.timeoutMs !== void 0) {
|
|
7885
7867
|
timer = setTimeout(() => {
|
|
7886
7868
|
this.off("task.completed", handler);
|
|
7887
|
-
|
|
7869
|
+
resolve9({ completed: [], pending: [...taskIds], timedOut: true });
|
|
7888
7870
|
}, opts.timeoutMs);
|
|
7889
7871
|
}
|
|
7890
7872
|
this.on("task.completed", handler);
|
|
@@ -8064,12 +8046,22 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8064
8046
|
}
|
|
8065
8047
|
this.inFlight++;
|
|
8066
8048
|
const startTime = Date.now();
|
|
8049
|
+
let latestPartial;
|
|
8067
8050
|
const runCtx = {
|
|
8068
8051
|
subagentId,
|
|
8069
8052
|
config: subagent.config,
|
|
8070
8053
|
budget,
|
|
8071
8054
|
signal: subagent.abortController.signal,
|
|
8072
|
-
bridge: subagent.context.parentBridge || null
|
|
8055
|
+
bridge: subagent.context.parentBridge || null,
|
|
8056
|
+
reportProgress: (partial) => {
|
|
8057
|
+
const text = partial.text.trim();
|
|
8058
|
+
if (!text) return;
|
|
8059
|
+
latestPartial = {
|
|
8060
|
+
...partial,
|
|
8061
|
+
// A partial is context recovery, not a transcript replacement.
|
|
8062
|
+
text: text.slice(-4e3)
|
|
8063
|
+
};
|
|
8064
|
+
}
|
|
8073
8065
|
};
|
|
8074
8066
|
let result;
|
|
8075
8067
|
budget.start();
|
|
@@ -8086,6 +8078,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8086
8078
|
taskId: task.id,
|
|
8087
8079
|
status: "success",
|
|
8088
8080
|
result: outcome.result,
|
|
8081
|
+
...outcome.report ? { report: outcome.report } : {},
|
|
8089
8082
|
iterations: outcome.iterations,
|
|
8090
8083
|
toolCalls: outcome.toolCalls,
|
|
8091
8084
|
durationMs: Date.now() - startTime
|
|
@@ -8100,6 +8093,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8100
8093
|
error: classifySubagentError(err, {
|
|
8101
8094
|
parentAborted: subagent.abortController.signal.aborted
|
|
8102
8095
|
}),
|
|
8096
|
+
...latestPartial ? { partial: latestPartial } : {},
|
|
8103
8097
|
iterations: usage.iterations,
|
|
8104
8098
|
toolCalls: usage.toolCalls,
|
|
8105
8099
|
durationMs: Date.now() - startTime
|
|
@@ -8154,12 +8148,12 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8154
8148
|
}
|
|
8155
8149
|
return new Promise((resolveDecision) => {
|
|
8156
8150
|
let settled = false;
|
|
8157
|
-
const
|
|
8151
|
+
const resolve9 = (d) => {
|
|
8158
8152
|
if (settled) return;
|
|
8159
8153
|
settled = true;
|
|
8160
8154
|
resolveDecision(d);
|
|
8161
8155
|
};
|
|
8162
|
-
const fallback = setTimeout(() =>
|
|
8156
|
+
const fallback = setTimeout(() => resolve9("stop"), DECISION_TIMEOUT_MS);
|
|
8163
8157
|
const sessionId = this.currentSessionId();
|
|
8164
8158
|
budget._events?.emit("budget.threshold_reached", {
|
|
8165
8159
|
...sessionId ? { sessionId } : {},
|
|
@@ -8177,11 +8171,11 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8177
8171
|
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
8178
8172
|
extend: (extra) => {
|
|
8179
8173
|
clearTimeout(fallback);
|
|
8180
|
-
queueMicrotask(() =>
|
|
8174
|
+
queueMicrotask(() => resolve9({ extend: extra }));
|
|
8181
8175
|
},
|
|
8182
8176
|
deny: () => {
|
|
8183
8177
|
clearTimeout(fallback);
|
|
8184
|
-
|
|
8178
|
+
resolve9("stop");
|
|
8185
8179
|
}
|
|
8186
8180
|
});
|
|
8187
8181
|
});
|
|
@@ -8345,6 +8339,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
8345
8339
|
taskId: result.taskId,
|
|
8346
8340
|
status: result.status,
|
|
8347
8341
|
result: result.result,
|
|
8342
|
+
report: result.report,
|
|
8343
|
+
partial: result.partial,
|
|
8348
8344
|
iterations: result.iterations,
|
|
8349
8345
|
toolCalls: result.toolCalls,
|
|
8350
8346
|
durationMs: result.durationMs
|
|
@@ -8729,6 +8725,8 @@ var Director = class _Director {
|
|
|
8729
8725
|
manifestDebounceMs;
|
|
8730
8726
|
/** Fleet-wide cost cap (entire fleet total, distinct from SubagentBudget limits). Infinity means no cap. */
|
|
8731
8727
|
maxFleetCostUsd;
|
|
8728
|
+
/** Fleet-wide input+output token cap. Infinity means no cap. */
|
|
8729
|
+
maxFleetTokens;
|
|
8732
8730
|
/** Max auto-extensions per subagent per budget kind before denying. */
|
|
8733
8731
|
maxBudgetExtensions;
|
|
8734
8732
|
/** Sessions root for direct subagent JSONL reads (fleet tool, action: session). */
|
|
@@ -8807,13 +8805,14 @@ var Director = class _Director {
|
|
|
8807
8805
|
this.taskResultNotifier = opts.taskResultNotifier;
|
|
8808
8806
|
this.sharedScratchpadPath = opts.sharedScratchpadPath ?? null;
|
|
8809
8807
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
8810
|
-
this.maxSpawnDepth = opts.maxSpawnDepth ??
|
|
8811
|
-
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
8808
|
+
this.maxSpawnDepth = opts.fleetManager?.maxSpawnDepth ?? resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
8809
|
+
this.spawnDepth = opts.fleetManager?.spawnDepth ?? opts.spawnDepth ?? 0;
|
|
8812
8810
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
8813
8811
|
this.sessionIdSource = opts.sessionId ?? (() => opts.sessionWriter?.id);
|
|
8814
8812
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
8815
8813
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
8816
8814
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
8815
|
+
this.maxFleetTokens = opts.directorBudget?.maxTokens ?? Number.POSITIVE_INFINITY;
|
|
8817
8816
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
8818
8817
|
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
8819
8818
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
@@ -8834,7 +8833,7 @@ var Director = class _Director {
|
|
|
8834
8833
|
this.fleetManager = opts.fleetManager;
|
|
8835
8834
|
this.logger = opts.logger;
|
|
8836
8835
|
if (this.sharedScratchpadPath) {
|
|
8837
|
-
void
|
|
8836
|
+
void fsp9.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
8838
8837
|
}
|
|
8839
8838
|
this.transport = new InMemoryBridgeTransport();
|
|
8840
8839
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -8907,6 +8906,8 @@ var Director = class _Director {
|
|
|
8907
8906
|
subagentName: this.manifestEntries.get(r.subagentId)?.name,
|
|
8908
8907
|
resultText,
|
|
8909
8908
|
errorText: r.error ? `${r.error.kind}: ${r.error.message}` : void 0,
|
|
8909
|
+
partialText: r.partial?.text,
|
|
8910
|
+
report: r.report,
|
|
8910
8911
|
iterations: r.iterations,
|
|
8911
8912
|
toolCalls: r.toolCalls,
|
|
8912
8913
|
durationMs: r.durationMs
|
|
@@ -9283,6 +9284,8 @@ var Director = class _Director {
|
|
|
9283
9284
|
throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
9284
9285
|
if (rejection.kind === "max_cost_usd")
|
|
9285
9286
|
throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
9287
|
+
if (rejection.kind === "max_tokens")
|
|
9288
|
+
throw new FleetTokenCapError(rejection.limit, rejection.observed);
|
|
9286
9289
|
if (rejection.kind === "max_context_load")
|
|
9287
9290
|
throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
9288
9291
|
}
|
|
@@ -9299,6 +9302,13 @@ var Director = class _Director {
|
|
|
9299
9302
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
9300
9303
|
}
|
|
9301
9304
|
}
|
|
9305
|
+
if (this.maxFleetTokens < Number.POSITIVE_INFINITY) {
|
|
9306
|
+
const total = this.usage.snapshot().total;
|
|
9307
|
+
const usedTokens = (total?.input ?? 0) + (total?.output ?? 0);
|
|
9308
|
+
if (usedTokens >= this.maxFleetTokens) {
|
|
9309
|
+
throw new FleetTokenCapError(this.maxFleetTokens, usedTokens);
|
|
9310
|
+
}
|
|
9311
|
+
}
|
|
9302
9312
|
if (this.maxLeaderContextLoad < 1) {
|
|
9303
9313
|
const maxContext = this.resolveMaxContext();
|
|
9304
9314
|
const threshold = maxContext * this.maxLeaderContextLoad;
|
|
@@ -9319,6 +9329,34 @@ var Director = class _Director {
|
|
|
9319
9329
|
this._usedNicknames.add(key);
|
|
9320
9330
|
}
|
|
9321
9331
|
}
|
|
9332
|
+
const budget = this.fleetManager ? this.fleetManager.budgetSnapshot() : (() => {
|
|
9333
|
+
const total = this.usage.snapshot().total;
|
|
9334
|
+
const usedTokens = (total?.input ?? 0) + (total?.output ?? 0);
|
|
9335
|
+
const usedCostUsd = total?.cost ?? 0;
|
|
9336
|
+
return {
|
|
9337
|
+
maxSpawns: this.maxSpawns,
|
|
9338
|
+
remainingSpawns: Math.max(0, this.maxSpawns - this.spawnCount - 1),
|
|
9339
|
+
maxTokens: this.maxFleetTokens,
|
|
9340
|
+
remainingTokens: Math.max(0, this.maxFleetTokens - usedTokens),
|
|
9341
|
+
maxCostUsd: this.maxFleetCostUsd,
|
|
9342
|
+
remainingCostUsd: Math.max(0, this.maxFleetCostUsd - usedCostUsd)
|
|
9343
|
+
};
|
|
9344
|
+
})();
|
|
9345
|
+
config.spawnLineage = {
|
|
9346
|
+
parentDirectorId: this.id,
|
|
9347
|
+
spawnDepth: this.spawnDepth + 1,
|
|
9348
|
+
maxSpawnDepth: this.maxSpawnDepth,
|
|
9349
|
+
fleetBudget: {
|
|
9350
|
+
...Number.isFinite(budget.maxSpawns) ? { maxSpawns: budget.maxSpawns } : {},
|
|
9351
|
+
...Number.isFinite(budget.remainingSpawns) ? {
|
|
9352
|
+
remainingSpawns: this.fleetManager ? Math.max(0, budget.remainingSpawns - 1) : budget.remainingSpawns
|
|
9353
|
+
} : {},
|
|
9354
|
+
...Number.isFinite(budget.maxTokens) ? { maxTokens: budget.maxTokens } : {},
|
|
9355
|
+
...Number.isFinite(budget.remainingTokens) ? { remainingTokens: budget.remainingTokens } : {},
|
|
9356
|
+
...Number.isFinite(budget.maxCostUsd) ? { maxCostUsd: budget.maxCostUsd } : {},
|
|
9357
|
+
...Number.isFinite(budget.remainingCostUsd) ? { remainingCostUsd: budget.remainingCostUsd } : {}
|
|
9358
|
+
}
|
|
9359
|
+
};
|
|
9322
9360
|
result = await this.coordinator.spawn(config);
|
|
9323
9361
|
if (this.fleetManager) {
|
|
9324
9362
|
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
@@ -9437,6 +9475,7 @@ var Director = class _Director {
|
|
|
9437
9475
|
toolCalls: r.toolCalls,
|
|
9438
9476
|
durationMs: r.durationMs,
|
|
9439
9477
|
result: r.result,
|
|
9478
|
+
report: r.report,
|
|
9440
9479
|
error: r.error
|
|
9441
9480
|
})),
|
|
9442
9481
|
null,
|
|
@@ -9454,6 +9493,7 @@ var Director = class _Director {
|
|
|
9454
9493
|
lines.push(`_${r.status} \u2014 ${r.iterations} iter \xB7 ${r.toolCalls} tools \xB7 ${r.durationMs}ms_`);
|
|
9455
9494
|
lines.push("");
|
|
9456
9495
|
if (r.error) lines.push(`**Error:** ${r.error}`);
|
|
9496
|
+
else if (r.report) lines.push(formatSubagentStructuredReport(r.report));
|
|
9457
9497
|
else if (typeof r.result === "string") lines.push(r.result);
|
|
9458
9498
|
else if (r.result !== void 0)
|
|
9459
9499
|
lines.push("```json\n" + JSON.stringify(r.result, null, 2) + "\n```");
|
|
@@ -9500,7 +9540,7 @@ var Director = class _Director {
|
|
|
9500
9540
|
})),
|
|
9501
9541
|
usage: this.usage.snapshot()
|
|
9502
9542
|
};
|
|
9503
|
-
await
|
|
9543
|
+
await fsp9.mkdir(path9.dirname(this.manifestPath), { recursive: true });
|
|
9504
9544
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
9505
9545
|
return this.manifestPath;
|
|
9506
9546
|
}
|
|
@@ -9654,11 +9694,11 @@ var Director = class _Director {
|
|
|
9654
9694
|
if (cached) return cached;
|
|
9655
9695
|
const existing = this.taskWaiters.get(id);
|
|
9656
9696
|
if (existing) return existing.promise;
|
|
9657
|
-
let
|
|
9697
|
+
let resolve9;
|
|
9658
9698
|
const promise = new Promise((res) => {
|
|
9659
|
-
|
|
9699
|
+
resolve9 = res;
|
|
9660
9700
|
});
|
|
9661
|
-
this.taskWaiters.set(id, { promise, resolve:
|
|
9701
|
+
this.taskWaiters.set(id, { promise, resolve: resolve9 });
|
|
9662
9702
|
return promise;
|
|
9663
9703
|
})
|
|
9664
9704
|
);
|
|
@@ -9687,10 +9727,10 @@ var Director = class _Director {
|
|
|
9687
9727
|
pending: taskIds.filter((id) => !done.has(id))
|
|
9688
9728
|
});
|
|
9689
9729
|
}
|
|
9690
|
-
return new Promise((
|
|
9730
|
+
return new Promise((resolve9) => {
|
|
9691
9731
|
const entry = {
|
|
9692
9732
|
ids: new Set(taskIds),
|
|
9693
|
-
resolve: (r) =>
|
|
9733
|
+
resolve: (r) => resolve9({
|
|
9694
9734
|
completed: [r],
|
|
9695
9735
|
pending: taskIds.filter((id) => id !== r.taskId)
|
|
9696
9736
|
})
|
|
@@ -9698,7 +9738,7 @@ var Director = class _Director {
|
|
|
9698
9738
|
if (opts?.timeoutMs !== void 0) {
|
|
9699
9739
|
entry.timer = setTimeout(() => {
|
|
9700
9740
|
this.anyWaiters.delete(entry);
|
|
9701
|
-
|
|
9741
|
+
resolve9({ completed: [], pending: [...taskIds], timedOut: true });
|
|
9702
9742
|
}, opts.timeoutMs);
|
|
9703
9743
|
}
|
|
9704
9744
|
this.anyWaiters.add(entry);
|
|
@@ -9820,10 +9860,10 @@ var Director = class _Director {
|
|
|
9820
9860
|
*/
|
|
9821
9861
|
async readSession(subagentId, tail) {
|
|
9822
9862
|
if (!this.sessionsRoot) return null;
|
|
9823
|
-
const filePath =
|
|
9863
|
+
const filePath = path9.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
9824
9864
|
let raw;
|
|
9825
9865
|
try {
|
|
9826
|
-
raw = await
|
|
9866
|
+
raw = await fsp9.readFile(filePath, "utf8");
|
|
9827
9867
|
} catch {
|
|
9828
9868
|
return null;
|
|
9829
9869
|
}
|
|
@@ -10003,9 +10043,10 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10003
10043
|
this.meta = meta;
|
|
10004
10044
|
this.events = events;
|
|
10005
10045
|
this.resumed = opts.resumed ?? false;
|
|
10006
|
-
this.manifestFile = opts.dir ?
|
|
10046
|
+
this.manifestFile = opts.dir ? path9.join(opts.dir, `${path9.basename(id)}.summary.json`) : "";
|
|
10007
10047
|
this.filePath = opts.filePath ?? "";
|
|
10008
10048
|
this.secretScrubber = opts.secretScrubber;
|
|
10049
|
+
this.checkpointCas = opts.checkpointCas;
|
|
10009
10050
|
this.onCloseCb = opts.onClose;
|
|
10010
10051
|
this.summary = {
|
|
10011
10052
|
id,
|
|
@@ -10047,6 +10088,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10047
10088
|
appendFailCount = 0;
|
|
10048
10089
|
lastAppendWarnAt = 0;
|
|
10049
10090
|
secretScrubber;
|
|
10091
|
+
checkpointCas;
|
|
10050
10092
|
onCloseCb;
|
|
10051
10093
|
/** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
|
|
10052
10094
|
traceId;
|
|
@@ -10067,9 +10109,12 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10067
10109
|
// a timer-driven flush racing an explicit flush()/close() issues two
|
|
10068
10110
|
// concurrent appendFile() calls on the shared O_APPEND handle — the kernel
|
|
10069
10111
|
// may complete them out of order (chronology breaks) or, for large
|
|
10070
|
-
// batches, interleave partial writes (torn JSONL lines). The chain
|
|
10071
|
-
//
|
|
10112
|
+
// batches, interleave partial writes (torn JSONL lines). The write chain
|
|
10113
|
+
// serializes handle I/O; flushChain additionally serializes the act of
|
|
10114
|
+
// draining/re-queueing the in-memory event buffer so a failed older batch
|
|
10115
|
+
// can never be overtaken by a newer successful one.
|
|
10072
10116
|
writeChain = Promise.resolve();
|
|
10117
|
+
flushChain = Promise.resolve();
|
|
10073
10118
|
/** Enqueue a write on the FIFO chain. Resolves/rejects with that write. */
|
|
10074
10119
|
enqueueWrite(data) {
|
|
10075
10120
|
const write = this.writeChain.then(() => this.handle.appendFile(data, "utf8"));
|
|
@@ -10090,13 +10135,25 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10090
10135
|
/**
|
|
10091
10136
|
* Scrub secrets out of conversation-turn events before they are observed
|
|
10092
10137
|
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
10093
|
-
* `user_input`
|
|
10094
|
-
* types either have no secret-bearing content
|
|
10095
|
-
* upstream (tool results).
|
|
10096
|
-
*
|
|
10138
|
+
* `user_input`, `llm_response`, and `context_snapshot` carry free-form
|
|
10139
|
+
* user/model text; other event types either have no secret-bearing content
|
|
10140
|
+
* or are already scrubbed upstream (tool results). Snapshot normalization
|
|
10141
|
+
* also strips transient token-cache fields even when no scrubber is set.
|
|
10097
10142
|
*/
|
|
10098
10143
|
scrubEvent(event) {
|
|
10099
10144
|
const s = this.secretScrubber;
|
|
10145
|
+
if (event.type === "context_snapshot") {
|
|
10146
|
+
return {
|
|
10147
|
+
...event,
|
|
10148
|
+
messages: event.messages.map((message) => {
|
|
10149
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
10150
|
+
return {
|
|
10151
|
+
...persisted,
|
|
10152
|
+
content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
|
|
10153
|
+
};
|
|
10154
|
+
})
|
|
10155
|
+
};
|
|
10156
|
+
}
|
|
10100
10157
|
if (!s) return event;
|
|
10101
10158
|
if (event.type === "user_input") {
|
|
10102
10159
|
return {
|
|
@@ -10110,10 +10167,55 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10110
10167
|
return event;
|
|
10111
10168
|
}
|
|
10112
10169
|
pendingFileSnapshots = [];
|
|
10170
|
+
/** Prompt whose tool work is currently executing. Set by writeCheckpoint. */
|
|
10171
|
+
activePromptIndex = null;
|
|
10113
10172
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
10114
10173
|
openToolUses = /* @__PURE__ */ new Set();
|
|
10174
|
+
/**
|
|
10175
|
+
* Buffer an event from a synchronous Context callback. ensureInit() starts
|
|
10176
|
+
* by pushing the lifecycle preamble synchronously, so session_start always
|
|
10177
|
+
* precedes observations even though these callbacks cannot await append().
|
|
10178
|
+
*/
|
|
10179
|
+
bufferSynchronousEvent(event) {
|
|
10180
|
+
if (this.closed) return;
|
|
10181
|
+
void this.ensureInit();
|
|
10182
|
+
this.observeForSummary(event);
|
|
10183
|
+
this.writeBuffer.push(event);
|
|
10184
|
+
if (this.writeBuffer.length >= _FileSessionWriter.FLUSH_SIZE) {
|
|
10185
|
+
if (this.flushTimer) {
|
|
10186
|
+
clearTimeout(this.flushTimer);
|
|
10187
|
+
this.flushTimer = null;
|
|
10188
|
+
}
|
|
10189
|
+
void this.flushBuffer().catch(() => {
|
|
10190
|
+
});
|
|
10191
|
+
} else {
|
|
10192
|
+
this.scheduleFlush();
|
|
10193
|
+
}
|
|
10194
|
+
}
|
|
10115
10195
|
recordFileChange(input) {
|
|
10116
|
-
this.
|
|
10196
|
+
if (this.closed) return;
|
|
10197
|
+
if (this.activePromptIndex === null) {
|
|
10198
|
+
this.pendingFileSnapshots.push(input);
|
|
10199
|
+
return;
|
|
10200
|
+
}
|
|
10201
|
+
const event = {
|
|
10202
|
+
type: "file_snapshot",
|
|
10203
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10204
|
+
promptIndex: this.activePromptIndex,
|
|
10205
|
+
files: [input]
|
|
10206
|
+
};
|
|
10207
|
+
this.bufferSynchronousEvent(event);
|
|
10208
|
+
}
|
|
10209
|
+
recordFileObservation(input) {
|
|
10210
|
+
if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs)) return;
|
|
10211
|
+
this.bufferSynchronousEvent({
|
|
10212
|
+
type: "file_observation",
|
|
10213
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10214
|
+
path: input.path,
|
|
10215
|
+
hash: input.hash.toLowerCase(),
|
|
10216
|
+
mtimeMs: input.mtimeMs,
|
|
10217
|
+
source: input.source
|
|
10218
|
+
});
|
|
10117
10219
|
}
|
|
10118
10220
|
recordSideEffect(input) {
|
|
10119
10221
|
this.append({
|
|
@@ -10131,18 +10233,13 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10131
10233
|
return Array.from(this.openToolUses);
|
|
10132
10234
|
}
|
|
10133
10235
|
async writeSessionStartLazy() {
|
|
10134
|
-
|
|
10236
|
+
this.writeBuffer.push({
|
|
10135
10237
|
type: this.resumed ? "session_resumed" : "session_start",
|
|
10136
10238
|
ts: this.startedAt,
|
|
10137
10239
|
id: this.id,
|
|
10138
10240
|
model: this.meta.model ?? "unknown",
|
|
10139
10241
|
provider: this.meta.provider ?? "unknown"
|
|
10140
|
-
})
|
|
10141
|
-
`;
|
|
10142
|
-
try {
|
|
10143
|
-
await this.enqueueWrite(record);
|
|
10144
|
-
} catch {
|
|
10145
|
-
}
|
|
10242
|
+
});
|
|
10146
10243
|
}
|
|
10147
10244
|
async append(event) {
|
|
10148
10245
|
if (this.closed) return;
|
|
@@ -10155,7 +10252,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10155
10252
|
clearTimeout(this.flushTimer);
|
|
10156
10253
|
this.flushTimer = null;
|
|
10157
10254
|
}
|
|
10158
|
-
await this.flushBuffer()
|
|
10255
|
+
await this.flushBuffer().catch(() => {
|
|
10256
|
+
});
|
|
10159
10257
|
} else {
|
|
10160
10258
|
this.scheduleFlush();
|
|
10161
10259
|
}
|
|
@@ -10173,7 +10271,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10173
10271
|
clearTimeout(this.flushTimer);
|
|
10174
10272
|
this.flushTimer = null;
|
|
10175
10273
|
}
|
|
10176
|
-
await this.flushBuffer()
|
|
10274
|
+
await this.flushBuffer().catch(() => {
|
|
10275
|
+
});
|
|
10177
10276
|
} else {
|
|
10178
10277
|
this.scheduleFlush();
|
|
10179
10278
|
}
|
|
@@ -10183,16 +10282,49 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10183
10282
|
* (user_input, llm_response) call this so they survive SIGKILL/crash
|
|
10184
10283
|
* instead of sitting in the in-memory buffer for up to 500ms.
|
|
10185
10284
|
*
|
|
10186
|
-
* Idempotent — cancels any pending timer
|
|
10187
|
-
*
|
|
10188
|
-
*
|
|
10285
|
+
* Idempotent — cancels any pending timer, writes whatever has accumulated,
|
|
10286
|
+
* then asks the OS to synchronize the file data before resolving. Even an
|
|
10287
|
+
* empty-buffer flush synchronizes any earlier timer-driven append.
|
|
10189
10288
|
*/
|
|
10190
10289
|
async flush() {
|
|
10290
|
+
if (this.closed) return;
|
|
10191
10291
|
if (this.flushTimer) {
|
|
10192
10292
|
clearTimeout(this.flushTimer);
|
|
10193
10293
|
this.flushTimer = null;
|
|
10194
10294
|
}
|
|
10195
10295
|
await this.flushBuffer();
|
|
10296
|
+
await this.writeChain;
|
|
10297
|
+
await this.handle.datasync();
|
|
10298
|
+
}
|
|
10299
|
+
/**
|
|
10300
|
+
* Last-gasp synchronous drain for hard-exit paths (process.exit after
|
|
10301
|
+
* rapid Ctrl+C). The async write chain cannot be awaited when the process
|
|
10302
|
+
* is about to die, but whatever still sits in the in-memory buffer CAN be
|
|
10303
|
+
* saved with a blocking append. Best-effort: an in-flight async write may
|
|
10304
|
+
* be cut off by the exit regardless; errors here are swallowed.
|
|
10305
|
+
*/
|
|
10306
|
+
flushSync() {
|
|
10307
|
+
if (this.writeBuffer.length === 0 || !this.filePath) return;
|
|
10308
|
+
if (this.flushTimer) {
|
|
10309
|
+
clearTimeout(this.flushTimer);
|
|
10310
|
+
this.flushTimer = null;
|
|
10311
|
+
}
|
|
10312
|
+
const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
10313
|
+
this.writeBuffer = [];
|
|
10314
|
+
let fd;
|
|
10315
|
+
try {
|
|
10316
|
+
fd = openSync(this.filePath, "a");
|
|
10317
|
+
writeSync(fd, batch, null, "utf8");
|
|
10318
|
+
fsyncSync(fd);
|
|
10319
|
+
} catch {
|
|
10320
|
+
} finally {
|
|
10321
|
+
if (fd !== void 0) {
|
|
10322
|
+
try {
|
|
10323
|
+
closeSync(fd);
|
|
10324
|
+
} catch {
|
|
10325
|
+
}
|
|
10326
|
+
}
|
|
10327
|
+
}
|
|
10196
10328
|
}
|
|
10197
10329
|
/** Schedule a deferred flush. No-op if a timer is already pending. */
|
|
10198
10330
|
scheduleFlush() {
|
|
@@ -10205,15 +10337,24 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10205
10337
|
}
|
|
10206
10338
|
/**
|
|
10207
10339
|
* Flush all buffered events to disk as a single appendFile call.
|
|
10208
|
-
*
|
|
10209
|
-
*
|
|
10210
|
-
*
|
|
10211
|
-
*
|
|
10340
|
+
* Concurrent callers join a FIFO flush chain. On failure the drained batch
|
|
10341
|
+
* is prepended to the live buffer, preserving chronology and allowing the
|
|
10342
|
+
* next timer/boundary flush to retry it. Warnings retain the existing
|
|
10343
|
+
* throttled behavior.
|
|
10212
10344
|
*/
|
|
10213
|
-
|
|
10345
|
+
flushBuffer() {
|
|
10346
|
+
const flush = this.flushChain.then(() => this.flushBufferOnce());
|
|
10347
|
+
this.flushChain = flush.then(
|
|
10348
|
+
() => void 0,
|
|
10349
|
+
() => void 0
|
|
10350
|
+
);
|
|
10351
|
+
return flush;
|
|
10352
|
+
}
|
|
10353
|
+
async flushBufferOnce() {
|
|
10214
10354
|
if (this.writeBuffer.length === 0) return;
|
|
10215
|
-
const
|
|
10216
|
-
const
|
|
10355
|
+
const events = this.writeBuffer;
|
|
10356
|
+
const eventCount = events.length;
|
|
10357
|
+
const batch = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
10217
10358
|
this.writeBuffer = [];
|
|
10218
10359
|
const t0 = Date.now();
|
|
10219
10360
|
let outcome = "success";
|
|
@@ -10223,6 +10364,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10223
10364
|
} catch (err) {
|
|
10224
10365
|
outcome = "failure";
|
|
10225
10366
|
errorMsg = toErrorMessage(err);
|
|
10367
|
+
this.writeBuffer = [...events, ...this.writeBuffer];
|
|
10226
10368
|
this.appendFailCount += eventCount;
|
|
10227
10369
|
const now = Date.now();
|
|
10228
10370
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
@@ -10236,6 +10378,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10236
10378
|
this.lastAppendWarnAt = now;
|
|
10237
10379
|
this.appendFailCount = 0;
|
|
10238
10380
|
}
|
|
10381
|
+
if (!this.closed) this.scheduleFlush();
|
|
10382
|
+
throw err;
|
|
10239
10383
|
} finally {
|
|
10240
10384
|
this.events?.emit("storage.write", {
|
|
10241
10385
|
sessionId: this.id,
|
|
@@ -10290,10 +10434,22 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10290
10434
|
}
|
|
10291
10435
|
async close() {
|
|
10292
10436
|
if (this.closePromise) return this.closePromise;
|
|
10293
|
-
this.closePromise = this.doClose()
|
|
10437
|
+
this.closePromise = this.doClose().catch((err) => {
|
|
10438
|
+
this.closed = false;
|
|
10439
|
+
this.closePromise = null;
|
|
10440
|
+
if (this.writeBuffer.length > 0) this.scheduleFlush();
|
|
10441
|
+
throw err;
|
|
10442
|
+
});
|
|
10294
10443
|
return this.closePromise;
|
|
10295
10444
|
}
|
|
10296
10445
|
async doClose() {
|
|
10446
|
+
if (this.pendingFileSnapshots.length > 0) {
|
|
10447
|
+
await this.writeFileSnapshot(
|
|
10448
|
+
this.activePromptIndex ?? 0,
|
|
10449
|
+
[...this.pendingFileSnapshots]
|
|
10450
|
+
);
|
|
10451
|
+
this.pendingFileSnapshots = [];
|
|
10452
|
+
}
|
|
10297
10453
|
this.closed = true;
|
|
10298
10454
|
if (this.flushTimer) {
|
|
10299
10455
|
clearTimeout(this.flushTimer);
|
|
@@ -10301,6 +10457,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10301
10457
|
}
|
|
10302
10458
|
await this.flushBuffer();
|
|
10303
10459
|
await this.writeChain;
|
|
10460
|
+
await this.handle.datasync();
|
|
10304
10461
|
this.summary = {
|
|
10305
10462
|
...this.summary,
|
|
10306
10463
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10365,12 +10522,29 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10365
10522
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
10366
10523
|
this.pendingFileSnapshots = [];
|
|
10367
10524
|
}
|
|
10525
|
+
let workspaceCheckpoint;
|
|
10526
|
+
try {
|
|
10527
|
+
workspaceCheckpoint = await this.checkpointCas?.capture(this.id, promptIndex);
|
|
10528
|
+
} catch (err) {
|
|
10529
|
+
console.warn(
|
|
10530
|
+
JSON.stringify({
|
|
10531
|
+
level: "warn",
|
|
10532
|
+
event: "session.workspace_checkpoint_capture_failed",
|
|
10533
|
+
sessionId: this.id,
|
|
10534
|
+
promptIndex,
|
|
10535
|
+
message: toErrorMessage(err),
|
|
10536
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10537
|
+
})
|
|
10538
|
+
);
|
|
10539
|
+
}
|
|
10368
10540
|
await this.append({
|
|
10369
10541
|
type: "checkpoint",
|
|
10370
10542
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10371
10543
|
promptIndex,
|
|
10372
|
-
promptPreview
|
|
10544
|
+
promptPreview,
|
|
10545
|
+
...workspaceCheckpoint ? { workspaceCheckpoint } : {}
|
|
10373
10546
|
});
|
|
10547
|
+
this.activePromptIndex = promptIndex;
|
|
10374
10548
|
this.events?.emit("checkpoint.written", {
|
|
10375
10549
|
sessionId: this.id,
|
|
10376
10550
|
promptIndex,
|
|
@@ -10409,7 +10583,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10409
10583
|
let removedCount = 0;
|
|
10410
10584
|
let targetCheckpointSeen = false;
|
|
10411
10585
|
try {
|
|
10412
|
-
fd = await
|
|
10586
|
+
fd = await fsp9.open(this.filePath, "r", 384);
|
|
10413
10587
|
while (true) {
|
|
10414
10588
|
const buf = Buffer.alloc(CHUNK_SIZE);
|
|
10415
10589
|
const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
|
|
@@ -10464,7 +10638,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10464
10638
|
await this.writeChain;
|
|
10465
10639
|
await this.handle.close();
|
|
10466
10640
|
const tmpPath = `${this.filePath}.rewind.tmp`;
|
|
10467
|
-
const src = await
|
|
10641
|
+
const src = await fsp9.open(this.filePath, "r", 384);
|
|
10468
10642
|
try {
|
|
10469
10643
|
const statResult = await src.stat();
|
|
10470
10644
|
const totalSize = statResult.size;
|
|
@@ -10480,7 +10654,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10480
10654
|
} else {
|
|
10481
10655
|
newlineAfterCheckpoint = totalSize;
|
|
10482
10656
|
}
|
|
10483
|
-
const writeFd = await
|
|
10657
|
+
const writeFd = await fsp9.open(tmpPath, "w", 384);
|
|
10484
10658
|
try {
|
|
10485
10659
|
let readOffset = 0;
|
|
10486
10660
|
while (readOffset < newlineAfterCheckpoint) {
|
|
@@ -10528,11 +10702,11 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10528
10702
|
await writeFd.close();
|
|
10529
10703
|
}
|
|
10530
10704
|
await src.close();
|
|
10531
|
-
await
|
|
10532
|
-
this.handle = await
|
|
10705
|
+
await fsp9.rename(tmpPath, this.filePath);
|
|
10706
|
+
this.handle = await fsp9.open(this.filePath, "a", 384);
|
|
10533
10707
|
} catch (err) {
|
|
10534
|
-
await
|
|
10535
|
-
this.handle = await
|
|
10708
|
+
await fsp9.unlink(tmpPath).catch(() => void 0);
|
|
10709
|
+
this.handle = await fsp9.open(this.filePath, "a", 384).catch(() => this.handle);
|
|
10536
10710
|
throw err;
|
|
10537
10711
|
}
|
|
10538
10712
|
await this.append({
|
|
@@ -10541,6 +10715,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10541
10715
|
toPromptIndex: targetPromptIndex,
|
|
10542
10716
|
revertedFiles: []
|
|
10543
10717
|
});
|
|
10718
|
+
this.activePromptIndex = targetPromptIndex;
|
|
10544
10719
|
this.events?.emit("session.rewound", {
|
|
10545
10720
|
sessionId: this.id,
|
|
10546
10721
|
toPromptIndex: targetPromptIndex,
|
|
@@ -10555,6 +10730,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10555
10730
|
clearTimeout(this.flushTimer);
|
|
10556
10731
|
this.flushTimer = null;
|
|
10557
10732
|
}
|
|
10733
|
+
await this.flushChain;
|
|
10558
10734
|
this.writeBuffer = [];
|
|
10559
10735
|
await this.writeChain;
|
|
10560
10736
|
const record = `${JSON.stringify({
|
|
@@ -10565,7 +10741,9 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10565
10741
|
provider: this.meta.provider ?? "unknown"
|
|
10566
10742
|
})}
|
|
10567
10743
|
`;
|
|
10568
|
-
await
|
|
10744
|
+
await fsp9.writeFile(this.filePath, record, "utf8");
|
|
10745
|
+
this.activePromptIndex = null;
|
|
10746
|
+
this.pendingFileSnapshots = [];
|
|
10569
10747
|
}
|
|
10570
10748
|
/**
|
|
10571
10749
|
* Write an in-flight marker. The agent loop should call
|
|
@@ -10592,6 +10770,13 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10592
10770
|
* cleanly X times, then died without finishing Y".
|
|
10593
10771
|
*/
|
|
10594
10772
|
async clearInFlightMarker(reason) {
|
|
10773
|
+
if (this.pendingFileSnapshots.length > 0) {
|
|
10774
|
+
await this.writeFileSnapshot(
|
|
10775
|
+
this.activePromptIndex ?? 0,
|
|
10776
|
+
[...this.pendingFileSnapshots]
|
|
10777
|
+
);
|
|
10778
|
+
this.pendingFileSnapshots = [];
|
|
10779
|
+
}
|
|
10595
10780
|
await this.append({
|
|
10596
10781
|
type: "in_flight_end",
|
|
10597
10782
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -10601,6 +10786,492 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
10601
10786
|
}
|
|
10602
10787
|
};
|
|
10603
10788
|
|
|
10789
|
+
// src/storage/storage-concurrency.ts
|
|
10790
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
10791
|
+
if (items.length === 0) return [];
|
|
10792
|
+
const limit = Math.max(1, Math.floor(concurrency));
|
|
10793
|
+
const results = new Array(items.length);
|
|
10794
|
+
let next = 0;
|
|
10795
|
+
async function worker() {
|
|
10796
|
+
while (true) {
|
|
10797
|
+
const index = next;
|
|
10798
|
+
next++;
|
|
10799
|
+
if (index >= items.length) return;
|
|
10800
|
+
results[index] = await mapper(items[index], index);
|
|
10801
|
+
}
|
|
10802
|
+
}
|
|
10803
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
|
|
10804
|
+
await Promise.all(workers);
|
|
10805
|
+
return results;
|
|
10806
|
+
}
|
|
10807
|
+
|
|
10808
|
+
// src/storage/session-checkpoint-cas.ts
|
|
10809
|
+
var HASH_RE = /^[a-f\d]{64}$/;
|
|
10810
|
+
var CAPTURE_CONCURRENCY = 8;
|
|
10811
|
+
var MAX_GIT_OUTPUT = 16 * 1024 * 1024;
|
|
10812
|
+
var MAX_BLOB_BYTES = 64 * 1024 * 1024;
|
|
10813
|
+
var MAX_CHECKPOINT_BYTES = 512 * 1024 * 1024;
|
|
10814
|
+
function sha256(content) {
|
|
10815
|
+
return createHash("sha256").update(content).digest("hex");
|
|
10816
|
+
}
|
|
10817
|
+
function isInside(root, target) {
|
|
10818
|
+
const relative4 = path9.relative(root, target);
|
|
10819
|
+
return relative4 === "" || !relative4.startsWith("..") && !path9.isAbsolute(relative4);
|
|
10820
|
+
}
|
|
10821
|
+
function normalizeRelative(input) {
|
|
10822
|
+
if (!input || path9.isAbsolute(input)) return null;
|
|
10823
|
+
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
10824
|
+
const resolved = path9.posix.normalize(normalized);
|
|
10825
|
+
if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
|
|
10826
|
+
return resolved;
|
|
10827
|
+
}
|
|
10828
|
+
function parseNulPaths(output) {
|
|
10829
|
+
return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
|
|
10830
|
+
}
|
|
10831
|
+
function isWrongStackWorktreePath(relative4) {
|
|
10832
|
+
return relative4 === ".wrongstack/worktrees" || relative4.startsWith(".wrongstack/worktrees/");
|
|
10833
|
+
}
|
|
10834
|
+
var SessionCheckpointCas = class {
|
|
10835
|
+
rootDir;
|
|
10836
|
+
projectRoot;
|
|
10837
|
+
runGit;
|
|
10838
|
+
constructor(opts) {
|
|
10839
|
+
this.rootDir = path9.resolve(opts.rootDir);
|
|
10840
|
+
this.projectRoot = path9.resolve(opts.projectRoot);
|
|
10841
|
+
this.runGit = opts.runGit ?? defaultRunGit;
|
|
10842
|
+
}
|
|
10843
|
+
async capture(_sessionId, _promptIndex) {
|
|
10844
|
+
const head = await this.runGit(["rev-parse", "--verify", "HEAD"], this.projectRoot);
|
|
10845
|
+
const baseHead = head.stdout.trim();
|
|
10846
|
+
if (head.code !== 0 || head.stdoutTruncated || !/^[a-f\d]{40,64}$/i.test(baseHead)) {
|
|
10847
|
+
return void 0;
|
|
10848
|
+
}
|
|
10849
|
+
const [tracked, untracked] = await Promise.all([
|
|
10850
|
+
this.runGit(["diff", "HEAD", "--name-only", "-z", "--"], this.projectRoot),
|
|
10851
|
+
this.runGit(["ls-files", "--others", "--exclude-standard", "-z"], this.projectRoot)
|
|
10852
|
+
]);
|
|
10853
|
+
if (tracked.code !== 0 || untracked.code !== 0 || tracked.stdoutTruncated || untracked.stdoutTruncated) {
|
|
10854
|
+
return void 0;
|
|
10855
|
+
}
|
|
10856
|
+
const relativePaths = [
|
|
10857
|
+
.../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
|
|
10858
|
+
].filter((relative4) => !isWrongStackWorktreePath(relative4)).sort();
|
|
10859
|
+
const unresolved = [];
|
|
10860
|
+
let scheduledBytes = 0;
|
|
10861
|
+
const captured = await mapWithConcurrency(
|
|
10862
|
+
relativePaths,
|
|
10863
|
+
CAPTURE_CONCURRENCY,
|
|
10864
|
+
async (relative4) => {
|
|
10865
|
+
const absolute = path9.resolve(this.projectRoot, ...relative4.split("/"));
|
|
10866
|
+
if (!isInside(this.projectRoot, absolute)) {
|
|
10867
|
+
unresolved.push({ path: relative4, reason: "path escapes project root" });
|
|
10868
|
+
return null;
|
|
10869
|
+
}
|
|
10870
|
+
try {
|
|
10871
|
+
const stat9 = await fsp9.lstat(absolute);
|
|
10872
|
+
if (stat9.isSymbolicLink()) {
|
|
10873
|
+
const linkTarget = await fsp9.readlink(absolute);
|
|
10874
|
+
const resolvedLink = path9.resolve(path9.dirname(absolute), linkTarget);
|
|
10875
|
+
if (path9.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
10876
|
+
unresolved.push({
|
|
10877
|
+
path: relative4,
|
|
10878
|
+
reason: "symlink target escapes project root"
|
|
10879
|
+
});
|
|
10880
|
+
return null;
|
|
10881
|
+
}
|
|
10882
|
+
return { path: relative4, state: "symlink", linkTarget };
|
|
10883
|
+
}
|
|
10884
|
+
if (!stat9.isFile()) {
|
|
10885
|
+
unresolved.push({ path: relative4, reason: "changed path is not a regular file" });
|
|
10886
|
+
return null;
|
|
10887
|
+
}
|
|
10888
|
+
if (stat9.size > MAX_BLOB_BYTES) {
|
|
10889
|
+
unresolved.push({
|
|
10890
|
+
path: relative4,
|
|
10891
|
+
reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
|
|
10892
|
+
});
|
|
10893
|
+
return null;
|
|
10894
|
+
}
|
|
10895
|
+
if (scheduledBytes + stat9.size > MAX_CHECKPOINT_BYTES) {
|
|
10896
|
+
unresolved.push({
|
|
10897
|
+
path: relative4,
|
|
10898
|
+
reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
|
|
10899
|
+
});
|
|
10900
|
+
return null;
|
|
10901
|
+
}
|
|
10902
|
+
scheduledBytes += stat9.size;
|
|
10903
|
+
const content = await fsp9.readFile(absolute);
|
|
10904
|
+
const blobHash = sha256(content);
|
|
10905
|
+
await this.putBlob(blobHash, content);
|
|
10906
|
+
return { path: relative4, state: "file", blobHash, mode: stat9.mode & 511 };
|
|
10907
|
+
} catch (err) {
|
|
10908
|
+
if (err.code === "ENOENT") {
|
|
10909
|
+
return { path: relative4, state: "absent" };
|
|
10910
|
+
}
|
|
10911
|
+
unresolved.push({ path: relative4, reason: toErrorMessage(err) });
|
|
10912
|
+
return null;
|
|
10913
|
+
}
|
|
10914
|
+
}
|
|
10915
|
+
);
|
|
10916
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10917
|
+
const manifest = {
|
|
10918
|
+
version: 1,
|
|
10919
|
+
baseHead: baseHead.toLowerCase(),
|
|
10920
|
+
coverage: "git-head-plus-dirty",
|
|
10921
|
+
entries: captured.filter((entry) => entry !== null),
|
|
10922
|
+
unresolved: [...unresolved].sort((a, b) => a.path.localeCompare(b.path))
|
|
10923
|
+
};
|
|
10924
|
+
const encoded = JSON.stringify(manifest);
|
|
10925
|
+
const manifestHash = sha256(encoded);
|
|
10926
|
+
await this.putManifest(manifestHash, encoded);
|
|
10927
|
+
return {
|
|
10928
|
+
manifestHash,
|
|
10929
|
+
baseHead: manifest.baseHead,
|
|
10930
|
+
entryCount: manifest.entries.length,
|
|
10931
|
+
unresolvedCount: manifest.unresolved.length,
|
|
10932
|
+
capturedAt,
|
|
10933
|
+
coverage: manifest.coverage
|
|
10934
|
+
};
|
|
10935
|
+
}
|
|
10936
|
+
async materialize(checkpoint, targetRoot) {
|
|
10937
|
+
const target = path9.resolve(targetRoot);
|
|
10938
|
+
if (target === this.projectRoot) {
|
|
10939
|
+
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
10940
|
+
}
|
|
10941
|
+
const targetStat = await fsp9.stat(target);
|
|
10942
|
+
if (!targetStat.isDirectory()) throw new Error(`Checkpoint target is not a directory: ${target}`);
|
|
10943
|
+
const manifest = await this.loadManifest(checkpoint.manifestHash);
|
|
10944
|
+
if (manifest.baseHead !== checkpoint.baseHead.toLowerCase() || manifest.coverage !== checkpoint.coverage || manifest.entries.length !== checkpoint.entryCount || manifest.unresolved.length !== checkpoint.unresolvedCount) {
|
|
10945
|
+
throw new Error("Workspace checkpoint reference does not match its CAS manifest");
|
|
10946
|
+
}
|
|
10947
|
+
if (manifest.unresolved.length > 0) {
|
|
10948
|
+
throw new Error(
|
|
10949
|
+
`Workspace checkpoint has ${manifest.unresolved.length} unresolved path(s); exact materialization refused`
|
|
10950
|
+
);
|
|
10951
|
+
}
|
|
10952
|
+
const targetHead = await this.runGit(["rev-parse", "--verify", "HEAD"], target);
|
|
10953
|
+
if (targetHead.code !== 0 || targetHead.stdoutTruncated || targetHead.stdout.trim().toLowerCase() !== manifest.baseHead) {
|
|
10954
|
+
throw new Error(
|
|
10955
|
+
`Checkpoint target HEAD must equal ${manifest.baseHead}; got ${targetHead.stdout.trim() || "unknown"}`
|
|
10956
|
+
);
|
|
10957
|
+
}
|
|
10958
|
+
const targetStatus = await this.runGit(
|
|
10959
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all", "--ignored=matching"],
|
|
10960
|
+
target
|
|
10961
|
+
);
|
|
10962
|
+
if (targetStatus.code !== 0 || targetStatus.stdoutTruncated) {
|
|
10963
|
+
throw new Error("Unable to verify that the checkpoint target is clean");
|
|
10964
|
+
}
|
|
10965
|
+
if (targetStatus.stdout.length > 0) {
|
|
10966
|
+
throw new Error("Checkpoint target must be a clean checkout before materialization");
|
|
10967
|
+
}
|
|
10968
|
+
const realTarget = await fsp9.realpath(target);
|
|
10969
|
+
const writtenFiles = [];
|
|
10970
|
+
const deletedFiles = [];
|
|
10971
|
+
const errors = [];
|
|
10972
|
+
const prepared = [];
|
|
10973
|
+
for (const entry of manifest.entries) {
|
|
10974
|
+
try {
|
|
10975
|
+
const output = await this.safeOutputPath(target, realTarget, entry.path);
|
|
10976
|
+
if (entry.state === "symlink") {
|
|
10977
|
+
if (path9.isAbsolute(entry.linkTarget)) {
|
|
10978
|
+
throw new Error("absolute symlink target refused");
|
|
10979
|
+
}
|
|
10980
|
+
const resolvedLink = path9.resolve(path9.dirname(output), entry.linkTarget);
|
|
10981
|
+
if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
|
|
10982
|
+
}
|
|
10983
|
+
prepared.push({
|
|
10984
|
+
entry,
|
|
10985
|
+
output,
|
|
10986
|
+
...entry.state === "file" ? { content: await this.readBlob(entry.blobHash) } : {}
|
|
10987
|
+
});
|
|
10988
|
+
} catch (err) {
|
|
10989
|
+
errors.push(`${entry.path}: ${toErrorMessage(err)}`);
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
10992
|
+
if (errors.length > 0) {
|
|
10993
|
+
return { targetRoot: target, writtenFiles, deletedFiles, errors };
|
|
10994
|
+
}
|
|
10995
|
+
for (const { entry, output, content } of prepared) {
|
|
10996
|
+
try {
|
|
10997
|
+
if (entry.state === "absent") {
|
|
10998
|
+
await fsp9.unlink(output).catch((err) => {
|
|
10999
|
+
if (err.code !== "ENOENT") throw err;
|
|
11000
|
+
});
|
|
11001
|
+
deletedFiles.push(entry.path);
|
|
11002
|
+
continue;
|
|
11003
|
+
}
|
|
11004
|
+
if (entry.state === "symlink") {
|
|
11005
|
+
await fsp9.unlink(output).catch((err) => {
|
|
11006
|
+
if (err.code !== "ENOENT") throw err;
|
|
11007
|
+
});
|
|
11008
|
+
await fsp9.mkdir(path9.dirname(output), { recursive: true });
|
|
11009
|
+
await fsp9.symlink(entry.linkTarget, output);
|
|
11010
|
+
writtenFiles.push(entry.path);
|
|
11011
|
+
continue;
|
|
11012
|
+
}
|
|
11013
|
+
if (!content) throw new Error("CAS blob was not prepared");
|
|
11014
|
+
await atomicWrite(output, content, { mode: entry.mode });
|
|
11015
|
+
await fsp9.chmod(output, entry.mode).catch(() => void 0);
|
|
11016
|
+
writtenFiles.push(entry.path);
|
|
11017
|
+
} catch (err) {
|
|
11018
|
+
errors.push(`${entry.path}: ${toErrorMessage(err)}`);
|
|
11019
|
+
}
|
|
11020
|
+
}
|
|
11021
|
+
return { targetRoot: target, writtenFiles, deletedFiles, errors };
|
|
11022
|
+
}
|
|
11023
|
+
objectPath(hash) {
|
|
11024
|
+
if (!HASH_RE.test(hash)) throw new Error(`Invalid CAS object hash: ${hash}`);
|
|
11025
|
+
return path9.join(this.rootDir, "objects", hash.slice(0, 2), hash.slice(2));
|
|
11026
|
+
}
|
|
11027
|
+
manifestPath(hash) {
|
|
11028
|
+
if (!HASH_RE.test(hash)) throw new Error(`Invalid checkpoint manifest hash: ${hash}`);
|
|
11029
|
+
return path9.join(this.rootDir, "manifests", `${hash}.json`);
|
|
11030
|
+
}
|
|
11031
|
+
async putBlob(hash, content) {
|
|
11032
|
+
const target = this.objectPath(hash);
|
|
11033
|
+
await fsp9.mkdir(path9.dirname(target), { recursive: true });
|
|
11034
|
+
try {
|
|
11035
|
+
const existing = await fsp9.readFile(target);
|
|
11036
|
+
if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
|
|
11037
|
+
return;
|
|
11038
|
+
} catch (err) {
|
|
11039
|
+
if (err.code !== "ENOENT") throw err;
|
|
11040
|
+
}
|
|
11041
|
+
const temp = path9.join(
|
|
11042
|
+
path9.dirname(target),
|
|
11043
|
+
`.${path9.basename(target)}.${process.pid}.${randomUUID()}.tmp`
|
|
11044
|
+
);
|
|
11045
|
+
let handle;
|
|
11046
|
+
try {
|
|
11047
|
+
handle = await fsp9.open(temp, "wx", 384);
|
|
11048
|
+
await handle.writeFile(content);
|
|
11049
|
+
await handle.sync();
|
|
11050
|
+
await handle.close();
|
|
11051
|
+
handle = void 0;
|
|
11052
|
+
try {
|
|
11053
|
+
await fsp9.link(temp, target);
|
|
11054
|
+
} catch (err) {
|
|
11055
|
+
if (err.code !== "EEXIST") throw err;
|
|
11056
|
+
const existing = await fsp9.readFile(target);
|
|
11057
|
+
if (sha256(existing) !== hash) throw new Error(`Corrupt CAS object collision: ${hash}`);
|
|
11058
|
+
}
|
|
11059
|
+
} finally {
|
|
11060
|
+
await handle?.close().catch(() => void 0);
|
|
11061
|
+
await fsp9.unlink(temp).catch(() => void 0);
|
|
11062
|
+
}
|
|
11063
|
+
}
|
|
11064
|
+
async putManifest(hash, encoded) {
|
|
11065
|
+
const target = this.manifestPath(hash);
|
|
11066
|
+
const existing = await fsp9.readFile(target).catch(() => null);
|
|
11067
|
+
if (existing) {
|
|
11068
|
+
if (sha256(existing) !== hash) throw new Error(`Corrupt checkpoint manifest collision: ${hash}`);
|
|
11069
|
+
return;
|
|
11070
|
+
}
|
|
11071
|
+
await atomicWrite(target, encoded, { mode: 384 });
|
|
11072
|
+
}
|
|
11073
|
+
async readBlob(hash) {
|
|
11074
|
+
const content = await fsp9.readFile(this.objectPath(hash));
|
|
11075
|
+
if (sha256(content) !== hash) throw new Error(`CAS blob integrity check failed: ${hash}`);
|
|
11076
|
+
return content;
|
|
11077
|
+
}
|
|
11078
|
+
async loadManifest(hash) {
|
|
11079
|
+
const raw = await fsp9.readFile(this.manifestPath(hash));
|
|
11080
|
+
if (sha256(raw) !== hash) throw new Error(`Checkpoint manifest integrity check failed: ${hash}`);
|
|
11081
|
+
const parsed = JSON.parse(raw.toString("utf8"));
|
|
11082
|
+
if (parsed.version !== 1 || typeof parsed.baseHead !== "string" || parsed.coverage !== "git-head-plus-dirty" || !Array.isArray(parsed.entries) || !Array.isArray(parsed.unresolved)) {
|
|
11083
|
+
throw new Error(`Invalid workspace checkpoint manifest: ${hash}`);
|
|
11084
|
+
}
|
|
11085
|
+
const entriesValid = parsed.entries.every((entry) => {
|
|
11086
|
+
if (!entry || typeof entry !== "object") return false;
|
|
11087
|
+
const candidate = entry;
|
|
11088
|
+
if (typeof candidate.path !== "string" || !normalizeRelative(candidate.path)) return false;
|
|
11089
|
+
if (candidate.state === "absent") return true;
|
|
11090
|
+
if (candidate.state === "symlink") return typeof candidate.linkTarget === "string";
|
|
11091
|
+
return candidate.state === "file" && typeof candidate.blobHash === "string" && HASH_RE.test(candidate.blobHash) && typeof candidate.mode === "number" && Number.isInteger(candidate.mode) && candidate.mode >= 0 && candidate.mode <= 511;
|
|
11092
|
+
});
|
|
11093
|
+
const unresolvedValid = parsed.unresolved.every(
|
|
11094
|
+
(entry) => entry !== null && typeof entry === "object" && typeof entry.path === "string" && typeof entry.reason === "string"
|
|
11095
|
+
);
|
|
11096
|
+
if (!entriesValid || !unresolvedValid) {
|
|
11097
|
+
throw new Error(`Invalid workspace checkpoint manifest entries: ${hash}`);
|
|
11098
|
+
}
|
|
11099
|
+
return parsed;
|
|
11100
|
+
}
|
|
11101
|
+
async safeOutputPath(target, realTarget, relative4) {
|
|
11102
|
+
const normalized = normalizeRelative(relative4);
|
|
11103
|
+
if (!normalized) throw new Error("invalid relative path");
|
|
11104
|
+
const output = path9.resolve(target, ...normalized.split("/"));
|
|
11105
|
+
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
11106
|
+
let probe = output;
|
|
11107
|
+
for (; ; ) {
|
|
11108
|
+
try {
|
|
11109
|
+
const real = await fsp9.realpath(probe);
|
|
11110
|
+
if (!isInside(realTarget, real)) throw new Error("path resolves through a symlink outside checkpoint target");
|
|
11111
|
+
return output;
|
|
11112
|
+
} catch (err) {
|
|
11113
|
+
if (err.code !== "ENOENT") throw err;
|
|
11114
|
+
const parent = path9.dirname(probe);
|
|
11115
|
+
if (parent === probe) throw err;
|
|
11116
|
+
probe = parent;
|
|
11117
|
+
}
|
|
11118
|
+
}
|
|
11119
|
+
}
|
|
11120
|
+
};
|
|
11121
|
+
function defaultRunGit(args, cwd) {
|
|
11122
|
+
return new Promise((resolve9) => {
|
|
11123
|
+
const stdoutChunks = [];
|
|
11124
|
+
const stderrChunks = [];
|
|
11125
|
+
let stdoutBytes = 0;
|
|
11126
|
+
let stderrBytes = 0;
|
|
11127
|
+
let stdoutTruncated = false;
|
|
11128
|
+
let stderrTruncated = false;
|
|
11129
|
+
const child = spawn("git", args, {
|
|
11130
|
+
cwd,
|
|
11131
|
+
env: buildChildEnv(),
|
|
11132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
11133
|
+
signal: AbortSignal.timeout(3e4),
|
|
11134
|
+
windowsHide: true
|
|
11135
|
+
});
|
|
11136
|
+
child.stdout?.on("data", (chunk) => {
|
|
11137
|
+
const remaining = MAX_GIT_OUTPUT - stdoutBytes;
|
|
11138
|
+
if (remaining <= 0) {
|
|
11139
|
+
stdoutTruncated = true;
|
|
11140
|
+
return;
|
|
11141
|
+
}
|
|
11142
|
+
const kept = chunk.subarray(0, remaining);
|
|
11143
|
+
stdoutChunks.push(kept);
|
|
11144
|
+
stdoutBytes += kept.length;
|
|
11145
|
+
if (kept.length < chunk.length) stdoutTruncated = true;
|
|
11146
|
+
});
|
|
11147
|
+
child.stderr?.on("data", (chunk) => {
|
|
11148
|
+
const remaining = MAX_GIT_OUTPUT - stderrBytes;
|
|
11149
|
+
if (remaining <= 0) {
|
|
11150
|
+
stderrTruncated = true;
|
|
11151
|
+
return;
|
|
11152
|
+
}
|
|
11153
|
+
const kept = chunk.subarray(0, remaining);
|
|
11154
|
+
stderrChunks.push(kept);
|
|
11155
|
+
stderrBytes += kept.length;
|
|
11156
|
+
if (kept.length < chunk.length) stderrTruncated = true;
|
|
11157
|
+
});
|
|
11158
|
+
const result = (code, extraStderr) => ({
|
|
11159
|
+
code,
|
|
11160
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
11161
|
+
stderr: `${Buffer.concat(stderrChunks).toString("utf8")}${extraStderr ?? ""}`,
|
|
11162
|
+
stdoutTruncated,
|
|
11163
|
+
stderrTruncated
|
|
11164
|
+
});
|
|
11165
|
+
child.on("error", (err) => resolve9(result(1, err.message)));
|
|
11166
|
+
child.on("close", (code) => resolve9(result(code ?? 1)));
|
|
11167
|
+
});
|
|
11168
|
+
}
|
|
11169
|
+
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
11170
|
+
var VALIDATION_CONCURRENCY = 8;
|
|
11171
|
+
var NOTICE_PATH_LIMIT = 20;
|
|
11172
|
+
function isInside2(root, target) {
|
|
11173
|
+
const relative4 = path9.relative(root, target);
|
|
11174
|
+
return relative4 === "" || !relative4.startsWith("..") && !path9.isAbsolute(relative4);
|
|
11175
|
+
}
|
|
11176
|
+
function errno(err) {
|
|
11177
|
+
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
11178
|
+
}
|
|
11179
|
+
function latestObservations(events, projectRoot) {
|
|
11180
|
+
const latest = /* @__PURE__ */ new Map();
|
|
11181
|
+
for (const event of events) {
|
|
11182
|
+
if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
|
|
11183
|
+
continue;
|
|
11184
|
+
}
|
|
11185
|
+
const normalized = path9.resolve(projectRoot, event.path);
|
|
11186
|
+
latest.set(normalized, {
|
|
11187
|
+
path: normalized,
|
|
11188
|
+
hash: event.hash.toLowerCase(),
|
|
11189
|
+
ts: event.ts
|
|
11190
|
+
});
|
|
11191
|
+
}
|
|
11192
|
+
return [...latest.values()];
|
|
11193
|
+
}
|
|
11194
|
+
async function validateOne(observation, lexicalRoot, realRoot) {
|
|
11195
|
+
const base = {
|
|
11196
|
+
path: observation.path,
|
|
11197
|
+
observedAt: observation.ts,
|
|
11198
|
+
expectedHash: observation.hash
|
|
11199
|
+
};
|
|
11200
|
+
if (!isInside2(lexicalRoot, observation.path)) {
|
|
11201
|
+
return {
|
|
11202
|
+
...base,
|
|
11203
|
+
status: "outside_project",
|
|
11204
|
+
detail: "Recorded path is outside the active project root."
|
|
11205
|
+
};
|
|
11206
|
+
}
|
|
11207
|
+
let realFile;
|
|
11208
|
+
try {
|
|
11209
|
+
realFile = await fsp9.realpath(observation.path);
|
|
11210
|
+
} catch (err) {
|
|
11211
|
+
if (errno(err) === "ENOENT") return { ...base, status: "deleted" };
|
|
11212
|
+
return { ...base, status: "unreadable", detail: toErrorMessage(err) };
|
|
11213
|
+
}
|
|
11214
|
+
if (!isInside2(realRoot, realFile)) {
|
|
11215
|
+
return {
|
|
11216
|
+
...base,
|
|
11217
|
+
status: "outside_project",
|
|
11218
|
+
detail: "Recorded path resolves through a symlink outside the active project root."
|
|
11219
|
+
};
|
|
11220
|
+
}
|
|
11221
|
+
try {
|
|
11222
|
+
const stat9 = await fsp9.stat(realFile);
|
|
11223
|
+
if (!stat9.isFile()) {
|
|
11224
|
+
return { ...base, status: "unreadable", detail: "Path is no longer a regular file." };
|
|
11225
|
+
}
|
|
11226
|
+
if (stat9.size > MAX_REVALIDATE_BYTES) {
|
|
11227
|
+
return {
|
|
11228
|
+
...base,
|
|
11229
|
+
status: "unreadable",
|
|
11230
|
+
detail: `File now exceeds the ${MAX_REVALIDATE_BYTES}-byte validation limit.`
|
|
11231
|
+
};
|
|
11232
|
+
}
|
|
11233
|
+
const content = await fsp9.readFile(realFile, "utf8");
|
|
11234
|
+
const actualHash = createHash("sha256").update(content, "utf8").digest("hex");
|
|
11235
|
+
if (actualHash === observation.hash) return null;
|
|
11236
|
+
return { ...base, status: "modified", actualHash };
|
|
11237
|
+
} catch (err) {
|
|
11238
|
+
if (errno(err) === "ENOENT") return { ...base, status: "deleted" };
|
|
11239
|
+
return { ...base, status: "unreadable", detail: toErrorMessage(err) };
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
async function validateResumeFileObservations(events, projectRoot) {
|
|
11243
|
+
const lexicalRoot = path9.resolve(projectRoot);
|
|
11244
|
+
const realRoot = await fsp9.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
11245
|
+
const observations = latestObservations(events, lexicalRoot);
|
|
11246
|
+
const results = await mapWithConcurrency(
|
|
11247
|
+
observations,
|
|
11248
|
+
VALIDATION_CONCURRENCY,
|
|
11249
|
+
(observation) => validateOne(observation, lexicalRoot, realRoot)
|
|
11250
|
+
);
|
|
11251
|
+
return {
|
|
11252
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11253
|
+
checkedFileCount: observations.length,
|
|
11254
|
+
staleFiles: results.filter((entry) => entry !== null)
|
|
11255
|
+
};
|
|
11256
|
+
}
|
|
11257
|
+
function formatResumeValidationNotice(validation, projectRoot) {
|
|
11258
|
+
if (validation.staleFiles.length === 0) return null;
|
|
11259
|
+
const root = path9.resolve(projectRoot);
|
|
11260
|
+
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
11261
|
+
const relative4 = path9.relative(root, entry.path);
|
|
11262
|
+
const display = isInside2(root, entry.path) ? relative4 || "." : entry.path;
|
|
11263
|
+
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
11264
|
+
});
|
|
11265
|
+
const omitted = validation.staleFiles.length - shown.length;
|
|
11266
|
+
if (omitted > 0) shown.push(`- ... and ${omitted} more stale path(s)`);
|
|
11267
|
+
return [
|
|
11268
|
+
"[SESSION RESUME FILE VALIDATION]",
|
|
11269
|
+
`Journal hash validation found ${validation.staleFiles.length} previously observed file(s) whose current state cannot be trusted.`,
|
|
11270
|
+
"Prior tool results and reasoning based on these paths are stale. Re-read each relevant file before relying on it or editing it.",
|
|
11271
|
+
...shown
|
|
11272
|
+
].join("\n");
|
|
11273
|
+
}
|
|
11274
|
+
|
|
10604
11275
|
// src/storage/session-id.ts
|
|
10605
11276
|
function generateSessionId(startedAt, _model) {
|
|
10606
11277
|
const date = startedAt.slice(0, 10);
|
|
@@ -10645,30 +11316,52 @@ function extractToolCallEnds(events) {
|
|
|
10645
11316
|
return out;
|
|
10646
11317
|
}
|
|
10647
11318
|
|
|
10648
|
-
// src/storage/
|
|
10649
|
-
|
|
10650
|
-
if (
|
|
10651
|
-
const
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
11319
|
+
// src/storage/session-store.ts
|
|
11320
|
+
function isReplayableMessage(value) {
|
|
11321
|
+
if (!value || typeof value !== "object") return false;
|
|
11322
|
+
const candidate = value;
|
|
11323
|
+
return (candidate.role === "user" || candidate.role === "assistant" || candidate.role === "system") && (typeof candidate.content === "string" || Array.isArray(candidate.content));
|
|
11324
|
+
}
|
|
11325
|
+
function applyContextSnapshot(target, openToolUses, snapshot) {
|
|
11326
|
+
if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;
|
|
11327
|
+
target.length = 0;
|
|
11328
|
+
openToolUses.clear();
|
|
11329
|
+
for (const raw of snapshot) {
|
|
11330
|
+
const { _estTokens: _ignored, ...message } = raw;
|
|
11331
|
+
target.push(message);
|
|
11332
|
+
if (!Array.isArray(message.content)) continue;
|
|
11333
|
+
for (const block of message.content) {
|
|
11334
|
+
if (block.type === "tool_use") openToolUses.add(block.id);
|
|
11335
|
+
else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
|
|
10660
11336
|
}
|
|
10661
11337
|
}
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
|
|
11338
|
+
return true;
|
|
11339
|
+
}
|
|
11340
|
+
function inheritsIntoFork(event) {
|
|
11341
|
+
switch (event.type) {
|
|
11342
|
+
case "session_start":
|
|
11343
|
+
case "session_resumed":
|
|
11344
|
+
case "session_forked":
|
|
11345
|
+
case "session_end":
|
|
11346
|
+
case "in_flight_start":
|
|
11347
|
+
case "in_flight_end":
|
|
11348
|
+
case "rewound":
|
|
11349
|
+
return false;
|
|
11350
|
+
// Parent snapshots describe mutations owned by the parent journal. A
|
|
11351
|
+
// child that shares the current workspace must not inherit authority to
|
|
11352
|
+
// rewind those historical side effects.
|
|
11353
|
+
case "file_snapshot":
|
|
11354
|
+
return false;
|
|
11355
|
+
default:
|
|
11356
|
+
return true;
|
|
11357
|
+
}
|
|
10665
11358
|
}
|
|
10666
|
-
|
|
10667
|
-
// src/storage/session-store.ts
|
|
10668
11359
|
var DefaultSessionStore = class _DefaultSessionStore {
|
|
10669
11360
|
dir;
|
|
10670
11361
|
events;
|
|
10671
11362
|
secretScrubber;
|
|
11363
|
+
projectRoot;
|
|
11364
|
+
checkpointCas;
|
|
10672
11365
|
isSessionInUse;
|
|
10673
11366
|
/**
|
|
10674
11367
|
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
@@ -10687,6 +11380,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10687
11380
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
10688
11381
|
constructor(opts) {
|
|
10689
11382
|
this.dir = opts.dir;
|
|
11383
|
+
this.projectRoot = opts.projectRoot ? path9.resolve(opts.projectRoot) : void 0;
|
|
11384
|
+
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
11385
|
+
rootDir: path9.join(this.dir, "_cas"),
|
|
11386
|
+
projectRoot: this.projectRoot
|
|
11387
|
+
}) : void 0;
|
|
10690
11388
|
this.events = opts.events;
|
|
10691
11389
|
this.secretScrubber = opts.secretScrubber;
|
|
10692
11390
|
this.isSessionInUse = opts.isSessionInUse;
|
|
@@ -10738,17 +11436,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10738
11436
|
}
|
|
10739
11437
|
/** Absolute path to the session index file. */
|
|
10740
11438
|
get indexFile() {
|
|
10741
|
-
return
|
|
11439
|
+
return path9.join(this.dir, "_index.jsonl");
|
|
10742
11440
|
}
|
|
10743
11441
|
/** Join session ID to its absolute path within the store directory. */
|
|
10744
11442
|
sessionPath(id, ext) {
|
|
10745
11443
|
return sessionScopedPath(this.dir, id, ext);
|
|
10746
11444
|
}
|
|
10747
11445
|
shardManifestPath(shardKey) {
|
|
10748
|
-
return shardKey ?
|
|
11446
|
+
return shardKey ? path9.join(this.dir, shardKey, "_manifest.json") : path9.join(this.dir, "_manifest.json");
|
|
10749
11447
|
}
|
|
10750
11448
|
shardKeyForSessionId(id) {
|
|
10751
|
-
const dirName =
|
|
11449
|
+
const dirName = path9.dirname(id);
|
|
10752
11450
|
return dirName === "." ? "" : dirName;
|
|
10753
11451
|
}
|
|
10754
11452
|
invalidateShardManifestBySessionId(id) {
|
|
@@ -10760,7 +11458,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10760
11458
|
* subdirectory so sessions group naturally by day.
|
|
10761
11459
|
*/
|
|
10762
11460
|
async ensureShardDir(id) {
|
|
10763
|
-
const dirPath =
|
|
11461
|
+
const dirPath = path9.dirname(sessionScopedPath(this.dir, id, ""));
|
|
10764
11462
|
await ensureDir(dirPath);
|
|
10765
11463
|
return dirPath;
|
|
10766
11464
|
}
|
|
@@ -10772,7 +11470,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10772
11470
|
const t0 = Date.now();
|
|
10773
11471
|
let handle;
|
|
10774
11472
|
try {
|
|
10775
|
-
handle = await
|
|
11473
|
+
handle = await fsp9.open(file, "a", 384);
|
|
10776
11474
|
} catch (err) {
|
|
10777
11475
|
this.emitError(id, file, "create", toErrorMessage(err), false);
|
|
10778
11476
|
throw new Error(
|
|
@@ -10785,6 +11483,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10785
11483
|
dir: shardDir,
|
|
10786
11484
|
filePath: file,
|
|
10787
11485
|
secretScrubber: this.secretScrubber,
|
|
11486
|
+
checkpointCas: this.checkpointCas,
|
|
10788
11487
|
onClose: (s) => this.appendToIndex(s)
|
|
10789
11488
|
});
|
|
10790
11489
|
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
@@ -10800,13 +11499,99 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10800
11499
|
throw err;
|
|
10801
11500
|
}
|
|
10802
11501
|
}
|
|
11502
|
+
async fork(id, opts = {}) {
|
|
11503
|
+
const parent = await this.load(id);
|
|
11504
|
+
let boundary = parent.events.length - 1;
|
|
11505
|
+
let targetCheckpoint;
|
|
11506
|
+
if (opts.checkpointPromptIndex !== void 0) {
|
|
11507
|
+
boundary = -1;
|
|
11508
|
+
for (let i = 0; i < parent.events.length; i++) {
|
|
11509
|
+
const event = parent.events[i];
|
|
11510
|
+
if (event?.type === "checkpoint" && event.promptIndex === opts.checkpointPromptIndex) {
|
|
11511
|
+
boundary = i;
|
|
11512
|
+
targetCheckpoint = event;
|
|
11513
|
+
}
|
|
11514
|
+
}
|
|
11515
|
+
if (boundary === -1) {
|
|
11516
|
+
throw new Error(
|
|
11517
|
+
`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`
|
|
11518
|
+
);
|
|
11519
|
+
}
|
|
11520
|
+
}
|
|
11521
|
+
const parentPrefix = parent.events.slice(0, boundary + 1);
|
|
11522
|
+
const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;
|
|
11523
|
+
const checkpointHash = createHash("sha256").update(parentPrefix.map((event) => JSON.stringify(event)).join("\n") + "\n", "utf8").digest("hex");
|
|
11524
|
+
const inherited = parentPrefix.filter(inheritsIntoFork);
|
|
11525
|
+
const writer = await this.create({
|
|
11526
|
+
id: "",
|
|
11527
|
+
title: parent.metadata.title,
|
|
11528
|
+
model: parent.metadata.model,
|
|
11529
|
+
provider: parent.metadata.provider
|
|
11530
|
+
});
|
|
11531
|
+
try {
|
|
11532
|
+
await writer.append({
|
|
11533
|
+
type: "session_forked",
|
|
11534
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11535
|
+
parentSessionId: id,
|
|
11536
|
+
parentCheckpointPromptIndex: opts.checkpointPromptIndex,
|
|
11537
|
+
parentCheckpointHash: checkpointHash,
|
|
11538
|
+
workspace: "shared-current",
|
|
11539
|
+
workspaceCheckpointHash: workspaceCheckpoint?.manifestHash
|
|
11540
|
+
});
|
|
11541
|
+
const batchSize = 250;
|
|
11542
|
+
for (let offset = 0; offset < inherited.length; offset += batchSize) {
|
|
11543
|
+
await writer.appendBatch(inherited.slice(offset, offset + batchSize));
|
|
11544
|
+
}
|
|
11545
|
+
await writer.flush();
|
|
11546
|
+
await writer.close();
|
|
11547
|
+
const data = await this.load(writer.id);
|
|
11548
|
+
return {
|
|
11549
|
+
id: writer.id,
|
|
11550
|
+
data,
|
|
11551
|
+
parentSessionId: id,
|
|
11552
|
+
checkpointPromptIndex: opts.checkpointPromptIndex,
|
|
11553
|
+
checkpointHash,
|
|
11554
|
+
workspace: "shared-current",
|
|
11555
|
+
workspaceCheckpoint
|
|
11556
|
+
};
|
|
11557
|
+
} catch (err) {
|
|
11558
|
+
await writer.close().catch(() => void 0);
|
|
11559
|
+
await this.delete(writer.id).catch(() => void 0);
|
|
11560
|
+
throw err;
|
|
11561
|
+
}
|
|
11562
|
+
}
|
|
11563
|
+
async materializeWorkspaceCheckpoint(checkpoint, targetRoot) {
|
|
11564
|
+
if (!this.checkpointCas) {
|
|
11565
|
+
throw new Error("Workspace checkpoint materialization requires a projectRoot-aware session store");
|
|
11566
|
+
}
|
|
11567
|
+
return this.checkpointCas.materialize(checkpoint, targetRoot);
|
|
11568
|
+
}
|
|
10803
11569
|
async resume(id) {
|
|
10804
11570
|
const file = this.sessionPath(id, ".jsonl");
|
|
10805
11571
|
const t0 = Date.now();
|
|
10806
11572
|
const data = await this.load(id);
|
|
11573
|
+
let resumedData = data;
|
|
11574
|
+
if (this.projectRoot) {
|
|
11575
|
+
try {
|
|
11576
|
+
const resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);
|
|
11577
|
+
const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);
|
|
11578
|
+
resumedData = {
|
|
11579
|
+
...data,
|
|
11580
|
+
resumeValidation,
|
|
11581
|
+
...notice ? {
|
|
11582
|
+
messages: [
|
|
11583
|
+
...data.messages,
|
|
11584
|
+
{ role: "system", content: notice, ts: resumeValidation.checkedAt }
|
|
11585
|
+
]
|
|
11586
|
+
} : {}
|
|
11587
|
+
};
|
|
11588
|
+
} catch (err) {
|
|
11589
|
+
this.emitError(id, file, "resume_validation", toErrorMessage(err), true);
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
10807
11592
|
let handle;
|
|
10808
11593
|
try {
|
|
10809
|
-
handle = await
|
|
11594
|
+
handle = await fsp9.open(file, "a", 384);
|
|
10810
11595
|
} catch (err) {
|
|
10811
11596
|
this.emitError(id, file, "resume", toErrorMessage(err), false);
|
|
10812
11597
|
throw new Error(
|
|
@@ -10830,14 +11615,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10830
11615
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
10831
11616
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
10832
11617
|
// sessions root (where summaryFor() would never find it).
|
|
10833
|
-
dir:
|
|
11618
|
+
dir: path9.dirname(file),
|
|
10834
11619
|
filePath: file,
|
|
10835
11620
|
secretScrubber: this.secretScrubber,
|
|
11621
|
+
checkpointCas: this.checkpointCas,
|
|
10836
11622
|
onClose: (s) => this.appendToIndex(s)
|
|
10837
11623
|
}
|
|
10838
11624
|
);
|
|
10839
11625
|
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
10840
|
-
return { writer, data };
|
|
11626
|
+
return { writer, data: resumedData };
|
|
10841
11627
|
} catch (err) {
|
|
10842
11628
|
await handle.close().catch((e) => console.warn(JSON.stringify({
|
|
10843
11629
|
level: "warn",
|
|
@@ -10876,10 +11662,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10876
11662
|
let errorMsg;
|
|
10877
11663
|
let cacheHit = false;
|
|
10878
11664
|
try {
|
|
10879
|
-
const s = await
|
|
10880
|
-
const
|
|
11665
|
+
const s = await fsp9.stat(file);
|
|
11666
|
+
const stat9 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
10881
11667
|
const cached = this._loadCache.get(id);
|
|
10882
|
-
if (cached && cached.mtimeMs ===
|
|
11668
|
+
if (cached && cached.mtimeMs === stat9.mtimeMs && cached.size === stat9.size) {
|
|
10883
11669
|
cacheHit = true;
|
|
10884
11670
|
this._loadCache.delete(id);
|
|
10885
11671
|
this._loadCache.set(id, cached);
|
|
@@ -10892,6 +11678,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10892
11678
|
let sessionModel;
|
|
10893
11679
|
let sessionProvider;
|
|
10894
11680
|
let sessionPendingToolUses;
|
|
11681
|
+
let sessionForkedEvent;
|
|
10895
11682
|
const messages = mode.full ? [] : void 0;
|
|
10896
11683
|
const openToolUses = mode.full ? /* @__PURE__ */ new Set() : void 0;
|
|
10897
11684
|
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
@@ -10914,8 +11701,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10914
11701
|
sessionEndEvent = ev;
|
|
10915
11702
|
sessionPendingToolUses = ev.pendingToolUses;
|
|
10916
11703
|
}
|
|
11704
|
+
if (ev.type === "session_forked" && !sessionForkedEvent) {
|
|
11705
|
+
sessionForkedEvent = ev;
|
|
11706
|
+
}
|
|
10917
11707
|
if (mode.full && messages !== void 0 && openToolUses !== void 0) {
|
|
10918
|
-
if (ev.type === "
|
|
11708
|
+
if (ev.type === "context_snapshot") {
|
|
11709
|
+
if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
11710
|
+
this.events?.emit("session.damaged", {
|
|
11711
|
+
sessionId: id,
|
|
11712
|
+
detail: "Ignored malformed context_snapshot event"
|
|
11713
|
+
});
|
|
11714
|
+
}
|
|
11715
|
+
} else if (ev.type === "user_input") {
|
|
10919
11716
|
openToolUses.clear();
|
|
10920
11717
|
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
10921
11718
|
} else if (ev.type === "llm_response") {
|
|
@@ -10991,7 +11788,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
10991
11788
|
endedAt: sessionEndEvent?.ts,
|
|
10992
11789
|
model: sessionModel,
|
|
10993
11790
|
provider: sessionProvider,
|
|
10994
|
-
pendingToolUses: sessionPendingToolUses
|
|
11791
|
+
pendingToolUses: sessionPendingToolUses,
|
|
11792
|
+
forkedFrom: sessionForkedEvent ? {
|
|
11793
|
+
sessionId: sessionForkedEvent.parentSessionId,
|
|
11794
|
+
checkpointPromptIndex: sessionForkedEvent.parentCheckpointPromptIndex,
|
|
11795
|
+
checkpointHash: sessionForkedEvent.parentCheckpointHash,
|
|
11796
|
+
workspace: sessionForkedEvent.workspace,
|
|
11797
|
+
workspaceCheckpointHash: sessionForkedEvent.workspaceCheckpointHash
|
|
11798
|
+
} : void 0
|
|
10995
11799
|
};
|
|
10996
11800
|
const toolCallEnds = extractToolCallEnds(events);
|
|
10997
11801
|
const data = { metadata: meta, events, messages: finalMessages, usage, toolCallEnds };
|
|
@@ -11002,7 +11806,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11002
11806
|
this._loadCache.delete(oldest);
|
|
11003
11807
|
}
|
|
11004
11808
|
}
|
|
11005
|
-
this._loadCache.set(id, { mtimeMs:
|
|
11809
|
+
this._loadCache.set(id, { mtimeMs: stat9.mtimeMs, size: stat9.size, data });
|
|
11006
11810
|
}
|
|
11007
11811
|
return data;
|
|
11008
11812
|
} catch (err) {
|
|
@@ -11044,17 +11848,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11044
11848
|
const limit = opts?.limit;
|
|
11045
11849
|
const signal = opts?.signal;
|
|
11046
11850
|
const out = [];
|
|
11047
|
-
let
|
|
11851
|
+
let stat9;
|
|
11048
11852
|
try {
|
|
11049
|
-
|
|
11853
|
+
stat9 = await fsp9.stat(file);
|
|
11050
11854
|
} catch (err) {
|
|
11051
11855
|
if (err.code === "ENOENT") return [];
|
|
11052
11856
|
throw err;
|
|
11053
11857
|
}
|
|
11054
|
-
if (
|
|
11858
|
+
if (stat9.size === 0) return [];
|
|
11055
11859
|
let fh;
|
|
11056
11860
|
try {
|
|
11057
|
-
fh = await
|
|
11861
|
+
fh = await fsp9.open(file, "r");
|
|
11058
11862
|
const CHUNK = 64 * 1024;
|
|
11059
11863
|
const buf = Buffer.alloc(CHUNK);
|
|
11060
11864
|
let leftover = "";
|
|
@@ -11167,7 +11971,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11167
11971
|
let shouldCompact = false;
|
|
11168
11972
|
await withFileLock(this.indexFile, async () => {
|
|
11169
11973
|
const line = JSON.stringify(summary) + "\n";
|
|
11170
|
-
await
|
|
11974
|
+
await fsp9.appendFile(this.indexFile, line, "utf8");
|
|
11171
11975
|
this._indexCache = null;
|
|
11172
11976
|
this.invalidateShardManifestBySessionId(summary.id);
|
|
11173
11977
|
this.indexAppendCount++;
|
|
@@ -11188,7 +11992,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11188
11992
|
await ensureDir(this.dir);
|
|
11189
11993
|
await withFileLock(this.indexFile, async () => {
|
|
11190
11994
|
const line = JSON.stringify({ action: "delete", id }) + "\n";
|
|
11191
|
-
await
|
|
11995
|
+
await fsp9.appendFile(this.indexFile, line, "utf8");
|
|
11192
11996
|
this._indexCache = null;
|
|
11193
11997
|
this.invalidateShardManifestBySessionId(id);
|
|
11194
11998
|
this.indexAppendCount++;
|
|
@@ -11234,20 +12038,20 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11234
12038
|
* Returns empty array when the index doesn't exist or is corrupt.
|
|
11235
12039
|
*/
|
|
11236
12040
|
async readIndex() {
|
|
11237
|
-
let
|
|
12041
|
+
let stat9;
|
|
11238
12042
|
try {
|
|
11239
|
-
const s = await
|
|
11240
|
-
|
|
12043
|
+
const s = await fsp9.stat(this.indexFile);
|
|
12044
|
+
stat9 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
11241
12045
|
} catch {
|
|
11242
12046
|
this._indexCache = null;
|
|
11243
12047
|
return [];
|
|
11244
12048
|
}
|
|
11245
|
-
if (this._indexCache !== null && this._indexCache.mtimeMs ===
|
|
12049
|
+
if (this._indexCache !== null && this._indexCache.mtimeMs === stat9.mtimeMs && this._indexCache.size === stat9.size) {
|
|
11246
12050
|
return [...this._indexCache.summaries];
|
|
11247
12051
|
}
|
|
11248
12052
|
let raw;
|
|
11249
12053
|
try {
|
|
11250
|
-
raw = await
|
|
12054
|
+
raw = await fsp9.readFile(this.indexFile, "utf8");
|
|
11251
12055
|
} catch {
|
|
11252
12056
|
this._indexCache = null;
|
|
11253
12057
|
return [];
|
|
@@ -11275,7 +12079,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11275
12079
|
if (a.startedAt > b.startedAt) return -1;
|
|
11276
12080
|
return a.id.localeCompare(b.id);
|
|
11277
12081
|
});
|
|
11278
|
-
this._indexCache = { ...
|
|
12082
|
+
this._indexCache = { ...stat9, summaries };
|
|
11279
12083
|
return [...summaries];
|
|
11280
12084
|
}
|
|
11281
12085
|
/**
|
|
@@ -11318,7 +12122,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11318
12122
|
async collectShardKeys() {
|
|
11319
12123
|
let entries;
|
|
11320
12124
|
try {
|
|
11321
|
-
entries = await
|
|
12125
|
+
entries = await fsp9.readdir(this.dir, { withFileTypes: true });
|
|
11322
12126
|
} catch {
|
|
11323
12127
|
return [""];
|
|
11324
12128
|
}
|
|
@@ -11335,7 +12139,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11335
12139
|
if (cached) return cached;
|
|
11336
12140
|
const manifestPath = this.shardManifestPath(shardKey);
|
|
11337
12141
|
try {
|
|
11338
|
-
const raw = await
|
|
12142
|
+
const raw = await fsp9.readFile(manifestPath, "utf8");
|
|
11339
12143
|
const parsed = JSON.parse(raw);
|
|
11340
12144
|
const entry2 = {
|
|
11341
12145
|
summaries: Array.isArray(parsed.summaries) ? parsed.summaries : [],
|
|
@@ -11366,14 +12170,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11366
12170
|
return entry;
|
|
11367
12171
|
}
|
|
11368
12172
|
async collectSessionFilesInShard(shardKey) {
|
|
11369
|
-
const dir = shardKey ?
|
|
12173
|
+
const dir = shardKey ? path9.join(this.dir, shardKey) : this.dir;
|
|
11370
12174
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
11371
12175
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
11372
12176
|
}
|
|
11373
12177
|
async collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
11374
12178
|
let entries;
|
|
11375
12179
|
try {
|
|
11376
|
-
entries = await
|
|
12180
|
+
entries = await fsp9.readdir(dir, { withFileTypes: true });
|
|
11377
12181
|
} catch {
|
|
11378
12182
|
return [];
|
|
11379
12183
|
}
|
|
@@ -11389,13 +12193,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11389
12193
|
if (entry.name === "_index.jsonl") continue;
|
|
11390
12194
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
11391
12195
|
const id = prefix ? `${prefix}/${base}` : base;
|
|
11392
|
-
files.push({ id, filePath:
|
|
12196
|
+
files.push({ id, filePath: path9.join(dir, entry.name) });
|
|
11393
12197
|
}
|
|
11394
12198
|
}
|
|
11395
12199
|
const childFileArrays = await Promise.all(
|
|
11396
12200
|
dirEntries.map((entry) => {
|
|
11397
12201
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
11398
|
-
return this.collectSessionFiles(
|
|
12202
|
+
return this.collectSessionFiles(path9.join(dir, entry.name), childPrefix, depth + 1);
|
|
11399
12203
|
})
|
|
11400
12204
|
);
|
|
11401
12205
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -11407,7 +12211,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11407
12211
|
async collectSessionIds(dir, prefix = "", depth = 0) {
|
|
11408
12212
|
let entries;
|
|
11409
12213
|
try {
|
|
11410
|
-
entries = await
|
|
12214
|
+
entries = await fsp9.readdir(dir, { withFileTypes: true });
|
|
11411
12215
|
} catch {
|
|
11412
12216
|
return [];
|
|
11413
12217
|
}
|
|
@@ -11428,7 +12232,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11428
12232
|
const childIdArrays = await Promise.all(
|
|
11429
12233
|
dirEntries.map((entry) => {
|
|
11430
12234
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
11431
|
-
return this.collectSessionIds(
|
|
12235
|
+
return this.collectSessionIds(path9.join(dir, entry.name), childPrefix, depth + 1);
|
|
11432
12236
|
})
|
|
11433
12237
|
);
|
|
11434
12238
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -11442,8 +12246,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11442
12246
|
if (fromManifest) return fromManifest;
|
|
11443
12247
|
try {
|
|
11444
12248
|
const full = this.sessionPath(id, ".jsonl");
|
|
11445
|
-
const
|
|
11446
|
-
const summary = await this.summarize(id,
|
|
12249
|
+
const stat9 = await fsp9.stat(full);
|
|
12250
|
+
const summary = await this.summarize(id, stat9.mtime.toISOString());
|
|
11447
12251
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
11448
12252
|
const msg = toErrorMessage(err);
|
|
11449
12253
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
@@ -11476,7 +12280,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11476
12280
|
async readSummaryManifest(id, startTime = Date.now()) {
|
|
11477
12281
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
11478
12282
|
try {
|
|
11479
|
-
const raw = await
|
|
12283
|
+
const raw = await fsp9.readFile(manifest, "utf8");
|
|
11480
12284
|
this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
|
|
11481
12285
|
return JSON.parse(raw);
|
|
11482
12286
|
} catch {
|
|
@@ -11486,18 +12290,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11486
12290
|
async summaryHeaderFor(ref) {
|
|
11487
12291
|
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
11488
12292
|
try {
|
|
11489
|
-
const
|
|
11490
|
-
if (!
|
|
12293
|
+
const stat9 = await fsp9.stat(ref.filePath);
|
|
12294
|
+
if (!stat9.isFile()) {
|
|
11491
12295
|
return {
|
|
11492
12296
|
id: ref.id,
|
|
11493
12297
|
title: "(damaged)",
|
|
11494
|
-
startedAt:
|
|
12298
|
+
startedAt: stat9.mtime.toISOString(),
|
|
11495
12299
|
model: "unknown",
|
|
11496
12300
|
provider: "unknown",
|
|
11497
12301
|
tokenTotal: 0
|
|
11498
12302
|
};
|
|
11499
12303
|
}
|
|
11500
|
-
mtime =
|
|
12304
|
+
mtime = stat9.mtime.toISOString();
|
|
11501
12305
|
} catch {
|
|
11502
12306
|
return null;
|
|
11503
12307
|
}
|
|
@@ -11545,15 +12349,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11545
12349
|
async deleteSession(id) {
|
|
11546
12350
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
11547
12351
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
11548
|
-
const shardDir =
|
|
11549
|
-
const base =
|
|
11550
|
-
const sessDir =
|
|
12352
|
+
const shardDir = path9.dirname(jsonlPath);
|
|
12353
|
+
const base = path9.basename(id);
|
|
12354
|
+
const sessDir = path9.join(shardDir, base);
|
|
11551
12355
|
const deletions = [
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
|
|
11556
|
-
|
|
12356
|
+
fsp9.unlink(jsonlPath),
|
|
12357
|
+
fsp9.unlink(summaryPath),
|
|
12358
|
+
fsp9.unlink(sessionScopedPath(this.dir, id, ".plan.json")),
|
|
12359
|
+
fsp9.unlink(sessionScopedPath(this.dir, id, ".tasks.json")),
|
|
12360
|
+
fsp9.unlink(sessionScopedPath(this.dir, id, ".todos.json"))
|
|
11557
12361
|
];
|
|
11558
12362
|
const results = await Promise.allSettled(deletions);
|
|
11559
12363
|
for (const r of results) {
|
|
@@ -11570,7 +12374,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11570
12374
|
}
|
|
11571
12375
|
}
|
|
11572
12376
|
}
|
|
11573
|
-
await
|
|
12377
|
+
await fsp9.rm(sessDir, { recursive: true, force: true }).catch((err) => {
|
|
11574
12378
|
console.warn(JSON.stringify({
|
|
11575
12379
|
level: "warn",
|
|
11576
12380
|
event: "session_store.rmdir_failed",
|
|
@@ -11588,7 +12392,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11588
12392
|
*/
|
|
11589
12393
|
async readActiveSessionId() {
|
|
11590
12394
|
try {
|
|
11591
|
-
const raw = await
|
|
12395
|
+
const raw = await fsp9.readFile(path9.join(this.dir, "active.json"), "utf8");
|
|
11592
12396
|
const active = JSON.parse(raw);
|
|
11593
12397
|
return active.sessionId ?? null;
|
|
11594
12398
|
} catch {
|
|
@@ -11615,7 +12419,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11615
12419
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
11616
12420
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
11617
12421
|
try {
|
|
11618
|
-
await
|
|
12422
|
+
await fsp9.stat(jsonlPath);
|
|
11619
12423
|
} catch (err) {
|
|
11620
12424
|
const code = err?.code;
|
|
11621
12425
|
if (code === "ENOENT") {
|
|
@@ -11651,10 +12455,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11651
12455
|
const activeSessionId = await this.readActiveSessionId();
|
|
11652
12456
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
11653
12457
|
const pruneFile = async (dir, name, prefix) => {
|
|
11654
|
-
const jsonlPath =
|
|
12458
|
+
const jsonlPath = path9.join(dir, name);
|
|
11655
12459
|
try {
|
|
11656
|
-
const
|
|
11657
|
-
if (
|
|
12460
|
+
const stat9 = await fsp9.stat(jsonlPath);
|
|
12461
|
+
if (stat9.mtimeMs >= cutoff) return;
|
|
11658
12462
|
} catch {
|
|
11659
12463
|
return;
|
|
11660
12464
|
}
|
|
@@ -11664,15 +12468,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11664
12468
|
await this.deleteSession(id);
|
|
11665
12469
|
deleted++;
|
|
11666
12470
|
};
|
|
11667
|
-
const entries = await
|
|
12471
|
+
const entries = await fsp9.readdir(this.dir, { withFileTypes: true }).catch(() => []);
|
|
11668
12472
|
for (const entry of entries) {
|
|
11669
12473
|
if (entry.isFile()) {
|
|
11670
12474
|
if (isPrunableJsonl(entry.name)) await pruneFile(this.dir, entry.name, "");
|
|
11671
12475
|
continue;
|
|
11672
12476
|
}
|
|
11673
12477
|
if (!entry.isDirectory()) continue;
|
|
11674
|
-
const dateDir =
|
|
11675
|
-
const files = await
|
|
12478
|
+
const dateDir = path9.join(this.dir, entry.name);
|
|
12479
|
+
const files = await fsp9.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
11676
12480
|
for (const file of files) {
|
|
11677
12481
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
11678
12482
|
await pruneFile(dateDir, file.name, entry.name);
|
|
@@ -11683,11 +12487,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11683
12487
|
}
|
|
11684
12488
|
for (const entry of entries) {
|
|
11685
12489
|
if (!entry.isDirectory()) continue;
|
|
11686
|
-
const dateDir =
|
|
12490
|
+
const dateDir = path9.join(this.dir, entry.name);
|
|
11687
12491
|
try {
|
|
11688
|
-
const remaining = await
|
|
12492
|
+
const remaining = await fsp9.readdir(dateDir);
|
|
11689
12493
|
if (remaining.length === 0) {
|
|
11690
|
-
await
|
|
12494
|
+
await fsp9.rmdir(dateDir).catch(() => void 0);
|
|
11691
12495
|
}
|
|
11692
12496
|
} catch {
|
|
11693
12497
|
}
|
|
@@ -11706,8 +12510,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
11706
12510
|
provider: "unknown"
|
|
11707
12511
|
})}
|
|
11708
12512
|
`;
|
|
11709
|
-
await
|
|
11710
|
-
await
|
|
12513
|
+
await fsp9.writeFile(file, record, "utf8");
|
|
12514
|
+
await fsp9.unlink(meta).catch(() => void 0);
|
|
11711
12515
|
}
|
|
11712
12516
|
async summarize(id, mtime) {
|
|
11713
12517
|
try {
|
|
@@ -11814,9 +12618,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
11814
12618
|
let dir;
|
|
11815
12619
|
if (opts.store) {
|
|
11816
12620
|
store = opts.store;
|
|
11817
|
-
dir = opts.sessionsRoot ?
|
|
12621
|
+
dir = opts.sessionsRoot ? path9.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
11818
12622
|
} else if (opts.sessionsRoot) {
|
|
11819
|
-
dir =
|
|
12623
|
+
dir = path9.join(opts.sessionsRoot, runId);
|
|
11820
12624
|
store = new DefaultSessionStore({ dir });
|
|
11821
12625
|
} else {
|
|
11822
12626
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -11862,6 +12666,8 @@ var FleetManager = class {
|
|
|
11862
12666
|
/** Fleet-wide cost cap. Infinity = no cap. Distinct from SubagentBudget limits,
|
|
11863
12667
|
* which track per-subagent spend — this field caps the entire fleet total. */
|
|
11864
12668
|
maxFleetCostUsd;
|
|
12669
|
+
/** Fleet-wide input+output token ceiling. Infinity = no cap. */
|
|
12670
|
+
maxFleetTokens;
|
|
11865
12671
|
manifestEntries = /* @__PURE__ */ new Map();
|
|
11866
12672
|
/** Pending tasks with their descriptions — populated by `addPendingTask`
|
|
11867
12673
|
* and cleared by `removePendingTask`. Replaces the host-side `pending`
|
|
@@ -11883,11 +12689,12 @@ var FleetManager = class {
|
|
|
11883
12689
|
this.manifestPath = opts.manifestPath;
|
|
11884
12690
|
this.directorRunId = opts.directorRunId ?? randomUUID();
|
|
11885
12691
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
11886
|
-
this.maxSpawnDepth = opts.maxSpawnDepth
|
|
12692
|
+
this.maxSpawnDepth = resolveMaxSpawnDepth(opts.maxSpawnDepth);
|
|
11887
12693
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
11888
12694
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
11889
12695
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
11890
12696
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
12697
|
+
this.maxFleetTokens = opts.directorBudget?.maxTokens ?? Number.POSITIVE_INFINITY;
|
|
11891
12698
|
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
11892
12699
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
11893
12700
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
@@ -11956,6 +12763,13 @@ var FleetManager = class {
|
|
|
11956
12763
|
return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
|
|
11957
12764
|
}
|
|
11958
12765
|
}
|
|
12766
|
+
if (this.maxFleetTokens < Number.POSITIVE_INFINITY) {
|
|
12767
|
+
const total = this.usage.snapshot().total;
|
|
12768
|
+
const usedTokens = (total?.input ?? 0) + (total?.output ?? 0);
|
|
12769
|
+
if (usedTokens >= this.maxFleetTokens) {
|
|
12770
|
+
return { kind: "max_tokens", limit: this.maxFleetTokens, observed: usedTokens };
|
|
12771
|
+
}
|
|
12772
|
+
}
|
|
11959
12773
|
if (this.maxLeaderContextLoad < 1) {
|
|
11960
12774
|
const maxContext = this.resolveMaxContext();
|
|
11961
12775
|
const threshold = maxContext * this.maxLeaderContextLoad;
|
|
@@ -11969,6 +12783,22 @@ var FleetManager = class {
|
|
|
11969
12783
|
}
|
|
11970
12784
|
return null;
|
|
11971
12785
|
}
|
|
12786
|
+
budgetSnapshot() {
|
|
12787
|
+
const total = this.usage.snapshot().total;
|
|
12788
|
+
const usedTokens = (total?.input ?? 0) + (total?.output ?? 0);
|
|
12789
|
+
const usedCostUsd = total?.cost ?? 0;
|
|
12790
|
+
return {
|
|
12791
|
+
maxSpawns: this.maxSpawns,
|
|
12792
|
+
usedSpawns: this.spawnCount,
|
|
12793
|
+
remainingSpawns: Math.max(0, this.maxSpawns - this.spawnCount),
|
|
12794
|
+
maxTokens: this.maxFleetTokens,
|
|
12795
|
+
usedTokens,
|
|
12796
|
+
remainingTokens: Math.max(0, this.maxFleetTokens - usedTokens),
|
|
12797
|
+
maxCostUsd: this.maxFleetCostUsd,
|
|
12798
|
+
usedCostUsd,
|
|
12799
|
+
remainingCostUsd: Math.max(0, this.maxFleetCostUsd - usedCostUsd)
|
|
12800
|
+
};
|
|
12801
|
+
}
|
|
11972
12802
|
setLeaderContextPressure(tokens) {
|
|
11973
12803
|
this.leaderContextPressure = tokens;
|
|
11974
12804
|
}
|
|
@@ -12072,7 +12902,7 @@ var FleetManager = class {
|
|
|
12072
12902
|
})),
|
|
12073
12903
|
usage: this.usage.snapshot()
|
|
12074
12904
|
};
|
|
12075
|
-
await
|
|
12905
|
+
await fsp9.mkdir(path9.dirname(this.manifestPath), { recursive: true });
|
|
12076
12906
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
12077
12907
|
return this.manifestPath;
|
|
12078
12908
|
}
|
|
@@ -12225,7 +13055,7 @@ var REGISTRY_CACHE_TTL_MS = 2e3;
|
|
|
12225
13055
|
var LINE_SEPARATOR = "\n";
|
|
12226
13056
|
var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
|
|
12227
13057
|
function resolveProjectDir(projectRoot, globalRoot) {
|
|
12228
|
-
return
|
|
13058
|
+
return path9.join(globalRoot, "projects", projectSlug(projectRoot));
|
|
12229
13059
|
}
|
|
12230
13060
|
var GlobalMailbox = class {
|
|
12231
13061
|
/** Path to the JSONL message file. */
|
|
@@ -12289,14 +13119,14 @@ var GlobalMailbox = class {
|
|
|
12289
13119
|
* @param hqPublisher — optional HQ publisher, or getter, for cross-project telemetry
|
|
12290
13120
|
*/
|
|
12291
13121
|
constructor(projectDir, events, hqPublisher) {
|
|
12292
|
-
this.messagePath =
|
|
12293
|
-
this.registryPath =
|
|
12294
|
-
this.clientRegistryPath =
|
|
13122
|
+
this.messagePath = path9.join(projectDir, MAILBOX_FILE);
|
|
13123
|
+
this.registryPath = path9.join(projectDir, "_mailbox.registry.json");
|
|
13124
|
+
this.clientRegistryPath = path9.join(projectDir, CLIENT_REGISTRY_FILE);
|
|
12295
13125
|
this._events = events;
|
|
12296
13126
|
this._hqPublisher = hqPublisher;
|
|
12297
13127
|
}
|
|
12298
13128
|
get hqMailboxId() {
|
|
12299
|
-
return `${
|
|
13129
|
+
return `${path9.basename(path9.dirname(this.messagePath))}:mailbox`;
|
|
12300
13130
|
}
|
|
12301
13131
|
get hqPublisher() {
|
|
12302
13132
|
return typeof this._hqPublisher === "function" ? this._hqPublisher() : this._hqPublisher;
|
|
@@ -12333,9 +13163,9 @@ var GlobalMailbox = class {
|
|
|
12333
13163
|
taskContext: input.taskContext
|
|
12334
13164
|
};
|
|
12335
13165
|
const line = JSON.stringify(msg) + LINE_SEPARATOR;
|
|
12336
|
-
await
|
|
13166
|
+
await fsp9.mkdir(path9.dirname(this.messagePath), { recursive: true });
|
|
12337
13167
|
await withFileLock(this.messagePath, async () => {
|
|
12338
|
-
await
|
|
13168
|
+
await fsp9.appendFile(this.messagePath, line, "utf8");
|
|
12339
13169
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
12340
13170
|
this._pushToCache(msg, mtimeMs, size);
|
|
12341
13171
|
});
|
|
@@ -12406,7 +13236,7 @@ var GlobalMailbox = class {
|
|
|
12406
13236
|
}
|
|
12407
13237
|
if (changed) {
|
|
12408
13238
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
12409
|
-
await
|
|
13239
|
+
await fsp9.writeFile(this.messagePath, serialized, "utf8");
|
|
12410
13240
|
}
|
|
12411
13241
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
12412
13242
|
this._setMessageCache(all, mtimeMs, size);
|
|
@@ -12448,7 +13278,7 @@ var GlobalMailbox = class {
|
|
|
12448
13278
|
updated = m;
|
|
12449
13279
|
}
|
|
12450
13280
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
12451
|
-
await
|
|
13281
|
+
await fsp9.writeFile(this.messagePath, serialized, "utf8");
|
|
12452
13282
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
12453
13283
|
this._setMessageCache(all, mtimeMs, size);
|
|
12454
13284
|
});
|
|
@@ -12473,7 +13303,7 @@ var GlobalMailbox = class {
|
|
|
12473
13303
|
updated = m;
|
|
12474
13304
|
}
|
|
12475
13305
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
12476
|
-
await
|
|
13306
|
+
await fsp9.writeFile(this.messagePath, serialized, "utf8");
|
|
12477
13307
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
12478
13308
|
this._setMessageCache(all, mtimeMs, size);
|
|
12479
13309
|
});
|
|
@@ -12720,7 +13550,7 @@ var GlobalMailbox = class {
|
|
|
12720
13550
|
}
|
|
12721
13551
|
async clearAll() {
|
|
12722
13552
|
await withFileLock(this.messagePath, async () => {
|
|
12723
|
-
await
|
|
13553
|
+
await fsp9.writeFile(this.messagePath, "", "utf8");
|
|
12724
13554
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
12725
13555
|
this._setMessageCache([], mtimeMs, size);
|
|
12726
13556
|
});
|
|
@@ -12753,7 +13583,7 @@ var GlobalMailbox = class {
|
|
|
12753
13583
|
remaining = kept.length;
|
|
12754
13584
|
if (kept.length < all.length) {
|
|
12755
13585
|
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
|
|
12756
|
-
await
|
|
13586
|
+
await fsp9.writeFile(this.messagePath, content, "utf8");
|
|
12757
13587
|
}
|
|
12758
13588
|
const { mtimeMs, size } = await this._statMessageFile();
|
|
12759
13589
|
this._setMessageCache(kept, mtimeMs, size);
|
|
@@ -12774,7 +13604,7 @@ var GlobalMailbox = class {
|
|
|
12774
13604
|
*/
|
|
12775
13605
|
async _readMessages() {
|
|
12776
13606
|
try {
|
|
12777
|
-
const raw = await
|
|
13607
|
+
const raw = await fsp9.readFile(this.messagePath, "utf8");
|
|
12778
13608
|
return this._parseLines(raw);
|
|
12779
13609
|
} catch (err) {
|
|
12780
13610
|
if (err.code === "ENOENT") return [];
|
|
@@ -12867,7 +13697,7 @@ var GlobalMailbox = class {
|
|
|
12867
13697
|
*/
|
|
12868
13698
|
async _statMessageFile() {
|
|
12869
13699
|
try {
|
|
12870
|
-
const st = await
|
|
13700
|
+
const st = await fsp9.stat(this.messagePath);
|
|
12871
13701
|
return { mtimeMs: st.mtimeMs, size: st.size };
|
|
12872
13702
|
} catch (err) {
|
|
12873
13703
|
if (err.code === "ENOENT") {
|
|
@@ -12913,12 +13743,12 @@ var GlobalMailbox = class {
|
|
|
12913
13743
|
*/
|
|
12914
13744
|
async _readMessagesCachedWork() {
|
|
12915
13745
|
try {
|
|
12916
|
-
const st = await
|
|
13746
|
+
const st = await fsp9.stat(this.messagePath);
|
|
12917
13747
|
if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
|
|
12918
13748
|
return this._messageCache;
|
|
12919
13749
|
}
|
|
12920
13750
|
if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
|
|
12921
|
-
const fd = await
|
|
13751
|
+
const fd = await fsp9.open(this.messagePath, "r");
|
|
12922
13752
|
try {
|
|
12923
13753
|
const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
|
|
12924
13754
|
this._messageCacheMtime = st.mtimeMs;
|
|
@@ -12994,14 +13824,14 @@ var GlobalMailbox = class {
|
|
|
12994
13824
|
this._messageCacheSize = size;
|
|
12995
13825
|
}
|
|
12996
13826
|
async _ensureRegistry() {
|
|
12997
|
-
await
|
|
13827
|
+
await fsp9.mkdir(path9.dirname(this.registryPath), { recursive: true });
|
|
12998
13828
|
}
|
|
12999
13829
|
async _readRegistry(opts) {
|
|
13000
13830
|
if (!opts?.fresh && this._registryCache && Date.now() - this._registryCacheAt < REGISTRY_CACHE_TTL_MS) {
|
|
13001
13831
|
return new Map(this._registryCache);
|
|
13002
13832
|
}
|
|
13003
13833
|
try {
|
|
13004
|
-
const raw = await
|
|
13834
|
+
const raw = await fsp9.readFile(this.registryPath, "utf8");
|
|
13005
13835
|
const data = JSON.parse(raw);
|
|
13006
13836
|
const map = /* @__PURE__ */ new Map();
|
|
13007
13837
|
for (const [id, agent] of Object.entries(data)) {
|
|
@@ -13044,19 +13874,19 @@ var GlobalMailbox = class {
|
|
|
13044
13874
|
obj[id] = agent;
|
|
13045
13875
|
}
|
|
13046
13876
|
const tmp = `${this.registryPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
13047
|
-
await
|
|
13048
|
-
await
|
|
13877
|
+
await fsp9.writeFile(tmp, JSON.stringify(obj), "utf8");
|
|
13878
|
+
await fsp9.rename(tmp, this.registryPath);
|
|
13049
13879
|
}
|
|
13050
13880
|
// ── Client registry internals ───────────────────────────────────────────
|
|
13051
13881
|
async _ensureClientRegistry() {
|
|
13052
|
-
await
|
|
13882
|
+
await fsp9.mkdir(path9.dirname(this.clientRegistryPath), { recursive: true });
|
|
13053
13883
|
}
|
|
13054
13884
|
async _readClientRegistry(opts) {
|
|
13055
13885
|
if (!opts?.fresh && this._clientRegistryCache && Date.now() - this._clientRegistryCacheAt < REGISTRY_CACHE_TTL_MS) {
|
|
13056
13886
|
return new Map(this._clientRegistryCache);
|
|
13057
13887
|
}
|
|
13058
13888
|
try {
|
|
13059
|
-
const raw = await
|
|
13889
|
+
const raw = await fsp9.readFile(this.clientRegistryPath, "utf8");
|
|
13060
13890
|
const data = JSON.parse(raw);
|
|
13061
13891
|
const map = /* @__PURE__ */ new Map();
|
|
13062
13892
|
for (const [id, client] of Object.entries(data)) {
|
|
@@ -13089,8 +13919,8 @@ var GlobalMailbox = class {
|
|
|
13089
13919
|
obj[id] = client;
|
|
13090
13920
|
}
|
|
13091
13921
|
const tmp = `${this.clientRegistryPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
13092
|
-
await
|
|
13093
|
-
await
|
|
13922
|
+
await fsp9.writeFile(tmp, JSON.stringify(obj), "utf8");
|
|
13923
|
+
await fsp9.rename(tmp, this.clientRegistryPath);
|
|
13094
13924
|
}
|
|
13095
13925
|
};
|
|
13096
13926
|
function defaultResolveProjectDir(ctx) {
|
|
@@ -13484,7 +14314,7 @@ var FleetSupervisor = class {
|
|
|
13484
14314
|
// ── observation state ─────────────────────────────────────────────────
|
|
13485
14315
|
/** taskId → first tick timestamp we saw it pending. */
|
|
13486
14316
|
pendingSince = /* @__PURE__ */ new Map();
|
|
13487
|
-
/** subagentId → last
|
|
14317
|
+
/** subagentId → last observable fleet event timestamp. */
|
|
13488
14318
|
lastActivityAt = /* @__PURE__ */ new Map();
|
|
13489
14319
|
/** subagentId → consecutive non-success completions. */
|
|
13490
14320
|
failStreaks = /* @__PURE__ */ new Map();
|
|
@@ -13502,7 +14332,7 @@ var FleetSupervisor = class {
|
|
|
13502
14332
|
if (!this.cfg.enabled || this.timer) return;
|
|
13503
14333
|
this.stopped = false;
|
|
13504
14334
|
this.offHandles.push(
|
|
13505
|
-
this.opts.fleet.
|
|
14335
|
+
this.opts.fleet.onAny((e) => {
|
|
13506
14336
|
this.lastActivityAt.set(e.subagentId, this.now());
|
|
13507
14337
|
})
|
|
13508
14338
|
);
|
|
@@ -13610,7 +14440,7 @@ var FleetSupervisor = class {
|
|
|
13610
14440
|
this.backlogTicks = backlogged ? this.backlogTicks + 1 : 0;
|
|
13611
14441
|
if (backlogged && this.backlogTicks >= 2 && this.cfg.allowSpawn) {
|
|
13612
14442
|
this.backlogTicks = 0;
|
|
13613
|
-
await this.engageBacklog(pending
|
|
14443
|
+
await this.engageBacklog(pending, live);
|
|
13614
14444
|
return;
|
|
13615
14445
|
}
|
|
13616
14446
|
for (const s of running) {
|
|
@@ -13695,7 +14525,7 @@ var FleetSupervisor = class {
|
|
|
13695
14525
|
};
|
|
13696
14526
|
const decision = await this.opts.brain.decide(request);
|
|
13697
14527
|
if (decision.type !== "answer") return { choice: null, decision };
|
|
13698
|
-
const choice =
|
|
14528
|
+
const choice = options.some((option) => option.id === decision.optionId) ? decision.optionId ?? null : null;
|
|
13699
14529
|
return { choice, decision };
|
|
13700
14530
|
}
|
|
13701
14531
|
async engageRebalance(kind, fromWorkerId, tasks, idleWorkerIds) {
|
|
@@ -13777,10 +14607,11 @@ Idle workers: ${idleWorkerIds.join(", ")}`,
|
|
|
13777
14607
|
this.engaging = false;
|
|
13778
14608
|
}
|
|
13779
14609
|
}
|
|
13780
|
-
async engageBacklog(
|
|
14610
|
+
async engageBacklog(pending, liveWorkers) {
|
|
13781
14611
|
if (!this.cooldownOk("backlog", "fleet")) return;
|
|
13782
14612
|
this.engaging = true;
|
|
13783
14613
|
try {
|
|
14614
|
+
const pendingCount = pending.length;
|
|
13784
14615
|
this.emitSignal(
|
|
13785
14616
|
"backlog",
|
|
13786
14617
|
`${pendingCount} pending tasks vs ${liveWorkers} live worker(s)`
|
|
@@ -13810,7 +14641,8 @@ Idle workers: ${idleWorkerIds.join(", ")}`,
|
|
|
13810
14641
|
return;
|
|
13811
14642
|
}
|
|
13812
14643
|
const res = await this.opts.actions.spawnHelper({
|
|
13813
|
-
reason: `queue backlog: ${pendingCount} pending / ${liveWorkers} workers
|
|
14644
|
+
reason: `queue backlog: ${pendingCount} pending / ${liveWorkers} workers`,
|
|
14645
|
+
task: pending[0]
|
|
13814
14646
|
});
|
|
13815
14647
|
if ("subagentId" in res) {
|
|
13816
14648
|
this.emitAction("spawn_helper", true, res.subagentId, res.subagentId);
|
|
@@ -13851,11 +14683,11 @@ Idle workers: ${idleWorkerIds.join(", ")}`,
|
|
|
13851
14683
|
try {
|
|
13852
14684
|
this.emitSignal(
|
|
13853
14685
|
"stuck_agent",
|
|
13854
|
-
`no
|
|
14686
|
+
`no observable activity for \u2265${Math.round(this.cfg.stuckMs / 1e3)}s`,
|
|
13855
14687
|
s.id
|
|
13856
14688
|
);
|
|
13857
14689
|
const { choice, decision } = await this.decide(
|
|
13858
|
-
`Worker ${s.name} (${s.id}) is marked running but produced no
|
|
14690
|
+
`Worker ${s.name} (${s.id}) is marked running but produced no observable activity for over ${Math.round(this.cfg.stuckMs / 1e3)}s. Nudge it with a steer message?`,
|
|
13859
14691
|
`Current task: ${s.currentTask ?? "unknown"}`,
|
|
13860
14692
|
[
|
|
13861
14693
|
{
|
|
@@ -13883,12 +14715,12 @@ Idle workers: ${idleWorkerIds.join(", ")}`,
|
|
|
13883
14715
|
await this.opts.actions.steerAgent(
|
|
13884
14716
|
s.id,
|
|
13885
14717
|
"Progress check from fleet supervisor",
|
|
13886
|
-
`You appear stalled (no
|
|
14718
|
+
`You appear stalled (no observable activity for ${Math.round(this.cfg.stuckMs / 1e3)}s on: ${s.currentTask ?? "your current task"}). If you are blocked, say so via mail_send to the leader and either narrow the scope or hand the task back \u2014 do not keep silently retrying the same step.`
|
|
13887
14719
|
);
|
|
13888
14720
|
this.emitAction("steer", true, "stuck nudge", s.id);
|
|
13889
14721
|
await this.opts.actions.notifyLeader(
|
|
13890
14722
|
`Worker ${s.name} may be stuck`,
|
|
13891
|
-
`No
|
|
14723
|
+
`No observable activity for ${Math.round(this.cfg.stuckMs / 1e3)}s. Supervisor nudged it; consider terminate_subagent + reassigning if it stays silent.`
|
|
13892
14724
|
).catch(() => {
|
|
13893
14725
|
});
|
|
13894
14726
|
this.record({
|
|
@@ -14185,7 +15017,7 @@ var DefaultMailbox = class {
|
|
|
14185
15017
|
/** Counts malformed JSONL lines skipped during parsing for observability. */
|
|
14186
15018
|
_corruptionCount = 0;
|
|
14187
15019
|
constructor(sessionDir) {
|
|
14188
|
-
this.filePath =
|
|
15020
|
+
this.filePath = path9.join(sessionDir, MAILBOX_FILE2);
|
|
14189
15021
|
}
|
|
14190
15022
|
get mailboxPath() {
|
|
14191
15023
|
return this.filePath;
|
|
@@ -14214,9 +15046,9 @@ var DefaultMailbox = class {
|
|
|
14214
15046
|
taskContext: input.taskContext
|
|
14215
15047
|
};
|
|
14216
15048
|
const line = JSON.stringify(msg) + LINE_SEPARATOR2;
|
|
14217
|
-
await
|
|
15049
|
+
await fsp9.mkdir(path9.dirname(this.filePath), { recursive: true });
|
|
14218
15050
|
await withFileLock(this.filePath, async () => {
|
|
14219
|
-
await
|
|
15051
|
+
await fsp9.appendFile(this.filePath, line, "utf8");
|
|
14220
15052
|
this._pushToCache(msg);
|
|
14221
15053
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
14222
15054
|
this._messageCacheMtime = mtime;
|
|
@@ -14316,7 +15148,7 @@ var DefaultMailbox = class {
|
|
|
14316
15148
|
}
|
|
14317
15149
|
if (changed) {
|
|
14318
15150
|
const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
|
|
14319
|
-
await
|
|
15151
|
+
await fsp9.writeFile(this.filePath, serialized, "utf8");
|
|
14320
15152
|
}
|
|
14321
15153
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
14322
15154
|
this._setMessageCache(all, mtime, size);
|
|
@@ -14377,7 +15209,7 @@ var DefaultMailbox = class {
|
|
|
14377
15209
|
}
|
|
14378
15210
|
async clearAll() {
|
|
14379
15211
|
await withFileLock(this.filePath, async () => {
|
|
14380
|
-
await
|
|
15212
|
+
await fsp9.writeFile(this.filePath, "", "utf8");
|
|
14381
15213
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
14382
15214
|
this._setMessageCache([], mtime, size);
|
|
14383
15215
|
});
|
|
@@ -14410,7 +15242,7 @@ var DefaultMailbox = class {
|
|
|
14410
15242
|
remaining = kept.length;
|
|
14411
15243
|
if (kept.length < all.length) {
|
|
14412
15244
|
const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
|
|
14413
|
-
await
|
|
15245
|
+
await fsp9.writeFile(this.filePath, content, "utf8");
|
|
14414
15246
|
}
|
|
14415
15247
|
const { mtime, size } = await this._statUnderLockOrAbsent();
|
|
14416
15248
|
this._setMessageCache(kept, mtime, size);
|
|
@@ -14433,7 +15265,7 @@ var DefaultMailbox = class {
|
|
|
14433
15265
|
// ── Internal ──────────────────────────────────────────────────────────
|
|
14434
15266
|
async _readAll() {
|
|
14435
15267
|
try {
|
|
14436
|
-
const raw = await
|
|
15268
|
+
const raw = await fsp9.readFile(this.filePath, "utf8");
|
|
14437
15269
|
return this._parseLines(raw);
|
|
14438
15270
|
} catch (err) {
|
|
14439
15271
|
if (err.code === "ENOENT") return [];
|
|
@@ -14508,7 +15340,7 @@ var DefaultMailbox = class {
|
|
|
14508
15340
|
*/
|
|
14509
15341
|
async _statUnderLockOrAbsent() {
|
|
14510
15342
|
try {
|
|
14511
|
-
const st = await
|
|
15343
|
+
const st = await fsp9.stat(this.filePath);
|
|
14512
15344
|
return { mtime: st.mtimeMs, size: st.size };
|
|
14513
15345
|
} catch (err) {
|
|
14514
15346
|
if (err.code === "ENOENT") {
|
|
@@ -14519,12 +15351,12 @@ var DefaultMailbox = class {
|
|
|
14519
15351
|
}
|
|
14520
15352
|
async _readAllCached() {
|
|
14521
15353
|
try {
|
|
14522
|
-
const st = await
|
|
15354
|
+
const st = await fsp9.stat(this.filePath);
|
|
14523
15355
|
if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
|
|
14524
15356
|
return this._messageCache;
|
|
14525
15357
|
}
|
|
14526
15358
|
if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
|
|
14527
|
-
const fd = await
|
|
15359
|
+
const fd = await fsp9.open(this.filePath, "r");
|
|
14528
15360
|
try {
|
|
14529
15361
|
const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
|
|
14530
15362
|
this._messageCacheMtime = st.mtimeMs;
|
|
@@ -14866,11 +15698,11 @@ var NULL_FLEET_BUS = new FleetBus();
|
|
|
14866
15698
|
var DEFAULT_MAX_ENTRIES = 1e4;
|
|
14867
15699
|
var LOG_FILENAME = "package-authors.json";
|
|
14868
15700
|
function logPath(storageDir) {
|
|
14869
|
-
return
|
|
15701
|
+
return path9.join(storageDir, LOG_FILENAME);
|
|
14870
15702
|
}
|
|
14871
15703
|
async function loadLog(storageDir, projectRoot) {
|
|
14872
15704
|
try {
|
|
14873
|
-
const raw = await
|
|
15705
|
+
const raw = await fsp9.readFile(logPath(storageDir), "utf-8");
|
|
14874
15706
|
const parsed = JSON.parse(raw);
|
|
14875
15707
|
if (!parsed.entries || !Array.isArray(parsed.entries)) {
|
|
14876
15708
|
return { projectRoot, entries: [] };
|
|
@@ -14884,13 +15716,13 @@ async function loadLog(storageDir, projectRoot) {
|
|
|
14884
15716
|
}
|
|
14885
15717
|
}
|
|
14886
15718
|
async function saveLog(storageDir, log) {
|
|
14887
|
-
await
|
|
15719
|
+
await fsp9.mkdir(storageDir, { recursive: true });
|
|
14888
15720
|
const tmp = `${logPath(storageDir)}.tmp.${Date.now()}`;
|
|
14889
|
-
await
|
|
14890
|
-
await
|
|
15721
|
+
await fsp9.writeFile(tmp, JSON.stringify(log, null, 2) + "\n", "utf-8");
|
|
15722
|
+
await fsp9.rename(tmp, logPath(storageDir));
|
|
14891
15723
|
}
|
|
14892
15724
|
function detectEcosystem(manifestPath) {
|
|
14893
|
-
const name =
|
|
15725
|
+
const name = path9.basename(manifestPath).toLowerCase();
|
|
14894
15726
|
if (name === "package.json") return "npm";
|
|
14895
15727
|
if (name === "go.mod") return "go";
|
|
14896
15728
|
if (name === "cargo.toml") return "cargo";
|
|
@@ -15138,8 +15970,8 @@ mvn versions:use-latest-versions`;
|
|
|
15138
15970
|
var MAILBOX_BRIDGE_LOCK_FILENAME = ".mailbox-bridge.lock";
|
|
15139
15971
|
var MAILBOX_BRIDGE_TOKEN_FILENAME = ".mailbox.token";
|
|
15140
15972
|
async function acquireOrJoin(opts) {
|
|
15141
|
-
const lockPath =
|
|
15142
|
-
const tokenPath =
|
|
15973
|
+
const lockPath = path9.join(opts.projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
15974
|
+
const tokenPath = path9.join(opts.projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
15143
15975
|
const inspected = await readLockForInspection(lockPath);
|
|
15144
15976
|
if (inspected.kind === "live") {
|
|
15145
15977
|
const existing = inspected.lock;
|
|
@@ -15149,7 +15981,7 @@ async function acquireOrJoin(opts) {
|
|
|
15149
15981
|
return { kind: "joined", lock: existing, tokenPath };
|
|
15150
15982
|
}
|
|
15151
15983
|
if (inspected.kind === "stale") {
|
|
15152
|
-
await
|
|
15984
|
+
await fsp9.unlink(lockPath).catch(() => void 0);
|
|
15153
15985
|
}
|
|
15154
15986
|
const generation = inspected.kind === "stale" ? inspected.lock.generation + 1 : 1;
|
|
15155
15987
|
const token = randomBytes(32).toString("hex");
|
|
@@ -15167,34 +15999,34 @@ async function acquireOrJoin(opts) {
|
|
|
15167
15999
|
return { kind: "acquired", lock: tentative, tokenPath };
|
|
15168
16000
|
}
|
|
15169
16001
|
async function finalize(projectDir, tentative, boundPort) {
|
|
15170
|
-
const lockPath =
|
|
15171
|
-
const tokenPath =
|
|
16002
|
+
const lockPath = path9.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
16003
|
+
const tokenPath = path9.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
15172
16004
|
const finalized = {
|
|
15173
16005
|
...tentative,
|
|
15174
16006
|
port: boundPort,
|
|
15175
16007
|
url: `http://${tentative.host}:${boundPort}`
|
|
15176
16008
|
};
|
|
15177
16009
|
await atomicWriteJson(lockPath, finalized);
|
|
15178
|
-
await
|
|
16010
|
+
await fsp9.writeFile(tokenPath, finalized.token, { mode: 384 });
|
|
15179
16011
|
return finalized;
|
|
15180
16012
|
}
|
|
15181
16013
|
async function release(projectDir, generation) {
|
|
15182
|
-
const lockPath =
|
|
15183
|
-
const tokenPath =
|
|
16014
|
+
const lockPath = path9.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
16015
|
+
const tokenPath = path9.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
|
|
15184
16016
|
try {
|
|
15185
|
-
const raw = await
|
|
16017
|
+
const raw = await fsp9.readFile(lockPath, "utf-8");
|
|
15186
16018
|
const parsed = JSON.parse(raw);
|
|
15187
16019
|
if (parsed.generation !== generation) return;
|
|
15188
16020
|
if (parsed.pid !== process.pid) return;
|
|
15189
|
-
await
|
|
15190
|
-
await
|
|
16021
|
+
await fsp9.unlink(lockPath).catch(() => void 0);
|
|
16022
|
+
await fsp9.unlink(tokenPath).catch(() => void 0);
|
|
15191
16023
|
} catch {
|
|
15192
16024
|
}
|
|
15193
16025
|
}
|
|
15194
16026
|
async function readLockForInspection(lockPath) {
|
|
15195
16027
|
let raw;
|
|
15196
16028
|
try {
|
|
15197
|
-
raw = await
|
|
16029
|
+
raw = await fsp9.readFile(lockPath, "utf-8");
|
|
15198
16030
|
} catch {
|
|
15199
16031
|
return { kind: "absent" };
|
|
15200
16032
|
}
|
|
@@ -15202,7 +16034,7 @@ async function readLockForInspection(lockPath) {
|
|
|
15202
16034
|
try {
|
|
15203
16035
|
parsed = JSON.parse(raw);
|
|
15204
16036
|
} catch {
|
|
15205
|
-
await
|
|
16037
|
+
await fsp9.unlink(lockPath).catch(() => void 0);
|
|
15206
16038
|
return { kind: "absent" };
|
|
15207
16039
|
}
|
|
15208
16040
|
if (!isProcessAlive(parsed.pid)) {
|
|
@@ -15214,7 +16046,7 @@ async function readLockForInspection(lockPath) {
|
|
|
15214
16046
|
return { kind: "live", lock: parsed };
|
|
15215
16047
|
}
|
|
15216
16048
|
async function readLiveLock(projectDir) {
|
|
15217
|
-
const lockPath =
|
|
16049
|
+
const lockPath = path9.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
|
|
15218
16050
|
const result = await readLockForInspection(lockPath);
|
|
15219
16051
|
if (result.kind === "live") {
|
|
15220
16052
|
return { kind: "live", lock: result.lock };
|
|
@@ -15225,15 +16057,15 @@ async function readLiveLock(projectDir) {
|
|
|
15225
16057
|
return { kind: "absent" };
|
|
15226
16058
|
}
|
|
15227
16059
|
async function atomicWriteJson(targetPath, value) {
|
|
15228
|
-
const dir =
|
|
15229
|
-
await
|
|
16060
|
+
const dir = path9.dirname(targetPath);
|
|
16061
|
+
await fsp9.mkdir(dir, { recursive: true });
|
|
15230
16062
|
const tmp = `${targetPath}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
15231
16063
|
const body = JSON.stringify(value, null, 2) + "\n";
|
|
15232
|
-
await
|
|
16064
|
+
await fsp9.writeFile(tmp, body, { mode: 384 });
|
|
15233
16065
|
try {
|
|
15234
|
-
await
|
|
16066
|
+
await fsp9.rename(tmp, targetPath);
|
|
15235
16067
|
} catch (err) {
|
|
15236
|
-
await
|
|
16068
|
+
await fsp9.unlink(tmp).catch(() => void 0);
|
|
15237
16069
|
throw err;
|
|
15238
16070
|
}
|
|
15239
16071
|
}
|
|
@@ -15697,11 +16529,11 @@ var AgentMonitorService = class {
|
|
|
15697
16529
|
this._onEntry?.(entry);
|
|
15698
16530
|
}
|
|
15699
16531
|
async _appendToFile(subagentId, entry) {
|
|
15700
|
-
const dir =
|
|
15701
|
-
await
|
|
15702
|
-
const filePath =
|
|
16532
|
+
const dir = path9.join(this._transcriptsDir, subagentId);
|
|
16533
|
+
await fsp9.mkdir(dir, { recursive: true });
|
|
16534
|
+
const filePath = path9.join(dir, "transcript.jsonl");
|
|
15703
16535
|
const line = JSON.stringify(entry) + "\n";
|
|
15704
|
-
await
|
|
16536
|
+
await fsp9.appendFile(filePath, line, { encoding: "utf8" });
|
|
15705
16537
|
}
|
|
15706
16538
|
_uid() {
|
|
15707
16539
|
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -16105,8 +16937,8 @@ var KnowledgeGraph = class {
|
|
|
16105
16937
|
return this.index;
|
|
16106
16938
|
}
|
|
16107
16939
|
constructor(sessionDir) {
|
|
16108
|
-
this.filePath =
|
|
16109
|
-
this.graphFilePath =
|
|
16940
|
+
this.filePath = path9.join(sessionDir, "_knowledge_graph");
|
|
16941
|
+
this.graphFilePath = path9.join(this.filePath, "graph.jsonl");
|
|
16110
16942
|
}
|
|
16111
16943
|
// ── Write ──────────────────────────────────────────────────────────────
|
|
16112
16944
|
/**
|
|
@@ -16300,23 +17132,23 @@ var KnowledgeGraph = class {
|
|
|
16300
17132
|
}
|
|
16301
17133
|
}
|
|
16302
17134
|
async _persist(node) {
|
|
16303
|
-
await
|
|
17135
|
+
await fsp9.mkdir(this.filePath, { recursive: true });
|
|
16304
17136
|
const line = JSON.stringify(node) + "\n";
|
|
16305
17137
|
await withFileLock(this.graphFilePath, async () => {
|
|
16306
|
-
await
|
|
17138
|
+
await fsp9.appendFile(this.graphFilePath, line, "utf8");
|
|
16307
17139
|
});
|
|
16308
17140
|
}
|
|
16309
17141
|
async _append(node) {
|
|
16310
|
-
await
|
|
17142
|
+
await fsp9.mkdir(this.filePath, { recursive: true });
|
|
16311
17143
|
const line = JSON.stringify({ op: "update", node }) + "\n";
|
|
16312
17144
|
await withFileLock(this.graphFilePath, async () => {
|
|
16313
|
-
await
|
|
17145
|
+
await fsp9.appendFile(this.graphFilePath, line, "utf8");
|
|
16314
17146
|
});
|
|
16315
17147
|
}
|
|
16316
17148
|
/** Rebuild in-memory state from the log file. Call on startup. */
|
|
16317
17149
|
async load() {
|
|
16318
17150
|
try {
|
|
16319
|
-
const content = await
|
|
17151
|
+
const content = await fsp9.readFile(this.graphFilePath, "utf8");
|
|
16320
17152
|
const lines = content.split("\n").filter(Boolean);
|
|
16321
17153
|
for (const line of lines) {
|
|
16322
17154
|
try {
|
|
@@ -17894,17 +18726,17 @@ ${input.detail}`
|
|
|
17894
18726
|
_waitForDagProgress(timeoutMs) {
|
|
17895
18727
|
const before = this._dagProgressKey();
|
|
17896
18728
|
if (this.dag.isDone()) return Promise.resolve();
|
|
17897
|
-
return new Promise((
|
|
18729
|
+
return new Promise((resolve9) => {
|
|
17898
18730
|
let off;
|
|
17899
18731
|
const timer = setTimeout(() => {
|
|
17900
18732
|
off?.();
|
|
17901
|
-
|
|
18733
|
+
resolve9();
|
|
17902
18734
|
}, timeoutMs);
|
|
17903
18735
|
off = this.dag.onEvent(() => {
|
|
17904
18736
|
if (this._dagProgressKey() === before) return;
|
|
17905
18737
|
clearTimeout(timer);
|
|
17906
18738
|
off?.();
|
|
17907
|
-
|
|
18739
|
+
resolve9();
|
|
17908
18740
|
});
|
|
17909
18741
|
});
|
|
17910
18742
|
}
|
|
@@ -18179,6 +19011,6 @@ ${input.detail}`
|
|
|
18179
19011
|
}
|
|
18180
19012
|
};
|
|
18181
19013
|
|
|
18182
|
-
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AdaptiveConcurrencyController, AgentMonitorService, AutonomousBrain, AutonomousCoordinator, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, ChangeManager, CollabSession, ConsensusProtocol, DECISION_TIMEOUT_MS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_QUALITY_CHECKS, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetSupervisor, FleetUsageAggregator, GlobalMailbox, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, KnowledgeGraph, LIGHT_BUDGET, LargeAnswerStore, MAILBOX_BRIDGE_LOCK_FILENAME, MAILBOX_BRIDGE_TOKEN_FILENAME, MAILBOX_HEALTH_DEFAULT_FAILURE_THRESHOLD, MAILBOX_HEALTH_DEFAULT_FROM, MAILBOX_HEALTH_DEFAULT_INTERVAL_MS, MAILBOX_HEALTH_DEFAULT_TIMEOUT_MS, MATRIX_PHASE_KEYS, MEDIUM_BUDGET, META_AGENTS, MailboxHealthWatchdog, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TaskAuctioneer, TaskDAG, VERIFY_AGENTS, WorktreeIntegrationError, acquireOrJoin, actionToAckInput, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, buildDownAlert, buildRecoveryAlert, composeDirectorPrompt, composeSubagentPrompt, createAgentMonitorService, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, finalize, formatHumanPrompt, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, isValidMatrixKey, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetTool as makeFleetHealthTool, makeFleetTool as makeFleetSessionTool, makeFleetStatusTool, makeFleetTool, makeFleetTool as makeFleetUsageTool, makeKanbanQueueTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeQualityGateTool, makeRollUpTool, makeSpawnTool, makeTerminateAllTool, makeTerminateTool, makeWorkCompleteTool, matrixKeyKind, normalizeRecipient, phaseForRole, readLiveLock, recordPackageAction, release, resolveImplementationModelTarget, resolveMailboxIdentity, resolveModelMatrix, resolveModelMatrixResolution, resolveModelTargetFromEntry, resolveProjectDir, resolveSubagentModelTarget, resolveSubagentWorktreeDecision, roleNeedsIndependentReviewModel, rosterSummaryFromConfigs, sameModelReference, scoreAgents, startPackageOutdatedWatcher, subagentNeedsWorktree, updatePackageOutdatedStatus, validateWatchdogOptions, withDisabledToolFiltering, wrapSubagentRunnerWithWorktrees };
|
|
19014
|
+
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AdaptiveConcurrencyController, AgentMonitorService, AutonomousBrain, AutonomousCoordinator, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, ChangeManager, CollabSession, ConsensusProtocol, DECISION_TIMEOUT_MS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_FLEET_SPAWNS, DEFAULT_QUALITY_CHECKS, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetSupervisor, FleetTokenCapError, FleetUsageAggregator, GlobalMailbox, HARD_MAX_SPAWN_DEPTH, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, KnowledgeGraph, LIGHT_BUDGET, LargeAnswerStore, MAILBOX_BRIDGE_LOCK_FILENAME, MAILBOX_BRIDGE_TOKEN_FILENAME, MAILBOX_HEALTH_DEFAULT_FAILURE_THRESHOLD, MAILBOX_HEALTH_DEFAULT_FROM, MAILBOX_HEALTH_DEFAULT_INTERVAL_MS, MAILBOX_HEALTH_DEFAULT_TIMEOUT_MS, MATRIX_PHASE_KEYS, MAX_SUBAGENT_STRUCTURED_REPORT_CHARS, MEDIUM_BUDGET, META_AGENTS, MailboxHealthWatchdog, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SUBAGENT_STRUCTURED_REPORT_META_KEY, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TaskAuctioneer, TaskDAG, VERIFY_AGENTS, WorktreeIntegrationError, acquireOrJoin, actionToAckInput, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, buildDownAlert, buildRecoveryAlert, composeDirectorPrompt, composeSubagentPrompt, createAgentMonitorService, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, finalize, formatHumanPrompt, formatSubagentStructuredReport, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, isValidMatrixKey, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetTool as makeFleetHealthTool, makeFleetTool as makeFleetSessionTool, makeFleetStatusTool, makeFleetTool, makeFleetTool as makeFleetUsageTool, makeKanbanQueueTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeQualityGateTool, makeRollUpTool, makeSpawnTool, makeSubagentResultTool, makeTerminateAllTool, makeTerminateTool, makeWorkCompleteTool, matrixKeyKind, normalizeRecipient, normalizeSubagentStructuredReport, phaseForRole, readLiveLock, readSubagentStructuredReport, recordPackageAction, release, resolveImplementationModelTarget, resolveMailboxIdentity, resolveMaxSpawnDepth, resolveModelMatrix, resolveModelMatrixResolution, resolveModelTargetFromEntry, resolveProjectDir, resolveSubagentModelTarget, resolveSubagentWorktreeDecision, roleNeedsIndependentReviewModel, rosterSummaryFromConfigs, sameModelReference, scoreAgents, startPackageOutdatedWatcher, subagentNeedsWorktree, updatePackageOutdatedStatus, validateWatchdogOptions, withDisabledToolFiltering, wrapSubagentRunnerWithWorktrees };
|
|
18183
19015
|
//# sourceMappingURL=index.js.map
|
|
18184
19016
|
//# sourceMappingURL=index.js.map
|