@wrongstack/core 0.9.7 → 0.9.20

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 (67) hide show
  1. package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-Dti3KXGk.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DaF_EgRG.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
  3. package/dist/compactor-D7X96RLZ.d.ts +41 -0
  4. package/dist/{config-SkMIDN9L.d.ts → config-CLXMDOSs.d.ts} +5 -1
  5. package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
  6. package/dist/coordination/index.d.ts +14 -13
  7. package/dist/coordination/index.js +1263 -123
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +22 -21
  10. package/dist/defaults/index.js +1477 -152
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{events-CiG9qUM_.d.ts → events-DH-9r-_C.d.ts} +42 -1
  13. package/dist/execution/index.d.ts +42 -30
  14. package/dist/execution/index.js +359 -112
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +7 -6
  17. package/dist/extension/index.js.map +1 -1
  18. package/dist/{index-CP8638Wm.d.ts → index-BIHJ4uII.d.ts} +12 -8
  19. package/dist/{index-Bsha5K4D.d.ts → index-CFO9QmJo.d.ts} +9 -8
  20. package/dist/index.d.ts +319 -37
  21. package/dist/index.js +2571 -324
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -6
  24. package/dist/infrastructure/index.js +36 -0
  25. package/dist/infrastructure/index.js.map +1 -1
  26. package/dist/kernel/index.d.ts +10 -9
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/{mcp-servers-BouUWYW6.d.ts → mcp-servers-DkESgh0G.d.ts} +25 -3
  29. package/dist/models/index.d.ts +2 -2
  30. package/dist/models/index.js +1 -1
  31. package/dist/models/index.js.map +1 -1
  32. package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-DNp6lAzg.d.ts} +35 -23
  33. package/dist/{multi-agent-coordinator-DTXF2aAl.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
  34. package/dist/{null-fleet-bus-Chrc_3Pp.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +496 -15
  35. package/dist/observability/index.d.ts +2 -2
  36. package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
  37. package/dist/{secret-scrubber-DttNiGYA.d.ts → permission-H35s9Evv.d.ts} +2 -7
  38. package/dist/{permission-policy-BpCGYBud.d.ts → permission-policy-CT-nRmTn.d.ts} +9 -2
  39. package/dist/{plan-templates-envSmNlZ.d.ts → plan-templates-Bs8iRwi6.d.ts} +28 -4
  40. package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BZdDrWrS.d.ts} +3 -3
  41. package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-J9N_PM40.d.ts} +1 -1
  42. package/dist/sdd/index.d.ts +10 -10
  43. package/dist/sdd/index.js +225 -68
  44. package/dist/sdd/index.js.map +1 -1
  45. package/dist/secret-scrubber-3MHDDAtm.d.ts +6 -0
  46. package/dist/{secret-scrubber-QSeI0ADi.d.ts → secret-scrubber-7rSC_emZ.d.ts} +1 -1
  47. package/dist/security/index.d.ts +5 -4
  48. package/dist/security/index.js +37 -6
  49. package/dist/security/index.js.map +1 -1
  50. package/dist/{selector-pox8abg0.d.ts → selector-CFTh3Z6p.d.ts} +1 -1
  51. package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-C7JJlxOw.d.ts} +2 -2
  52. package/dist/skills/index.d.ts +1 -1
  53. package/dist/skills/index.js +1 -1
  54. package/dist/skills/index.js.map +1 -1
  55. package/dist/storage/index.d.ts +391 -6
  56. package/dist/storage/index.js +760 -40
  57. package/dist/storage/index.js.map +1 -1
  58. package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CneIxVip.d.ts} +1 -1
  59. package/dist/{tool-executor-CsktM3h9.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
  60. package/dist/types/index.d.ts +16 -15
  61. package/dist/types/index.js +60 -0
  62. package/dist/types/index.js.map +1 -1
  63. package/dist/utils/index.d.ts +25 -2
  64. package/dist/utils/index.js +90 -1
  65. package/dist/utils/index.js.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/compactor-DVTKL7XD.d.ts +0 -23
@@ -1,5 +1,5 @@
1
1
  import { randomUUID, randomBytes } from 'crypto';
2
- import * as fsp4 from 'fs/promises';
2
+ import * as fsp5 from 'fs/promises';
3
3
  import * as path4 from 'path';
4
4
  import { EventEmitter } from 'events';
5
5
 
@@ -11,16 +11,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
11
11
  });
