@wrongstack/core 0.283.1 → 0.284.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/{compactor-BxddyCFu.d.ts → compactor-DQLL4HTo.d.ts} +1 -1
  2. package/dist/{config-DkCbA3Bw.d.ts → config-MRqn1V-u.d.ts} +130 -22
  3. package/dist/coordination/index.d.ts +43 -20
  4. package/dist/coordination/index.js +1744 -912
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/{default-config-Buho8yge.d.ts → default-config-B79_gJYv.d.ts} +7 -0
  7. package/dist/defaults/index.d.ts +25 -29
  8. package/dist/defaults/index.js +2546 -7396
  9. package/dist/defaults/index.js.map +1 -1
  10. package/dist/{director-state-B_GPa78Q.d.ts → director-state-CMS_bMs4.d.ts} +2 -0
  11. package/dist/{events-KmxSmvho.d.ts → events-Bgnh43r9.d.ts} +27 -10
  12. package/dist/execution/index.d.ts +15 -16
  13. package/dist/execution/index.js +534 -140
  14. package/dist/execution/index.js.map +1 -1
  15. package/dist/execution/prompt-enhancer.d.ts +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{global-mailbox-rWVt8YlO.d.ts → global-mailbox-sZwyI6CS.d.ts} +13 -2
  18. package/dist/{goal-store-8owBXJT2.d.ts → goal-store-Datpp-ar.d.ts} +1 -1
  19. package/dist/hq/index.d.ts +6 -6
  20. package/dist/hq/index.js +58 -7
  21. package/dist/hq/index.js.map +1 -1
  22. package/dist/{index-BhCteHAF.d.ts → index--Gm41aBJ.d.ts} +26 -11
  23. package/dist/{index-BEFWNZYs.d.ts → index-CbyuOE5y.d.ts} +5 -1
  24. package/dist/index.d.ts +50 -1015
  25. package/dist/index.js +21028 -27775
  26. package/dist/index.js.map +1 -1
  27. package/dist/infrastructure/index.d.ts +9 -7
  28. package/dist/infrastructure/index.js +7 -2
  29. package/dist/infrastructure/index.js.map +1 -1
  30. package/dist/kernel/index.d.ts +11 -11
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{mcp-servers-BUx9A_AQ.d.ts → mcp-servers-DEV4s6Ks.d.ts} +3 -3
  33. package/dist/models/index.d.ts +4 -4
  34. package/dist/models/index.js +18 -3
  35. package/dist/models/index.js.map +1 -1
  36. package/dist/{agent-subagent-runner-BsuWhB28.d.ts → multi-agent-coordinator-4RCRZKjN.d.ts} +417 -9
  37. package/dist/{null-fleet-bus-D4e9R7Qc.d.ts → null-fleet-bus-CFoNVQEK.d.ts} +53 -12
  38. package/dist/observability/index.d.ts +3 -3
  39. package/dist/{parallel-eternal-engine-iY2uxocj.d.ts → parallel-eternal-engine-HUrtePLd.d.ts} +4 -5
  40. package/dist/{permission-B6YAtNFz.d.ts → permission-ByDKXEcG.d.ts} +1 -1
  41. package/dist/{pipeline-ixSFe5jg.d.ts → pipeline-DGDyIohT.d.ts} +28 -7
  42. package/dist/{provider-model-resolve-DIxKL_T-.d.ts → provider-model-resolve-o2hHLs79.d.ts} +3 -3
  43. package/dist/{provider-runner-oQhDaZmH.d.ts → provider-runner-ChL-kVg6.d.ts} +3 -3
  44. package/dist/{retry-policy-V67lBOJz.d.ts → retry-policy-CQ7KwXJa.d.ts} +2 -2
  45. package/dist/{secret-vault-D7jeyXUQ.d.ts → secret-vault-DxohkUBc.d.ts} +3 -2
  46. package/dist/security/index.d.ts +4 -4
  47. package/dist/security/index.js +232 -2
  48. package/dist/security/index.js.map +1 -1
  49. package/dist/{selector-Bx2HgUkU.d.ts → selector-D8O6B_Rm.d.ts} +1 -1
  50. package/dist/{session-event-bridge-Bkp7ORfc.d.ts → session-event-bridge-D_z_mBwk.d.ts} +3 -3
  51. package/dist/{session-reader-B-Fi9L6I.d.ts → session-reader-7Esxrkpw.d.ts} +1 -1
  52. package/dist/storage/index.d.ts +77 -39
  53. package/dist/storage/index.js +1223 -288
  54. package/dist/storage/index.js.map +1 -1
  55. package/dist/tasking/index.d.ts +124 -0
  56. package/dist/tasking/index.js +492 -0
  57. package/dist/tasking/index.js.map +1 -0
  58. package/dist/{todos-checkpoint-BwCrj4Cb.d.ts → todos-checkpoint-FY7PjmMx.d.ts} +13 -4
  59. package/dist/{tool-LVSLiE1s.d.ts → tool-BNlnIJvo.d.ts} +216 -17
  60. package/dist/{tool-executor-kElSEgO0.d.ts → tool-executor-CckSCM5E.d.ts} +12 -15
  61. package/dist/tools/index.d.ts +2 -2
  62. package/dist/tools/index.js.map +1 -1
  63. package/dist/types/index.d.ts +94 -19
  64. package/dist/types/index.js +76 -12
  65. package/dist/types/index.js.map +1 -1
  66. package/dist/utils/index.d.ts +44 -10
  67. package/dist/utils/index.js +106 -17
  68. package/dist/utils/index.js.map +1 -1
  69. package/dist/{worktree-manager-BjyAW30D.d.ts → worktree-manager-DzwxKE35.d.ts} +1 -1
  70. package/instructions/coordination/subagent-baseline.md +5 -0
  71. package/instructions/llm/autonomy-brain.md +3 -2
  72. package/instructions/system.md +4 -0
  73. package/package.json +10 -3
  74. package/dist/multi-agent-coordinator-CrjeTB9i.d.ts +0 -357
  75. package/dist/sdd/index.d.ts +0 -1993
  76. package/dist/sdd/index.js +0 -9500
  77. package/dist/sdd/index.js.map +0 -1
  78. package/dist/spec-TBi3Jr6T.d.ts +0 -78
