@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
@@ -319,9 +319,11 @@ function isEmptyMessage(msg) {
319
319
  var DefaultSessionStore = class {
320
320
  dir;
321
321
  events;
322
+ secretScrubber;
322
323
  constructor(opts) {
323
324
  this.dir = opts.dir;
324
325
  this.events = opts.events;
326
+ this.secretScrubber = opts.secretScrubber;
325
327
  }
326
328
  /** Join session ID to its absolute path within the store directory. */
327
329
  sessionPath(id, ext) {
@@ -346,7 +348,11 @@ var DefaultSessionStore = class {
346
348
  );
347
349
  }
348
350
  try {
349
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir: shardDir, filePath: file });
351
+ return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
352
+ dir: shardDir,
353
+ filePath: file,
354
+ secretScrubber: this.secretScrubber
355
+ });
350
356
  } catch (err) {
351
357
  await handle.close().catch(() => {
352
358
  });
@@ -376,7 +382,7 @@ var DefaultSessionStore = class {
376
382
  provider: data.metadata.provider
377
383
  },
378
384
  this.events,
379
- { resumed: true, dir: this.dir, filePath: file }
385
+ { resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
380
386
  );
381
387
  return { writer, data };
382
388
  } catch (err) {
@@ -582,6 +588,7 @@ var FileSessionWriter = class {
582
588
  this.resumed = opts.resumed ?? false;
583
589
  this.manifestFile = opts.dir ? path15.join(opts.dir, `${id}.summary.json`) : "";
584
590
  this.filePath = opts.filePath ?? "";
591
+ this.secretScrubber = opts.secretScrubber;
585
592
  this.summary = {
586
593
  id,
587
594
  title: "(empty session)",
@@ -610,7 +617,29 @@ var FileSessionWriter = class {
610
617
  resumed;
611
618
  appendFailCount = 0;
612
619
  lastAppendWarnAt = 0;
613
- promptIndex = 0;
620
+ secretScrubber;
621
+ /**
622
+ * Scrub secrets out of conversation-turn events before they are observed
623
+ * for the summary, written to the JSONL log, or surfaced on resume. Only
624
+ * `user_input` / `llm_response` carry free-form user/model text; other event
625
+ * types either have no secret-bearing content or are already scrubbed
626
+ * upstream (tool results). Returns the event unchanged when no scrubber is
627
+ * configured.
628
+ */
629
+ scrubEvent(event) {
630
+ const s = this.secretScrubber;
631
+ if (!s) return event;
632
+ if (event.type === "user_input") {
633
+ return {
634
+ ...event,
635
+ content: typeof event.content === "string" ? s.scrub(event.content) : s.scrubObject(event.content)
636
+ };
637
+ }
638
+ if (event.type === "llm_response") {
639
+ return { ...event, content: s.scrubObject(event.content) };
640
+ }
641
+ return event;
642
+ }
614
643
  pendingFileSnapshots = [];
615
644
  /** Tracks open tool_use IDs during the current run to serialize on close for resume. */
616
645
  openToolUses = /* @__PURE__ */ new Set();
@@ -642,9 +671,10 @@ var FileSessionWriter = class {
642
671
  this.initDone = true;
643
672
  await this.writeSessionStartLazy();
644
673
  }
645
- this.observeForSummary(event);
674
+ const scrubbed = this.scrubEvent(event);
675
+ this.observeForSummary(scrubbed);
646
676
  try {
647
- await this.handle.appendFile(`${JSON.stringify(event)}
677
+ await this.handle.appendFile(`${JSON.stringify(scrubbed)}
648
678
  `, "utf8");
649
679
  } catch (err) {
650
680
  this.appendFailCount++;
@@ -700,7 +730,6 @@ var FileSessionWriter = class {
700
730
  await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
701
731
  this.pendingFileSnapshots = [];
702
732
  }
703
- this.promptIndex = promptIndex + 1;
704
733
  await this.append({
705
734
  type: "checkpoint",
706
735
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -796,6 +825,38 @@ var FileSessionWriter = class {
796
825
  `;
797
826
  await fsp.writeFile(this.filePath, record, "utf8");
798
827
  }
828
+ /**
829
+ * Idea #1 — write an in-flight marker. The agent loop should call
830
+ * this at the start of each long-running operation; a matching
831
+ * `clearInFlightMarker` follows on clean exit. A stale marker
832
+ * (no end) is what `SessionRecovery.detectStale` looks for.
833
+ */
834
+ async writeInFlightMarker(context) {
835
+ if (!context || context.length > 500) {
836
+ throw new Error("In-flight context must be 1..500 chars");
837
+ }
838
+ await this.append({
839
+ type: "in_flight_start",
840
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
841
+ context
842
+ });
843
+ this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
844
+ }
845
+ /**
846
+ * Idea #1 — close the in-flight marker. Idempotent in spirit
847
+ * (you can call it after a successful iteration even if you
848
+ * didn't open one this round) — but the session log records
849
+ * every call so postmortem tooling can see "the agent finished
850
+ * cleanly X times, then died without finishing Y".
851
+ */
852
+ async clearInFlightMarker(reason) {
853
+ await this.append({
854
+ type: "in_flight_end",
855
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
856
+ reason
857
+ });
858
+ this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
859
+ }
799
860
  };
800
861
  function userInputTitle(content) {
801
862
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
@@ -1266,6 +1327,9 @@ function isSecretField(name) {
1266
1327
  return SECRET_KEY_PATTERN.test(lc);
1267
1328
  }
1268
1329
 
1330
+ // src/storage/config-loader.ts
1331
+ init_atomic_write();
1332
+
1269
1333
  // src/types/context-window.ts
1270
1334
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
1271
1335
  var CONTEXT_WINDOW_MODES = Object.freeze([
@@ -1510,6 +1574,40 @@ var DefaultConfigLoader = class {
1510
1574
  }
1511
1575
  return Object.freeze(cfg);
1512
1576
  }
1577
+ /**
1578
+ * Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
1579
+ * by the vault (if provided). The file is isolated from the main config
1580
+ * hierarchy to prevent accidental commits.
1581
+ */
1582
+ async persistSyncConfig(cfg) {
1583
+ let toWrite = { ...cfg };
1584
+ if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
1585
+ toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
1586
+ }
1587
+ await atomicWrite(this.paths.syncConfig, JSON.stringify(toWrite, null, 2), { mode: 384 });
1588
+ }
1589
+ /**
1590
+ * Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
1591
+ * the token if a vault is available. Returns null if the file doesn't exist.
1592
+ * This is separate from main config loading because sync.json is intentionally
1593
+ * isolated — it should never be part of project-local or env-driven config.
1594
+ */
1595
+ async loadSyncConfig() {
1596
+ try {
1597
+ const raw = await fsp.readFile(this.paths.syncConfig, "utf8");
1598
+ const parsed = safeParse(raw);
1599
+ if (!parsed.ok || !parsed.value) return null;
1600
+ if (this.vault) {
1601
+ const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
1602
+ return decrypted.sync ?? null;
1603
+ }
1604
+ return parsed.value;
1605
+ } catch (err) {
1606
+ if (err.code === "ENOENT") return null;
1607
+ console.warn("[config] Failed to load sync config:", err);
1608
+ return null;
1609
+ }
1610
+ }
1513
1611
  async readJson(file) {
1514
1612
  let raw;
1515
1613
  try {
@@ -3126,7 +3224,11 @@ var DefaultPermissionPolicy = class {
3126
3224
  return { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" };
3127
3225
  }
3128
3226
  if (this.sessionAllowed.has(subjectKey)) {
3129
- return { permission: "auto", source: "trust", reason: "session soft allow (user pressed yes)" };
3227
+ return {
3228
+ permission: "auto",
3229
+ source: "trust",
3230
+ reason: "session soft allow (user pressed yes)"
3231
+ };
3130
3232
  }
3131
3233
  if (entry?.deny && subject && matchAny(entry.deny, subject)) {
3132
3234
  return { permission: "deny", source: "deny", reason: "matched deny pattern" };
@@ -3146,7 +3248,11 @@ var DefaultPermissionPolicy = class {
3146
3248
  const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
3147
3249
  if (decision === "always") {
3148
3250
  await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
3149
- return { permission: "auto", source: "user", reason: "destructive yolo always-allowed" };
3251
+ return {
3252
+ permission: "auto",
3253
+ source: "user",
3254
+ reason: "destructive yolo always-allowed"
3255
+ };
3150
3256
  }
3151
3257
  if (decision === "deny") {
3152
3258
  await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
@@ -3154,13 +3260,22 @@ var DefaultPermissionPolicy = class {
3154
3260
  }
3155
3261
  return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
3156
3262
  }
3157
- return { permission: "confirm", source: "yolo_destructive", riskTier: "destructive", reason: "destructive tool needs explicit approval even in yolo mode" };
3263
+ return {
3264
+ permission: "confirm",
3265
+ source: "yolo_destructive",
3266
+ riskTier: "destructive",
3267
+ reason: "destructive tool needs explicit approval even in yolo mode"
3268
+ };
3158
3269
  }
3159
3270
  return { permission: "auto", source: "yolo" };
3160
3271
  }
3161
3272
  if (tool.name === "write" && subject) {
3162
3273
  if (ctx.hasRead(subject)) {
3163
- return { permission: "auto", source: "context", reason: "file already read in this session" };
3274
+ return {
3275
+ permission: "auto",
3276
+ source: "context",
3277
+ reason: "file already read in this session"
3278
+ };
3164
3279
  }
3165
3280
  }
3166
3281
  if (tool.permission === "auto" && !tool.mutating) {
@@ -3268,6 +3383,10 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
3268
3383
  // arbitrary shell — use exec for constrained shell
3269
3384
  "write",
3270
3385
  // arbitrary file write
3386
+ "edit",
3387
+ // arbitrary in-project file modification (equivalent to write)
3388
+ "replace",
3389
+ // arbitrary multi-file find/replace (equivalent to write)
3271
3390
  "scaffold",
3272
3391
  // arbitrary file generation outside project root
3273
3392
  "patch",
@@ -3277,12 +3396,22 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
3277
3396
  "exec"
3278
3397
  // restricted shell but with arbitrary command args
3279
3398
  ]);
3399
+ /**
3400
+ * Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
3401
+ * unknown capability — they may wrap a shell or filesystem. They are
3402
+ * fail-closed here: not auto-approved for subagents by default, so the
3403
+ * leader must allow them explicitly per-spawn.
3404
+ */
3405
+ static isMcpTool(name) {
3406
+ return name.startsWith("mcp__");
3407
+ }
3280
3408
  async evaluate(tool) {
3281
- if (tool.permission === "deny" || _AutoApprovePermissionPolicy.DENY.has(tool.name)) {
3409
+ const blocked = _AutoApprovePermissionPolicy.DENY.has(tool.name) || _AutoApprovePermissionPolicy.isMcpTool(tool.name);
3410
+ if (tool.permission === "deny" || blocked) {
3282
3411
  return {
3283
3412
  permission: "deny",
3284
3413
  source: "subagent_guard",
3285
- reason: _AutoApprovePermissionPolicy.DENY.has(tool.name) ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
3414
+ reason: blocked ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
3286
3415
  };
3287
3416
  }
3288
3417
  return { permission: "auto", source: "yolo" };
@@ -3697,6 +3826,95 @@ function parseDescription(raw) {
3697
3826
  return { trigger, scope };
3698
3827
  }
3699
3828
 
3829
+ // src/utils/json-repair.ts
3830
+ function completePartialObject(s) {
3831
+ if (!s.trim().startsWith("{")) return s;
3832
+ if (tryParse(s).ok) return s;
3833
+ const stack = [];
3834
+ let inString = false;
3835
+ let escaped = false;
3836
+ let sawKey = false;
3837
+ let prevSig = "";
3838
+ let contentEnd = 0;
3839
+ let stringBraceDepth = 0;
3840
+ for (let i = 0; i < s.length; i++) {
3841
+ const ch = s[i];
3842
+ if (inString) {
3843
+ contentEnd = i + 1;
3844
+ if (escaped) {
3845
+ escaped = false;
3846
+ continue;
3847
+ }
3848
+ if (ch === "\\") {
3849
+ escaped = true;
3850
+ continue;
3851
+ }
3852
+ if (ch === '"') {
3853
+ inString = false;
3854
+ prevSig = '"';
3855
+ stringBraceDepth = 0;
3856
+ continue;
3857
+ }
3858
+ if (ch === "{") stringBraceDepth++;
3859
+ else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
3860
+ continue;
3861
+ }
3862
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
3863
+ contentEnd = i + 1;
3864
+ if (ch === '"') {
3865
+ inString = true;
3866
+ sawKey = true;
3867
+ stringBraceDepth = 0;
3868
+ prevSig = '"';
3869
+ } else if (ch === "{" || ch === "[") {
3870
+ stack.push(ch);
3871
+ prevSig = ch;
3872
+ } else if (ch === "}" || ch === "]") {
3873
+ stack.pop();
3874
+ prevSig = ch;
3875
+ } else {
3876
+ prevSig = ch;
3877
+ }
3878
+ }
3879
+ if (!sawKey && !inString) return s;
3880
+ let result = s.slice(0, contentEnd);
3881
+ if (inString) {
3882
+ if (escaped) {
3883
+ result = result.slice(0, -1);
3884
+ } else if (endsWithInvalidEscape(result)) {
3885
+ result = result.slice(0, -2);
3886
+ }
3887
+ if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
3888
+ result += '"';
3889
+ } else if (prevSig === ":") {
3890
+ result += "null";
3891
+ }
3892
+ for (let k = stack.length - 1; k >= 0; k--) {
3893
+ result += stack[k] === "{" ? "}" : "]";
3894
+ }
3895
+ if (!tryParse(result).ok) {
3896
+ const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
3897
+ if (tryParse(patched).ok) result = patched;
3898
+ }
3899
+ return result;
3900
+ }
3901
+ var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
3902
+ function endsWithInvalidEscape(str) {
3903
+ const last = str[str.length - 1];
3904
+ if (str[str.length - 2] !== "\\" || last === void 0) return false;
3905
+ if (VALID_ESCAPE.has(last)) return false;
3906
+ let backslashes = 0;
3907
+ for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
3908
+ return backslashes % 2 === 1;
3909
+ }
3910
+ function tryParse(s) {
3911
+ try {
3912
+ return { ok: true, value: JSON.parse(s) };
3913
+ } catch {
3914
+ return { ok: false };
3915
+ }
3916
+ }
3917
+
3700
3918
  // src/core/streaming-response-builder.ts
3701
3919
  function buildResponse(state) {
3702
3920
  const content = [];
@@ -3794,7 +4012,12 @@ function safeJsonOrRaw(s) {
3794
4012
  try {
3795
4013
  return JSON.parse(s);
3796
4014
  } catch {
3797
- return { _raw: s };
4015
+ const repaired = completePartialObject(s);
4016
+ try {
4017
+ return JSON.parse(repaired);
4018
+ } catch {
4019
+ return { _raw: repaired };
4020
+ }
3798
4021
  }
3799
4022
  }
3800
4023
  function handleToolUseStop(state, ev) {
@@ -4093,6 +4316,7 @@ var HybridCompactor = class {
4093
4316
  }
4094
4317
  async compact(ctx, opts = {}) {
4095
4318
  const beforeTokens = this.estimateMessages(ctx.messages);
4319
+ const beforeFull = this.estimateFullRequest(ctx);
4096
4320
  const reductions = [];
4097
4321
  const policy = readContextWindowPolicy(ctx);
4098
4322
  const preserveK = policy?.preserveK ?? this.preserveK;
@@ -4108,9 +4332,12 @@ var HybridCompactor = class {
4108
4332
  ctx.state.replaceMessages(repaired.messages);
4109
4333
  }
4110
4334
  const afterTokens = this.estimateMessages(ctx.messages);
4335
+ const afterFull = this.estimateFullRequest(ctx);
4111
4336
  return {
4112
4337
  before: beforeTokens,
4113
4338
  after: afterTokens,
4339
+ fullRequestTokensBefore: beforeFull,
4340
+ fullRequestTokensAfter: afterFull,
4114
4341
  reductions,
4115
4342
  repaired: repaired.report.changed ? {
4116
4343
  removedToolUses: repaired.report.removedToolUses,
@@ -4119,6 +4346,14 @@ var HybridCompactor = class {
4119
4346
  } : void 0
4120
4347
  };
4121
4348
  }
4349
+ /**
4350
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
4351
+ * This is the accurate figure for context-window pressure monitoring.
4352
+ */
4353
+ estimateFullRequest(ctx) {
4354
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
4355
+ return breakdown.total;
4356
+ }
4122
4357
  eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
4123
4358
  const messages = ctx.messages;
4124
4359
  let pairCount = 0;
@@ -4264,8 +4499,9 @@ var IntelligentCompactor = class {
4264
4499
  }
4265
4500
  async compact(ctx, opts = {}) {
4266
4501
  const beforeTokens = this.estimateTokens(ctx.messages);
4502
+ const beforeFull = this.estimateFullRequest(ctx);
4267
4503
  const reductions = [];
4268
- const load = beforeTokens / this.maxContext;
4504
+ const load = beforeFull / this.maxContext;
4269
4505
  const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
4270
4506
  const saved1 = this.eliseOldToolResults(ctx);
4271
4507
  if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
@@ -4279,9 +4515,12 @@ var IntelligentCompactor = class {
4279
4515
  const repaired = repairToolUseAdjacency(ctx.messages);
4280
4516
  if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
4281
4517
  const afterTokens = this.estimateTokens(ctx.messages);
4518
+ const afterFull = this.estimateFullRequest(ctx);
4282
4519
  return {
4283
4520
  before: beforeTokens,
4284
4521
  after: afterTokens,
4522
+ fullRequestTokensBefore: beforeFull,
4523
+ fullRequestTokensAfter: afterFull,
4285
4524
  reductions,
4286
4525
  repaired: repaired.report.changed ? {
4287
4526
  removedToolUses: repaired.report.removedToolUses,
@@ -4290,6 +4529,14 @@ var IntelligentCompactor = class {
4290
4529
  } : void 0
4291
4530
  };
4292
4531
  }
4532
+ /**
4533
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
4534
+ * This is the accurate figure for context-window pressure monitoring.
4535
+ */
4536
+ estimateFullRequest(ctx) {
4537
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
4538
+ return breakdown.total;
4539
+ }
4293
4540
  async summarizeAncientTurns(ctx) {
4294
4541
  const messages = ctx.messages;
4295
4542
  const cutoff = Math.max(0, messages.length - this.preserveK * 2);
@@ -4302,29 +4549,26 @@ var IntelligentCompactor = class {
4302
4549
  try {
4303
4550
  summaryText = await this.callSummarizer(toSummarize, ctx);
4304
4551
  } catch {
4305
- const toolNames = /* @__PURE__ */ new Set();
4306
- const filePaths = /* @__PURE__ */ new Set();
4307
- let userTurns = 0;
4308
- let assistantTurns = 0;
4552
+ const preservedMessages = [];
4309
4553
  for (const m of toSummarize) {
4310
- if (m.role === "user") userTurns++;
4311
- else if (m.role === "assistant") {
4312
- assistantTurns++;
4313
- if (Array.isArray(m.content)) {
4314
- for (const b of m.content) {
4315
- if (b.type === "tool_use") toolNames.add(b.name ?? "unknown");
4316
- }
4317
- }
4554
+ if (m.role === "system") continue;
4555
+ if (typeof m.content === "string") {
4556
+ preservedMessages.push(m);
4557
+ } else if (Array.isArray(m.content)) {
4558
+ const textParts = m.content.filter(isTextBlock);
4559
+ const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
4560
+ const next = {
4561
+ role: m.role,
4562
+ content: [
4563
+ ...textParts,
4564
+ ...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
4565
+ ]
4566
+ };
4567
+ preservedMessages.push(next);
4318
4568
  }
4319
- const text = typeof m.content === "string" ? m.content : "";
4320
- const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
4321
- for (const m_ of matches) filePaths.add(m_[0]);
4322
4569
  }
4323
- const parts = [`${toSummarize.length} turns (${userTurns} user, ${assistantTurns} assistant)`];
4324
- if (toolNames.size > 0) parts.push(`tools: ${[...toolNames].join(", ")}`);
4325
- if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
4326
- summaryText = parts.join(" | ");
4327
- if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
4570
+ summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
4571
+ if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
4328
4572
  }
4329
4573
  const summaryMsg = {
4330
4574
  role: "system",
@@ -4570,7 +4814,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
4570
4814
  const res = await this.provider.complete(req, { signal: ac.signal });
4571
4815
  const textBlocks = res.content.filter(isTextBlock);
4572
4816
  raw = textBlocks.map((b) => b.text).join("\n").trim();
4573
- } catch (err) {
4817
+ } catch (_err) {
4574
4818
  return this.fallbackSelect(messages, effectiveBudget);
4575
4819
  }
4576
4820
  return this.parseSelectorOutput(raw, messages.length);
@@ -4662,15 +4906,24 @@ var SelectiveCompactor = class {
4662
4906
  }
4663
4907
  async compact(ctx, opts = {}) {
4664
4908
  const beforeTokens = this.estimateTokens(ctx.messages);
4909
+ const beforeFull = this.estimateFullRequest(ctx);
4665
4910
  const reductions = [];
4666
- const load = beforeTokens / this.maxContext;
4911
+ const load = beforeFull / this.maxContext;
4667
4912
  const shouldCompact = load >= this.warnThreshold || opts.aggressive;
4668
4913
  if (!shouldCompact) {
4669
4914
  const saved = this.eliseOldToolResults(ctx);
4670
4915
  if (saved > 0) reductions.push({ phase: "elision", saved });
4671
4916
  const repair2 = this.repairProtocolAdjacency(ctx);
4672
4917
  const afterTokens2 = this.estimateTokens(ctx.messages);
4673
- return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
4918
+ const afterFull2 = this.estimateFullRequest(ctx);
4919
+ return {
4920
+ before: beforeTokens,
4921
+ after: afterTokens2,
4922
+ fullRequestTokensBefore: beforeFull,
4923
+ fullRequestTokensAfter: afterFull2,
4924
+ reductions,
4925
+ repaired: repair2
4926
+ };
4674
4927
  }
4675
4928
  const savedElision = this.eliseOldToolResults(ctx);
4676
4929
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
@@ -4682,7 +4935,23 @@ var SelectiveCompactor = class {
4682
4935
  }
4683
4936
  const repair = this.repairProtocolAdjacency(ctx);
4684
4937
  const afterTokens = this.estimateTokens(ctx.messages);
4685
- return { before: beforeTokens, after: afterTokens, reductions, repaired: repair };
4938
+ const afterFull = this.estimateFullRequest(ctx);
4939
+ return {
4940
+ before: beforeTokens,
4941
+ after: afterTokens,
4942
+ fullRequestTokensBefore: beforeFull,
4943
+ fullRequestTokensAfter: afterFull,
4944
+ reductions,
4945
+ repaired: repair
4946
+ };
4947
+ }
4948
+ /**
4949
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
4950
+ * This is the accurate figure for context-window pressure monitoring.
4951
+ */
4952
+ estimateFullRequest(ctx) {
4953
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
4954
+ return breakdown.total;
4686
4955
  }
4687
4956
  repairProtocolAdjacency(ctx) {
4688
4957
  const repaired = repairToolUseAdjacency(ctx.messages);
@@ -4872,7 +5141,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
4872
5141
  var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
4873
5142
  name = "AutoCompaction";
4874
5143
  compactor;
4875
- estimator;
5144
+ /** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
5145
+ _estimator;
4876
5146
  warnThreshold;
4877
5147
  softThreshold;
4878
5148
  hardThreshold;
@@ -4882,19 +5152,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
4882
5152
  events;
4883
5153
  failureMode;
4884
5154
  policyProvider;
4885
- /**
4886
- * Calibration factor applied to the estimator output. The estimator is now
4887
- * expected to already include messages + system prompt + tool definitions
4888
- * (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
4889
- * as a constant so a future calibration against real provider tokenization
4890
- * (BPE vs the chars/3.5 rough estimator) can dial this without touching the
4891
- * threshold-check math.
4892
- *
4893
- * Historical note: was 1.3 when the estimator only counted messages — that
4894
- * double-counted system+tools once the estimator was upgraded, firing
4895
- * compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
4896
- */
4897
- static OVERHEAD_FACTOR = 1;
4898
5155
  /**
4899
5156
  * Once a compaction attempt reduces nothing (preserveK protects everything,
4900
5157
  * no oversized tool_results remain to elide), retrying on every iteration
@@ -4907,8 +5164,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
4907
5164
  lastNoopAttempt = null;
4908
5165
  /**
4909
5166
  * @param compactor Compactor to use for compaction.
4910
- * @param maxContext Provider's max context window in tokens.
4911
- * @param estimator Token estimation function.
5167
+ * @param maxContext Provider's max context window in tokens.
5168
+ * @param _estimator Deprecated parameter kept for backward compatibility.
5169
+ * The middleware now uses `estimateRequestTokens` internally
5170
+ * for accurate full-request token counting (messages +
5171
+ * systemPrompt + toolDefs).
4912
5172
  * @param thresholds Threshold fractions (0-1) of maxContext.
4913
5173
  * @param opts Optional behavior. By default, failures at the
4914
5174
  * hard threshold throw AGENT_CONTEXT_OVERFLOW so
@@ -4916,11 +5176,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
4916
5176
  * provider context overflow. Warn/soft failures
4917
5177
  * still emit compaction.failed and continue.
4918
5178
  */
4919
- constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
5179
+ constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
4920
5180
  const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
4921
5181
  this.compactor = compactor;
4922
5182
  this._maxContext = maxContext;
4923
- this.estimator = estimator;
5183
+ this._estimator = _estimator;
4924
5184
  this.warnThreshold = thresholds.warn;
4925
5185
  this.softThreshold = thresholds.soft;
4926
5186
  this.hardThreshold = thresholds.hard;
@@ -4936,8 +5196,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
4936
5196
  }
4937
5197
  handler() {
4938
5198
  return async (ctx, next) => {
4939
- const rawTokens = this.estimator(ctx);
4940
- const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
5199
+ const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
4941
5200
  const load = tokens / this._maxContext;
4942
5201
  const policy = this.policyProvider?.(ctx);
4943
5202
  const thresholds = policy?.thresholds ?? {
@@ -4972,7 +5231,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
4972
5231
  return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
4973
5232
  }
4974
5233
  recordAttempt(level, tokens, report) {
4975
- const reduced = report.before > report.after;
5234
+ const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
4976
5235
  const repaired = !!report.repaired;
4977
5236
  if (reduced || repaired) {
4978
5237
  this.lastNoopAttempt = null;
@@ -6217,17 +6476,7 @@ var SubagentBudget = class _SubagentBudget {
6217
6476
  startTime = null;
6218
6477
  _onThreshold;
6219
6478
  /**
6220
- * Tracks which budget kinds currently have an extension request
6221
- * in flight. While a kind is here, further `checkLimit` calls for the
6222
- * same kind are no-ops — without this dedup, every `recordIteration`
6223
- * after the limit is reached spawns a fresh decision Promise (until
6224
- * the first one lands and patches limits), flooding the FleetBus
6225
- * with redundant threshold events. Cleared in `negotiateExtension`'s
6226
- * `finally`.
6227
- */
6228
- pendingExtensions = /* @__PURE__ */ new Set();
6229
- /**
6230
- * Hard cap on how long `negotiateExtension` waits for the coordinator to
6479
+ * Hard cap on how long `_negotiateExtension` waits for the coordinator to
6231
6480
  * respond before defaulting to 'stop'. Without this fallback an absent
6232
6481
  * or hung listener (Director not built / event filter detached mid-run)
6233
6482
  * leaves the budget over-limit and never enforces anything.
@@ -6289,38 +6538,83 @@ var SubagentBudget = class _SubagentBudget {
6289
6538
  * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
6290
6539
  * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
6291
6540
  */
6292
- checkLimit(kind, used, limit) {
6541
+ /**
6542
+ * Collects all exceeded budget kinds into a single NOOP-free negotiation.
6543
+ * Called by recordIteration / recordToolCall / recordUsage — each may call
6544
+ * this for its own kind. The first call starts the negotiation and stores
6545
+ * the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
6546
+ * kinds (while a negotiation is in flight) are NOOPs — they don't start
6547
+ * new conversations with the coordinator. This prevents an EventBus flood
6548
+ * when multiple budget kinds are exceeded simultaneously in one iteration.
6549
+ *
6550
+ * Returns the kinds that were found to be exceeded (for logging/debugging).
6551
+ */
6552
+ checkLimits(elapsedMs) {
6553
+ const exceeded = [];
6554
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
6555
+ exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
6556
+ }
6557
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
6558
+ exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
6559
+ }
6560
+ const totalTokens = this.tokenInput + this.tokenOutput;
6561
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
6562
+ exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
6563
+ }
6564
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6565
+ exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
6566
+ }
6567
+ if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
6568
+ exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
6569
+ }
6570
+ if (exceeded.length === 0) return [];
6293
6571
  if (!this._onThreshold) {
6294
- throw new BudgetExceededError(kind, limit, used);
6572
+ const first2 = exceeded[0];
6573
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
6295
6574
  }
6296
6575
  if (this._mode === "sync") {
6297
- throw new BudgetExceededError(kind, limit, used);
6576
+ const first2 = exceeded[0];
6577
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
6298
6578
  }
6299
6579
  const bus = this._events;
6300
6580
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
6301
- throw new BudgetExceededError(kind, limit, used);
6581
+ const first2 = exceeded[0];
6582
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
6583
+ }
6584
+ for (const entry of exceeded) {
6585
+ if (this._pendingNegotiations.has(entry.kind)) continue;
6586
+ const decision2 = this._negotiateExtension(entry.kind, exceeded);
6587
+ this._pendingNegotiations.set(entry.kind, decision2);
6302
6588
  }
6303
- if (this.pendingExtensions.has(kind)) return;
6304
- this.pendingExtensions.add(kind);
6305
- const decision = this.negotiateExtension(kind, used, limit);
6306
- throw new BudgetThresholdSignal(kind, limit, used, decision);
6589
+ const first = exceeded[0];
6590
+ const decision = this._pendingNegotiations.get(first.kind);
6591
+ throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
6307
6592
  }
6593
+ /**
6594
+ * Per-kind in-flight negotiation Promises. Each budget kind can have its
6595
+ * own concurrent negotiation — e.g. iterations and timeout can both
6596
+ * be exceeded simultaneously without blocking each other. The same kind
6597
+ * cannot start two concurrent negotiations (dedup guard).
6598
+ * Cleared in `_negotiateExtension`'s `finally` block.
6599
+ */
6600
+ _pendingNegotiations = /* @__PURE__ */ new Map();
6308
6601
  /**
6309
6602
  * Drive the threshold handler to a decision. Resolves with `'stop'`
6310
6603
  * (signal the runner to abort) or `{ extend: ... }` (limits already
6311
- * patched in-place; the runner should not abort). Always releases the
6312
- * `pendingExtensions` slot in `finally`.
6604
+ * patched in-place; the runner should not abort). Clears the
6605
+ * per-kind slot in `_pendingNegotiations` in `finally`.
6313
6606
  *
6314
6607
  * The 'continue' return from a sync handler is treated as
6315
6608
  * `{ extend: {} }` — keep going without patching; next overrun fires
6316
6609
  * a fresh signal.
6317
6610
  */
6318
- async negotiateExtension(kind, used, limit) {
6611
+ async _negotiateExtension(kind, exceeded) {
6319
6612
  try {
6613
+ const first = exceeded[0];
6320
6614
  const result = this._onThreshold({
6321
- kind,
6322
- used,
6323
- limit,
6615
+ kind: first.kind,
6616
+ used: first.used,
6617
+ limit: first.limit,
6324
6618
  requestDecision: () => {
6325
6619
  const bus = this._events;
6326
6620
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
@@ -6337,20 +6631,22 @@ var SubagentBudget = class _SubagentBudget {
6337
6631
  () => respond("stop"),
6338
6632
  _SubagentBudget.DECISION_TIMEOUT_MS
6339
6633
  );
6340
- bus.emit("budget.threshold_reached", {
6341
- kind,
6342
- used,
6343
- limit,
6344
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
6345
- extend: (extra) => {
6346
- clearTimeout(fallback);
6347
- respond({ extend: extra });
6348
- },
6349
- deny: () => {
6350
- clearTimeout(fallback);
6351
- respond("stop");
6352
- }
6353
- });
6634
+ for (const { kind: kind2, used, limit } of exceeded) {
6635
+ bus.emit("budget.threshold_reached", {
6636
+ kind: kind2,
6637
+ used,
6638
+ limit,
6639
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
6640
+ extend: (extra) => {
6641
+ clearTimeout(fallback);
6642
+ respond({ extend: extra });
6643
+ },
6644
+ deny: () => {
6645
+ clearTimeout(fallback);
6646
+ respond("stop");
6647
+ }
6648
+ });
6649
+ }
6354
6650
  });
6355
6651
  }
6356
6652
  });
@@ -6376,47 +6672,39 @@ var SubagentBudget = class _SubagentBudget {
6376
6672
  }
6377
6673
  return decision;
6378
6674
  } finally {
6379
- this.pendingExtensions.delete(kind);
6675
+ this._pendingNegotiations.delete(kind);
6380
6676
  }
6381
6677
  }
6382
6678
  recordIteration() {
6383
6679
  this.iterations++;
6384
- if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
6385
- void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
6386
- }
6680
+ void this.checkLimits();
6387
6681
  }
6388
6682
  recordToolCall() {
6389
6683
  this.toolCalls++;
6390
- if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
6391
- void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
6392
- }
6684
+ void this.checkLimits();
6393
6685
  }
6394
6686
  recordUsage(usage, costUsd = 0) {
6395
6687
  this.tokenInput += usage.input;
6396
6688
  this.tokenOutput += usage.output;
6397
6689
  this.costUsd += costUsd;
6398
- const totalTokens = this.tokenInput + this.tokenOutput;
6399
- if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
6400
- void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
6401
- }
6402
- if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6403
- void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
6404
- }
6690
+ void this.checkLimits();
6405
6691
  }
6406
6692
  /**
6407
- * Wall-clock budget check. Unlike other limits, timeout check passes through
6408
- * `checkLimit` and is subject to the same negotiation-mode decision table.
6409
- * In practice, `'sync'` mode (the usual test configuration) means a timeout
6410
- * immediately throws `BudgetExceededError`. In production with a coordinator,
6411
- * a timeout emits `budget.threshold_reached` so the Director can decide whether
6412
- * to extend or abort.
6693
+ * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
6694
+ * wall-clock time cannot be "extended" by the coordinator, so it throws
6695
+ * synchronously rather than entering the negotiation flow.
6696
+ *
6697
+ * Decision table:
6698
+ * - no `onThreshold` handler → throw `BudgetExceededError`
6699
+ * - `mode === 'sync'` → throw `BudgetExceededError`
6700
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
6701
+ * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
6413
6702
  */
6414
6703
  checkTimeout() {
6415
6704
  if (this.startTime === null || this.limits.timeoutMs === void 0) return;
6416
6705
  const elapsed = Date.now() - this.startTime;
6417
- if (elapsed > this.limits.timeoutMs) {
6418
- void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
6419
- }
6706
+ if (elapsed <= this.limits.timeoutMs) return;
6707
+ void this.checkLimits(elapsed);
6420
6708
  }
6421
6709
  /** Returns true if a timeout has occurred without throwing. Useful for races. */
6422
6710
  isTimedOut() {
@@ -9069,11 +9357,46 @@ Working rules:
9069
9357
  - When in doubt, flag as medium rather than ignoring potential issues`
9070
9358
  // Budgets are set by the orchestrator per task — see fleet.ts header.
9071
9359
  };
9360
+ var CRITIC_AGENT = {
9361
+ id: "critic",
9362
+ name: "Critic",
9363
+ role: "critic",
9364
+ prompt: `You are the Critic agent. Your job is to evaluate code quality,
9365
+ architectural decisions, and proposed changes against project conventions,
9366
+ engineering standards, and known quality gates. You do not write code \u2014
9367
+ you judge it.
9368
+
9369
+ Scope:
9370
+ - Evaluate bug severity and fix quality from Bug Hunter reports
9371
+ - Score refactoring plans from Refactor Planner (risk, completeness, trade-offs)
9372
+ - Flag gaps in test coverage, error handling, and edge case coverage
9373
+ - Assess whether a proposed change aligns with existing project patterns
9374
+ - Detect over-engineering or under-engineering relative to the problem scope
9375
+
9376
+ Input format you accept:
9377
+ { "task": "evaluate | score | review", "subject": "bug_report | refactor_plan | diff", "focus": "correctness | maintainability | risk | all" }
9378
+
9379
+ Output: Markdown critic report:
9380
+ - ## Overall Score (0-10 with rationale)
9381
+ - ## Strengths (what's solid)
9382
+ - ## Weaknesses (what needs work)
9383
+ - ## Specific Concerns (with file:line when applicable)
9384
+ - ## Verdict: **Approve / Needs Revision / Reject**
9385
+
9386
+ Working rules:
9387
+ - Be specific \u2014 "looks fine" is not a review. Cite concrete evidence.
9388
+ - When scoring, explain the delta from a perfect score.
9389
+ - If you have no basis to evaluate a concern, say so rather than speculating.
9390
+ - Prioritise correctness over style; correctness issues block approval.
9391
+ - Score thresholds: \u22657 = Approve, 4-6 = Needs Revision, <4 = Reject`
9392
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
9393
+ };
9072
9394
  var FLEET_ROSTER = {
9073
9395
  "audit-log": AUDIT_LOG_AGENT,
9074
9396
  "bug-hunter": BUG_HUNTER_AGENT,
9075
9397
  "refactor-planner": REFACTOR_PLANNER_AGENT,
9076
9398
  "security-scanner": SECURITY_SCANNER_AGENT,
9399
+ "critic": CRITIC_AGENT,
9077
9400
  ...Object.fromEntries(
9078
9401
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
9079
9402
  )
@@ -9083,6 +9406,7 @@ var FLEET_ROSTER_BUDGETS = {
9083
9406
  "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
9084
9407
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
9085
9408
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
9409
+ "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
9086
9410
  ...Object.fromEntries(
9087
9411
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
9088
9412
  )
@@ -9168,6 +9492,107 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
9168
9492
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
9169
9493
  });
9170
9494
 
9495
+ // src/coordination/subagent-nicknames.ts
9496
+ var NICKNAME_POOL = {
9497
+ // Physics & fundamental sciences
9498
+ "einstein": { name: "Einstein", domain: "physics" },
9499
+ "newton": { name: "Newton", domain: "physics" },
9500
+ "feynman": { name: "Feynman", domain: "physics" },
9501
+ "dirac": { name: "Dirac", domain: "physics" },
9502
+ "bohr": { name: "Bohr", domain: "physics" },
9503
+ "planck": { name: "Planck", domain: "physics" },
9504
+ "curie": { name: "Curie", domain: "physics" },
9505
+ "fermi": { name: "Fermi", domain: "physics" },
9506
+ "heisenberg": { name: "Heisenberg", domain: "physics" },
9507
+ "schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
9508
+ // Mathematics
9509
+ "euclid": { name: "Euclid", domain: "math" },
9510
+ "gauss": { name: "Gauss", domain: "math" },
9511
+ "turing": { name: "Turing", domain: "math" },
9512
+ "poincare": { name: "Poincar\xE9", domain: "math" },
9513
+ "riemann": { name: "Riemann", domain: "math" },
9514
+ "hilbert": { name: "Hilbert", domain: "math" },
9515
+ "pythagoras": { name: "Pythagoras", domain: "math" },
9516
+ // Computing & information theory
9517
+ "von-neumann": { name: "Von Neumann", domain: "computing" },
9518
+ "shannon": { name: "Shannon", domain: "computing" },
9519
+ "hopper": { name: "Hopper", domain: "computing" },
9520
+ "backus": { name: "Backus", domain: "computing" },
9521
+ "knuth": { name: "Knuth", domain: "computing" },
9522
+ "torvalds": { name: "Torvalds", domain: "computing" },
9523
+ "stallman": { name: "Stallman", domain: "computing" },
9524
+ "berners-lee": { name: "Berners-Lee", domain: "computing" },
9525
+ "babbage": { name: "Babbage", domain: "computing" },
9526
+ "lovelace": { name: "Lovelace", domain: "computing" },
9527
+ "klein": { name: "Klein", domain: "computing" },
9528
+ // Electronics & electrical engineering
9529
+ "edison": { name: "Edison", domain: "ee" },
9530
+ "tesla": { name: "Tesla", domain: "ee" },
9531
+ "faraday": { name: "Faraday", domain: "ee" },
9532
+ "maxwell": { name: "Maxwell", domain: "ee" },
9533
+ "ohm": { name: "Ohm", domain: "ee" },
9534
+ "bell": { name: "Bell", domain: "ee" },
9535
+ "marconi": { name: "Marconi", domain: "ee" },
9536
+ "lamarr": { name: "Lamarr", domain: "ee" },
9537
+ // General science / multi-disciplinary
9538
+ "darwin": { name: "Darwin", domain: "biology" },
9539
+ "mendel": { name: "Mendel", domain: "biology" },
9540
+ "pasteur": { name: "Pasteur", domain: "biology" },
9541
+ "hawking": { name: "Hawking", domain: "cosmology" },
9542
+ "sagan": { name: "Sagan", domain: "cosmology" },
9543
+ // Chemistry / materials
9544
+ "lavoisier": { name: "Lavoisier", domain: "chemistry" },
9545
+ "mendeleev": { name: "Mendeleev", domain: "chemistry" }
9546
+ };
9547
+ var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
9548
+ var DOMAIN_PREFERENCES = {
9549
+ "security": ["shannon", "turing", "lamarr", "stallman"],
9550
+ "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
9551
+ "refactor": ["gauss", "hilbert", "euclid", "planck"],
9552
+ "audit-log": ["sagan", "hawking", "poincare", "newton"],
9553
+ "planner": ["hilbert", "gauss", "turing", "euclid"],
9554
+ "researcher": ["sagan", "hawking", "darwin", "pasteur"],
9555
+ "explorer": ["marconi", "bell", "columbus", "polo"],
9556
+ "testing": ["pasteur", "curie", "fermi", "bohr"],
9557
+ "frontend": ["lovelace", "hopper", "babbage", "backus"],
9558
+ "backend": ["torvalds", "stallman", "von-neumann", "backus"],
9559
+ "database": ["turing", "shannon", "backus", "knuth"],
9560
+ "devops": ["tesla", "edison", "faraday", "bell"],
9561
+ "security-scanner": ["shannon", "turing", "lamarr", "stallman"],
9562
+ "refactor-planner": ["gauss", "hilbert", "planck", "newton"],
9563
+ "architect": ["von-neumann", "turing", "gauss", "hilbert"],
9564
+ "critic": ["einstein", "feynman", "dirac", "bohr"],
9565
+ "e2e": ["hopper", "bell", "marconi", "tesla"],
9566
+ "performance": ["knuth", "gauss", "planck", "feynman"],
9567
+ "chaos": ["tesla", "edison", "curie", "fermi"],
9568
+ "cost": ["ohm", "bell", "marconi", "tesla"],
9569
+ // default fallback
9570
+ "default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
9571
+ };
9572
+ function assignNickname(role, used) {
9573
+ const preferences = [
9574
+ ...DOMAIN_PREFERENCES[role] ?? [],
9575
+ ...DOMAIN_PREFERENCES["default"] ?? []
9576
+ ];
9577
+ for (const key of preferences) {
9578
+ const entry = NICKNAME_POOL[key];
9579
+ if (entry && !used.has(key)) {
9580
+ return `${entry.name} (${formatRole(role)})`;
9581
+ }
9582
+ }
9583
+ for (const entry of ALL_NICKNAMES) {
9584
+ const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
9585
+ if (key && !used.has(key)) {
9586
+ return `${entry.name} (${formatRole(role)})`;
9587
+ }
9588
+ }
9589
+ const counter = used.size + 1;
9590
+ return `Scientist #${counter} (${formatRole(role)})`;
9591
+ }
9592
+ function formatRole(role) {
9593
+ return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
9594
+ }
9595
+
9171
9596
  // src/coordination/multi-agent-coordinator.ts
9172
9597
  var DefaultMultiAgentCoordinator = class extends EventEmitter {
9173
9598
  coordinatorId;
@@ -9175,6 +9600,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9175
9600
  runner;
9176
9601
  fleetBus;
9177
9602
  subagents = /* @__PURE__ */ new Map();
9603
+ /**
9604
+ * Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
9605
+ * Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
9606
+ * (parallel/eternal engine, SDD parallel run) don't go through
9607
+ * `Director.spawn()` where nicknames are normally assigned, so the
9608
+ * coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
9609
+ * names) to memorable ones here — that's what surfaces in the fleet monitor.
9610
+ */
9611
+ usedNicknames = /* @__PURE__ */ new Set();
9178
9612
  pendingTasks = [];
9179
9613
  completedResults = [];
9180
9614
  totalIterations = 0;
@@ -9223,7 +9657,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9223
9657
  this.config.maxConcurrent = Math.floor(n);
9224
9658
  this.tryDispatchNext();
9225
9659
  }
9660
+ /**
9661
+ * Upgrade a placeholder/role-derived name to a memorable scientist nickname
9662
+ * (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
9663
+ * empty, equals the role (case-insensitive), is a generic default
9664
+ * ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
9665
+ * Explicit, human-chosen names — including nicknames already assigned by
9666
+ * `Director.spawn()` — are left untouched, so this never double-assigns.
9667
+ */
9668
+ withNickname(subagent) {
9669
+ const role = subagent.role ?? "subagent";
9670
+ const name = subagent.name?.trim() ?? "";
9671
+ const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
9672
+ if (!isPlaceholder) return subagent;
9673
+ const nickname = assignNickname(role, this.usedNicknames);
9674
+ const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
9675
+ this.usedNicknames.add(baseKey);
9676
+ return { ...subagent, name: nickname };
9677
+ }
9226
9678
  async spawn(subagent) {
9679
+ subagent = this.withNickname(subagent);
9227
9680
  const id = subagent.id || randomUUID();
9228
9681
  if (this.subagents.has(id)) {
9229
9682
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -9817,12 +10270,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
9817
10270
  var ParallelEternalEngine = class {
9818
10271
  constructor(opts) {
9819
10272
  this.opts = opts;
9820
- this.goalPath = goalFilePath(opts.projectRoot);
10273
+ this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
9821
10274
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
9822
10275
  this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
9823
10276
  this.dispatchEnabled = opts.dispatch !== false;
9824
10277
  this.dispatchClassifier = opts.dispatchClassifier;
9825
- this.agentFactory = opts.subagentFactory ?? (async (config) => ({
10278
+ this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
9826
10279
  agent: this.opts.agent,
9827
10280
  events: this.opts.agent.events
9828
10281
  }));
@@ -9916,7 +10369,6 @@ var ParallelEternalEngine = class {
9916
10369
  }
9917
10370
  const tasks = await this.decomposeGoal(goal);
9918
10371
  if (!tasks || tasks.length === 0) {
9919
- await sleep2(5e3);
9920
10372
  return false;
9921
10373
  }
9922
10374
  const fanOut = await this.fanOut(goal, tasks);
@@ -9937,6 +10389,7 @@ var ParallelEternalEngine = class {
9937
10389
  });
9938
10390
  if (fanOut.goalComplete) {
9939
10391
  this.stopRequested = true;
10392
+ this.state = "stopped";
9940
10393
  return true;
9941
10394
  }
9942
10395
  await this.maybeCompact();
@@ -10117,7 +10570,11 @@ ${lastFew}` : "No prior iterations.",
10117
10570
  } finally {
10118
10571
  clearTimeout(timer);
10119
10572
  }
10120
- } catch {
10573
+ } catch (err) {
10574
+ this.opts.onError?.(
10575
+ err instanceof Error ? err : new Error(String(err)),
10576
+ this.consecutiveFailures
10577
+ );
10121
10578
  return [];
10122
10579
  }
10123
10580
  }
@@ -10478,7 +10935,11 @@ Working rules:
10478
10935
  thrashing, terminate it rather than letting cost climb silently.
10479
10936
  6. Never claim a subagent's work as your own without verifying it. If a
10480
10937
  result looks wrong, ask_subagent for clarification before passing it
10481
- to the user.`;
10938
+ to the user.
10939
+ 7. Wind down when satisfied. When the results are good enough, call
10940
+ work_complete \u2014 no new subagents will spawn and queued tasks complete
10941
+ as aborted. Running subagents finish naturally. Call terminate_subagent
10942
+ only for ones you need to stop immediately.`;
10482
10943
  var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
10483
10944
  a specific slice of a larger plan \u2014 do that slice well and report back.
10484
10945
 
@@ -10623,14 +11084,12 @@ var FleetBus = class {
10623
11084
  };
10624
11085
  var FleetUsageAggregator = class {
10625
11086
  constructor(bus, priceLookup, metaLookup) {
10626
- this.bus = bus;
10627
11087
  this.priceLookup = priceLookup;
10628
11088
  this.metaLookup = metaLookup;
10629
11089
  bus.filter("provider.response", (e) => this.onProviderResponse(e));
10630
11090
  bus.filter("tool.executed", (e) => this.onToolExecuted(e));
10631
11091
  bus.filter("iteration.started", (e) => this.onIterationStarted(e));
10632
11092
  }
10633
- bus;
10634
11093
  priceLookup;
10635
11094
  metaLookup;
10636
11095
  perSubagent = /* @__PURE__ */ new Map();
@@ -10868,7 +11327,7 @@ function makeRollUpTool(director) {
10868
11327
  function makeTerminateTool(director) {
10869
11328
  return {
10870
11329
  name: "terminate_subagent",
10871
- description: "Forcibly abort a subagent.",
11330
+ description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
10872
11331
  permission: "auto",
10873
11332
  mutating: true,
10874
11333
  inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
@@ -10879,6 +11338,19 @@ function makeTerminateTool(director) {
10879
11338
  }
10880
11339
  };
10881
11340
  }
11341
+ function makeTerminateAllTool(director) {
11342
+ return {
11343
+ name: "terminate_all",
11344
+ 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.',
11345
+ permission: "auto",
11346
+ mutating: true,
11347
+ inputSchema: { type: "object", properties: {}, required: [] },
11348
+ async execute() {
11349
+ await director.terminateAll();
11350
+ return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
11351
+ }
11352
+ };
11353
+ }
10882
11354
  function makeFleetStatusTool(director) {
10883
11355
  return {
10884
11356
  name: "fleet_status",
@@ -10922,7 +11394,6 @@ function makeFleetSessionTool(director) {
10922
11394
  type: "object",
10923
11395
  properties: {
10924
11396
  subagentId: { type: "string", description: "Subagent id to read the transcript of." },
10925
- /** Number of trailing lines to return (last N JSONL lines). Default: all. */
10926
11397
  tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
10927
11398
  },
10928
11399
  required: ["subagentId"]
@@ -10958,8 +11429,6 @@ function makeFleetHealthTool(director) {
10958
11429
  id: s.id,
10959
11430
  status: s.status,
10960
11431
  lastEventAt: usage?.lastEventAt,
10961
- // Budget pressure: fraction of each limit consumed if we have it.
10962
- // BudgetWarning events carry used/limit ratios; surface them here.
10963
11432
  budgetPressure: {
10964
11433
  iterations: usage?.iterations,
10965
11434
  toolCalls: usage?.toolCalls,
@@ -10971,16 +11440,612 @@ function makeFleetHealthTool(director) {
10971
11440
  }
10972
11441
  };
10973
11442
  }
11443
+ function makeCollabDebugTool(director) {
11444
+ return {
11445
+ name: "collab_debug",
11446
+ 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).",
11447
+ permission: "auto",
11448
+ mutating: false,
11449
+ inputSchema: {
11450
+ type: "object",
11451
+ properties: {
11452
+ targetPaths: {
11453
+ type: "array",
11454
+ items: { type: "string" },
11455
+ description: "File paths / glob patterns to scan for bugs."
11456
+ },
11457
+ timeoutMs: {
11458
+ type: "number",
11459
+ description: "Timeout in ms. Default: 600000 (10 minutes)."
11460
+ }
11461
+ },
11462
+ required: ["targetPaths"]
11463
+ },
11464
+ async execute(input) {
11465
+ const i = input;
11466
+ if (!i.targetPaths?.length) {
11467
+ return { error: "collab_debug: targetPaths is required and must be non-empty." };
11468
+ }
11469
+ const options = {
11470
+ targetPaths: i.targetPaths,
11471
+ timeoutMs: i.timeoutMs
11472
+ };
11473
+ try {
11474
+ const report = await director.spawnCollab(options);
11475
+ return {
11476
+ sessionId: report.sessionId,
11477
+ overallVerdict: report.overallVerdict,
11478
+ bugCount: report.bugs.length,
11479
+ planCount: report.refactorPlans.length,
11480
+ evaluationCount: report.evaluations.length,
11481
+ summary: report.summary,
11482
+ bugs: report.bugs,
11483
+ refactorPlans: report.refactorPlans,
11484
+ evaluations: report.evaluations
11485
+ };
11486
+ } catch (err) {
11487
+ const msg = err instanceof Error ? err.message : String(err);
11488
+ return { error: "collab_debug failed: " + msg };
11489
+ }
11490
+ }
11491
+ };
11492
+ }
11493
+ function makeFleetEmitTool(director) {
11494
+ return {
11495
+ name: "fleet_emit",
11496
+ 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.",
11497
+ permission: "auto",
11498
+ mutating: false,
11499
+ inputSchema: {
11500
+ type: "object",
11501
+ properties: {
11502
+ type: {
11503
+ type: "string",
11504
+ description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
11505
+ },
11506
+ payload: {
11507
+ type: "object",
11508
+ description: "Event payload. Structure depends on event type. Use null if no payload."
11509
+ }
11510
+ },
11511
+ required: ["type"]
11512
+ },
11513
+ async execute(input) {
11514
+ const i = input;
11515
+ director.fleet.emit({
11516
+ subagentId: director.id,
11517
+ ts: Date.now(),
11518
+ type: i.type,
11519
+ payload: i.payload ?? {}
11520
+ });
11521
+ return { ok: true, event: i.type };
11522
+ }
11523
+ };
11524
+ }
11525
+ function makeWorkCompleteTool(director) {
11526
+ return {
11527
+ name: "work_complete",
11528
+ 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.",
11529
+ permission: "auto",
11530
+ mutating: false,
11531
+ inputSchema: { type: "object", properties: {}, required: [] },
11532
+ async execute() {
11533
+ director.workComplete();
11534
+ return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
11535
+ }
11536
+ };
11537
+ }
11538
+ var CollabSession = class extends EventEmitter {
11539
+ sessionId;
11540
+ options;
11541
+ snapshot;
11542
+ director;
11543
+ fleetBus;
11544
+ subagentIds = /* @__PURE__ */ new Map();
11545
+ // role → subagentId
11546
+ bugs = /* @__PURE__ */ new Map();
11547
+ plans = /* @__PURE__ */ new Map();
11548
+ evaluations = /* @__PURE__ */ new Map();
11549
+ disposers = new Array();
11550
+ settled = false;
11551
+ timeoutMs;
11552
+ cancelled = false;
11553
+ alerts = [];
11554
+ /** Tracks tool call counts per subagent for progress-based timeout decisions. */
11555
+ progressBySubagent = /* @__PURE__ */ new Map();
11556
+ /** Last tool call count when a timeout warning was handled. */
11557
+ lastTimeoutProgress = /* @__PURE__ */ new Map();
11558
+ /** Session-level timeout timer handle (cleared on cancel or natural completion). */
11559
+ _timeoutTimer;
11560
+ constructor(director, fleetBus, options) {
11561
+ super();
11562
+ this.sessionId = randomUUID();
11563
+ this.options = options;
11564
+ this.director = director;
11565
+ this.fleetBus = fleetBus;
11566
+ this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
11567
+ if (options.prebuiltSnapshot) {
11568
+ this.snapshot = options.prebuiltSnapshot;
11569
+ } else {
11570
+ this.snapshot = {
11571
+ id: this.sessionId,
11572
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11573
+ files: []
11574
+ };
11575
+ }
11576
+ }
11577
+ get id() {
11578
+ return this.sessionId;
11579
+ }
11580
+ getSessionAlerts() {
11581
+ return [...this.alerts];
11582
+ }
11583
+ isCancelled() {
11584
+ return this.cancelled;
11585
+ }
11586
+ /**
11587
+ * Snapshot of role → subagentId map. The Director calls coordinator.stop()
11588
+ * for each agent when cancelling the session, using this map to enumerate
11589
+ * all three collab agents.
11590
+ */
11591
+ getSubagentIds() {
11592
+ return new Map(this.subagentIds);
11593
+ }
11594
+ async buildSnapshot() {
11595
+ if (this.snapshot.files.length > 0) return this.snapshot;
11596
+ for (const filePath of this.options.targetPaths) {
11597
+ try {
11598
+ const content = await fsp.readFile(filePath, "utf8");
11599
+ const ext = filePath.split(".").pop() ?? "";
11600
+ const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
11601
+ this.snapshot.files.push({ path: filePath, content, language });
11602
+ } catch {
11603
+ this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
11604
+ }
11605
+ }
11606
+ return this.snapshot;
11607
+ }
11608
+ /**
11609
+ * Cancel the session. Emits director.cancel_collab on the FleetBus so all
11610
+ * collab agents finish early. The session-level timeout timer is also cleared.
11611
+ * Safe to call multiple times (idempotent after first call).
11612
+ */
11613
+ cancel(reason = "Director cancelled collab session") {
11614
+ if (this.settled) return;
11615
+ this.cancelled = true;
11616
+ if (this._timeoutTimer) {
11617
+ clearTimeout(this._timeoutTimer);
11618
+ this._timeoutTimer = void 0;
11619
+ }
11620
+ this.fleetBus.emit({
11621
+ subagentId: this.director.id,
11622
+ ts: Date.now(),
11623
+ type: "director.cancel_collab",
11624
+ payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
11625
+ });
11626
+ this.fleetBus.emit({
11627
+ subagentId: this.director.id,
11628
+ ts: Date.now(),
11629
+ type: "collab.cancelled",
11630
+ payload: { sessionId: this.sessionId, reason }
11631
+ });
11632
+ }
11633
+ async start() {
11634
+ if (this.settled) throw new Error("session already settled");
11635
+ this.settled = true;
11636
+ await this.buildSnapshot();
11637
+ this.wireFleetBus();
11638
+ const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
11639
+ this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
11640
+ this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
11641
+ this.spawnAgent("critic", this.buildCriticTask())
11642
+ ]);
11643
+ this.subagentIds.set("bug-hunter", bugHunterId);
11644
+ this.subagentIds.set("refactor-planner", refactorPlannerId);
11645
+ this.subagentIds.set("critic", criticId);
11646
+ const timeout = new Promise((_, reject) => {
11647
+ this._timeoutTimer = setTimeout(() => {
11648
+ this.cancel("Session-level timeout reached");
11649
+ reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
11650
+ }, this.timeoutMs);
11651
+ });
11652
+ let results;
11653
+ try {
11654
+ results = await Promise.race([
11655
+ Promise.all([
11656
+ this.director.awaitTasks([bugHunterId]),
11657
+ this.director.awaitTasks([refactorPlannerId]),
11658
+ this.director.awaitTasks([criticId])
11659
+ ]),
11660
+ timeout
11661
+ ]);
11662
+ } catch (err) {
11663
+ if (this._timeoutTimer) {
11664
+ clearTimeout(this._timeoutTimer);
11665
+ this._timeoutTimer = void 0;
11666
+ }
11667
+ this.cleanup();
11668
+ const error = err instanceof Error ? err : new Error(String(err));
11669
+ this.emit("session.error", error);
11670
+ throw error;
11671
+ }
11672
+ for (const result of results.flat()) {
11673
+ await this.parseAndEmit(result);
11674
+ }
11675
+ const report = this.assembleReport();
11676
+ this.cleanup();
11677
+ this.emit("session.done", report);
11678
+ return report;
11679
+ }
11680
+ async parseAndEmit(result) {
11681
+ if (result.status !== "success" || result.result == null) return;
11682
+ const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
11683
+ for (const obj of this.extractJsonObjects(text)) {
11684
+ const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
11685
+ if (!type) continue;
11686
+ this.fleetBus.emit({
11687
+ subagentId: result.subagentId,
11688
+ taskId: result.taskId,
11689
+ ts: Date.now(),
11690
+ type,
11691
+ payload: obj
11692
+ });
11693
+ }
11694
+ }
11695
+ extractJsonObjects(text) {
11696
+ const objects = [];
11697
+ let depth = 0;
11698
+ let start = -1;
11699
+ let inString = false;
11700
+ let escaped = false;
11701
+ for (let i = 0; i < text.length; i++) {
11702
+ const ch = text[i];
11703
+ if (inString) {
11704
+ if (escaped) escaped = false;
11705
+ else if (ch === "\\") escaped = true;
11706
+ else if (ch === '"') inString = false;
11707
+ continue;
11708
+ }
11709
+ if (ch === '"') {
11710
+ inString = true;
11711
+ } else if (ch === "{") {
11712
+ if (depth === 0) start = i;
11713
+ depth++;
11714
+ } else if (ch === "}" && depth > 0) {
11715
+ depth--;
11716
+ if (depth === 0 && start >= 0) {
11717
+ const candidate = text.slice(start, i + 1);
11718
+ try {
11719
+ const parsed = JSON.parse(candidate);
11720
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
11721
+ objects.push(parsed);
11722
+ }
11723
+ } catch {
11724
+ }
11725
+ start = -1;
11726
+ }
11727
+ }
11728
+ }
11729
+ return objects;
11730
+ }
11731
+ budgetForRole(role) {
11732
+ if (this.options.budgetOverrides?.[role]) {
11733
+ return this.options.budgetOverrides[role];
11734
+ }
11735
+ const defaults = {
11736
+ "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
11737
+ "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
11738
+ "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
11739
+ };
11740
+ return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
11741
+ }
11742
+ async spawnAgent(role, taskBrief) {
11743
+ const budget = this.budgetForRole(role);
11744
+ const cfg = {
11745
+ id: `${role}-${this.sessionId}`,
11746
+ name: role,
11747
+ role,
11748
+ tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
11749
+ maxIterations: budget.maxIterations,
11750
+ maxToolCalls: budget.maxToolCalls,
11751
+ timeoutMs: budget.timeoutMs
11752
+ };
11753
+ const subagentId = await this.director.spawn(cfg);
11754
+ await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
11755
+ return subagentId;
11756
+ }
11757
+ buildBugHunterTask() {
11758
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
11759
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
11760
+ ${f.content}`).join("\n\n");
11761
+ return `You are BugHunter. Scan the following files for bugs and code smells.
11762
+
11763
+ Target files:
11764
+ ${fileContents}
11765
+
11766
+ For each bug found, emit it using the fleet_emit tool immediately:
11767
+ { "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
11768
+
11769
+ After scanning all files, write your full markdown bug report to:
11770
+ ${scratchpad}/bug-hunter-report-${this.sessionId}.md
11771
+
11772
+ Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
11773
+ }
11774
+ buildRefactorPlannerTask() {
11775
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
11776
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
11777
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
11778
+ ${f.content}`).join("\n\n");
11779
+ return `You are RefactorPlanner. Plan refactorings for the following files.
11780
+
11781
+ Target files:
11782
+ ${fileContents}
11783
+
11784
+ Read the BugHunter report at: ${bugHunterReportPath}
11785
+
11786
+ For each bug you can address, emit a refactor plan using fleet_emit:
11787
+ { "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>" } } }
11788
+
11789
+ Also write your full markdown plan to:
11790
+ ${scratchpad}/refactor-plan-${this.sessionId}.md
11791
+
11792
+ Emit each plan immediately. Do not wait until planning is complete.`;
11793
+ }
11794
+ buildCriticTask() {
11795
+ const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
11796
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
11797
+ const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
11798
+ const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
11799
+ ${f.content}`).join("\n\n");
11800
+ return `You are Critic. Evaluate bug findings and refactor plans.
11801
+
11802
+ Target files:
11803
+ ${fileContents}
11804
+
11805
+ Read the BugHunter report at: ${bugHunterReportPath}
11806
+ Read the RefactorPlanner report at: ${refactorPlanPath}
11807
+
11808
+ For each bug and refactor plan, emit your evaluation using fleet_emit:
11809
+ { "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>" }] } } }
11810
+
11811
+ After all evaluations, write your markdown report to:
11812
+ ${scratchpad}/critic-report-${this.sessionId}.md
11813
+
11814
+ Emit each evaluation immediately. Do not wait until you have read all reports.`;
11815
+ }
11816
+ wireFleetBus() {
11817
+ const dTool = this.fleetBus.filter("tool.executed", (e) => {
11818
+ this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
11819
+ });
11820
+ this.disposers.push(dTool);
11821
+ const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
11822
+ const payload = e.payload;
11823
+ const role = this.roleFromSubagentId(e.subagentId);
11824
+ if (!role) return;
11825
+ const btwNotes = this.director.getLeaderBtwNotes();
11826
+ const alert = {
11827
+ sessionId: this.sessionId,
11828
+ subagentId: e.subagentId,
11829
+ role,
11830
+ level: "warning" /* WARNING */,
11831
+ message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
11832
+ budgetKind: payload.kind,
11833
+ elapsedMs: payload.timeoutMs,
11834
+ limit: payload.limit,
11835
+ btwNotes
11836
+ };
11837
+ this.alerts.push(alert);
11838
+ this.fleetBus.emit({
11839
+ subagentId: e.subagentId,
11840
+ ts: Date.now(),
11841
+ type: "collab.warning",
11842
+ payload: alert
11843
+ });
11844
+ const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
11845
+ if (decision === "cancel") {
11846
+ this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
11847
+ return;
11848
+ }
11849
+ if (payload.kind === "timeout") {
11850
+ const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
11851
+ const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
11852
+ if (progress <= lastProgress) {
11853
+ payload.deny();
11854
+ return;
11855
+ }
11856
+ this.lastTimeoutProgress.set(e.subagentId, progress);
11857
+ const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
11858
+ setImmediate(() => {
11859
+ payload.extend({ timeoutMs: newLimit });
11860
+ });
11861
+ return;
11862
+ }
11863
+ if (decision === "extend") {
11864
+ setImmediate(() => {
11865
+ const base = Math.max(payload.limit, payload.used);
11866
+ const extra = {};
11867
+ switch (payload.kind) {
11868
+ case "iterations":
11869
+ extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
11870
+ break;
11871
+ case "tool_calls":
11872
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
11873
+ break;
11874
+ case "tokens":
11875
+ extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
11876
+ break;
11877
+ case "cost":
11878
+ extra.maxCostUsd = Math.min(base * 1.5, 100);
11879
+ break;
11880
+ }
11881
+ payload.extend(extra);
11882
+ });
11883
+ return;
11884
+ }
11885
+ if (payload.kind !== "timeout") {
11886
+ setImmediate(() => {
11887
+ const base = Math.max(payload.limit, payload.used);
11888
+ const extra = {};
11889
+ switch (payload.kind) {
11890
+ case "iterations":
11891
+ extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
11892
+ break;
11893
+ case "tool_calls":
11894
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
11895
+ break;
11896
+ case "tokens":
11897
+ extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
11898
+ break;
11899
+ case "cost":
11900
+ extra.maxCostUsd = Math.min(base * 1.25, 100);
11901
+ break;
11902
+ }
11903
+ payload.extend(extra);
11904
+ });
11905
+ }
11906
+ });
11907
+ this.disposers.push(dBudget);
11908
+ const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
11909
+ const payload = e.payload;
11910
+ if (payload.sessionId !== this.sessionId) return;
11911
+ this.cancelled = true;
11912
+ if (this._timeoutTimer) {
11913
+ clearTimeout(this._timeoutTimer);
11914
+ this._timeoutTimer = void 0;
11915
+ }
11916
+ this.fleetBus.emit({
11917
+ subagentId: this.director.id,
11918
+ ts: Date.now(),
11919
+ type: "collab.cancelled",
11920
+ payload: { sessionId: this.sessionId, reason: payload.reason }
11921
+ });
11922
+ });
11923
+ this.disposers.push(dCancel);
11924
+ const d1 = this.fleetBus.filter("bug.found", (e) => {
11925
+ const payload = e.payload;
11926
+ if (payload?.finding) {
11927
+ this.bugs.set(payload.finding.id, payload.finding);
11928
+ this.emit("bug.found", payload);
11929
+ }
11930
+ });
11931
+ this.disposers.push(d1);
11932
+ const d2 = this.fleetBus.filter("refactor.plan", (e) => {
11933
+ const payload = e.payload;
11934
+ if (payload?.plan) {
11935
+ this.plans.set(payload.plan.id, payload.plan);
11936
+ this.emit("refactor.plan", payload);
11937
+ }
11938
+ });
11939
+ this.disposers.push(d2);
11940
+ const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
11941
+ const payload = e.payload;
11942
+ if (payload?.evaluation) {
11943
+ this.evaluations.set(payload.evaluation.id, payload.evaluation);
11944
+ this.emit("critic.evaluation", payload);
11945
+ }
11946
+ });
11947
+ this.disposers.push(d3);
11948
+ }
11949
+ roleFromSubagentId(subagentId) {
11950
+ for (const [role, id] of this.subagentIds) {
11951
+ if (id === subagentId) return role;
11952
+ }
11953
+ const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
11954
+ return match?.[1] ?? null;
11955
+ }
11956
+ assembleReport() {
11957
+ const bugList = Array.from(this.bugs.values());
11958
+ const planList = Array.from(this.plans.values());
11959
+ const evalList = Array.from(this.evaluations.values());
11960
+ let disposition = "completed";
11961
+ if (this.cancelled) disposition = "cancelled";
11962
+ const verdictOrder = {
11963
+ approve: 0,
11964
+ needs_revision: 1,
11965
+ reject: 2
11966
+ };
11967
+ const overallVerdict = evalList.reduce(
11968
+ (worst, eval_) => {
11969
+ const w = verdictOrder[worst];
11970
+ const c = verdictOrder[eval_.verdict];
11971
+ return c > w ? eval_.verdict : worst;
11972
+ },
11973
+ "approve"
11974
+ );
11975
+ const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
11976
+ return {
11977
+ sessionId: this.sessionId,
11978
+ startedAt: this.snapshot.createdAt,
11979
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
11980
+ targetPaths: this.options.targetPaths,
11981
+ disposition,
11982
+ bugs: bugList,
11983
+ refactorPlans: planList,
11984
+ evaluations: evalList,
11985
+ alerts: [...this.alerts],
11986
+ overallVerdict,
11987
+ summary
11988
+ };
11989
+ }
11990
+ buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
11991
+ const lines = [
11992
+ `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
11993
+ "",
11994
+ `**Target:** ${this.options.targetPaths.join(", ")}`,
11995
+ `**Disposition:** ${disposition.toUpperCase()}`,
11996
+ `**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
11997
+ ""
11998
+ ];
11999
+ if (this.alerts.length > 0) {
12000
+ lines.push("### Alerts", "");
12001
+ for (const alert of this.alerts) {
12002
+ lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
12003
+ }
12004
+ lines.push("");
12005
+ }
12006
+ if (bugs.length > 0) {
12007
+ lines.push("### Bugs Found", "");
12008
+ for (const b of bugs) {
12009
+ lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
12010
+ }
12011
+ lines.push("");
12012
+ }
12013
+ if (plans.length > 0) {
12014
+ lines.push("### Refactor Plans", "");
12015
+ for (const p of plans) {
12016
+ lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
12017
+ for (const phase of p.phases) {
12018
+ lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
12019
+ }
12020
+ }
12021
+ lines.push("");
12022
+ }
12023
+ if (evals.length > 0) {
12024
+ lines.push("### Critic Evaluations", "");
12025
+ for (const e of evals) {
12026
+ lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
12027
+ for (const c of e.concerns) {
12028
+ if (c.severity === "blocking") lines.push(` - ${c.description}`);
12029
+ }
12030
+ }
12031
+ lines.push("");
12032
+ }
12033
+ return lines.join("\n");
12034
+ }
12035
+ cleanup() {
12036
+ for (const dispose of this.disposers) dispose();
12037
+ this.disposers.length = 0;
12038
+ }
12039
+ };
10974
12040
 
10975
12041
  // src/coordination/director.ts
10976
12042
  var FleetSpawnBudgetError = class extends Error {
10977
12043
  kind;
10978
12044
  limit;
10979
12045
  observed;
10980
- constructor(kind, limit, observed) {
10981
- super(
10982
- 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}`
10983
- );
12046
+ constructor(kind, limit, observed, message) {
12047
+ 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}`;
12048
+ super(message ?? defaultMsg);
10984
12049
  this.name = "FleetSpawnBudgetError";
10985
12050
  this.kind = kind;
10986
12051
  this.limit = limit;
@@ -11001,6 +12066,20 @@ var FleetCostCapError = class extends Error {
11001
12066
  this.observed = observed;
11002
12067
  }
11003
12068
  };
12069
+ var FleetContextOverflowError = class extends Error {
12070
+ kind;
12071
+ limit;
12072
+ observed;
12073
+ constructor(limit, observed) {
12074
+ super(
12075
+ `Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
12076
+ );
12077
+ this.name = "FleetContextOverflowError";
12078
+ this.kind = "max_context_load";
12079
+ this.limit = limit;
12080
+ this.observed = observed;
12081
+ }
12082
+ };
11004
12083
  var Director = class {
11005
12084
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
11006
12085
  get coordinatorId() {
@@ -11017,6 +12096,25 @@ var Director = class {
11017
12096
  * injected; otherwise own FleetUsageAggregator.
11018
12097
  */
11019
12098
  usage;
12099
+ /**
12100
+ * Update the leader agent's current context pressure (full request tokens:
12101
+ * messages + systemPrompt + toolDefs). The director checks this before every
12102
+ * spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
12103
+ * spawning is refused with a `FleetContextOverflowError`.
12104
+ *
12105
+ * Call this after each leader agent iteration to keep the pressure current.
12106
+ * The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
12107
+ */
12108
+ setLeaderContextPressure(tokens) {
12109
+ this.leaderContextPressure = tokens;
12110
+ this.fleetManager?.setLeaderContextPressure(tokens);
12111
+ }
12112
+ /**
12113
+ * Read the leader agent's current context pressure.
12114
+ */
12115
+ getLeaderContextPressure() {
12116
+ return this.leaderContextPressure;
12117
+ }
11020
12118
  /**
11021
12119
  * Optional fleet-level policy container. When provided the Director
11022
12120
  * delegates spawn budgeting, manifest entries, and checkpointing to it
@@ -11049,6 +12147,8 @@ var Director = class {
11049
12147
  subagentBridges = /* @__PURE__ */ new Map();
11050
12148
  /** Tracks per-spawn config + assigned task ids for manifest writing. */
11051
12149
  manifestEntries = /* @__PURE__ */ new Map();
12150
+ /** Tracks assigned nicknames so the same name is never reused in one fleet. */
12151
+ _usedNicknames = /* @__PURE__ */ new Set();
11052
12152
  manifestPath;
11053
12153
  roster;
11054
12154
  directorPreamble;
@@ -11100,6 +12200,23 @@ var Director = class {
11100
12200
  taskCompletedListener = null;
11101
12201
  /** Optional LLM classifier for smart dispatch. Passed from options. */
11102
12202
  dispatchClassifier;
12203
+ /** Leader agent's current context pressure (full request tokens). */
12204
+ leaderContextPressure = 0;
12205
+ /** Maximum context load fraction before spawn is refused. */
12206
+ maxLeaderContextLoad;
12207
+ /** Provider's max context window in tokens. */
12208
+ maxContext;
12209
+ /**
12210
+ * When set by `workComplete()`, the director stops dispatching new tasks
12211
+ * and terminates all running subagents. Used when the director's LLM decides
12212
+ * the goal is satisfied and no further spawns are needed — prevents the
12213
+ * coordinator from keeping workers alive for tasks that will never arrive.
12214
+ */
12215
+ workCompleteFlag = false;
12216
+ /** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
12217
+ _leaderBtwNotes = [];
12218
+ /** Active collab sessions tracked by sessionId (see spawnCollab). */
12219
+ _activeCollabSessions = /* @__PURE__ */ new Map();
11103
12220
  constructor(opts) {
11104
12221
  this.id = opts.config.coordinatorId || randomUUID();
11105
12222
  this.manifestPath = opts.manifestPath;
@@ -11115,6 +12232,8 @@ var Director = class {
11115
12232
  this.dispatchClassifier = opts.dispatchClassifier;
11116
12233
  this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
11117
12234
  this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
12235
+ this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
12236
+ this.maxContext = opts.maxContext ?? 128e3;
11118
12237
  this.sessionsRoot = opts.sessionsRoot;
11119
12238
  this.directorRunId = opts.directorRunId ?? this.id;
11120
12239
  this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
@@ -11142,7 +12261,7 @@ var Director = class {
11142
12261
  this.fleet = new FleetBus();
11143
12262
  this.usage = new FleetUsageAggregator(
11144
12263
  this.fleet,
11145
- (id, provider, model) => {
12264
+ (_id, provider, model) => {
11146
12265
  if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
11147
12266
  return void 0;
11148
12267
  },
@@ -11204,6 +12323,9 @@ var Director = class {
11204
12323
  });
11205
12324
  this.fleet.filter("budget.threshold_reached", (e) => {
11206
12325
  const payload = e.payload;
12326
+ if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
12327
+ return;
12328
+ }
11207
12329
  if (payload.kind === "timeout") {
11208
12330
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
11209
12331
  const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
@@ -11282,6 +12404,107 @@ var Director = class {
11282
12404
  extensionsFor(subagentId) {
11283
12405
  return this.extendTotals.get(subagentId) ?? 0;
11284
12406
  }
12407
+ /**
12408
+ * Signal that the director's work is done. Once called:
12409
+ * - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
12410
+ * subagents can be created
12411
+ * - Running subagents are NOT forcibly stopped — they finish naturally,
12412
+ * but no new tasks are dispatched to them
12413
+ *
12414
+ * This lets the director LLM say "I'm satisfied with the results, stop
12415
+ * spawning and wind down" — without killing in-flight work mid-execution.
12416
+ * Call `terminateAll()` separately if you need immediate teardown.
12417
+ *
12418
+ * Idempotent — calling twice is a no-op.
12419
+ */
12420
+ workComplete() {
12421
+ this.workCompleteFlag = true;
12422
+ this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
12423
+ }
12424
+ /** Returns true if `workComplete()` has been called on this director. */
12425
+ isWorkComplete() {
12426
+ return this.workCompleteFlag;
12427
+ }
12428
+ /**
12429
+ * Stashes a /btw note on the leader agent's context. The leader's agent loop
12430
+ * calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
12431
+ * into the conversation as a visible block — no abort, no restart, just a
12432
+ * "by the way" nudge the model picks up on its next turn.
12433
+ *
12434
+ * This is the entry point for the host (CLI, TUI) to inject /btw notes
12435
+ * programmatically without going through the slash-command path.
12436
+ */
12437
+ setLeaderBtwNote(note) {
12438
+ const trimmed = note.trim();
12439
+ if (!trimmed) return this._leaderBtwNotes.length;
12440
+ this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
12441
+ return this._leaderBtwNotes.length;
12442
+ }
12443
+ /**
12444
+ * Read and clear all pending /btw notes the leader has stashed.
12445
+ * Returns them in FIFO order (empty array when none).
12446
+ *
12447
+ * Called by CollabSession when a budget threshold event fires so the
12448
+ * Director can inspect accumulated /btw notes before deciding whether
12449
+ * to cancel the collab session or let it continue.
12450
+ */
12451
+ getLeaderBtwNotes() {
12452
+ const notes = this._leaderBtwNotes;
12453
+ this._leaderBtwNotes = [];
12454
+ return notes;
12455
+ }
12456
+ /**
12457
+ * Peek at pending /btw notes without consuming them.
12458
+ * Useful for UI to show "N pending notes" without clearing them.
12459
+ */
12460
+ peekLeaderBtwNotes() {
12461
+ return [...this._leaderBtwNotes];
12462
+ }
12463
+ /**
12464
+ * Drain (read + clear) all /btw notes in one call.
12465
+ * Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
12466
+ * in the agent's btw.ts. The Director calls this at the point where it
12467
+ * makes a cancellation decision, not on every budget event.
12468
+ */
12469
+ drainLeaderBtwNotes() {
12470
+ return this.getLeaderBtwNotes();
12471
+ }
12472
+ /**
12473
+ * Cancel an active collab session by its id.
12474
+ * Emits `director.cancel_collab` on the FleetBus so the session's agents
12475
+ * finish early with a 'cancelled' disposition.
12476
+ *
12477
+ * Returns silently if the session id is not tracked or already settled.
12478
+ * The CollabDebugReport will reflect 'cancelled' disposition when awaited.
12479
+ */
12480
+ cancelCollabSession(sessionId, reason = "Director cancelled") {
12481
+ const session = this._activeCollabSessions.get(sessionId);
12482
+ if (!session || session.isCancelled()) return;
12483
+ session.cancel(reason);
12484
+ for (const [_role, subagentId] of session.getSubagentIds()) {
12485
+ this.coordinator.stop(subagentId).catch(() => {
12486
+ });
12487
+ }
12488
+ }
12489
+ /**
12490
+ * Subscribe a callback to be notified whenever a collab session raises
12491
+ * an alert (warning level). The callback receives the full DirectorAlert
12492
+ * payload so the host (CLI, TUI) can display it to the user.
12493
+ * Returns an unsubscribe function.
12494
+ */
12495
+ onCollabAlert(handler) {
12496
+ return this.fleet.filter("collab.warning", (e) => {
12497
+ handler(e.payload);
12498
+ });
12499
+ }
12500
+ /**
12501
+ * Returns all active (not yet settled) collab session ids.
12502
+ * Useful for the TUI to render a "N active sessions" badge and for
12503
+ * the host to know what can be cancelled.
12504
+ */
12505
+ activeCollabSessions() {
12506
+ return Array.from(this._activeCollabSessions.keys());
12507
+ }
11285
12508
  /** Best-effort session-writer append. Swallows failures — the director
11286
12509
  * must not break a fleet run because the session JSONL handle closed. */
11287
12510
  async appendSessionEvent(event) {
@@ -11320,12 +12543,21 @@ var Director = class {
11320
12543
  * it the `cost` column in `usage.snapshot()` stays at 0.
11321
12544
  */
11322
12545
  async spawn(config, priceLookup) {
12546
+ if (this.workCompleteFlag) {
12547
+ throw new FleetSpawnBudgetError(
12548
+ "max_spawns",
12549
+ this.maxSpawns,
12550
+ this.spawnCount + 1,
12551
+ "workComplete() has been called \u2014 director closed further spawning"
12552
+ );
12553
+ }
11323
12554
  if (this.fleetManager) {
11324
12555
  const rejection = this.fleetManager.canSpawn(config);
11325
12556
  if (rejection) {
11326
12557
  if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
11327
12558
  if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
11328
12559
  if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
12560
+ if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
11329
12561
  }
11330
12562
  } else {
11331
12563
  if (this.spawnDepth >= this.maxSpawnDepth) {
@@ -11340,11 +12572,29 @@ var Director = class {
11340
12572
  throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
11341
12573
  }
11342
12574
  }
12575
+ if (this.maxLeaderContextLoad < 1) {
12576
+ const threshold = this.maxContext * this.maxLeaderContextLoad;
12577
+ if (this.leaderContextPressure >= threshold) {
12578
+ throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
12579
+ }
12580
+ }
11343
12581
  }
11344
12582
  let result;
12583
+ const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
12584
+ if (needsNickname) {
12585
+ const role = config.role ?? "subagent";
12586
+ if (this.fleetManager) {
12587
+ this.fleetManager.assignNicknameAndRecord("", config, priceLookup);
12588
+ } else {
12589
+ config.name = assignNickname(role, this._usedNicknames);
12590
+ this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
12591
+ }
12592
+ }
11345
12593
  result = await this.coordinator.spawn(config);
11346
12594
  if (this.fleetManager) {
11347
- this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
12595
+ if (!needsNickname) {
12596
+ this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
12597
+ }
11348
12598
  } else {
11349
12599
  this.spawnCount += 1;
11350
12600
  this.subagentMeta.set(result.subagentId, {
@@ -11555,6 +12805,24 @@ var Director = class {
11555
12805
  */
11556
12806
  async assign(task) {
11557
12807
  const taskWithId = task.id ? task : { ...task, id: randomUUID() };
12808
+ if (this.workCompleteFlag) {
12809
+ const synthetic = {
12810
+ subagentId: taskWithId.subagentId ?? "unassigned",
12811
+ taskId: taskWithId.id,
12812
+ status: "stopped",
12813
+ error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
12814
+ iterations: 0,
12815
+ toolCalls: 0,
12816
+ durationMs: 0
12817
+ };
12818
+ this.completed.set(taskWithId.id, synthetic);
12819
+ const waiter = this.taskWaiters.get(taskWithId.id);
12820
+ if (waiter) {
12821
+ waiter.resolve(synthetic);
12822
+ this.taskWaiters.delete(taskWithId.id);
12823
+ }
12824
+ return taskWithId.id;
12825
+ }
11558
12826
  if (task.subagentId) {
11559
12827
  if (this.fleetManager) {
11560
12828
  this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
@@ -11777,10 +13045,14 @@ var Director = class {
11777
13045
  makeAskTool(this),
11778
13046
  makeRollUpTool(this),
11779
13047
  makeTerminateTool(this),
13048
+ makeTerminateAllTool(this),
11780
13049
  makeFleetStatusTool(this),
11781
13050
  makeFleetUsageTool(this),
11782
13051
  makeFleetSessionTool(this),
11783
- makeFleetHealthTool(this)
13052
+ makeFleetHealthTool(this),
13053
+ makeCollabDebugTool(this),
13054
+ makeFleetEmitTool(this),
13055
+ makeWorkCompleteTool(this)
11784
13056
  ];
11785
13057
  return t;
11786
13058
  }
@@ -11792,6 +13064,25 @@ var Director = class {
11792
13064
  async acquireCheckpointLock() {
11793
13065
  return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
11794
13066
  }
13067
+ /**
13068
+ * Start a collaborative debugging session: BugHunter, RefactorPlanner,
13069
+ * and Critic run in parallel on the same target files, with findings
13070
+ * flowing through the FleetBus (bug.found → refactor.plan →
13071
+ * critic.evaluation). Returns a structured CollabDebugReport when all
13072
+ * three agents complete or the session times out.
13073
+ */
13074
+ async spawnCollab(options) {
13075
+ const session = new CollabSession(this, this.fleet, {
13076
+ ...options,
13077
+ onBudgetWarning: (alert) => {
13078
+ return options.onBudgetWarning?.(alert) ?? "ignore";
13079
+ }
13080
+ });
13081
+ this._activeCollabSessions.set(session.sessionId, session);
13082
+ session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
13083
+ session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
13084
+ return session.start();
13085
+ }
11795
13086
  /**
11796
13087
  * Resume from a prior checkpoint snapshot (loaded via
11797
13088
  * `loadDirectorState()`). Re-attach to the fleet mid-flight so
@@ -14025,10 +15316,10 @@ var AISpecBuilder = class {
14025
15316
  async saveSession() {
14026
15317
  if (!this.sessionPath) return;
14027
15318
  try {
14028
- const fsp14 = await import('fs/promises');
15319
+ const fsp15 = await import('fs/promises');
14029
15320
  const path18 = await import('path');
14030
15321
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
14031
- await fsp14.mkdir(path18.dirname(this.sessionPath), { recursive: true });
15322
+ await fsp15.mkdir(path18.dirname(this.sessionPath), { recursive: true });
14032
15323
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
14033
15324
  } catch {
14034
15325
  }
@@ -14037,8 +15328,8 @@ var AISpecBuilder = class {
14037
15328
  async loadSession() {
14038
15329
  if (!this.sessionPath) return false;
14039
15330
  try {
14040
- const fsp14 = await import('fs/promises');
14041
- const raw = await fsp14.readFile(this.sessionPath, "utf8");
15331
+ const fsp15 = await import('fs/promises');
15332
+ const raw = await fsp15.readFile(this.sessionPath, "utf8");
14042
15333
  const loaded = JSON.parse(raw);
14043
15334
  if (loaded?.id && loaded?.phase && loaded?.title) {
14044
15335
  this.session = loaded;
@@ -14052,8 +15343,8 @@ var AISpecBuilder = class {
14052
15343
  async deleteSession() {
14053
15344
  if (!this.sessionPath) return;
14054
15345
  try {
14055
- const fsp14 = await import('fs/promises');
14056
- await fsp14.unlink(this.sessionPath);
15346
+ const fsp15 = await import('fs/promises');
15347
+ await fsp15.unlink(this.sessionPath);
14057
15348
  } catch {
14058
15349
  }
14059
15350
  }
@@ -14680,7 +15971,7 @@ function analyzeCriticalPath(graph) {
14680
15971
  blockedTasks
14681
15972
  };
14682
15973
  }
14683
- function getTransitiveBlocked(graph, taskId, blocksMap) {
15974
+ function getTransitiveBlocked(_graph, taskId, blocksMap) {
14684
15975
  const visited = /* @__PURE__ */ new Set();
14685
15976
  const queue = [taskId];
14686
15977
  while (queue.length > 0) {
@@ -14696,7 +15987,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
14696
15987
  }
14697
15988
  return visited;
14698
15989
  }
14699
- function computeCriticalPath(graph, topoOrder, blockedByMap) {
15990
+ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
14700
15991
  const allIds = Array.from(graph.nodes.keys());
14701
15992
  if (allIds.length === 0) return [];
14702
15993
  const dist = /* @__PURE__ */ new Map();
@@ -15114,13 +16405,11 @@ function createAutoExecutor(opts) {
15114
16405
 
15115
16406
  // src/sdd/sdd-task-decomposer.ts
15116
16407
  var SddTaskDecomposer = class {
15117
- constructor(tracker, graph, opts = {}) {
16408
+ constructor(tracker, _graph, opts = {}) {
15118
16409
  this.tracker = tracker;
15119
- this.graph = graph;
15120
16410
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
15121
16411
  }
15122
16412
  tracker;
15123
- graph;
15124
16413
  slots;
15125
16414
  wave = 0;
15126
16415
  // -------------------------------------------------------------------
@@ -15273,7 +16562,7 @@ var SddParallelRun = class {
15273
16562
  this.coordinator.setRunner?.(runner);
15274
16563
  }
15275
16564
  defaultFactory() {
15276
- return async (config) => ({
16565
+ return async (_config) => ({
15277
16566
  agent: this.opts.agent,
15278
16567
  events: this.opts.agent.events
15279
16568
  });
@@ -16087,6 +17376,12 @@ function roughEstimate(messages) {
16087
17376
  return total;
16088
17377
  }
16089
17378
  function createContextManagerTool(opts = {}) {
17379
+ const minCompactThreshold = opts.minCompactThreshold ?? 0;
17380
+ const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
17381
+ const maxContext = opts.maxContext ?? 128e3;
17382
+ const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
17383
+ const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
17384
+ let lastNoopTokens = 0;
16090
17385
  return {
16091
17386
  name: CONTEXT_MANAGER_TOOL_NAME,
16092
17387
  description: 'Inspect or reorganize the conversation context window. Use "check" to see token budget. Use "summary" to collapse a message range into a concise note (provide "text" for custom summary). Use "prune" to remove specific messages by index. Use "add_note" to inject a summary note. Use "compact" to run aggressive compaction. Use "repair" to remove orphan tool_use/tool_result blocks after manual context surgery.',
@@ -16177,11 +17472,41 @@ function createContextManagerTool(opts = {}) {
16177
17472
  notes: "No compactor registered. Use /compact aggressive via slash command instead."
16178
17473
  };
16179
17474
  }
17475
+ const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
17476
+ const currentTokens = fullEstimate.total;
17477
+ if (lastNoopTokens > 0) {
17478
+ const delta = currentTokens - lastNoopTokens;
17479
+ if (delta < noopRetryDeltaTokens) {
17480
+ return {
17481
+ action: "compact",
17482
+ beforeTokens,
17483
+ afterTokens: beforeTokens,
17484
+ messageCount: messages.length,
17485
+ notes: `Compact is a NOOP retry: context grew only ${delta} tokens since the last no-op attempt (threshold: ${noopRetryDeltaTokens}). Skip until more content accumulates.`
17486
+ };
17487
+ }
17488
+ }
17489
+ if (currentTokens < effectiveThreshold) {
17490
+ return {
17491
+ action: "compact",
17492
+ beforeTokens,
17493
+ afterTokens: beforeTokens,
17494
+ messageCount: messages.length,
17495
+ notes: `Context tokens (${currentTokens}) below compact threshold (${effectiveThreshold}). Skipping.`
17496
+ };
17497
+ }
16180
17498
  const report = await opts.compactor.compact(ctx);
16181
17499
  ctx.clearFileTracking();
16182
17500
  const repair = applyMessages([...ctx.messages]);
16183
17501
  const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
16184
17502
  const repaired = report.repaired ?? (repair.changed ? repair : void 0);
17503
+ const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
17504
+ const repairedSomething = !!report.repaired;
17505
+ if (reduced || repairedSomething) {
17506
+ lastNoopTokens = 0;
17507
+ } else {
17508
+ lastNoopTokens = currentTokens;
17509
+ }
16185
17510
  return {
16186
17511
  action: "compact",
16187
17512
  beforeTokens,
@@ -16420,6 +17745,6 @@ var allServers = () => ({
16420
17745
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
16421
17746
  });
16422
17747
 
16423
- export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeDirectorSessionFactory, makeLLMClassifier, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
17748
+ export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets2 as decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
16424
17749
  //# sourceMappingURL=index.js.map
16425
17750
  //# sourceMappingURL=index.js.map