12
12
  async function atomicWrite(targetPath, content, opts = {}) {
13
13
  const dir = path4.dirname(targetPath);
14
- await fsp4.mkdir(dir, { recursive: true });
14
+ await fsp5.mkdir(dir, { recursive: true });
15
15
  const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
16
16
  try {
17
17
  if (typeof content === "string") {
18
- await fsp4.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
18
+ await fsp5.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
19
19
  } else {
20
- await fsp4.writeFile(tmp, content, { flag: "wx" });
20
+ await fsp5.writeFile(tmp, content, { flag: "wx" });
21
21
  }
22
22
  try {
23
- const fh = await fsp4.open(tmp, "r+");
23
+ const fh = await fsp5.open(tmp, "r+");
24
24
  try {
25
25
  await fh.sync();
26
26
  } finally {
@@ -30,37 +30,37 @@ async function atomicWrite(targetPath, content, opts = {}) {
30
30
  }
31
31
  let mode;
32
32
  try {
33
- const stat3 = await fsp4.stat(targetPath);
33
+ const stat3 = await fsp5.stat(targetPath);
34
34
  mode = stat3.mode & 511;
35
35
  } catch {
36
36
  mode = opts.mode;
37
37
  }
38
38
  if (mode !== void 0) {
39
- await fsp4.chmod(tmp, mode);
39
+ await fsp5.chmod(tmp, mode);
40
40
  }
41
41
  await renameWithRetry(tmp, targetPath);
42
42
  } catch (err) {
43
43
  try {
44
- await fsp4.unlink(tmp);
44
+ await fsp5.unlink(tmp);
45
45
  } catch {
46
46
  }
47
47
  throw err;
48
48
  }
49
49
  }
50
50
  async function ensureDir(dir) {
51
- await fsp4.mkdir(dir, { recursive: true });
51
+ await fsp5.mkdir(dir, { recursive: true });
52
52
  }
53
53
  var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
54
54
  async function renameWithRetry(from, to) {
55
55
  if (process.platform !== "win32") {
56
- await fsp4.rename(from, to);
56
+ await fsp5.rename(from, to);
57
57
  return;
58
58
  }
59
59
  const delays = [10, 25, 60, 120, 250];
60
60
  let lastErr;
61
61
  for (let i = 0; i <= delays.length; i++) {
62
62
  try {
63
- await fsp4.rename(from, to);
63
+ await fsp5.rename(from, to);
64
64
  return;
65
65
  } catch (err) {
66
66
  lastErr = err;
@@ -76,7 +76,7 @@ async function renameWithRetry(from, to) {
76
76
  async function acquireDirectorStateLock(lockPath, processId = process.pid) {
77
77
  let existing;
78
78
  try {
79
- existing = await fsp4.readFile(lockPath, "utf8");
79
+ existing = await fsp5.readFile(lockPath, "utf8");
80
80
  } catch {
81
81
  }
82
82
  if (existing) {
@@ -100,7 +100,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
100
100
  }
101
101
  async function releaseDirectorStateLock(lockPath) {
102
102
  try {
103
- await fsp4.unlink(lockPath);
103
+ await fsp5.unlink(lockPath);
104
104
  } catch {
105
105
  }
106
106
  }
@@ -410,7 +410,11 @@ Working rules:
410
410
  thrashing, terminate it rather than letting cost climb silently.
411
411
  6. Never claim a subagent's work as your own without verifying it. If a
412
412
  result looks wrong, ask_subagent for clarification before passing it
413
- to the user.`;
413
+ to the user.
414
+ 7. Wind down when satisfied. When the results are good enough, call
415
+ work_complete \u2014 no new subagents will spawn and queued tasks complete
416
+ as aborted. Running subagents finish naturally. Call terminate_subagent
417
+ only for ones you need to stop immediately.`;
414
418
  var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
415
419
  a specific slice of a larger plan \u2014 do that slice well and report back.
416
420
 
@@ -555,14 +559,12 @@ var FleetBus = class {
555
559
  };
556
560
  var FleetUsageAggregator = class {
557
561
  constructor(bus, priceLookup, metaLookup) {
558
- this.bus = bus;
559
562
  this.priceLookup = priceLookup;
560
563
  this.metaLookup = metaLookup;
561
564
  bus.filter("provider.response", (e) => this.onProviderResponse(e));
562
565
  bus.filter("tool.executed", (e) => this.onToolExecuted(e));
563
566
  bus.filter("iteration.started", (e) => this.onIterationStarted(e));
564
567
  }
565
- bus;
566
568
  priceLookup;
567
569
  metaLookup;
568
570
  perSubagent = /* @__PURE__ */ new Map();
@@ -631,6 +633,107 @@ var FleetUsageAggregator = class {
631
633
  }
632
634
  };
633
635
 
636
+ // src/coordination/subagent-nicknames.ts
637
+ var NICKNAME_POOL = {
638
+ // Physics & fundamental sciences
639
+ "einstein": { name: "Einstein", domain: "physics" },
640
+ "newton": { name: "Newton", domain: "physics" },
641
+ "feynman": { name: "Feynman", domain: "physics" },
642
+ "dirac": { name: "Dirac", domain: "physics" },
643
+ "bohr": { name: "Bohr", domain: "physics" },
644
+ "planck": { name: "Planck", domain: "physics" },
645
+ "curie": { name: "Curie", domain: "physics" },
646
+ "fermi": { name: "Fermi", domain: "physics" },
647
+ "heisenberg": { name: "Heisenberg", domain: "physics" },
648
+ "schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
649
+ // Mathematics
650
+ "euclid": { name: "Euclid", domain: "math" },
651
+ "gauss": { name: "Gauss", domain: "math" },
652
+ "turing": { name: "Turing", domain: "math" },
653
+ "poincare": { name: "Poincar\xE9", domain: "math" },
654
+ "riemann": { name: "Riemann", domain: "math" },
655
+ "hilbert": { name: "Hilbert", domain: "math" },
656
+ "pythagoras": { name: "Pythagoras", domain: "math" },
657
+ // Computing & information theory
658
+ "von-neumann": { name: "Von Neumann", domain: "computing" },
659
+ "shannon": { name: "Shannon", domain: "computing" },
660
+ "hopper": { name: "Hopper", domain: "computing" },
661
+ "backus": { name: "Backus", domain: "computing" },
662
+ "knuth": { name: "Knuth", domain: "computing" },
663
+ "torvalds": { name: "Torvalds", domain: "computing" },
664
+ "stallman": { name: "Stallman", domain: "computing" },
665
+ "berners-lee": { name: "Berners-Lee", domain: "computing" },
666
+ "babbage": { name: "Babbage", domain: "computing" },
667
+ "lovelace": { name: "Lovelace", domain: "computing" },
668
+ "klein": { name: "Klein", domain: "computing" },
669
+ // Electronics & electrical engineering
670
+ "edison": { name: "Edison", domain: "ee" },
671
+ "tesla": { name: "Tesla", domain: "ee" },
672
+ "faraday": { name: "Faraday", domain: "ee" },
673
+ "maxwell": { name: "Maxwell", domain: "ee" },
674
+ "ohm": { name: "Ohm", domain: "ee" },
675
+ "bell": { name: "Bell", domain: "ee" },
676
+ "marconi": { name: "Marconi", domain: "ee" },
677
+ "lamarr": { name: "Lamarr", domain: "ee" },
678
+ // General science / multi-disciplinary
679
+ "darwin": { name: "Darwin", domain: "biology" },
680
+ "mendel": { name: "Mendel", domain: "biology" },
681
+ "pasteur": { name: "Pasteur", domain: "biology" },
682
+ "hawking": { name: "Hawking", domain: "cosmology" },
683
+ "sagan": { name: "Sagan", domain: "cosmology" },
684
+ // Chemistry / materials
685
+ "lavoisier": { name: "Lavoisier", domain: "chemistry" },
686
+ "mendeleev": { name: "Mendeleev", domain: "chemistry" }
687
+ };
688
+ var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
689
+ var DOMAIN_PREFERENCES = {
690
+ "security": ["shannon", "turing", "lamarr", "stallman"],
691
+ "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
692
+ "refactor": ["gauss", "hilbert", "euclid", "planck"],
693
+ "audit-log": ["sagan", "hawking", "poincare", "newton"],
694
+ "planner": ["hilbert", "gauss", "turing", "euclid"],
695
+ "researcher": ["sagan", "hawking", "darwin", "pasteur"],
696
+ "explorer": ["marconi", "bell", "columbus", "polo"],
697
+ "testing": ["pasteur", "curie", "fermi", "bohr"],
698
+ "frontend": ["lovelace", "hopper", "babbage", "backus"],
699
+ "backend": ["torvalds", "stallman", "von-neumann", "backus"],
700
+ "database": ["turing", "shannon", "backus", "knuth"],
701
+ "devops": ["tesla", "edison", "faraday", "bell"],
702
+ "security-scanner": ["shannon", "turing", "lamarr", "stallman"],
703
+ "refactor-planner": ["gauss", "hilbert", "planck", "newton"],
704
+ "architect": ["von-neumann", "turing", "gauss", "hilbert"],
705
+ "critic": ["einstein", "feynman", "dirac", "bohr"],
706
+ "e2e": ["hopper", "bell", "marconi", "tesla"],
707
+ "performance": ["knuth", "gauss", "planck", "feynman"],
708
+ "chaos": ["tesla", "edison", "curie", "fermi"],
709
+ "cost": ["ohm", "bell", "marconi", "tesla"],
710
+ // default fallback
711
+ "default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
712
+ };
713
+ function assignNickname(role, used) {
714
+ const preferences = [
715
+ ...DOMAIN_PREFERENCES[role] ?? [],
716
+ ...DOMAIN_PREFERENCES["default"] ?? []
717
+ ];
718
+ for (const key of preferences) {
719
+ const entry = NICKNAME_POOL[key];
720
+ if (entry && !used.has(key)) {
721
+ return `${entry.name} (${formatRole(role)})`;
722
+ }
723
+ }
724
+ for (const entry of ALL_NICKNAMES) {
725
+ const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
726
+ if (key && !used.has(key)) {
727
+ return `${entry.name} (${formatRole(role)})`;
728
+ }
729
+ }
730
+ const counter = used.size + 1;
731
+ return `Scientist #${counter} (${formatRole(role)})`;
732
+ }
733
+ function formatRole(role) {
734
+ return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
735
+ }
736
+
634
737
  // src/types/errors.ts
635
738
  var ERROR_CODES = {
636
739
  // Provider
@@ -779,17 +882,7 @@ var SubagentBudget = class _SubagentBudget {
779
882
  startTime = null;
780
883
  _onThreshold;
781
884
  /**
782
- * Tracks which budget kinds currently have an extension request
783
- * in flight. While a kind is here, further `checkLimit` calls for the
784
- * same kind are no-ops — without this dedup, every `recordIteration`
785
- * after the limit is reached spawns a fresh decision Promise (until
786
- * the first one lands and patches limits), flooding the FleetBus
787
- * with redundant threshold events. Cleared in `negotiateExtension`'s
788
- * `finally`.
789
- */
790
- pendingExtensions = /* @__PURE__ */ new Set();
791
- /**
792
- * Hard cap on how long `negotiateExtension` waits for the coordinator to
885
+ * Hard cap on how long `_negotiateExtension` waits for the coordinator to
793
886
  * respond before defaulting to 'stop'. Without this fallback an absent
794
887
  * or hung listener (Director not built / event filter detached mid-run)
795
888
  * leaves the budget over-limit and never enforces anything.
@@ -851,38 +944,83 @@ var SubagentBudget = class _SubagentBudget {
851
944
  * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
852
945
  * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
853
946
  */
854
- checkLimit(kind, used, limit) {
947
+ /**
948
+ * Collects all exceeded budget kinds into a single NOOP-free negotiation.
949
+ * Called by recordIteration / recordToolCall / recordUsage — each may call
950
+ * this for its own kind. The first call starts the negotiation and stores
951
+ * the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
952
+ * kinds (while a negotiation is in flight) are NOOPs — they don't start
953
+ * new conversations with the coordinator. This prevents an EventBus flood
954
+ * when multiple budget kinds are exceeded simultaneously in one iteration.
955
+ *
956
+ * Returns the kinds that were found to be exceeded (for logging/debugging).
957
+ */
958
+ checkLimits(elapsedMs) {
959
+ const exceeded = [];
960
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
961
+ exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
962
+ }
963
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
964
+ exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
965
+ }
966
+ const totalTokens = this.tokenInput + this.tokenOutput;
967
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
968
+ exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
969
+ }
970
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
971
+ exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
972
+ }
973
+ if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
974
+ exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
975
+ }
976
+ if (exceeded.length === 0) return [];
855
977
  if (!this._onThreshold) {
856
- throw new BudgetExceededError(kind, limit, used);
978
+ const first2 = exceeded[0];
979
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
857
980
  }
858
981
  if (this._mode === "sync") {
859
- throw new BudgetExceededError(kind, limit, used);
982
+ const first2 = exceeded[0];
983
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
860
984
  }
861
985
  const bus = this._events;
862
986
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
863
- throw new BudgetExceededError(kind, limit, used);
987
+ const first2 = exceeded[0];
988
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
989
+ }
990
+ for (const entry of exceeded) {
991
+ if (this._pendingNegotiations.has(entry.kind)) continue;
992
+ const decision2 = this._negotiateExtension(entry.kind, exceeded);
993
+ this._pendingNegotiations.set(entry.kind, decision2);
864
994
  }
865
- if (this.pendingExtensions.has(kind)) return;
866
- this.pendingExtensions.add(kind);
867
- const decision = this.negotiateExtension(kind, used, limit);
868
- throw new BudgetThresholdSignal(kind, limit, used, decision);
995
+ const first = exceeded[0];
996
+ const decision = this._pendingNegotiations.get(first.kind);
997
+ throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
869
998
  }
999
+ /**
1000
+ * Per-kind in-flight negotiation Promises. Each budget kind can have its
1001
+ * own concurrent negotiation — e.g. iterations and timeout can both
1002
+ * be exceeded simultaneously without blocking each other. The same kind
1003
+ * cannot start two concurrent negotiations (dedup guard).
1004
+ * Cleared in `_negotiateExtension`'s `finally` block.
1005
+ */
1006
+ _pendingNegotiations = /* @__PURE__ */ new Map();
870
1007
  /**
871
1008
  * Drive the threshold handler to a decision. Resolves with `'stop'`
872
1009
  * (signal the runner to abort) or `{ extend: ... }` (limits already
873
- * patched in-place; the runner should not abort). Always releases the
874
- * `pendingExtensions` slot in `finally`.
1010
+ * patched in-place; the runner should not abort). Clears the
1011
+ * per-kind slot in `_pendingNegotiations` in `finally`.
875
1012
  *
876
1013
  * The 'continue' return from a sync handler is treated as
877
1014
  * `{ extend: {} }` — keep going without patching; next overrun fires
878
1015
  * a fresh signal.
879
1016
  */
880
- async negotiateExtension(kind, used, limit) {
1017
+ async _negotiateExtension(kind, exceeded) {
881
1018
  try {
1019
+ const first = exceeded[0];
882
1020
  const result = this._onThreshold({
883
- kind,
884
- used,
885
- limit,
1021
+ kind: first.kind,
1022
+ used: first.used,
1023
+ limit: first.limit,
886
1024
  requestDecision: () => {
887
1025
  const bus = this._events;
888
1026
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
@@ -899,20 +1037,22 @@ var SubagentBudget = class _SubagentBudget {
899
1037
  () => respond("stop"),
900
1038
  _SubagentBudget.DECISION_TIMEOUT_MS
901
1039
  );
902
- bus.emit("budget.threshold_reached", {
903
- kind,
904
- used,
905
- limit,
906
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
907
- extend: (extra) => {
908
- clearTimeout(fallback);
909
- respond({ extend: extra });
910
- },
911
- deny: () => {
912
- clearTimeout(fallback);
913
- respond("stop");
914
- }
915
- });
1040
+ for (const { kind: kind2, used, limit } of exceeded) {
1041
+ bus.emit("budget.threshold_reached", {
1042
+ kind: kind2,
1043
+ used,
1044
+ limit,
1045
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
1046
+ extend: (extra) => {
1047
+ clearTimeout(fallback);
1048
+ respond({ extend: extra });
1049
+ },
1050
+ deny: () => {
1051
+ clearTimeout(fallback);
1052
+ respond("stop");
1053
+ }
1054
+ });
1055
+ }
916
1056
  });
917
1057
  }
918
1058
  });
@@ -938,47 +1078,39 @@ var SubagentBudget = class _SubagentBudget {
938
1078
  }
939
1079
  return decision;
940
1080
  } finally {
941
- this.pendingExtensions.delete(kind);
1081
+ this._pendingNegotiations.delete(kind);
942
1082
  }
943
1083
  }
944
1084
  recordIteration() {
945
1085
  this.iterations++;
946
- if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
947
- void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
948
- }
1086
+ void this.checkLimits();
949
1087
  }
950
1088
  recordToolCall() {
951
1089
  this.toolCalls++;
952
- if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
953
- void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
954
- }
1090
+ void this.checkLimits();
955
1091
  }
956
1092
  recordUsage(usage, costUsd = 0) {
957
1093
  this.tokenInput += usage.input;
958
1094
  this.tokenOutput += usage.output;
959
1095
  this.costUsd += costUsd;
960
- const totalTokens = this.tokenInput + this.tokenOutput;
961
- if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
962
- void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
963
- }
964
- if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
965
- void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
966
- }
1096
+ void this.checkLimits();
967
1097
  }
