@wrongstack/core 0.257.2 → 0.264.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 (82) hide show
  1. package/dist/{agent-bridge-BrxWHEOm.d.ts → agent-bridge-D8sa1vtv.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-US741uBH.d.ts → agent-subagent-runner-c9DLkaas.d.ts} +31 -9
  3. package/dist/{brain-TjEEwSpw.d.ts → brain-O1IdKPaK.d.ts} +59 -2
  4. package/dist/{compactor-C5sT4U7I.d.ts → compactor-BBy0rCtB.d.ts} +1 -1
  5. package/dist/{config-DuAu23zm.d.ts → config-Dz2F3H2K.d.ts} +7 -1
  6. package/dist/{context-CGdgA0q6.d.ts → context-BGSpZNSE.d.ts} +33 -0
  7. package/dist/coordination/index.d.ts +1681 -15
  8. package/dist/coordination/index.js +2826 -405
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +2258 -1433
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  14. package/dist/execution/index.d.ts +15 -15
  15. package/dist/execution/index.js +502 -398
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/execution/prompt-enhancer.d.ts +2 -2
  18. package/dist/execution/prompt-enhancer.js +7 -1
  19. package/dist/execution/prompt-enhancer.js.map +1 -1
  20. package/dist/extension/index.d.ts +6 -6
  21. package/dist/extension/index.js.map +1 -1
  22. package/dist/{goal-preamble-CznHTZqP.d.ts → goal-preamble-DzjFuN3p.d.ts} +21 -9
  23. package/dist/{goal-store-CV9Yz2X_.d.ts → goal-store-CxWmCGbH.d.ts} +4 -2
  24. package/dist/{index-CC0Mcm05.d.ts → index-CYIQrXVF.d.ts} +8 -8
  25. package/dist/{index-CitPrI3a.d.ts → index-CbLSI66_.d.ts} +5 -5
  26. package/dist/index.d.ts +50 -94
  27. package/dist/index.js +16009 -12406
  28. package/dist/index.js.map +1 -1
  29. package/dist/infrastructure/index.d.ts +6 -6
  30. package/dist/kernel/index.d.ts +9 -9
  31. package/dist/kernel/index.js +6 -1
  32. package/dist/kernel/index.js.map +1 -1
  33. package/dist/{llm-selector-CJ4SyAFE.d.ts → llm-selector-DzxuZnNz.d.ts} +2 -2
  34. package/dist/{mcp-servers-D8YnLaEp.d.ts → mcp-servers-DC4QRPUI.d.ts} +3 -3
  35. package/dist/models/index.d.ts +5 -5
  36. package/dist/models/index.js +6 -1
  37. package/dist/models/index.js.map +1 -1
  38. package/dist/{models-registry-ByZCdFuQ.d.ts → models-registry-B_siPxqN.d.ts} +1 -1
  39. package/dist/{multi-agent-coordinator-DqTUEAeC.d.ts → multi-agent-coordinator-CK5Jdj9K.d.ts} +2 -2
  40. package/dist/{null-fleet-bus-B5mfTJXT.d.ts → null-fleet-bus-DgvD4SCO.d.ts} +13 -8
  41. package/dist/observability/index.d.ts +2 -2
  42. package/dist/observability/index.js +8 -3
  43. package/dist/observability/index.js.map +1 -1
  44. package/dist/{parallel-eternal-engine-C0juOszP.d.ts → parallel-eternal-engine-bK0JQBR_.d.ts} +13 -9
  45. package/dist/{path-resolver-CbkT-RMU.d.ts → path-resolver-BPEDlN38.d.ts} +3 -3
  46. package/dist/{permission-CwBBpCoF.d.ts → permission-4yvGmMRB.d.ts} +1 -1
  47. package/dist/{permission-policy-B8rSu908.d.ts → permission-policy-C6XpsBOy.d.ts} +3 -2
  48. package/dist/{pipeline-JG8XoudC.d.ts → pipeline-CXCeMz8J.d.ts} +58 -3
  49. package/dist/{plan-templates-DPiQMkBz.d.ts → plan-templates-BvzRBkJc.d.ts} +32 -11
  50. package/dist/{provider-runner-hM7EXlLI.d.ts → provider-runner-C5aQpDWE.d.ts} +3 -3
  51. package/dist/{retry-policy-Tg7LXkoK.d.ts → retry-policy-CFhdtRzz.d.ts} +1 -1
  52. package/dist/sdd/index.d.ts +8 -8
  53. package/dist/sdd/index.js +59 -31
  54. package/dist/sdd/index.js.map +1 -1
  55. package/dist/{secret-vault-gxtFZYBt.d.ts → secret-vault-CxiVLbt1.d.ts} +1 -1
  56. package/dist/security/index.d.ts +4 -4
  57. package/dist/security/index.js +238 -204
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-DWsqVjGf.d.ts → selector-gIuhRTkN.d.ts} +1 -1
  60. package/dist/{session-event-bridge-BAFWdgQ3.d.ts → session-event-bridge-DkvvrpDt.d.ts} +8 -2
  61. package/dist/{session-reader-CqRvaL5v.d.ts → session-reader-KdfVwkKP.d.ts} +1 -1
  62. package/dist/skills/index.js +67 -64
  63. package/dist/skills/index.js.map +1 -1
  64. package/dist/storage/index.d.ts +50 -22
  65. package/dist/storage/index.js +1654 -525
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +57 -0
  68. package/dist/tools/index.js +411 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +711 -694
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/error.d.ts +7 -0
  74. package/dist/utils/error.js +8 -0
  75. package/dist/utils/error.js.map +1 -0
  76. package/dist/utils/index.d.ts +7 -67
  77. package/dist/utils/index.js +17 -5
  78. package/dist/utils/index.js.map +1 -1
  79. package/package.json +5 -1
  80. package/skills/output-standards/SKILL.md +14 -9
  81. package/skills/output-standards/SKILL.save.md +3 -2
  82. package/dist/package-outdated-watcher-BSgR_kK-.d.ts +0 -581
@@ -1,6 +1,6 @@
1
1
  import { randomUUID, createHash, randomBytes } from 'crypto';
2
2
  import * as fsp6 from 'fs/promises';
3
- import * as path4 from 'path';
3
+ import * as path5 from 'path';
4
4
  import { isAbsolute, resolve } from 'path';
5
5
  import * as os from 'os';
6
6
  import { hostname } from 'os';
@@ -152,9 +152,9 @@ function formatHumanPrompt(request) {
152
152
  return lines.join("\n");
153
153
  }
154
154
  async function atomicWrite(targetPath, content, opts = {}) {
155
- const dir = path4.dirname(targetPath);
155
+ const dir = path5.dirname(targetPath);
156
156
  await fsp6.mkdir(dir, { recursive: true });
157
- const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
157
+ const tmp = path5.join(dir, `.${path5.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
158
158
  try {
159
159
  if (typeof content === "string") {
160
160
  await fsp6.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -172,8 +172,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
172
172
  }
173
173
  let mode;
174
174
  try {
175
- const stat5 = await fsp6.stat(targetPath);
176
- mode = stat5.mode & 511;
175
+ const stat6 = await fsp6.stat(targetPath);
176
+ mode = stat6.mode & 511;
177
177
  } catch {
178
178
  mode = opts.mode;
179
179
  }
@@ -193,9 +193,9 @@ async function ensureDir(dir) {
193
193
  await fsp6.mkdir(dir, { recursive: true });
194
194
  }
195
195
  async function withFileLock(targetPath, fn, opts = {}) {
196
- const dir = path4.dirname(targetPath);
196
+ const dir = path5.dirname(targetPath);
197
197
  await fsp6.mkdir(dir, { recursive: true });
198
- const lockPath = path4.join(dir, `.${path4.basename(targetPath)}.lock`);
198
+ const lockPath = path5.join(dir, `.${path5.basename(targetPath)}.lock`);
199
199
  const timeoutMs = opts.timeoutMs ?? 5e3;
200
200
  const staleMs = opts.staleMs ?? 3e4;
201
201
  const started = Date.now();
@@ -206,10 +206,15 @@ async function withFileLock(targetPath, fn, opts = {}) {
206
206
  await handle.writeFile(`${process.pid}:${Date.now()}`);
207
207
  break;
208
208
  } catch (err) {
209
- if (err.code !== "EEXIST") throw err;
209
+ const code = err.code;
210
+ if (code === "ENOENT") {
211
+ await fsp6.mkdir(dir, { recursive: true });
212
+ continue;
213
+ }
214
+ if (code !== "EEXIST") throw err;
210
215
  try {
211
- const stat5 = await fsp6.stat(lockPath);
212
- if (Date.now() - stat5.mtimeMs > staleMs) {
216
+ const stat6 = await fsp6.stat(lockPath);
217
+ if (Date.now() - stat6.mtimeMs > staleMs) {
213
218
  await fsp6.unlink(lockPath);
214
219
  continue;
215
220
  }
@@ -259,6 +264,11 @@ async function renameWithRetry(from, to) {
259
264
  throw lastErr;
260
265
  }
261
266
 
267
+ // src/utils/error.ts
268
+ function toErrorMessage(err) {
269
+ return err instanceof Error ? err.message : String(err);
270
+ }
271
+
262
272
  // src/storage/director-state.ts
263
273
  async function acquireDirectorStateLock(lockPath, processId = process.pid) {
264
274
  let existing;
@@ -412,7 +422,7 @@ var DirectorStateCheckpoint = class {
412
422
  } catch (err) {
413
423
  console.warn(
414
424
  "[director-state] checkpoint write failed:",
415
- err instanceof Error ? err.message : String(err)
425
+ toErrorMessage(err)
416
426
  );
417
427
  } finally {
418
428
  this.writing = false;
@@ -434,11 +444,246 @@ function safeParse(input, maxBytes = 5e6) {
434
444
  } catch (err) {
435
445
  return {
436
446
  ok: false,
437
- error: err instanceof Error ? err.message : String(err)
447
+ error: toErrorMessage(err)
438
448
  };
439
449
  }
440
450
  }
441
451
 
452
+ // src/utils/string.ts
453
+ function truncate(s, max) {
454
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
455
+ }
456
+
457
+ // src/utils/expect-defined.ts
458
+ function expectDefined(value, label) {
459
+ if (value === null || value === void 0) {
460
+ const err = new Error("Expected value to be defined");
461
+ err.name = "ExpectDefinedError";
462
+ throw err;
463
+ }
464
+ return value;
465
+ }
466
+ function projectSlug(absRoot) {
467
+ const base = slugify(path5.basename(absRoot));
468
+ const hash = createHash("sha256").update(path5.resolve(absRoot)).digest("hex").slice(0, 6);
469
+ return `${base}-${hash}`;
470
+ }
471
+ function slugify(name) {
472
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
473
+ }
474
+ function wstackGlobalRoot() {
475
+ const fromEnv = process.env["WRONGSTACK_HOME"];
476
+ if (fromEnv && fromEnv.trim().length > 0) return path5.resolve(fromEnv);
477
+ return path5.join(os.homedir(), ".wrongstack");
478
+ }
479
+
480
+ // src/utils/message-invariants.ts
481
+ function repairToolUseAdjacency(messages) {
482
+ const removedToolUses = [];
483
+ const removedToolResults = [];
484
+ let removedMessages = 0;
485
+ let changed = false;
486
+ const out = [];
487
+ for (let i = 0; i < messages.length; i++) {
488
+ const original = expectDefined(messages[i]);
489
+ let msg = original;
490
+ if (hasToolUse(msg)) {
491
+ const nextIds = toolResultIds(messages[i + 1]);
492
+ const filtered = mapContent(msg, (blocks) => {
493
+ const next = [];
494
+ for (const block of blocks) {
495
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
496
+ removedToolUses.push(block.id);
497
+ changed = true;
498
+ continue;
499
+ }
500
+ next.push(block);
501
+ }
502
+ return next;
503
+ });
504
+ msg = filtered ?? msg;
505
+ }
506
+ if (hasToolResult(msg)) {
507
+ const allowed = toolUseIds(out[out.length - 1]);
508
+ const filtered = mapContent(msg, (blocks) => {
509
+ const next = [];
510
+ for (const block of blocks) {
511
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
512
+ removedToolResults.push(block.tool_use_id);
513
+ changed = true;
514
+ continue;
515
+ }
516
+ next.push(block);
517
+ }
518
+ return next;
519
+ });
520
+ msg = filtered ?? msg;
521
+ }
522
+ if (isEmptyMessage(msg)) {
523
+ removedMessages++;
524
+ changed = true;
525
+ continue;
526
+ }
527
+ out.push(msg);
528
+ }
529
+ return {
530
+ messages: changed ? out : messages,
531
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
532
+ };
533
+ }
534
+ function hasToolUse(msg) {
535
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
536
+ }
537
+ function hasToolResult(msg) {
538
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
539
+ }
540
+ function toolUseIds(msg) {
541
+ const ids = /* @__PURE__ */ new Set();
542
+ if (!msg || msg.role !== "assistant") return ids;
543
+ for (const block of contentBlocks(msg)) {
544
+ if (block.type === "tool_use") ids.add(block.id);
545
+ }
546
+ return ids;
547
+ }
548
+ function toolResultIds(msg) {
549
+ const ids = /* @__PURE__ */ new Set();
550
+ if (!msg || msg.role !== "user") return ids;
551
+ for (const block of contentBlocks(msg)) {
552
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
553
+ }
554
+ return ids;
555
+ }
556
+ function contentBlocks(msg) {
557
+ return msg && Array.isArray(msg.content) ? msg.content : [];
558
+ }
559
+ function mapContent(msg, fn) {
560
+ if (!Array.isArray(msg.content)) return msg;
561
+ const next = fn(msg.content);
562
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
563
+ return msg;
564
+ }
565
+ return { ...msg, content: next };
566
+ }
567
+ function isEmptyMessage(msg) {
568
+ if (typeof msg.content === "string") return msg.content.trim().length === 0;
569
+ return msg.content.length === 0;
570
+ }
571
+ var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
572
+ var IS_WINDOWS = process.platform === "win32";
573
+ var SEP = IS_WINDOWS ? "\\" : "/";
574
+ function isGlob(p) {
575
+ for (const c of p) {
576
+ if (GLOB_CHARS.has(c)) return true;
577
+ }
578
+ return false;
579
+ }
580
+ function globToRegex(pat) {
581
+ let i = 0;
582
+ let re = "^";
583
+ while (i < pat.length) {
584
+ const c = expectDefined(pat[i]);
585
+ if (c === "*") {
586
+ if (pat[i + 1] === "*") {
587
+ re += ".*";
588
+ i += 2;
589
+ if (pat[i] === "/") i++;
590
+ } else {
591
+ re += "[^/\\\\]*";
592
+ i++;
593
+ }
594
+ } else if (c === "?") {
595
+ re += "[^/\\\\]";
596
+ i++;
597
+ } else if (c === "[") {
598
+ let cls = "[";
599
+ i++;
600
+ if (pat[i] === "!" || pat[i] === "^") {
601
+ cls += "^";
602
+ i++;
603
+ }
604
+ while (i < pat.length && pat[i] !== "]") {
605
+ const ch = pat[i] ?? "";
606
+ if (ch === "\\") cls += "\\\\";
607
+ else if (ch === "]" || ch === "^") cls += `\\${ch}`;
608
+ else cls += ch;
609
+ i++;
610
+ }
611
+ cls += "]";
612
+ re += cls;
613
+ i++;
614
+ } else {
615
+ re += c.replace(/[.+^${}()|\\]/g, "\\$&");
616
+ i++;
617
+ }
618
+ }
619
+ return new RegExp(re + "$");
620
+ }
621
+ function baseDir(pat) {
622
+ let i = pat.length - 1;
623
+ while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
624
+ const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
625
+ return cut < 0 ? "." : pat.slice(0, cut);
626
+ }
627
+ async function expandGlob(pattern) {
628
+ if (!isGlob(pattern)) return [pattern];
629
+ const results = /* @__PURE__ */ new Set();
630
+ const abs = isAbsolute(pattern);
631
+ const base = abs ? baseDir(pattern) : baseDir(pattern);
632
+ const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
633
+ async function walk(dir, pat) {
634
+ let entries;
635
+ try {
636
+ entries = await fsp6.readdir(dir);
637
+ } catch {
638
+ return;
639
+ }
640
+ const firstGlob = pat.search(/[*?[[]/);
641
+ if (firstGlob < 0) {
642
+ const re = globToRegex(pat);
643
+ for (const e of entries) {
644
+ if (re.test(e)) {
645
+ const full = `${dir}${SEP}${e}`;
646
+ results.add(abs ? resolve(full) : full);
647
+ }
648
+ }
649
+ return;
650
+ }
651
+ const before = pat.slice(0, firstGlob);
652
+ const rest = pat.slice(firstGlob);
653
+ if (before.endsWith("**")) {
654
+ await walk(dir, rest);
655
+ for (const e of entries) {
656
+ const full = `${dir}${SEP}${e}`;
657
+ try {
658
+ const stat6 = await fsp6.stat(full);
659
+ if (stat6.isDirectory()) await walk(full, rest);
660
+ } catch {
661
+ }
662
+ }
663
+ } else if (before === "") {
664
+ const re = globToRegex(rest);
665
+ for (const e of entries) {
666
+ if (re.test(e)) {
667
+ const full = `${dir}${SEP}${e}`;
668
+ results.add(abs ? resolve(full) : full);
669
+ }
670
+ }
671
+ } else {
672
+ const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
673
+ if (entries.includes(seg)) {
674
+ const full = `${dir}${SEP}${seg}`;
675
+ try {
676
+ const stat6 = await fsp6.stat(full);
677
+ if (stat6.isDirectory()) await walk(full, rest);
678
+ } catch {
679
+ }
680
+ }
681
+ }
682
+ }
683
+ await walk(base === "." ? "." : base, relPat);
684
+ return [...results];
685
+ }
686
+
442
687
  // src/types/errors.ts
443
688
  var ERROR_CODES = {
444
689
  // Provider
@@ -657,133 +902,6 @@ function createMessage(type, from, payload, to) {
657
902
  priority: "normal"
658
903
  };
659
904
  }
660
-
661
- // src/utils/expect-defined.ts
662
- function expectDefined(value, label) {
663
- if (value === null || value === void 0) {
664
- const err = new Error("Expected value to be defined");
665
- err.name = "ExpectDefinedError";
666
- throw err;
667
- }
668
- return value;
669
- }
670
- var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
671
- var IS_WINDOWS = process.platform === "win32";
672
- var SEP = IS_WINDOWS ? "\\" : "/";
673
- function isGlob(p) {
674
- for (const c of p) {
675
- if (GLOB_CHARS.has(c)) return true;
676
- }
677
- return false;
678
- }
679
- function globToRegex(pat) {
680
- let i = 0;
681
- let re = "^";
682
- while (i < pat.length) {
683
- const c = expectDefined(pat[i]);
684
- if (c === "*") {
685
- if (pat[i + 1] === "*") {
686
- re += ".*";
687
- i += 2;
688
- if (pat[i] === "/") i++;
689
- } else {
690
- re += "[^/\\\\]*";
691
- i++;
692
- }
693
- } else if (c === "?") {
694
- re += "[^/\\\\]";
695
- i++;
696
- } else if (c === "[") {
697
- let cls = "[";
698
- i++;
699
- if (pat[i] === "!" || pat[i] === "^") {
700
- cls += "^";
701
- i++;
702
- }
703
- while (i < pat.length && pat[i] !== "]") {
704
- const ch = pat[i] ?? "";
705
- if (ch === "\\") cls += "\\\\";
706
- else if (ch === "]" || ch === "^") cls += `\\${ch}`;
707
- else cls += ch;
708
- i++;
709
- }
710
- cls += "]";
711
- re += cls;
712
- i++;
713
- } else {
714
- re += c.replace(/[.+^${}()|\\]/g, "\\$&");
715
- i++;
716
- }
717
- }
718
- return new RegExp(re + "$");
719
- }
720
- function baseDir(pat) {
721
- let i = pat.length - 1;
722
- while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
723
- const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
724
- return cut < 0 ? "." : pat.slice(0, cut);
725
- }
726
- async function expandGlob(pattern) {
727
- if (!isGlob(pattern)) return [pattern];
728
- const results = /* @__PURE__ */ new Set();
729
- const abs = isAbsolute(pattern);
730
- const base = abs ? baseDir(pattern) : baseDir(pattern);
731
- const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
732
- async function walk(dir, pat) {
733
- let entries;
734
- try {
735
- entries = await fsp6.readdir(dir);
736
- } catch {
737
- return;
738
- }
739
- const firstGlob = pat.search(/[*?[[]/);
740
- if (firstGlob < 0) {
741
- const re = globToRegex(pat);
742
- for (const e of entries) {
743
- if (re.test(e)) {
744
- const full = `${dir}${SEP}${e}`;
745
- results.add(abs ? resolve(full) : full);
746
- }
747
- }
748
- return;
749
- }
750
- const before = pat.slice(0, firstGlob);
751
- const rest = pat.slice(firstGlob);
752
- if (before.endsWith("**")) {
753
- await walk(dir, rest);
754
- for (const e of entries) {
755
- const full = `${dir}${SEP}${e}`;
756
- try {
757
- const stat5 = await fsp6.stat(full);
758
- if (stat5.isDirectory()) await walk(full, rest);
759
- } catch {
760
- }
761
- }
762
- } else if (before === "") {
763
- const re = globToRegex(rest);
764
- for (const e of entries) {
765
- if (re.test(e)) {
766
- const full = `${dir}${SEP}${e}`;
767
- results.add(abs ? resolve(full) : full);
768
- }
769
- }
770
- } else {
771
- const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
772
- if (entries.includes(seg)) {
773
- const full = `${dir}${SEP}${seg}`;
774
- try {
775
- const stat5 = await fsp6.stat(full);
776
- if (stat5.isDirectory()) await walk(full, rest);
777
- } catch {
778
- }
779
- }
780
- }
781
- }
782
- await walk(base === "." ? "." : base, relPat);
783
- return [...results];
784
- }
785
-
786
- // src/coordination/collab-debug.ts
787
905
  var DEFAULT_MAX_TARGET_FILES = 30;
788
906
  var DirectorAlertLevel = /* @__PURE__ */ ((DirectorAlertLevel2) => {
789
907
  DirectorAlertLevel2["WARNING"] = "warning";
@@ -878,7 +996,7 @@ var CollabSession = class extends EventEmitter {
878
996
  }
879
997
  for (const filePath of allFiles) {
880
998
  try {
881
- const [content, stat5] = await Promise.all([
999
+ const [content, stat6] = await Promise.all([
882
1000
  fsp6.readFile(filePath, "utf8"),
883
1001
  fsp6.stat(filePath)
884
1002
  ]);
@@ -888,8 +1006,8 @@ var CollabSession = class extends EventEmitter {
888
1006
  path: filePath,
889
1007
  content,
890
1008
  language,
891
- snapshotMtimeMs: stat5.mtimeMs,
892
- snapshotSizeBytes: stat5.size
1009
+ snapshotMtimeMs: stat6.mtimeMs,
1010
+ snapshotSizeBytes: stat6.size
893
1011
  });
894
1012
  } catch {
895
1013
  this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
@@ -1302,9 +1420,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
1302
1420
  for (const file of this.snapshot.files) {
1303
1421
  if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
1304
1422
  try {
1305
- const stat5 = await fsp6.stat(file.path);
1306
- const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat5.mtimeMs > file.snapshotMtimeMs + 1;
1307
- const sizeChanged = file.snapshotSizeBytes !== void 0 && stat5.size !== file.snapshotSizeBytes;
1423
+ const stat6 = await fsp6.stat(file.path);
1424
+ const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat6.mtimeMs > file.snapshotMtimeMs + 1;
1425
+ const sizeChanged = file.snapshotSizeBytes !== void 0 && stat6.size !== file.snapshotSizeBytes;
1308
1426
  if (mtimeChanged || sizeChanged) {
1309
1427
  warnings.push(`${file.path} changed after the collab snapshot was captured.`);
1310
1428
  }
@@ -1409,7 +1527,12 @@ Working rules:
1409
1527
  6. Never claim a subagent's work as your own without verifying it. If a
1410
1528
  result looks wrong, ask_subagent for clarification before passing it
1411
1529
  to the user.
1412
- 7. Wind down when satisfied. When the results are good enough, call
1530
+ 7. **Act on subagent mail immediately**. Subagent messages (result, ask,
1531
+ assign, note) are injected inline before every step \u2014 even mid-task.
1532
+ When you see one, address it before continuing: reply to asks, factor
1533
+ in results, act on assignments. Use \`mailbox action=ack\` to mark
1534
+ completed messages.
1535
+ 8. Wind down when satisfied. When the results are good enough, call
1413
1536
  work_complete \u2014 no new subagents will spawn and queued tasks complete
1414
1537
  as aborted. Running subagents finish naturally. Call terminate_subagent
1415
1538
  only for ones you need to stop immediately.`;
@@ -1426,6 +1549,13 @@ Bridge contract:
1426
1549
  structured, and self-contained \u2014 assume the Director will paste your
1427
1550
  output into its own context.
1428
1551
 
1552
+ CRITICAL CONSTRAINT \u2014 NO FURTHER DELEGATION:
1553
+ - You MUST NOT call the \`delegate\` tool or attempt to spawn subagents.
1554
+ - You MUST NOT use \`spawn_subagent\`, \`assign_task\`, or any equivalent.
1555
+ - Your role is to execute the assigned task yourself, not to orchestrate.
1556
+ - If a subtask is too complex, report back to the Director with what you
1557
+ found and let the Director decide how to decompose.
1558
+
1429
1559
  Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` tools):
1430
1560
  - You are part of a project-wide fleet that may span other terminals and
1431
1561
  WebUIs. Your mailbox identity is \`<your-name>@<session-tag>\` (unique
@@ -1440,7 +1570,12 @@ Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` to
1440
1570
  their exact id instead of doing everything yourself. Discover ids with
1441
1571
  \`mailbox action=online\`.
1442
1572
  - Answer your mail: reply to the sender's exact \`from\` id. When done with
1443
- an assigned task, post a \`result\` back to whoever assigned it.`;
1573
+ an assigned task, post a \`result\` back to whoever assigned it.
1574
+ - **Mail to the leader is always seen**: when you send \`ask\`, \`result\`,
1575
+ or \`assign\` to the director/leader, the message is injected inline into
1576
+ the leader's conversation before their next step \u2014 even if the leader is
1577
+ mid-task. Use \`mail_send\` to reliably reach the leader instead of
1578
+ waiting for them to check in.`;
1444
1579
  function composeDirectorPrompt(parts = {}) {
1445
1580
  const sections = [];
1446
1581
  const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -4312,7 +4447,7 @@ function makeSpawnTool(director, roster) {
4312
4447
  if (err instanceof FleetCostCapError) {
4313
4448
  return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
4314
4449
  }
4315
- return { error: err instanceof Error ? err.message : String(err) };
4450
+ return { error: toErrorMessage(err) };
4316
4451
  }
4317
4452
  }
4318
4453
  };