@@ -1,28 +1,29 @@
1
1
  import { randomUUID, createHash, randomBytes } from 'crypto';
2
- import * as fsp7 from 'fs/promises';
3
- import * as path10 from 'path';
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 = path10.dirname(targetPath);
16
- await fsp7.mkdir(dir, { recursive: true });
17
- const tmp = path10.join(dir, `.${path10.basename(targetPath)}.${randomBytes(6).toString("hex")}.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 fsp7.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
21
+ await fsp9.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
21
22
  } else {
22
- await fsp7.writeFile(tmp, content, { flag: "wx" });
23
+ await fsp9.writeFile(tmp, content, { flag: "wx" });
23
24
  }
24
25
  try {
25
- const fh = await fsp7.open(tmp, "r+");
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 stat8 = await fsp7.stat(targetPath);
36
- mode = stat8.mode & 511;
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 fsp7.chmod(tmp, mode);
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 fsp7.chmod(targetPath, mode);
47
+ await fsp9.chmod(targetPath, mode);
47
48
  } catch {
48
49
  }
49
50
  }
50
51
  } catch (err) {
51
52
  try {
52
- await fsp7.unlink(tmp);
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 fsp7.mkdir(dir, { recursive: true });
60
+ await fsp9.mkdir(dir, { recursive: true });
60
61
  }
61
62
  async function withFileLock(targetPath, fn, opts = {}) {
62
- const dir = path10.dirname(targetPath);
63
- await fsp7.mkdir(dir, { recursive: true });
64
- const lockPath = path10.join(dir, `.${path10.basename(targetPath)}.lock`);
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 fsp7.open(lockPath, "wx");
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 fsp7.mkdir(dir, { recursive: true });
78
+ await fsp9.mkdir(dir, { recursive: true });
78
79
  continue;
79
80
  }
80
81
  if (code !== "EEXIST") throw err;
81
82
  try {
82
- const stat8 = await fsp7.stat(lockPath);
83
- if (Date.now() - stat8.mtimeMs > staleMs) {
84
- await fsp7.unlink(lockPath);
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((resolve7) => setTimeout(resolve7, 25));
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 fsp7.unlink(lockPath);
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 fsp7.rename(from, to);
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 fsp7.rename(from, to);
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((resolve7) => setTimeout(resolve7, delays[i]));
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 fsp7.readdir(dir);
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 stat8 = await fsp7.stat(full);
240
- if (stat8.isDirectory()) await walk(full, rest);
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 stat8 = await fsp7.stat(full);
258
- if (stat8.isDirectory()) await walk(full, rest);
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 = path10.resolve(dir, `${sessionId}${suffix}`);
400
- const rel = path10.relative(path10.resolve(dir), resolved);
401
- if (rel.startsWith("..") || path10.isAbsolute(rel)) {
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(path10.basename(absRoot));
446
- const hash = createHash("sha256").update(path10.resolve(absRoot)).digest("hex").slice(0, 6);
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 path10.resolve(fromEnv);
455
- return path10.join(os.homedir(), ".wrongstack");
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((resolve7, reject) => {
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: resolve7,
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((resolve7) => {
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
- resolve7(d);
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(path10.join(dir, fileName), "utf8").trimEnd();
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"] || path10.join(os.homedir(), ".wrongstack");
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 = path10.dirname(fileURLToPath(import.meta.url));
1712
+ const here = path9.dirname(fileURLToPath(import.meta.url));
1434
1713
  const explicitDir = envDir || void 0;
1435
1714
  const candidates = [
1436
- ...explicitDir ? [path10.resolve(explicitDir)] : [],
1437
- path10.join(globalRoot, "instructions", "agents"),
1438
- path10.resolve(here, "../../../../instructions/agents"),
1439
- path10.resolve(here, "../../../instructions/agents"),
1440
- path10.resolve(here, "../../instructions/agents"),
1441
- path10.resolve(here, "../instructions/agents"),
1442
- path10.resolve(here, "instructions/agents")
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((resolve7) => {
3059
- const entry = { request, resolve: resolve7 };
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
- resolve7({ type: "deny", reason: "Brain human decision timed out." });
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, stat8] = await Promise.all([
3383
- fsp7.readFile(filePath, "utf8"),
3384
- fsp7.stat(filePath)
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: stat8.mtimeMs,
3393
- snapshotSizeBytes: stat8.size
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 stat8 = await fsp7.stat(file.path);
3807
- const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat8.mtimeMs > file.snapshotMtimeMs + 1;
3808
- const sizeChanged = file.snapshotSizeBytes !== void 0 && stat8.size !== file.snapshotSizeBytes;
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((resolve7) => {
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
- resolve7(value);
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 : await readSubagentPartial(opts, subagentId);
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(path10.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
4819
+ candidates.push(path9.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
4428
4820
  } else {
4429
4821
  try {
4430
- const entries = await fsp7.readdir(opts.sessionsRoot, { withFileTypes: true });
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(path10.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
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 fsp7.readFile(file, "utf8");
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 basename7 = filePath.split("/").pop()?.split("\\").pop() ?? "";
4536
- if (plainPatterns.includes(basename7)) return true;
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(basename7)) return true;
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 fsp7.readFile(lockPath, "utf8");
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 fsp7.unlink(lockPath);
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(path10.join(root, relativePath), "utf8").trimEnd();
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 = path10.dirname(fileURLToPath(import.meta.url));
5287
+ const here = path9.dirname(fileURLToPath(import.meta.url));
4874
5288
  const candidates = [
4875
- path10.resolve(here, "../../instructions"),
4876
- path10.resolve(here, "../instructions"),
4877
- path10.resolve(here, "instructions")
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/kanban/types.ts
4952
- var DEFAULT_COLUMNS = [
4953
- { id: "backlog", title: "Backlog", order: 0, wipLimit: 0, color: "#64748b" },
4954
- { id: "todo", title: "To Do", order: 1, wipLimit: 0, color: "#2563eb" },
4955
- { id: "in-progress", title: "In Progress", order: 2, wipLimit: 5, color: "#d97706" },
4956
- { id: "review", title: "Review", order: 3, wipLimit: 0, color: "#7c3aed" },
4957
- { id: "done", title: "Done", order: 4, wipLimit: 0, color: "#16a34a" }
4958
- ];
4959
- var CURRENT_KANBAN_VERSION = 1;
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
- async function listBoardIds(projectRoot) {
5009
- const dir = getKanbanDir(projectRoot);
5010
- try {
5011
- const entries = await fsp7.readdir(dir, { withFileTypes: true });
5012
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -5)).filter(isValidBoardId).sort();
5013
- } catch (err) {
5014
- if (isEnoent(err)) return [];
5015
- throw err;
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
- async function resolveBoardRef(projectRoot, boardRef) {
5019
- assertValidBoardId(boardRef);
5020
- if (await pathExists(getKanbanPath(projectRoot, boardRef))) return boardRef;
5021
- const matches = (await listBoardIds(projectRoot)).filter((id) => id.startsWith(boardRef));
5022
- if (matches.length === 0) return null;
5023
- if (matches.length > 1) {
5024
- throw new Error(`Ambiguous kanban board id "${boardRef}": ${matches.slice(0, 5).join(", ")}`);
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 readBoard(projectRoot, boardRef) {
5029
- const boardId = await resolveBoardRef(projectRoot, boardRef);
5030
- if (!boardId) return null;
5031
- try {
5032
- const raw = await fsp7.readFile(getKanbanPath(projectRoot, boardId), "utf8");
5033
- return normalizeBoard(JSON.parse(raw));
5034
- } catch (err) {
5035
- if (isEnoent(err)) return null;
5036
- throw err;
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
- async function appendKanbanEvent(projectRoot, boardId, event) {
5040
- const filePath = getKanbanEventsPath(projectRoot, boardId);
5041
- await withFileLock(filePath, async () => {
5042
- await fsp7.mkdir(path10.dirname(filePath), { recursive: true });
5043
- await fsp7.appendFile(filePath, `${JSON.stringify(event)}
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 nowIso2() {
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: stringArray(raw.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: stringArray(raw.targets),
5867
- commands: stringArray(raw.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 stringArray(value) {
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 = nowIso2();
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: stringArray(raw.fallbackModels),
6343
- tools: stringArray(raw.tools),
6344
- allowedCapabilities: stringArray(raw.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. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
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.COORDINATION_FLEET_READ],
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: director.id,
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: providerStatusToCode(status, opts.body?.type),
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
- function providerStatusToCode(status, type) {
7185
- if (status === 0) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
7186
- if (type === "rate_limit_error" || status === 429) return ERROR_CODES.PROVIDER_RATE_LIMITED;
7187
- if (type === "authentication_error" || status === 401) return ERROR_CODES.PROVIDER_AUTH_FAILED;
7188
- if (type === "overloaded_error" || status === 529) return ERROR_CODES.PROVIDER_OVERLOADED;
7189
- if (type === "invalid_request_error" || status === 400) return ERROR_CODES.PROVIDER_INVALID_REQUEST;
7190
- if (status === 408) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
7191
- if (status >= 500) return ERROR_CODES.PROVIDER_SERVER_ERROR;
7192
- return ERROR_CODES.PROVIDER_INVALID_REQUEST;
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
- const status = err.status;
7442
- if (status === 429 || err.body?.type === "rate_limit_error") {
7443
- return { kind: "provider_rate_limit", message, retryable: true, backoffMs: 5e3, cause };
7444
- }
7445
- if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
7446
- return { kind: "provider_auth", message, retryable: false, cause };
7447
- }
7448
- if (status === 408 || status === 0) {
7449
- return { kind: "provider_timeout", message, retryable: true, cause };
7450
- }
7451
- if (status >= 500 && status < 600) {
7452
- return { kind: "provider_5xx", message, retryable: true, backoffMs: 3e3, cause };
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((resolve7, reject) => {
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
- resolve7(result);
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((resolve7) => {
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
- resolve7({
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
- resolve7({ completed: [], pending: [...taskIds], timedOut: true });
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 resolve7 = (d) => {
8151
+ const resolve9 = (d) => {
8158
8152
  if (settled) return;
8159
8153
  settled = true;
8160
8154
  resolveDecision(d);
8161
8155
  };
8162
- const fallback = setTimeout(() => resolve7("stop"), DECISION_TIMEOUT_MS);
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(() => resolve7({ extend: extra }));
8174
+ queueMicrotask(() => resolve9({ extend: extra }));
8181
8175
  },
8182
8176
  deny: () => {
8183
8177
  clearTimeout(fallback);
8184
- resolve7("stop");
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 ?? 2;
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 fsp7.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
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 fsp7.mkdir(path10.dirname(this.manifestPath), { recursive: true });
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 resolve7;
9697
+ let resolve9;
9658
9698
  const promise = new Promise((res) => {
9659
- resolve7 = res;
9699
+ resolve9 = res;
9660
9700
  });
9661
- this.taskWaiters.set(id, { promise, resolve: resolve7 });
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((resolve7) => {
9730
+ return new Promise((resolve9) => {
9691
9731
  const entry = {
9692
9732
  ids: new Set(taskIds),
9693
- resolve: (r) => resolve7({
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
- resolve7({ completed: [], pending: [...taskIds], timedOut: true });
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 = path10.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
9863
+ const filePath = path9.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
9824
9864
  let raw;
9825
9865
  try {
9826
- raw = await fsp7.readFile(filePath, "utf8");
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 ? path10.join(opts.dir, `${path10.basename(id)}.summary.json`) : "";
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 keeps
10071
- // exactly one write in flight; failures don't break the chain.
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` / `llm_response` carry free-form user/model text; other event
10094
- * types either have no secret-bearing content or are already scrubbed
10095
- * upstream (tool results). Returns the event unchanged when no scrubber is
10096
- * configured.
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.pendingFileSnapshots.push(input);
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
- const record = `${JSON.stringify({
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 and writes whatever has
10187
- * accumulated in the buffer. Safe to call even when the buffer
10188
- * is empty (no-op).
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
- * Errors use the same throttled-warning pattern the old per-event
10209
- * append path used one warning every 5s with a suppressed count.
10210
- * On failure the buffer is cleared (events are best-effort, same as
10211
- * the old per-event path where a failed write was silently dropped).
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
- async flushBuffer() {
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 eventCount = this.writeBuffer.length;
10216
- const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
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 fsp7.open(this.filePath, "r", 384);
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 fsp7.open(this.filePath, "r", 384);
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 fsp7.open(tmpPath, "w", 384);
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 fsp7.rename(tmpPath, this.filePath);
10532
- this.handle = await fsp7.open(this.filePath, "a", 384);
10705
+ await fsp9.rename(tmpPath, this.filePath);
10706
+ this.handle = await fsp9.open(this.filePath, "a", 384);
10533
10707
  } catch (err) {
10534
- await fsp7.unlink(tmpPath).catch(() => void 0);
10535
- this.handle = await fsp7.open(this.filePath, "a", 384).catch(() => this.handle);
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 fsp7.writeFile(this.filePath, record, "utf8");
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/storage-concurrency.ts
10649
- async function mapWithConcurrency(items, concurrency, mapper) {
10650
- if (items.length === 0) return [];
10651
- const limit = Math.max(1, Math.floor(concurrency));
10652
- const results = new Array(items.length);
10653
- let next = 0;
10654
- async function worker() {
10655
- while (true) {
10656
- const index = next;
10657
- next++;
10658
- if (index >= items.length) return;
10659
- results[index] = await mapper(items[index], index);
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
- const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());
10663
- await Promise.all(workers);
10664
- return results;
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 path10.join(this.dir, "_index.jsonl");
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 ? path10.join(this.dir, shardKey, "_manifest.json") : path10.join(this.dir, "_manifest.json");
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 = path10.dirname(id);
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 = path10.dirname(sessionScopedPath(this.dir, id, ""));
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 fsp7.open(file, "a", 384);
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 fsp7.open(file, "a", 384);
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: path10.dirname(file),
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 fsp7.stat(file);
10880
- const stat8 = { mtimeMs: s.mtimeMs, size: s.size };
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 === stat8.mtimeMs && cached.size === stat8.size) {
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 === "user_input") {
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: stat8.mtimeMs, size: stat8.size, data });
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 stat8;
11851
+ let stat9;
11048
11852
  try {
11049
- stat8 = await fsp7.stat(file);
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 (stat8.size === 0) return [];
11858
+ if (stat9.size === 0) return [];
11055
11859
  let fh;
11056
11860
  try {
11057
- fh = await fsp7.open(file, "r");
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 fsp7.appendFile(this.indexFile, line, "utf8");
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 fsp7.appendFile(this.indexFile, line, "utf8");
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 stat8;
12041
+ let stat9;
11238
12042
  try {
11239
- const s = await fsp7.stat(this.indexFile);
11240
- stat8 = { mtimeMs: s.mtimeMs, size: s.size };
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 === stat8.mtimeMs && this._indexCache.size === stat8.size) {
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 fsp7.readFile(this.indexFile, "utf8");
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 = { ...stat8, summaries };
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 fsp7.readdir(this.dir, { withFileTypes: true });
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 fsp7.readFile(manifestPath, "utf8");
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 ? path10.join(this.dir, shardKey) : this.dir;
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 fsp7.readdir(dir, { withFileTypes: true });
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: path10.join(dir, entry.name) });
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(path10.join(dir, entry.name), childPrefix, depth + 1);
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 fsp7.readdir(dir, { withFileTypes: true });
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(path10.join(dir, entry.name), childPrefix, depth + 1);
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 stat8 = await fsp7.stat(full);
11446
- const summary = await this.summarize(id, stat8.mtime.toISOString());
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 fsp7.readFile(manifest, "utf8");
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 stat8 = await fsp7.stat(ref.filePath);
11490
- if (!stat8.isFile()) {
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: stat8.mtime.toISOString(),
12298
+ startedAt: stat9.mtime.toISOString(),
11495
12299
  model: "unknown",
11496
12300
  provider: "unknown",
11497
12301
  tokenTotal: 0
11498
12302
  };
11499
12303
  }
11500
- mtime = stat8.mtime.toISOString();
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 = path10.dirname(jsonlPath);
11549
- const base = path10.basename(id);
11550
- const sessDir = path10.join(shardDir, base);
12352
+ const shardDir = path9.dirname(jsonlPath);
12353
+ const base = path9.basename(id);
12354
+ const sessDir = path9.join(shardDir, base);
11551
12355
  const deletions = [
11552
- fsp7.unlink(jsonlPath),
11553
- fsp7.unlink(summaryPath),
11554
- fsp7.unlink(sessionScopedPath(this.dir, id, ".plan.json")),
11555
- fsp7.unlink(sessionScopedPath(this.dir, id, ".tasks.json")),
11556
- fsp7.unlink(sessionScopedPath(this.dir, id, ".todos.json"))
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 fsp7.rm(sessDir, { recursive: true, force: true }).catch((err) => {
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 fsp7.readFile(path10.join(this.dir, "active.json"), "utf8");
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 fsp7.stat(jsonlPath);
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 = path10.join(dir, name);
12458
+ const jsonlPath = path9.join(dir, name);
11655
12459
  try {
11656
- const stat8 = await fsp7.stat(jsonlPath);
11657
- if (stat8.mtimeMs >= cutoff) return;
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 fsp7.readdir(this.dir, { withFileTypes: true }).catch(() => []);
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 = path10.join(this.dir, entry.name);
11675
- const files = await fsp7.readdir(dateDir, { withFileTypes: true }).catch(() => []);
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 = path10.join(this.dir, entry.name);
12490
+ const dateDir = path9.join(this.dir, entry.name);
11687
12491
  try {
11688
- const remaining = await fsp7.readdir(dateDir);
12492
+ const remaining = await fsp9.readdir(dateDir);
11689
12493
  if (remaining.length === 0) {
11690
- await fsp7.rmdir(dateDir).catch(() => void 0);
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 fsp7.writeFile(file, record, "utf8");
11710
- await fsp7.unlink(meta).catch(() => void 0);
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 ? path10.join(opts.sessionsRoot, runId) : "(caller-managed)";
12621
+ dir = opts.sessionsRoot ? path9.join(opts.sessionsRoot, runId) : "(caller-managed)";
11818
12622
  } else if (opts.sessionsRoot) {
11819
- dir = path10.join(opts.sessionsRoot, runId);
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 ?? 2;
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 fsp7.mkdir(path10.dirname(this.manifestPath), { recursive: true });
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 path10.join(globalRoot, "projects", projectSlug(projectRoot));
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 = path10.join(projectDir, MAILBOX_FILE);
12293
- this.registryPath = path10.join(projectDir, "_mailbox.registry.json");
12294
- this.clientRegistryPath = path10.join(projectDir, CLIENT_REGISTRY_FILE);
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 `${path10.basename(path10.dirname(this.messagePath))}:mailbox`;
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 fsp7.mkdir(path10.dirname(this.messagePath), { recursive: true });
13166
+ await fsp9.mkdir(path9.dirname(this.messagePath), { recursive: true });
12337
13167
  await withFileLock(this.messagePath, async () => {
12338
- await fsp7.appendFile(this.messagePath, line, "utf8");
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 fsp7.writeFile(this.messagePath, serialized, "utf8");
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 fsp7.writeFile(this.messagePath, serialized, "utf8");
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 fsp7.writeFile(this.messagePath, serialized, "utf8");
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 fsp7.writeFile(this.messagePath, "", "utf8");
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 fsp7.writeFile(this.messagePath, content, "utf8");
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 fsp7.readFile(this.messagePath, "utf8");
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 fsp7.stat(this.messagePath);
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 fsp7.stat(this.messagePath);
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 fsp7.open(this.messagePath, "r");
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 fsp7.mkdir(path10.dirname(this.registryPath), { recursive: true });
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 fsp7.readFile(this.registryPath, "utf8");
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 fsp7.writeFile(tmp, JSON.stringify(obj), "utf8");
13048
- await fsp7.rename(tmp, this.registryPath);
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 fsp7.mkdir(path10.dirname(this.clientRegistryPath), { recursive: true });
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 fsp7.readFile(this.clientRegistryPath, "utf8");
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 fsp7.writeFile(tmp, JSON.stringify(obj), "utf8");
13093
- await fsp7.rename(tmp, this.clientRegistryPath);
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 tool.executed timestamp (fleet-wide activity map). */
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.filter("tool.executed", (e) => {
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.length, live);
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 = decision.optionId ?? options.find((o) => o.recommended)?.id ?? null;
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(pendingCount, liveWorkers) {
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 tool activity for \u2265${Math.round(this.cfg.stuckMs / 1e3)}s`,
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 tool activity for over ${Math.round(this.cfg.stuckMs / 1e3)}s. Nudge it with a steer message?`,
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 tool 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.`
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 tool activity for ${Math.round(this.cfg.stuckMs / 1e3)}s. Supervisor nudged it; consider terminate_subagent + reassigning if it stays silent.`
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 = path10.join(sessionDir, MAILBOX_FILE2);
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 fsp7.mkdir(path10.dirname(this.filePath), { recursive: true });
15049
+ await fsp9.mkdir(path9.dirname(this.filePath), { recursive: true });
14218
15050
  await withFileLock(this.filePath, async () => {
14219
- await fsp7.appendFile(this.filePath, line, "utf8");
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 fsp7.writeFile(this.filePath, serialized, "utf8");
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 fsp7.writeFile(this.filePath, "", "utf8");
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 fsp7.writeFile(this.filePath, content, "utf8");
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 fsp7.readFile(this.filePath, "utf8");
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 fsp7.stat(this.filePath);
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 fsp7.stat(this.filePath);
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 fsp7.open(this.filePath, "r");
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 path10.join(storageDir, LOG_FILENAME);
15701
+ return path9.join(storageDir, LOG_FILENAME);
14870
15702
  }
14871
15703
  async function loadLog(storageDir, projectRoot) {
14872
15704
  try {
14873
- const raw = await fsp7.readFile(logPath(storageDir), "utf-8");
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 fsp7.mkdir(storageDir, { recursive: true });
15719
+ await fsp9.mkdir(storageDir, { recursive: true });
14888
15720
  const tmp = `${logPath(storageDir)}.tmp.${Date.now()}`;
14889
- await fsp7.writeFile(tmp, JSON.stringify(log, null, 2) + "\n", "utf-8");
14890
- await fsp7.rename(tmp, logPath(storageDir));
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 = path10.basename(manifestPath).toLowerCase();
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 = path10.join(opts.projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
15142
- const tokenPath = path10.join(opts.projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
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 fsp7.unlink(lockPath).catch(() => void 0);
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 = path10.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
15171
- const tokenPath = path10.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
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 fsp7.writeFile(tokenPath, finalized.token, { mode: 384 });
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 = path10.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
15183
- const tokenPath = path10.join(projectDir, MAILBOX_BRIDGE_TOKEN_FILENAME);
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 fsp7.readFile(lockPath, "utf-8");
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 fsp7.unlink(lockPath).catch(() => void 0);
15190
- await fsp7.unlink(tokenPath).catch(() => void 0);
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 fsp7.readFile(lockPath, "utf-8");
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 fsp7.unlink(lockPath).catch(() => void 0);
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 = path10.join(projectDir, MAILBOX_BRIDGE_LOCK_FILENAME);
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 = path10.dirname(targetPath);
15229
- await fsp7.mkdir(dir, { recursive: true });
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 fsp7.writeFile(tmp, body, { mode: 384 });
16064
+ await fsp9.writeFile(tmp, body, { mode: 384 });
15233
16065
  try {
15234
- await fsp7.rename(tmp, targetPath);
16066
+ await fsp9.rename(tmp, targetPath);
15235
16067
  } catch (err) {
15236
- await fsp7.unlink(tmp).catch(() => void 0);
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 = path10.join(this._transcriptsDir, subagentId);
15701
- await fsp7.mkdir(dir, { recursive: true });
15702
- const filePath = path10.join(dir, "transcript.jsonl");
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 fsp7.appendFile(filePath, line, { encoding: "utf8" });
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 = path10.join(sessionDir, "_knowledge_graph");
16109
- this.graphFilePath = path10.join(this.filePath, "graph.jsonl");
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 fsp7.mkdir(this.filePath, { recursive: true });
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 fsp7.appendFile(this.graphFilePath, line, "utf8");
17138
+ await fsp9.appendFile(this.graphFilePath, line, "utf8");
16307
17139
  });
16308
17140
  }
16309
17141
  async _append(node) {
16310
- await fsp7.mkdir(this.filePath, { recursive: true });
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 fsp7.appendFile(this.graphFilePath, line, "utf8");
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 fsp7.readFile(this.graphFilePath, "utf8");
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((resolve7) => {
18729
+ return new Promise((resolve9) => {
17898
18730
  let off;
17899
18731
  const timer = setTimeout(() => {
17900
18732
  off?.();
17901
- resolve7();
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
- resolve7();
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