968
1098
  /**
969
- * Wall-clock budget check. Unlike other limits, timeout check passes through
970
- * `checkLimit` and is subject to the same negotiation-mode decision table.
971
- * In practice, `'sync'` mode (the usual test configuration) means a timeout
972
- * immediately throws `BudgetExceededError`. In production with a coordinator,
973
- * a timeout emits `budget.threshold_reached` so the Director can decide whether
974
- * to extend or abort.
1099
+ * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
1100
+ * wall-clock time cannot be "extended" by the coordinator, so it throws
1101
+ * synchronously rather than entering the negotiation flow.
1102
+ *
1103
+ * Decision table:
1104
+ * - no `onThreshold` handler → throw `BudgetExceededError`
1105
+ * - `mode === 'sync'` → throw `BudgetExceededError`
1106
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
1107
+ * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
975
1108
  */
976
1109
  checkTimeout() {
977
1110
  if (this.startTime === null || this.limits.timeoutMs === void 0) return;
978
1111
  const elapsed = Date.now() - this.startTime;
979
- if (elapsed > this.limits.timeoutMs) {
980
- void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
981
- }
1112
+ if (elapsed <= this.limits.timeoutMs) return;
1113
+ void this.checkLimits(elapsed);
982
1114
  }
983
1115
  /** Returns true if a timeout has occurred without throwing. Useful for races. */