@@ -4394,7 +4529,7 @@ function makeAskTool(director) {
4394
4529
  _hint: "Response was large and stored. Use ask_result with the key to retrieve it."
4395
4530
  };
4396
4531
  } catch (err) {
4397
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
4532
+ return { ok: false, error: toErrorMessage(err) };
4398
4533
  }
4399
4534
  }
4400
4535
  };
@@ -4620,7 +4755,7 @@ function makeCollabDebugTool(director) {
4620
4755
  evaluations: report.evaluations
4621
4756
  };
4622
4757
  } catch (err) {
4623
- const msg = err instanceof Error ? err.message : String(err);
4758
+ const msg = toErrorMessage(err);
4624
4759
  return { error: "collab_debug failed: " + msg };
4625
4760
  }
4626
4761
  }
@@ -5296,11 +5431,6 @@ var SubagentBudget = class _SubagentBudget {
5296
5431
  }
5297
5432
  };
5298
5433
 
5299
- // src/utils/string.ts
5300
- function truncate(s, max) {
5301
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
5302
- }
5303
-
5304
5434
  // src/types/provider.ts
5305
5435
  var ProviderError = class extends WrongStackError {
5306
5436
  status;
@@ -5380,7 +5510,7 @@ function classifySubagentError(err, hints = {}) {
5380
5510
  const baseMessage2 = err.describe();
5381
5511
  return providerErrorToSubagentError(err, baseMessage2, cause);
5382
5512
  }
5383
- const baseMessage = err instanceof Error ? err.message : String(err);
5513
+ const baseMessage = toErrorMessage(err);
5384
5514
  if (err instanceof BudgetExceededError) {
5385
5515
  const map = {
5386
5516
  iterations: "budget_iterations",
@@ -7366,7 +7496,7 @@ var Director = class _Director {
7366
7496
  })),
7367
7497
  usage: this.usage.snapshot()
7368
7498
  };
7369
- await fsp6.mkdir(path4.dirname(this.manifestPath), { recursive: true });
7499
+ await fsp6.mkdir(path5.dirname(this.manifestPath), { recursive: true });
7370
7500
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
7371
7501
  return this.manifestPath;
7372
7502
  }
