@wrongstack/core 0.89.1 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/dist/{agent-bridge-B8F2ORwg.d.ts → agent-bridge-mOxbpFcg.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-CQaLFNnA.d.ts → agent-subagent-runner-DukQLUcS.d.ts} +433 -433
  3. package/dist/{events-DzyLtja6.d.ts → brain-Dfv4Y82E.d.ts} +179 -92
  4. package/dist/{compactor-CJq7LQev.d.ts → compactor-DXLxLcmU.d.ts} +2 -2
  5. package/dist/{config-_DZ7dN-T.d.ts → config-BSU-6vah.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 +128 -37
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +34 -35
  11. package/dist/defaults/index.js +752 -218
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/{director-state-CgIc30qi.d.ts → director-state-BfeCUbmk.d.ts} +1 -1
  14. package/dist/execution/index.d.ts +65 -370
  15. package/dist/execution/index.js +446 -53
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/extension/index.d.ts +9 -10
  18. package/dist/extension/index.js +8 -6
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/goal-preamble-CI8lxeY1.d.ts +378 -0
  21. package/dist/{goal-store-DvWLNu52.d.ts → goal-store-ht0VmR1A.d.ts} +63 -21
  22. package/dist/{index-Ca_lOmKl.d.ts → index-BWRN6wOb.d.ts} +13 -15
  23. package/dist/{index-Dx-CnrDN.d.ts → index-DIKEcfgC.d.ts} +9 -10
  24. package/dist/index.d.ts +188 -176
  25. package/dist/index.js +1508 -407
  26. package/dist/index.js.map +1 -1
  27. package/dist/infrastructure/index.d.ts +9 -9
  28. package/dist/infrastructure/index.js +4 -2
  29. package/dist/infrastructure/index.js.map +1 -1
  30. package/dist/kernel/index.d.ts +14 -16
  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-CXCsANdY.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-Da9nd8Ic.d.ts → multi-agent-coordinator-51LvnXkD.d.ts} +2 -2
  42. package/dist/{null-fleet-bus-CHJOXv3t.d.ts → null-fleet-bus-D09hMzFQ.d.ts} +32 -31
  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-DyMCj2Pw.d.ts → parallel-eternal-engine-CUtmM_0V.d.ts} +54 -35
  46. package/dist/{path-resolver-TjBlRKyH.d.ts → path-resolver-DDJiMAtX.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-BqiA_UMr.d.ts} +73 -73
  50. package/dist/{plan-templates-D9UhwoGh.d.ts → plan-templates-BdDxl9cI.d.ts} +79 -11
  51. package/dist/{provider-runner-BEPWwkxH.d.ts → provider-runner-BUunikwY.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 +17 -33
  54. package/dist/sdd/index.js +145 -36
  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 +4 -2
  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-BpJ5trO9.d.ts} +2 -2
  62. package/dist/{session-reader-BQU-toaN.d.ts → session-reader-DDz1Ek4V.d.ts} +2 -2
  63. package/dist/{skill-BJeF2DwY.d.ts → skill-Bj6Ezqb8.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/spec-TBi3Jr6T.d.ts +78 -0
  68. package/dist/storage/index.d.ts +145 -13
  69. package/dist/storage/index.js +990 -172
  70. package/dist/storage/index.js.map +1 -1
  71. package/dist/task-format-vGOIftmK.d.ts +23 -0
  72. package/dist/task-graph-u1q9Jkyk.d.ts +84 -0
  73. package/dist/types/index.d.ts +27 -28
  74. package/dist/types/index.js +19 -31
  75. package/dist/types/index.js.map +1 -1
  76. package/dist/utils/expect-defined.d.ts +7 -0
  77. package/dist/utils/expect-defined.js +11 -0
  78. package/dist/utils/expect-defined.js.map +1 -0
  79. package/dist/utils/index.d.ts +8 -20
  80. package/dist/utils/index.js +120 -41
  81. package/dist/utils/index.js.map +1 -1
  82. package/dist/{wstack-paths-BQMvEllz.d.ts → wstack-paths-_lqjzErq.d.ts} +1 -1
  83. package/package.json +5 -1
  84. package/skills/tech-stack/SKILL.md +177 -0
  85. package/dist/memory-CEXuo7sz.d.ts +0 -16
  86. package/dist/secret-scrubber-3MHDDAtm.d.ts +0 -6
  87. package/dist/task-graph-CikNdRTG.d.ts +0 -161
@@ -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
  };
@@ -294,16 +289,18 @@ function formatCtx(ctx) {
294
289
  }
295
290
  }
296
291
 
