@wrongstack/core 0.87.0 → 0.89.3

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 (83) hide show
  1. package/dist/{agent-bridge-C9P_HPez.d.ts → agent-bridge-DbVe1dHT.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-2Aq0jOSj.d.ts → agent-subagent-runner-C6eqID1t.d.ts} +477 -476
  3. package/dist/{events-DnRqXaZ3.d.ts → brain-s9DyD_Vf.d.ts} +185 -92
  4. package/dist/{compactor-CJq7LQev.d.ts → compactor-DXLxLcmU.d.ts} +2 -2
  5. package/dist/{config-_DZ7dN-T.d.ts → config-84VaZpH6.d.ts} +45 -38
  6. package/dist/{context-ToHAp4-U.d.ts → context-CNRYfhUv.d.ts} +1 -1
  7. package/dist/coordination/index.d.ts +16 -17
  8. package/dist/coordination/index.js +208 -93
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/{default-config-DEXI4jsl.d.ts → default-config-CXsDvOmP.d.ts} +3 -1
  11. package/dist/defaults/index.d.ts +32 -34
  12. package/dist/defaults/index.js +744 -274
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/{director-state-CgIc30qi.d.ts → director-state-BfeCUbmk.d.ts} +1 -1
  15. package/dist/execution/index.d.ts +18 -19
  16. package/dist/execution/index.js +79 -64
  17. package/dist/execution/index.js.map +1 -1
  18. package/dist/extension/index.d.ts +9 -10
  19. package/dist/extension/index.js +8 -6
  20. package/dist/extension/index.js.map +1 -1
  21. package/dist/{index-BNOLadHw.d.ts → index-CqrroYcU.d.ts} +13 -15
  22. package/dist/{index-N0_c4bHQ.d.ts → index-D3Nax3YU.d.ts} +11 -10
  23. package/dist/index.d.ts +191 -178
  24. package/dist/index.js +1419 -526
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +9 -9
  27. package/dist/infrastructure/index.js +16 -5
  28. package/dist/infrastructure/index.js.map +1 -1
  29. package/dist/kernel/index.d.ts +14 -16
  30. package/dist/kernel/index.js +17 -0
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{logger-B72yyPc6.d.ts → logger-B63L5bTg.d.ts} +1 -1
  33. package/dist/{logger-C_27pj9i.d.ts → logger-B9J5puGM.d.ts} +1 -1
  34. package/dist/{mcp-servers-Dck3T85_.d.ts → mcp-servers-D3E5ixAR.d.ts} +3 -3
  35. package/dist/{mode-CHo2XtHs.d.ts → mode-ARA3HrkY.d.ts} +1 -1
  36. package/dist/models/index.d.ts +5 -5
  37. package/dist/models/index.js +8 -6
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-Be3osGt5.d.ts → models-registry-B6_KfS65.d.ts} +1 -1
  40. package/dist/{models-registry-Boz639EI.d.ts → models-registry-DU64QxQa.d.ts} +1 -1
  41. package/dist/{multi-agent-coordinator-DllpCVkF.d.ts → multi-agent-coordinator-DHNrjPaA.d.ts} +2 -2
  42. package/dist/{null-fleet-bus-BY0AN-sr.d.ts → null-fleet-bus-BzzciAc4.d.ts} +39 -32
  43. package/dist/observability/index.d.ts +3 -3
  44. package/dist/{observability-CoSNZdhX.d.ts → observability-D-HZN_mF.d.ts} +1 -1
  45. package/dist/{parallel-eternal-engine-D402RASp.d.ts → parallel-eternal-engine-CTpcrb9O.d.ts} +34 -33
  46. package/dist/{path-resolver-UPFTsDyD.d.ts → path-resolver-B7hgMAVe.d.ts} +3 -3
  47. package/dist/{permission-14CChMmO.d.ts → permission-BDv7z0mk.d.ts} +7 -2
  48. package/dist/{permission-policy-gW5htOo1.d.ts → permission-policy-dF74EpDp.d.ts} +2 -2
  49. package/dist/{system-prompt-C0rLCeyn.d.ts → pipeline-C1Ad9OjI.d.ts} +73 -73
  50. package/dist/{plan-templates-DRvPgkfZ.d.ts → plan-templates-CNphsz0n.d.ts} +79 -11
  51. package/dist/{provider-runner-COAJM8tC.d.ts → provider-runner-cqvlB_oS.d.ts} +5 -5
  52. package/dist/{retry-policy-DSu6O6rD.d.ts → retry-policy-BcmuT_V0.d.ts} +2 -2
  53. package/dist/sdd/index.d.ts +12 -33
  54. package/dist/sdd/index.js +110 -50
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/{secret-scrubber-yGBFQYju.d.ts → secret-vault-DrOhc2i5.d.ts} +7 -7
  57. package/dist/security/index.d.ts +4 -5
  58. package/dist/security/index.js +22 -4
  59. package/dist/security/index.js.map +1 -1
  60. package/dist/{selector-11-fm95U.d.ts → selector-C7wcdqMA.d.ts} +1 -1
  61. package/dist/{session-event-bridge-D0u-x576.d.ts → session-event-bridge-IVzs2GpE.d.ts} +1 -1
  62. package/dist/{session-reader-BQU-toaN.d.ts → session-reader-DDz1Ek4V.d.ts} +2 -2
  63. package/dist/{skill-BJeF2DwY.d.ts → skill-BJxzIyyN.d.ts} +1 -1
  64. package/dist/skills/index.d.ts +1 -1
  65. package/dist/skills/index.js +3 -9
  66. package/dist/skills/index.js.map +1 -1
  67. package/dist/storage/index.d.ts +134 -14
  68. package/dist/storage/index.js +982 -248
  69. package/dist/storage/index.js.map +1 -1
  70. package/dist/{task-graph-CikNdRTG.d.ts → task-graph-DJBqO0EY.d.ts} +1 -1
  71. package/dist/types/index.d.ts +26 -28
  72. package/dist/types/index.js +62 -37
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/utils/expect-defined.d.ts +7 -0
  75. package/dist/utils/expect-defined.js +11 -0
  76. package/dist/utils/expect-defined.js.map +1 -0
  77. package/dist/utils/index.d.ts +11 -20
  78. package/dist/utils/index.js +61 -43
  79. package/dist/utils/index.js.map +1 -1
  80. package/dist/{wstack-paths-BQMvEllz.d.ts → wstack-paths-_lqjzErq.d.ts} +1 -1
  81. package/package.json +5 -1
  82. package/dist/memory-CEXuo7sz.d.ts +0 -16
  83. package/dist/secret-scrubber-3MHDDAtm.d.ts +0 -6
@@ -5,18 +5,13 @@ import * as path3 from 'path';
5
5
  import { isAbsolute, resolve } from 'path';
6
6
  import * as fs5 from 'fs';
7
7
  import * as os from 'os';
8
+ import { hostname } from 'os';
8
9
  import { execFile } from 'child_process';
9
10
  import { promisify } from 'util';
10
11
  import { EventEmitter } from 'events';
11
12
 
12
13
  var __defProp = Object.defineProperty;
13
14
  var __getOwnPropNames = Object.getOwnPropertyNames;
14
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
15
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
16
- }) : x)(function(x) {
17
- if (typeof require !== "undefined") return require.apply(this, arguments);
18
- throw Error('Dynamic require of "' + x + '" is not supported');
19
- });
20
15
  var __esm = (fn, res) => function __init() {
21
16
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
22
17
  };