@@ -7416,7 +7546,7 @@ var Director = class _Director {
7416
7546
  * listener for structured collection, and never affects exit code.
7417
7547
  */
7418
7548
  logShutdownError(phase, err) {
7419
- const detail = err instanceof Error ? err.message : String(err);
7549
+ const detail = toErrorMessage(err);
7420
7550
  process.emitWarning(
7421
7551
  `Director shutdown phase "${phase}" failed: ${detail}`,
7422
7552
  "DirectorShutdownWarning"
@@ -7580,7 +7710,7 @@ var Director = class _Director {
7580
7710
  */
7581
7711
  async readSession(subagentId, tail) {
7582
7712
  if (!this.sessionsRoot) return null;
7583
- const filePath = path4.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
7713
+ const filePath = path5.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
7584
7714
  let raw;
7585
7715
  try {
7586
7716
  raw = await fsp6.readFile(filePath, "utf8");
@@ -8009,7 +8139,7 @@ function createDelegateTool(opts) {
8009
8139
  summary
8010
8140
  };
8011
8141
  } catch (err) {
8012
- const message = err instanceof Error ? err.message : String(err);
8142
+ const message = toErrorMessage(err);
8013
8143
  opts.events?.emit("delegate.completed", {
8014
8144
  target,
8015
8145
  task: i.task,
@@ -8110,13 +8240,13 @@ async function readSubagentPartial(opts, subagentId) {
8110
8240
  if (!opts.sessionsRoot) return void 0;
8111
8241
  const candidates = [];
8112
8242
  if (opts.directorRunId) {
8113
- candidates.push(path4.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
8243
+ candidates.push(path5.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
8114
8244
  } else {
8115
8245
  try {
8116
8246
  const entries = await fsp6.readdir(opts.sessionsRoot, { withFileTypes: true });
8117
8247
  for (const entry of entries) {
8118
8248
  if (entry.isDirectory()) {
8119
- candidates.push(path4.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
8249
+ candidates.push(path5.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
8120
8250
  }
8121
8251
  }
8122
8252
  } catch {
@@ -8161,6 +8291,20 @@ async function readSubagentPartial(opts, subagentId) {
8161
8291
  }
8162
8292
 
8163
8293
  // src/coordination/agent-subagent-runner.ts
8294
+ function withDisabledToolFiltering(factory) {
8295
+ return async (config) => {
8296
+ const result = await factory(config);
8297
+ const disabled = config.disabledTools ?? [];
8298
+ if (disabled.length === 0) return result;
8299
+ const registry = result.agent.tools;
8300
+ if (registry && typeof registry.unregister === "function") {
8301
+ for (const toolName of disabled) {
8302
+ registry.unregister(toolName);
8303
+ }
8304
+ }
8305
+ return result;
8306
+ };
8307
+ }
8164
8308
  function makeAgentSubagentRunner(opts) {
8165
8309
  const format = opts.formatTaskInput ?? defaultFormatTaskInput;
8166
8310
  return async (task, ctx) => {
@@ -8358,100 +8502,6 @@ function makeAgentSubagentRunner(opts) {
8358
8502
  function defaultFormatTaskInput(task) {
8359
8503
  return task.description ?? "";
8360
8504
  }
8361
-
8362
- // src/utils/message-invariants.ts
8363
- function repairToolUseAdjacency(messages) {
8364
- const removedToolUses = [];
8365
- const removedToolResults = [];
8366
- let removedMessages = 0;
8367
- let changed = false;
8368
- const out = [];
8369
- for (let i = 0; i < messages.length; i++) {
8370
- const original = expectDefined(messages[i]);
8371
- let msg = original;
8372
- if (hasToolUse(msg)) {
8373
- const nextIds = toolResultIds(messages[i + 1]);
8374
- const filtered = mapContent(msg, (blocks) => {
8375
- const next = [];
8376
- for (const block of blocks) {
8377
- if (block.type === "tool_use" && !nextIds.has(block.id)) {
8378
- removedToolUses.push(block.id);
8379
- changed = true;
8380
- continue;
8381
- }
8382
- next.push(block);
8383
- }
8384
- return next;
8385
- });
8386
- msg = filtered ?? msg;
8387
- }
8388
- if (hasToolResult(msg)) {
8389
- const allowed = toolUseIds(out[out.length - 1]);
8390
- const filtered = mapContent(msg, (blocks) => {
8391
- const next = [];
8392
- for (const block of blocks) {
8393
- if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
8394
- removedToolResults.push(block.tool_use_id);
8395
- changed = true;
8396
- continue;
8397
- }
8398
- next.push(block);
8399
- }
8400
- return next;
8401
- });
8402
- msg = filtered ?? msg;
8403
- }
8404
- if (isEmptyMessage(msg)) {
8405
- removedMessages++;
8406
- changed = true;
8407
- continue;
8408
- }
8409
- out.push(msg);
8410
- }
8411
- return {
8412
- messages: changed ? out : messages,
8413
- report: { changed, removedToolUses, removedToolResults, removedMessages }
8414
- };
8415
- }
8416
- function hasToolUse(msg) {
8417
- return contentBlocks(msg).some((b) => b.type === "tool_use");
8418
- }
8419
- function hasToolResult(msg) {
8420
- return contentBlocks(msg).some((b) => b.type === "tool_result");
8421
- }
8422
- function toolUseIds(msg) {
8423
- const ids = /* @__PURE__ */ new Set();
8424
- if (!msg || msg.role !== "assistant") return ids;
8425
- for (const block of contentBlocks(msg)) {
8426
- if (block.type === "tool_use") ids.add(block.id);
8427
- }
8428
- return ids;
8429
- }
8430
- function toolResultIds(msg) {
8431
- const ids = /* @__PURE__ */ new Set();
8432
- if (!msg || msg.role !== "user") return ids;
8433
- for (const block of contentBlocks(msg)) {
8434
- if (block.type === "tool_result") ids.add(block.tool_use_id);
8435
- }
8436
- return ids;
8437
- }
8438
- function contentBlocks(msg) {
8439
- return msg && Array.isArray(msg.content) ? msg.content : [];
8440
- }
8441
- function mapContent(msg, fn) {
8442
- if (!Array.isArray(msg.content)) return msg;
8443
- const next = fn(msg.content);
8444
- if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
8445
- return msg;
8446
- }
8447
- return { ...msg, content: next };
8448
- }
8449
- function isEmptyMessage(msg) {
8450
- if (typeof msg.content === "string") return msg.content.trim().length === 0;
8451
- return msg.content.length === 0;
8452
- }
8453
-
8454
- // src/storage/session-store.ts
8455
8505
  function sanitizeModel(model) {
8456
8506
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
8457
8507
  }
@@ -8471,13 +8521,47 @@ var DefaultSessionStore = class _DefaultSessionStore {
8471
8521
  this.events = opts.events;
8472
8522
  this.secretScrubber = opts.secretScrubber;
8473
8523
  }
8524
+ // ── Storage event helpers ───────────────────────────────────────────────────
8525
+ emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
8526
+ this.events?.emit("storage.read", {
8527
+ sessionId,
8528
+ store: "session",
8529
+ filePath,
8530
+ operation,
8531
+ outcome,
8532
+ durationMs,
8533
+ ...error !== void 0 ? { error } : {}
8534
+ });
8535
+ }
8536
+ emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error) {
8537
+ this.events?.emit("storage.write", {
8538
+ sessionId,
8539
+ store: "session",
8540
+ filePath,
8541
+ operation,
8542
+ outcome,
8543
+ durationMs,
8544
+ ...eventCount !== void 0 ? { eventCount } : {},
8545
+ ...error !== void 0 ? { error } : {}
8546
+ });
8547
+ }
8548
+ emitError(sessionId, filePath, operation, error, recoverable) {
8549
+ this.events?.emit("storage.error", {
8550
+ sessionId,
8551
+ store: "session",
8552
+ filePath,
8553
+ operation,
8554
+ error,
8555
+ recoverable
8556
+ });
8557
+ }
8474
8558
  /** Absolute path to the session index file. */
8475
8559
  get indexFile() {
8476
- return path4.join(this.dir, "_index.jsonl");
8560
+ return path5.join(this.dir, "_index.jsonl");
8477
8561
  }
8478
8562
  /** Join session ID to its absolute path within the store directory. */
8479
8563
  sessionPath(id, ext) {
8480
- return path4.join(this.dir, `${id}${ext}`);
8564
+ return path5.join(this.dir, `${id}${ext}`);
8481
8565
  }
8482
8566
  /**
8483
8567
  * Ensure the directory implied by the session ID exists. When the ID
@@ -8485,7 +8569,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8485
8569
  * subdirectory so sessions group naturally by day.
8486
8570
  */
8487
8571
  async ensureShardDir(id) {
8488
- const dirPath = path4.dirname(path4.join(this.dir, id));
8572
+ const dirPath = path5.dirname(path5.join(this.dir, id));
8489
8573
  await ensureDir(dirPath);
8490
8574
  return dirPath;
8491
8575
  }
@@ -8493,23 +8577,27 @@ var DefaultSessionStore = class _DefaultSessionStore {
8493
8577
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
8494
8578
  const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
8495
8579
  const shardDir = await this.ensureShardDir(id);
8496
- const file = path4.join(shardDir, `${path4.basename(id)}.jsonl`);
8580
+ const file = path5.join(shardDir, `${path5.basename(id)}.jsonl`);
8581
+ const t0 = Date.now();
8497
8582
  let handle;
8498
8583
  try {
8499
8584
  handle = await fsp6.open(file, "a", 384);
8500
8585
  } catch (err) {
8586
+ this.emitError(id, file, "create", toErrorMessage(err), false);
8501
8587
  throw new Error(
8502
- `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
8588
+ `Failed to open session file: ${toErrorMessage(err)}`,
8503
8589
  { cause: err }
8504
8590
  );
8505
8591
  }
8506
8592
  try {
8507
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
8593
+ const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
8508
8594
  dir: shardDir,
8509
8595
  filePath: file,
8510
8596
  secretScrubber: this.secretScrubber,
8511
8597
  onClose: (s) => this.appendToIndex(s)
8512
8598
  });
8599
+ this.emitWrite(id, file, "create", "success", Date.now() - t0);
8600
+ return writer;
8513
8601
  } catch (err) {
8514
8602
  await handle.close().catch((e) => console.warn(JSON.stringify({
8515
8603
  level: "warn",
@@ -8517,18 +8605,21 @@ var DefaultSessionStore = class _DefaultSessionStore {
8517
8605
  message: e instanceof Error ? e.message : String(e),
8518
8606
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
8519
8607
  })));
8608
+ this.emitError(id, file, "create", toErrorMessage(err), true);
8520
8609
  throw err;
8521
8610
  }
8522
8611
  }
8523
8612
  async resume(id) {
8524
8613
  const file = this.sessionPath(id, ".jsonl");
8614
+ const t0 = Date.now();
8525
8615
  const data = await this.load(id);
8526
8616
  let handle;
8527
8617
  try {
8528
8618
  handle = await fsp6.open(file, "a", 384);
8529
8619
  } catch (err) {
8620
+ this.emitError(id, file, "resume", toErrorMessage(err), false);
8530
8621
  throw new Error(
8531
- `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
8622
+ `Failed to open session "${id}" for append: ${toErrorMessage(err)}`,
8532
8623
  { cause: err }
8533
8624
  );
8534
8625
  }
@@ -8548,12 +8639,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
8548
8639
  // Shard directory (sessions/<date>/) — must match create() so the
8549
8640
  // .summary.json sidecar lands next to the JSONL instead of the
8550
8641
  // sessions root (where summaryFor() would never find it).
8551
- dir: path4.dirname(file),
8642
+ dir: path5.dirname(file),
8552
8643
  filePath: file,
8553
8644
  secretScrubber: this.secretScrubber,
8554
8645
  onClose: (s) => this.appendToIndex(s)
8555
8646
  }
8556
8647
  );
8648
+ this.emitWrite(id, file, "resume", "success", Date.now() - t0);
8557
8649
  return { writer, data };
8558
8650
  } catch (err) {
8559
8651
  await handle.close().catch((e) => console.warn(JSON.stringify({
@@ -8562,27 +8654,39 @@ var DefaultSessionStore = class _DefaultSessionStore {
8562
8654
  message: e instanceof Error ? e.message : String(e),
8563
8655
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
8564
8656
  })));
8657
+ this.emitError(id, file, "resume", toErrorMessage(err), true);
8565
8658
  throw err;
8566
8659
  }
8567
8660
  }
8568
8661
  async load(id) {
8569
8662
  const file = this.sessionPath(id, ".jsonl");
8570
- const raw = await fsp6.readFile(file, "utf8");
8571
- const lines = raw.split("\n").filter((l) => l.trim());
8572
- const events = [];
8573
- for (const line of lines) {
8574
- try {
8575
- const parsed = JSON.parse(line);
8576
- if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
8577
- events.push(parsed);
8663
+ const t0 = Date.now();
8664
+ let outcome = "success";
8665
+ let errorMsg;
8666
+ try {
8667
+ const raw = await fsp6.readFile(file, "utf8");
8668
+ const lines = raw.split("\n").filter((l) => l.trim());
8669
+ const events = [];
8670
+ for (const line of lines) {
8671
+ try {
8672
+ const parsed = JSON.parse(line);
8673
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
8674
+ events.push(parsed);
8675
+ }
8676
+ } catch {
8578
8677
  }
8579
- } catch {
8580
8678
  }
8679
+ const meta = this.metaFromEvents(id, events);
8680
+ const { messages, usage } = this.replay(events, id);
8681
+ const toolCallEnds = extractToolCallEnds(events);
8682
+ return { metadata: meta, events, messages, usage, toolCallEnds };
8683
+ } catch (err) {
8684
+ outcome = "failure";
8685
+ errorMsg = toErrorMessage(err);
8686
+ throw err;
8687
+ } finally {
8688
+ this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
8581
8689
  }
8582
- const meta = this.metaFromEvents(id, events);
8583
- const { messages, usage } = this.replay(events, id);
8584
- const toolCallEnds = extractToolCallEnds(events);
8585
- return { metadata: meta, events, messages, usage, toolCallEnds };
8586
8690
  }
8587
8691
  async list(limit = 20) {
8588
8692
  try {
@@ -8649,12 +8753,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
8649
8753
  * (keep latest per session), and rewrite. Atomic via temp+rename.
8650
8754
  */
8651
8755
  async compactIndex() {
8652
- const entries = await this.readIndex();
8653
- if (entries.length === 0) return;
8654
- const tmp = `${this.indexFile}.compact.tmp`;
8655
- const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
8656
- await fsp6.writeFile(tmp, lines, "utf8");
8657
- await fsp6.rename(tmp, this.indexFile);
8756
+ const t0 = Date.now();
8757
+ let outcome = "success";
8758
+ let errorMsg;
8759
+ try {
8760
+ const entries = await this.readIndex();
8761
+ if (entries.length === 0) return;
8762
+ const tmp = `${this.indexFile}.compact.tmp`;
8763
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
8764
+ await fsp6.writeFile(tmp, lines, "utf8");
8765
+ await fsp6.rename(tmp, this.indexFile);
8766
+ } catch (err) {
8767
+ outcome = "failure";
8768
+ errorMsg = toErrorMessage(err);
8769
+ } finally {
8770
+ this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
8771
+ }
8658
8772
  }
8659
8773
  /**
8660
8774
  * Read the index file and return deduplicated session summaries.
@@ -8719,7 +8833,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8719
8833
  continue;
8720
8834
  if (entry.isDirectory()) {
8721
8835
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
8722
- ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
8836
+ ids.push(...await this.collectSessionIds(path5.join(dir, entry.name), childPrefix, depth + 1));
8723
8837
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
8724
8838
  if (entry.name === "_index.jsonl") continue;
8725
8839
  const base = entry.name.replace(/\.jsonl$/, "");
@@ -8730,22 +8844,31 @@ var DefaultSessionStore = class _DefaultSessionStore {
8730
8844
  }
8731
8845
  async summaryFor(id) {
8732
8846
  const manifest = this.sessionPath(id, ".summary.json");
8847
+ const t0 = Date.now();
8848
+ let outcome = "success";
8849
+ let errorMsg;
8733
8850
  try {
8734
8851
  const raw = await fsp6.readFile(manifest, "utf8");
8852
+ this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
8735
8853
  return JSON.parse(raw);
8736
8854
  } catch {
8737
8855
  const full = this.sessionPath(id, ".jsonl");
8738
- const stat5 = await fsp6.stat(full);
8739
- const summary = await this.summarize(id, stat5.mtime.toISOString());
8856
+ const stat6 = await fsp6.stat(full);
8857
+ const summary = await this.summarize(id, stat6.mtime.toISOString());
8740
8858
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
8859
+ const msg = toErrorMessage(err);
8860
+ this.emitError(id, manifest, "summary_fallback", msg, true);
8741
8861
  console.warn(JSON.stringify({
8742
8862
  level: "warn",
8743
8863
  event: "session_store.manifest_write_failed",
8744
8864
  sessionId: id,
8745
- message: err instanceof Error ? err.message : String(err),
8865
+ message: msg,
8746
8866
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
8747
8867
  }));
8748
8868
  });
8869
+ outcome = "failure";
8870
+ errorMsg = "summary fallback \u2014 manifest rebuilt";
8871
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
8749
8872
  return summary;
8750
8873
  }
8751
8874
  }
@@ -8761,14 +8884,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
8761
8884
  async deleteSession(id) {
8762
8885
  const jsonlPath = this.sessionPath(id, ".jsonl");
8763
8886
  const summaryPath = this.sessionPath(id, ".summary.json");
8764
- const shardDir = path4.dirname(path4.join(this.dir, id));
8765
- const base = path4.basename(id);
8766
- const sessDir = path4.join(shardDir, base);
8887
+ const shardDir = path5.dirname(path5.join(this.dir, id));
8888
+ const base = path5.basename(id);
8889
+ const sessDir = path5.join(shardDir, base);
8767
8890
  const deletions = [
8768
8891
  fsp6.unlink(jsonlPath),
8769
8892
  fsp6.unlink(summaryPath),
8770
- fsp6.unlink(path4.join(shardDir, `${base}.plan.json`)),
8771
- fsp6.unlink(path4.join(shardDir, `${base}.todos.json`))
8893
+ fsp6.unlink(path5.join(shardDir, `${base}.plan.json`)),
8894
+ fsp6.unlink(path5.join(shardDir, `${base}.todos.json`))
8772
8895
  ];
8773
8896
  const results = await Promise.allSettled(deletions);
8774
8897
  for (const r of results) {
@@ -8790,7 +8913,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8790
8913
  level: "warn",
8791
8914
  event: "session_store.rmdir_failed",
8792
8915
  sessionId: id,
8793
- message: err instanceof Error ? err.message : String(err),
8916
+ message: toErrorMessage(err),
8794
8917
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
8795
8918
  }));
8796
8919
  });
@@ -8804,17 +8927,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
8804
8927
  let deleted = 0;
8805
8928
  let activeSessionId = null;
8806
8929
  try {
8807
- const raw = await fsp6.readFile(path4.join(this.dir, "active.json"), "utf8");
8930
+ const raw = await fsp6.readFile(path5.join(this.dir, "active.json"), "utf8");
8808
8931
  const active = JSON.parse(raw);
8809
8932
  activeSessionId = active.sessionId ?? null;
8810
8933
  } catch {
8811
8934
  }
8812
8935
  const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
8813
8936
  const pruneFile = async (dir, name, prefix) => {
8814
- const jsonlPath = path4.join(dir, name);
8937
+ const jsonlPath = path5.join(dir, name);
8815
8938
  try {
8816
- const stat5 = await fsp6.stat(jsonlPath);
8817
- if (stat5.mtimeMs >= cutoff) return;
8939
+ const stat6 = await fsp6.stat(jsonlPath);
8940
+ if (stat6.mtimeMs >= cutoff) return;
8818
8941
  } catch {
8819
8942
  return;
8820
8943
  }
@@ -8831,7 +8954,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8831
8954
  continue;
8832
8955
  }
8833
8956
  if (!entry.isDirectory()) continue;
8834
- const dateDir = path4.join(this.dir, entry.name);
8957
+ const dateDir = path5.join(this.dir, entry.name);
8835
8958
  const files = await fsp6.readdir(dateDir, { withFileTypes: true }).catch(() => []);
8836
8959
  for (const file of files) {
8837
8960
  if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
@@ -8843,7 +8966,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8843
8966
  }
8844
8967
  for (const entry of entries) {
8845
8968
  if (!entry.isDirectory()) continue;
8846
- const dateDir = path4.join(this.dir, entry.name);
8969
+ const dateDir = path5.join(this.dir, entry.name);
8847
8970
  try {
8848
8971
  const remaining = await fsp6.readdir(dateDir);
8849
8972
  if (remaining.length === 0) {
@@ -9011,14 +9134,14 @@ function extractToolCallEnds(events) {
9011
9134
  return result;
9012
9135
  }
9013
9136
  var FileSessionWriter = class _FileSessionWriter {
9014
- constructor(id, handle, startedAt, meta, events, opts = {}) {
9137
+ constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
9015
9138
  this.id = id;
9016
9139
  this.handle = handle;
9017
9140
  this.startedAt = startedAt;
9018
9141
  this.meta = meta;
9019
9142
  this.events = events;
9020
9143
  this.resumed = opts.resumed ?? false;
9021
- this.manifestFile = opts.dir ? path4.join(opts.dir, `${path4.basename(id)}.summary.json`) : "";
9144
+ this.manifestFile = opts.dir ? path5.join(opts.dir, `${path5.basename(id)}.summary.json`) : "";
9022
9145
  this.filePath = opts.filePath ?? "";
9023
9146
  this.secretScrubber = opts.secretScrubber;
9024
9147
  this.onCloseCb = opts.onClose;
@@ -9030,6 +9153,7 @@ var FileSessionWriter = class _FileSessionWriter {
9030
9153
  provider: meta.provider ?? "unknown",
9031
9154
  tokenTotal: 0
9032
9155
  };
9156
+ this.traceId = traceId;
9033
9157
  }
9034
9158
  id;
9035
9159
  handle;
@@ -9062,6 +9186,8 @@ var FileSessionWriter = class _FileSessionWriter {
9062
9186
  lastAppendWarnAt = 0;
9063
9187
  secretScrubber;
9064
9188
  onCloseCb;
9189
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
9190
+ traceId;
9065
9191
  // ── Write buffer — batches events to reduce per-event disk I/O ─────────
9066
9192
  //
9067
9193
  // Every append() pushes the scrubbed event into an in-memory buffer instead
@@ -9215,9 +9341,14 @@ var FileSessionWriter = class _FileSessionWriter {
9215
9341
  const eventCount = this.writeBuffer.length;
9216
9342
  const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
9217
9343
  this.writeBuffer = [];
9344
+ const t0 = Date.now();
9345
+ let outcome = "success";
9346
+ let errorMsg;
9218
9347
  try {
9219
9348
  await this.enqueueWrite(batch);
9220
9349
  } catch (err) {
9350
+ outcome = "failure";
9351
+ errorMsg = toErrorMessage(err);
9221
9352
  this.appendFailCount += eventCount;
9222
9353
  const now = Date.now();
9223
9354
  if (now - this.lastAppendWarnAt > 5e3) {
@@ -9225,12 +9356,24 @@ var FileSessionWriter = class _FileSessionWriter {
9225
9356
  const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
9226
9357
  console.warn(
9227
9358
  "[session] flush failed:",
9228
- err instanceof Error ? err.message : String(err),
9359
+ toErrorMessage(err),
9229
9360
  tail
9230
9361
  );
9231
9362
  this.lastAppendWarnAt = now;
9232
9363
  this.appendFailCount = 0;
9233
9364
  }
9365
+ } finally {
9366
+ this.events?.emit("storage.write", {
9367
+ sessionId: this.id,
9368
+ store: "session",
9369
+ filePath: this.filePath,
9370
+ operation: "flush",
9371
+ outcome,
9372
+ durationMs: Date.now() - t0,
9373
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
9374
+ ...eventCount !== void 0 ? { eventCount } : {},
9375
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
9376
+ });
9234
9377
  }
9235
9378
  }
9236
9379
  observeForSummary(event) {
@@ -9296,14 +9439,46 @@ var FileSessionWriter = class _FileSessionWriter {
9296
9439
  outcome: this.outcome ?? "completed"
9297
9440
  };
9298
9441
  if (this.manifestFile) {
9442
+ const t0 = Date.now();
9443
+ let outcome = "success";
9444
+ let errorMsg;
9299
9445
  try {
9300
9446
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
9301
- } catch {
9447
+ } catch (err) {
9448
+ outcome = "failure";
9449
+ errorMsg = toErrorMessage(err);
9450
+ } finally {
9451
+ this.events?.emit("storage.write", {
9452
+ sessionId: this.id,
9453
+ store: "session",
9454
+ filePath: this.manifestFile,
9455
+ operation: "close",
9456
+ outcome,
9457
+ durationMs: Date.now() - t0,
9458
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
9459
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
9460
+ });
9302
9461
  }
9303
9462
  }
9463
+ const idxT0 = Date.now();
9464
+ let idxOutcome = "success";
9465
+ let idxError;
9304
9466
  try {
9305
9467
  await this.onCloseCb?.(this.summary);
9306
- } catch {
9468
+ } catch (err) {
9469
+ idxOutcome = "failure";
9470
+ idxError = toErrorMessage(err);
9471
+ } finally {
9472
+ this.events?.emit("storage.write", {
9473
+ sessionId: this.summary.id,
9474
+ store: "session",
9475
+ filePath: this.filePath,
9476
+ operation: "index_append",
9477
+ outcome: idxOutcome,
9478
+ durationMs: Date.now() - idxT0,
9479
+ ...idxError !== void 0 ? { error: idxError } : {},
9480
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
9481
+ });
9307
9482
  }
9308
9483
  try {
9309
9484
  await this.handle.close();
@@ -9464,13 +9639,14 @@ function userInputTitle(content) {
9464
9639
  // src/coordination/director-session.ts
9465
9640
  function makeDirectorSessionFactory(opts) {
9466
9641
  const runId = opts.directorRunId ?? `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-director`;
9642
+ const { traceId } = opts;
9467
9643
  let store;
9468
9644
  let dir;
9469
9645
  if (opts.store) {
9470
9646
  store = opts.store;
9471
- dir = opts.sessionsRoot ? path4.join(opts.sessionsRoot, runId) : "(caller-managed)";
9647
+ dir = opts.sessionsRoot ? path5.join(opts.sessionsRoot, runId) : "(caller-managed)";
9472
9648
  } else if (opts.sessionsRoot) {
9473
- dir = path4.join(opts.sessionsRoot, runId);
9649
+ dir = path5.join(opts.sessionsRoot, runId);
9474
9650
  store = new DefaultSessionStore({ dir });
9475
9651
  } else {
9476
9652
  throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
@@ -9479,12 +9655,16 @@ function makeDirectorSessionFactory(opts) {
9479
9655
  dir,
9480
9656
  directorRunId: runId,
9481
9657
  async createSubagentSession({ subagentId, provider, model, title }) {
9482
- return store.create({
9658
+ const writer = await store.create({
9483
9659
  id: subagentId,
9484
9660
  title: title ?? subagentId,
9485
9661
  provider: provider ?? "unknown",
9486
9662
  model: model ?? "unknown"
9487
9663
  });
9664
+ if (traceId !== void 0) {
9665
+ writer.traceId = traceId;
9666
+ }
9667
+ return writer;
9488
9668
  }
9489
9669
  };
9490
9670
  }
@@ -9774,7 +9954,7 @@ var FleetManager = class {
9774
9954
  })),
9775
9955
  usage: this.usage.snapshot()
9776
9956
  };
9777
- await fsp6.mkdir(path4.dirname(this.manifestPath), { recursive: true });
9957
+ await fsp6.mkdir(path5.dirname(this.manifestPath), { recursive: true });
9778
9958
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
9779
9959
  return this.manifestPath;
9780
9960
  }
@@ -9795,7 +9975,7 @@ var FleetManager = class {
9795
9975
  if (!this.manifestPath) return;
9796
9976
  if (this.manifestDebounceMs === 0) {
9797
9977
  void this.writeManifest().catch((err) => {
9798
- const detail = err instanceof Error ? err.message : String(err);
9978
+ const detail = toErrorMessage(err);
9799
9979
  process.emitWarning(
9800
9980
  `FleetManager manifest write failed: ${detail}`,
9801
9981
  "FleetManagerWarning"
@@ -9808,7 +9988,7 @@ var FleetManager = class {
9808
9988
  this.manifestTimer = setTimeout(() => {
9809
9989
  this.manifestTimer = null;
9810
9990
  void this.writeManifest().catch((err) => {
9811
- const detail = err instanceof Error ? err.message : String(err);
9991
+ const detail = toErrorMessage(err);
9812
9992
  process.emitWarning(
9813
9993
  `FleetManager manifest write failed: ${detail}`,
9814
9994
  "FleetManagerWarning"
@@ -9827,7 +10007,7 @@ var FleetManager = class {
9827
10007
  this.manifestTimer = null;
9828
10008
  }
9829
10009
  await this.writeManifest().catch((err) => {
9830
- const detail = err instanceof Error ? err.message : String(err);
10010
+ const detail = toErrorMessage(err);
9831
10011
  process.emitWarning(
9832
10012
  `FleetManager manifest write failed: ${detail}`,
9833
10013
  "FleetManagerWarning"
@@ -9922,7 +10102,7 @@ var LINE_SEPARATOR = "\n";
9922
10102
  var DefaultMailbox = class {
9923
10103
  filePath;
9924
10104
  constructor(sessionDir) {
9925
- this.filePath = path4.join(sessionDir, MAILBOX_FILE);
10105
+ this.filePath = path5.join(sessionDir, MAILBOX_FILE);
9926
10106
  }
9927
10107
  get mailboxPath() {
9928
10108
  return this.filePath;
@@ -9947,7 +10127,7 @@ var DefaultMailbox = class {
9947
10127
  taskContext: input.taskContext
9948
10128
  };
9949
10129
  const line = JSON.stringify(msg) + LINE_SEPARATOR;
9950
- await fsp6.mkdir(path4.dirname(this.filePath), { recursive: true });
10130
+ await fsp6.mkdir(path5.dirname(this.filePath), { recursive: true });
9951
10131
  await withFileLock(this.filePath, async () => {
9952
10132
  await fsp6.appendFile(this.filePath, line, "utf8");
9953
10133
  });
@@ -9987,29 +10167,43 @@ var DefaultMailbox = class {
9987
10167
  }
9988
10168
  // ── Ack ───────────────────────────────────────────────────────────────
9989
10169
  async ack(input) {
9990
- let result = null;
10170
+ const updated = await this.ackMany({ acks: [input] });
10171
+ return updated.length > 0 ? updated[0] : null;
10172
+ }
10173
+ async ackMany(input) {
10174
+ if (input.acks.length === 0) return [];
10175
+ const updated = [];
10176
+ const byId = /* @__PURE__ */ new Map();
10177
+ for (const a of input.acks) byId.set(a.messageId, a);
9991
10178
  await withFileLock(this.filePath, async () => {
9992
10179
  const all = await this._readAll();
9993
- const idx = all.findIndex((m) => m.id === input.messageId);
9994
- if (idx === -1) return;
9995
- const msg = all[idx];
9996
10180
  const now = (/* @__PURE__ */ new Date()).toISOString();
9997
- if (input.read !== false) {
9998
- msg.readBy[input.readerId] = now;
9999
- }
10000
- if (input.completed) {
10001
- msg.completed = true;
10002
- msg.completedBy = input.readerId;
10003
- msg.completedAt = now;
10181
+ let changed = false;
10182
+ for (const msg of all) {
10183
+ const a = byId.get(msg.id);
10184
+ if (!a) continue;
10185
+ updated.push(msg);
10186
+ if (a.read !== false && !(a.readerId in msg.readBy)) {
10187
+ msg.readBy[a.readerId] = now;
10188
+ changed = true;
10189
+ }
10190
+ if (a.completed && !msg.completed) {
10191
+ msg.completed = true;
10192
+ msg.completedBy = a.readerId;
10193
+ msg.completedAt = now;
10194
+ changed = true;
10195
+ }
10196
+ if (a.outcome !== void 0 && msg.outcome !== a.outcome) {
10197
+ msg.outcome = a.outcome;
10198
+ changed = true;
10199
+ }
10004
10200
  }
10005
- if (input.outcome !== void 0) {
10006
- msg.outcome = input.outcome;
10201
+ if (changed) {
10202
+ const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
10203
+ await fsp6.writeFile(this.filePath, serialized, "utf8");
10007
10204
  }
10008
- const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
10009
- await fsp6.writeFile(this.filePath, serialized, "utf8");
10010
- result = msg;
10011
10205
  });
10012
- return result;
10206
+ return updated;
10013
10207
  }
10014
10208
  // ── Agent statuses ────────────────────────────────────────────────────
10015
10209
  async getAgentStatuses() {
@@ -10061,6 +10255,43 @@ var DefaultMailbox = class {
10061
10255
  await fsp6.writeFile(this.filePath, "", "utf8");
10062
10256
  });
10063
10257
  }
10258
+ async purgeStale(opts) {
10259
+ const COMPLETED_MAX_AGE_MS = opts?.completedMaxAgeMs ?? 864e5;
10260
+ const INCOMPLETE_MAX_AGE_MS = opts?.incompleteMaxAgeMs ?? 6048e5;
10261
+ let completedPurged = 0;
10262
+ let incompletePurged = 0;
10263
+ await withFileLock(this.filePath, async () => {
10264
+ const all2 = await this._readAll();
10265
+ const now = Date.now();
10266
+ const cutoffCompleted = now - COMPLETED_MAX_AGE_MS;
10267
+ const cutoffIncomplete = now - INCOMPLETE_MAX_AGE_MS;
10268
+ const kept = [];
10269
+ for (const msg of all2) {
10270
+ const msgTime = new Date(msg.timestamp).getTime();
10271
+ const completedTime = msg.completedAt ? new Date(msg.completedAt).getTime() : 0;
10272
+ if (msg.completed && completedTime < cutoffCompleted) {
10273
+ completedPurged++;
10274
+ continue;
10275
+ }
10276
+ if (!msg.completed && msgTime < cutoffIncomplete) {
10277
+ incompletePurged++;
10278
+ continue;
10279
+ }
10280
+ kept.push(msg);
10281
+ }
10282
+ if (kept.length < all2.length) {
10283
+ const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
10284
+ await fsp6.writeFile(this.filePath, content, "utf8");
10285
+ }
10286
+ });
10287
+ const all = await this._readAll();
10288
+ return {
10289
+ completedPurged,
10290
+ incompletePurged,
10291
+ totalPurged: completedPurged + incompletePurged,
10292
+ remaining: all.length
10293
+ };
10294
+ }
10064
10295
  // ── Client registry stubs (not applicable per-session) ─────────────────
10065
10296
  async registerClient(_input) {
10066
10297
  }
@@ -10229,21 +10460,6 @@ var BrainMonitor = class {
10229
10460
  }
10230
10461
  }
10231
10462
  };
10232
- function projectSlug(absRoot) {
10233
- const base = slugify(path4.basename(absRoot));
10234
- const hash = createHash("sha256").update(path4.resolve(absRoot)).digest("hex").slice(0, 6);
10235
- return `${base}-${hash}`;
10236
- }
10237
- function slugify(name) {
10238
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
10239
- }
10240
- function wstackGlobalRoot() {
10241
- const fromEnv = process.env["WRONGSTACK_HOME"];
10242
- if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
10243
- return path4.join(os.homedir(), ".wrongstack");
10244
- }
10245
-
10246
- // src/coordination/global-mailbox.ts
10247
10463
  var MAILBOX_FILE2 = "_mailbox.jsonl";
10248
10464
  var CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
10249
10465
  var AGENT_STALE_MS = 6e4;
@@ -10251,8 +10467,9 @@ var CLIENT_STALE_MS = 6e4;
10251
10467
  var HEARTBEAT_THROTTLE_MS = 5e3;
10252
10468
  var REGISTRY_CACHE_TTL_MS = 2e3;
10253
10469
  var LINE_SEPARATOR2 = "\n";
10470
+ var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
10254
10471
  function resolveProjectDir(projectRoot, globalRoot) {
10255
- return path4.join(globalRoot, "projects", projectSlug(projectRoot));
10472
+ return path5.join(globalRoot, "projects", projectSlug(projectRoot));
10256
10473
  }
10257
10474
  var GlobalMailbox = class {
10258
10475
  /** Path to the JSONL message file. */
@@ -10283,14 +10500,28 @@ var GlobalMailbox = class {
10283
10500
  _lastHeartbeat = /* @__PURE__ */ new Map();
10284
10501
  /** Last time each local client sent a heartbeat (throttle). */
10285
10502
  _lastClientHeartbeat = /* @__PURE__ */ new Map();
10503
+ /**
10504
+ * In-memory mirror of the JSONL message file. The mailbox is shared
10505
+ * ACROSS PROCESSES, so reads cannot trust the cache blindly — we pair it
10506
+ * with an mtime check. The file lock serializes every write, so a
10507
+ * changed mtimeMs is a definitive signal that another process (or this
10508
+ * one) wrote; an unchanged mtimeMs guarantees no write happened and the
10509
+ * cache is current. This collapses the per-iteration `query()` cost from
10510
+ * O(file_size) disk + parse to O(messages) in memory.
10511
+ */
10512
+ _messageCache = null;
10513
+ /** mtimeMs of the file when `_messageCache` was populated. */
10514
+ _messageCacheMtime = -1;
10515
+ /** Size of the file when `_messageCache` was populated (extra guard). */
10516
+ _messageCacheSize = -1;
10286
10517
  /**
10287
10518
  * @param projectDir — `~/.wrongstack/projects/<slug>/`
10288
10519
  * @param events — optional EventBus for real-time TUI/WebUI notifications
10289
10520
  */
10290
10521
  constructor(projectDir, events) {
10291
- this.messagePath = path4.join(projectDir, MAILBOX_FILE2);
10292
- this.registryPath = path4.join(projectDir, "_mailbox.registry.json");
10293
- this.clientRegistryPath = path4.join(projectDir, CLIENT_REGISTRY_FILE);
10522
+ this.messagePath = path5.join(projectDir, MAILBOX_FILE2);
10523
+ this.registryPath = path5.join(projectDir, "_mailbox.registry.json");
10524
+ this.clientRegistryPath = path5.join(projectDir, CLIENT_REGISTRY_FILE);
10294
10525
  this._events = events;
10295
10526
  }
10296
10527
  // ── Messages ────────────────────────────────────────────────────────────
@@ -10313,73 +10544,89 @@ var GlobalMailbox = class {
10313
10544
  taskContext: input.taskContext
10314
10545
  };
10315
10546
  const line = JSON.stringify(msg) + LINE_SEPARATOR2;
10316
- await fsp6.mkdir(path4.dirname(this.messagePath), { recursive: true });
10547
+ await fsp6.mkdir(path5.dirname(this.messagePath), { recursive: true });
10317
10548
  await withFileLock(this.messagePath, async () => {
10318
10549
  await fsp6.appendFile(this.messagePath, line, "utf8");
10550
+ this._pushToCache(msg);
10319
10551
  });
10320
10552
  return msg;
10321
10553
  }
10322
10554
  async query(q) {
10323
- const all = await this._readMessages();
10555
+ const all = await this._readMessagesCached();
10324
10556
  const limit = q.limit ?? 50;
10325
- let filtered = all;
10326
- if (q.to !== void 0) {
10327
- filtered = filtered.filter((m) => m.to === q.to || m.to === "*");
10328
- }
10329
- if (q.from !== void 0) {
10330
- filtered = filtered.filter((m) => m.from === q.from);
10331
- }
10332
- if (q.unreadBy !== void 0) {
10333
- filtered = filtered.filter((m) => !(q.unreadBy in m.readBy));
10334
- }
10335
- if (q.incompleteOnly) {
10336
- filtered = filtered.filter((m) => !m.completed);
10337
- }
10338
- if (q.type !== void 0) {
10339
- filtered = filtered.filter((m) => m.type === q.type);
10340
- }
10341
- if (q.minPriority !== void 0) {
10342
- const order = { low: 0, normal: 1, high: 2 };
10343
- const min = order[q.minPriority];
10344
- filtered = filtered.filter((m) => (order[m.priority] ?? 1) >= min);
10345
- }
10346
- if (q.since !== void 0) {
10347
- const since = q.since;
10348
- filtered = filtered.filter((m) => m.timestamp > since);
10557
+ const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
10558
+ const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
10559
+ const out = [];
10560
+ for (let i = 0; i < all.length; i++) {
10561
+ const m = all[i];
10562
+ if (q.to !== void 0 && m.to !== q.to && m.to !== "*") continue;
10563
+ if (q.from !== void 0 && m.from !== q.from) continue;
10564
+ if (q.unreadBy !== void 0 && q.unreadBy in m.readBy) continue;
10565
+ if (q.incompleteOnly && m.completed) continue;
10566
+ if (q.type !== void 0 && m.type !== q.type) continue;
10567
+ if (order !== null && (order[m.priority] ?? 1) < minPriorityRank) {
10568
+ continue;
10569
+ }
10570
+ if (q.since !== void 0 && m.timestamp <= q.since) continue;
10571
+ out.push(m);
10349
10572
  }
10350
- filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
10351
- return filtered.slice(0, limit);
10573
+ out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
10574
+ return out.slice(0, limit).map((m) => ({ ...m, readBy: { ...m.readBy } }));
10352
10575
  }
10353
10576
  async ack(input) {
10354
- let result = null;
10577
+ const updated = await this.ackMany({ acks: [input] });
10578
+ return updated.length > 0 ? updated[0] : null;
10579
+ }
10580
+ async ackMany(input) {
10581
+ if (input.acks.length === 0) return [];
10582
+ const updated = [];
10583
+ const byId = /* @__PURE__ */ new Map();
10584
+ for (const a of input.acks) {
10585
+ byId.set(a.messageId, a);
10586
+ }
10587
+ let cacheSnapshot = null;
10355
10588
  await withFileLock(this.messagePath, async () => {
10356
- const all = await this._readMessages();
10357
- const idx = all.findIndex((m) => m.id === input.messageId);
10358
- if (idx === -1) return;
10359
- const msg = all[idx];
10589
+ const all = await this._readMessagesFresh();
10360
10590
  const now = (/* @__PURE__ */ new Date()).toISOString();
10361
- if (input.read !== false) {
10362
- msg.readBy[input.readerId] = now;
10363
- }
10364
- if (input.completed) {
10365
- msg.completed = true;
10366
- msg.completedBy = input.readerId;
10367
- msg.completedAt = now;
10591
+ let changed = false;
10592
+ for (const msg of all) {
10593
+ const a = byId.get(msg.id);
10594
+ if (!a) continue;
10595
+ updated.push(msg);
10596
+ if (a.read !== false && !(a.readerId in msg.readBy)) {
10597
+ msg.readBy[a.readerId] = now;
10598
+ changed = true;
10599
+ }
10600
+ if (a.completed && !msg.completed) {
10601
+ msg.completed = true;
10602
+ msg.completedBy = a.readerId;
10603
+ msg.completedAt = now;
10604
+ changed = true;
10605
+ }
10606
+ if (a.outcome !== void 0 && msg.outcome !== a.outcome) {
10607
+ msg.outcome = a.outcome;
10608
+ changed = true;
10609
+ }
10368
10610
  }
10369
- if (input.outcome !== void 0) {
10370
- msg.outcome = input.outcome;
10611
+ if (changed) {
10612
+ const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
10613
+ await fsp6.writeFile(this.messagePath, serialized, "utf8");
10371
10614
  }
10372
- const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
10373
- await fsp6.writeFile(this.messagePath, serialized, "utf8");
10374
- result = msg;
10615
+ cacheSnapshot = all;
10375
10616
  });
10376
- return result;
10617
+ if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
10618
+ return updated;
10377
10619
  }
10378
10620
  async unreadCount(forAgentId) {
10379
- const all = await this._readMessages();
10380
- return all.filter(
10381
- (m) => (m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed
10382
- ).length;
10621
+ const all = await this._readMessagesCached();
10622
+ let count = 0;
10623
+ for (let i = 0; i < all.length; i++) {
10624
+ const m = all[i];
10625
+ if ((m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed) {
10626
+ count++;
10627
+ }
10628
+ }
10629
+ return count;
10383
10630
  }
10384
10631
  // ── Agent registry ──────────────────────────────────────────────────────
10385
10632
  async registerAgent(input) {
@@ -10540,13 +10787,62 @@ var GlobalMailbox = class {
10540
10787
  async close() {
10541
10788
  this._registryCache = null;
10542
10789
  this._clientRegistryCache = null;
10790
+ this._messageCache = null;
10791
+ this._messageCacheMtime = -1;
10792
+ this._messageCacheSize = -1;
10543
10793
  }
10544
10794
  async clearAll() {
10545
10795
  await withFileLock(this.messagePath, async () => {
10546
10796
  await fsp6.writeFile(this.messagePath, "", "utf8");
10547
10797
  });
10798
+ this._setMessageCache([]);
10799
+ }
10800
+ async purgeStale(opts) {
10801
+ const COMPLETED_MAX_AGE_MS = opts?.completedMaxAgeMs ?? 864e5;
10802
+ const INCOMPLETE_MAX_AGE_MS = opts?.incompleteMaxAgeMs ?? 6048e5;
10803
+ let completedPurged = 0;
10804
+ let incompletePurged = 0;
10805
+ let remaining = 0;
10806
+ await withFileLock(this.messagePath, async () => {
10807
+ const all = await this._readMessagesFresh();
10808
+ const now = Date.now();
10809
+ const cutoffCompleted = now - COMPLETED_MAX_AGE_MS;
10810
+ const cutoffIncomplete = now - INCOMPLETE_MAX_AGE_MS;
10811
+ const kept = [];
10812
+ for (const msg of all) {
10813
+ const msgTime = new Date(msg.timestamp).getTime();
10814
+ const completedTime = msg.completedAt ? new Date(msg.completedAt).getTime() : 0;
10815
+ if (msg.completed && completedTime < cutoffCompleted) {
10816
+ completedPurged++;
10817
+ continue;
10818
+ }
10819
+ if (!msg.completed && msgTime < cutoffIncomplete) {
10820
+ incompletePurged++;
10821
+ continue;
10822
+ }
10823
+ kept.push(msg);
10824
+ }
10825
+ remaining = kept.length;
10826
+ if (kept.length < all.length) {
10827
+ const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR2) + LINE_SEPARATOR2;
10828
+ await fsp6.writeFile(this.messagePath, content, "utf8");
10829
+ }
10830
+ this._setMessageCache(kept);
10831
+ });
10832
+ return {
10833
+ completedPurged,
10834
+ incompletePurged,
10835
+ totalPurged: completedPurged + incompletePurged,
10836
+ remaining
10837
+ };
10548
10838
  }
10549
10839
  // ── Internal ────────────────────────────────────────────────────────────
10840
+ /**
10841
+ * Read all messages from the JSONL file. Always reads + parses the file.
10842
+ * Callers that can tolerate a stale-by-mtime view should use
10843
+ * {@link _readMessagesCached}; writers that need the post-lock truth
10844
+ * should call this directly (it's what {@link _readMessagesFresh} aliases).
10845
+ */
10550
10846
  async _readMessages() {
10551
10847
  try {
10552
10848
  const raw = await fsp6.readFile(this.messagePath, "utf8");
@@ -10574,8 +10870,85 @@ var GlobalMailbox = class {
10574
10870
  throw err;
10575
10871
  }
10576
10872
  }
10873
+ /**
10874
+ * Read messages, then adopt the result as the in-memory cache. Use this
10875
+ * from writers that just took the file lock — the read reflects the
10876
+ * authoritative post-lock state and should be served to subsequent
10877
+ * queries without re-reading.
10878
+ */
10879
+ async _readMessagesFresh() {
10880
+ const all = await this._readMessages();
10881
+ this._setMessageCache(all);
10882
+ return all;
10883
+ }
10884
+ /**
10885
+ * Read messages, consulting the mtime-bounded in-memory cache first.
10886
+ * The mailbox file is shared across processes; every `send`/`ack`/
10887
+ * `clearAll`/`purgeStale` takes the file lock, so writes are serialized
10888
+ * and a changed mtimeMs is a definitive freshness signal. When the
10889
+ * stat matches the cached mtime+size we return the cached array — no
10890
+ * file read and no JSON.parse — collapsing the per-iteration query
10891
+ * cost on the mailbox-loop hot path.
10892
+ */
10893
+ async _readMessagesCached() {
10894
+ try {
10895
+ const st = await fsp6.stat(this.messagePath);
10896
+ if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
10897
+ return this._messageCache;
10898
+ }
10899
+ const all = await this._readMessages();
10900
+ this._setMessageCache(all, st.mtimeMs, st.size);
10901
+ return all;
10902
+ } catch (err) {
10903
+ if (err.code === "ENOENT") {
10904
+ this._setMessageCache([], -1, -1);
10905
+ return [];
10906
+ }
10907
+ throw err;
10908
+ }
10909
+ }
10910
+ /**
10911
+ * Replace the in-memory cache. Caller is responsible for guaranteeing
10912
+ * that `messages` reflects the current on-disk state (e.g. they just
10913
+ * read or wrote it under the file lock).
10914
+ */
10915
+ _setMessageCache(messages, mtime, size) {
10916
+ if (messages.length > MESSAGE_CACHE_MAX_ENTRIES) {
10917
+ this._messageCache = null;
10918
+ this._messageCacheMtime = -1;
10919
+ this._messageCacheSize = -1;
10920
+ return;
10921
+ }
10922
+ this._messageCache = messages;
10923
+ if (mtime !== void 0 && size !== void 0) {
10924
+ this._messageCacheMtime = mtime;
10925
+ this._messageCacheSize = size;
10926
+ } else {
10927
+ void fsp6.stat(this.messagePath).then((st) => {
10928
+ this._messageCacheMtime = st.mtimeMs;
10929
+ this._messageCacheSize = st.size;
10930
+ }).catch(() => {
10931
+ });
10932
+ }
10933
+ }
10934
+ /**
10935
+ * Append a single just-sent message to the in-memory cache without
10936
+ * re-reading the file. The caller must hold the file lock (or have
10937
+ * just released it after a successful append) so the cache stays
10938
+ * consistent with on-disk state.
10939
+ */
10940
+ _pushToCache(msg) {
10941
+ if (this._messageCache === null) return;
10942
+ if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES) {
10943
+ this._messageCache = null;
10944
+ this._messageCacheMtime = -1;
10945
+ this._messageCacheSize = -1;
10946
+ return;
10947
+ }
10948
+ this._messageCache.push(msg);
10949
+ }
10577
10950
  async _ensureRegistry() {
10578
- await fsp6.mkdir(path4.dirname(this.registryPath), { recursive: true });
10951
+ await fsp6.mkdir(path5.dirname(this.registryPath), { recursive: true });
10579
10952
  }
10580
10953
  async _readRegistry(opts) {
10581
10954
  if (!opts?.fresh && this._registryCache && Date.now() - this._registryCacheAt < REGISTRY_CACHE_TTL_MS) {
@@ -10620,7 +10993,7 @@ var GlobalMailbox = class {
10620
10993
  }
10621
10994
  // ── Client registry internals ───────────────────────────────────────────
10622
10995
  async _ensureClientRegistry() {
10623
- await fsp6.mkdir(path4.dirname(this.clientRegistryPath), { recursive: true });
10996
+ await fsp6.mkdir(path5.dirname(this.clientRegistryPath), { recursive: true });
10624
10997
  }
10625
10998
  async _readClientRegistry(opts) {
10626
10999
  if (!opts?.fresh && this._clientRegistryCache && Date.now() - this._clientRegistryCacheAt < REGISTRY_CACHE_TTL_MS) {
@@ -11260,7 +11633,7 @@ function createMailboxHooks(opts) {
11260
11633
  var DEFAULT_MAX_ENTRIES = 1e4;
11261
11634
  var LOG_FILENAME = "package-authors.json";
11262
11635
  function logPath(storageDir) {
11263
- return path4.join(storageDir, LOG_FILENAME);
11636
+ return path5.join(storageDir, LOG_FILENAME);
11264
11637
  }
11265
11638
  async function loadLog(storageDir, projectRoot) {
11266
11639
  try {
@@ -11284,7 +11657,7 @@ async function saveLog(storageDir, log) {
11284
11657
  await fsp6.rename(tmp, logPath(storageDir));
11285
11658
  }
11286
11659
  function detectEcosystem(manifestPath) {
11287
- const name = path4.basename(manifestPath).toLowerCase();
11660
+ const name = path5.basename(manifestPath).toLowerCase();
11288
11661
  if (name === "package.json") return "npm";
11289
11662
  if (name === "go.mod") return "go";
11290
11663
  if (name === "cargo.toml") return "cargo";
@@ -11481,7 +11854,7 @@ function startPackageOutdatedWatcher(opts) {
11481
11854
  );
11482
11855
  } catch (err) {
11483
11856
  handleError(err);
11484
- log(`[pkg-outdated-watcher] Failed to notify for ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
11857
+ log(`[pkg-outdated-watcher] Failed to notify for ${entry.name}: ${toErrorMessage(err)}`);
11485
11858
  }
11486
11859
  }
11487
11860
  }
@@ -11554,7 +11927,2055 @@ mvn versions:use-latest-versions`;
11554
11927
  return `# Update ${entry.name} to ${entry.latestVersion} using your package manager`;
11555
11928
  }
11556
11929
  }
11930
+ var KnowledgeGraph = class {
11931
+ nodes = /* @__PURE__ */ new Map();
11932
+ index = /* @__PURE__ */ new Map();
11933
+ // tag/field → node ids
11934
+ subs = /* @__PURE__ */ new Map();
11935
+ pendingDeliveries = /* @__PURE__ */ new Map();
11936
+ filePath;
11937
+ graphFilePath;
11938
+ constructor(sessionDir) {
11939
+ this.filePath = path5.join(sessionDir, "_knowledge_graph");
11940
+ this.graphFilePath = path5.join(this.filePath, "graph.jsonl");
11941
+ }
11942
+ // ── Write ──────────────────────────────────────────────────────────────
11943
+ /**
11944
+ * Add a node. Fires to all matching subscriptions synchronously.
11945
+ * Returns the node with its assigned id.
11946
+ */
11947
+ async add(node) {
11948
+ const full = { id: randomUUID(), ...node };
11949
+ this.nodes.set(full.id, full);
11950
+ this._index(full);
11951
+ await this._persist(full);
11952
+ this._deliver(full);
11953
+ return full;
11954
+ }
11955
+ /** Update an existing node by id. Returns updated node or null if not found. */
11956
+ async update(id, patch) {
11957
+ const existing = this.nodes.get(id);
11958
+ if (!existing) return null;
11959
+ const updated = { ...existing, ...patch };
11960
+ this.nodes.set(id, updated);
11961
+ this._deliver(updated);
11962
+ await this._append(updated);
11963
+ return updated;
11964
+ }
11965
+ // ── Read ───────────────────────────────────────────────────────────────
11966
+ get(id) {
11967
+ return this.nodes.get(id);
11968
+ }
11969
+ getAll(filter) {
11970
+ return Array.from(this.nodes.values()).filter((n) => this._matches(n, filter ?? {}));
11971
+ }
11972
+ getGoals(filter) {
11973
+ return this.getAll({ type: "goal", ...filter });
11974
+ }
11975
+ getFacts(filter) {
11976
+ return this.getAll({ type: "fact", ...filter });
11977
+ }
11978
+ getChanges(filter) {
11979
+ return this.getAll({ type: "change", ...filter });
11980
+ }
11981
+ getOpenGoals() {
11982
+ return this.getGoals({ status: "pending" }).concat(
11983
+ this.getGoals({ status: "in_progress" })
11984
+ );
11985
+ }
11986
+ getTopLevelGoals() {
11987
+ return this.getGoals({}).filter((g) => !g.parentGoal);
11988
+ }
11989
+ getBlockedGoals() {
11990
+ return this.getGoals({ status: "blocked" });
11991
+ }
11992
+ getPendingChanges() {
11993
+ return this.getChanges({ status: "proposed" });
11994
+ }
11995
+ getDecisions(since) {
11996
+ return this.getAll({ type: "decision", since });
11997
+ }
11998
+ // ── Search ─────────────────────────────────────────────────────────────
11999
+ searchFacts(query) {
12000
+ const q = query.toLowerCase();
12001
+ return this.getFacts().filter(
12002
+ (f) => f.subject.toLowerCase().includes(q) || f.detail.toLowerCase().includes(q) || f.file?.toLowerCase().includes(q) || f.tags.some((t) => t.toLowerCase().includes(q))
12003
+ );
12004
+ }
12005
+ getRelatedFacts(factId) {
12006
+ const fact = this.nodes.get(factId);
12007
+ if (!fact) return [];
12008
+ return fact.related.map((id) => this.nodes.get(id)).filter((n) => n?.type === "fact");
12009
+ }
12010
+ // ── Subscriptions ──────────────────────────────────────────────────────
12011
+ /**
12012
+ * Subscribe to nodes matching a filter. Returns a channel id that can be
12013
+ * used to poll for new nodes since the last check.
12014
+ */
12015
+ subscribe(agentId, filter) {
12016
+ const channel = randomUUID();
12017
+ const sub = { id: randomUUID(), agentId, filter, channel };
12018
+ this.subs.set(channel, sub);
12019
+ this.pendingDeliveries.set(channel, []);
12020
+ return channel;
12021
+ }
12022
+ /**
12023
+ * Poll for new nodes delivered to a channel since last check.
12024
+ * Clears the delivery buffer after reading.
12025
+ */
12026
+ poll(channel) {
12027
+ const pending = this.pendingDeliveries.get(channel);
12028
+ if (!pending) return [];
12029
+ const delivered = [...pending];
12030
+ pending.length = 0;
12031
+ return delivered;
12032
+ }
12033
+ unsubscribe(channel) {
12034
+ this.subs.delete(channel);
12035
+ this.pendingDeliveries.delete(channel);
12036
+ }
12037
+ // ── Quality gate helpers ───────────────────────────────────────────────
12038
+ /**
12039
+ * Create a quality gate result. Call this when a change is being proposed
12040
+ * so the change node carries the gate result.
12041
+ */
12042
+ static makeQualityGate(checks) {
12043
+ return { passed: checks.every((c) => c.passed), checks };
12044
+ }
12045
+ // ── Private ────────────────────────────────────────────────────────────
12046
+ _index(node) {
12047
+ const add = (key) => {
12048
+ let set = this.index.get(key);
12049
+ if (!set) {
12050
+ set = /* @__PURE__ */ new Set();
12051
+ this.index.set(key, set);
12052
+ }
12053
+ set.add(node.id);
12054
+ };
12055
+ add(`type:${node.type}`);
12056
+ if (node.type === "fact") {
12057
+ const f = node;
12058
+ add(`cat:${f.category}`);
12059
+ if (f.severity) add(`sev:${f.severity}`);
12060
+ add(`by:${f.discoveredBy}`);
12061
+ for (const tag of f.tags) add(`tag:${tag}`);
12062
+ add(`key:${f.key}`);
12063
+ }
12064
+ if (node.type === "goal") {
12065
+ const g = node;
12066
+ add(`status:${g.status}`);
12067
+ add(`prio:${g.priority}`);
12068
+ if (g.assignee) add(`assign:${g.assignee}`);
12069
+ for (const tag of g.tags) add(`tag:${tag}`);
12070
+ }
12071
+ if (node.type === "change") {
12072
+ const c = node;
12073
+ add(`change:${c.status}`);
12074
+ add(`by:${c.proposedBy}`);
12075
+ for (const g of c.satisfiesGoals) add(`goal:${g}`);
12076
+ }
12077
+ }
12078
+ _matches(node, f) {
12079
+ if (f.type && node.type !== f.type) return false;
12080
+ if (f.category && node.category !== f.category) return false;
12081
+ if (f.status) {
12082
+ if (node.type === "goal" && node.status !== f.status) return false;
12083
+ if (node.type === "change" && node.status !== f.status) return false;
12084
+ }
12085
+ if (f.assignee && node.assignee !== f.assignee) return false;
12086
+ if (f.discoveredBy && node.discoveredBy !== f.discoveredBy) return false;
12087
+ if (f.proposedBy && node.proposedBy !== f.proposedBy) return false;
12088
+ if (f.tags?.length) {
12089
+ const nodeTags = node.tags ?? node.tags ?? [];
12090
+ if (!f.tags.some((t) => nodeTags.includes(t))) return false;
12091
+ }
12092
+ if (f.since && node.id > f.since) ;
12093
+ return true;
12094
+ }
12095
+ _deliver(node) {
12096
+ for (const sub of this.subs.values()) {
12097
+ if (this._matches(node, sub.filter)) {
12098
+ const pending = this.pendingDeliveries.get(sub.channel);
12099
+ if (pending) pending.push(node);
12100
+ }
12101
+ }
12102
+ }
12103
+ async _persist(node) {
12104
+ await fsp6.mkdir(this.filePath, { recursive: true });
12105
+ const line = JSON.stringify(node) + "\n";
12106
+ await withFileLock(this.graphFilePath, async () => {
12107
+ await fsp6.appendFile(this.graphFilePath, line, "utf8");
12108
+ });
12109
+ }
12110
+ async _append(node) {
12111
+ await fsp6.mkdir(this.filePath, { recursive: true });
12112
+ const line = JSON.stringify({ op: "update", node }) + "\n";
12113
+ await withFileLock(this.graphFilePath, async () => {
12114
+ await fsp6.appendFile(this.graphFilePath, line, "utf8");
12115
+ });
12116
+ }
12117
+ /** Rebuild in-memory state from the log file. Call on startup. */
12118
+ async load() {
12119
+ try {
12120
+ const content = await fsp6.readFile(this.graphFilePath, "utf8");
12121
+ const lines = content.split("\n").filter(Boolean);
12122
+ for (const line of lines) {
12123
+ try {
12124
+ const parsed = JSON.parse(line);
12125
+ if (parsed.op === "update") {
12126
+ this.nodes.set(parsed.node.id, parsed.node);
12127
+ this._index(parsed.node);
12128
+ } else {
12129
+ this.nodes.set(parsed.id, parsed);
12130
+ this._index(parsed);
12131
+ }
12132
+ } catch {
12133
+ }
12134
+ }
12135
+ } catch {
12136
+ }
12137
+ }
12138
+ /** Snapshot for serialization. */
12139
+ snapshot() {
12140
+ return {
12141
+ nodes: Array.from(this.nodes.values()),
12142
+ subs: this.subs.size
12143
+ };
12144
+ }
12145
+ };
12146
+
12147
+ // src/coordination/task-dag.ts
12148
+ var TaskDAG = class {
12149
+ nodes = /* @__PURE__ */ new Map();
12150
+ handlers = /* @__PURE__ */ new Set();
12151
+ runnablesHandlers = /* @__PURE__ */ new Set();
12152
+ runnableCache = null;
12153
+ // ── Node management ───────────────────────────────────────────────────
12154
+ /**
12155
+ * Add a task node. Dependencies are validated for cycles.
12156
+ * Throws if adding a dep would create a cycle.
12157
+ */
12158
+ addNode(id, description, deps = [], opts = {}) {
12159
+ if (this.nodes.has(id)) return;
12160
+ for (const depId of deps) {
12161
+ if (!this.nodes.has(depId)) {
12162
+ throw new Error(`TaskDAG.addNode: unknown dependency "${depId}" for task "${id}". Add the dep first.`);
12163
+ }
12164
+ }
12165
+ if (this._wouldCycle(id, deps)) {
12166
+ throw new Error(`TaskDAG.addNode: adding deps [${deps.join(", ")}] to "${id}" would create a cycle.`);
12167
+ }
12168
+ const node = {
12169
+ id,
12170
+ description,
12171
+ deps: [...deps],
12172
+ status: deps.length === 0 ? "ready" : "pending",
12173
+ role: opts.role,
12174
+ priority: opts.priority ?? 5,
12175
+ dependents: [],
12176
+ tags: opts.tags ?? []
12177
+ };
12178
+ this.nodes.set(id, node);
12179
+ for (const depId of deps) {
12180
+ this.nodes.get(depId).dependents.push(id);
12181
+ }
12182
+ this.invalidateCache();
12183
+ this._emitReady();
12184
+ }
12185
+ /**
12186
+ * Remove a node and all edges to/from it.
12187
+ * Skips any dependents that would become dangling.
12188
+ */
12189
+ removeNode(id) {
12190
+ const node = this.nodes.get(id);
12191
+ if (!node) return;
12192
+ for (const depId of node.deps) {
12193
+ const dep = this.nodes.get(depId);
12194
+ if (dep) {
12195
+ dep.dependents = dep.dependents.filter((d) => d !== id);
12196
+ }
12197
+ }
12198
+ for (const depId of node.dependents) {
12199
+ const dep = this.nodes.get(depId);
12200
+ if (dep && dep.deps.every((d) => !this.nodes.has(d) || this.nodes.get(d).status === "done")) {
12201
+ this._transition(depId, "pending", "ready");
12202
+ }
12203
+ }
12204
+ this.nodes.delete(id);
12205
+ this.invalidateCache();
12206
+ }
12207
+ // ── State transitions ──────────────────────────────────────────────────
12208
+ /**
12209
+ * Mark a task as running. Returns true if the transition was valid
12210
+ * (task was in 'ready' state), false otherwise.
12211
+ */
12212
+ start(id, assignedTo) {
12213
+ const node = this.nodes.get(id);
12214
+ if (!node) return false;
12215
+ if (node.status !== "ready") return false;
12216
+ node.status = "running";
12217
+ node.assignedTo = assignedTo;
12218
+ node.spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
12219
+ this.invalidateCache();
12220
+ this._emit({ type: "node:started", nodeId: id, assignedTo });
12221
+ return true;
12222
+ }
12223
+ /**
12224
+ * Mark a task as completed. Unblocks all dependents; they become 'ready'
12225
+ * if all their deps are done.
12226
+ */
12227
+ complete(id, result) {
12228
+ const node = this.nodes.get(id);
12229
+ if (!node) return;
12230
+ node.status = "done";
12231
+ node.result = result;
12232
+ node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
12233
+ this.invalidateCache();
12234
+ const blocked = [];
12235
+ for (const depId of node.dependents) {
12236
+ const dep = this.nodes.get(depId);
12237
+ if (!dep) continue;
12238
+ const allDone = dep.deps.filter((d) => this.nodes.has(d)).every((d) => this.nodes.get(d).status === "done");
12239
+ if (allDone) {
12240
+ this._transition(depId, "pending", "ready");
12241
+ } else {
12242
+ blocked.push(depId);
12243
+ }
12244
+ }
12245
+ this._emit({ type: "node:completed", nodeId: id, result, blockers: blocked });
12246
+ if (blocked.length === 0) this._emitReady();
12247
+ }
12248
+ /**
12249
+ * Mark a task as failed. Unblocks dependents but they remain 'pending'
12250
+ * (they may still be runnable if other deps succeeded).
12251
+ */
12252
+ fail(id, error) {
12253
+ const node = this.nodes.get(id);
12254
+ if (!node) return;
12255
+ node.status = "failed";
12256
+ node.error = error;
12257
+ node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
12258
+ this.invalidateCache();
12259
+ const blocked = [];
12260
+ for (const depId of node.dependents) {
12261
+ const dep = this.nodes.get(depId);
12262
+ if (!dep) continue;
12263
+ const allDone = dep.deps.filter((d) => this.nodes.has(d)).every((d) => {
12264
+ const s = this.nodes.get(d).status;
12265
+ return s === "done" || s === "skipped";
12266
+ });
12267
+ if (allDone) {
12268
+ this._transition(depId, "pending", "ready");
12269
+ } else {
12270
+ blocked.push(depId);
12271
+ }
12272
+ }
12273
+ this._emit({ type: "node:failed", nodeId: id, error, blockers: blocked });
12274
+ if (blocked.length === 0) this._emitReady();
12275
+ }
12276
+ /**
12277
+ * Skip a task (e.g., it was deemed unnecessary by an earlier step).
12278
+ * Treats it as done for dependency purposes.
12279
+ */
12280
+ skip(id, reason) {
12281
+ const node = this.nodes.get(id);
12282
+ if (!node) return;
12283
+ node.status = "skipped";
12284
+ node.completedAt = (/* @__PURE__ */ new Date()).toISOString();
12285
+ this.invalidateCache();
12286
+ for (const depId of node.dependents) {
12287
+ const dep = this.nodes.get(depId);
12288
+ if (!dep) continue;
12289
+ const allDone = dep.deps.filter((d) => this.nodes.has(d)).every((d) => {
12290
+ const s = this.nodes.get(d).status;
12291
+ return s === "done" || s === "skipped";
12292
+ });
12293
+ if (allDone) this._transition(depId, "pending", "ready");
12294
+ }
12295
+ this._emit({ type: "node:skipped", nodeId: id, reason });
12296
+ }
12297
+ // ── Queries ────────────────────────────────────────────────────────────
12298
+ getNode(id) {
12299
+ return this.nodes.get(id);
12300
+ }
12301
+ getAll() {
12302
+ return Array.from(this.nodes.values());
12303
+ }
12304
+ getReady() {
12305
+ if (this.runnableCache) return this.runnableCache;
12306
+ const runnable = Array.from(this.nodes.values()).filter((n) => n.status === "ready").sort((a, b) => a.priority - b.priority);
12307
+ this.runnableCache = runnable;
12308
+ return runnable;
12309
+ }
12310
+ getRunning() {
12311
+ return Array.from(this.nodes.values()).filter((n) => n.status === "running");
12312
+ }
12313
+ getPending() {
12314
+ return Array.from(this.nodes.values()).filter((n) => n.status === "pending");
12315
+ }
12316
+ getDone() {
12317
+ return Array.from(this.nodes.values()).filter((n) => n.status === "done");
12318
+ }
12319
+ getFailed() {
12320
+ return Array.from(this.nodes.values()).filter((n) => n.status === "failed");
12321
+ }
12322
+ getCompleted() {
12323
+ return Array.from(this.nodes.values()).filter((n) => n.status === "done" || n.status === "skipped");
12324
+ }
12325
+ isDone() {
12326
+ return Array.from(this.nodes.values()).every(
12327
+ (n) => n.status === "done" || n.status === "failed" || n.status === "skipped"
12328
+ );
12329
+ }
12330
+ isFailed() {
12331
+ return Array.from(this.nodes.values()).some((n) => n.status === "failed");
12332
+ }
12333
+ /** All tasks that are currently blocked (pending but not ready). */
12334
+ getBlocked() {
12335
+ return Array.from(this.nodes.values()).filter((n) => n.status === "pending");
12336
+ }
12337
+ /** Topological sort — tasks in dependency order. */
12338
+ getTopologicalOrder() {
12339
+ const visited = /* @__PURE__ */ new Set();
12340
+ const result = [];
12341
+ const visit = (id) => {
12342
+ if (visited.has(id)) return;
12343
+ visited.add(id);
12344
+ const node = this.nodes.get(id);
12345
+ if (!node) return;
12346
+ for (const depId of node.deps) visit(depId);
12347
+ result.push(node);
12348
+ };
12349
+ for (const id of this.nodes.keys()) visit(id);
12350
+ return result;
12351
+ }
12352
+ /** Check for deadlock: no runnable tasks but not done. */
12353
+ hasDeadlock() {
12354
+ if (this.isDone()) return false;
12355
+ return this.getReady().length === 0 && this.getRunning().length === 0;
12356
+ }
12357
+ /** Stats snapshot for reporting. */
12358
+ stats() {
12359
+ const all = Array.from(this.nodes.values());
12360
+ const done = all.filter((n) => n.status === "done" || n.status === "skipped").length;
12361
+ return {
12362
+ total: all.length,
12363
+ pending: all.filter((n) => n.status === "pending").length,
12364
+ ready: all.filter((n) => n.status === "ready").length,
12365
+ running: all.filter((n) => n.status === "running").length,
12366
+ done: all.filter((n) => n.status === "done").length,
12367
+ failed: all.filter((n) => n.status === "failed").length,
12368
+ skipped: all.filter((n) => n.status === "skipped").length,
12369
+ progress: all.length ? done / all.length : 0
12370
+ };
12371
+ }
12372
+ // ── Events ────────────────────────────────────────────────────────────
12373
+ onEvent(handler) {
12374
+ this.handlers.add(handler);
12375
+ return () => void this.handlers.delete(handler);
12376
+ }
12377
+ onRunnable(handler) {
12378
+ this.runnablesHandlers.add(handler);
12379
+ return () => void this.runnablesHandlers.delete(handler);
12380
+ }
12381
+ // ── Private ───────────────────────────────────────────────────────────
12382
+ _transition(id, from, to) {
12383
+ const node = this.nodes.get(id);
12384
+ if (!node || node.status !== from) return;
12385
+ node.status = to;
12386
+ this.invalidateCache();
12387
+ if (to === "ready") {
12388
+ this._emit({ type: "node:ready", nodeId: id, deps: node.deps });
12389
+ }
12390
+ }
12391
+ _emit(event) {
12392
+ for (const h of this.handlers) {
12393
+ try {
12394
+ h(event);
12395
+ } catch {
12396
+ }
12397
+ }
12398
+ }
12399
+ _emitReady() {
12400
+ const runnable = this.getReady();
12401
+ if (this.hasDeadlock()) {
12402
+ this._emit({ type: "deadlock", blocked: this.getBlocked().map((n) => n.id) });
12403
+ } else {
12404
+ this._emit({ type: "graph:done", allDone: this.isDone() });
12405
+ }
12406
+ if (runnable.length > 0) {
12407
+ for (const h of this.runnablesHandlers) {
12408
+ try {
12409
+ h(runnable);
12410
+ } catch {
12411
+ }
12412
+ }
12413
+ }
12414
+ }
12415
+ invalidateCache() {
12416
+ this.runnableCache = null;
12417
+ }
12418
+ /**
12419
+ * DFS cycle detection. Adding edge (id → dep) creates a cycle if
12420
+ * there already exists a path from dep to id.
12421
+ */
12422
+ _wouldCycle(id, newDeps) {
12423
+ const visited = /* @__PURE__ */ new Set();
12424
+ const stack = [...newDeps];
12425
+ while (stack.length > 0) {
12426
+ const current = stack.pop();
12427
+ if (current === id) return true;
12428
+ if (visited.has(current)) continue;
12429
+ visited.add(current);
12430
+ const node = this.nodes.get(current);
12431
+ if (node) stack.push(...node.dependents);
12432
+ }
12433
+ return false;
12434
+ }
12435
+ };
12436
+
12437
+ // src/coordination/consensus-protocol.ts
12438
+ var ConsensusProtocol = class {
12439
+ graph;
12440
+ fleet;
12441
+ rules;
12442
+ voters;
12443
+ // agentId → config
12444
+ constructor(opts) {
12445
+ this.graph = opts.graph;
12446
+ this.fleet = opts.fleet ?? void 0;
12447
+ this.voters = new Map(opts.voters.map((v) => [v.agentId, v]));
12448
+ this.rules = {
12449
+ quorumFraction: opts.rules?.quorumFraction ?? 0.5,
12450
+ approvalFraction: opts.rules?.approvalFraction ?? 0.6,
12451
+ vetoRoles: opts.rules?.vetoRoles ?? [],
12452
+ approvalWeightFraction: opts.rules?.approvalWeightFraction
12453
+ };
12454
+ }
12455
+ // ── Vote lifecycle ────────────────────────────────────────────────────
12456
+ /**
12457
+ * Initiate a vote on a proposed change. Updates the change node's status
12458
+ * to 'proposed' and notifies eligible voters via FleetBus.
12459
+ */
12460
+ initiateVote(changeId) {
12461
+ const change = this.graph.get(changeId);
12462
+ if (!change || change.type !== "change") {
12463
+ throw new Error(`ConsensusProtocol: no change found with id "${changeId}"`);
12464
+ }
12465
+ this.graph.update(changeId, { status: "proposed", votes: [] });
12466
+ const eligible = this._eligibleVoters(change);
12467
+ this._notifyVoters(change, eligible, "vote_initiated");
12468
+ }
12469
+ /**
12470
+ * Cast a vote. Updates the change node in the graph and re-evaluates
12471
+ * consensus. If the vote triggers a resolution, updates the change status.
12472
+ */
12473
+ castVote(changeId, voterId, value, rationale) {
12474
+ const change = this.graph.get(changeId);
12475
+ if (!change || change.type !== "change") {
12476
+ throw new Error(`ConsensusProtocol: no change found for "${changeId}"`);
12477
+ }
12478
+ const voter = this.voters.get(voterId);
12479
+ if (!voter) {
12480
+ throw new Error(`ConsensusProtocol: unknown voter "${voterId}"`);
12481
+ }
12482
+ const eligible = this._eligibleVoters(change);
12483
+ if (!eligible.includes(voterId)) {
12484
+ throw new Error(`ConsensusProtocol: voter "${voterId}" is not eligible for this vote`);
12485
+ }
12486
+ const vote = {
12487
+ agentId: voterId,
12488
+ agentName: voter.agentName,
12489
+ value,
12490
+ rationale,
12491
+ votedAt: (/* @__PURE__ */ new Date()).toISOString()
12492
+ };
12493
+ const existingIdx = change.votes.findIndex((v) => v.agentId === voterId);
12494
+ const newVotes = existingIdx >= 0 ? change.votes.with(existingIdx, vote) : [...change.votes, vote];
12495
+ const result = this._resolve(changeId, newVotes, eligible);
12496
+ this.graph.update(changeId, {
12497
+ votes: newVotes,
12498
+ ...result.outcome !== "pending" ? { status: this._toChangeStatus(result.outcome) } : {}
12499
+ });
12500
+ this._notifyVoters(change, eligible, "vote_cast", { voterId, value, result });
12501
+ return result;
12502
+ }
12503
+ /**
12504
+ * Resolve the current vote without waiting for all eligible voters.
12505
+ * Useful when a timeout fires or an agent decides to finalize early.
12506
+ */
12507
+ resolveNow(changeId) {
12508
+ const change = this.graph.get(changeId);
12509
+ if (!change) throw new Error(`ConsensusProtocol: unknown change "${changeId}"`);
12510
+ const eligible = this._eligibleVoters(change);
12511
+ const result = this._resolve(changeId, change.votes, eligible);
12512
+ if (result.outcome !== "pending") {
12513
+ this.graph.update(changeId, { status: this._toChangeStatus(result.outcome) });
12514
+ this._notifyVoters(change, eligible, "vote_resolved", { result });
12515
+ }
12516
+ return result;
12517
+ }
12518
+ /**
12519
+ * Register or update a voter's configuration.
12520
+ */
12521
+ registerVoter(config) {
12522
+ this.voters.set(config.agentId, config);
12523
+ }
12524
+ /**
12525
+ * Get the current vote status for a change.
12526
+ */
12527
+ getStatus(changeId) {
12528
+ const change = this.graph.get(changeId);
12529
+ if (!change || change.type !== "change") return null;
12530
+ const eligible = this._eligibleVoters(change);
12531
+ return this._resolve(changeId, change.votes, eligible);
12532
+ }
12533
+ // ── Private ───────────────────────────────────────────────────────────
12534
+ _eligibleVoters(_change) {
12535
+ return Array.from(this.voters.keys());
12536
+ }
12537
+ _resolve(changeId, votes, eligible) {
12538
+ const totalEligible = eligible.length;
12539
+ const approve = votes.filter((v) => v.value === "approve");
12540
+ const reject = votes.filter((v) => v.value === "reject");
12541
+ const abstain = votes.filter((v) => v.value === "abstain");
12542
+ const totalWeightApprove = approve.reduce(
12543
+ (sum, v) => sum + (this.voters.get(v.agentId)?.weight ?? 1),
12544
+ 0
12545
+ );
12546
+ const totalWeightReject = reject.reduce(
12547
+ (sum, v) => sum + (this.voters.get(v.agentId)?.weight ?? 1),
12548
+ 0
12549
+ );
12550
+ const totalWeight = Array.from(this.voters.values()).reduce(
12551
+ (sum, v) => sum + v.weight,
12552
+ 0
12553
+ );
12554
+ const castCount = votes.length;
12555
+ const quorumRequired = Math.ceil(totalEligible * this.rules.quorumFraction);
12556
+ const quorumMet = castCount >= quorumRequired;
12557
+ for (const v of reject) {
12558
+ const config = this.voters.get(v.agentId);
12559
+ if (config?.veto && this.rules.vetoRoles.includes(config.role)) {
12560
+ return {
12561
+ changeId,
12562
+ outcome: "vetoed",
12563
+ votes,
12564
+ approveCount: approve.length,
12565
+ rejectCount: reject.length,
12566
+ abstainCount: abstain.length,
12567
+ totalWeightApprove,
12568
+ totalWeightReject,
12569
+ eligibleVoters: eligible,
12570
+ quorumMet,
12571
+ approvalMet: false,
12572
+ vetoedBy: config.agentId,
12573
+ rationale: `Hard veto from role "${config.role}" (${config.agentName}).`
12574
+ };
12575
+ }
12576
+ }
12577
+ if (!quorumMet) {
12578
+ return {
12579
+ changeId,
12580
+ outcome: "pending",
12581
+ votes,
12582
+ approveCount: approve.length,
12583
+ rejectCount: reject.length,
12584
+ abstainCount: abstain.length,
12585
+ totalWeightApprove,
12586
+ totalWeightReject,
12587
+ eligibleVoters: eligible,
12588
+ quorumMet: false,
12589
+ approvalMet: false,
12590
+ rationale: `Quorum not met: ${castCount}/${quorumRequired} required.`
12591
+ };
12592
+ }
12593
+ const approvalRequired = Math.ceil(castCount * this.rules.approvalFraction);
12594
+ const approvalMet = approve.length >= approvalRequired;
12595
+ if (this.rules.approvalWeightFraction !== void 0 && approvalMet) {
12596
+ const weightRequired = totalWeight * this.rules.approvalWeightFraction;
12597
+ if (totalWeightApprove < weightRequired) {
12598
+ return {
12599
+ changeId,
12600
+ outcome: "rejected",
12601
+ votes,
12602
+ approveCount: approve.length,
12603
+ rejectCount: reject.length,
12604
+ abstainCount: abstain.length,
12605
+ totalWeightApprove,
12606
+ totalWeightReject,
12607
+ eligibleVoters: eligible,
12608
+ quorumMet: true,
12609
+ approvalMet: false,
12610
+ rationale: `Weight threshold not met: ${totalWeightApprove.toFixed(2)}/${weightRequired.toFixed(2)} required.`
12611
+ };
12612
+ }
12613
+ }
12614
+ if (approvalMet) {
12615
+ return {
12616
+ changeId,
12617
+ outcome: "approved",
12618
+ votes,
12619
+ approveCount: approve.length,
12620
+ rejectCount: reject.length,
12621
+ abstainCount: abstain.length,
12622
+ totalWeightApprove,
12623
+ totalWeightReject,
12624
+ eligibleVoters: eligible,
12625
+ quorumMet: true,
12626
+ approvalMet: true,
12627
+ rationale: `Approved: ${approve.length}/${castCount} votes (threshold: ${approvalRequired}).`
12628
+ };
12629
+ }
12630
+ return {
12631
+ changeId,
12632
+ outcome: "rejected",
12633
+ votes,
12634
+ approveCount: approve.length,
12635
+ rejectCount: reject.length,
12636
+ abstainCount: abstain.length,
12637
+ totalWeightApprove,
12638
+ totalWeightReject,
12639
+ eligibleVoters: eligible,
12640
+ quorumMet: true,
12641
+ approvalMet: false,
12642
+ rationale: `Rejected: ${approve.length}/${castCount} approve votes (threshold: ${approvalRequired}).`
12643
+ };
12644
+ }
12645
+ _toChangeStatus(outcome) {
12646
+ switch (outcome) {
12647
+ case "approved":
12648
+ return "approved";
12649
+ case "rejected":
12650
+ return "rejected";
12651
+ case "vetoed":
12652
+ return "rejected";
12653
+ case "quorum_not_met":
12654
+ return "proposed";
12655
+ default:
12656
+ return "proposed";
12657
+ }
12658
+ }
12659
+ _notifyVoters(change, eligible, event, extra) {
12660
+ if (!this.fleet) return;
12661
+ this.fleet.emit({
12662
+ subagentId: "consensus",
12663
+ ts: Date.now(),
12664
+ type: `consensus:${event}`,
12665
+ payload: { changeId: change.id, changeTitle: change.title, eligible, ...extra }
12666
+ });
12667
+ }
12668
+ };
12669
+
12670
+ // src/coordination/change-manager.ts
12671
+ var DEFAULT_QUALITY_CHECKS = {
12672
+ runTests: true,
12673
+ runTypecheck: true,
12674
+ runLint: false,
12675
+ // warning only
12676
+ runSecurityScan: true,
12677
+ checkTestCoverage: false,
12678
+ minCoveragePercent: 70
12679
+ };
12680
+ var ChangeManager = class {
12681
+ graph;
12682
+ consensus;
12683
+ fleet;
12684
+ checks;
12685
+ /** Track applied changes for rollback lookup. */
12686
+ appliedChanges = /* @__PURE__ */ new Map();
12687
+ // changeId → rollbackId
12688
+ constructor(opts) {
12689
+ this.graph = opts.graph;
12690
+ this.consensus = opts.consensus;
12691
+ this.fleet = opts.fleet ?? void 0;
12692
+ this.checks = { ...DEFAULT_QUALITY_CHECKS, ...opts.checks };
12693
+ }
12694
+ // ── Lifecycle ────────────────────────────────────────────────────────
12695
+ /**
12696
+ * Propose a new code change. Creates a ChangeNode in the knowledge graph.
12697
+ * Does NOT automatically initiate voting — call `submitForReview()` for that.
12698
+ */
12699
+ async propose(input) {
12700
+ const node = await this.graph.add({
12701
+ type: "change",
12702
+ title: input.title,
12703
+ description: input.description,
12704
+ files: input.files.map((f) => ({
12705
+ path: f.path,
12706
+ action: f.action
12707
+ })),
12708
+ status: "proposed",
12709
+ proposedBy: input.proposedBy,
12710
+ proposedAt: (/* @__PURE__ */ new Date()).toISOString(),
12711
+ approvedBy: [],
12712
+ rejectedBy: [],
12713
+ votes: [],
12714
+ qualityGate: { passed: false, checks: [] },
12715
+ // filled after quality gate
12716
+ satisfiesGoals: input.satisfiesGoals
12717
+ });
12718
+ void this._runQualityGate(node.id, input.files).then((gate) => {
12719
+ void this.graph.update(node.id, { qualityGate: gate });
12720
+ });
12721
+ this._emit("change:proposed", { changeId: node.id, title: node.title });
12722
+ return node;
12723
+ }
12724
+ /**
12725
+ * Submit an approved change for application.
12726
+ * Returns the change node — actual file mutations are performed by agents
12727
+ * acting on this node's data from the knowledge graph.
12728
+ */
12729
+ async submitForReview(changeId) {
12730
+ const change = this.graph.get(changeId);
12731
+ if (!change || change.type !== "change") {
12732
+ throw new Error(`ChangeManager: no change found "${changeId}"`);
12733
+ }
12734
+ if (change.status !== "proposed") {
12735
+ throw new Error(`ChangeManager: change "${changeId}" is not in 'proposed' state`);
12736
+ }
12737
+ this.consensus.initiateVote(changeId);
12738
+ this._emit("change:submitted_for_review", { changeId, title: change.title });
12739
+ }
12740
+ /**
12741
+ * Apply an approved change. Updates the change node to 'applied'.
12742
+ * Agents should watch for 'applied' status and perform the actual file mutations.
12743
+ */
12744
+ async markApplied(changeId, appliedAt) {
12745
+ const change = this.graph.get(changeId);
12746
+ if (!change) return null;
12747
+ const updated = await this.graph.update(changeId, {
12748
+ status: "applied",
12749
+ appliedAt
12750
+ });
12751
+ if (updated) {
12752
+ this.appliedChanges.set(changeId, "");
12753
+ this._emit("change:applied", { changeId, title: updated.title, files: updated.files });
12754
+ }
12755
+ return updated;
12756
+ }
12757
+ /**
12758
+ * Mark a change as applied and trigger rollback for any satisfied goal
12759
+ * that turns out to be broken.
12760
+ */
12761
+ async markAppliedWithVerification(changeId, verify) {
12762
+ const change = this.graph.get(changeId);
12763
+ if (!change) throw new Error(`ChangeManager: unknown change "${changeId}"`);
12764
+ const appliedAt = (/* @__PURE__ */ new Date()).toISOString();
12765
+ await this.markApplied(changeId, appliedAt);
12766
+ const verificationResult = await verify();
12767
+ if (!verificationResult.passed) {
12768
+ const rollbackResult = await this.proposeRollback(changeId, "Quality gate failed after apply");
12769
+ return {
12770
+ changeId,
12771
+ success: false,
12772
+ appliedAt,
12773
+ filesTouched: change.files.map((f) => f.path),
12774
+ verificationResult,
12775
+ rollbackChangeId: rollbackResult?.id,
12776
+ error: `Quality gate failed: ${verificationResult.checks.filter((c) => !c.passed).map((c) => c.name).join(", ")}`
12777
+ };
12778
+ }
12779
+ return {
12780
+ changeId,
12781
+ success: true,
12782
+ appliedAt,
12783
+ filesTouched: change.files.map((f) => f.path),
12784
+ verificationResult
12785
+ };
12786
+ }
12787
+ /**
12788
+ * Propose a rollback for an applied change. Creates a new change that
12789
+ * reverses the original. Goes through full consensus.
12790
+ */
12791
+ async proposeRollback(appliedChangeId, reason) {
12792
+ const original = this.graph.get(appliedChangeId);
12793
+ if (!original || original.type !== "change") return null;
12794
+ const rollbackFiles = original.files.map((f) => ({
12795
+ path: f.path,
12796
+ action: f.action === "create" ? "delete" : f.action === "delete" ? "create" : "modify"
12797
+ }));
12798
+ const rollback = await this.propose({
12799
+ title: `Rollback: ${original.title}`,
12800
+ description: `Rollback of "${original.title}" applied at ${original.appliedAt}. Reason: ${reason}`,
12801
+ files: rollbackFiles,
12802
+ proposedBy: "change-manager",
12803
+ satisfiesGoals: [],
12804
+ tags: ["rollback", `original:${appliedChangeId}`]
12805
+ });
12806
+ this.appliedChanges.set(appliedChangeId, rollback.id);
12807
+ await this.graph.update(appliedChangeId, {
12808
+ rolledBackAt: (/* @__PURE__ */ new Date()).toISOString(),
12809
+ rollbackReason: reason
12810
+ });
12811
+ this._emit("change:rollback_proposed", {
12812
+ originalChangeId: appliedChangeId,
12813
+ rollbackChangeId: rollback.id,
12814
+ reason
12815
+ });
12816
+ return rollback;
12817
+ }
12818
+ /**
12819
+ * Mark a change as rolled back.
12820
+ */
12821
+ async markRolledBack(changeId, rolledBackAt) {
12822
+ const updated = await this.graph.update(changeId, {
12823
+ status: "rolled_back",
12824
+ rolledBackAt
12825
+ });
12826
+ if (updated) {
12827
+ this._emit("change:rolled_back", { changeId, title: updated.title });
12828
+ }
12829
+ return updated;
12830
+ }
12831
+ // ── Queries ───────────────────────────────────────────────────────────
12832
+ getPendingReviews() {
12833
+ return this.graph.getChanges({ status: "proposed" });
12834
+ }
12835
+ getAppliedChanges() {
12836
+ return this.graph.getChanges({ status: "applied" });
12837
+ }
12838
+ getChange(id) {
12839
+ return this.graph.get(id);
12840
+ }
12841
+ getChangesForGoal(goalId) {
12842
+ return this.graph.getChanges({}).filter(
12843
+ (c) => c.satisfiesGoals.includes(goalId)
12844
+ );
12845
+ }
12846
+ // ── Quality gate ──────────────────────────────────────────────────────
12847
+ /**
12848
+ * Run quality gate checks. This is informational — actual test/lint/typecheck
12849
+ * execution is done by agents spawned for this purpose. This method stores
12850
+ * the result in the change node.
12851
+ */
12852
+ async _runQualityGate(_changeId, _files) {
12853
+ const checks = [];
12854
+ if (this.checks.runTests) {
12855
+ checks.push({ name: "tests", passed: false, detail: "Tests must be run by a verify agent" });
12856
+ }
12857
+ if (this.checks.runTypecheck) {
12858
+ checks.push({ name: "typecheck", passed: false, detail: "TypeScript must compile" });
12859
+ }
12860
+ if (this.checks.runSecurityScan) {
12861
+ checks.push({ name: "security", passed: false, detail: "Security scan must pass" });
12862
+ }
12863
+ if (this.checks.runLint) {
12864
+ checks.push({ name: "lint", passed: false, detail: "Lint check" });
12865
+ }
12866
+ const result = {
12867
+ passed: checks.length === 0,
12868
+ checks
12869
+ };
12870
+ await this.graph.update(_changeId, { qualityGate: result });
12871
+ return result;
12872
+ }
12873
+ /**
12874
+ * Update quality gate result for a change. Called by verify agents
12875
+ * after running their checks.
12876
+ */
12877
+ async updateQualityGate(changeId, checkName, result) {
12878
+ const change = this.graph.get(changeId);
12879
+ if (!change) return;
12880
+ const checks = change.qualityGate.checks.map(
12881
+ (c) => c.name === checkName ? { ...c, ...result } : c
12882
+ );
12883
+ const allPassed = checks.every((c) => c.passed);
12884
+ await this.graph.update(changeId, {
12885
+ qualityGate: { passed: allPassed, checks }
12886
+ });
12887
+ this._emit("quality_gate:updated", { changeId, checkName, passed: result.passed });
12888
+ }
12889
+ // ── Helpers ──────────────────────────────────────────────────────────
12890
+ _emit(type, payload) {
12891
+ if (!this.fleet) return;
12892
+ this.fleet.emit({ subagentId: "change-manager", ts: Date.now(), type, payload });
12893
+ }
12894
+ };
12895
+ var AutonomousBrain = class {
12896
+ graph;
12897
+ // Fleet bus for emitting decisions — null-safe, no-op if not provided
12898
+ fleetBus;
12899
+ llmProvider;
12900
+ maxRetries;
12901
+ consensusRiskThreshold;
12902
+ selfImprove;
12903
+ /** Decision history for self-improvement and audit. */
12904
+ decisionHistory = [];
12905
+ /** Tracks failure patterns for self-improvement. */
12906
+ failurePatterns = /* @__PURE__ */ new Map();
12907
+ RISK_ORDER = ["low", "medium", "high", "critical"];
12908
+ // ── Fleet bus integration ─────────────────────────────────────────────
12909
+ _emit(type, payload) {
12910
+ if (!this.fleetBus) return;
12911
+ this.fleetBus.emit({ subagentId: "brain", ts: Date.now(), type, payload });
12912
+ }
12913
+ constructor(opts) {
12914
+ this.graph = opts.graph;
12915
+ this.fleetBus = opts.fleet ?? void 0;
12916
+ this.llmProvider = opts.llmProvider;
12917
+ this.maxRetries = opts.maxRetries ?? 3;
12918
+ this.consensusRiskThreshold = opts.consensusRiskThreshold ?? "high";
12919
+ this.selfImprove = opts.selfImprove ?? true;
12920
+ }
12921
+ // ── BrainArbiter interface ────────────────────────────────────────────
12922
+ /** Implements BrainArbiter — bridges standard brain.ts interface to autonomous engine. */
12923
+ async decide(request) {
12924
+ return this.decideAuto(this._toAutonomous(request));
12925
+ }
12926
+ // ── Main entry point ──────────────────────────────────────────────────
12927
+ /**
12928
+ * Primary autonomous decision engine — receives AutonomousDecisionRequest,
12929
+ * queries the LLM, records the decision, and returns a BrainDecision.
12930
+ *
12931
+ * Specialized methods (decideSpawn, decideApproval, etc.) should call this
12932
+ * directly with a pre-built AutonomousDecisionRequest.
12933
+ */
12934
+ async decideAuto(request) {
12935
+ const { id, decisionType, question, context, options, risk, requiresConsensus } = request;
12936
+ const history = this.selfImprove ? this._loadHistory(decisionType, risk) : [];
12937
+ const hints = this.selfImprove ? this._getSelfImproveHints(decisionType) : [];
12938
+ const prompt = {
12939
+ decisionType,
12940
+ question,
12941
+ context: this._serializeContext(context),
12942
+ options,
12943
+ risk,
12944
+ decisionHistory: history,
12945
+ selfImproveHints: hints
12946
+ };
12947
+ let result;
12948
+ try {
12949
+ result = await this.llmProvider.decide(prompt);
12950
+ } catch (err) {
12951
+ const recommended = options.find((o) => o.recommended);
12952
+ if (recommended && risk === "low") {
12953
+ return { type: "answer", optionId: recommended.id, text: recommended.label };
12954
+ }
12955
+ return { type: "deny", reason: `Brain LLM failed: ${String(err)}` };
12956
+ }
12957
+ void this._recordDecision({
12958
+ id,
12959
+ decisionType,
12960
+ question,
12961
+ options,
12962
+ chosen: result.optionId,
12963
+ rationale: result.rationale,
12964
+ madeBy: "autonomous-brain",
12965
+ context: JSON.stringify(context)
12966
+ });
12967
+ if (requiresConsensus) {
12968
+ this._emit("brain.decision", { id, decisionType, optionId: result.optionId, rationale: result.rationale, consensusRequired: true });
12969
+ return {
12970
+ type: "answer",
12971
+ optionId: result.optionId,
12972
+ text: options.find((o) => o.id === result.optionId)?.label ?? result.optionId,
12973
+ rationale: `${result.rationale}
12974
+
12975
+ \u26A0\uFE0F This decision requires consensus approval before execution.`
12976
+ };
12977
+ }
12978
+ this._emit("brain.decision", { id, decisionType, optionId: result.optionId, rationale: result.rationale, consensusRequired: false });
12979
+ return {
12980
+ type: "answer",
12981
+ optionId: result.optionId,
12982
+ text: options.find((o) => o.id === result.optionId)?.label ?? result.optionId,
12983
+ rationale: result.rationale
12984
+ };
12985
+ }
12986
+ // ── Specialized decision methods ────────────────────────────────────
12987
+ /**
12988
+ * Decide whether to spawn a subagent, which role to use, and what budget.
12989
+ */
12990
+ async decideSpawn(source, taskDescription, availableFacts, fleetStatus) {
12991
+ const roleHints = this._inferRoles(taskDescription);
12992
+ const risk = roleHints.length > 1 ? "medium" : "low";
12993
+ const options = roleHints.map((role, i) => ({
12994
+ id: `spawn:${role}`,
12995
+ label: `Spawn ${role} agent`,
12996
+ risk: i === 0 ? "low" : "medium",
12997
+ recommended: i === 0,
12998
+ consequence: i === 0 ? `Spawn the most appropriate agent for: ${taskDescription.slice(0, 80)}` : `Spawn an alternative agent for the same task`
12999
+ }));
13000
+ return this.decideAuto({
13001
+ id: randomUUID(),
13002
+ source,
13003
+ decisionType: "spawn",
13004
+ question: `Should we spawn a subagent for this task?`,
13005
+ context: {
13006
+ facts: availableFacts,
13007
+ fleetStatus,
13008
+ taskDescription
13009
+ },
13010
+ options,
13011
+ risk,
13012
+ requiresConsensus: false
13013
+ });
13014
+ }
13015
+ /**
13016
+ * Decide whether to approve a proposed change.
13017
+ */
13018
+ async decideApproval(source, change, relevantFacts) {
13019
+ const risk = this._changeRisk(change);
13020
+ const options = [
13021
+ {
13022
+ id: "approve",
13023
+ label: "Approve change",
13024
+ recommended: change.qualityGate.passed && relevantFacts.filter((f) => f.severity === "critical").length === 0,
13025
+ risk,
13026
+ consequence: `Apply changes to: ${change.files.map((f) => f.path).join(", ")}`
13027
+ },
13028
+ {
13029
+ id: "reject",
13030
+ label: "Reject change",
13031
+ recommended: false,
13032
+ risk: "medium",
13033
+ consequence: "Return change to proposer with feedback"
13034
+ },
13035
+ {
13036
+ id: "request_changes",
13037
+ label: "Request specific changes",
13038
+ recommended: false,
13039
+ risk: "low",
13040
+ consequence: "Send back for revision with conditions"
13041
+ }
13042
+ ];
13043
+ return this.decideAuto({
13044
+ id: randomUUID(),
13045
+ source,
13046
+ decisionType: "approve_change",
13047
+ question: `Should we approve the change "${change.title}"?`,
13048
+ context: {
13049
+ facts: relevantFacts,
13050
+ change
13051
+ },
13052
+ options,
13053
+ risk,
13054
+ requiresConsensus: risk === "critical" || risk === "high"
13055
+ });
13056
+ }
13057
+ /**
13058
+ * Decide how to handle a failed task.
13059
+ */
13060
+ async decideEscalation(source, taskId, error, attempts) {
13061
+ const retryCount = this.failurePatterns.get(taskId)?.failures ?? attempts;
13062
+ const options = [];
13063
+ if (retryCount < this.maxRetries) {
13064
+ options.push({
13065
+ id: "retry",
13066
+ label: `Retry task (attempt ${retryCount + 1}/${this.maxRetries})`,
13067
+ recommended: retryCount < 2,
13068
+ risk: "medium",
13069
+ consequence: `Restart the task with same or adjusted budget`
13070
+ });
13071
+ options.push({
13072
+ id: "retry_with_adjustment",
13073
+ label: `Retry with more budget`,
13074
+ recommended: retryCount >= 1,
13075
+ risk: "medium",
13076
+ consequence: `Increase timeout or iterations before retrying`
13077
+ });
13078
+ }
13079
+ options.push({
13080
+ id: "delegate",
13081
+ label: "Delegate to different role",
13082
+ recommended: retryCount >= 1,
13083
+ risk: "medium",
13084
+ consequence: "Try a different agent role for the same task"
13085
+ });
13086
+ if (retryCount >= this.maxRetries) {
13087
+ options.push({
13088
+ id: "mark_failed",
13089
+ label: "Mark task as failed",
13090
+ recommended: true,
13091
+ risk: "high",
13092
+ consequence: "Stop retrying, propagate failure upward"
13093
+ });
13094
+ }
13095
+ options.push({
13096
+ id: "decompose",
13097
+ label: "Decompose and retry in parts",
13098
+ recommended: false,
13099
+ risk: "low",
13100
+ consequence: "Break the task into smaller sub-tasks"
13101
+ });
13102
+ return this.decideAuto({
13103
+ id: randomUUID(),
13104
+ source,
13105
+ decisionType: "escalate_task",
13106
+ question: `Task failed: ${error.slice(0, 100)}. How should we proceed?`,
13107
+ context: { error, attempts: retryCount },
13108
+ options,
13109
+ risk: retryCount >= this.maxRetries ? "critical" : "medium",
13110
+ requiresConsensus: false
13111
+ });
13112
+ }
13113
+ // ── Self-improvement ─────────────────────────────────────────────────
13114
+ /**
13115
+ * Record the outcome of a decision for self-improvement.
13116
+ * Call this after a spawned agent completes or a change is applied.
13117
+ */
13118
+ recordOutcome(decisionId, outcome, _detail) {
13119
+ const node = this.graph.get(decisionId);
13120
+ if (!node) return;
13121
+ const key = `decision:${node.decisionType}`;
13122
+ if (outcome === "failure") {
13123
+ const existing = this.failurePatterns.get(key) ?? { failures: 0, lastFailure: "" };
13124
+ existing.failures += 1;
13125
+ existing.lastFailure = (/* @__PURE__ */ new Date()).toISOString();
13126
+ this.failurePatterns.set(key, existing);
13127
+ } else {
13128
+ this.failurePatterns.delete(key);
13129
+ }
13130
+ void this.graph.update(decisionId, { decisionType: node.decisionType });
13131
+ }
13132
+ _getSelfImproveHints(decisionType) {
13133
+ const pattern = this.failurePatterns.get(`decision:${decisionType}`);
13134
+ if (!pattern || pattern.failures < 2) return [];
13135
+ return [
13136
+ `\u26A0\uFE0F ${decisionType} decisions have failed ${pattern.failures} times recently.`,
13137
+ "Consider alternative approaches before defaulting to this pattern."
13138
+ ];
13139
+ }
13140
+ // ── Private ───────────────────────────────────────────────────────────
13141
+ _toAutonomous(req) {
13142
+ const decisionType = this._inferDecisionType(req);
13143
+ return {
13144
+ id: req.id,
13145
+ source: req.source,
13146
+ decisionType,
13147
+ question: req.question,
13148
+ context: {
13149
+ taskDescription: req.context ?? ""
13150
+ },
13151
+ options: req.options ?? [],
13152
+ risk: req.risk,
13153
+ requiresConsensus: this.RISK_ORDER.indexOf(req.risk) >= this.RISK_ORDER.indexOf(this.consensusRiskThreshold)
13154
+ };
13155
+ }
13156
+ _inferDecisionType(req) {
13157
+ if (req.question.toLowerCase().includes("spawn")) return "spawn";
13158
+ if (req.question.toLowerCase().includes("approve") || req.question.toLowerCase().includes("change")) return "approve_change";
13159
+ if (req.question.toLowerCase().includes("retry") || req.question.toLowerCase().includes("fail")) return "retry_task";
13160
+ if (req.question.toLowerCase().includes("priorit")) return "prioritize_goals";
13161
+ if (req.question.toLowerCase().includes("decompos")) return "decompose_goal";
13162
+ return "assign_task";
13163
+ }
13164
+ _serializeContext(ctx) {
13165
+ const parts = [];
13166
+ if (ctx.facts?.length) {
13167
+ parts.push(`## Relevant Facts
13168
+ ${ctx.facts.map((f) => `- [${f.severity ?? "info"}] ${f.subject}: ${f.detail}`).join("\n")}`);
13169
+ }
13170
+ if (ctx.goals?.length) {
13171
+ parts.push(`## Active Goals
13172
+ ${ctx.goals.map((g) => `- [${g.status}] ${g.priority}: ${g.title}`).join("\n")}`);
13173
+ }
13174
+ if (ctx.change) {
13175
+ const c = ctx.change;
13176
+ parts.push(`## Change Under Review
13177
+ - Title: ${c.title}
13178
+ - Status: ${c.status}
13179
+ - Files: ${c.files.map((f) => `${f.action} ${f.path}`).join(", ")}
13180
+ - Quality gate: ${c.qualityGate.passed ? "PASSED" : "FAILED"}
13181
+ Checks: ${c.qualityGate.checks.map((ch) => `${ch.name}:${ch.passed ? "\u2705" : "\u274C"}`).join(", ")}`);
13182
+ }
13183
+ if (ctx.fleetStatus) {
13184
+ parts.push(`## Fleet Status
13185
+ - Running: ${ctx.fleetStatus.running}, Idle: ${ctx.fleetStatus.idle}, Total: ${ctx.fleetStatus.total}
13186
+ - Cost so far: $${ctx.fleetStatus.costSoFar.toFixed(4)}`);
13187
+ }
13188
+ if (ctx.taskDescription) {
13189
+ parts.push(`## Task
13190
+ ${ctx.taskDescription}`);
13191
+ }
13192
+ if (ctx.error) {
13193
+ parts.push(`## Error
13194
+ ${ctx.error}`);
13195
+ }
13196
+ return parts.join("\n\n");
13197
+ }
13198
+ _loadHistory(type, _risk) {
13199
+ const all = this.graph.getDecisions().filter((d) => d.decisionType === type);
13200
+ return all.slice(-10);
13201
+ }
13202
+ async _recordDecision(input) {
13203
+ const node = await this.graph.add({
13204
+ type: "decision",
13205
+ decisionType: input.decisionType,
13206
+ question: input.question,
13207
+ options: input.options,
13208
+ chosen: input.chosen,
13209
+ rationale: input.rationale,
13210
+ madeBy: input.madeBy,
13211
+ madeAt: (/* @__PURE__ */ new Date()).toISOString(),
13212
+ context: input.context
13213
+ });
13214
+ this.decisionHistory.push(node);
13215
+ return node;
13216
+ }
13217
+ _inferRoles(task) {
13218
+ const t = task.toLowerCase();
13219
+ if (t.includes("bug") || t.includes("error") || t.includes("crash")) return ["bug-hunter", "fixer"];
13220
+ if (t.includes("security") || t.includes("secret") || t.includes("injection")) return ["security-scanner"];
13221
+ if (t.includes("refactor") || t.includes("architecture") || t.includes("debt")) return ["refactor-planner", "critic"];
13222
+ if (t.includes("audit") || t.includes("log") || t.includes("analyze")) return ["audit-log"];
13223
+ if (t.includes("test") || t.includes("coverage")) return ["tester", "bug-hunter"];
13224
+ return ["bug-hunter", "refactor-planner"];
13225
+ }
13226
+ _changeRisk(change) {
13227
+ const criticalFiles = change.files.filter(
13228
+ (f) => f.path.includes("auth") || f.path.includes("config") || f.path.includes("schema")
13229
+ );
13230
+ if (criticalFiles.length > 0) return "high";
13231
+ if (change.files.length > 10) return "medium";
13232
+ return "low";
13233
+ }
13234
+ };
13235
+ var TaskAuctioneer = class {
13236
+ graph;
13237
+ fleet;
13238
+ mailbox;
13239
+ selfAgentId;
13240
+ bidWindowMs;
13241
+ maxTasksPerAgent;
13242
+ minConfidence;
13243
+ // minimum dispatcher confidence to accept a bid
13244
+ /** Pending bids keyed by taskId. */
13245
+ pendingBids = /* @__PURE__ */ new Map();
13246
+ /** Active bid windows keyed by taskId. */
13247
+ bidTimers = /* @__PURE__ */ new Map();
13248
+ /** Agent → current task count (from graph + in-flight). */
13249
+ agentTaskCounts = /* @__PURE__ */ new Map();
13250
+ constructor(opts) {
13251
+ this.graph = opts.graph;
13252
+ this.fleet = opts.fleet;
13253
+ this.mailbox = opts.mailbox;
13254
+ this.selfAgentId = opts.selfAgentId ?? "auctioneer";
13255
+ this.bidWindowMs = opts.bidWindowMs ?? 3e4;
13256
+ this.maxTasksPerAgent = opts.maxTasksPerAgent ?? 3;
13257
+ this.minConfidence = opts.minConfidence ?? 0.3;
13258
+ this.fleet?.filter("task:bid", (e) => this._onBidEvent(e));
13259
+ this.fleet?.filter("task:claimed", (e) => this._onClaimedEvent(e));
13260
+ }
13261
+ // ── Publish a task ────────────────────────────────────────────────────
13262
+ /**
13263
+ * Publish a new task to the auction. Creates a GoalNode and broadcasts
13264
+ * it to all online agents. Returns the goal id.
13265
+ *
13266
+ * If `targetAgent` is specified, the task is assigned directly without auction.
13267
+ */
13268
+ async publishTask(input) {
13269
+ const goal = await this.graph.add({
13270
+ type: "goal",
13271
+ title: input.title,
13272
+ description: input.description,
13273
+ status: input.targetAgent ? "in_progress" : "pending",
13274
+ priority: input.priority ?? "medium",
13275
+ assignee: input.targetAgent,
13276
+ blockedBy: [],
13277
+ dependsOn: input.satisfiesGoals ?? [],
13278
+ createdBy: this.selfAgentId,
13279
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13280
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13281
+ tags: input.tags ?? [],
13282
+ children: [],
13283
+ parentGoal: input.parentGoal
13284
+ });
13285
+ if (input.parentGoal) {
13286
+ const parent = this.graph.get(input.parentGoal);
13287
+ if (parent) {
13288
+ await this.graph.update(input.parentGoal, {
13289
+ children: [...parent.children, goal.id]
13290
+ });
13291
+ }
13292
+ }
13293
+ if (input.targetAgent) {
13294
+ await this._assignDirect(goal.id, input.targetAgent);
13295
+ } else {
13296
+ await this._broadcastTask(goal);
13297
+ this._startBidWindow(goal.id);
13298
+ }
13299
+ this._emit("task:published", {
13300
+ taskId: goal.id,
13301
+ title: goal.title,
13302
+ priority: goal.priority,
13303
+ tags: goal.tags
13304
+ });
13305
+ return goal.id;
13306
+ }
13307
+ // ── Bid on a task ─────────────────────────────────────────────────────
13308
+ /**
13309
+ * Submit a bid for a task. Called by agents who want to work on it.
13310
+ * Returns true if the bid was accepted, false if the task was already claimed.
13311
+ */
13312
+ async bid(taskId, agent, rationale) {
13313
+ const goal = this.graph.get(taskId);
13314
+ if (!goal || goal.type !== "goal") return false;
13315
+ if (goal.status !== "pending") return false;
13316
+ const currentCount = this._getAgentTaskCount(agent.agentId);
13317
+ if (currentCount >= this.maxTasksPerAgent) return false;
13318
+ const dispatchResult = await dispatchAgent(goal.description);
13319
+ const score = dispatchResult.confidence * (dispatchResult.role === agent.agentRole ? 1.2 : 1);
13320
+ if (score < this.minConfidence) return false;
13321
+ const bid = {
13322
+ id: randomUUID(),
13323
+ taskId,
13324
+ agentId: agent.agentId,
13325
+ agentName: agent.agentName,
13326
+ agentRole: agent.agentRole,
13327
+ score,
13328
+ rationale,
13329
+ submittedAt: (/* @__PURE__ */ new Date()).toISOString()
13330
+ };
13331
+ let bids = this.pendingBids.get(taskId);
13332
+ if (!bids) {
13333
+ bids = [];
13334
+ this.pendingBids.set(taskId, bids);
13335
+ }
13336
+ const existingIdx = bids.findIndex((b) => b.agentId === agent.agentId);
13337
+ if (existingIdx >= 0) {
13338
+ bids[existingIdx] = bid;
13339
+ } else {
13340
+ bids.push(bid);
13341
+ }
13342
+ this._emit("task:bid", {
13343
+ taskId,
13344
+ bid: { ...bid, score: Math.round(score * 100) / 100 },
13345
+ agentName: agent.agentName
13346
+ });
13347
+ await this._mailboxPublish({
13348
+ type: "note",
13349
+ subject: `[bid] ${agent.agentName} \u2192 ${goal.title}`,
13350
+ body: `${agent.agentName} (${agent.agentRole}) bidded on task "${goal.title}" (${goal.id})
13351
+ Rationale: ${rationale}
13352
+ Score: ${score.toFixed(2)}`
13353
+ });
13354
+ return true;
13355
+ }
13356
+ // ── Claim (award) a task ───────────────────────────────────────────────
13357
+ /**
13358
+ * Award a task to a specific agent. Called internally by the bid window
13359
+ * expiry, or can be called directly to force an award.
13360
+ */
13361
+ async claim(taskId, agentId, agentName) {
13362
+ const goal = this.graph.get(taskId);
13363
+ if (!goal || goal.type !== "goal") return false;
13364
+ if (goal.status !== "pending") return false;
13365
+ this._cancelBidWindow(taskId);
13366
+ await this.graph.update(taskId, {
13367
+ status: "in_progress",
13368
+ assignee: agentId,
13369
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13370
+ });
13371
+ this.pendingBids.delete(taskId);
13372
+ this.agentTaskCount(agentId, 1);
13373
+ await this._notifyAgent(agentId, {
13374
+ type: "assign",
13375
+ subject: `[assigned] ${goal.title}`,
13376
+ body: `You have been assigned: "${goal.title}"
13377
+
13378
+ ${goal.description}
13379
+
13380
+ Task ID: ${taskId}
13381
+ Priority: ${goal.priority}`,
13382
+ taskContext: {
13383
+ agentRole: goal.tags[0],
13384
+ taskId,
13385
+ status: "in_progress"
13386
+ }
13387
+ });
13388
+ this._emit("task:claimed", { taskId, agentId, agentName });
13389
+ return true;
13390
+ }
13391
+ // ── Complete a task ────────────────────────────────────────────────────
13392
+ /**
13393
+ * Mark a task as done. Called by the agent when it finishes.
13394
+ */
13395
+ async complete(taskId, _result) {
13396
+ const goal = this.graph.get(taskId);
13397
+ if (!goal) return;
13398
+ const agentId = goal.assignee ?? "unknown";
13399
+ this.agentTaskCount(agentId, -1);
13400
+ await this.graph.update(taskId, {
13401
+ status: "done",
13402
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13403
+ });
13404
+ for (const childId of goal.children) {
13405
+ const child = this.graph.get(childId);
13406
+ if (child) {
13407
+ const allUnblocked = child.blockedBy.every((blockedId) => {
13408
+ const blocked = this.graph.get(blockedId);
13409
+ return blocked?.status === "done";
13410
+ });
13411
+ if (allUnblocked) {
13412
+ await this.graph.update(childId, { status: "pending", updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
13413
+ const unblockedGoal = this.graph.get(childId);
13414
+ await this._broadcastTask(unblockedGoal);
13415
+ }
13416
+ }
13417
+ }
13418
+ this._emit("task:completed", { taskId, agentId, result: _result });
13419
+ await this._mailboxPublish({
13420
+ type: "result",
13421
+ subject: `[done] ${goal.title}`,
13422
+ body: `Task completed by ${agentId}: "${goal.title}"
13423
+
13424
+ ${_result ?? "No result provided."}`
13425
+ });
13426
+ }
13427
+ /**
13428
+ * Mark a task as failed. Optionally spawn a retry.
13429
+ */
13430
+ async fail(taskId, error) {
13431
+ const goal = this.graph.get(taskId);
13432
+ if (!goal) return;
13433
+ const agentId = goal.assignee ?? "unknown";
13434
+ this.agentTaskCount(agentId, -1);
13435
+ await this.graph.update(taskId, {
13436
+ status: "failed",
13437
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13438
+ result: error
13439
+ });
13440
+ this._emit("task:failed", { taskId, agentId, error });
13441
+ await this._mailboxPublish({
13442
+ type: "note",
13443
+ subject: `[failed] ${goal.title}`,
13444
+ body: `Task failed: "${goal.title}"
13445
+ Error: ${error}
13446
+ Assignee: ${agentId}`
13447
+ });
13448
+ }
13449
+ // ── Work finding ──────────────────────────────────────────────────────
13450
+ /**
13451
+ * Find the best available tasks for an agent based on its capabilities.
13452
+ * Returns tasks sorted by match score (best first).
13453
+ */
13454
+ async findWork(_agentId, agentRole, limit = 5) {
13455
+ const pending = this.graph.getGoals({ status: "pending" });
13456
+ const scored = [];
13457
+ for (const goal of pending) {
13458
+ if (goal.blockedBy.length > 0) continue;
13459
+ const dispatchResult = await dispatchAgent(goal.description);
13460
+ const roleBonus = agentRole && goal.tags.includes(agentRole) ? 1.3 : 1;
13461
+ const priorityBonus = goal.priority === "critical" ? 1.5 : goal.priority === "high" ? 1.2 : 1;
13462
+ const score = dispatchResult.confidence * roleBonus * priorityBonus;
13463
+ const bids = this.pendingBids.get(goal.id)?.length ?? 0;
13464
+ scored.push({ task: goal, score, bids });
13465
+ }
13466
+ scored.sort((a, b) => b.score - a.score);
13467
+ return scored.slice(0, limit);
13468
+ }
13469
+ // ── Queries ──────────────────────────────────────────────────────────
13470
+ /** Get all pending tasks (available for bidding). */
13471
+ getPendingTasks() {
13472
+ return this.graph.getGoals({ status: "pending" }).filter((g) => g.blockedBy.length === 0);
13473
+ }
13474
+ /** Get tasks assigned to a specific agent. */
13475
+ getTasksForAgent(agentId) {
13476
+ return this.graph.getGoals({}).filter((g) => g.assignee === agentId);
13477
+ }
13478
+ /** Get the current bid count for a task. */
13479
+ getBidCount(taskId) {
13480
+ return this.pendingBids.get(taskId)?.length ?? 0;
13481
+ }
13482
+ /** Get bids for a task. */
13483
+ getBids(taskId) {
13484
+ return this.pendingBids.get(taskId) ?? [];
13485
+ }
13486
+ /** Get task stats for a project-wide dashboard. */
13487
+ getStats() {
13488
+ const all = this.graph.getGoals({});
13489
+ const pending = all.filter((g) => g.status === "pending" && g.blockedBy.length === 0);
13490
+ const inProgress = all.filter((g) => g.status === "in_progress");
13491
+ const done = all.filter((g) => g.status === "done");
13492
+ const failed = all.filter((g) => g.status === "failed");
13493
+ let totalBids = 0;
13494
+ for (const bids of this.pendingBids.values()) totalBids += bids.length;
13495
+ return {
13496
+ total: all.length,
13497
+ pending: pending.length,
13498
+ in_progress: inProgress.length,
13499
+ done: done.length,
13500
+ failed: failed.length,
13501
+ totalBids,
13502
+ avgBidsPerTask: pending.length > 0 ? totalBids / pending.length : 0
13503
+ };
13504
+ }
13505
+ // ── Private ───────────────────────────────────────────────────────────
13506
+ _emit(type, payload) {
13507
+ if (!this.fleet) return;
13508
+ this.fleet.emit({ subagentId: this.selfAgentId, ts: Date.now(), type, payload });
13509
+ }
13510
+ _broadcastTask(goal) {
13511
+ this._emit("task:available", {
13512
+ taskId: goal.id,
13513
+ title: goal.title,
13514
+ description: goal.description,
13515
+ priority: goal.priority,
13516
+ tags: goal.tags
13517
+ });
13518
+ void this._mailboxPublish({
13519
+ type: "broadcast",
13520
+ subject: `[task] ${goal.title} (${goal.priority})`,
13521
+ body: `New task available: "${goal.title}"
13522
+ Priority: ${goal.priority}
13523
+ Description: ${goal.description.slice(0, 200)}${goal.description.length > 200 ? "..." : ""}
13524
+
13525
+ Task ID: ${goal.id}
13526
+ Tags: ${goal.tags.join(", ") || "none"}
13527
+
13528
+ Bid by calling taskAuctioneer.bid("${goal.id}", ...)`
13529
+ });
13530
+ }
13531
+ async _mailboxPublish(msg) {
13532
+ if (!this.mailbox) return;
13533
+ try {
13534
+ await this.mailbox.send({
13535
+ from: this.selfAgentId,
13536
+ to: "*",
13537
+ type: msg.type,
13538
+ subject: msg.subject,
13539
+ body: msg.body,
13540
+ priority: "normal"
13541
+ });
13542
+ } catch {
13543
+ }
13544
+ }
13545
+ async _notifyAgent(agentId, msg) {
13546
+ if (!this.mailbox) return;
13547
+ try {
13548
+ await this.mailbox.send({
13549
+ from: this.selfAgentId,
13550
+ to: agentId,
13551
+ type: msg.type,
13552
+ subject: msg.subject,
13553
+ body: msg.body,
13554
+ priority: "high",
13555
+ taskContext: msg.taskContext
13556
+ });
13557
+ } catch {
13558
+ }
13559
+ }
13560
+ _startBidWindow(taskId) {
13561
+ this._cancelBidWindow(taskId);
13562
+ const timer = setTimeout(async () => {
13563
+ this.bidTimers.delete(taskId);
13564
+ await this._evaluateBids(taskId);
13565
+ }, this.bidWindowMs);
13566
+ this.bidTimers.set(taskId, timer);
13567
+ }
13568
+ _cancelBidWindow(taskId) {
13569
+ const timer = this.bidTimers.get(taskId);
13570
+ if (timer) {
13571
+ clearTimeout(timer);
13572
+ this.bidTimers.delete(taskId);
13573
+ }
13574
+ }
13575
+ async _evaluateBids(taskId) {
13576
+ const bids = this.pendingBids.get(taskId);
13577
+ if (!bids || bids.length === 0) {
13578
+ const goal = this.graph.get(taskId);
13579
+ if (goal) {
13580
+ await this._broadcastTask(goal);
13581
+ this._startBidWindow(taskId);
13582
+ }
13583
+ return;
13584
+ }
13585
+ bids.sort((a, b) => b.score - a.score);
13586
+ const winner = bids[0];
13587
+ await this.claim(taskId, winner.agentId, winner.agentName);
13588
+ }
13589
+ async _assignDirect(taskId, agentId) {
13590
+ const goal = this.graph.get(taskId);
13591
+ if (!goal) return;
13592
+ await this.graph.update(taskId, {
13593
+ status: "in_progress",
13594
+ assignee: agentId,
13595
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13596
+ });
13597
+ this.agentTaskCount(agentId, 1);
13598
+ await this._notifyAgent(agentId, {
13599
+ type: "assign",
13600
+ subject: `[assigned] ${goal.title}`,
13601
+ body: `You have been directly assigned: "${goal.title}"
13602
+
13603
+ ${goal.description}`,
13604
+ taskContext: { taskId, status: "in_progress" }
13605
+ });
13606
+ }
13607
+ _onBidEvent(_e) {
13608
+ }
13609
+ _onClaimedEvent(e) {
13610
+ const { taskId } = e.payload;
13611
+ this.pendingBids.delete(taskId);
13612
+ this._cancelBidWindow(taskId);
13613
+ }
13614
+ _getAgentTaskCount(agentId) {
13615
+ return this.agentTaskCounts.get(agentId) ?? 0;
13616
+ }
13617
+ agentTaskCount(agentId, delta) {
13618
+ const current = this._getAgentTaskCount(agentId);
13619
+ const next = Math.max(0, current + delta);
13620
+ this.agentTaskCounts.set(agentId, next);
13621
+ }
13622
+ };
13623
+ var AutonomousCoordinator = class {
13624
+ graph;
13625
+ dag;
13626
+ auction;
13627
+ consensus;
13628
+ changes;
13629
+ brain;
13630
+ selfAgentId;
13631
+ fleet;
13632
+ fleetManager;
13633
+ mailbox;
13634
+ events;
13635
+ running = false;
13636
+ iterationCount = 0;
13637
+ constructor(opts) {
13638
+ this.selfAgentId = opts.selfAgentId;
13639
+ this.fleet = opts.fleet ?? void 0;
13640
+ this.fleetManager = opts.fleetManager ?? void 0;
13641
+ this.mailbox = opts.mailbox ?? void 0;
13642
+ this.events = opts.events ?? void 0;
13643
+ this.graph = new KnowledgeGraph(opts.sessionDir);
13644
+ this.dag = new TaskDAG();
13645
+ this.auction = new TaskAuctioneer({
13646
+ graph: this.graph,
13647
+ fleet: this.fleet ?? void 0,
13648
+ mailbox: this.mailbox ?? void 0,
13649
+ selfAgentId: this.selfAgentId
13650
+ });
13651
+ this.consensus = new ConsensusProtocol({
13652
+ graph: this.graph,
13653
+ fleet: this.fleet ?? void 0,
13654
+ voters: this._buildVoters(),
13655
+ rules: {
13656
+ quorumFraction: 0.5,
13657
+ approvalFraction: 0.6,
13658
+ vetoRoles: ["critic"],
13659
+ // Critic has veto power
13660
+ approvalWeightFraction: 0.5
13661
+ }
13662
+ });
13663
+ this.changes = new ChangeManager({
13664
+ graph: this.graph,
13665
+ consensus: this.consensus,
13666
+ fleet: this.fleet ?? void 0,
13667
+ checks: DEFAULT_QUALITY_CHECKS
13668
+ });
13669
+ this.brain = new AutonomousBrain({
13670
+ llmProvider: opts.llmProvider,
13671
+ graph: this.graph,
13672
+ fleet: this.fleet ?? void 0,
13673
+ selfImprove: !opts.disableSelfImprove
13674
+ });
13675
+ this.dag.onEvent((event) => {
13676
+ this._onDagEvent(event);
13677
+ });
13678
+ this.fleet?.filter("subagent.terminated", (e) => {
13679
+ this._onSubagentTerminated(e);
13680
+ });
13681
+ }
13682
+ // ── Public API ───────────────────────────────────────────────────────
13683
+ /**
13684
+ * Run the autonomous loop until the goal is satisfied or max iterations reached.
13685
+ * This is the main entry point for a fully autonomous session.
13686
+ */
13687
+ async run(opts = {}) {
13688
+ if (this.running) throw new Error("AutonomousCoordinator: already running");
13689
+ this.running = true;
13690
+ this.iterationCount = 0;
13691
+ const maxIterations = opts.maxIterations ?? 100;
13692
+ const goal = opts.goal ?? "Improve the codebase";
13693
+ const maxCost = opts.maxCostUsd;
13694
+ await this.graph.load();
13695
+ try {
13696
+ const goalConfigs = await this._decomposeGoal(goal);
13697
+ for (const g of goalConfigs) {
13698
+ await this.auction.publishTask(g);
13699
+ }
13700
+ while (this.running) {
13701
+ this.iterationCount++;
13702
+ if (this.iterationCount >= maxIterations) break;
13703
+ if (maxCost !== void 0) {
13704
+ const cost = this.fleetManager?.snapshot()?.total?.cost ?? 0;
13705
+ if (cost >= maxCost) break;
13706
+ }
13707
+ if (opts.runUntilComplete && this.dag.isDone()) break;
13708
+ const decision = await this.brain.decideAuto({
13709
+ id: randomUUID(),
13710
+ source: "system",
13711
+ decisionType: "prioritize_goals",
13712
+ question: `What should we work on next? Open goals: ${this.auction.getPendingTasks().map((g) => g.title).join(", ") || "none"}`,
13713
+ context: {
13714
+ goals: this.auction.getPendingTasks(),
13715
+ fleetStatus: this._fleetStatus()
13716
+ },
13717
+ options: this._goalToOptions(this.auction.getPendingTasks()),
13718
+ risk: "medium",
13719
+ requiresConsensus: false
13720
+ });
13721
+ if (decision.type === "deny") {
13722
+ const blocked = this.dag.getBlocked();
13723
+ if (blocked.length > 0 && this.dag.hasDeadlock()) {
13724
+ (this.events?.emit)("autonomous:deadlock", { blocked });
13725
+ this.running = false;
13726
+ }
13727
+ break;
13728
+ }
13729
+ if (decision.type === "ask_human") {
13730
+ (this.events?.emit)("autonomous:ask_human", { prompt: decision.prompt });
13731
+ break;
13732
+ }
13733
+ if (decision.optionId) {
13734
+ const goalNode = this._optionToGoal(decision.optionId);
13735
+ if (goalNode) {
13736
+ await this._processGoal(goalNode.id);
13737
+ }
13738
+ }
13739
+ const pendingChanges = this.changes.getPendingReviews();
13740
+ for (const change of pendingChanges) {
13741
+ await this._handlePendingChange(change);
13742
+ }
13743
+ }
13744
+ } finally {
13745
+ this.running = false;
13746
+ }
13747
+ return this.getStats();
13748
+ }
13749
+ /** Stop the autonomous loop. */
13750
+ stop() {
13751
+ this.running = false;
13752
+ }
13753
+ /** Get a stats snapshot. */
13754
+ getStats() {
13755
+ const dagStats = this.dag.stats();
13756
+ const auctionStats = this.auction.getStats();
13757
+ const allGoals = this.graph.getGoals({});
13758
+ const allChanges = this.graph.getChanges({});
13759
+ const allDecisions = this.graph.getDecisions();
13760
+ return {
13761
+ goals: {
13762
+ total: allGoals.length,
13763
+ done: allGoals.filter((g) => g.status === "done").length,
13764
+ pending: allGoals.filter((g) => g.status === "pending").length,
13765
+ failed: allGoals.filter((g) => g.status === "failed").length,
13766
+ progress: allGoals.length > 0 ? allGoals.filter((g) => g.status === "done").length / allGoals.length : 0
13767
+ },
13768
+ dag: dagStats,
13769
+ auction: auctionStats,
13770
+ changes: {
13771
+ proposed: allChanges.filter((c) => c.status === "proposed").length,
13772
+ approved: allChanges.filter((c) => c.status === "approved").length,
13773
+ applied: allChanges.filter((c) => c.status === "applied").length,
13774
+ rejected: allChanges.filter((c) => c.status === "rejected").length
13775
+ },
13776
+ decisions: allDecisions.length,
13777
+ costSoFar: this.fleetManager?.snapshot()?.total?.cost
13778
+ };
13779
+ }
13780
+ // ── Fact publishing ──────────────────────────────────────────────────
13781
+ /**
13782
+ * Publish a fact discovered by an agent. Facts are immutable and form
13783
+ * the basis for other agents' decisions.
13784
+ */
13785
+ async publishFact(input) {
13786
+ const fact = await this.graph.add({
13787
+ type: "fact",
13788
+ category: input.category,
13789
+ subject: input.subject,
13790
+ detail: input.detail,
13791
+ file: input.file,
13792
+ line: input.line,
13793
+ severity: input.severity,
13794
+ discoveredBy: this.selfAgentId,
13795
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
13796
+ tags: input.tags ?? [],
13797
+ key: `${input.category}:${input.subject}:${input.file ?? ""}:${input.line ?? ""}`,
13798
+ related: []
13799
+ });
13800
+ await this._mailboxBroadcast({
13801
+ type: "note",
13802
+ subject: `[${input.severity ?? "info"}] ${input.category}: ${input.subject}`,
13803
+ body: `**${input.category}**${input.file ? ` in ${input.file}${input.line ? `:${input.line}` : ""}` : ""}
13804
+ ${input.detail}`
13805
+ });
13806
+ return fact;
13807
+ }
13808
+ // ── Goal creation helpers ────────────────────────────────────────────
13809
+ /**
13810
+ * Publish a goal and add it to the DAG.
13811
+ */
13812
+ async createGoal(input) {
13813
+ const resolvedPriority = input.priority ?? "medium";
13814
+ const goalId = await this.auction.publishTask({
13815
+ title: input.title,
13816
+ description: input.description,
13817
+ priority: resolvedPriority,
13818
+ ...input.tags ? { tags: input.tags } : {}
13819
+ });
13820
+ const goal = this.graph.get(goalId);
13821
+ for (const depId of input.deps ?? []) {
13822
+ this.dag.addNode(depId, this.graph.get(depId)?.type === "goal" ? this.graph.get(depId).title : depId);
13823
+ }
13824
+ this.dag.addNode(goalId, input.description, input.deps ?? []);
13825
+ return goal;
13826
+ }
13827
+ // ── Private ───────────────────────────────────────────────────────────
13828
+ async _decomposeGoal(goalText) {
13829
+ const category = this._inferCategory(goalText);
13830
+ const subGoals = [];
13831
+ if (category === "security") {
13832
+ subGoals.push({ title: "Audit for secrets", description: "Scan codebase for hardcoded secrets and API keys", priority: "critical", tags: ["security"] });
13833
+ subGoals.push({ title: "Check injection vectors", description: "Find eval, innerHTML, SQL concat, shell injection patterns", priority: "critical", tags: ["security", "injection"] });
13834
+ subGoals.push({ title: "Dependency audit", description: "Run npm/pnpm audit for known CVEs", priority: "high", tags: ["security", "deps"] });
13835
+ } else if (category === "bug") {
13836
+ subGoals.push({ title: "Find bugs", description: `Scan for bugs related to: ${goalText}`, priority: "high", tags: ["bug"] });
13837
+ subGoals.push({ title: "Fix bugs", description: "Fix discovered bugs with tests", priority: "high", tags: ["fix"] });
13838
+ } else if (category === "refactor") {
13839
+ subGoals.push({ title: "Plan refactor", description: `Analyze code structure for: ${goalText}`, priority: "medium", tags: ["refactor", "planning"] });
13840
+ subGoals.push({ title: "Implement refactor", description: "Apply the refactoring plan", priority: "medium", tags: ["refactor", "implementation"] });
13841
+ } else {
13842
+ subGoals.push({ title: goalText, description: goalText, priority: "medium", tags: [category] });
13843
+ }
13844
+ return subGoals;
13845
+ }
13846
+ _inferCategory(goal) {
13847
+ const g = goal.toLowerCase();
13848
+ if (g.includes("security") || g.includes("secret") || g.includes("injection")) return "security";
13849
+ if (g.includes("bug") || g.includes("fix") || g.includes("error")) return "bug";
13850
+ if (g.includes("refactor") || g.includes("debt") || g.includes("architecture")) return "architecture";
13851
+ if (g.includes("test") || g.includes("coverage")) return "test";
13852
+ if (g.includes("perf") || g.includes("speed") || g.includes("optimize")) return "perf";
13853
+ if (g.includes("deps") || g.includes("package") || g.includes("update")) return "deps";
13854
+ return "quality";
13855
+ }
13856
+ async _processGoal(goalId) {
13857
+ const ready = this.dag.getReady();
13858
+ if (ready.length === 0) return;
13859
+ const next = ready.find((n) => n.id === goalId) ?? ready[0];
13860
+ this.dag.start(next.id, "auctioneer");
13861
+ const existingAgent = this.auction.getTasksForAgent(this.selfAgentId).find((g) => g.id === next.id);
13862
+ if (!existingAgent) {
13863
+ await this.auction.publishTask({
13864
+ title: next.description,
13865
+ description: next.description,
13866
+ priority: this._dagPriorityToGoal(next.priority),
13867
+ tags: next.tags
13868
+ });
13869
+ }
13870
+ await this._waitForClaim(next.id);
13871
+ }
13872
+ async _waitForClaim(taskId) {
13873
+ const maxWait = 6e4;
13874
+ const pollInterval = 2e3;
13875
+ const start = Date.now();
13876
+ while (Date.now() - start < maxWait) {
13877
+ const goal = this.graph.get(taskId);
13878
+ if (goal?.status === "in_progress" || goal?.status === "done") {
13879
+ return;
13880
+ }
13881
+ await this._sleep(pollInterval);
13882
+ }
13883
+ }
13884
+ async _handlePendingChange(change) {
13885
+ const result = this.consensus.getStatus(change.id);
13886
+ if (result?.outcome !== "pending") return;
13887
+ if (change.qualityGate.passed) {
13888
+ const voteResult = await this.consensus.castVote(
13889
+ change.id,
13890
+ this.selfAgentId,
13891
+ "approve",
13892
+ `Quality gate passed: ${change.qualityGate.checks.map((c) => c.name).join(", ")}`
13893
+ );
13894
+ if (voteResult.outcome === "approved") {
13895
+ await this.changes.markApplied(change.id, (/* @__PURE__ */ new Date()).toISOString());
13896
+ }
13897
+ }
13898
+ }
13899
+ _onDagEvent(event) {
13900
+ if (event.type === "node:ready") {
13901
+ const node = this.dag.getNode(event.nodeId);
13902
+ if (node) {
13903
+ (this.events?.emit)("autonomous:task_ready", { taskId: event.nodeId, description: node.description });
13904
+ }
13905
+ }
13906
+ if (event.type === "deadlock") {
13907
+ (this.events?.emit)("autonomous:deadlock", { blocked: event.blocked });
13908
+ this.running = false;
13909
+ }
13910
+ if (event.type === "graph:done") {
13911
+ (this.events?.emit)("autonomous:all_done", this.getStats());
13912
+ }
13913
+ }
13914
+ _onSubagentTerminated(e) {
13915
+ const payload = e.payload;
13916
+ const subagentId = payload?.subagentId ?? e.subagentId;
13917
+ const stopReason = payload?.stopReason ?? "unknown";
13918
+ const tasks = this.auction.getTasksForAgent(subagentId);
13919
+ for (const task of tasks) {
13920
+ if (stopReason === "end_turn") {
13921
+ void this.auction.complete(task.id, "Subagent completed successfully");
13922
+ } else {
13923
+ void this.auction.fail(task.id, `Subagent terminated: ${stopReason}`);
13924
+ }
13925
+ }
13926
+ }
13927
+ _fleetStatus() {
13928
+ return {
13929
+ running: this.fleetManager?.getFleetStats().running ?? 0,
13930
+ idle: this.fleetManager?.getFleetStats().idle ?? 0,
13931
+ total: this.fleetManager?.getFleetStats().total ?? 0,
13932
+ costSoFar: this.fleetManager?.snapshot()?.total?.cost ?? 0
13933
+ };
13934
+ }
13935
+ _buildVoters() {
13936
+ return [
13937
+ { agentId: "critic", agentName: "Critic", role: "critic", weight: 2, veto: true },
13938
+ { agentId: "bug-hunter", agentName: "Bug Hunter", role: "bug-hunter", weight: 1.5 },
13939
+ { agentId: "security-scanner", agentName: "Security Scanner", role: "security-scanner", weight: 1.5 },
13940
+ { agentId: "audit-log", agentName: "Audit Log", role: "audit-log", weight: 1 },
13941
+ { agentId: "refactor-planner", agentName: "Refactor Planner", role: "refactor-planner", weight: 1 }
13942
+ ];
13943
+ }
13944
+ _goalToOptions(goals) {
13945
+ return goals.slice(0, 5).map((g, i) => ({
13946
+ id: g.id,
13947
+ label: `[${g.priority}] ${g.title}`,
13948
+ recommended: i === 0
13949
+ }));
13950
+ }
13951
+ _optionToGoal(optionId) {
13952
+ return this.graph.get(optionId);
13953
+ }
13954
+ _dagPriorityToGoal(p) {
13955
+ if (p <= 1) return "critical";
13956
+ if (p <= 2) return "high";
13957
+ if (p <= 4) return "medium";
13958
+ return "low";
13959
+ }
13960
+ async _mailboxBroadcast(msg) {
13961
+ if (!this.mailbox) return;
13962
+ try {
13963
+ await this.mailbox.send({
13964
+ from: this.selfAgentId,
13965
+ to: "*",
13966
+ type: msg.type,
13967
+ subject: msg.subject,
13968
+ body: msg.body,
13969
+ priority: "normal"
13970
+ });
13971
+ } catch {
13972
+ }
13973
+ }
13974
+ _sleep(ms) {
13975
+ return new Promise((r) => setTimeout(r, ms));
13976
+ }
13977
+ };
11557
13978
 
11558
- export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GlobalMailbox, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, formatHumanPrompt, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, normalizeRecipient, recordPackageAction, resolveMailboxIdentity, resolveProjectDir, rosterSummaryFromConfigs, scoreAgents, startPackageOutdatedWatcher, updatePackageOutdatedStatus };
13979
+ export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutonomousBrain, AutonomousCoordinator, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, ChangeManager, CollabSession, ConsensusProtocol, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_QUALITY_CHECKS, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GlobalMailbox, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, KnowledgeGraph, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, TaskAuctioneer, TaskDAG, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, formatHumanPrompt, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, normalizeRecipient, recordPackageAction, resolveMailboxIdentity, resolveProjectDir, rosterSummaryFromConfigs, scoreAgents, startPackageOutdatedWatcher, updatePackageOutdatedStatus, withDisabledToolFiltering };
11559
13980
  //# sourceMappingURL=index.js.map
11560
13981
  //# sourceMappingURL=index.js.map