984
1116
  isTimedOut() {
@@ -3341,11 +3473,46 @@ Working rules:
3341
3473
  - When in doubt, flag as medium rather than ignoring potential issues`
3342
3474
  // Budgets are set by the orchestrator per task — see fleet.ts header.
3343
3475
  };
3476
+ var CRITIC_AGENT = {
3477
+ id: "critic",
3478
+ name: "Critic",
3479
+ role: "critic",
3480
+ prompt: `You are the Critic agent. Your job is to evaluate code quality,
3481
+ architectural decisions, and proposed changes against project conventions,
3482
+ engineering standards, and known quality gates. You do not write code \u2014
3483
+ you judge it.
3484
+
3485
+ Scope:
3486
+ - Evaluate bug severity and fix quality from Bug Hunter reports
3487
+ - Score refactoring plans from Refactor Planner (risk, completeness, trade-offs)
3488
+ - Flag gaps in test coverage, error handling, and edge case coverage
3489
+ - Assess whether a proposed change aligns with existing project patterns
3490
+ - Detect over-engineering or under-engineering relative to the problem scope
3491
+
3492
+ Input format you accept:
3493
+ { "task": "evaluate | score | review", "subject": "bug_report | refactor_plan | diff", "focus": "correctness | maintainability | risk | all" }
3494
+
3495
+ Output: Markdown critic report:
3496
+ - ## Overall Score (0-10 with rationale)
3497
+ - ## Strengths (what's solid)
3498
+ - ## Weaknesses (what needs work)
3499
+ - ## Specific Concerns (with file:line when applicable)
3500
+ - ## Verdict: **Approve / Needs Revision / Reject**
3501
+
3502
+ Working rules:
3503
+ - Be specific \u2014 "looks fine" is not a review. Cite concrete evidence.
3504
+ - When scoring, explain the delta from a perfect score.
3505
+ - If you have no basis to evaluate a concern, say so rather than speculating.
3506
+ - Prioritise correctness over style; correctness issues block approval.
3507
+ - Score thresholds: \u22657 = Approve, 4-6 = Needs Revision, <4 = Reject`
3508
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
3509
+ };
3344
3510
  var FLEET_ROSTER = {
3345
3511
  "audit-log": AUDIT_LOG_AGENT,
3346
3512
  "bug-hunter": BUG_HUNTER_AGENT,
3347
3513
  "refactor-planner": REFACTOR_PLANNER_AGENT,
3348
3514
  "security-scanner": SECURITY_SCANNER_AGENT,
3515
+ "critic": CRITIC_AGENT,
3349
3516
  ...Object.fromEntries(
3350
3517
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
3351
3518
  )
@@ -3355,6 +3522,7 @@ var FLEET_ROSTER_BUDGETS = {
3355
3522
  "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
3356
3523
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
3357
3524
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
3525
+ "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
3358
3526
  ...Object.fromEntries(
3359
3527
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
3360
3528
  )
@@ -3447,6 +3615,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3447
3615
  runner;
3448
3616
  fleetBus;
3449
3617
  subagents = /* @__PURE__ */ new Map();
3618
+ /**
3619
+ * Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
3620
+ * Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
3621
+ * (parallel/eternal engine, SDD parallel run) don't go through
3622
+ * `Director.spawn()` where nicknames are normally assigned, so the
3623
+ * coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
3624
+ * names) to memorable ones here — that's what surfaces in the fleet monitor.
3625
+ */
3626
+ usedNicknames = /* @__PURE__ */ new Set();
3450
3627
  pendingTasks = [];
3451
3628
  completedResults = [];
3452
3629
  totalIterations = 0;
@@ -3495,7 +3672,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
3495
3672
  this.config.maxConcurrent = Math.floor(n);
3496
3673
  this.tryDispatchNext();
3497
3674
  }
3675
+ /**
3676
+ * Upgrade a placeholder/role-derived name to a memorable scientist nickname
3677
+ * (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
3678
+ * empty, equals the role (case-insensitive), is a generic default
3679
+ * ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
3680
+ * Explicit, human-chosen names — including nicknames already assigned by
3681
+ * `Director.spawn()` — are left untouched, so this never double-assigns.
3682
+ */
3683
+ withNickname(subagent) {
3684
+ const role = subagent.role ?? "subagent";
3685
+ const name = subagent.name?.trim() ?? "";
3686
+ const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
3687
+ if (!isPlaceholder) return subagent;
3688
+ const nickname = assignNickname(role, this.usedNicknames);
3689
+ const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
3690
+ this.usedNicknames.add(baseKey);
3691
+ return { ...subagent, name: nickname };
3692
+ }
3498
3693
  async spawn(subagent) {
3694
+ subagent = this.withNickname(subagent);
3499
3695
  const id = subagent.id || randomUUID();
3500
3696
  if (this.subagents.has(id)) {
3501
3697
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -4374,7 +4570,7 @@ function makeRollUpTool(director) {
4374
4570
  function makeTerminateTool(director) {
4375
4571
  return {
4376
4572
  name: "terminate_subagent",
4377
- description: "Forcibly abort a subagent.",
4573
+ description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
4378
4574
  permission: "auto",
4379
4575
  mutating: true,
4380
4576
  inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
@@ -4385,6 +4581,19 @@ function makeTerminateTool(director) {
4385
4581
  }
4386
4582
  };
4387
4583
  }
4584
+ function makeTerminateAllTool(director) {
4585
+ return {
4586
+ name: "terminate_all",
4587
+ description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
4588
+ permission: "auto",
4589
+ mutating: true,
4590
+ inputSchema: { type: "object", properties: {}, required: [] },
4591
+ async execute() {
4592
+ await director.terminateAll();
4593
+ return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
4594
+ }
4595
+ };
4596
+ }
4388
4597
  function makeFleetStatusTool(director) {
4389
4598
  return {
4390
4599
  name: "fleet_status",
@@ -4428,7 +4637,6 @@ function makeFleetSessionTool(director) {
4428
4637
  type: "object",
4429
4638
  properties: {
4430
4639
  subagentId: { type: "string", description: "Subagent id to read the transcript of." },
4431
- /** Number of trailing lines to return (last N JSONL lines). Default: all. */
4432
4640
  tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
4433
4641
  },
4434
4642
  required: ["subagentId"]
@@ -4464,8 +4672,6 @@ function makeFleetHealthTool(director) {
4464
4672
  id: s.id,
4465
4673
  status: s.status,
4466
4674
  lastEventAt: usage?.lastEventAt,
4467
- // Budget pressure: fraction of each limit consumed if we have it.
4468
- // BudgetWarning events carry used/limit ratios; surface them here.
4469
4675
  budgetPressure: {
4470
4676
  iterations: usage?.iterations,
4471
4677
  toolCalls: usage?.toolCalls,
@@ -4477,16 +4683,618 @@ function makeFleetHealthTool(director) {
4477
4683
  }
4478
4684
  };
4479
4685
  }
4686
+ function makeCollabDebugTool(director) {
4687
+ return {
4688
+ name: "collab_debug",
4689
+ description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
4690
+ permission: "auto",
4691
+ mutating: false,
4692
+ inputSchema: {
4693
+ type: "object",
4694
+ properties: {
4695
+ targetPaths: {
4696
+ type: "array",
4697
+ items: { type: "string" },
4698
+ description: "File paths / glob patterns to scan for bugs."
4699
+ },
4700
+ timeoutMs: {
4701
+ type: "number",
4702
+ description: "Timeout in ms. Default: 600000 (10 minutes)."
4703
+ }
4704
+ },
4705
+ required: ["targetPaths"]
4706
+ },
4707
+ async execute(input) {
4708
+ const i = input;
4709
+ if (!i.targetPaths?.length) {
4710
+ return { error: "collab_debug: targetPaths is required and must be non-empty." };
4711
+ }
4712
+ const options = {
4713
+ targetPaths: i.targetPaths,
4714
+ timeoutMs: i.timeoutMs
4715
+ };
4716
+ try {
4717
+ const report = await director.spawnCollab(options);
4718
+ return {
4719
+ sessionId: report.sessionId,
4720
+ overallVerdict: report.overallVerdict,
4721
+ bugCount: report.bugs.length,
4722
+ planCount: report.refactorPlans.length,
4723
+ evaluationCount: report.evaluations.length,
4724
+ summary: report.summary,
4725
+ bugs: report.bugs,
4726
+ refactorPlans: report.refactorPlans,
4727
+ evaluations: report.evaluations
4728
+ };
4729
+ } catch (err) {
4730
+ const msg = err instanceof Error ? err.message : String(err);
4731
+ return { error: "collab_debug failed: " + msg };
4732
+ }
4733
+ }
4734
+ };
4735
+ }
4736
+ function makeFleetEmitTool(director) {
4737
+ return {
4738
+ name: "fleet_emit",
4739
+ description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
4740
+ permission: "auto",
4741
+ mutating: false,
4742
+ inputSchema: {
4743
+ type: "object",
4744
+ properties: {
4745
+ type: {
4746
+ type: "string",
4747
+ description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
4748
+ },
4749
+ payload: {
4750
+ type: "object",
4751
+ description: "Event payload. Structure depends on event type. Use null if no payload."
4752
+ }
4753
+ },
4754
+ required: ["type"]
4755
+ },
4756
+ async execute(input) {
4757
+ const i = input;
4758
+ director.fleet.emit({
4759
+ subagentId: director.id,
4760
+ ts: Date.now(),
4761
+ type: i.type,
4762
+ payload: i.payload ?? {}
4763
+ });
4764
+ return { ok: true, event: i.type };
4765
+ }
4766
+ };
4767
+ }
4768
+ function makeWorkCompleteTool(director) {
4769
+ return {
4770
+ name: "work_complete",
4771
+ description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
4772
+ permission: "auto",
4773
+ mutating: false,
4774
+ inputSchema: { type: "object", properties: {}, required: [] },
4775
+ async execute() {
4776
+ director.workComplete();
4777
+ return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
4778
+ }
4779
+ };
4780
+ }
4781
+ var DirectorAlertLevel = /* @__PURE__ */ ((DirectorAlertLevel2) => {
4782
+ DirectorAlertLevel2["WARNING"] = "warning";
4783
+ DirectorAlertLevel2["CRITICAL"] = "critical";
4784
+ DirectorAlertLevel2["CANCELLED"] = "cancelled";
4785
+ return DirectorAlertLevel2;
4786
+ })(DirectorAlertLevel || {});
4787
+ var CollabSession = class extends EventEmitter {
4788
+ sessionId;
4789
+ options;
4790
+ snapshot;
4791
+ director;
4792
+ fleetBus;
4793
+ subagentIds = /* @__PURE__ */ new Map();
4794
+ // role → subagentId
4795
+ bugs = /* @__PURE__ */ new Map();
4796
+ plans = /* @__PURE__ */ new Map();
4797
+ evaluations = /* @__PURE__ */ new Map();
4798
+ disposers = new Array();
4799
+ settled = false;
4800
+ timeoutMs;
4801
+ cancelled = false;
4802
+ alerts = [];
4803
+ /** Tracks tool call counts per subagent for progress-based timeout decisions. */
4804
+ progressBySubagent = /* @__PURE__ */ new Map();
4805
+ /** Last tool call count when a timeout warning was handled. */
4806
+ lastTimeoutProgress = /* @__PURE__ */ new Map();
4807
+ /** Session-level timeout timer handle (cleared on cancel or natural completion). */
4808
+ _timeoutTimer;
4809
+ constructor(director, fleetBus, options) {
4810
+ super();
4811
+ this.sessionId = randomUUID();
4812
+ this.options = options;
4813
+ this.director = director;
4814
+ this.fleetBus = fleetBus;
4815
+ this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
4816
+ if (options.prebuiltSnapshot) {
4817
+ this.snapshot = options.prebuiltSnapshot;
4818
+ } else {
4819
+ this.snapshot = {
4820
+ id: this.sessionId,
4821
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4822
+ files: []
4823
+ };
4824
+ }
4825
+ }
4826
+ get id() {
4827
+ return this.sessionId;
4828
+ }
4829
+ getSessionAlerts() {
4830
+ return [...this.alerts];
4831
+ }
4832
+ isCancelled() {
4833
+ return this.cancelled;
4834
+ }
4835
+ /**
4836
+ * Snapshot of role → subagentId map. The Director calls coordinator.stop()
4837
+ * for each agent when cancelling the session, using this map to enumerate
4838
+ * all three collab agents.
4839
+ */
4840
+ getSubagentIds() {
4841
+ return new Map(this.subagentIds);
4842
+ }
4843
+ async buildSnapshot() {
4844
+ if (this.snapshot.files.length > 0) return this.snapshot;
4845
+ for (const filePath of this.options.targetPaths) {
4846
+ try {
4847
+ const content = await fsp5.readFile(filePath, "utf8");
4848
+ const ext = filePath.split(".").pop() ?? "";
4849
+ const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
4850
+ this.snapshot.files.push({ path: filePath, content, language });
4851
+ } catch {
4852
+ this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
4853
+ }
4854
+ }
4855
+ return this.snapshot;
4856
+ }
4857
+ /**
4858
+ * Cancel the session. Emits director.cancel_collab on the FleetBus so all
4859
+ * collab agents finish early. The session-level timeout timer is also cleared.
4860
+ * Safe to call multiple times (idempotent after first call).
4861
+ */
4862
+ cancel(reason = "Director cancelled collab session") {
4863
+ if (this.settled) return;
4864
+ this.cancelled = true;
4865
+ if (this._timeoutTimer) {
4866
+ clearTimeout(this._timeoutTimer);
4867
+ this._timeoutTimer = void 0;
4868
+ }
4869
+ this.fleetBus.emit({
4870
+ subagentId: this.director.id,
4871
+ ts: Date.now(),
4872
+ type: "director.cancel_collab",
4873
+ payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
4874
+ });
4875
+ this.fleetBus.emit({
4876
+ subagentId: this.director.id,
4877
+ ts: Date.now(),
4878
+ type: "collab.cancelled",
4879
+ payload: { sessionId: this.sessionId, reason }
4880
+ });
4881
+ }
4882
+ async start() {
4883
+ if (this.settled) throw new Error("session already settled");
4884
+ this.settled = true;
4885
+ await this.buildSnapshot();
4886
+ this.wireFleetBus();
4887
+ const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
4888
+ this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
4889
+ this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
4890
+ this.spawnAgent("critic", this.buildCriticTask())
4891
+ ]);
4892
+ this.subagentIds.set("bug-hunter", bugHunterId);
4893
+ this.subagentIds.set("refactor-planner", refactorPlannerId);
4894
+ this.subagentIds.set("critic", criticId);
4895
+ const timeout = new Promise((_, reject) => {
4896
+ this._timeoutTimer = setTimeout(() => {
4897
+ this.cancel("Session-level timeout reached");
4898
+ reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
4899
+ }, this.timeoutMs);
4900
+ });
4901
+ let results;
4902
+ try {
4903
+ results = await Promise.race([
4904
+ Promise.all([
4905
+ this.director.awaitTasks([bugHunterId]),
4906
+ this.director.awaitTasks([refactorPlannerId]),
4907
+ this.director.awaitTasks([criticId])
4908
+ ]),
4909
+ timeout
4910
+ ]);
4911
+ } catch (err) {
4912
+ if (this._timeoutTimer) {
4913
+ clearTimeout(this._timeoutTimer);
4914
+ this._timeoutTimer = void 0;
4915
+ }
4916
+ this.cleanup();
4917
+ const error = err instanceof Error ? err : new Error(String(err));
4918
+ this.emit("session.error", error);
4919
+ throw error;
4920
+ }
4921
+ for (const result of results.flat()) {
4922
+ await this.parseAndEmit(result);
4923
+ }
4924
+ const report = this.assembleReport();
4925
+ this.cleanup();
4926
+ this.emit("session.done", report);
4927
+ return report;
4928
+ }
4929
+ async parseAndEmit(result) {
4930
+ if (result.status !== "success" || result.result == null) return;
4931
+ const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
4932
+ for (const obj of this.extractJsonObjects(text)) {
4933
+ const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
4934
+ if (!type) continue;
4935
+ this.fleetBus.emit({
4936
+ subagentId: result.subagentId,
4937
+ taskId: result.taskId,
4938
+ ts: Date.now(),
4939
+ type,
4940
+ payload: obj
4941
+ });
4942
+ }
4943
+ }
4944
+ extractJsonObjects(text) {
4945
+ const objects = [];
4946
+ let depth = 0;
4947
+ let start = -1;
4948
+ let inString = false;
4949
+ let escaped = false;
4950
+ for (let i = 0; i < text.length; i++) {
4951
+ const ch = text[i];
4952
+ if (inString) {
4953
+ if (escaped) escaped = false;
4954
+ else if (ch === "\\") escaped = true;
4955
+ else if (ch === '"') inString = false;
4956
+ continue;
4957
+ }
4958
+ if (ch === '"') {
4959
+ inString = true;
4960
+ } else if (ch === "{") {
4961
+ if (depth === 0) start = i;
4962
+ depth++;
4963
+ } else if (ch === "}" && depth > 0) {
4964
+ depth--;
4965
+ if (depth === 0 && start >= 0) {
4966
+ const candidate = text.slice(start, i + 1);
4967
+ try {
4968
+ const parsed = JSON.parse(candidate);
4969
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4970
+ objects.push(parsed);
4971
+ }
4972
+ } catch {
4973
+ }
4974
+ start = -1;
4975
+ }
4976
+ }
4977
+ }
4978
+ return objects;
4979
+ }
4980
+ budgetForRole(role) {
4981
+ if (this.options.budgetOverrides?.[role]) {
4982
+ return this.options.budgetOverrides[role];
4983
+ }
4984
+ const defaults = {
4985
+ "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
4986
+ "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
4987
+ "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
4988
+ };
4989
+ return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
4990
+ }
4991
+ async spawnAgent(role, taskBrief) {
4992
+ const budget = this.budgetForRole(role);
4993
+ const cfg = {
4994
+ id: `${role}-${this.sessionId}`,
4995
+ name: role,
4996
+ role,
4997
+ tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
4998
+ maxIterations: budget.maxIterations,
4999
+ maxToolCalls: budget.maxToolCalls,
5000
+ timeoutMs: budget.timeoutMs
5001
+ };
5002
+ const subagentId = await this.director.spawn(cfg);
5003
+ await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
5004
+ return subagentId;
5005
+ }
5006
+ buildBugHunterTask() {
5007
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
5008
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
5009
+ ${f.content}`).join("\n\n");
5010
+ return `You are BugHunter. Scan the following files for bugs and code smells.
5011
+
5012
+ Target files:
5013
+ ${fileContents}
5014
+
5015
+ For each bug found, emit it using the fleet_emit tool immediately:
5016
+ { "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
5017
+
5018
+ After scanning all files, write your full markdown bug report to:
5019
+ ${scratchpad}/bug-hunter-report-${this.sessionId}.md
5020
+
5021
+ Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
5022
+ }
5023
+ buildRefactorPlannerTask() {
5024
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
5025
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
5026
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
5027
+ ${f.content}`).join("\n\n");
5028
+ return `You are RefactorPlanner. Plan refactorings for the following files.
5029
+
5030
+ Target files:
5031
+ ${fileContents}
5032
+
5033
+ Read the BugHunter report at: ${bugHunterReportPath}
5034
+
5035
+ For each bug you can address, emit a refactor plan using fleet_emit:
5036
+ { "type": "refactor.plan", "payload": { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [{ "number": 1, "title": "<phase>", "tasks": ["<task>"], "risk": "<low|medium|high>" }], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } } }
5037
+
5038
+ Also write your full markdown plan to:
5039
+ ${scratchpad}/refactor-plan-${this.sessionId}.md
5040
+
5041
+ Emit each plan immediately. Do not wait until planning is complete.`;
5042
+ }
5043
+ buildCriticTask() {
5044
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
5045
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
5046
+ const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
5047
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
5048
+ ${f.content}`).join("\n\n");
5049
+ return `You are Critic. Evaluate bug findings and refactor plans.
5050
+
5051
+ Target files:
5052
+ ${fileContents}
5053
+
5054
+ Read the BugHunter report at: ${bugHunterReportPath}
5055
+ Read the RefactorPlanner report at: ${refactorPlanPath}
5056
+
5057
+ For each bug and refactor plan, emit your evaluation using fleet_emit:
5058
+ { "type": "critic.evaluation", "payload": { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": ["<strength>"], "weaknesses": ["<weakness>"], "concerns": [{ "description": "<concern>", "severity": "<blocking|advisory>" }] } } }
5059
+
5060
+ After all evaluations, write your markdown report to:
5061
+ ${scratchpad}/critic-report-${this.sessionId}.md
5062
+
5063
+ Emit each evaluation immediately. Do not wait until you have read all reports.`;
5064
+ }
5065
+ wireFleetBus() {
5066
+ const dTool = this.fleetBus.filter("tool.executed", (e) => {
5067
+ this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
5068
+ });
5069
+ this.disposers.push(dTool);
5070
+ const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
5071
+ const payload = e.payload;
5072
+ const role = this.roleFromSubagentId(e.subagentId);
5073
+ if (!role) return;
5074
+ const btwNotes = this.director.getLeaderBtwNotes();
5075
+ const alert = {
5076
+ sessionId: this.sessionId,
5077
+ subagentId: e.subagentId,
5078
+ role,
5079
+ level: "warning" /* WARNING */,
5080
+ message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
5081
+ budgetKind: payload.kind,
5082
+ elapsedMs: payload.timeoutMs,
5083
+ limit: payload.limit,
5084
+ btwNotes
5085
+ };
5086
+ this.alerts.push(alert);
5087
+ this.fleetBus.emit({
5088
+ subagentId: e.subagentId,
5089
+ ts: Date.now(),
5090
+ type: "collab.warning",
5091
+ payload: alert
5092
+ });
5093
+ const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
5094
+ if (decision === "cancel") {
5095
+ this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
5096
+ return;
5097
+ }
5098
+ if (payload.kind === "timeout") {
5099
+ const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
5100
+ const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
5101
+ if (progress <= lastProgress) {
5102
+ payload.deny();
5103
+ return;
5104
+ }
5105
+ this.lastTimeoutProgress.set(e.subagentId, progress);
5106
+ const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
5107
+ setImmediate(() => {
5108
+ payload.extend({ timeoutMs: newLimit });
5109
+ });
5110
+ return;
5111
+ }
5112
+ if (decision === "extend") {
5113
+ setImmediate(() => {
5114
+ const base = Math.max(payload.limit, payload.used);
5115
+ const extra = {};
5116
+ switch (payload.kind) {
5117
+ case "iterations":
5118
+ extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
5119
+ break;
5120
+ case "tool_calls":
5121
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
5122
+ break;
5123
+ case "tokens":
5124
+ extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
5125
+ break;
5126
+ case "cost":
5127
+ extra.maxCostUsd = Math.min(base * 1.5, 100);
5128
+ break;
5129
+ }
5130
+ payload.extend(extra);
5131
+ });
5132
+ return;
5133
+ }
5134
+ if (payload.kind !== "timeout") {
5135
+ setImmediate(() => {
5136
+ const base = Math.max(payload.limit, payload.used);
5137
+ const extra = {};
5138
+ switch (payload.kind) {
5139
+ case "iterations":
5140
+ extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
5141
+ break;
5142
+ case "tool_calls":
5143
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
5144
+ break;
5145
+ case "tokens":
5146
+ extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
5147
+ break;
5148
+ case "cost":
5149
+ extra.maxCostUsd = Math.min(base * 1.25, 100);
5150
+ break;
5151
+ }
5152
+ payload.extend(extra);
5153
+ });
5154
+ }
5155
+ });
5156
+ this.disposers.push(dBudget);
5157
+ const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
5158
+ const payload = e.payload;
5159
+ if (payload.sessionId !== this.sessionId) return;
5160
+ this.cancelled = true;
5161
+ if (this._timeoutTimer) {
5162
+ clearTimeout(this._timeoutTimer);
5163
+ this._timeoutTimer = void 0;
5164
+ }
5165
+ this.fleetBus.emit({
5166
+ subagentId: this.director.id,
5167
+ ts: Date.now(),
5168
+ type: "collab.cancelled",
5169
+ payload: { sessionId: this.sessionId, reason: payload.reason }
5170
+ });
5171
+ });
5172
+ this.disposers.push(dCancel);
5173
+ const d1 = this.fleetBus.filter("bug.found", (e) => {
5174
+ const payload = e.payload;
5175
+ if (payload?.finding) {
5176
+ this.bugs.set(payload.finding.id, payload.finding);
5177
+ this.emit("bug.found", payload);
5178
+ }
5179
+ });
5180
+ this.disposers.push(d1);
5181
+ const d2 = this.fleetBus.filter("refactor.plan", (e) => {
5182
+ const payload = e.payload;
5183
+ if (payload?.plan) {
5184
+ this.plans.set(payload.plan.id, payload.plan);
5185
+ this.emit("refactor.plan", payload);
5186
+ }
5187
+ });
5188
+ this.disposers.push(d2);
5189
+ const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
5190
+ const payload = e.payload;
5191
+ if (payload?.evaluation) {
5192
+ this.evaluations.set(payload.evaluation.id, payload.evaluation);
5193
+ this.emit("critic.evaluation", payload);
5194
+ }
5195
+ });
5196
+ this.disposers.push(d3);
5197
+ }
5198
+ roleFromSubagentId(subagentId) {
5199
+ for (const [role, id] of this.subagentIds) {
5200
+ if (id === subagentId) return role;
5201
+ }
5202
+ const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
5203
+ return match?.[1] ?? null;
5204
+ }
5205
+ assembleReport() {
5206
+ const bugList = Array.from(this.bugs.values());
5207
+ const planList = Array.from(this.plans.values());
5208
+ const evalList = Array.from(this.evaluations.values());
5209
+ let disposition = "completed";
5210
+ if (this.cancelled) disposition = "cancelled";
5211
+ const verdictOrder = {
5212
+ approve: 0,
5213
+ needs_revision: 1,
5214
+ reject: 2
5215
+ };
5216
+ const overallVerdict = evalList.reduce(
5217
+ (worst, eval_) => {
5218
+ const w = verdictOrder[worst];
5219
+ const c = verdictOrder[eval_.verdict];
5220
+ return c > w ? eval_.verdict : worst;
5221
+ },
5222
+ "approve"
5223
+ );
5224
+ const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
5225
+ return {
5226
+ sessionId: this.sessionId,
5227
+ startedAt: this.snapshot.createdAt,
5228
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
5229
+ targetPaths: this.options.targetPaths,
5230
+ disposition,
5231
+ bugs: bugList,
5232
+ refactorPlans: planList,
5233
+ evaluations: evalList,
5234
+ alerts: [...this.alerts],
5235
+ overallVerdict,
5236
+ summary
5237
+ };
5238
+ }
5239
+ buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
5240
+ const lines = [
5241
+ `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
5242
+ "",
5243
+ `**Target:** ${this.options.targetPaths.join(", ")}`,
5244
+ `**Disposition:** ${disposition.toUpperCase()}`,
5245
+ `**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
5246
+ ""
5247
+ ];
5248
+ if (this.alerts.length > 0) {
5249
+ lines.push("### Alerts", "");
5250
+ for (const alert of this.alerts) {
5251
+ lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
5252
+ }
5253
+ lines.push("");
5254
+ }
5255
+ if (bugs.length > 0) {
5256
+ lines.push("### Bugs Found", "");
5257
+ for (const b of bugs) {
5258
+ lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
5259
+ }
5260
+ lines.push("");
5261
+ }
5262
+ if (plans.length > 0) {
5263
+ lines.push("### Refactor Plans", "");
5264
+ for (const p of plans) {
5265
+ lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
5266
+ for (const phase of p.phases) {
5267
+ lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
5268
+ }
5269
+ }
5270
+ lines.push("");
5271
+ }
5272
+ if (evals.length > 0) {
5273
+ lines.push("### Critic Evaluations", "");
5274
+ for (const e of evals) {
5275
+ lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
5276
+ for (const c of e.concerns) {
5277
+ if (c.severity === "blocking") lines.push(` - ${c.description}`);
5278
+ }
5279
+ }
5280
+ lines.push("");
5281
+ }
5282
+ return lines.join("\n");
5283
+ }
5284
+ cleanup() {
5285
+ for (const dispose of this.disposers) dispose();
5286
+ this.disposers.length = 0;
5287
+ }
5288
+ };
4480
5289
 
4481
5290
  // src/coordination/director.ts
4482
5291
  var FleetSpawnBudgetError = class extends Error {
4483
5292
  kind;
4484
5293
  limit;
4485
5294
  observed;
4486
- constructor(kind, limit, observed) {
4487
- super(
4488
- kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`
4489
- );
5295
+ constructor(kind, limit, observed, message) {
5296
+ const defaultMsg = kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`;
5297
+ super(message ?? defaultMsg);
4490
5298
  this.name = "FleetSpawnBudgetError";
4491
5299
  this.kind = kind;
4492
5300
  this.limit = limit;
@@ -4507,6 +5315,20 @@ var FleetCostCapError = class extends Error {
4507
5315
  this.observed = observed;
4508
5316
  }
4509
5317
  };
5318
+ var FleetContextOverflowError = class extends Error {
5319
+ kind;
5320
+ limit;
5321
+ observed;
5322
+ constructor(limit, observed) {
5323
+ super(
5324
+ `Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
5325
+ );
5326
+ this.name = "FleetContextOverflowError";
5327
+ this.kind = "max_context_load";
5328
+ this.limit = limit;
5329
+ this.observed = observed;
5330
+ }
5331
+ };
4510
5332
  var Director = class {
4511
5333
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
4512
5334
  get coordinatorId() {
@@ -4523,6 +5345,25 @@ var Director = class {
4523
5345
  * injected; otherwise own FleetUsageAggregator.
4524
5346
  */
4525
5347
  usage;
5348
+ /**
5349
+ * Update the leader agent's current context pressure (full request tokens:
5350
+ * messages + systemPrompt + toolDefs). The director checks this before every
5351
+ * spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
5352
+ * spawning is refused with a `FleetContextOverflowError`.
5353
+ *
5354
+ * Call this after each leader agent iteration to keep the pressure current.
5355
+ * The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
5356
+ */
5357
+ setLeaderContextPressure(tokens) {
5358
+ this.leaderContextPressure = tokens;
5359
+ this.fleetManager?.setLeaderContextPressure(tokens);
5360
+ }
5361
+ /**
5362
+ * Read the leader agent's current context pressure.
5363
+ */
5364
+ getLeaderContextPressure() {
5365
+ return this.leaderContextPressure;
5366
+ }
4526
5367
  /**
4527
5368
  * Optional fleet-level policy container. When provided the Director
4528
5369
  * delegates spawn budgeting, manifest entries, and checkpointing to it
@@ -4555,6 +5396,8 @@ var Director = class {
4555
5396
  subagentBridges = /* @__PURE__ */ new Map();
4556
5397
  /** Tracks per-spawn config + assigned task ids for manifest writing. */
4557
5398
  manifestEntries = /* @__PURE__ */ new Map();
5399
+ /** Tracks assigned nicknames so the same name is never reused in one fleet. */
5400
+ _usedNicknames = /* @__PURE__ */ new Set();
4558
5401
  manifestPath;
4559
5402
  roster;
4560
5403
  directorPreamble;
@@ -4606,6 +5449,23 @@ var Director = class {
4606
5449
  taskCompletedListener = null;
4607
5450
  /** Optional LLM classifier for smart dispatch. Passed from options. */
4608
5451
  dispatchClassifier;
5452
+ /** Leader agent's current context pressure (full request tokens). */
5453
+ leaderContextPressure = 0;
5454
+ /** Maximum context load fraction before spawn is refused. */
5455
+ maxLeaderContextLoad;
5456
+ /** Provider's max context window in tokens. */
5457
+ maxContext;
5458
+ /**
5459
+ * When set by `workComplete()`, the director stops dispatching new tasks
5460
+ * and terminates all running subagents. Used when the director's LLM decides
5461
+ * the goal is satisfied and no further spawns are needed — prevents the
5462
+ * coordinator from keeping workers alive for tasks that will never arrive.
5463
+ */
5464
+ workCompleteFlag = false;
5465
+ /** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
5466
+ _leaderBtwNotes = [];
5467
+ /** Active collab sessions tracked by sessionId (see spawnCollab). */
5468
+ _activeCollabSessions = /* @__PURE__ */ new Map();
4609
5469
  constructor(opts) {
4610
5470
  this.id = opts.config.coordinatorId || randomUUID();
4611
5471
  this.manifestPath = opts.manifestPath;
@@ -4621,6 +5481,8 @@ var Director = class {
4621
5481
  this.dispatchClassifier = opts.dispatchClassifier;
4622
5482
  this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
4623
5483
  this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
5484
+ this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
5485
+ this.maxContext = opts.maxContext ?? 128e3;
4624
5486
  this.sessionsRoot = opts.sessionsRoot;
4625
5487
  this.directorRunId = opts.directorRunId ?? this.id;
4626
5488
  this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
@@ -4632,7 +5494,7 @@ var Director = class {
4632
5494
  }, opts.checkpointDebounceMs ?? 250) : null;
4633
5495
  this.fleetManager = opts.fleetManager;
4634
5496
  if (this.sharedScratchpadPath) {
4635
- void fsp4.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
5497
+ void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
4636
5498
  (err) => this.logShutdownError("shared_scratchpad_mkdir", err)
4637
5499
  );
4638
5500
  }
@@ -4648,7 +5510,7 @@ var Director = class {
4648
5510
  this.fleet = new FleetBus();
4649
5511
  this.usage = new FleetUsageAggregator(
4650
5512
  this.fleet,
4651
- (id, provider, model) => {
5513
+ (_id, provider, model) => {
4652
5514
  if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
4653
5515
  return void 0;
4654
5516
  },
@@ -4710,6 +5572,9 @@ var Director = class {
4710
5572
  });
4711
5573
  this.fleet.filter("budget.threshold_reached", (e) => {
4712
5574
  const payload = e.payload;
5575
+ if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
5576
+ return;
5577
+ }
4713
5578
  if (payload.kind === "timeout") {
4714
5579
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
4715
5580
  const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
@@ -4788,6 +5653,107 @@ var Director = class {
4788
5653
  extensionsFor(subagentId) {
4789
5654
  return this.extendTotals.get(subagentId) ?? 0;
4790
5655
  }
5656
+ /**
5657
+ * Signal that the director's work is done. Once called:
5658
+ * - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
5659
+ * subagents can be created
5660
+ * - Running subagents are NOT forcibly stopped — they finish naturally,
5661
+ * but no new tasks are dispatched to them
5662
+ *
5663
+ * This lets the director LLM say "I'm satisfied with the results, stop
5664
+ * spawning and wind down" — without killing in-flight work mid-execution.
5665
+ * Call `terminateAll()` separately if you need immediate teardown.
5666
+ *
5667
+ * Idempotent — calling twice is a no-op.
5668
+ */
5669
+ workComplete() {
5670
+ this.workCompleteFlag = true;
5671
+ this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
5672
+ }
5673
+ /** Returns true if `workComplete()` has been called on this director. */
5674
+ isWorkComplete() {
5675
+ return this.workCompleteFlag;
5676
+ }
5677
+ /**
5678
+ * Stashes a /btw note on the leader agent's context. The leader's agent loop
5679
+ * calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
5680
+ * into the conversation as a visible block — no abort, no restart, just a
5681
+ * "by the way" nudge the model picks up on its next turn.
5682
+ *
5683
+ * This is the entry point for the host (CLI, TUI) to inject /btw notes
5684
+ * programmatically without going through the slash-command path.
5685
+ */
5686
+ setLeaderBtwNote(note) {
5687
+ const trimmed = note.trim();
5688
+ if (!trimmed) return this._leaderBtwNotes.length;
5689
+ this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
5690
+ return this._leaderBtwNotes.length;
5691
+ }
5692
+ /**
5693
+ * Read and clear all pending /btw notes the leader has stashed.
5694
+ * Returns them in FIFO order (empty array when none).
5695
+ *
5696
+ * Called by CollabSession when a budget threshold event fires so the
5697
+ * Director can inspect accumulated /btw notes before deciding whether
5698
+ * to cancel the collab session or let it continue.
5699
+ */
5700
+ getLeaderBtwNotes() {
5701
+ const notes = this._leaderBtwNotes;
5702
+ this._leaderBtwNotes = [];
5703
+ return notes;
5704
+ }
5705
+ /**
5706
+ * Peek at pending /btw notes without consuming them.
5707
+ * Useful for UI to show "N pending notes" without clearing them.
5708
+ */
5709
+ peekLeaderBtwNotes() {
5710
+ return [...this._leaderBtwNotes];
5711
+ }
5712
+ /**
5713
+ * Drain (read + clear) all /btw notes in one call.
5714
+ * Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
5715
+ * in the agent's btw.ts. The Director calls this at the point where it
5716
+ * makes a cancellation decision, not on every budget event.
5717
+ */
5718
+ drainLeaderBtwNotes() {
5719
+ return this.getLeaderBtwNotes();
5720
+ }
5721
+ /**
5722
+ * Cancel an active collab session by its id.
5723
+ * Emits `director.cancel_collab` on the FleetBus so the session's agents
5724
+ * finish early with a 'cancelled' disposition.
5725
+ *
5726
+ * Returns silently if the session id is not tracked or already settled.
5727
+ * The CollabDebugReport will reflect 'cancelled' disposition when awaited.
5728
+ */
5729
+ cancelCollabSession(sessionId, reason = "Director cancelled") {
5730
+ const session = this._activeCollabSessions.get(sessionId);
5731
+ if (!session || session.isCancelled()) return;
5732
+ session.cancel(reason);
5733
+ for (const [_role, subagentId] of session.getSubagentIds()) {
5734
+ this.coordinator.stop(subagentId).catch(() => {
5735
+ });
5736
+ }
5737
+ }
5738
+ /**
5739
+ * Subscribe a callback to be notified whenever a collab session raises
5740
+ * an alert (warning level). The callback receives the full DirectorAlert
5741
+ * payload so the host (CLI, TUI) can display it to the user.
5742
+ * Returns an unsubscribe function.
5743
+ */
5744
+ onCollabAlert(handler) {
5745
+ return this.fleet.filter("collab.warning", (e) => {
5746
+ handler(e.payload);
5747
+ });
5748
+ }
5749
+ /**
5750
+ * Returns all active (not yet settled) collab session ids.
5751
+ * Useful for the TUI to render a "N active sessions" badge and for
5752
+ * the host to know what can be cancelled.
5753
+ */
5754
+ activeCollabSessions() {
5755
+ return Array.from(this._activeCollabSessions.keys());
5756
+ }
4791
5757
  /** Best-effort session-writer append. Swallows failures — the director
4792
5758
  * must not break a fleet run because the session JSONL handle closed. */
4793
5759
  async appendSessionEvent(event) {
@@ -4826,12 +5792,21 @@ var Director = class {
4826
5792
  * it the `cost` column in `usage.snapshot()` stays at 0.
4827
5793
  */
4828
5794
  async spawn(config, priceLookup) {
5795
+ if (this.workCompleteFlag) {
5796
+ throw new FleetSpawnBudgetError(
5797
+ "max_spawns",
5798
+ this.maxSpawns,
5799
+ this.spawnCount + 1,
5800
+ "workComplete() has been called \u2014 director closed further spawning"
5801
+ );
5802
+ }
4829
5803
  if (this.fleetManager) {
4830
5804
  const rejection = this.fleetManager.canSpawn(config);
4831
5805
  if (rejection) {
4832
5806
  if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
4833
5807
  if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
4834
5808
  if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
5809
+ if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
4835
5810
  }
4836
5811
  } else {
4837
5812
  if (this.spawnDepth >= this.maxSpawnDepth) {
@@ -4846,11 +5821,29 @@ var Director = class {
4846
5821
  throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
4847
5822
  }
4848
5823
  }
5824
+ if (this.maxLeaderContextLoad < 1) {
5825
+ const threshold = this.maxContext * this.maxLeaderContextLoad;
5826
+ if (this.leaderContextPressure >= threshold) {
5827
+ throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
5828
+ }
5829
+ }
4849
5830
  }
4850
5831
  let result;
5832
+ const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
5833
+ if (needsNickname) {
5834
+ const role = config.role ?? "subagent";
5835
+ if (this.fleetManager) {
5836
+ this.fleetManager.assignNicknameAndRecord("", config, priceLookup);
5837
+ } else {
5838
+ config.name = assignNickname(role, this._usedNicknames);
5839
+ this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
5840
+ }
5841
+ }
4851
5842
  result = await this.coordinator.spawn(config);
4852
5843
  if (this.fleetManager) {
4853
- this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
5844
+ if (!needsNickname) {
5845
+ this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
5846
+ }
4854
5847
  } else {
4855
5848
  this.spawnCount += 1;
4856
5849
  this.subagentMeta.set(result.subagentId, {
@@ -5006,7 +5999,7 @@ var Director = class {
5006
5999
  })),
5007
6000
  usage: this.usage.snapshot()
5008
6001
  };
5009
- await fsp4.mkdir(path4.dirname(this.manifestPath), { recursive: true });
6002
+ await fsp5.mkdir(path4.dirname(this.manifestPath), { recursive: true });
5010
6003
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
5011
6004
  return this.manifestPath;
5012
6005
  }
@@ -5061,6 +6054,24 @@ var Director = class {
5061
6054
  */
5062
6055
  async assign(task) {
5063
6056
  const taskWithId = task.id ? task : { ...task, id: randomUUID() };
6057
+ if (this.workCompleteFlag) {
6058
+ const synthetic = {
6059
+ subagentId: taskWithId.subagentId ?? "unassigned",
6060
+ taskId: taskWithId.id,
6061
+ status: "stopped",
6062
+ error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
6063
+ iterations: 0,
6064
+ toolCalls: 0,
6065
+ durationMs: 0
6066
+ };
6067
+ this.completed.set(taskWithId.id, synthetic);
6068
+ const waiter = this.taskWaiters.get(taskWithId.id);
6069
+ if (waiter) {
6070
+ waiter.resolve(synthetic);
6071
+ this.taskWaiters.delete(taskWithId.id);
6072
+ }
6073
+ return taskWithId.id;
6074
+ }
5064
6075
  if (task.subagentId) {
5065
6076
  if (this.fleetManager) {
5066
6077
  this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
@@ -5175,7 +6186,7 @@ var Director = class {
5175
6186
  const filePath = path4.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
5176
6187
  let raw;
5177
6188
  try {
5178
- raw = await fsp4.readFile(filePath, "utf8");
6189
+ raw = await fsp5.readFile(filePath, "utf8");
5179
6190
  } catch {
5180
6191
  return null;
5181
6192
  }
@@ -5283,10 +6294,14 @@ var Director = class {
5283
6294
  makeAskTool(this),
5284
6295
  makeRollUpTool(this),
5285
6296
  makeTerminateTool(this),
6297
+ makeTerminateAllTool(this),
5286
6298
  makeFleetStatusTool(this),
5287
6299
  makeFleetUsageTool(this),
5288
6300
  makeFleetSessionTool(this),
5289
- makeFleetHealthTool(this)
6301
+ makeFleetHealthTool(this),
6302
+ makeCollabDebugTool(this),
6303
+ makeFleetEmitTool(this),
6304
+ makeWorkCompleteTool(this)
5290
6305
  ];
5291
6306
  return t;
5292
6307
  }
@@ -5298,6 +6313,25 @@ var Director = class {
5298
6313
  async acquireCheckpointLock() {
5299
6314
  return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
5300
6315
  }
6316
+ /**
6317
+ * Start a collaborative debugging session: BugHunter, RefactorPlanner,
6318
+ * and Critic run in parallel on the same target files, with findings
6319
+ * flowing through the FleetBus (bug.found → refactor.plan →
6320
+ * critic.evaluation). Returns a structured CollabDebugReport when all
6321
+ * three agents complete or the session times out.
6322
+ */
6323
+ async spawnCollab(options) {
6324
+ const session = new CollabSession(this, this.fleet, {
6325
+ ...options,
6326
+ onBudgetWarning: (alert) => {
6327
+ return options.onBudgetWarning?.(alert) ?? "ignore";
6328
+ }
6329
+ });
6330
+ this._activeCollabSessions.set(session.sessionId, session);
6331
+ session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
6332
+ session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
6333
+ return session.start();
6334
+ }
5301
6335
  /**
5302
6336
  * Resume from a prior checkpoint snapshot (loaded via
5303
6337
  * `loadDirectorState()`). Re-attach to the fleet mid-flight so
@@ -5578,7 +6612,7 @@ async function readSubagentPartial(opts, subagentId) {
5578
6612
  candidates.push(path4.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
5579
6613
  } else {
5580
6614
  try {
5581
- const entries = await fsp4.readdir(opts.sessionsRoot, { withFileTypes: true });
6615
+ const entries = await fsp5.readdir(opts.sessionsRoot, { withFileTypes: true });
5582
6616
  for (const entry of entries) {
5583
6617
  if (entry.isDirectory()) {
5584
6618
  candidates.push(path4.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
@@ -5591,7 +6625,7 @@ async function readSubagentPartial(opts, subagentId) {
5591
6625
  for (const file of candidates) {
5592
6626
  let raw;
5593
6627
  try {
5594
- raw = await fsp4.readFile(file, "utf8");
6628
+ raw = await fsp5.readFile(file, "utf8");
5595
6629
  } catch {
5596
6630
  continue;
5597
6631
  }
@@ -5887,9 +6921,11 @@ function isEmptyMessage(msg) {
5887
6921
  var DefaultSessionStore = class {
5888
6922
  dir;
5889
6923
  events;
6924
+ secretScrubber;
5890
6925
  constructor(opts) {
5891
6926
  this.dir = opts.dir;
5892
6927
  this.events = opts.events;
6928
+ this.secretScrubber = opts.secretScrubber;
5893
6929
  }
5894
6930
  /** Join session ID to its absolute path within the store directory. */
5895
6931
  sessionPath(id, ext) {
@@ -5906,7 +6942,7 @@ var DefaultSessionStore = class {
5906
6942
  const file = path4.join(shardDir, `${id}.jsonl`);
5907
6943
  let handle;
5908
6944
  try {
5909
- handle = await fsp4.open(file, "a", 384);
6945
+ handle = await fsp5.open(file, "a", 384);
5910
6946
  } catch (err) {
5911
6947
  throw new Error(
5912
6948
  `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
@@ -5914,7 +6950,11 @@ var DefaultSessionStore = class {
5914
6950
  );
5915
6951
  }
5916
6952
  try {
5917
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir: shardDir, filePath: file });
6953
+ return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
6954
+ dir: shardDir,
6955
+ filePath: file,
6956
+ secretScrubber: this.secretScrubber
6957
+ });
5918
6958
  } catch (err) {
5919
6959
  await handle.close().catch(() => {
5920
6960
  });
@@ -5926,7 +6966,7 @@ var DefaultSessionStore = class {
5926
6966
  const data = await this.load(id);
5927
6967
  let handle;
5928
6968
  try {
5929
- handle = await fsp4.open(file, "a", 384);
6969
+ handle = await fsp5.open(file, "a", 384);
5930
6970
  } catch (err) {
5931
6971
  throw new Error(
5932
6972
  `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
@@ -5944,7 +6984,7 @@ var DefaultSessionStore = class {
5944
6984
  provider: data.metadata.provider
5945
6985
  },
5946
6986
  this.events,
5947
- { resumed: true, dir: this.dir, filePath: file }
6987
+ { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
5948
6988
  );
5949
6989
  return { writer, data };
5950
6990
  } catch (err) {
@@ -5955,7 +6995,7 @@ var DefaultSessionStore = class {
5955
6995
  }
5956
6996
  async load(id) {
5957
6997
  const file = this.sessionPath(id, ".jsonl");
5958
- const raw = await fsp4.readFile(file, "utf8");
6998
+ const raw = await fsp5.readFile(file, "utf8");
5959
6999
  const lines = raw.split("\n").filter((l) => l.trim());
5960
7000
  const events = [];
5961
7001
  for (const line of lines) {
@@ -5990,7 +7030,7 @@ var DefaultSessionStore = class {
5990
7030
  /** Recursively collect all session IDs from shard subdirectories. */
5991
7031
  async collectSessionIds(dir) {
5992
7032
  const ids = [];
5993
- const entries = await fsp4.readdir(dir, { withFileTypes: true });
7033
+ const entries = await fsp5.readdir(dir, { withFileTypes: true });
5994
7034
  for (const entry of entries) {
5995
7035
  const full = path4.join(dir, entry.name);
5996
7036
  if (entry.isDirectory()) {
@@ -6004,11 +7044,11 @@ var DefaultSessionStore = class {
6004
7044
  async summaryFor(id) {
6005
7045
  const manifest = this.sessionPath(id, ".summary.json");
6006
7046
  try {
6007
- const raw = await fsp4.readFile(manifest, "utf8");
7047
+ const raw = await fsp5.readFile(manifest, "utf8");
6008
7048
  return JSON.parse(raw);
6009
7049
  } catch {
6010
7050
  const full = this.sessionPath(id, ".jsonl");
6011
- const stat3 = await fsp4.stat(full);
7051
+ const stat3 = await fsp5.stat(full);
6012
7052
  const summary = await this.summarize(id, stat3.mtime.toISOString());
6013
7053
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
6014
7054
  console.warn(
@@ -6020,8 +7060,8 @@ var DefaultSessionStore = class {
6020
7060
  }
6021
7061
  }
6022
7062
  async delete(id) {
6023
- await fsp4.unlink(this.sessionPath(id, ".jsonl"));
6024
- await fsp4.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
7063
+ await fsp5.unlink(this.sessionPath(id, ".jsonl"));
7064
+ await fsp5.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
6025
7065
  }
6026
7066
  async clearHistory(id) {
6027
7067
  await this.ensureShardDir(id);
@@ -6035,8 +7075,8 @@ var DefaultSessionStore = class {
6035
7075
  provider: "unknown"
6036
7076
  })}
6037
7077
  `;
6038
- await fsp4.writeFile(file, record, "utf8");
6039
- await fsp4.unlink(meta).catch(() => void 0);
7078
+ await fsp5.writeFile(file, record, "utf8");
7079
+ await fsp5.unlink(meta).catch(() => void 0);
6040
7080
  }
6041
7081
  async summarize(id, mtime) {
6042
7082
  try {
@@ -6150,6 +7190,7 @@ var FileSessionWriter = class {
6150
7190
  this.resumed = opts.resumed ?? false;
6151
7191
  this.manifestFile = opts.dir ? path4.join(opts.dir, `${id}.summary.json`) : "";
6152
7192
  this.filePath = opts.filePath ?? "";
7193
+ this.secretScrubber = opts.secretScrubber;
6153
7194
  this.summary = {
6154
7195
  id,
6155
7196
  title: "(empty session)",
@@ -6178,7 +7219,29 @@ var FileSessionWriter = class {
6178
7219
  resumed;
6179
7220
  appendFailCount = 0;
6180
7221
  lastAppendWarnAt = 0;
6181
- promptIndex = 0;
7222
+ secretScrubber;
7223
+ /**
7224
+ * Scrub secrets out of conversation-turn events before they are observed
7225
+ * for the summary, written to the JSONL log, or surfaced on resume. Only
7226
+ * `user_input` / `llm_response` carry free-form user/model text; other event
7227
+ * types either have no secret-bearing content or are already scrubbed
7228
+ * upstream (tool results). Returns the event unchanged when no scrubber is
7229
+ * configured.
7230
+ */
7231
+ scrubEvent(event) {
7232
+ const s = this.secretScrubber;
7233
+ if (!s) return event;
7234
+ if (event.type === "user_input") {
7235
+ return {
7236
+ ...event,
7237
+ content: typeof event.content === "string" ? s.scrub(event.content) : s.scrubObject(event.content)
7238
+ };
7239
+ }
7240
+ if (event.type === "llm_response") {
7241
+ return { ...event, content: s.scrubObject(event.content) };
7242
+ }
7243
+ return event;
7244
+ }
6182
7245
  pendingFileSnapshots = [];
6183
7246
  /** Tracks open tool_use IDs during the current run to serialize on close for resume. */
6184
7247
  openToolUses = /* @__PURE__ */ new Set();
@@ -6199,7 +7262,7 @@ var FileSessionWriter = class {
6199
7262
  `;
6200
7263
  try {
6201
7264
  if (this.filePath) {
6202
- await fsp4.writeFile(this.filePath, record, { flag: "a", mode: 384 });
7265
+ await fsp5.writeFile(this.filePath, record, { flag: "a", mode: 384 });
6203
7266
  }
6204
7267
  } catch {
6205
7268
  }
@@ -6210,9 +7273,10 @@ var FileSessionWriter = class {
6210
7273
  this.initDone = true;
6211
7274
  await this.writeSessionStartLazy();
6212
7275
  }
6213
- this.observeForSummary(event);
7276
+ const scrubbed = this.scrubEvent(event);
7277
+ this.observeForSummary(scrubbed);
6214
7278
  try {
6215
- await this.handle.appendFile(`${JSON.stringify(event)}
7279
+ await this.handle.appendFile(`${JSON.stringify(scrubbed)}
6216
7280
  `, "utf8");
6217
7281
  } catch (err) {
6218
7282
  this.appendFailCount++;
@@ -6268,7 +7332,6 @@ var FileSessionWriter = class {
6268
7332
  await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
6269
7333
  this.pendingFileSnapshots = [];
6270
7334
  }
6271
- this.promptIndex = promptIndex + 1;
6272
7335
  await this.append({
6273
7336
  type: "checkpoint",
6274
7337
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6292,7 +7355,7 @@ var FileSessionWriter = class {
6292
7355
  }
6293
7356
  async truncateToCheckpoint(targetPromptIndex) {
6294
7357
  if (!this.filePath) return 0;
6295
- const raw = await fsp4.readFile(this.filePath, "utf8");
7358
+ const raw = await fsp5.readFile(this.filePath, "utf8");
6296
7359
  const lines = raw.split("\n");
6297
7360
  const kept = [];
6298
7361
  let removedCount = 0;
@@ -6330,13 +7393,13 @@ var FileSessionWriter = class {
6330
7393
  }
6331
7394
  const truncated = kept.join("\n");
6332
7395
  const tmpPath = `${this.filePath}.rewind.tmp`;
6333
- await fsp4.writeFile(tmpPath, truncated + "\n", "utf8");
7396
+ await fsp5.writeFile(tmpPath, truncated + "\n", "utf8");
6334
7397
  try {
6335
7398
  await this.handle.close();
6336
- await fsp4.rename(tmpPath, this.filePath);
6337
- this.handle = await fsp4.open(this.filePath, "a", 384);
7399
+ await fsp5.rename(tmpPath, this.filePath);
7400
+ this.handle = await fsp5.open(this.filePath, "a", 384);
6338
7401
  } catch (err) {
6339
- await fsp4.unlink(tmpPath).catch(() => void 0);
7402
+ await fsp5.unlink(tmpPath).catch(() => void 0);
6340
7403
  throw err;
6341
7404
  }
6342
7405
  await this.append({
@@ -6362,7 +7425,39 @@ var FileSessionWriter = class {
6362
7425
  provider: this.meta.provider ?? "unknown"
6363
7426
  })}
6364
7427
  `;
6365
- await fsp4.writeFile(this.filePath, record, "utf8");
7428
+ await fsp5.writeFile(this.filePath, record, "utf8");
7429
+ }
7430
+ /**
7431
+ * Idea #1 — write an in-flight marker. The agent loop should call
7432
+ * this at the start of each long-running operation; a matching
7433
+ * `clearInFlightMarker` follows on clean exit. A stale marker
7434
+ * (no end) is what `SessionRecovery.detectStale` looks for.
7435
+ */
7436
+ async writeInFlightMarker(context) {
7437
+ if (!context || context.length > 500) {
7438
+ throw new Error("In-flight context must be 1..500 chars");
7439
+ }
7440
+ await this.append({
7441
+ type: "in_flight_start",
7442
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7443
+ context
7444
+ });
7445
+ this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
7446
+ }
7447
+ /**
7448
+ * Idea #1 — close the in-flight marker. Idempotent in spirit
7449
+ * (you can call it after a successful iteration even if you
7450
+ * didn't open one this round) — but the session log records
7451
+ * every call so postmortem tooling can see "the agent finished
7452
+ * cleanly X times, then died without finishing Y".
7453
+ */
7454
+ async clearInFlightMarker(reason) {
7455
+ await this.append({
7456
+ type: "in_flight_end",
7457
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7458
+ reason
7459
+ });
7460
+ this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
6366
7461
  }
6367
7462
  };
6368
7463
  function userInputTitle(content) {
@@ -6464,7 +7559,6 @@ var FleetManager = class {
6464
7559
  /** Usage rollup across all subagents. */
6465
7560
  usage;
6466
7561
  manifestPath;
6467
- sessionsRoot;
6468
7562
  directorRunId;
6469
7563
  /** Spawn cap (lifetime total). Infinity means unlimited. */
6470
7564
  maxSpawns;
@@ -6488,11 +7582,18 @@ var FleetManager = class {
6488
7582
  pendingTasks = /* @__PURE__ */ new Map();
6489
7583
  subagentMeta = /* @__PURE__ */ new Map();
6490
7584
  priceLookups = /* @__PURE__ */ new Map();
7585
+ /** Tracks which nickname keys are already assigned — prevents collisions. */
7586
+ _usedNicknames = /* @__PURE__ */ new Set();
6491
7587
  /** The coordinator (wired via setCoordinator by Director after construction). */
6492
7588
  coordinator = null;
7589
+ /** Leader agent's current context pressure (full request tokens). */
7590
+ leaderContextPressure = 0;
7591
+ /** Maximum context load fraction before spawn is refused. */
7592
+ maxLeaderContextLoad;
7593
+ /** Provider's max context window in tokens. */
7594
+ maxContext;
6493
7595
  constructor(opts = {}) {
6494
7596
  this.manifestPath = opts.manifestPath;
6495
- this.sessionsRoot = opts.sessionsRoot;
6496
7597
  this.directorRunId = opts.directorRunId ?? randomUUID();
6497
7598
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
6498
7599
  this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
@@ -6500,6 +7601,8 @@ var FleetManager = class {
6500
7601
  this.sessionWriter = opts.sessionWriter ?? null;
6501
7602
  this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
6502
7603
  this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
7604
+ this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
7605
+ this.maxContext = opts.maxContext ?? 128e3;
6503
7606
  this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
6504
7607
  opts.stateCheckpointPath,
6505
7608
  {
@@ -6514,7 +7617,7 @@ var FleetManager = class {
6514
7617
  this.fleet = new FleetBus();
6515
7618
  this.usage = new FleetUsageAggregator(
6516
7619
  this.fleet,
6517
- (id, provider, model) => {
7620
+ (_id, provider, model) => {
6518
7621
  if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
6519
7622
  return void 0;
6520
7623
  },
@@ -6553,7 +7656,7 @@ var FleetManager = class {
6553
7656
  * which cap was exceeded. Does NOT throw — the caller decides
6554
7657
  * how to surface the rejection.
6555
7658
  */
6556
- canSpawn(config) {
7659
+ canSpawn(_config) {
6557
7660
  if (this.spawnDepth >= this.maxSpawnDepth) {
6558
7661
  return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
6559
7662
  }
@@ -6566,8 +7669,45 @@ var FleetManager = class {
6566
7669
  return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
6567
7670
  }
6568
7671
  }
7672
+ if (this.maxLeaderContextLoad < 1) {
7673
+ const threshold = this.maxContext * this.maxLeaderContextLoad;
7674
+ if (this.leaderContextPressure >= threshold) {
7675
+ return {
7676
+ kind: "max_context_load",
7677
+ limit: threshold,
7678
+ observed: this.leaderContextPressure
7679
+ };
7680
+ }
7681
+ }
6569
7682
  return null;
6570
7683
  }
7684
+ setLeaderContextPressure(tokens) {
7685
+ this.leaderContextPressure = tokens;
7686
+ }
7687
+ /**
7688
+ * Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
7689
+ * record it so the same name is never reused, then record the spawn.
7690
+ *
7691
+ * Call this INSTEAD of `recordSpawn` when you want automatic nicknames.
7692
+ * The nickname is written back to `config.name` BEFORE the coordinator
7693
+ * sees the config, so the manifest, logs, and fleet UI all show it.
7694
+ */
7695
+ assignNicknameAndRecord(subagentId, config, priceLookup) {
7696
+ const role = config.role ?? "subagent";
7697
+ const nickname = assignNickname(role, this._usedNicknames);
7698
+ const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
7699
+ this._usedNicknames.add(baseKey);
7700
+ config.name = nickname;
7701
+ this.recordSpawn(subagentId, config, priceLookup);
7702
+ return nickname;
7703
+ }
7704
+ /**
7705
+ * Returns the set of already-assigned nickname keys — useful for debugging
7706
+ * and testing.
7707
+ */
7708
+ get usedNicknames() {
7709
+ return this._usedNicknames;
7710
+ }
6571
7711
  /**
6572
7712
  * Records a spawn: increments counter, stores metadata, updates state checkpoint,
6573
7713
  * and schedules a debounced manifest write. Call AFTER the coordinator
@@ -6626,7 +7766,7 @@ var FleetManager = class {
6626
7766
  })),
6627
7767
  usage: this.usage.snapshot()
6628
7768
  };
6629
- await fsp4.mkdir(path4.dirname(this.manifestPath), { recursive: true });
7769
+ await fsp5.mkdir(path4.dirname(this.manifestPath), { recursive: true });
6630
7770
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
6631
7771
  return this.manifestPath;
6632
7772
  }
@@ -6738,6 +7878,6 @@ var FleetManager = class {
6738
7878
  }
6739
7879
  };
6740
7880
 
6741
- 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, BudgetExceededError, BudgetThresholdSignal, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultMultiAgentCoordinator, Director, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, getAgentDefinition, makeAgentSubagentRunner, makeDirectorSessionFactory, makeLLMClassifier, rosterSummaryFromConfigs, scoreAgents };
7881
+ 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, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, getAgentDefinition, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, rosterSummaryFromConfigs, scoreAgents };
6742
7882
  //# sourceMappingURL=index.js.map
6743
7883
  //# sourceMappingURL=index.js.map