@@ -29,7 +24,8 @@ var __export = (target, all) => {
29
24
  var atomic_write_exports = {};
30
25
  __export(atomic_write_exports, {
31
26
  atomicWrite: () => atomicWrite,
32
- ensureDir: () => ensureDir
27
+ ensureDir: () => ensureDir,
28
+ withFileLock: () => withFileLock
33
29
  });
34
30
  async function atomicWrite(targetPath, content, opts = {}) {
35
31
  const dir = path3.dirname(targetPath);
@@ -52,8 +48,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
52
48
  }
53
49
  let mode;
54
50
  try {
55
- const stat5 = await fsp.stat(targetPath);
56
- mode = stat5.mode & 511;
51
+ const stat6 = await fsp.stat(targetPath);
52
+ mode = stat6.mode & 511;
57
53
  } catch {
58
54
  mode = opts.mode;
59
55
  }
@@ -72,6 +68,49 @@ async function atomicWrite(targetPath, content, opts = {}) {
72
68
  async function ensureDir(dir) {
73
69
  await fsp.mkdir(dir, { recursive: true });
74
70
  }
71
+ async function withFileLock(targetPath, fn, opts = {}) {
72
+ const dir = path3.dirname(targetPath);
73
+ await fsp.mkdir(dir, { recursive: true });
74
+ const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
75
+ const timeoutMs = opts.timeoutMs ?? 5e3;
76
+ const staleMs = opts.staleMs ?? 3e4;
77
+ const started = Date.now();
78
+ let handle;
79
+ for (; ; ) {
80
+ try {
81
+ handle = await fsp.open(lockPath, "wx");
82
+ await handle.writeFile(`${process.pid}:${Date.now()}`);
83
+ break;
84
+ } catch (err) {
85
+ if (err.code !== "EEXIST") throw err;
86
+ try {
87
+ const stat6 = await fsp.stat(lockPath);
88
+ if (Date.now() - stat6.mtimeMs > staleMs) {
89
+ await fsp.unlink(lockPath);
90
+ continue;
91
+ }
92
+ } catch {
93
+ continue;
94
+ }
95
+ if (Date.now() - started >= timeoutMs) {
96
+ throw new Error(`Timed out waiting for file lock: ${targetPath}`);
97
+ }
98
+ await new Promise((resolve5) => setTimeout(resolve5, 25));
99
+ }
100
+ }
101
+ try {
102
+ return await fn();
103
+ } finally {
104
+ try {
105
+ await handle?.close();
106
+ } catch {
107
+ }
108
+ try {
109
+ await fsp.unlink(lockPath);
110
+ } catch {
111
+ }
112
+ }
113
+ }
75
114
  async function renameWithRetry(from, to) {
76
115
  if (process.platform !== "win32") {
77
116
  await fsp.rename(from, to);
@@ -119,10 +158,15 @@ function writeErr(s, stream = process.stderr) {
119
158
 
120
159
  // src/utils/color.ts
121
160
  var isColorTty = () => {
122
- if (process.env.NO_COLOR) return false;
123
- if (process.env.FORCE_COLOR) return true;
161
+ if (envFlag(process.env.NO_COLOR)) return false;
162
+ if (envFlag(process.env.FORCE_COLOR)) return true;
124
163
  return isStdoutTTY();
125
164
  };
165
+ function envFlag(value) {
166
+ if (value === void 0) return false;
167
+ if (value.trim() === "") return false;
168
+ return !/^(0|false|no|off)$/i.test(value.trim());
169
+ }
126
170
  var COLOR = isColorTty();
127
171
  var wrap = (open3, close) => (s) => COLOR ? `\x1B[${open3}m${s}\x1B[${close}m` : s;
128
172
  var color = {
@@ -159,6 +203,7 @@ var COLORS = {
159
203
  debug: color.gray,
160
204
  trace: color.dim
161
205
  };
206
+ var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
162
207
  var DefaultLogger = class _DefaultLogger {
163
208
  level;
164
209
  file;
@@ -166,7 +211,7 @@ var DefaultLogger = class _DefaultLogger {
166
211
  pretty;
167
212
  stderr;
168
213
  constructor(opts = {}) {
169
- this.level = opts.level ?? process.env.WRONGSTACK_LOG_LEVEL ?? "info";
214
+ this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
170
215
  this.file = opts.file;
171
216
  this.bindings = opts.bindings ?? {};
172
217
  this.pretty = opts.pretty ?? true;
@@ -231,6 +276,9 @@ var DefaultLogger = class _DefaultLogger {
231
276
  }
232
277
  }
233
278
  };
279
+ function parseLogLevel(raw) {
280
+ return raw && LOG_LEVELS.has(raw) ? raw : "info";
281
+ }
234
282
  function formatCtx(ctx) {
235
283
  if (ctx instanceof Error) return color.dim(ctx.message);
236
284
  if (typeof ctx === "string") return color.dim(ctx);
@@ -241,16 +289,18 @@ function formatCtx(ctx) {
241
289
  }
242
290
  }
243
291
 
244
- // src/storage/session-store.ts
245
- init_atomic_write();
246
-
247
- // src/utils/message-invariants.ts
248
- function expectDefined(value) {
292
+ // src/utils/expect-defined.ts
293
+ function expectDefined(value, label) {
249
294
  if (value === null || value === void 0) {
250
295
  throw new Error("Expected value to be defined");
251
296
  }
252
297
  return value;
253
298
  }
299
+
300
+ // src/storage/session-store.ts
301
+ init_atomic_write();
302
+
303
+ // src/utils/message-invariants.ts
254
304
  function repairToolUseAdjacency(messages) {
255
305
  const removedToolUses = [];
256
306
  const removedToolResults = [];
@@ -343,12 +393,6 @@ function isEmptyMessage(msg) {
343
393
  }
344
394
 
345
395
  // src/storage/session-store.ts
346
- function expectDefined2(value) {
347
- if (value === null || value === void 0) {
348
- throw new Error("Expected value to be defined");
349
- }
350
- return value;
351
- }
352
396
  function sanitizeModel(model) {
353
397
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
354
398
  }
@@ -614,8 +658,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
614
658
  return JSON.parse(raw);
615
659
  } catch {
616
660
  const full = this.sessionPath(id, ".jsonl");
617
- const stat5 = await fsp.stat(full);
618
- const summary = await this.summarize(id, stat5.mtime.toISOString());
661
+ const stat6 = await fsp.stat(full);
662
+ const summary = await this.summarize(id, stat6.mtime.toISOString());
619
663
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
620
664
  console.warn(
621
665
  `[session-store] Failed to write manifest for "${id}":`,
@@ -663,8 +707,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
663
707
  if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
664
708
  const jsonlPath = path3.join(dateDir, file.name);
665
709
  try {
666
- const stat5 = await fsp.stat(jsonlPath);
667
- if (stat5.mtimeMs >= cutoff) continue;
710
+ const stat6 = await fsp.stat(jsonlPath);
711
+ if (stat6.mtimeMs >= cutoff) continue;
668
712
  } catch {
669
713
  continue;
670
714
  }
@@ -1059,7 +1103,7 @@ var FileSessionWriter = class {
1059
1103
  let targetCheckpointLine = -1;
1060
1104
  let afterTarget = false;
1061
1105
  for (let i = 0; i < lines.length; i++) {
1062
- const line = expectDefined2(lines[i]);
1106
+ const line = expectDefined(lines[i]);
1063
1107
  if (!line.trim()) continue;
1064
1108
  let event;
1065
1109
  try {
@@ -1355,11 +1399,235 @@ function mergeAdjacentText(blocks) {
1355
1399
  return out;
1356
1400
  }
1357
1401
 
1358
- // src/storage/memory-store.ts
1402
+ // src/types/memory.ts
1403
+ var MEMORY_TYPE_LABELS = {
1404
+ fact: "Fact",
1405
+ decision: "Decision",
1406
+ convention: "Convention",
1407
+ preference: "Preference",
1408
+ reference: "Reference",
1409
+ anti_pattern: "Anti-pattern"
1410
+ };
1411
+
1412
+ // src/storage/memory-backend.ts
1359
1413
  init_atomic_write();
1414
+ var TYPE_PRIORITY_RE = /^\[(\w+)\|(\w+)\]\s+/;
1415
+ var TAG_RE = /#([\w-]+)/g;
1416
+ function formatMetadata(entry) {
1417
+ const parts = [];
1418
+ if (entry.type && entry.priority) {
1419
+ parts.push(`[${entry.type}|${entry.priority}]`);
1420
+ } else if (entry.type) {
1421
+ parts.push(`[${entry.type}]`);
1422
+ } else if (entry.priority) {
1423
+ parts.push(`[${entry.priority}]`);
1424
+ }
1425
+ if (entry.tags && entry.tags.length > 0) {
1426
+ parts.push(entry.tags.map((t) => `#${t}`).join(" "));
1427
+ }
1428
+ return parts.length > 0 ? ` ${parts.join(" ")}` : "";
1429
+ }
1430
+ function lineToEntry(line, scope) {
1431
+ const trimmed = line.trim();
1432
+ if (!trimmed.startsWith("- [")) return null;
1433
+ const tsMatch = trimmed.match(/^-\s*\[([^\]]+)\]/);
1434
+ if (!tsMatch) return null;
1435
+ const ts = tsMatch[1] ?? "";
1436
+ let rest = trimmed.slice(tsMatch[0].length).trim();
1437
+ let type;
1438
+ let priority;
1439
+ const tpMatch = rest.match(TYPE_PRIORITY_RE);
1440
+ if (tpMatch) {
1441
+ const a = tpMatch[1] ?? "";
1442
+ const b = tpMatch[2] ?? "";
1443
+ if (isMemoryType(a)) {
1444
+ type = a;
1445
+ priority = isPriority(b) ? b : void 0;
1446
+ } else if (isPriority(a)) {
1447
+ priority = a;
1448
+ }
1449
+ rest = rest.slice(tpMatch[0].length).trim();
1450
+ }
1451
+ const idMatch = rest.match(/^mem_\d+_\w+\s+/);
1452
+ let text;
1453
+ if (idMatch) {
1454
+ text = rest.slice(idMatch[0].length).trim();
1455
+ } else {
1456
+ text = rest.trim();
1457
+ }
1458
+ const tags = [];
1459
+ let tagMatch;
1460
+ TAG_RE.lastIndex = 0;
1461
+ while ((tagMatch = TAG_RE.exec(text)) !== null) {
1462
+ tags.push(tagMatch[1] ?? "");
1463
+ }
1464
+ const cleanText = text.replace(TAG_RE, "").replace(/\s{2,}/g, " ").trim();
1465
+ if (!cleanText) return null;
1466
+ return {
1467
+ scope,
1468
+ text: cleanText,
1469
+ ts,
1470
+ type,
1471
+ priority,
1472
+ tags: tags.length > 0 ? tags : void 0
1473
+ };
1474
+ }
1475
+ function isMemoryType(s) {
1476
+ return s in MEMORY_TYPE_LABELS;
1477
+ }
1478
+ function isPriority(s) {
1479
+ return s === "critical" || s === "high" || s === "medium" || s === "low";
1480
+ }
1481
+ var FileMemoryBackend = class {
1482
+ kind = "file";
1483
+ files;
1484
+ constructor(opts) {
1485
+ this.files = {
1486
+ "project-agents": opts.paths.inProjectAgentsFile,
1487
+ "project-memory": opts.paths.projectMemory,
1488
+ "user-memory": opts.paths.globalMemory
1489
+ };
1490
+ }
1491
+ resolveFile(filePath, scope) {
1492
+ return filePath || this.files[scope];
1493
+ }
1494
+ async remember(scope, entry, filePath) {
1495
+ const file = this.resolveFile(filePath, scope);
1496
+ await ensureDir(path3.dirname(file));
1497
+ let existing = "";
1498
+ try {
1499
+ existing = await fsp.readFile(file, "utf8");
1500
+ } catch {
1501
+ }
1502
+ const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
1503
+ const meta = formatMetadata(entry);
1504
+ const line = `
1505
+ - [${entry.ts}] ${id}${meta} ${entry.text.replace(/\n/g, " ")}
1506
+ `;
1507
+ const next = existing.trim() ? existing.replace(/\n+$/, "") + line : `# WrongStack Memory
1508
+ ${line}`;
1509
+ await atomicWrite(file, next);
1510
+ }
1511
+ async forget(scope, query, filePath) {
1512
+ const file = this.resolveFile(filePath, scope);
1513
+ let existing;
1514
+ try {
1515
+ existing = await fsp.readFile(file, "utf8");
1516
+ } catch {
1517
+ return 0;
1518
+ }
1519
+ const needle = query.toLowerCase();
1520
+ const idMatcher = /mem_\d+_\w+/;
1521
+ let removed = 0;
1522
+ const lines = existing.split("\n").filter((line) => {
1523
+ const trimmed = line.trim();
1524
+ if (!trimmed.startsWith("- ")) return true;
1525
+ if (idMatcher.test(query)) {
1526
+ const entryIdMatch = /mem_\d+_\w+/.exec(trimmed);
1527
+ if (entryIdMatch && entryIdMatch[0] === query) {
1528
+ removed++;
1529
+ return false;
1530
+ }
1531
+ }
1532
+ if (trimmed.toLowerCase().includes(needle)) {
1533
+ removed++;
1534
+ return false;
1535
+ }
1536
+ return true;
1537
+ });
1538
+ if (removed > 0) {
1539
+ if (lines.length === 0 || lines.length === 1 && !lines[0]?.trim()) {
1540
+ await atomicWrite(file, "");
1541
+ } else {
1542
+ await atomicWrite(file, lines.join("\n"));
1543
+ }
1544
+ }
1545
+ return removed;
1546
+ }
1547
+ async readAll(scope, filePath) {
1548
+ const file = this.resolveFile(filePath, scope);
1549
+ try {
1550
+ return await fsp.readFile(file, "utf8");
1551
+ } catch {
1552
+ return "";
1553
+ }
1554
+ }
1555
+ async list(scope, filePath, limit) {
1556
+ const raw = await this.readAll(scope, filePath);
1557
+ if (!raw.trim()) return [];
1558
+ const entries = parseEntries(raw, scope);
1559
+ return limit ? entries.slice(0, limit) : entries;
1560
+ }
1561
+ async search(scope, query, filePath, limit) {
1562
+ const entries = await this.list(scope, filePath);
1563
+ const needle = query.toLowerCase().split(/\s+/);
1564
+ const scored = entries.map((e) => {
1565
+ const words = e.text.toLowerCase().split(/\s+/);
1566
+ let score = 0;
1567
+ for (const n of needle) {
1568
+ if (words.some((w) => w.includes(n))) score += 1;
1569
+ if (e.tags?.some((t) => t.toLowerCase().includes(n))) score += 2;
1570
+ }
1571
+ return { entry: e, score };
1572
+ });
1573
+ scored.sort((a, b) => b.score - a.score);
1574
+ const matched = scored.filter((s) => s.score > 0).map((s) => s.entry);
1575
+ return limit ? matched.slice(0, limit) : matched;
1576
+ }
1577
+ async clear(scope, filePath) {
1578
+ const file = this.resolveFile(filePath, scope);
1579
+ await atomicWrite(file, "");
1580
+ }
1581
+ async consolidate(scope, filePath) {
1582
+ const file = this.resolveFile(filePath, scope);
1583
+ let existing;
1584
+ try {
1585
+ existing = await fsp.readFile(file, "utf8");
1586
+ } catch {
1587
+ return 0;
1588
+ }
1589
+ const seen = /* @__PURE__ */ new Set();
1590
+ let removed = 0;
1591
+ const lines = existing.split("\n").filter((line) => {
1592
+ const trimmed = line.trim();
1593
+ if (!trimmed.startsWith("- ")) return true;
1594
+ const norm = trimmed.replace(/\[[^\]]+\]/g, "").replace(/\bmem_\d+_\w+\s*/, "").replace(/#[\w-]+/g, "").replace(/\s+/g, " ").trim().toLowerCase();
1595
+ if (seen.has(norm)) {
1596
+ removed++;
1597
+ return false;
1598
+ }
1599
+ seen.add(norm);
1600
+ return true;
1601
+ });
1602
+ const next = lines.join("\n");
1603
+ const backup = `${file}.bak.${Date.now()}`;
1604
+ try {
1605
+ await fsp.copyFile(file, backup);
1606
+ } catch {
1607
+ }
1608
+ try {
1609
+ await atomicWrite(file, next);
1610
+ } catch {
1611
+ return 0;
1612
+ }
1613
+ return removed;
1614
+ }
1615
+ };
1616
+ function parseEntries(raw, scope = "project-memory") {
1617
+ const entries = [];
1618
+ for (const line of raw.split("\n")) {
1619
+ const entry = lineToEntry(line, scope);
1620
+ if (entry) entries.push(entry);
1621
+ }
1622
+ return entries.reverse();
1623
+ }
1624
+
1625
+ // src/storage/memory-store.ts
1360
1626
  var MAX_BYTES_TOTAL = 32e3;
1361
1627
  var DefaultMemoryStore = class {
1362
1628
  files;
1629
+ events;
1630
+ backend;
1363
1631
  /**
1364
1632
  * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
1365
1633
  * `clear` are read-modify-write against a single file; without a lock,
@@ -1376,12 +1644,29 @@ var DefaultMemoryStore = class {
1376
1644
  writeChain = /* @__PURE__ */ new Map();
1377
1645
  /** Last write error per scope — surfaced as warnings on the next readAll(). */
1378
1646
  writeErrors = /* @__PURE__ */ new Map();
1647
+ /**
1648
+ * When the global root is a temporary directory (opencode, CI sandboxes),
1649
+ * memory files are also mirrored to the project tree so they survive
1650
+ * session cleanup. The primary path stays in the temp root for isolation;
1651
+ * this backup ensures memory is never lost.
1652
+ */
1653
+ persistBackup;
1654
+ backupDir;
1379
1655
  constructor(opts) {
1380
1656
  this.files = {
1381
1657
  "project-agents": opts.paths.inProjectAgentsFile,
1382
1658
  "project-memory": opts.paths.projectMemory,
1383
1659
  "user-memory": opts.paths.globalMemory
1384
1660
  };
1661
+ this.events = opts.events;
1662
+ this.backend = opts.backend ?? new FileMemoryBackend({ paths: opts.paths });
1663
+ const root = opts.paths.globalRoot.toLowerCase();
1664
+ this.persistBackup = /[/\\](tmp|temp|cache)[/\\]/.test(root);
1665
+ this.backupDir = this.persistBackup ? opts.paths.inProjectAgentsFile.replace(/AGENTS\.md$/, "memory-persist") : "";
1666
+ }
1667
+ /** Expose the backend for plugin introspection and advanced queries. */
1668
+ getBackend() {
1669
+ return this.backend;
1385
1670
  }
1386
1671
  async runSerialized(scope, work) {
1387
1672
  const prior = this.writeChain.get(scope) ?? Promise.resolve();
@@ -1408,7 +1693,7 @@ var DefaultMemoryStore = class {
1408
1693
  if (writeErr2) {
1409
1694
  parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
1410
1695
  }
1411
- const body = await this.read(scope);
1696
+ const body = await this.backend.readAll(scope, this.files[scope]);
1412
1697
  if (body.trim()) parts.push(`## ${labelOf(scope)}
1413
1698
 
1414
1699
  ${body.trim()}`);
@@ -1416,117 +1701,214 @@ ${body.trim()}`);
1416
1701
  return parts.join("\n\n");
1417
1702
  }
1418
1703
  async read(scope) {
1419
- try {
1420
- return await fsp.readFile(this.files[scope], "utf8");
1421
- } catch {
1422
- return "";
1704
+ return this.backend.readAll(scope, this.files[scope]);
1705
+ }
1706
+ /**
1707
+ * List entries from a scope, newest first. Delegates to the backend
1708
+ * so graph/semantic backends can return enriched or filtered results.
1709
+ */
1710
+ async list(scope = "project-memory", limit) {
1711
+ return this.backend.list(scope, this.files[scope], limit);
1712
+ }
1713
+ /**
1714
+ * Find memories related to the given text via graph traversal.
1715
+ * Falls back to content search when no graph backend is available.
1716
+ */
1717
+ async findRelated(text, scope = "project-memory", limit = 5) {
1718
+ if (this.backend.findRelated) {
1719
+ return this.backend.findRelated(scope, this.files[scope], text, limit);
1423
1720
  }
1721
+ return this.search(text, scope, limit);
1722
+ }
1723
+ async search(query, scope = "project-memory", limit) {
1724
+ return this.backend.search(scope, query, this.files[scope], limit);
1424
1725
  }
1425
- async remember(text, scope = "project-memory") {
1726
+ async remember(text, scope = "project-memory", metadata) {
1727
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
1426
1728
  return this.runSerialized(scope, async () => {
1427
- const file = this.files[scope];
1428
- await ensureDir(path3.dirname(file));
1429
- let existing = "";
1430
- try {
1431
- existing = await fsp.readFile(file, "utf8");
1432
- } catch {
1433
- }
1434
- const ts = (/* @__PURE__ */ new Date()).toISOString();
1435
- const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
1436
- const entry = `
1437
- - [${ts}] ${id} ${text.replace(/\n/g, " ")}
1438
- `;
1439
- const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
1440
- ${entry}`;
1441
- await atomicWrite(file, next);
1442
- const buf = Buffer.byteLength(next, "utf8");
1443
- if (buf > MAX_BYTES_TOTAL) {
1444
- await this.consolidateUnsafe(scope);
1729
+ const entry = { scope, text, ts, ...metadata };
1730
+ await this.backend.remember(scope, entry, this.files[scope]);
1731
+ const raw = await this.backend.readAll(scope, this.files[scope]);
1732
+ if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
1733
+ const removed = await this.backend.consolidate(scope, this.files[scope]);
1734
+ if (removed > 0) {
1735
+ this.events?.emit("memory.consolidated", {
1736
+ scope,
1737
+ removed
1738
+ });
1739
+ }
1445
1740
  }
1741
+ await this.mirrorBackup(scope);
1742
+ this.events?.emit("memory.remembered", {
1743
+ scope,
1744
+ text,
1745
+ ts,
1746
+ type: entry.type,
1747
+ tags: entry.tags,
1748
+ priority: entry.priority
1749
+ });
1446
1750
  });
1447
1751
  }
1448
- async forget(query, scope = "project-memory") {
1449
- return this.runSerialized(scope, async () => this.forgetUnsafe(query, scope));
1450
- }
1451
- async forgetUnsafe(query, scope) {
1452
- const file = this.files[scope];
1453
- let existing;
1454
- try {
1455
- existing = await fsp.readFile(file, "utf8");
1456
- } catch {
1457
- return 0;
1458
- }
1459
- const needle = query.toLowerCase();
1460
- const idMatcher = /mem_\d+_\w+/;
1461
- let removed = 0;
1462
- const lines = existing.split("\n").filter((line) => {
1463
- const trimmed = line.trim();
1464
- if (!trimmed.startsWith("- ")) return true;
1465
- if (idMatcher.test(query)) {
1466
- const afterBracket = trimmed.indexOf("] ");
1467
- if (afterBracket !== -1) {
1468
- const afterTs = trimmed.slice(afterBracket + 2);
1469
- const entryIdMatch = /^mem_\d+_\w+/.exec(afterTs);
1470
- if (entryIdMatch && entryIdMatch[0] === query) {
1471
- removed++;
1472
- return false;
1473
- }
1752
+ /**
1753
+ * Score and rank memories by relevance to the current context.
1754
+ * Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.
1755
+ */
1756
+ async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
1757
+ const all = await this.list(scope);
1758
+ if (all.length === 0) return [];
1759
+ const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
1760
+ const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
1761
+ const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
1762
+ const now = Date.now();
1763
+ const scored = [];
1764
+ for (const entry of all) {
1765
+ let score = 0;
1766
+ const reasons = [];
1767
+ const textLower = entry.text.toLowerCase();
1768
+ const tagsLower = (entry.tags ?? []).map((t) => t.toLowerCase());
1769
+ let taskHits = 0;
1770
+ for (const w of taskWords) {
1771
+ if (textLower.includes(w)) {
1772
+ taskHits++;
1773
+ score += 2;
1774
+ }
1775
+ if (tagsLower.some((t) => t.includes(w))) {
1776
+ taskHits++;
1777
+ score += 3;
1474
1778
  }
1475
1779
  }
1476
- if (trimmed.toLowerCase().includes(needle)) {
1477
- removed++;
1478
- return false;
1780
+ if (taskHits > 0) reasons.push(`task match (${taskHits})`);
1781
+ let skillHits = 0;
1782
+ for (const w of skillWords) {
1783
+ if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
1784
+ skillHits++;
1785
+ }
1786
+ }
1787
+ score += skillHits;
1788
+ if (skillHits > 0) reasons.push(`skill match (${skillHits})`);
1789
+ for (const w of toolWords) {
1790
+ if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
1791
+ score += 1;
1792
+ reasons.push(`tool mention: ${w}`);
1793
+ }
1794
+ }
1795
+ switch (entry.priority) {
1796
+ case "critical":
1797
+ score += 5;
1798
+ reasons.push("critical");
1799
+ break;
1800
+ case "high":
1801
+ score += 3;
1802
+ reasons.push("high priority");
1803
+ break;
1804
+ case "medium":
1805
+ score += 1;
1806
+ break;
1807
+ case "low":
1808
+ score -= 2;
1809
+ reasons.push("low priority");
1810
+ break;
1811
+ }
1812
+ switch (entry.type) {
1813
+ case "decision":
1814
+ score += 2;
1815
+ reasons.push("decision");
1816
+ break;
1817
+ case "convention":
1818
+ score += 2;
1819
+ reasons.push("convention");
1820
+ break;
1821
+ case "anti_pattern":
1822
+ score += 3;
1823
+ reasons.push("anti-pattern");
1824
+ break;
1825
+ case "preference":
1826
+ score += 1;
1827
+ reasons.push("preference");
1828
+ break;
1829
+ }
1830
+ const ageDays = (now - new Date(entry.ts).getTime()) / (1e3 * 60 * 60 * 24);
1831
+ if (ageDays < 1) score += 1;
1832
+ else if (ageDays > 30) score -= 1;
1833
+ if (entry.confidence !== void 0 && entry.confidence < 0.5) {
1834
+ score -= 2;
1835
+ reasons.push("low confidence");
1836
+ }
1837
+ if (entry.lastAccessed) {
1838
+ const hoursSinceAccess = (now - new Date(entry.lastAccessed).getTime()) / (1e3 * 60 * 60);
1839
+ if (hoursSinceAccess < 1) score -= 1;
1840
+ }
1841
+ if (score > 0) {
1842
+ scored.push({
1843
+ ...entry,
1844
+ score,
1845
+ matchReason: reasons.join(", ") || "keyword match"
1846
+ });
1479
1847
  }
1480
- return true;
1481
- });
1482
- if (removed > 0) {
1483
- await atomicWrite(file, lines.join("\n"));
1484
1848
  }
1485
- return removed;
1849
+ scored.sort((a, b) => b.score - a.score);
1850
+ const threshold = 2;
1851
+ const relevant = scored.filter(
1852
+ (s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
1853
+ );
1854
+ return relevant.slice(0, Math.min(limit, 15));
1486
1855
  }
1487
- async consolidate(scope) {
1488
- return this.runSerialized(scope, async () => this.consolidateUnsafe(scope));
1856
+ async forget(query, scope = "project-memory") {
1857
+ return this.runSerialized(scope, async () => {
1858
+ const removed = await this.backend.forget(scope, query, this.files[scope]);
1859
+ if (removed > 0) {
1860
+ this.events?.emit("memory.forgotten", {
1861
+ scope,
1862
+ query,
1863
+ removed
1864
+ });
1865
+ await this.mirrorBackup(scope);
1866
+ }
1867
+ return removed;
1868
+ });
1489
1869
  }
1490
- async consolidateUnsafe(scope) {
1491
- const file = this.files[scope];
1492
- let existing;
1493
- try {
1494
- existing = await fsp.readFile(file, "utf8");
1495
- } catch {
1496
- return;
1497
- }
1498
- const seen = /* @__PURE__ */ new Set();
1499
- const lines = existing.split("\n").filter((line) => {
1500
- const trimmed = line.trim();
1501
- if (!trimmed.startsWith("- ")) return true;
1502
- const norm = trimmed.replace(/\[[^\]]+\]/, "").replace(/\bmem_\d+_\w+\s*/, "").trim().toLowerCase();
1503
- if (seen.has(norm)) return false;
1504
- seen.add(norm);
1505
- return true;
1870
+ async consolidate(scope) {
1871
+ return this.runSerialized(scope, async () => {
1872
+ const removed = await this.backend.consolidate(scope, this.files[scope]);
1873
+ if (removed > 0) {
1874
+ this.events?.emit("memory.consolidated", {
1875
+ scope,
1876
+ removed
1877
+ });
1878
+ await this.mirrorBackup(scope);
1879
+ }
1506
1880
  });
1507
- const next = lines.join("\n");
1508
- const backup = `${file}.bak.${Date.now()}`;
1509
- try {
1510
- await fsp.copyFile(file, backup);
1511
- } catch {
1512
- }
1513
- try {
1514
- await atomicWrite(file, next);
1515
- } catch {
1516
- return;
1517
- }
1518
1881
  }
1519
1882
  async clear(scope) {
1520
1883
  if (scope) {
1521
- await this.runSerialized(scope, async () => atomicWrite(this.files[scope], ""));
1884
+ await this.runSerialized(scope, async () => {
1885
+ await this.backend.clear(scope, this.files[scope]);
1886
+ this.events?.emit("memory.cleared", { scope });
1887
+ await this.mirrorBackup(scope);
1888
+ });
1522
1889
  return;
1523
1890
  }
1524
1891
  await Promise.all(
1525
1892
  ["project-agents", "project-memory", "user-memory"].map(
1526
- (s) => this.runSerialized(s, async () => atomicWrite(this.files[s], ""))
1893
+ (s) => this.runSerialized(s, async () => {
1894
+ await this.backend.clear(s, this.files[s]);
1895
+ this.events?.emit("memory.cleared", { scope: s });
1896
+ await this.mirrorBackup(s);
1897
+ })
1527
1898
  )
1528
1899
  );
1529
1900
  }
1901
+ /** Mirror current memory content to the persistent backup directory. */
1902
+ async mirrorBackup(scope) {
1903
+ if (!this.persistBackup || scope === "project-agents") return;
1904
+ try {
1905
+ const content = await this.backend.readAll(scope, this.files[scope]);
1906
+ const { writeFile: writeFile4, mkdir: mkdir6 } = await import('fs/promises');
1907
+ await mkdir6(this.backupDir, { recursive: true });
1908
+ await writeFile4(`${this.backupDir}/${scope}.md`, content, "utf8");
1909
+ } catch {
1910
+ }
1911
+ }
1530
1912
  };
1531
1913
  function labelOf(scope) {
1532
1914
  switch (scope) {
@@ -1774,6 +2156,13 @@ var BEHAVIOR_DEFAULTS = {
1774
2156
  function envBool(v) {
1775
2157
  return !/^(0|false|no|off)$/i.test(v.trim());
1776
2158
  }
2159
+ function envBoolOptional(v) {
2160
+ return v !== void 0 && envBool(v);
2161
+ }
2162
+ var LOG_LEVELS2 = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
2163
+ function envLogLevel(v) {
2164
+ return LOG_LEVELS2.has(v) ? v : "info";
2165
+ }
1777
2166
  var ENV_MAP = {
1778
2167
  WRONGSTACK_PROVIDER: (c, v) => {
1779
2168
  c.provider = v;
@@ -1797,7 +2186,7 @@ var ENV_MAP = {
1797
2186
  },
1798
2187
  WRONGSTACK_LOG_LEVEL: (c, v) => {
1799
2188
  if (!c.log) c.log = { level: "info" };
1800
- c.log.level = v;
2189
+ c.log.level = envLogLevel(v);
1801
2190
  },
1802
2191
  WRONGSTACK_INDEX_ON_START: (c, v) => {
1803
2192
  c.indexing = { ...defaultIndexing, ...c.indexing, onSessionStart: envBool(v) };
@@ -1839,7 +2228,7 @@ function deepMerge(base, patch) {
1839
2228
  out[k] = [.../* @__PURE__ */ new Set([...existing, ...v])];
1840
2229
  } else {
1841
2230
  out[k] = v;
1842
- if (process.env.WRONGSTACK_DEBUG_CONFIG) {
2231
+ if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
1843
2232
  console.warn(
1844
2233
  `[config] Non-primitive array for "${k}" replaced (global + local config merge). Global entries: ${existing?.length ?? 0}, local entries: ${v.length}.`
1845
2234
  );
@@ -2235,12 +2624,6 @@ function compileUserRegex(pattern, flags) {
2235
2624
  }
2236
2625
 
2237
2626
  // src/storage/session-reader.ts
2238
- function expectDefined3(value) {
2239
- if (value === null || value === void 0) {
2240
- throw new Error("Expected value to be defined");
2241
- }
2242
- return value;
2243
- }
2244
2627
  var DefaultSessionReader = class {
2245
2628
  store;
2246
2629
  constructor(opts) {
@@ -2302,7 +2685,7 @@ var DefaultSessionReader = class {
2302
2685
  continue;
2303
2686
  }
2304
2687
  for (let i = 0; i < data.events.length; i++) {
2305
- const ev = expectDefined3(data.events[i]);
2688
+ const ev = expectDefined(data.events[i]);
2306
2689
  if (allowedTypes && !allowedTypes.has(ev.type)) continue;
2307
2690
  const text = eventText(ev);
2308
2691
  if (text === null) continue;
@@ -2751,7 +3134,10 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
2751
3134
  let pending = null;
2752
3135
  let writeChain = Promise.resolve();
2753
3136
  const enqueueWrite = (todos) => {
2754
- writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos));
3137
+ writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
3138
+ const msg = err instanceof Error ? err.message : String(err);
3139
+ console.error(`[TodosCheckpoint] save failed for session ${sessionId}: ${msg}`);
3140
+ });
2755
3141
  return writeChain;
2756
3142
  };
2757
3143
  const flush = () => {
@@ -3055,7 +3441,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
3055
3441
  }
3056
3442
  const lock = {
3057
3443
  pid: processId,
3058
- hostname: __require("os").hostname(),
3444
+ hostname: hostname(),
3059
3445
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3060
3446
  };
3061
3447
  await atomicWrite(lockPath, JSON.stringify(lock), { mode: 384 });
@@ -3467,9 +3853,18 @@ async function restrictFilePermissions(filePath, opts) {
3467
3853
  const { execFile: execFile2 } = await import('child_process');
3468
3854
  const { promisify: promisify2 } = await import('util');
3469
3855
  const execFileAsync = promisify2(execFile2);
3470
- await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${process.env.USERNAME}:(F)`]);
3856
+ const user = windowsAccountName();
3857
+ if (!user) {
3858
+ warn(
3859
+ `[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
3860
+ );
3861
+ return;
3862
+ }
3863
+ await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
3471
3864
  } catch {
3472
- warn(`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`);
3865
+ warn(
3866
+ `[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
3867
+ );
3473
3868
  }
3474
3869
  } else {
3475
3870
  try {
@@ -3478,6 +3873,13 @@ async function restrictFilePermissions(filePath, opts) {
3478
3873
  }
3479
3874
  }
3480
3875
  }
3876
+ function windowsAccountName() {
3877
+ const username = process.env.USERNAME || process.env.USER;
3878
+ if (!username || username.includes("\0")) return void 0;
3879
+ const domain = process.env.USERDOMAIN;
3880
+ if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
3881
+ return username;
3882
+ }
3481
3883
  function walkCount(node, vault, counter) {
3482
3884
  if (node === null || node === void 0) return node;
3483
3885
  if (typeof node !== "object") return node;
@@ -3565,12 +3967,6 @@ function getDangerousCapabilities(toolOrCaps) {
3565
3967
  init_atomic_write();
3566
3968
 
3567
3969
  // src/utils/glob-match.ts
3568
- function expectDefined4(value) {
3569
- if (value === null || value === void 0) {
3570
- throw new Error("Expected value to be defined");
3571
- }
3572
- return value;
3573
- }
3574
3970
  function escapeRegex(s) {
3575
3971
  return s.replace(/[.+^${}()|\\]/g, "\\$&");
3576
3972
  }
@@ -3582,7 +3978,7 @@ function getCachedGlob(pattern) {
3582
3978
  if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
3583
3979
  const keys = [...COMPILED_GLOB_CACHE.keys()];
3584
3980
  for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
3585
- COMPILED_GLOB_CACHE.delete(expectDefined4(keys[i]));
3981
+ COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
3586
3982
  }
3587
3983
  }
3588
3984
  const re = compileGlob(pattern);
@@ -4447,12 +4843,6 @@ function parseDescription(raw) {
4447
4843
  }
4448
4844
 
4449
4845
  // src/utils/json-repair.ts
4450
- function expectDefined5(value) {
4451
- if (value === null || value === void 0) {
4452
- throw new Error("Expected value to be defined");
4453
- }
4454
- return value;
4455
- }
4456
4846
  function completePartialObject(s) {
4457
4847
  if (!s.trim().startsWith("{")) return s;
4458
4848
  if (tryParse(s).ok) return s;
@@ -4464,7 +4854,7 @@ function completePartialObject(s) {
4464
4854
  let contentEnd = 0;
4465
4855
  let stringBraceDepth = 0;
4466
4856
  for (let i = 0; i < s.length; i++) {
4467
- const ch = expectDefined5(s[i]);
4857
+ const ch = expectDefined(s[i]);
4468
4858
  if (inString) {
4469
4859
  contentEnd = i + 1;
4470
4860
  if (escaped) {
@@ -4542,6 +4932,7 @@ function tryParse(s) {
4542
4932
  }
4543
4933
 
4544
4934
  // src/core/streaming-response-builder.ts
4935
+ var STREAM_DRAIN_TIMEOUT_MS = 500;
4545
4936
  function buildResponse(state) {
4546
4937
  const content = [];
4547
4938
  for (const b of state.blockOrder) {
@@ -4754,7 +5145,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
4754
5145
  await Promise.race([
4755
5146
  Promise.resolve(iter.return?.()),
4756
5147
  new Promise((resolve5) => {
4757
- drainTimer = setTimeout(resolve5, 500);
5148
+ drainTimer = setTimeout(resolve5, STREAM_DRAIN_TIMEOUT_MS);
4758
5149
  })
4759
5150
  ]);
4760
5151
  } finally {
@@ -4849,12 +5240,6 @@ var DefaultProviderRunner = class {
4849
5240
  };
4850
5241
 
4851
5242
  // src/utils/token-estimate.ts
4852
- function expectDefined6(value) {
4853
- if (value === null || value === void 0) {
4854
- throw new Error("Expected value to be defined");
4855
- }
4856
- return value;
4857
- }
4858
5243
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
4859
5244
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
4860
5245
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
@@ -4864,7 +5249,7 @@ function getCachedEstimate(key, compute) {
4864
5249
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
4865
5250
  const keys = [...ESTIMATE_CACHE.keys()];
4866
5251
  for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
4867
- ESTIMATE_CACHE.delete(expectDefined6(keys[i]));
5252
+ ESTIMATE_CACHE.delete(expectDefined(keys[i]));
4868
5253
  }
4869
5254
  }
4870
5255
  const estimate = compute();
@@ -5357,12 +5742,6 @@ var IntelligentCompactor = class {
5357
5742
  };
5358
5743
 
5359
5744
  // src/models/llm-selector.ts
5360
- function expectDefined7(value) {
5361
- if (value === null || value === void 0) {
5362
- throw new Error("Expected value to be defined");
5363
- }
5364
- return value;
5365
- }
5366
5745
  var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
5367
5746
 
5368
5747
  Output a JSON object with this structure:
@@ -5403,7 +5782,7 @@ function formatMessages(messages, maxChars = 8e3) {
5403
5782
  const lines = [];
5404
5783
  let used = 0;
5405
5784
  for (let i = 0; i < messages.length; i++) {
5406
- const m = expectDefined7(messages[i]);
5785
+ const m = expectDefined(messages[i]);
5407
5786
  const role = m.role.padEnd(10, " ");
5408
5787
  let text;
5409
5788
  if (typeof m.content === "string") {
@@ -5468,7 +5847,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
5468
5847
  let tokenCount = 0;
5469
5848
  let startIdx = 0;
5470
5849
  for (let i = messages.length - 1; i >= 0; i--) {
5471
- const m = expectDefined7(messages[i]);
5850
+ const m = expectDefined(messages[i]);
5472
5851
  const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5473
5852
  (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5474
5853
  0
@@ -6075,17 +6454,12 @@ function createToolOutputSerializer(opts = {}) {
6075
6454
  }
6076
6455
 
6077
6456
  // src/execution/tool-executor.ts
6078
- function expectDefined8(value) {
6079
- if (value === null || value === void 0) {
6080
- throw new Error("Expected value to be defined");
6081
- }
6082
- return value;
6083
- }
6084
6457
  var ToolExecutor = class {
6085
6458
  constructor(registry, opts) {
6086
6459
  this.registry = registry;
6087
6460
  this.opts = opts;
6088
6461
  this.iterationTimeoutMs = opts.iterationTimeoutMs ?? 3e5;
6462
+ this.maxToolTimeoutMs = opts.maxToolTimeoutMs ?? 3e5;
6089
6463
  this.serializer = createToolOutputSerializer({
6090
6464
  perIterationOutputCapBytes: opts.perIterationOutputCapBytes ?? 1e5
6091
6465
  });
@@ -6094,6 +6468,7 @@ var ToolExecutor = class {
6094
6468
  opts;
6095
6469
  serializer;
6096
6470
  iterationTimeoutMs;
6471
+ maxToolTimeoutMs;
6097
6472
  /**
6098
6473
  * Clear the interactive confirm awaiter so the executor returns
6099
6474
  * `ToolConfirmPendingResult` instead of blocking on stdin. Used by
@@ -6331,7 +6706,10 @@ ${post.additionalContext}` };
6331
6706
  if (parentSignal.reason instanceof Error) throw parentSignal.reason;
6332
6707
  throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
6333
6708
  }
6334
- const timeoutMs = tool.timeoutMs ?? this.iterationTimeoutMs;
6709
+ const timeoutMs = clampTimeoutMs(
6710
+ tool.timeoutMs ?? this.iterationTimeoutMs,
6711
+ this.maxToolTimeoutMs
6712
+ );
6335
6713
  const ctrl = new AbortController();
6336
6714
  const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
6337
6715
  const combined = AbortSignal.any([parentSignal, ctrl.signal]);
@@ -6466,6 +6844,12 @@ ${excerpt}`;
6466
6844
  return void 0;
6467
6845
  }
6468
6846
  };
6847
+ function clampTimeoutMs(timeoutMs, maxTimeoutMs) {
6848
+ const fallback = 3e5;
6849
+ const finiteTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : fallback;
6850
+ const finiteMax = Number.isFinite(maxTimeoutMs) && maxTimeoutMs > 0 ? maxTimeoutMs : fallback;
6851
+ return Math.max(1, Math.min(finiteTimeout, finiteMax));
6852
+ }
6469
6853
  var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
6470
6854
  function hasMalformedArguments(input) {
6471
6855
  if (!input || typeof input !== "object" || Array.isArray(input)) return false;
@@ -6476,7 +6860,7 @@ function hasMalformedArguments(input) {
6476
6860
  function extractMalformedRaw(input) {
6477
6861
  if (!hasMalformedArguments(input)) return void 0;
6478
6862
  const obj = input;
6479
- const value = obj[expectDefined8(Object.keys(obj)[0])];
6863
+ const value = obj[expectDefined(Object.keys(obj)[0])];
6480
6864
  if (value === void 0 || value === null) return void 0;
6481
6865
  if (typeof value === "string") return value;
6482
6866
  try {
@@ -7278,6 +7662,7 @@ ${recentJournal}` : "No prior iterations.",
7278
7662
  };
7279
7663
 
7280
7664
  // src/coordination/subagent-budget.ts
7665
+ var TIMEOUT_PREEMPT_FRACTION = 0.85;
7281
7666
  var BudgetExceededError = class extends Error {
7282
7667
  kind;
7283
7668
  limit;
@@ -7633,15 +8018,7 @@ function makeAgentSubagentRunner(opts) {
7633
8018
  let budgetError = null;
7634
8019
  const onBudgetError = (err) => {
7635
8020
  if (err instanceof BudgetThresholdSignal) {
7636
- err.decision.then((decision) => {
7637
- if (decision === "stop") {
7638
- budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
7639
- aborter.abort();
7640
- }
7641
- }).catch(() => {
7642
- budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
7643
- aborter.abort();
7644
- });
8021
+ budgetError = err;
7645
8022
  return;
7646
8023
  }
7647
8024
  aborter.abort();
@@ -7749,15 +8126,23 @@ function makeAgentSubagentRunner(opts) {
7749
8126
  }
7750
8127
  if (budgetError) {
7751
8128
  if ("decision" in budgetError) {
7752
- const decision = await budgetError.decision;
7753
- if (decision === "stop") {
8129
+ try {
8130
+ const decision = await budgetError.decision;
8131
+ if (decision === "stop") {
8132
+ budgetError = new BudgetExceededError(
8133
+ budgetError.kind,
8134
+ budgetError.limit,
8135
+ budgetError.used
8136
+ );
8137
+ } else {
8138
+ budgetError = null;
8139
+ }
8140
+ } catch {
7754
8141
  budgetError = new BudgetExceededError(
7755
8142
  budgetError.kind,
7756
8143
  budgetError.limit,
7757
8144
  budgetError.used
7758
8145
  );
7759
- } else {
7760
- budgetError = null;
7761
8146
  }
7762
8147
  }
7763
8148
  if (budgetError) throw budgetError;
@@ -10079,16 +10464,17 @@ Do not add prose, markdown, or code fences.`;
10079
10464
  const raw = (await complete(prompt)).trim();
10080
10465
  const match = raw.match(/\{[\s\S]*\}/);
10081
10466
  if (!match) return null;
10082
- try {
10083
- const parsed = JSON.parse(match[0]);
10084
- if (typeof parsed.role !== "string") return null;
10085
- const role = parsed.role.trim();
10086
- const valid = candidates.some((c) => c.role === role);
10087
- if (!valid) return null;
10088
- return { role, reason: typeof parsed.reason === "string" ? parsed.reason : void 0 };
10089
- } catch {
10090
- return null;
10091
- }
10467
+ const parsed = safeParse(
10468
+ match[0]
10469
+ );
10470
+ if (!parsed.ok || !parsed.value || typeof parsed.value.role !== "string") return null;
10471
+ const role = parsed.value.role.trim();
10472
+ const valid = candidates.some((c) => c.role === role);
10473
+ if (!valid) return null;
10474
+ return {
10475
+ role,
10476
+ reason: typeof parsed.value.reason === "string" ? parsed.value.reason : void 0
10477
+ };
10092
10478
  };
10093
10479
  }
10094
10480
 
@@ -11043,15 +11429,38 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11043
11429
  }
11044
11430
  const start = Date.now();
11045
11431
  let timer = null;
11432
+ let preemptedForLimit = null;
11046
11433
  const timeoutPromise = new Promise((_, reject) => {
11047
11434
  const armFor = (ms) => {
11048
11435
  if (timer) clearTimeout(timer);
11049
11436
  timer = setTimeout(onTick, Math.max(0, ms));
11050
11437
  };
11051
11438
  const scheduleNext = () => {
11052
- const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
11439
+ const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
11440
+ const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
11053
11441
  const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
11054
- armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
11442
+ const preemptRemaining = initialTimeoutMs === void 0 || preemptedForLimit === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * TIMEOUT_PREEMPT_FRACTION - (Date.now() - start);
11443
+ armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
11444
+ };
11445
+ const negotiateTimeout = async (used, limit) => {
11446
+ const handler = budget.onThreshold;
11447
+ if (!handler) return "stop";
11448
+ const result = handler({
11449
+ kind: "timeout",
11450
+ used,
11451
+ limit,
11452
+ requestDecision: () => new Promise((resolveDecision) => {
11453
+ budget._events?.emit("budget.threshold_reached", {
11454
+ kind: "timeout",
11455
+ used,
11456
+ limit,
11457
+ timeoutMs: 6e4,
11458
+ extend: (extra) => resolveDecision({ extend: extra }),
11459
+ deny: () => resolveDecision("stop")
11460
+ });
11461
+ })
11462
+ });
11463
+ return typeof result === "string" ? result : await result;
11055
11464
  };
11056
11465
  const onTick = async () => {
11057
11466
  const elapsed = Date.now() - start;
@@ -11064,6 +11473,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11064
11473
  reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
11065
11474
  return;
11066
11475
  }
11476
+ if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptedForLimit !== wallLimit && elapsed >= wallLimit * TIMEOUT_PREEMPT_FRACTION) {
11477
+ try {
11478
+ const decision = await negotiateTimeout(elapsed, wallLimit);
11479
+ if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
11480
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
11481
+ preemptedForLimit = null;
11482
+ } else {
11483
+ preemptedForLimit = wallLimit;
11484
+ }
11485
+ } catch {
11486
+ preemptedForLimit = wallLimit;
11487
+ }
11488
+ scheduleNext();
11489
+ return;
11490
+ }
11067
11491
  if (!wallExceeded) {
11068
11492
  scheduleNext();
11069
11493
  return;
@@ -11075,28 +11499,15 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11075
11499
  return;
11076
11500
  }
11077
11501
  try {
11078
- const result = budget.onThreshold({
11079
- kind: "timeout",
11080
- used: elapsed,
11081
- limit,
11082
- requestDecision: () => new Promise((resolveDecision) => {
11083
- budget._events?.emit("budget.threshold_reached", {
11084
- kind: "timeout",
11085
- used: elapsed,
11086
- limit,
11087
- timeoutMs: 6e4,
11088
- extend: (extra) => resolveDecision({ extend: extra }),
11089
- deny: () => resolveDecision("stop")
11090
- });
11091
- })
11092
- });
11093
- const decision = typeof result === "string" ? result : await result;
11502
+ const decision = await negotiateTimeout(elapsed, limit);
11094
11503
  if (decision === "continue" || decision === "throw" || decision === "stop") {
11504
+ preemptedForLimit = null;
11095
11505
  armFor(Math.max(1e3, limit));
11096
11506
  return;
11097
11507
  }
11098
11508
  if (decision.extend.timeoutMs !== void 0) {
11099
11509
  budget.limits.timeoutMs = decision.extend.timeoutMs;
11510
+ preemptedForLimit = null;
11100
11511
  scheduleNext();
11101
11512
  return;
11102
11513
  }
@@ -11227,12 +11638,6 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11227
11638
  };
11228
11639
 
11229
11640
  // src/execution/parallel-eternal-engine.ts
11230
- function expectDefined9(value) {
11231
- if (value === null || value === void 0) {
11232
- throw new Error("Expected value to be defined");
11233
- }
11234
- return value;
11235
- }
11236
11641
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
11237
11642
  var ParallelEternalEngine = class {
11238
11643
  constructor(opts) {
@@ -11388,7 +11793,7 @@ var ParallelEternalEngine = class {
11388
11793
  // Fan-out
11389
11794
  // -------------------------------------------------------------------------
11390
11795
  async fanOut(goal, tasks) {
11391
- const coordinator = expectDefined9(this.coordinator);
11796
+ const coordinator = expectDefined(this.coordinator);
11392
11797
  const slotCount = Math.min(this.slots, tasks.length);
11393
11798
  const routes = this.dispatchEnabled ? await Promise.all(
11394
11799
  tasks.slice(0, slotCount).map(
@@ -11420,7 +11825,7 @@ ${recentJournal}` : "No prior iterations.",
11420
11825
  const routeInfo = [];
11421
11826
  const spawnPromises = [];
11422
11827
  for (let i = 0; i < slotCount; i++) {
11423
- const task = expectDefined9(tasks[i]);
11828
+ const task = expectDefined(tasks[i]);
11424
11829
  const route = routes[i] ?? null;
11425
11830
  const subagentId = `parallel-${this.iterations}-${i}`;
11426
11831
  const taskId = randomUUID();
@@ -11911,12 +12316,6 @@ function createMessage(type, from, payload, to) {
11911
12316
  priority: "normal"
11912
12317
  };
11913
12318
  }
11914
- function expectDefined10(value) {
11915
- if (value === null || value === void 0) {
11916
- throw new Error("Expected value to be defined");
11917
- }
11918
- return value;
11919
- }
11920
12319
  var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
11921
12320
  var IS_WINDOWS = process.platform === "win32";
11922
12321
  var SEP = IS_WINDOWS ? "\\" : "/";
@@ -11930,7 +12329,7 @@ function globToRegex(pat) {
11930
12329
  let i = 0;
11931
12330
  let re = "^";
11932
12331
  while (i < pat.length) {
11933
- const c = expectDefined10(pat[i]);
12332
+ const c = expectDefined(pat[i]);
11934
12333
  if (c === "*") {
11935
12334
  if (pat[i + 1] === "*") {
11936
12335
  re += ".*";
@@ -11969,7 +12368,7 @@ function globToRegex(pat) {
11969
12368
  }
11970
12369
  function baseDir(pat) {
11971
12370
  let i = pat.length - 1;
11972
- while (i >= 0 && !GLOB_CHARS.has(expectDefined10(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
12371
+ while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
11973
12372
  const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
11974
12373
  return cut < 0 ? "." : pat.slice(0, cut);
11975
12374
  }
@@ -12004,8 +12403,8 @@ async function expandGlob(pattern) {
12004
12403
  for (const e of entries) {
12005
12404
  const full = `${dir}${SEP}${e}`;
12006
12405
  try {
12007
- const stat5 = await fsp.stat(full);
12008
- if (stat5.isDirectory()) await walk4(full, rest);
12406
+ const stat6 = await fsp.stat(full);
12407
+ if (stat6.isDirectory()) await walk4(full, rest);
12009
12408
  } catch {
12010
12409
  }
12011
12410
  }
@@ -12022,8 +12421,8 @@ async function expandGlob(pattern) {
12022
12421
  if (entries.includes(seg)) {
12023
12422
  const full = `${dir}${SEP}${seg}`;
12024
12423
  try {
12025
- const stat5 = await fsp.stat(full);
12026
- if (stat5.isDirectory()) await walk4(full, rest);
12424
+ const stat6 = await fsp.stat(full);
12425
+ if (stat6.isDirectory()) await walk4(full, rest);
12027
12426
  } catch {
12028
12427
  }
12029
12428
  }
@@ -12050,7 +12449,9 @@ var CollabSession = class extends EventEmitter {
12050
12449
  settled = false;
12051
12450
  timeoutMs;
12052
12451
  cancelled = false;
12452
+ _raceResolved = false;
12053
12453
  alerts = [];
12454
+ snapshotWarnings = [];
12054
12455
  /** Tracks tool call counts per subagent for progress-based timeout decisions. */
12055
12456
  progressBySubagent = /* @__PURE__ */ new Map();
12056
12457
  /** Last tool call count when a timeout warning was handled. */
@@ -12120,10 +12521,19 @@ var CollabSession = class extends EventEmitter {
12120
12521
  }
12121
12522
  for (const filePath of allFiles) {
12122
12523
  try {
12123
- const content = await fsp.readFile(filePath, "utf8");
12524
+ const [content, stat6] = await Promise.all([
12525
+ fsp.readFile(filePath, "utf8"),
12526
+ fsp.stat(filePath)
12527
+ ]);
12124
12528
  const ext = filePath.split(".").pop() ?? "";
12125
12529
  const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
12126
- this.snapshot.files.push({ path: filePath, content, language });
12530
+ this.snapshot.files.push({
12531
+ path: filePath,
12532
+ content,
12533
+ language,
12534
+ snapshotMtimeMs: stat6.mtimeMs,
12535
+ snapshotSizeBytes: stat6.size
12536
+ });
12127
12537
  } catch {
12128
12538
  this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
12129
12539
  }
@@ -12136,7 +12546,7 @@ var CollabSession = class extends EventEmitter {
12136
12546
  * Safe to call multiple times (idempotent after first call).
12137
12547
  */
12138
12548
  cancel(reason = "Director cancelled collab session") {
12139
- if (this.settled) return;
12549
+ if (this.cancelled) return;
12140
12550
  this.cancelled = true;
12141
12551
  if (this._timeoutTimer) {
12142
12552
  clearTimeout(this._timeoutTimer);
@@ -12146,7 +12556,11 @@ var CollabSession = class extends EventEmitter {
12146
12556
  subagentId: this.director.id,
12147
12557
  ts: Date.now(),
12148
12558
  type: "director.cancel_collab",
12149
- payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
12559
+ payload: {
12560
+ sessionId: this.sessionId,
12561
+ reason,
12562
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
12563
+ }
12150
12564
  });
12151
12565
  this.fleetBus.emit({
12152
12566
  subagentId: this.director.id,
@@ -12170,6 +12584,7 @@ var CollabSession = class extends EventEmitter {
12170
12584
  this.subagentIds.set("critic", criticId);
12171
12585
  const timeout = new Promise((_, reject) => {
12172
12586
  this._timeoutTimer = setTimeout(() => {
12587
+ if (this._raceResolved) return;
12173
12588
  this.cancel("Session-level timeout reached");
12174
12589
  reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
12175
12590
  }, this.timeoutMs);
@@ -12185,6 +12600,7 @@ var CollabSession = class extends EventEmitter {
12185
12600
  timeout
12186
12601
  ]);
12187
12602
  } catch (err) {
12603
+ this._raceResolved = true;
12188
12604
  if (this._timeoutTimer) {
12189
12605
  clearTimeout(this._timeoutTimer);
12190
12606
  this._timeoutTimer = void 0;
@@ -12197,7 +12613,9 @@ var CollabSession = class extends EventEmitter {
12197
12613
  for (const result of results?.flat() ?? []) {
12198
12614
  await this.parseAndEmit(result);
12199
12615
  }
12616
+ this.snapshotWarnings = await this.checkSnapshotFreshness();
12200
12617
  const report = this.assembleReport();
12618
+ this._raceResolved = true;
12201
12619
  this.cleanup();
12202
12620
  this.emit("session.done", report);
12203
12621
  return report;
@@ -12260,7 +12678,7 @@ var CollabSession = class extends EventEmitter {
12260
12678
  const defaults = {
12261
12679
  "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
12262
12680
  "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
12263
- "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
12681
+ critic: { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
12264
12682
  };
12265
12683
  return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
12266
12684
  }
@@ -12340,7 +12758,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12340
12758
  }
12341
12759
  wireFleetBus() {
12342
12760
  const dTool = this.fleetBus.filter("tool.executed", (e) => {
12343
- this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
12761
+ this.progressBySubagent.set(
12762
+ e.subagentId,
12763
+ (this.progressBySubagent.get(e.subagentId) ?? 0) + 1
12764
+ );
12344
12765
  });
12345
12766
  this.disposers.push(dTool);
12346
12767
  const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
@@ -12379,7 +12800,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12379
12800
  return;
12380
12801
  }
12381
12802
  this.lastTimeoutProgress.set(e.subagentId, progress);
12382
- const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
12803
+ const newLimit = Math.min(
12804
+ Math.ceil((payload.timeoutMs ?? payload.limit) * 2),
12805
+ 24 * 60 * 6e4
12806
+ );
12383
12807
  setImmediate(() => {
12384
12808
  payload.extend({ timeoutMs: newLimit });
12385
12809
  });
@@ -12489,15 +12913,18 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12489
12913
  needs_revision: 1,
12490
12914
  reject: 2
12491
12915
  };
12492
- const overallVerdict = evalList.reduce(
12493
- (worst, eval_) => {
12494
- const w = verdictOrder[worst];
12495
- const c = verdictOrder[eval_.verdict];
12496
- return c > w ? eval_.verdict : worst;
12497
- },
12498
- "approve"
12916
+ const overallVerdict = evalList.reduce((worst, eval_) => {
12917
+ const w = verdictOrder[worst];
12918
+ const c = verdictOrder[eval_.verdict];
12919
+ return c > w ? eval_.verdict : worst;
12920
+ }, "approve");
12921
+ const summary = this.buildMarkdownSummary(
12922
+ bugList,
12923
+ planList,
12924
+ evalList,
12925
+ overallVerdict,
12926
+ disposition
12499
12927
  );
12500
- const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
12501
12928
  return {
12502
12929
  sessionId: this.sessionId,
12503
12930
  startedAt: this.snapshot.createdAt,
@@ -12508,10 +12935,28 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12508
12935
  refactorPlans: planList,
12509
12936
  evaluations: evalList,
12510
12937
  alerts: [...this.alerts],
12938
+ ...this.snapshotWarnings.length > 0 ? { snapshotWarnings: this.snapshotWarnings } : {},
12511
12939
  overallVerdict,
12512
12940
  summary
12513
12941
  };
12514
12942
  }
12943
+ async checkSnapshotFreshness() {
12944
+ const warnings = [];
12945
+ for (const file of this.snapshot.files) {
12946
+ if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
12947
+ try {
12948
+ const stat6 = await fsp.stat(file.path);
12949
+ const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat6.mtimeMs > file.snapshotMtimeMs + 1;
12950
+ const sizeChanged = file.snapshotSizeBytes !== void 0 && stat6.size !== file.snapshotSizeBytes;
12951
+ if (mtimeChanged || sizeChanged) {
12952
+ warnings.push(`${file.path} changed after the collab snapshot was captured.`);
12953
+ }
12954
+ } catch {
12955
+ warnings.push(`${file.path} could not be checked after the collab snapshot was captured.`);
12956
+ }
12957
+ }
12958
+ return warnings;
12959
+ }
12515
12960
  buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
12516
12961
  const lines = [
12517
12962
  `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
@@ -12528,10 +12973,19 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12528
12973
  }
12529
12974
  lines.push("");
12530
12975
  }
12976
+ if (this.snapshotWarnings.length > 0) {
12977
+ lines.push("### Snapshot Warnings", "");
12978
+ for (const warning of this.snapshotWarnings) {
12979
+ lines.push(`- ${warning}`);
12980
+ }
12981
+ lines.push("");
12982
+ }
12531
12983
  if (bugs.length > 0) {
12532
12984
  lines.push("### Bugs Found", "");
12533
12985
  for (const b of bugs) {
12534
- lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
12986
+ lines.push(
12987
+ `- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
12988
+ );
12535
12989
  }
12536
12990
  lines.push("");
12537
12991
  }
@@ -13134,6 +13588,7 @@ var FleetBus = class {
13134
13588
  set.add(handler);
13135
13589
  return () => {
13136
13590
  set.delete(handler);
13591
+ if (set.size === 0) this.byId.delete(subagentId);
13137
13592
  };
13138
13593
  }
13139
13594
  /** Subscribe to one event type across all subagents. */
@@ -13146,6 +13601,7 @@ var FleetBus = class {
13146
13601
  set.add(handler);
13147
13602
  return () => {
13148
13603
  set.delete(handler);
13604
+ if (set.size === 0) this.byType.delete(type);
13149
13605
  };
13150
13606
  }
13151
13607
  /** Subscribe to literally everything. The fleet roll-up uses this. */
@@ -13179,6 +13635,11 @@ var FleetBus = class {
13179
13635
  }
13180
13636
  }
13181
13637
  }
13638
+ clear() {
13639
+ this.byId.clear();
13640
+ this.byType.clear();
13641
+ this.any.clear();
13642
+ }
13182
13643
  };
13183
13644
  var FleetUsageAggregator = class {
13184
13645
  constructor(bus, priceLookup, metaLookup) {
@@ -14411,7 +14872,9 @@ var Director = class _Director {
14411
14872
  let toolUses = 0;
14412
14873
  for (const line of targetLines) {
14413
14874
  try {
14414
- const ev = JSON.parse(line);
14875
+ const parsed = safeParse(line);
14876
+ if (!parsed.ok || !parsed.value) continue;
14877
+ const ev = parsed.value;
14415
14878
  if (ev.type === "assistant" && typeof ev.text === "string") {
14416
14879
  lastAssistantText = ev.text;
14417
14880
  } else if (ev.type === "stop" && ev.stopReason) {
@@ -14725,7 +15188,7 @@ function createDelegateTool(opts) {
14725
15188
  const offIter = dir.fleet.filter("iteration.started", bump);
14726
15189
  const offProgress = dir.fleet.filter("tool.progress", bump);
14727
15190
  arm();
14728
- dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __timeout: true })).catch(() => finish({ __timeout: true }));
15191
+ dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
14729
15192
  });
14730
15193
  if ("__timeout" in result) {
14731
15194
  const partial2 = await readSubagentPartial(opts, subagentId);
@@ -14750,6 +15213,29 @@ function createDelegateTool(opts) {
14750
15213
  partial: partial2
14751
15214
  };
14752
15215
  }
15216
+ if ("__emptyResult" in result) {
15217
+ const partial2 = await readSubagentPartial(opts, subagentId);
15218
+ opts.events?.emit("delegate.completed", {
15219
+ target,
15220
+ task: i.task,
15221
+ ok: false,
15222
+ status: "empty_result",
15223
+ summary: `[${target}] completed without a task result`,
15224
+ durationMs: 0,
15225
+ iterations: partial2?.events ?? 0,
15226
+ toolCalls: partial2?.toolUsesObserved ?? 0,
15227
+ subagentId
15228
+ });
15229
+ return {
15230
+ ok: false,
15231
+ stopReason: "error",
15232
+ error: "Director returned no task result for the delegated task.",
15233
+ hint: "Check fleet state with /fleet status, then retry or reassign the task.",
15234
+ subagentId,
15235
+ taskId,
15236
+ partial: partial2
15237
+ };
15238
+ }
14753
15239
  const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
14754
15240
  const partial = result.status === "success" ? void 0 : await readSubagentPartial(opts, subagentId);
14755
15241
  const errorKind = result.error?.kind;
@@ -14922,7 +15408,9 @@ async function readSubagentPartial(opts, subagentId) {
14922
15408
  let toolUses = 0;
14923
15409
  for (const line of lines) {
14924
15410
  try {
14925
- const ev = JSON.parse(line);
15411
+ const parsed = safeParse(line);
15412
+ if (!parsed.ok || !parsed.value) continue;
15413
+ const ev = parsed.value;
14926
15414
  if (ev.type === "tool_use") toolUses += 1;
14927
15415
  if (ev.type === "llm_response") {
14928
15416
  if (typeof ev.stopReason === "string") lastStopReason = ev.stopReason;
@@ -15694,8 +16182,8 @@ async function loadProjectModes(modesDir) {
15694
16182
  for (const entry of entries) {
15695
16183
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
15696
16184
  const filePath = path3.join(modesDir, entry);
15697
- const stat5 = await fsp.stat(filePath);
15698
- if (!stat5.isFile()) continue;
16185
+ const stat6 = await fsp.stat(filePath);
16186
+ if (!stat6.isFile()) continue;
15699
16187
  const content = await fsp.readFile(filePath, "utf8");
15700
16188
  const id = path3.basename(entry, path3.extname(entry));
15701
16189
  modes.push({
@@ -16835,12 +17323,6 @@ var TaskGraphStore = class {
16835
17323
  };
16836
17324
 
16837
17325
  // src/sdd/spec-builder.ts
16838
- function expectDefined11(value) {
16839
- if (value === null || value === void 0) {
16840
- throw new Error("Expected value to be defined");
16841
- }
16842
- return value;
16843
- }
16844
17326
  function buildQuestioningPrompt(session, min, max) {
16845
17327
  const answered = session.answers.length;
16846
17328
  const remaining = Math.max(0, min - answered);
@@ -16886,7 +17368,7 @@ function buildQuestioningPrompt(session, min, max) {
16886
17368
  if (answered > 0) {
16887
17369
  lines.push("", "**Conversation so far:**");
16888
17370
  for (let i = 0; i < answered; i++) {
16889
- const a = expectDefined11(session.answers[i]);
17371
+ const a = expectDefined(session.answers[i]);
16890
17372
  lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
16891
17373
  }
16892
17374
  }
@@ -17628,12 +18110,6 @@ function truncate2(str, maxLen) {
17628
18110
  }
17629
18111
 
17630
18112
  // src/sdd/critical-path.ts
17631
- function expectDefined12(value) {
17632
- if (value === null || value === void 0) {
17633
- throw new Error("Expected value to be defined");
17634
- }
17635
- return value;
17636
- }
17637
18113
  function analyzeCriticalPath(graph) {
17638
18114
  const nodes = Array.from(graph.nodes.values());
17639
18115
  const topoOrder = topologicalSort(graph);
@@ -17709,7 +18185,7 @@ function getTransitiveBlocked(_graph, taskId, blocksMap) {
17709
18185
  const visited = /* @__PURE__ */ new Set();
17710
18186
  const queue = [taskId];
17711
18187
  while (queue.length > 0) {
17712
- const current = expectDefined12(queue.shift());
18188
+ const current = expectDefined(queue.shift());
17713
18189
  const blocked = blocksMap.get(current);
17714
18190
  if (!blocked) continue;
17715
18191
  for (const id of blocked) {
@@ -17755,7 +18231,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17755
18231
  if (!changed) break;
17756
18232
  }
17757
18233
  let maxDist = 0;
17758
- let maxId = expectDefined12(allIds[0]);
18234
+ let maxId = expectDefined(allIds[0]);
17759
18235
  for (const id of allIds) {
17760
18236
  const d = dist.get(id) ?? 0;
17761
18237
  if (d > maxDist) {
@@ -18224,12 +18700,6 @@ var SddTaskDecomposer = class {
18224
18700
  return nodes.some((n) => n.status === "blocked");
18225
18701
  }
18226
18702
  };
18227
- function expectDefined13(value) {
18228
- if (value === null || value === void 0) {
18229
- throw new Error("Expected value to be defined");
18230
- }
18231
- return value;
18232
- }
18233
18703
  var SddParallelRun = class {
18234
18704
  constructor(opts) {
18235
18705
  this.opts = opts;
@@ -18378,8 +18848,8 @@ var SddParallelRun = class {
18378
18848
  const successCount = results.filter((r) => r.status === "success").length;
18379
18849
  const failCount = results.length - successCount;
18380
18850
  for (let i = 0; i < results.length; i++) {
18381
- const result = expectDefined13(results[i]);
18382
- const taskId = expectDefined13(taskIds[i]);
18851
+ const result = expectDefined(results[i]);
18852
+ const taskId = expectDefined(taskIds[i]);
18383
18853
  if (result.status === "success") {
18384
18854
  this.opts.tracker.updateNodeStatus(taskId, "completed");
18385
18855
  } else {