297
- // src/storage/session-store.ts
298
- init_atomic_write();
299
-
300
- // src/utils/message-invariants.ts
301
- function expectDefined(value) {
292
+ // src/utils/expect-defined.ts
293
+ function expectDefined(value, label) {
302
294
  if (value === null || value === void 0) {
303
295
  throw new Error("Expected value to be defined");
304
296
  }
305
297
  return value;
306
298
  }
299
+
300
+ // src/storage/session-store.ts
301
+ init_atomic_write();
302
+
303
+ // src/utils/message-invariants.ts
307
304
  function repairToolUseAdjacency(messages) {
308
305
  const removedToolUses = [];
309
306
  const removedToolResults = [];
@@ -396,12 +393,6 @@ function isEmptyMessage(msg) {
396
393
  }
397
394
 
398
395
  // src/storage/session-store.ts
399
- function expectDefined2(value) {
400
- if (value === null || value === void 0) {
401
- throw new Error("Expected value to be defined");
402
- }
403
- return value;
404
- }
405
396
  function sanitizeModel(model) {
406
397
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
407
398
  }
@@ -1112,7 +1103,7 @@ var FileSessionWriter = class {
1112
1103
  let targetCheckpointLine = -1;
1113
1104
  let afterTarget = false;
1114
1105
  for (let i = 0; i < lines.length; i++) {
1115
- const line = expectDefined2(lines[i]);
1106
+ const line = expectDefined(lines[i]);
1116
1107
  if (!line.trim()) continue;
1117
1108
  let event;
1118
1109
  try {
@@ -1408,11 +1399,235 @@ function mergeAdjacentText(blocks) {
1408
1399
  return out;
1409
1400
  }
1410
1401
 
1411
- // 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
1412
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
1413
1626
  var MAX_BYTES_TOTAL = 32e3;
1414
1627
  var DefaultMemoryStore = class {
1415
1628
  files;
1629
+ events;
1630
+ backend;
1416
1631
  /**
1417
1632
  * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
1418
1633
  * `clear` are read-modify-write against a single file; without a lock,
@@ -1429,12 +1644,29 @@ var DefaultMemoryStore = class {
1429
1644
  writeChain = /* @__PURE__ */ new Map();
1430
1645
  /** Last write error per scope — surfaced as warnings on the next readAll(). */
1431
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;
1432
1655
  constructor(opts) {
1433
1656
  this.files = {
1434
1657
  "project-agents": opts.paths.inProjectAgentsFile,
1435
1658
  "project-memory": opts.paths.projectMemory,
1436
1659
  "user-memory": opts.paths.globalMemory
1437
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;
1438
1670
  }
1439
1671
  async runSerialized(scope, work) {
1440
1672
  const prior = this.writeChain.get(scope) ?? Promise.resolve();
@@ -1461,7 +1693,7 @@ var DefaultMemoryStore = class {
1461
1693
  if (writeErr2) {
1462
1694
  parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
1463
1695
  }
1464
- const body = await this.read(scope);
1696
+ const body = await this.backend.readAll(scope, this.files[scope]);
1465
1697
  if (body.trim()) parts.push(`## ${labelOf(scope)}
1466
1698
 
1467
1699
  ${body.trim()}`);
@@ -1469,117 +1701,214 @@ ${body.trim()}`);
1469
1701
  return parts.join("\n\n");
1470
1702
  }
1471
1703
  async read(scope) {
1472
- try {
1473
- return await fsp.readFile(this.files[scope], "utf8");
1474
- } catch {
1475
- 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);
1476
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);
1477
1725
  }
1478
- async remember(text, scope = "project-memory") {
1726
+ async remember(text, scope = "project-memory", metadata) {
1727
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
1479
1728
  return this.runSerialized(scope, async () => {
1480
- const file = this.files[scope];
1481
- await ensureDir(path3.dirname(file));
1482
- let existing = "";
1483
- try {
1484
- existing = await fsp.readFile(file, "utf8");
1485
- } catch {
1486
- }
1487
- const ts = (/* @__PURE__ */ new Date()).toISOString();
1488
- const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
1489
- const entry = `
1490
- - [${ts}] ${id} ${text.replace(/\n/g, " ")}
1491
- `;
1492
- const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
1493
- ${entry}`;
1494
- await atomicWrite(file, next);
1495
- const buf = Buffer.byteLength(next, "utf8");
1496
- if (buf > MAX_BYTES_TOTAL) {
1497
- 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
+ }
1498
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
+ });
1499
1750
  });
1500
1751
  }
1501
- async forget(query, scope = "project-memory") {
1502
- return this.runSerialized(scope, async () => this.forgetUnsafe(query, scope));
1503
- }
1504
- async forgetUnsafe(query, scope) {
1505
- const file = this.files[scope];
1506
- let existing;
1507
- try {
1508
- existing = await fsp.readFile(file, "utf8");
1509
- } catch {
1510
- return 0;
1511
- }
1512
- const needle = query.toLowerCase();
1513
- const idMatcher = /mem_\d+_\w+/;
1514
- let removed = 0;
1515
- const lines = existing.split("\n").filter((line) => {
1516
- const trimmed = line.trim();
1517
- if (!trimmed.startsWith("- ")) return true;
1518
- if (idMatcher.test(query)) {
1519
- const afterBracket = trimmed.indexOf("] ");
1520
- if (afterBracket !== -1) {
1521
- const afterTs = trimmed.slice(afterBracket + 2);
1522
- const entryIdMatch = /^mem_\d+_\w+/.exec(afterTs);
1523
- if (entryIdMatch && entryIdMatch[0] === query) {
1524
- removed++;
1525
- return false;
1526
- }
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;
1527
1778
  }
1528
1779
  }
1529
- if (trimmed.toLowerCase().includes(needle)) {
1530
- removed++;
1531
- 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
+ });
1532
1847
  }
1533
- return true;
1534
- });
1535
- if (removed > 0) {
1536
- await atomicWrite(file, lines.join("\n"));
1537
1848
  }
1538
- 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));
1539
1855
  }
1540
- async consolidate(scope) {
1541
- 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
+ });
1542
1869
  }
1543
- async consolidateUnsafe(scope) {
1544
- const file = this.files[scope];
1545
- let existing;
1546
- try {
1547
- existing = await fsp.readFile(file, "utf8");
1548
- } catch {
1549
- return;
1550
- }
1551
- const seen = /* @__PURE__ */ new Set();
1552
- const lines = existing.split("\n").filter((line) => {
1553
- const trimmed = line.trim();
1554
- if (!trimmed.startsWith("- ")) return true;
1555
- const norm = trimmed.replace(/\[[^\]]+\]/, "").replace(/\bmem_\d+_\w+\s*/, "").trim().toLowerCase();
1556
- if (seen.has(norm)) return false;
1557
- seen.add(norm);
1558
- 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
+ }
1559
1880
  });
1560
- const next = lines.join("\n");
1561
- const backup = `${file}.bak.${Date.now()}`;
1562
- try {
1563
- await fsp.copyFile(file, backup);
1564
- } catch {
1565
- }
1566
- try {
1567
- await atomicWrite(file, next);
1568
- } catch {
1569
- return;
1570
- }
1571
1881
  }
1572
1882
  async clear(scope) {
1573
1883
  if (scope) {
1574
- 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
+ });
1575
1889
  return;
1576
1890
  }
1577
1891
  await Promise.all(
1578
1892
  ["project-agents", "project-memory", "user-memory"].map(
1579
- (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
+ })
1580
1898
  )
1581
1899
  );
1582
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
+ }
1583
1912
  };
1584
1913
  function labelOf(scope) {
1585
1914
  switch (scope) {
@@ -2295,12 +2624,6 @@ function compileUserRegex(pattern, flags) {
2295
2624
  }
2296
2625
 
2297
2626
  // src/storage/session-reader.ts
2298
- function expectDefined3(value) {
2299
- if (value === null || value === void 0) {
2300
- throw new Error("Expected value to be defined");
2301
- }
2302
- return value;
2303
- }
2304
2627
  var DefaultSessionReader = class {
2305
2628
  store;
2306
2629
  constructor(opts) {
@@ -2362,7 +2685,7 @@ var DefaultSessionReader = class {
2362
2685
  continue;
2363
2686
  }
2364
2687
  for (let i = 0; i < data.events.length; i++) {
2365
- const ev = expectDefined3(data.events[i]);
2688
+ const ev = expectDefined(data.events[i]);
2366
2689
  if (allowedTypes && !allowedTypes.has(ev.type)) continue;
2367
2690
  const text = eventText(ev);
2368
2691
  if (text === null) continue;
@@ -2811,7 +3134,10 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
2811
3134
  let pending = null;
2812
3135
  let writeChain = Promise.resolve();
2813
3136
  const enqueueWrite = (todos) => {
2814
- 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
+ });
2815
3141
  return writeChain;
2816
3142
  };
2817
3143
  const flush = () => {
@@ -3115,7 +3441,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
3115
3441
  }
3116
3442
  const lock = {
3117
3443
  pid: processId,
3118
- hostname: __require("os").hostname(),
3444
+ hostname: hostname(),
3119
3445
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3120
3446
  };
3121
3447
  await atomicWrite(lockPath, JSON.stringify(lock), { mode: 384 });
@@ -3641,12 +3967,6 @@ function getDangerousCapabilities(toolOrCaps) {
3641
3967
  init_atomic_write();
3642
3968
 
3643
3969
  // src/utils/glob-match.ts
3644
- function expectDefined4(value) {
3645
- if (value === null || value === void 0) {
3646
- throw new Error("Expected value to be defined");
3647
- }
3648
- return value;
3649
- }
3650
3970
  function escapeRegex(s) {
3651
3971
  return s.replace(/[.+^${}()|\\]/g, "\\$&");
3652
3972
  }
@@ -3658,7 +3978,7 @@ function getCachedGlob(pattern) {
3658
3978
  if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
3659
3979
  const keys = [...COMPILED_GLOB_CACHE.keys()];
3660
3980
  for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
3661
- COMPILED_GLOB_CACHE.delete(expectDefined4(keys[i]));
3981
+ COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
3662
3982
  }
3663
3983
  }
3664
3984
  const re = compileGlob(pattern);
@@ -4523,12 +4843,6 @@ function parseDescription(raw) {
4523
4843
  }
4524
4844
 
4525
4845
  // src/utils/json-repair.ts
4526
- function expectDefined5(value) {
4527
- if (value === null || value === void 0) {
4528
- throw new Error("Expected value to be defined");
4529
- }
4530
- return value;
4531
- }
4532
4846
  function completePartialObject(s) {
4533
4847
  if (!s.trim().startsWith("{")) return s;
4534
4848
  if (tryParse(s).ok) return s;
@@ -4540,7 +4854,7 @@ function completePartialObject(s) {
4540
4854
  let contentEnd = 0;
4541
4855
  let stringBraceDepth = 0;
4542
4856
  for (let i = 0; i < s.length; i++) {
4543
- const ch = expectDefined5(s[i]);
4857
+ const ch = expectDefined(s[i]);
4544
4858
  if (inString) {
4545
4859
  contentEnd = i + 1;
4546
4860
  if (escaped) {
@@ -4926,12 +5240,6 @@ var DefaultProviderRunner = class {
4926
5240
  };
4927
5241
 
4928
5242
  // src/utils/token-estimate.ts
4929
- function expectDefined6(value) {
4930
- if (value === null || value === void 0) {
4931
- throw new Error("Expected value to be defined");
4932
- }
4933
- return value;
4934
- }
4935
5243
  var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
4936
5244
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
4937
5245
  var ESTIMATE_CACHE_MAX_SIZE = 1e4;
@@ -4941,7 +5249,7 @@ function getCachedEstimate(key, compute) {
4941
5249
  if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
4942
5250
  const keys = [...ESTIMATE_CACHE.keys()];
4943
5251
  for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
4944
- ESTIMATE_CACHE.delete(expectDefined6(keys[i]));
5252
+ ESTIMATE_CACHE.delete(expectDefined(keys[i]));
4945
5253
  }
4946
5254
  }
4947
5255
  const estimate = compute();
@@ -5434,12 +5742,6 @@ var IntelligentCompactor = class {
5434
5742
  };
5435
5743
 
5436
5744
  // src/models/llm-selector.ts
5437
- function expectDefined7(value) {
5438
- if (value === null || value === void 0) {
5439
- throw new Error("Expected value to be defined");
5440
- }
5441
- return value;
5442
- }
5443
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.
5444
5746
 
5445
5747
  Output a JSON object with this structure:
@@ -5480,7 +5782,7 @@ function formatMessages(messages, maxChars = 8e3) {
5480
5782
  const lines = [];
5481
5783
  let used = 0;
5482
5784
  for (let i = 0; i < messages.length; i++) {
5483
- const m = expectDefined7(messages[i]);
5785
+ const m = expectDefined(messages[i]);
5484
5786
  const role = m.role.padEnd(10, " ");
5485
5787
  let text;
5486
5788
  if (typeof m.content === "string") {
@@ -5545,7 +5847,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
5545
5847
  let tokenCount = 0;
5546
5848
  let startIdx = 0;
5547
5849
  for (let i = messages.length - 1; i >= 0; i--) {
5548
- const m = expectDefined7(messages[i]);
5850
+ const m = expectDefined(messages[i]);
5549
5851
  const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
5550
5852
  (acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
5551
5853
  0
@@ -6152,12 +6454,6 @@ function createToolOutputSerializer(opts = {}) {
6152
6454
  }
6153
6455
 
6154
6456
  // src/execution/tool-executor.ts
6155
- function expectDefined8(value) {
6156
- if (value === null || value === void 0) {
6157
- throw new Error("Expected value to be defined");
6158
- }
6159
- return value;
6160
- }
6161
6457
  var ToolExecutor = class {
6162
6458
  constructor(registry, opts) {
6163
6459
  this.registry = registry;
@@ -6564,7 +6860,7 @@ function hasMalformedArguments(input) {
6564
6860
  function extractMalformedRaw(input) {
6565
6861
  if (!hasMalformedArguments(input)) return void 0;
6566
6862
  const obj = input;
6567
- const value = obj[expectDefined8(Object.keys(obj)[0])];
6863
+ const value = obj[expectDefined(Object.keys(obj)[0])];
6568
6864
  if (value === void 0 || value === null) return void 0;
6569
6865
  if (typeof value === "string") return value;
6570
6866
  try {
@@ -6788,12 +7084,58 @@ function appendJournal(goal, entry) {
6788
7084
  journal: trimmed
6789
7085
  };
6790
7086
  }
7087
+ function parseProgressFromText(text) {
7088
+ const re = /\[progress:\s*(\d{1,3})%\]\s*(?:[—\-]\s*(.+))?/i;
7089
+ const m = text.match(re);
7090
+ if (!m) return null;
7091
+ const progress = Math.min(100, Math.max(0, Number.parseInt(m[1], 10)));
7092
+ const note = m[2]?.trim() || void 0;
7093
+ return { progress, note };
7094
+ }
7095
+ function recordProgress(goal, progress, note) {
7096
+ const clamped = Math.min(100, Math.max(0, progress));
7097
+ const history = [...goal.progressHistory ?? [], { at: (/* @__PURE__ */ new Date()).toISOString(), progress: clamped, note }];
7098
+ const trimmed = history.length > 200 ? history.slice(-200) : history;
7099
+ return {
7100
+ ...goal,
7101
+ progress: clamped,
7102
+ progressNote: note ?? `${clamped}% complete`,
7103
+ progressHistory: trimmed,
7104
+ progressTrend: computeTrend(trimmed)
7105
+ };
7106
+ }
7107
+ function computeTrend(history) {
7108
+ if (history.length < 3) return void 0;
7109
+ const recent = history.slice(-5);
7110
+ const deltas = [];
7111
+ for (let i = 1; i < recent.length; i++) {
7112
+ deltas.push(recent[i].progress - recent[i - 1].progress);
7113
+ }
7114
+ if (deltas.length < 2) return void 0;
7115
+ const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length;
7116
+ if (avgDelta > 2) return "accelerating";
7117
+ if (avgDelta < -1) return "stalling";
7118
+ return "steady";
7119
+ }
6791
7120
 
6792
7121
  // src/utils/sleep.ts
6793
7122
  function sleep(ms) {
6794
7123
  return new Promise((resolve5) => setTimeout(resolve5, ms));
6795
7124
  }
6796
7125
 
7126
+ // src/execution/autonomy-brain.ts
7127
+ function formatDecisionSummary(decision, request) {
7128
+ const question = request.question.length > 80 ? request.question.slice(0, 77) + "\u2026" : request.question;
7129
+ if (decision.type === "deny") {
7130
+ return `\u{1F9E0} Brain: DENIED \u2014 "${question}" \u2192 ${decision.reason}`;
7131
+ }
7132
+ if (decision.type === "answer") {
7133
+ const action = decision.optionId ? `chose [${decision.optionId}]` : decision.text.length > 60 ? decision.text.slice(0, 57) + "\u2026" : decision.text;
7134
+ return `\u{1F9E0} Brain: DECIDED \u2014 "${question}" \u2192 ${action}`;
7135
+ }
7136
+ return `\u{1F9E0} Brain: ASKED HUMAN \u2014 "${question}"`;
7137
+ }
7138
+
6797
7139
  // src/execution/eternal-autonomy.ts
6798
7140
  var execFileP = promisify(execFile);
6799
7141
  var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
@@ -6863,7 +7205,7 @@ var EternalAutonomyEngine = class {
6863
7205
  this.consecutiveFailures = 0;
6864
7206
  }
6865
7207
  if (this.stopRequested) break;
6866
- await sleep(this.opts.cycleGapMs ?? 1e3);
7208
+ await sleep(this.opts.cycleGapMs ?? 0);
6867
7209
  }
6868
7210
  } finally {
6869
7211
  this.state = "stopped";
@@ -7033,6 +7375,18 @@ var EternalAutonomyEngine = class {
7033
7375
  this.stopRequested = true;
7034
7376
  return true;
7035
7377
  }
7378
+ const parsed = parseProgressFromText(finalText);
7379
+ if (parsed) {
7380
+ await this.updateProgress(parsed.progress, parsed.note);
7381
+ if (parsed.progress >= 100) {
7382
+ await this.markGoalCompleted(
7383
+ { source: action.source, task: action.task, directive: action.directive },
7384
+ `progress reached 100%: ${parsed.note ?? "goal complete"}`
7385
+ );
7386
+ this.stopRequested = true;
7387
+ return true;
7388
+ }
7389
+ }
7036
7390
  this.iterationsSinceCompact++;
7037
7391
  await this.maybeCompact().catch((err) => {
7038
7392
  this.opts.onError?.(
@@ -7119,11 +7473,16 @@ var EternalAutonomyEngine = class {
7119
7473
  this.consecutiveBrainstormDone++;
7120
7474
  const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
7121
7475
  if (this.consecutiveBrainstormDone >= threshold) {
7122
- await this.markGoalCompleted(
7123
- { source: "brainstorm", task: "no further work", directive: "" },
7124
- `brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
7125
- );
7126
- this.stopRequested = true;
7476
+ const shouldStop = await this.consultBrainForDone(goal);
7477
+ if (shouldStop) {
7478
+ await this.markGoalCompleted(
7479
+ { source: "brainstorm", task: "no further work", directive: "" },
7480
+ `brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row${this.opts.brain ? ", brain confirmed" : ""}`
7481
+ );
7482
+ this.stopRequested = true;
7483
+ } else {
7484
+ this.consecutiveBrainstormDone = 0;
7485
+ }
7127
7486
  }
7128
7487
  return null;
7129
7488
  }
@@ -7357,6 +7716,69 @@ ${recentJournal}` : "No prior iterations.",
7357
7716
  async appendFailure(task, note) {
7358
7717
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
7359
7718
  }
7719
+ /**
7720
+ * Consult the brain on whether the goal is truly complete.
7721
+ * Without a brain, always returns true (use heuristic: DONE threshold met = done).
7722
+ */
7723
+ async consultBrainForDone(goal) {
7724
+ if (!this.opts.brain) return true;
7725
+ const deliverablesStatus = goal.deliverables?.length ? `
7726
+ Deliverables: ${goal.deliverables.length} total, progress ${goal.progress ?? "unknown"}%` : "";
7727
+ const recentJournal = goal.journal.slice(-5).map(
7728
+ (e) => ` #${e.iteration} [${e.status}] ${e.task}`
7729
+ ).join("\n");
7730
+ try {
7731
+ const decision = await this.opts.brain.decide({
7732
+ id: `goal-done-${goal.iterations}`,
7733
+ source: "system",
7734
+ question: `Brainstorm returned DONE ${this.consecutiveBrainstormDone}x. Is the goal truly complete?`,
7735
+ context: [
7736
+ `Goal: ${goal.goal}`,
7737
+ `Iterations: ${goal.iterations}`,
7738
+ `Progress: ${goal.progress ?? "unknown"}%`,
7739
+ deliverablesStatus,
7740
+ recentJournal ? `
7741
+ Recent work:
7742
+ ${recentJournal}` : ""
7743
+ ].join("\n"),
7744
+ risk: "high",
7745
+ fallback: "continue"
7746
+ });
7747
+ const summary = formatDecisionSummary(decision, {
7748
+ id: `goal-done-${goal.iterations}`,
7749
+ source: "system",
7750
+ question: "Is the goal complete?",
7751
+ risk: "high",
7752
+ fallback: "continue"
7753
+ });
7754
+ const rationale = "rationale" in decision ? decision.rationale : void 0;
7755
+ await this.appendIterationEntry({
7756
+ source: "brainstorm",
7757
+ task: summary,
7758
+ status: "success",
7759
+ note: rationale
7760
+ });
7761
+ if (decision.type === "deny") {
7762
+ return false;
7763
+ }
7764
+ if (decision.type === "answer") {
7765
+ const text = decision.text.toLowerCase();
7766
+ return text.includes("complete") || text.includes("done") || text.includes("yes");
7767
+ }
7768
+ return true;
7769
+ } catch {
7770
+ return true;
7771
+ }
7772
+ }
7773
+ /**
7774
+ * Persist a progress update from the agent's [PROGRESS: N%] output.
7775
+ */
7776
+ async updateProgress(progress, note) {
7777
+ const current = await loadGoal(this.goalPath);
7778
+ if (!current) return;
7779
+ const updated = recordProgress(current, progress, note);
7780
+ await saveGoal(this.goalPath, updated);
7781
+ }
7360
7782
  async persistEngineState(state) {
7361
7783
  const current = await loadGoal(this.goalPath);
7362
7784
  if (!current) return;
@@ -7722,15 +8144,7 @@ function makeAgentSubagentRunner(opts) {
7722
8144
  let budgetError = null;
7723
8145
  const onBudgetError = (err) => {
7724
8146
  if (err instanceof BudgetThresholdSignal) {
7725
- err.decision.then((decision) => {
7726
- if (decision === "stop") {
7727
- budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
7728
- aborter.abort();
7729
- }
7730
- }).catch(() => {
7731
- budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
7732
- aborter.abort();
7733
- });
8147
+ budgetError = err;
7734
8148
  return;
7735
8149
  }
7736
8150
  aborter.abort();
@@ -7838,15 +8252,23 @@ function makeAgentSubagentRunner(opts) {
7838
8252
  }
7839
8253
  if (budgetError) {
7840
8254
  if ("decision" in budgetError) {
7841
- const decision = await budgetError.decision;
7842
- if (decision === "stop") {
8255
+ try {
8256
+ const decision = await budgetError.decision;
8257
+ if (decision === "stop") {
8258
+ budgetError = new BudgetExceededError(
8259
+ budgetError.kind,
8260
+ budgetError.limit,
8261
+ budgetError.used
8262
+ );
8263
+ } else {
8264
+ budgetError = null;
8265
+ }
8266
+ } catch {
7843
8267
  budgetError = new BudgetExceededError(
7844
8268
  budgetError.kind,
7845
8269
  budgetError.limit,
7846
8270
  budgetError.used
7847
8271
  );
7848
- } else {
7849
- budgetError = null;
7850
8272
  }
7851
8273
  }
7852
8274
  if (budgetError) throw budgetError;
@@ -10010,6 +10432,109 @@ Working rules:
10010
10432
  "cloud cost"
10011
10433
  ]
10012
10434
  }
10435
+ },
10436
+ {
10437
+ config: {
10438
+ id: "tech-stack",
10439
+ name: "Tech Stack Validator",
10440
+ role: "tech-stack",
10441
+ tools: ["search", "fetch", "read", "grep", "glob", "outdated", "audit", "json"],
10442
+ prompt: `You are the Tech Stack Validator \u2014 a single-shot validation agent that fires
10443
+ before any package, library, or framework choice is committed.
10444
+
10445
+ Your ONLY job: verify that a technology choice is current, real, and not obsolete.
10446
+ You are the "this isn't code, this is 10-year-old technology" agent. Intervene
10447
+ hard when the LLM hallucinates a version number or suggests dead tech.
10448
+
10449
+ ## Critical rules
10450
+
10451
+ 1. **Verify existence.** Search npm registry (fetch https://registry.npmjs.org/<pkg>/latest)
10452
+ or web search. A package that doesn't exist = hallucination.
10453
+
10454
+ 2. **Check latest version.** Never trust any version number from the model. Always
10455
+ fetch the actual latest stable version from npm or the project's release page.
10456
+
10457
+ 3. **Reject dead packages.** No release in >2 years + unresolved critical issues =
10458
+ dead. Suggest a maintained replacement.
10459
+
10460
+ 4. **Reject prehistoric tech.** Any package/pattern superseded \u22655 years ago is
10461
+ REJECTED. Key blocklist:
10462
+ - axios / node-fetch / got / request \u2192 native fetch (Node 18+)
10463
+ - moment \u2192 date-fns / luxon / Temporal
10464
+ - jQuery (new projects) \u2192 vanilla DOM / React
10465
+ - Gulp / Grunt \u2192 tsup / esbuild / vite
10466
+ - CoffeeScript / Flow \u2192 TypeScript
10467
+ - Bluebird \u2192 native Promises
10468
+ - crypto-js \u2192 node:crypto / Web Crypto
10469
+ - Bower \u2192 npm/pnpm
10470
+ - underscore \u2192 lodash or native ES2020+
10471
+
10472
+ 5. **The intervention phrase.** When rejecting on age grounds, you MUST output
10473
+ exactly: "This isn't code, this is X-year-old technology." where X =
10474
+ current year \u2212 the year the technology was made obsolete. Follow with
10475
+ what replaced it and a one-step migration path.
10476
+
10477
+ 6. **Prefer built-in over third-party.** Check Node 22+ native APIs first:
10478
+ node:test, node:sqlite, fetch, WebSocket, Web Crypto \u2014 all built-in.
10479
+
10480
+ ## Workflow (single-shot \u2014 do NOT loop)
10481
+
10482
+ 1. Receive the proposed package + version
10483
+ 2. Search npm registry or web for the latest version
10484
+ 3. Check age, maintenance status, deprecation
10485
+ 4. Output verdict: APPROVED (with exact version) or REJECTED (with replacement)
10486
+
10487
+ ## Output format
10488
+
10489
+ ### Tech Stack Validation \u2014 <package>
10490
+
10491
+ **Status**: APPROVED | REJECTED
10492
+
10493
+ **Package**: <name>@<version>
10494
+ **Source**: <URL you checked \u2014 npm registry, GitHub, web search>
10495
+ **Age**: <first release> \u2014 <last release date>
10496
+ **Verdict**: 1\u20132 sentence explanation.
10497
+
10498
+ When REJECTED on age:
10499
+ **"This isn't code, this is X-year-old technology."**
10500
+ **Replaced by**: <modern alternative>
10501
+ **Migration**: <one concrete step>
10502
+
10503
+ When APPROVED:
10504
+ **Install**: pnpm add <name>@^<major>.<minor>.0`
10505
+ },
10506
+ budget: {
10507
+ timeoutMs: 6e4,
10508
+ maxIterations: 5,
10509
+ maxToolCalls: 20,
10510
+ maxTokens: 4e4,
10511
+ maxCostUsd: 0.1
10512
+ },
10513
+ capability: {
10514
+ phase: "meta",
10515
+ summary: "Single-shot tech stack validator: checks npm for latest versions, rejects dead/obsolete packages, enforces modern alternatives.",
10516
+ keywords: [
10517
+ "tech stack",
10518
+ "version",
10519
+ "package",
10520
+ "library",
10521
+ "framework",
10522
+ "dependency",
10523
+ "install",
10524
+ "upgrade",
10525
+ "latest",
10526
+ "npm",
10527
+ "pnpm add",
10528
+ "outdated",
10529
+ "obsolete",
10530
+ "deprecated",
10531
+ "what version",
10532
+ "which package",
10533
+ "check version",
10534
+ "verify version",
10535
+ "is this current"
10536
+ ]
10537
+ }
10013
10538
  }
10014
10539
  ];
10015
10540
 
@@ -11342,12 +11867,6 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11342
11867
  };
11343
11868
 
11344
11869
  // src/execution/parallel-eternal-engine.ts
11345
- function expectDefined9(value) {
11346
- if (value === null || value === void 0) {
11347
- throw new Error("Expected value to be defined");
11348
- }
11349
- return value;
11350
- }
11351
11870
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
11352
11871
  var ParallelEternalEngine = class {
11353
11872
  constructor(opts) {
@@ -11503,7 +12022,7 @@ var ParallelEternalEngine = class {
11503
12022
  // Fan-out
11504
12023
  // -------------------------------------------------------------------------
11505
12024
  async fanOut(goal, tasks) {
11506
- const coordinator = expectDefined9(this.coordinator);
12025
+ const coordinator = expectDefined(this.coordinator);
11507
12026
  const slotCount = Math.min(this.slots, tasks.length);
11508
12027
  const routes = this.dispatchEnabled ? await Promise.all(
11509
12028
  tasks.slice(0, slotCount).map(
@@ -11535,7 +12054,7 @@ ${recentJournal}` : "No prior iterations.",
11535
12054
  const routeInfo = [];
11536
12055
  const spawnPromises = [];
11537
12056
  for (let i = 0; i < slotCount; i++) {
11538
- const task = expectDefined9(tasks[i]);
12057
+ const task = expectDefined(tasks[i]);
11539
12058
  const route = routes[i] ?? null;
11540
12059
  const subagentId = `parallel-${this.iterations}-${i}`;
11541
12060
  const taskId = randomUUID();
@@ -11808,7 +12327,17 @@ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first ite
11808
12327
  }
11809
12328
 
11810
12329
  // src/execution/goal-preamble.ts
11811
- function buildGoalPreamble(goal) {
12330
+ function buildGoalPreamble(goal, deliverables) {
12331
+ const deliverableBlock = deliverables && deliverables.length > 0 ? [
12332
+ "",
12333
+ "CONCRETE DELIVERABLES (check these off as you go):",
12334
+ ...deliverables.map((d, i) => ` ${i + 1}. ${d}`),
12335
+ "",
12336
+ "After EACH iteration, estimate your completion percentage (0\u2013100)",
12337
+ "against this deliverable list. Output it as:",
12338
+ " [PROGRESS: N%] \u2014 <1-sentence status>",
12339
+ "The eternal engine reads this to update the progress bar."
12340
+ ].join("\n") : "";
11812
12341
  return [
11813
12342
  "[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
11814
12343
  "The user granted you full autonomy. Read these constraints once, then act.",
@@ -11817,6 +12346,7 @@ function buildGoalPreamble(goal) {
11817
12346
  "---",
11818
12347
  goal,
11819
12348
  "---",
12349
+ deliverableBlock,
11820
12350
  "",
11821
12351
  "AUTHORITY YOU HAVE:",
11822
12352
  "- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
@@ -11838,6 +12368,7 @@ function buildGoalPreamble(goal) {
11838
12368
  "- You can tell the user HOW to verify it themselves in 10 seconds.",
11839
12369
  '- You have NOT hedged. None of: "looks like it should work", "I',
11840
12370
  ' believe this fixes it", "the changes appear correct".',
12371
+ `- Progress bar shows 100%. All deliverables checked off.`,
11841
12372
  "",
11842
12373
  "WHAT IS NOT DONE \u2014 never report any of these as completion:",
11843
12374
  "- An error message you didn't recover from.",
@@ -11852,6 +12383,13 @@ function buildGoalPreamble(goal) {
11852
12383
  " respond to with a fresh attempt (different role, different model,",
11853
12384
  " tighter prompt).",
11854
12385
  "",
12386
+ "PROGRESS REPORTING \u2014 MANDATORY every iteration:",
12387
+ "- End every response with exactly: [PROGRESS: N%] \u2014 <status note>",
12388
+ " where N is your honest estimate (0-100) of how much of the goal",
12389
+ " is done. The engine tracks this and shows a live progress bar.",
12390
+ "- Example: [PROGRESS: 45%] \u2014 Auth module refactored, 3/5 tests pass,",
12391
+ " remaining: test coverage + docs update.",
12392
+ "",
11855
12393
  "PERSISTENCE PROTOCOL:",
11856
12394
  "- If blocked, try at least 3 different angles before reporting the",
11857
12395
  " problem to the user. Different tool inputs, different subagent",
@@ -12026,12 +12564,6 @@ function createMessage(type, from, payload, to) {
12026
12564
  priority: "normal"
12027
12565
  };
12028
12566
  }
12029
- function expectDefined10(value) {
12030
- if (value === null || value === void 0) {
12031
- throw new Error("Expected value to be defined");
12032
- }
12033
- return value;
12034
- }
12035
12567
  var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
12036
12568
  var IS_WINDOWS = process.platform === "win32";
12037
12569
  var SEP = IS_WINDOWS ? "\\" : "/";
@@ -12045,7 +12577,7 @@ function globToRegex(pat) {
12045
12577
  let i = 0;
12046
12578
  let re = "^";
12047
12579
  while (i < pat.length) {
12048
- const c = expectDefined10(pat[i]);
12580
+ const c = expectDefined(pat[i]);
12049
12581
  if (c === "*") {
12050
12582
  if (pat[i + 1] === "*") {
12051
12583
  re += ".*";
@@ -12084,7 +12616,7 @@ function globToRegex(pat) {
12084
12616
  }
12085
12617
  function baseDir(pat) {
12086
12618
  let i = pat.length - 1;
12087
- while (i >= 0 && !GLOB_CHARS.has(expectDefined10(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
12619
+ while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
12088
12620
  const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
12089
12621
  return cut < 0 ? "." : pat.slice(0, cut);
12090
12622
  }
@@ -12165,6 +12697,7 @@ var CollabSession = class extends EventEmitter {
12165
12697
  settled = false;
12166
12698
  timeoutMs;
12167
12699
  cancelled = false;
12700
+ _raceResolved = false;
12168
12701
  alerts = [];
12169
12702
  snapshotWarnings = [];
12170
12703
  /** Tracks tool call counts per subagent for progress-based timeout decisions. */
@@ -12261,7 +12794,7 @@ var CollabSession = class extends EventEmitter {
12261
12794
  * Safe to call multiple times (idempotent after first call).
12262
12795
  */
12263
12796
  cancel(reason = "Director cancelled collab session") {
12264
- if (this.settled) return;
12797
+ if (this.cancelled) return;
12265
12798
  this.cancelled = true;
12266
12799
  if (this._timeoutTimer) {
12267
12800
  clearTimeout(this._timeoutTimer);
@@ -12299,6 +12832,7 @@ var CollabSession = class extends EventEmitter {
12299
12832
  this.subagentIds.set("critic", criticId);
12300
12833
  const timeout = new Promise((_, reject) => {
12301
12834
  this._timeoutTimer = setTimeout(() => {
12835
+ if (this._raceResolved) return;
12302
12836
  this.cancel("Session-level timeout reached");
12303
12837
  reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
12304
12838
  }, this.timeoutMs);
@@ -12314,6 +12848,7 @@ var CollabSession = class extends EventEmitter {
12314
12848
  timeout
12315
12849
  ]);
12316
12850
  } catch (err) {
12851
+ this._raceResolved = true;
12317
12852
  if (this._timeoutTimer) {
12318
12853
  clearTimeout(this._timeoutTimer);
12319
12854
  this._timeoutTimer = void 0;
@@ -12328,6 +12863,7 @@ var CollabSession = class extends EventEmitter {
12328
12863
  }
12329
12864
  this.snapshotWarnings = await this.checkSnapshotFreshness();
12330
12865
  const report = this.assembleReport();
12866
+ this._raceResolved = true;
12331
12867
  this.cleanup();
12332
12868
  this.emit("session.done", report);
12333
12869
  return report;
@@ -16552,7 +17088,7 @@ var TaskTracker = class {
16552
17088
  const blockers = this.getBlockers(taskId);
16553
17089
  return blockers.every((id) => {
16554
17090
  const node = this.graph?.nodes.get(id);
16555
- return node?.status === "completed";
17091
+ return node?.status === "completed" || node?.status === "failed";
16556
17092
  });
16557
17093
  }
16558
17094
  getProgress() {
@@ -16584,7 +17120,7 @@ var TaskTracker = class {
16584
17120
  const remainingBlockers = this.getBlockers(depId);
16585
17121
  const allUnblocked = remainingBlockers.every((id) => {
16586
17122
  const blocker = this.graph?.nodes.get(id);
16587
- return blocker?.status === "completed";
17123
+ return blocker?.status === "completed" || blocker?.status === "failed";
16588
17124
  });
16589
17125
  if (allUnblocked) {
16590
17126
  dep.status = "pending";
@@ -16598,7 +17134,7 @@ var TaskTracker = class {
16598
17134
  const blockers = this.getBlockers(taskId);
16599
17135
  const someBlocked = blockers.some((id) => {
16600
17136
  const blocker = this.graph?.nodes.get(id);
16601
- return blocker?.status !== "completed";
17137
+ return blocker?.status !== "completed" && blocker?.status !== "failed";
16602
17138
  });
16603
17139
  if (someBlocked) {
16604
17140
  const node = this.graph.nodes.get(taskId);
@@ -17035,12 +17571,6 @@ var TaskGraphStore = class {
17035
17571
  };
17036
17572
 
17037
17573
  // src/sdd/spec-builder.ts
17038
- function expectDefined11(value) {
17039
- if (value === null || value === void 0) {
17040
- throw new Error("Expected value to be defined");
17041
- }
17042
- return value;
17043
- }
17044
17574
  function buildQuestioningPrompt(session, min, max) {
17045
17575
  const answered = session.answers.length;
17046
17576
  const remaining = Math.max(0, min - answered);
@@ -17086,7 +17616,7 @@ function buildQuestioningPrompt(session, min, max) {
17086
17616
  if (answered > 0) {
17087
17617
  lines.push("", "**Conversation so far:**");
17088
17618
  for (let i = 0; i < answered; i++) {
17089
- const a = expectDefined11(session.answers[i]);
17619
+ const a = expectDefined(session.answers[i]);
17090
17620
  lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
17091
17621
  }
17092
17622
  }
@@ -17828,12 +18358,6 @@ function truncate2(str, maxLen) {
17828
18358
  }
17829
18359
 
17830
18360
  // src/sdd/critical-path.ts
17831
- function expectDefined12(value) {
17832
- if (value === null || value === void 0) {
17833
- throw new Error("Expected value to be defined");
17834
- }
17835
- return value;
17836
- }
17837
18361
  function analyzeCriticalPath(graph) {
17838
18362
  const nodes = Array.from(graph.nodes.values());
17839
18363
  const topoOrder = topologicalSort(graph);
@@ -17909,7 +18433,7 @@ function getTransitiveBlocked(_graph, taskId, blocksMap) {
17909
18433
  const visited = /* @__PURE__ */ new Set();
17910
18434
  const queue = [taskId];
17911
18435
  while (queue.length > 0) {
17912
- const current = expectDefined12(queue.shift());
18436
+ const current = expectDefined(queue.shift());
17913
18437
  const blocked = blocksMap.get(current);
17914
18438
  if (!blocked) continue;
17915
18439
  for (const id of blocked) {
@@ -17955,7 +18479,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17955
18479
  if (!changed) break;
17956
18480
  }
17957
18481
  let maxDist = 0;
17958
- let maxId = expectDefined12(allIds[0]);
18482
+ let maxId = expectDefined(allIds[0]);
17959
18483
  for (const id of allIds) {
17960
18484
  const d = dist.get(id) ?? 0;
17961
18485
  if (d > maxDist) {
@@ -18424,25 +18948,22 @@ var SddTaskDecomposer = class {
18424
18948
  return nodes.some((n) => n.status === "blocked");
18425
18949
  }
18426
18950
  };
18427
- function expectDefined13(value) {
18428
- if (value === null || value === void 0) {
18429
- throw new Error("Expected value to be defined");
18430
- }
18431
- return value;
18432
- }
18433
18951
  var SddParallelRun = class {
18434
18952
  constructor(opts) {
18435
18953
  this.opts = opts;
18436
18954
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
18437
18955
  this.timeoutMs = opts.taskTimeoutMs ?? 3e5;
18956
+ this.maxRetries = Math.max(0, opts.maxRetries ?? 2);
18438
18957
  this.decomposer = new SddTaskDecomposer(opts.tracker, opts.graph, { parallelSlots: this.slots });
18439
18958
  }
18440
18959
  opts;
18441
18960
  slots;
18442
18961
  timeoutMs;
18962
+ maxRetries;
18443
18963
  decomposer;
18444
18964
  coordinator = null;
18445
18965
  stopRequested = false;
18966
+ retryMap = /* @__PURE__ */ new Map();
18446
18967
  // -------------------------------------------------------------------
18447
18968
  // Public API
18448
18969
  // -------------------------------------------------------------------
@@ -18454,6 +18975,7 @@ var SddParallelRun = class {
18454
18975
  /** Execute all waves until completion or deadlock. Returns final summary. */
18455
18976
  async run() {
18456
18977
  this.stopRequested = false;
18978
+ this.retryMap.clear();
18457
18979
  const startTime = Date.now();
18458
18980
  let totalCompleted = 0;
18459
18981
  let totalFailed = 0;
@@ -18578,13 +19100,24 @@ var SddParallelRun = class {
18578
19100
  const successCount = results.filter((r) => r.status === "success").length;
18579
19101
  const failCount = results.length - successCount;
18580
19102
  for (let i = 0; i < results.length; i++) {
18581
- const result = expectDefined13(results[i]);
18582
- const taskId = expectDefined13(taskIds[i]);
19103
+ const result = expectDefined(results[i]);
19104
+ const taskId = expectDefined(taskIds[i]);
18583
19105
  if (result.status === "success") {
18584
19106
  this.opts.tracker.updateNodeStatus(taskId, "completed");
19107
+ this.retryMap.delete(taskId);
18585
19108
  } else {
18586
19109
  const errMsg = result.error?.kind ? `${result.error.kind}: ${result.error.message}` : result.error?.message ?? "unknown error";
18587
- this.opts.tracker.updateNodeStatus(taskId, "failed", errMsg);
19110
+ const currentRetries = this.retryMap.get(taskId) ?? 0;
19111
+ if (currentRetries < this.maxRetries) {
19112
+ this.retryMap.set(taskId, currentRetries + 1);
19113
+ this.opts.tracker.updateNodeStatus(
19114
+ taskId,
19115
+ "pending",
19116
+ `Retry ${currentRetries + 1}/${this.maxRetries}: ${errMsg}`
19117
+ );
19118
+ } else {
19119
+ this.opts.tracker.updateNodeStatus(taskId, "failed", errMsg);
19120
+ }
18588
19121
  }
18589
19122
  }
18590
19123
  return {
@@ -18599,6 +19132,7 @@ var SddParallelRun = class {
18599
19132
  }
18600
19133
  buildProgress() {
18601
19134
  const gp = this.opts.tracker.getProgress();
19135
+ const isDeadlocked = !this.decomposer.isDone() && this.decomposer.nextBatch().deadlocked;
18602
19136
  return {
18603
19137
  wave: this.decomposer.getWaveCount(),
18604
19138
  total: gp.total,
@@ -18608,7 +19142,7 @@ var SddParallelRun = class {
18608
19142
  blocked: gp.blocked,
18609
19143
  pending: gp.pending,
18610
19144
  percent: gp.percentComplete,
18611
- deadlocked: false
19145
+ deadlocked: isDeadlocked
18612
19146
  };
18613
19147
  }
18614
19148
  };