@wrongstack/core 0.9.19 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +37 -33
  2. package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-DBBGP5O4.d.ts} +1 -1
  3. package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-KwufTWwn.d.ts} +3 -4
  4. package/dist/compactor-D1RHFRmF.d.ts +41 -0
  5. package/dist/{config-CWva0qoL.d.ts → config-CJXBka2r.d.ts} +38 -2
  6. package/dist/{context-BRNbHmRM.d.ts → context-7u93AcGD.d.ts} +140 -0
  7. package/dist/coordination/index.d.ts +13 -13
  8. package/dist/coordination/index.js +786 -200
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/{default-config-Uzla85H-.d.ts → default-config-DEXI4jsl.d.ts} +10 -1
  11. package/dist/defaults/index.d.ts +24 -24
  12. package/dist/defaults/index.js +1612 -528
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/{events-CiG9qUM_.d.ts → events-Bw1K5E6K.d.ts} +49 -1
  15. package/dist/execution/index.d.ts +46 -32
  16. package/dist/execution/index.js +663 -169
  17. package/dist/execution/index.js.map +1 -1
  18. package/dist/extension/index.d.ts +6 -6
  19. package/dist/extension/index.js.map +1 -1
  20. package/dist/goal-store-BeRsj7YX.d.ts +188 -0
  21. package/dist/{index-p95HQ22A.d.ts → index-DIt5TIaF.d.ts} +8 -8
  22. package/dist/{index-aizK8olO.d.ts → index-DgQWnZxP.d.ts} +11 -8
  23. package/dist/index.d.ts +338 -41
  24. package/dist/index.js +2747 -787
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +6 -6
  27. package/dist/infrastructure/index.js +36 -0
  28. package/dist/infrastructure/index.js.map +1 -1
  29. package/dist/kernel/index.d.ts +9 -9
  30. package/dist/kernel/index.js +57 -8
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-BgINZzuo.d.ts} +25 -3
  33. package/dist/models/index.d.ts +3 -3
  34. package/dist/models/index.js +164 -11
  35. package/dist/models/index.js.map +1 -1
  36. package/dist/models-registry-gwMAo6E3.d.ts +86 -0
  37. package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-C1O8EFzb.d.ts} +77 -24
  38. package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-DoVSBFNx.d.ts} +20 -2
  39. package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-T-s3JaOt.d.ts} +413 -110
  40. package/dist/observability/index.d.ts +2 -2
  41. package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-BfyK-I7w.d.ts} +2 -2
  42. package/dist/{permission-bPuzAy4x.d.ts → permission-B6sldrSp.d.ts} +1 -1
  43. package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CtNscWOA.d.ts} +10 -9
  44. package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-BWs6Je7j.d.ts} +8 -8
  45. package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BmPe2RuU.d.ts} +3 -3
  46. package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-KF18W4dg.d.ts} +1 -1
  47. package/dist/sdd/index.d.ts +9 -10
  48. package/dist/sdd/index.js +350 -122
  49. package/dist/sdd/index.js.map +1 -1
  50. package/dist/security/index.d.ts +65 -4
  51. package/dist/security/index.js +67 -19
  52. package/dist/security/index.js.map +1 -1
  53. package/dist/{selector-pox8abg0.d.ts → selector-DmXxpFyM.d.ts} +1 -1
  54. package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-CCSBfQmd.d.ts} +2 -2
  55. package/dist/skills/index.d.ts +1 -1
  56. package/dist/skills/index.js +1 -1
  57. package/dist/skills/index.js.map +1 -1
  58. package/dist/storage/index.d.ts +392 -8
  59. package/dist/storage/index.js +796 -40
  60. package/dist/storage/index.js.map +1 -1
  61. package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CM6zOhd2.d.ts} +19 -1
  62. package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-D7RxLtg4.d.ts} +9 -4
  63. package/dist/types/index.d.ts +16 -16
  64. package/dist/types/index.js +404 -14
  65. package/dist/types/index.js.map +1 -1
  66. package/dist/utils/index.d.ts +40 -8
  67. package/dist/utils/index.js +127 -57
  68. package/dist/utils/index.js.map +1 -1
  69. package/dist/{wstack-paths-BCgmTNlG.d.ts → wstack-paths-eMXnY1_X.d.ts} +8 -0
  70. package/package.json +1 -1
  71. package/dist/compactor-DVTKL7XD.d.ts +0 -23
  72. package/dist/goal-store-C7jcumEh.d.ts +0 -96
  73. package/dist/models-registry-OG_30xqZ.d.ts +0 -46
@@ -640,7 +640,6 @@ var FileSessionWriter = class {
640
640
  }
641
641
  return event;
642
642
  }
643
- promptIndex = 0;
644
643
  pendingFileSnapshots = [];
645
644
  /** Tracks open tool_use IDs during the current run to serialize on close for resume. */
646
645
  openToolUses = /* @__PURE__ */ new Set();
@@ -731,7 +730,6 @@ var FileSessionWriter = class {
731
730
  await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
732
731
  this.pendingFileSnapshots = [];
733
732
  }
734
- this.promptIndex = promptIndex + 1;
735
733
  await this.append({
736
734
  type: "checkpoint",
737
735
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -827,6 +825,38 @@ var FileSessionWriter = class {
827
825
  `;
828
826
  await fsp.writeFile(this.filePath, record, "utf8");
829
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
+ }
830
860
  };
831
861
  function userInputTitle(content) {
832
862
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
@@ -889,7 +919,7 @@ function isPersistedQueueItem(v) {
889
919
  // src/storage/attachment-store.ts
890
920
  init_atomic_write();
891
921
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
892
- var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
922
+ var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
893
923
  var DefaultAttachmentStore = class {
894
924
  items = /* @__PURE__ */ new Map();
895
925
  refs = [];
@@ -943,9 +973,15 @@ var DefaultAttachmentStore = class {
943
973
  const idx = m.index ?? 0;
944
974
  const before = text.slice(lastIndex, idx);
945
975
  if (before) blocks.push({ type: "text", text: before });
946
- const kind = prefixToKind(m[1]);
947
- const seq = Number(m[2]);
948
- const ref = this.refs.find((r) => r.kind === kind && r.seq === seq);
976
+ let ref;
977
+ if (m[3] !== void 0) {
978
+ const wantPath = m[3];
979
+ ref = findLast(this.refs, (r) => r.kind === "file" && refPath(r) === wantPath);
980
+ } else {
981
+ const kind = prefixToKind(m[1]);
982
+ const seq = Number(m[2]);
983
+ ref = this.refs.find((r) => r.kind === kind && r.seq === seq);
984
+ }
949
985
  const att = ref ? this.items.get(ref.id) : void 0;
950
986
  if (!att) {
951
987
  blocks.push({ type: "text", text: m[0] });
@@ -998,6 +1034,15 @@ function prefixToKind(prefix) {
998
1034
  if (prefix === "image") return "image";
999
1035
  return "file";
1000
1036
  }
1037
+ function refPath(ref) {
1038
+ return ref.meta.filename ?? ref.meta.label;
1039
+ }
1040
+ function findLast(arr, pred) {
1041
+ for (let i = arr.length - 1; i >= 0; i--) {
1042
+ if (pred(arr[i])) return arr[i];
1043
+ }
1044
+ return void 0;
1045
+ }
1001
1046
  function mergeAdjacentText(blocks) {
1002
1047
  const out = [];
1003
1048
  for (const b of blocks) {
@@ -1382,6 +1427,14 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
1382
1427
  var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
1383
1428
  autoProceedDelayMs: 45e3
1384
1429
  });
1430
+ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
1431
+ auditLevel: "standard",
1432
+ sampling: {
1433
+ toolProgress: {
1434
+ sampleRate: 8
1435
+ }
1436
+ }
1437
+ });
1385
1438
 
1386
1439
  // src/storage/config-loader.ts
1387
1440
  var BEHAVIOR_DEFAULTS = {
@@ -1410,7 +1463,8 @@ var BEHAVIOR_DEFAULTS = {
1410
1463
  memory: true,
1411
1464
  modelsRegistry: true,
1412
1465
  skills: true
1413
- }
1466
+ },
1467
+ session: { ...DEFAULT_SESSION_LOGGING_CONFIG }
1414
1468
  };
1415
1469
  var ENV_MAP = {
1416
1470
  WRONGSTACK_PROVIDER: (c, v) => {
@@ -2214,6 +2268,101 @@ var SessionAnalyzer = class {
2214
2268
  }
2215
2269
  };
2216
2270
 
2271
+ // src/storage/session-event-bridge.ts
2272
+ var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
2273
+ "session_start",
2274
+ "session_resumed",
2275
+ "user_input",
2276
+ "llm_response",
2277
+ "tool_result",
2278
+ "checkpoint",
2279
+ "file_snapshot",
2280
+ "rewound",
2281
+ "in_flight_start",
2282
+ "in_flight_end",
2283
+ "session_end"
2284
+ ]);
2285
+ var STANDARD_AUDIT_EVENTS = /* @__PURE__ */ new Set([
2286
+ "llm_request",
2287
+ "tool_use",
2288
+ "tool_call_start",
2289
+ "tool_call_end",
2290
+ "compaction",
2291
+ "error",
2292
+ "message_truncated",
2293
+ "provider_retry",
2294
+ "provider_error"
2295
+ ]);
2296
+ var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set([
2297
+ "tool_progress"
2298
+ ]);
2299
+ function isAllowed(type, level) {
2300
+ if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;
2301
+ if (level === "minimal") return false;
2302
+ if (STANDARD_AUDIT_EVENTS.has(type)) return true;
2303
+ if (level === "standard") return false;
2304
+ if (FULL_ONLY_EVENTS.has(type)) {
2305
+ return level === "full";
2306
+ }
2307
+ return true;
2308
+ }
2309
+ function createSessionEventBridge(writer, level = "standard", options = {}) {
2310
+ const normalizedLevel = level ?? "standard";
2311
+ const progressCounters = /* @__PURE__ */ new Map();
2312
+ const toolProgressConfig = options.sampling?.toolProgress ?? {};
2313
+ const TOOL_PROGRESS_SAMPLE_RATE = toolProgressConfig.sampleRate ?? 8;
2314
+ function shouldSample(event) {
2315
+ if (event.type !== "tool_progress") return true;
2316
+ const progEvent = event;
2317
+ const innerType = progEvent.event?.type;
2318
+ if (innerType === "warning" || innerType === "metric" || innerType === "file_changed") {
2319
+ return true;
2320
+ }
2321
+ if (innerType === "log" || innerType === "partial_output") {
2322
+ const key = progEvent.id || progEvent.name;
2323
+ const count = (progressCounters.get(key) || 0) + 1;
2324
+ progressCounters.set(key, count);
2325
+ return count === 1 || count % TOOL_PROGRESS_SAMPLE_RATE === 0;
2326
+ }
2327
+ return true;
2328
+ }
2329
+ return {
2330
+ level: normalizedLevel,
2331
+ allows(type) {
2332
+ return isAllowed(type, normalizedLevel);
2333
+ },
2334
+ async append(event) {
2335
+ if (!writer) return;
2336
+ if (!isAllowed(event.type, normalizedLevel)) return;
2337
+ if (!shouldSample(event)) return;
2338
+ try {
2339
+ await writer.append(event);
2340
+ } catch (err) {
2341
+ }
2342
+ }
2343
+ };
2344
+ }
2345
+ function resolveAuditLevel(cfg) {
2346
+ const raw = cfg?.session?.auditLevel;
2347
+ if (raw === "minimal" || raw === "standard" || raw === "full") {
2348
+ return raw;
2349
+ }
2350
+ return "standard";
2351
+ }
2352
+ function resolveSessionLoggingConfig(cfg) {
2353
+ const session = cfg?.session ?? {};
2354
+ const auditLevel = resolveAuditLevel(cfg);
2355
+ const toolProgressSampleRate = session.sampling?.toolProgress?.sampleRate ?? 8;
2356
+ return {
2357
+ auditLevel,
2358
+ sampling: {
2359
+ toolProgress: {
2360
+ sampleRate: Math.max(1, Math.floor(toolProgressSampleRate))
2361
+ }
2362
+ }
2363
+ };
2364
+ }
2365
+
2217
2366
  // src/storage/todos-checkpoint.ts
2218
2367
  init_atomic_write();
2219
2368
  async function loadTodosCheckpoint(filePath) {
@@ -3022,6 +3171,47 @@ function deepMerge2(a, b) {
3022
3171
  return out;
3023
3172
  }
3024
3173
 
3174
+ // src/security/capabilities.ts
3175
+ var ToolCapabilities = {
3176
+ /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
3177
+ SHELL_ARBITRARY: "shell.arbitrary",
3178
+ /** Can write / modify / delete files inside the project. */
3179
+ FS_WRITE: "fs.write",
3180
+ /** Can write files outside the current project root (very high risk). */
3181
+ FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
3182
+ /** Proxies tools from external MCP servers (unknown capability). */
3183
+ MCP_PROXY: "mcp.proxy",
3184
+ /** Can spawn or manage subagents / multi-agent tasks. */
3185
+ SUBAGENT_SPAWN: "subagent.spawn",
3186
+ /** Can mutate global or session configuration / trust state. */
3187
+ CONFIG_MUTATE: "config.mutate",
3188
+ /** Can install packages or run package managers with side effects. */
3189
+ PACKAGE_INSTALL: "package.install"
3190
+ };
3191
+ var DANGEROUS_FOR_SUBAGENTS = [
3192
+ ToolCapabilities.SHELL_ARBITRARY,
3193
+ ToolCapabilities.FS_WRITE,
3194
+ ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
3195
+ ToolCapabilities.MCP_PROXY,
3196
+ ToolCapabilities.SUBAGENT_SPAWN,
3197
+ ToolCapabilities.CONFIG_MUTATE,
3198
+ ToolCapabilities.PACKAGE_INSTALL
3199
+ ];
3200
+ function hasDangerousCapabilityForSubagents(toolOrCaps) {
3201
+ if (!toolOrCaps) return false;
3202
+ const input = toolOrCaps;
3203
+ const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
3204
+ return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
3205
+ }
3206
+ function getDangerousCapabilities(toolOrCaps) {
3207
+ if (!toolOrCaps) return [];
3208
+ const input = toolOrCaps;
3209
+ const caps = Array.isArray(input) ? input : input?.capabilities ?? [];
3210
+ return caps.filter(
3211
+ (c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
3212
+ );
3213
+ }
3214
+
3025
3215
  // src/security/permission-policy.ts
3026
3216
  init_atomic_write();
3027
3217
 
@@ -3341,31 +3531,21 @@ var DefaultPermissionPolicy = class {
3341
3531
  };
3342
3532
  var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
3343
3533
  /**
3344
- * Tools that are too dangerous to auto-approve even in a delegated
3345
- * subagent context. Subagents run non-interactively under a director
3346
- * and cannot answer prompts, but inherited authorization does not
3347
- * imply blanket permission for destructive or privilege-escalating
3348
- * operations. These tools remain at their declared `permission`
3349
- * level so the leader must explicitly allow them per-spawn.
3534
+ * Legacy name-based denylist.
3535
+ * @deprecated Prefer declaring `capabilities` on the Tool and using capability-based checks.
3350
3536
  */
3351
- static DENY = /* @__PURE__ */ new Set([
3537
+ static LEGACY_NAME_DENY = /* @__PURE__ */ new Set([
3352
3538
  "bash",
3353
- // arbitrary shell — use exec for constrained shell
3354
3539
  "write",
3355
- // arbitrary file write
3356
3540
  "edit",
3357
- // arbitrary in-project file modification (equivalent to write)
3358
3541
  "replace",
3359
- // arbitrary multi-file find/replace (equivalent to write)
3360
3542
  "scaffold",
3361
- // arbitrary file generation outside project root
3362
3543
  "patch",
3363
- // arbitrary diff application
3364
3544
  "install",
3365
- // installs from arbitrary package sources
3366
3545
  "exec"
3367
- // restricted shell but with arbitrary command args
3368
3546
  ]);
3547
+ // Note: hasDangerousCapabilityForSubagents is now the shared helper from capabilities.ts
3548
+ // The old private method was removed in favor of the centralized utility.
3369
3549
  /**
3370
3550
  * Tools from MCP servers (`mcp__<server>__<tool>`) are external code of
3371
3551
  * unknown capability — they may wrap a shell or filesystem. They are
@@ -3376,12 +3556,16 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
3376
3556
  return name.startsWith("mcp__");
3377
3557
  }
3378
3558
  async evaluate(tool) {
3379
- const blocked = _AutoApprovePermissionPolicy.DENY.has(tool.name) || _AutoApprovePermissionPolicy.isMcpTool(tool.name);
3380
- if (tool.permission === "deny" || blocked) {
3559
+ const hasDangerousCap = hasDangerousCapabilityForSubagents(tool);
3560
+ const legacyNameBlock = _AutoApprovePermissionPolicy.LEGACY_NAME_DENY.has(tool.name);
3561
+ const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
3562
+ const blocked = tool.permission === "deny" || hasDangerousCap || legacyNameBlock || isMcp;
3563
+ if (blocked) {
3564
+ const reason = hasDangerousCap ? `tool declares dangerous capability (${tool.capabilities?.join(", ")}) \u2014 not auto-approved for subagents` : legacyNameBlock || isMcp ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny";
3381
3565
  return {
3382
3566
  permission: "deny",
3383
3567
  source: "subagent_guard",
3384
- reason: blocked ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
3568
+ reason
3385
3569
  };
3386
3570
  }
3387
3571
  return { permission: "auto", source: "yolo" };
@@ -3798,80 +3982,85 @@ function parseDescription(raw) {
3798
3982
 
3799
3983
  // src/utils/json-repair.ts
3800
3984
  function completePartialObject(s) {
3801
- let result = s;
3802
- const trimmed = result.trim();
3803
- if (!trimmed.startsWith("{")) return s;
3804
- for (let pass = 0; pass < 3; pass++) {
3805
- let braceDepth = 0;
3806
- let inString2 = false;
3807
- let escaped2 = false;
3808
- let foundClose = false;
3809
- for (const ch of result) {
3810
- if (escaped2) {
3811
- escaped2 = false;
3985
+ if (!s.trim().startsWith("{")) return s;
3986
+ if (tryParse(s).ok) return s;
3987
+ const stack = [];
3988
+ let inString = false;
3989
+ let escaped = false;
3990
+ let sawKey = false;
3991
+ let prevSig = "";
3992
+ let contentEnd = 0;
3993
+ let stringBraceDepth = 0;
3994
+ for (let i = 0; i < s.length; i++) {
3995
+ const ch = s[i];
3996
+ if (inString) {
3997
+ contentEnd = i + 1;
3998
+ if (escaped) {
3999
+ escaped = false;
3812
4000
  continue;
3813
4001
  }
3814
4002
  if (ch === "\\") {
3815
- escaped2 = true;
4003
+ escaped = true;
3816
4004
  continue;
3817
4005
  }
3818
4006
  if (ch === '"') {
3819
- inString2 = !inString2;
4007
+ inString = false;
4008
+ prevSig = '"';
4009
+ stringBraceDepth = 0;
3820
4010
  continue;
3821
4011
  }
3822
- if (inString2) continue;
3823
- if (ch === "{") {
3824
- braceDepth++;
3825
- foundClose = false;
3826
- } else if (ch === "}") {
3827
- braceDepth--;
3828
- if (braceDepth === 0) foundClose = true;
3829
- }
3830
- }
3831
- if (foundClose || braceDepth <= 0) break;
3832
- result += "}".repeat(braceDepth);
3833
- }
3834
- if (tryParse(result).ok) return result;
3835
- let inString = false;
3836
- let escaped = false;
3837
- for (let i = result.length - 1; i >= 0; i--) {
3838
- const ch = result[i];
3839
- if (escaped) {
3840
- escaped = false;
3841
- continue;
3842
- }
3843
- if (ch === "\\") {
3844
- escaped = true;
4012
+ if (ch === "{") stringBraceDepth++;
4013
+ else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
3845
4014
  continue;
3846
4015
  }
4016
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
4017
+ contentEnd = i + 1;
3847
4018
  if (ch === '"') {
3848
- let nextNonWs;
3849
- for (let j = i + 1; j < result.length; j++) {
3850
- const nc = result[j];
3851
- if (nc === " " || nc === " " || nc === "\n" || nc === "\r") continue;
3852
- nextNonWs = result[j];
3853
- break;
3854
- }
3855
- if (nextNonWs === ":") {
3856
- continue;
3857
- } else {
3858
- inString = !inString;
3859
- continue;
3860
- }
4019
+ inString = true;
4020
+ sawKey = true;
4021
+ stringBraceDepth = 0;
4022
+ prevSig = '"';
4023
+ } else if (ch === "{" || ch === "[") {
4024
+ stack.push(ch);
4025
+ prevSig = ch;
4026
+ } else if (ch === "}" || ch === "]") {
4027
+ stack.pop();
4028
+ prevSig = ch;
4029
+ } else {
4030
+ prevSig = ch;
3861
4031
  }
3862
4032
  }
4033
+ if (!sawKey && !inString) return s;
4034
+ let result = s.slice(0, contentEnd);
3863
4035
  if (inString) {
3864
- result = result.trimEnd();
3865
- if (result.endsWith("\\")) result = result.slice(0, -1);
3866
- let depth = 0;
3867
- for (const ch of result) {
3868
- if (ch === "{") depth++;
3869
- else if (ch === "}") depth = Math.max(0, depth - 1);
4036
+ if (escaped) {
4037
+ result = result.slice(0, -1);
4038
+ } else if (endsWithInvalidEscape(result)) {
4039
+ result = result.slice(0, -2);
3870
4040
  }
3871
- result += '"' + "}".repeat(Math.max(1, depth));
4041
+ if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
4042
+ result += '"';
4043
+ } else if (prevSig === ":") {
4044
+ result += "null";
4045
+ }
4046
+ for (let k = stack.length - 1; k >= 0; k--) {
4047
+ result += stack[k] === "{" ? "}" : "]";
4048
+ }
4049
+ if (!tryParse(result).ok) {
4050
+ const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
4051
+ if (tryParse(patched).ok) result = patched;
3872
4052
  }
3873
4053
  return result;
3874
4054
  }
4055
+ var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
4056
+ function endsWithInvalidEscape(str) {
4057
+ const last = str[str.length - 1];
4058
+ if (str[str.length - 2] !== "\\" || last === void 0) return false;
4059
+ if (VALID_ESCAPE.has(last)) return false;
4060
+ let backslashes = 0;
4061
+ for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
4062
+ return backslashes % 2 === 1;
4063
+ }
3875
4064
  function tryParse(s) {
3876
4065
  try {
3877
4066
  return { ok: true, value: JSON.parse(s) };
@@ -4281,6 +4470,7 @@ var HybridCompactor = class {
4281
4470
  }
4282
4471
  async compact(ctx, opts = {}) {
4283
4472
  const beforeTokens = this.estimateMessages(ctx.messages);
4473
+ const beforeFull = this.estimateFullRequest(ctx);
4284
4474
  const reductions = [];
4285
4475
  const policy = readContextWindowPolicy(ctx);
4286
4476
  const preserveK = policy?.preserveK ?? this.preserveK;
@@ -4296,9 +4486,12 @@ var HybridCompactor = class {
4296
4486
  ctx.state.replaceMessages(repaired.messages);
4297
4487
  }
4298
4488
  const afterTokens = this.estimateMessages(ctx.messages);
4489
+ const afterFull = this.estimateFullRequest(ctx);
4299
4490
  return {
4300
4491
  before: beforeTokens,
4301
4492
  after: afterTokens,
4493
+ fullRequestTokensBefore: beforeFull,
4494
+ fullRequestTokensAfter: afterFull,
4302
4495
  reductions,
4303
4496
  repaired: repaired.report.changed ? {
4304
4497
  removedToolUses: repaired.report.removedToolUses,
@@ -4307,6 +4500,14 @@ var HybridCompactor = class {
4307
4500
  } : void 0
4308
4501
  };
4309
4502
  }
4503
+ /**
4504
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
4505
+ * This is the accurate figure for context-window pressure monitoring.
4506
+ */
4507
+ estimateFullRequest(ctx) {
4508
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
4509
+ return breakdown.total;
4510
+ }
4310
4511
  eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
4311
4512
  const messages = ctx.messages;
4312
4513
  let pairCount = 0;
@@ -4452,8 +4653,9 @@ var IntelligentCompactor = class {
4452
4653
  }
4453
4654
  async compact(ctx, opts = {}) {
4454
4655
  const beforeTokens = this.estimateTokens(ctx.messages);
4656
+ const beforeFull = this.estimateFullRequest(ctx);
4455
4657
  const reductions = [];
4456
- const load = beforeTokens / this.maxContext;
4658
+ const load = beforeFull / this.maxContext;
4457
4659
  const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
4458
4660
  const saved1 = this.eliseOldToolResults(ctx);
4459
4661
  if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
@@ -4467,9 +4669,12 @@ var IntelligentCompactor = class {
4467
4669
  const repaired = repairToolUseAdjacency(ctx.messages);
4468
4670
  if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
4469
4671
  const afterTokens = this.estimateTokens(ctx.messages);
4672
+ const afterFull = this.estimateFullRequest(ctx);
4470
4673
  return {
4471
4674
  before: beforeTokens,
4472
4675
  after: afterTokens,
4676
+ fullRequestTokensBefore: beforeFull,
4677
+ fullRequestTokensAfter: afterFull,
4473
4678
  reductions,
4474
4679
  repaired: repaired.report.changed ? {
4475
4680
  removedToolUses: repaired.report.removedToolUses,
@@ -4478,6 +4683,14 @@ var IntelligentCompactor = class {
4478
4683
  } : void 0
4479
4684
  };
4480
4685
  }
4686
+ /**
4687
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
4688
+ * This is the accurate figure for context-window pressure monitoring.
4689
+ */
4690
+ estimateFullRequest(ctx) {
4691
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
4692
+ return breakdown.total;
4693
+ }
4481
4694
  async summarizeAncientTurns(ctx) {
4482
4695
  const messages = ctx.messages;
4483
4696
  const cutoff = Math.max(0, messages.length - this.preserveK * 2);
@@ -4490,29 +4703,26 @@ var IntelligentCompactor = class {
4490
4703
  try {
4491
4704
  summaryText = await this.callSummarizer(toSummarize, ctx);
4492
4705
  } catch {
4493
- const toolNames = /* @__PURE__ */ new Set();
4494
- const filePaths = /* @__PURE__ */ new Set();
4495
- let userTurns = 0;
4496
- let assistantTurns = 0;
4706
+ const preservedMessages = [];
4497
4707
  for (const m of toSummarize) {
4498
- if (m.role === "user") userTurns++;
4499
- else if (m.role === "assistant") {
4500
- assistantTurns++;
4501
- if (Array.isArray(m.content)) {
4502
- for (const b of m.content) {
4503
- if (b.type === "tool_use") toolNames.add(b.name ?? "unknown");
4504
- }
4505
- }
4708
+ if (m.role === "system") continue;
4709
+ if (typeof m.content === "string") {
4710
+ preservedMessages.push(m);
4711
+ } else if (Array.isArray(m.content)) {
4712
+ const textParts = m.content.filter(isTextBlock);
4713
+ const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
4714
+ const next = {
4715
+ role: m.role,
4716
+ content: [
4717
+ ...textParts,
4718
+ ...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
4719
+ ]
4720
+ };
4721
+ preservedMessages.push(next);
4506
4722
  }
4507
- const text = typeof m.content === "string" ? m.content : "";
4508
- const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
4509
- for (const m_ of matches) filePaths.add(m_[0]);
4510
4723
  }
4511
- const parts = [`${toSummarize.length} turns (${userTurns} user, ${assistantTurns} assistant)`];
4512
- if (toolNames.size > 0) parts.push(`tools: ${[...toolNames].join(", ")}`);
4513
- if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
4514
- summaryText = parts.join(" | ");
4515
- if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
4724
+ summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
4725
+ if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
4516
4726
  }
4517
4727
  const summaryMsg = {
4518
4728
  role: "system",
@@ -4758,7 +4968,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
4758
4968
  const res = await this.provider.complete(req, { signal: ac.signal });
4759
4969
  const textBlocks = res.content.filter(isTextBlock);
4760
4970
  raw = textBlocks.map((b) => b.text).join("\n").trim();
4761
- } catch (err) {
4971
+ } catch (_err) {
4762
4972
  return this.fallbackSelect(messages, effectiveBudget);
4763
4973
  }
4764
4974
  return this.parseSelectorOutput(raw, messages.length);
@@ -4850,15 +5060,24 @@ var SelectiveCompactor = class {
4850
5060
  }
4851
5061
  async compact(ctx, opts = {}) {
4852
5062
  const beforeTokens = this.estimateTokens(ctx.messages);
5063
+ const beforeFull = this.estimateFullRequest(ctx);
4853
5064
  const reductions = [];
4854
- const load = beforeTokens / this.maxContext;
5065
+ const load = beforeFull / this.maxContext;
4855
5066
  const shouldCompact = load >= this.warnThreshold || opts.aggressive;
4856
5067
  if (!shouldCompact) {
4857
5068
  const saved = this.eliseOldToolResults(ctx);
4858
5069
  if (saved > 0) reductions.push({ phase: "elision", saved });
4859
5070
  const repair2 = this.repairProtocolAdjacency(ctx);
4860
5071
  const afterTokens2 = this.estimateTokens(ctx.messages);
4861
- return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
5072
+ const afterFull2 = this.estimateFullRequest(ctx);
5073
+ return {
5074
+ before: beforeTokens,
5075
+ after: afterTokens2,
5076
+ fullRequestTokensBefore: beforeFull,
5077
+ fullRequestTokensAfter: afterFull2,
5078
+ reductions,
5079
+ repaired: repair2
5080
+ };
4862
5081
  }
4863
5082
  const savedElision = this.eliseOldToolResults(ctx);
4864
5083
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
@@ -4870,7 +5089,23 @@ var SelectiveCompactor = class {
4870
5089
  }
4871
5090
  const repair = this.repairProtocolAdjacency(ctx);
4872
5091
  const afterTokens = this.estimateTokens(ctx.messages);
4873
- return { before: beforeTokens, after: afterTokens, reductions, repaired: repair };
5092
+ const afterFull = this.estimateFullRequest(ctx);
5093
+ return {
5094
+ before: beforeTokens,
5095
+ after: afterTokens,
5096
+ fullRequestTokensBefore: beforeFull,
5097
+ fullRequestTokensAfter: afterFull,
5098
+ reductions,
5099
+ repaired: repair
5100
+ };
5101
+ }
5102
+ /**
5103
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
5104
+ * This is the accurate figure for context-window pressure monitoring.
5105
+ */
5106
+ estimateFullRequest(ctx) {
5107
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
5108
+ return breakdown.total;
4874
5109
  }
4875
5110
  repairProtocolAdjacency(ctx) {
4876
5111
  const repaired = repairToolUseAdjacency(ctx.messages);
@@ -5060,7 +5295,8 @@ var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
5060
5295
  var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5061
5296
  name = "AutoCompaction";
5062
5297
  compactor;
5063
- estimator;
5298
+ /** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
5299
+ _estimator;
5064
5300
  warnThreshold;
5065
5301
  softThreshold;
5066
5302
  hardThreshold;
@@ -5070,19 +5306,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5070
5306
  events;
5071
5307
  failureMode;
5072
5308
  policyProvider;
5073
- /**
5074
- * Calibration factor applied to the estimator output. The estimator is now
5075
- * expected to already include messages + system prompt + tool definitions
5076
- * (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
5077
- * as a constant so a future calibration against real provider tokenization
5078
- * (BPE vs the chars/3.5 rough estimator) can dial this without touching the
5079
- * threshold-check math.
5080
- *
5081
- * Historical note: was 1.3 when the estimator only counted messages — that
5082
- * double-counted system+tools once the estimator was upgraded, firing
5083
- * compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
5084
- */
5085
- static OVERHEAD_FACTOR = 1;
5309
+ sessionBridge;
5086
5310
  /**
5087
5311
  * Once a compaction attempt reduces nothing (preserveK protects everything,
5088
5312
  * no oversized tool_results remain to elide), retrying on every iteration
@@ -5095,8 +5319,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5095
5319
  lastNoopAttempt = null;
5096
5320
  /**
5097
5321
  * @param compactor Compactor to use for compaction.
5098
- * @param maxContext Provider's max context window in tokens.
5099
- * @param estimator Token estimation function.
5322
+ * @param maxContext Provider's max context window in tokens.
5323
+ * @param _estimator Deprecated parameter kept for backward compatibility.
5324
+ * The middleware now uses `estimateRequestTokens` internally
5325
+ * for accurate full-request token counting (messages +
5326
+ * systemPrompt + toolDefs).
5100
5327
  * @param thresholds Threshold fractions (0-1) of maxContext.
5101
5328
  * @param opts Optional behavior. By default, failures at the
5102
5329
  * hard threshold throw AGENT_CONTEXT_OVERFLOW so
@@ -5104,11 +5331,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5104
5331
  * provider context overflow. Warn/soft failures
5105
5332
  * still emit compaction.failed and continue.
5106
5333
  */
5107
- constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
5334
+ constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
5108
5335
  const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
5109
5336
  this.compactor = compactor;
5110
5337
  this._maxContext = maxContext;
5111
- this.estimator = estimator;
5338
+ this._estimator = _estimator;
5112
5339
  this.warnThreshold = thresholds.warn;
5113
5340
  this.softThreshold = thresholds.soft;
5114
5341
  this.hardThreshold = thresholds.hard;
@@ -5116,6 +5343,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5116
5343
  this.events = opts.events;
5117
5344
  this.failureMode = opts.failureMode ?? "throw_on_hard";
5118
5345
  this.policyProvider = opts.policyProvider;
5346
+ this.sessionBridge = opts.sessionBridge;
5119
5347
  }
5120
5348
  /** Allow callers (e.g. model-switch in WebUI) to update the context window
5121
5349
  * denominator when the active model changes. */
@@ -5124,8 +5352,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5124
5352
  }
5125
5353
  handler() {
5126
5354
  return async (ctx, next) => {
5127
- const rawTokens = this.estimator(ctx);
5128
- const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
5355
+ const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
5129
5356
  const load = tokens / this._maxContext;
5130
5357
  const policy = this.policyProvider?.(ctx);
5131
5358
  const thresholds = policy?.thresholds ?? {
@@ -5160,7 +5387,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5160
5387
  return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
5161
5388
  }
5162
5389
  recordAttempt(level, tokens, report) {
5163
- const reduced = report.before > report.after;
5390
+ const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
5164
5391
  const repaired = !!report.repaired;
5165
5392
  if (reduced || repaired) {
5166
5393
  this.lastNoopAttempt = null;
@@ -5180,6 +5407,15 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5180
5407
  report,
5181
5408
  aggressive
5182
5409
  });
5410
+ await this.sessionBridge?.append({
5411
+ type: "compaction",
5412
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5413
+ before: report.before,
5414
+ after: report.after,
5415
+ level: pressure.level,
5416
+ aggressive,
5417
+ reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved }))
5418
+ });
5183
5419
  ctx.clearFileTracking();
5184
5420
  } catch (err) {
5185
5421
  const error = err instanceof Error ? err : new Error(String(err));
@@ -5210,6 +5446,100 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
5210
5446
  }
5211
5447
  };
5212
5448
 
5449
+ // src/utils/json-schema-validate.ts
5450
+ function validateAgainstSchema(value, schema) {
5451
+ const errors = [];
5452
+ walk3(value, schema, "", errors);
5453
+ return { ok: errors.length === 0, errors };
5454
+ }
5455
+ function walk3(value, schema, path18, errors) {
5456
+ if (schema.enum !== void 0) {
5457
+ if (!schema.enum.some((e) => deepEqual(e, value))) {
5458
+ errors.push({
5459
+ path: path18 || "<root>",
5460
+ message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
5461
+ });
5462
+ return;
5463
+ }
5464
+ }
5465
+ if (typeof schema.type === "string") {
5466
+ if (!checkType(value, schema.type)) {
5467
+ errors.push({
5468
+ path: path18 || "<root>",
5469
+ message: `expected ${schema.type}, got ${describeType(value)}`
5470
+ });
5471
+ return;
5472
+ }
5473
+ }
5474
+ if (schema.type === "object" && isPlainObject(value)) {
5475
+ const obj = value;
5476
+ for (const req of schema.required ?? []) {
5477
+ if (!(req in obj)) {
5478
+ errors.push({ path: joinPath(path18, req), message: "required property missing" });
5479
+ }
5480
+ }
5481
+ if (schema.properties) {
5482
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
5483
+ if (key in obj) {
5484
+ walk3(obj[key], subSchema, joinPath(path18, key), errors);
5485
+ }
5486
+ }
5487
+ }
5488
+ }
5489
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
5490
+ value.forEach((item, i) => walk3(item, schema.items, `${path18}[${i}]`, errors));
5491
+ }
5492
+ }
5493
+ function checkType(value, type) {
5494
+ switch (type) {
5495
+ case "string":
5496
+ return typeof value === "string";
5497
+ case "number":
5498
+ return typeof value === "number" && !Number.isNaN(value);
5499
+ case "integer":
5500
+ return typeof value === "number" && Number.isInteger(value);
5501
+ case "boolean":
5502
+ return typeof value === "boolean";
5503
+ case "null":
5504
+ return value === null;
5505
+ case "array":
5506
+ return Array.isArray(value);
5507
+ case "object":
5508
+ return isPlainObject(value);
5509
+ default:
5510
+ return true;
5511
+ }
5512
+ }
5513
+ function isPlainObject(v) {
5514
+ return typeof v === "object" && v !== null && !Array.isArray(v);
5515
+ }
5516
+ function describeType(v) {
5517
+ if (v === null) return "null";
5518
+ if (Array.isArray(v)) return "array";
5519
+ return typeof v;
5520
+ }
5521
+ function joinPath(parent, key) {
5522
+ if (!parent) return key;
5523
+ return `${parent}.${key}`;
5524
+ }
5525
+ function deepEqual(a, b) {
5526
+ if (a === b) return true;
5527
+ if (typeof a !== typeof b) return false;
5528
+ if (a === null || b === null) return a === b;
5529
+ if (Array.isArray(a) && Array.isArray(b)) {
5530
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
5531
+ }
5532
+ if (typeof a === "object" && typeof b === "object") {
5533
+ const ak = Object.keys(a);
5534
+ const bk = Object.keys(b);
5535
+ if (ak.length !== bk.length) return false;
5536
+ return ak.every(
5537
+ (k) => deepEqual(a[k], b[k])
5538
+ );
5539
+ }
5540
+ return false;
5541
+ }
5542
+
5213
5543
  // src/utils/tool-output-serializer.ts
5214
5544
  function createToolOutputSerializer(opts = {}) {
5215
5545
  const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
@@ -5291,18 +5621,45 @@ var ToolExecutor = class {
5291
5621
  budget = this.decrementBudget(result, budget);
5292
5622
  return { result, tool, durationMs: Date.now() - start };
5293
5623
  }
5624
+ const validation = validateAgainstSchema(use.input, tool.inputSchema);
5625
+ if (!validation.ok) {
5626
+ const errorDetails = validation.errors.map((e) => ` - ${e.path || "input"}: ${e.message}`).join("\n");
5627
+ const result = {
5628
+ type: "tool_result",
5629
+ tool_use_id: use.id,
5630
+ content: `Invalid arguments for tool "${tool.name}".
5631
+
5632
+ Validation errors:
5633
+ ${errorDetails}
5634
+
5635
+ Please call the tool again with arguments that match its inputSchema. You can use the "tool-help" tool with name="${tool.name}" to see the exact expected schema.`,
5636
+ is_error: true
5637
+ };
5638
+ budget = this.decrementBudget(result, budget);
5639
+ return { result, tool, durationMs: Date.now() - start };
5640
+ }
5641
+ const toolDangerousCaps = getDangerousCapabilities(tool);
5642
+ if (toolDangerousCaps.length > 0) {
5643
+ if (this.opts.events) ;
5644
+ }
5294
5645
  if (hasMalformedArguments(use.input)) {
5295
5646
  const result = this.malformedInputResult(use, extractMalformedRaw(use.input));
5296
5647
  budget = this.decrementBudget(result, budget);
5297
5648
  return { result, tool, durationMs: Date.now() - start };
5298
5649
  }
5299
5650
  const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
5300
- if (decision.permission === "deny") {
5651
+ let effectivePermission = decision.permission;
5652
+ const policy = this.opts.permissionPolicy;
5653
+ const yolo = policy.getYolo?.() === true || policy.getForceAllYolo?.() === true;
5654
+ if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
5655
+ effectivePermission = "confirm";
5656
+ }
5657
+ if (effectivePermission === "deny") {
5301
5658
  const result = this.deniedResult(use, decision.reason);
5302
5659
  budget = this.decrementBudget(result, budget);
5303
5660
  return { result, tool, durationMs: Date.now() - start };
5304
5661
  }
5305
- if (decision.permission === "confirm") {
5662
+ if (effectivePermission === "confirm") {
5306
5663
  if (this.opts.confirmAwaiter) {
5307
5664
  const choice = await this.opts.confirmAwaiter(tool, use.input, use.id, tool.name);
5308
5665
  if (choice !== "yes" && choice !== "always") {
@@ -5327,10 +5684,13 @@ var ToolExecutor = class {
5327
5684
  return { result: pending, tool, durationMs: Date.now() - start };
5328
5685
  }
5329
5686
  }
5687
+ const toolCapsForAudit = hasDangerousCapabilityForSubagents(tool) ? tool.capabilities ?? [] : [];
5330
5688
  const span = this.opts.tracer?.startSpan(`tool.${tool.name}`, {
5331
5689
  "tool.name": tool.name,
5332
5690
  "tool.mutating": tool.mutating,
5333
- "tool.permission": tool.permission
5691
+ "tool.permission": tool.permission,
5692
+ "tool.capabilities": JSON.stringify(tool.capabilities ?? []),
5693
+ "tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
5334
5694
  });
5335
5695
  try {
5336
5696
  const result = await this.executeTool(tool, use, ctx, budget);
@@ -6403,19 +6763,16 @@ var SubagentBudget = class _SubagentBudget {
6403
6763
  tokenOutput = 0;
6404
6764
  costUsd = 0;
6405
6765
  startTime = null;
6406
- _onThreshold;
6407
6766
  /**
6408
- * Tracks which budget kinds currently have an extension request
6409
- * in flight. While a kind is here, further `checkLimit` calls for the
6410
- * same kind are no-ops without this dedup, every `recordIteration`
6411
- * after the limit is reached spawns a fresh decision Promise (until
6412
- * the first one lands and patches limits), flooding the FleetBus
6413
- * with redundant threshold events. Cleared in `negotiateExtension`'s
6414
- * `finally`.
6767
+ * Timestamp of the most recent activity (iteration / tool call / token
6768
+ * usage / streamed progress). Drives the idle timeout reset by
6769
+ * `markActivity()`. Initialised to `start()` time so a never-active agent
6770
+ * still eventually trips its idle window.
6415
6771
  */
6416
- pendingExtensions = /* @__PURE__ */ new Set();
6772
+ lastActivityTime = null;
6773
+ _onThreshold;
6417
6774
  /**
6418
- * Hard cap on how long `negotiateExtension` waits for the coordinator to
6775
+ * Hard cap on how long `_negotiateExtension` waits for the coordinator to
6419
6776
  * respond before defaulting to 'stop'. Without this fallback an absent
6420
6777
  * or hung listener (Director not built / event filter detached mid-run)
6421
6778
  * leaves the budget over-limit and never enforces anything.
@@ -6456,6 +6813,24 @@ var SubagentBudget = class _SubagentBudget {
6456
6813
  }
6457
6814
  start() {
6458
6815
  this.startTime = Date.now();
6816
+ this.lastActivityTime = this.startTime;
6817
+ }
6818
+ /**
6819
+ * Reset the idle clock. Called on any sign of forward progress —
6820
+ * iterations, tool calls, token usage, and streamed tool/text progress —
6821
+ * so a long-but-productive subagent never trips its `idleTimeoutMs`.
6822
+ */
6823
+ markActivity() {
6824
+ this.lastActivityTime = Date.now();
6825
+ }
6826
+ /**
6827
+ * Milliseconds since the last activity signal. Returns 0 before `start()`
6828
+ * (nothing to measure yet). Used by the coordinator watchdog to decide
6829
+ * whether to re-arm (still active) or reap (genuinely idle).
6830
+ */
6831
+ idleMs() {
6832
+ const since = this.lastActivityTime ?? this.startTime;
6833
+ return since === null ? 0 : Date.now() - since;
6459
6834
  }
6460
6835
  /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
6461
6836
  isNearLimit() {
@@ -6477,38 +6852,88 @@ var SubagentBudget = class _SubagentBudget {
6477
6852
  * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
6478
6853
  * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
6479
6854
  */
6480
- checkLimit(kind, used, limit) {
6855
+ /**
6856
+ * Collects all exceeded budget kinds into a single NOOP-free negotiation.
6857
+ * Called by recordIteration / recordToolCall / recordUsage — each may call
6858
+ * this for its own kind. The first call starts the negotiation and stores
6859
+ * the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
6860
+ * kinds (while a negotiation is in flight) are NOOPs — they don't start
6861
+ * new conversations with the coordinator. This prevents an EventBus flood
6862
+ * when multiple budget kinds are exceeded simultaneously in one iteration.
6863
+ *
6864
+ * Returns the kinds that were found to be exceeded (for logging/debugging).
6865
+ */
6866
+ checkLimits(elapsedMs) {
6867
+ const exceeded = [];
6868
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
6869
+ exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
6870
+ }
6871
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
6872
+ exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
6873
+ }
6874
+ const totalTokens = this.tokenInput + this.tokenOutput;
6875
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
6876
+ exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
6877
+ }
6878
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6879
+ exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
6880
+ }
6881
+ if (elapsedMs !== void 0) {
6882
+ const idle = this.idleMs();
6883
+ if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
6884
+ exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
6885
+ } else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
6886
+ exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
6887
+ }
6888
+ }
6889
+ if (exceeded.length === 0) return [];
6481
6890
  if (!this._onThreshold) {
6482
- throw new BudgetExceededError(kind, limit, used);
6891
+ const first2 = exceeded[0];
6892
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
6483
6893
  }
6484
6894
  if (this._mode === "sync") {
6485
- throw new BudgetExceededError(kind, limit, used);
6895
+ const first2 = exceeded[0];
6896
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
6486
6897
  }
6487
6898
  const bus = this._events;
6488
6899
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
6489
- throw new BudgetExceededError(kind, limit, used);
6900
+ const first2 = exceeded[0];
6901
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
6490
6902
  }
6491
- if (this.pendingExtensions.has(kind)) return;
6492
- this.pendingExtensions.add(kind);
6493
- const decision = this.negotiateExtension(kind, used, limit);
6494
- throw new BudgetThresholdSignal(kind, limit, used, decision);
6903
+ for (const entry of exceeded) {
6904
+ if (this._pendingNegotiations.has(entry.kind)) continue;
6905
+ const decision2 = this._negotiateExtension(entry.kind, exceeded);
6906
+ this._pendingNegotiations.set(entry.kind, decision2);
6907
+ }
6908
+ const first = exceeded[0];
6909
+ const decision = this._pendingNegotiations.get(first.kind);
6910
+ throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
6495
6911
  }
6912
+ /**
6913
+ * Per-kind in-flight negotiation Promises. Each budget kind can have its
6914
+ * own concurrent negotiation — e.g. iterations and timeout can both
6915
+ * be exceeded simultaneously without blocking each other. The same kind
6916
+ * cannot start two concurrent negotiations (dedup guard).
6917
+ * Cleared in `_negotiateExtension`'s `finally` block.
6918
+ */
6919
+ _pendingNegotiations = /* @__PURE__ */ new Map();
6496
6920
  /**
6497
6921
  * Drive the threshold handler to a decision. Resolves with `'stop'`
6498
6922
  * (signal the runner to abort) or `{ extend: ... }` (limits already
6499
- * patched in-place; the runner should not abort). Always releases the
6500
- * `pendingExtensions` slot in `finally`.
6923
+ * patched in-place; the runner should not abort). Clears the
6924
+ * per-kind slot in `_pendingNegotiations` in `finally`.
6501
6925
  *
6502
6926
  * The 'continue' return from a sync handler is treated as
6503
6927
  * `{ extend: {} }` — keep going without patching; next overrun fires
6504
6928
  * a fresh signal.
6505
6929
  */
6506
- async negotiateExtension(kind, used, limit) {
6930
+ async _negotiateExtension(kind, exceeded) {
6507
6931
  try {
6932
+ const first = exceeded[0];
6508
6933
  const result = this._onThreshold({
6509
- kind,
6510
- used,
6511
- limit,
6934
+ kind: first.kind,
6935
+ used: first.used,
6936
+ limit: first.limit,
6512
6937
  requestDecision: () => {
6513
6938
  const bus = this._events;
6514
6939
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
@@ -6525,20 +6950,22 @@ var SubagentBudget = class _SubagentBudget {
6525
6950
  () => respond("stop"),
6526
6951
  _SubagentBudget.DECISION_TIMEOUT_MS
6527
6952
  );
6528
- bus.emit("budget.threshold_reached", {
6529
- kind,
6530
- used,
6531
- limit,
6532
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
6533
- extend: (extra) => {
6534
- clearTimeout(fallback);
6535
- respond({ extend: extra });
6536
- },
6537
- deny: () => {
6538
- clearTimeout(fallback);
6539
- respond("stop");
6540
- }
6541
- });
6953
+ for (const { kind: kind2, used, limit } of exceeded) {
6954
+ bus.emit("budget.threshold_reached", {
6955
+ kind: kind2,
6956
+ used,
6957
+ limit,
6958
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
6959
+ extend: (extra) => {
6960
+ clearTimeout(fallback);
6961
+ respond({ extend: extra });
6962
+ },
6963
+ deny: () => {
6964
+ clearTimeout(fallback);
6965
+ respond("stop");
6966
+ }
6967
+ });
6968
+ }
6542
6969
  });
6543
6970
  }
6544
6971
  });
@@ -6562,54 +6989,59 @@ var SubagentBudget = class _SubagentBudget {
6562
6989
  if (ext.timeoutMs !== void 0) {
6563
6990
  this.limits.timeoutMs = ext.timeoutMs;
6564
6991
  }
6992
+ if (ext.idleTimeoutMs !== void 0) {
6993
+ this.limits.idleTimeoutMs = ext.idleTimeoutMs;
6994
+ }
6565
6995
  return decision;
6566
6996
  } finally {
6567
- this.pendingExtensions.delete(kind);
6997
+ this._pendingNegotiations.delete(kind);
6568
6998
  }
6569
6999
  }
6570
7000
  recordIteration() {
6571
7001
  this.iterations++;
6572
- if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
6573
- void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
6574
- }
7002
+ this.markActivity();
7003
+ void this.checkLimits();
6575
7004
  }
6576
7005
  recordToolCall() {
6577
7006
  this.toolCalls++;
6578
- if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
6579
- void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
6580
- }
7007
+ this.markActivity();
7008
+ void this.checkLimits();
6581
7009
  }
6582
7010
  recordUsage(usage, costUsd = 0) {
6583
7011
  this.tokenInput += usage.input;
6584
7012
  this.tokenOutput += usage.output;
6585
7013
  this.costUsd += costUsd;
6586
- const totalTokens = this.tokenInput + this.tokenOutput;
6587
- if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
6588
- void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
6589
- }
6590
- if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
6591
- void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
6592
- }
7014
+ this.markActivity();
7015
+ void this.checkLimits();
6593
7016
  }
6594
7017
  /**
6595
- * Wall-clock budget check. Unlike other limits, timeout check passes through
6596
- * `checkLimit` and is subject to the same negotiation-mode decision table.
6597
- * In practice, `'sync'` mode (the usual test configuration) means a timeout
6598
- * immediately throws `BudgetExceededError`. In production with a coordinator,
6599
- * a timeout emits `budget.threshold_reached` so the Director can decide whether
6600
- * to extend or abort.
7018
+ * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
7019
+ * wall-clock time cannot be "extended" by the coordinator, so it throws
7020
+ * synchronously rather than entering the negotiation flow.
7021
+ *
7022
+ * Decision table:
7023
+ * - no `onThreshold` handler → throw `BudgetExceededError`
7024
+ * - `mode === 'sync'` → throw `BudgetExceededError`
7025
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
7026
+ * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
6601
7027
  */
6602
7028
  checkTimeout() {
6603
- if (this.startTime === null || this.limits.timeoutMs === void 0) return;
7029
+ if (this.startTime === null) return;
7030
+ const { timeoutMs, idleTimeoutMs } = this.limits;
7031
+ if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
6604
7032
  const elapsed = Date.now() - this.startTime;
6605
- if (elapsed > this.limits.timeoutMs) {
6606
- void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
6607
- }
7033
+ const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
7034
+ const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
7035
+ if (!wallTripped && !idleTripped) return;
7036
+ void this.checkLimits(elapsed);
6608
7037
  }
6609
- /** Returns true if a timeout has occurred without throwing. Useful for races. */
7038
+ /** Returns true if a wall-clock or idle timeout has occurred without throwing. */
6610
7039
  isTimedOut() {
6611
- if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
6612
- return Date.now() - this.startTime > this.limits.timeoutMs;
7040
+ if (this.startTime === null) return false;
7041
+ const { timeoutMs, idleTimeoutMs } = this.limits;
7042
+ if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
7043
+ if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
7044
+ return false;
6613
7045
  }
6614
7046
  usage() {
6615
7047
  return {
@@ -6716,6 +7148,7 @@ function makeAgentSubagentRunner(opts) {
6716
7148
  // Cheap: O(1) per progress event, and the budget short-circuits
6717
7149
  // when timeoutMs is unset (most subagents have one set anyway).
6718
7150
  events.on("tool.progress", () => {
7151
+ ctx.budget.markActivity();
6719
7152
  try {
6720
7153
  ctx.budget.checkTimeout();
6721
7154
  } catch (e) {
@@ -6732,6 +7165,7 @@ function makeAgentSubagentRunner(opts) {
6732
7165
  currentToolName = e.name;
6733
7166
  }),
6734
7167
  events.on("provider.text_delta", (e) => {
7168
+ ctx.budget.markActivity();
6735
7169
  streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
6736
7170
  })
6737
7171
  );
@@ -9301,6 +9735,7 @@ var FLEET_ROSTER = {
9301
9735
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
9302
9736
  )
9303
9737
  };
9738
+ var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
9304
9739
  var FLEET_ROSTER_BUDGETS = {
9305
9740
  "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
9306
9741
  "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
@@ -9312,7 +9747,7 @@ var FLEET_ROSTER_BUDGETS = {
9312
9747
  )
9313
9748
  };
9314
9749
  var GENERIC_SUBAGENT_BUDGET = {
9315
- timeoutMs: 3 * 60 * 60 * 1e3,
9750
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
9316
9751
  maxIterations: 5e3,
9317
9752
  maxToolCalls: 15e3
9318
9753
  };
@@ -9322,7 +9757,13 @@ function applyRosterBudget(cfg) {
9322
9757
  if (!defaultBudget) return cfg;
9323
9758
  return {
9324
9759
  ...cfg,
9325
- timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
9760
+ // Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
9761
+ // do NOT impose the roster's historical multi-hour wall-clock default — it
9762
+ // killed agents that were still actively working. Reaping is idle-based.
9763
+ timeoutMs: cfg.timeoutMs,
9764
+ // Idle window is the default reaper. Resets on activity, so a long-but-
9765
+ // productive run is never killed; only a genuine stall is reaped.
9766
+ idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
9326
9767
  maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
9327
9768
  maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
9328
9769
  maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
@@ -9392,74 +9833,203 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
9392
9833
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
9393
9834
  });
9394
9835
 
9395
- // src/coordination/multi-agent-coordinator.ts
9396
- var DefaultMultiAgentCoordinator = class extends EventEmitter {
9397
- coordinatorId;
9398
- config;
9399
- runner;
9400
- fleetBus;
9401
- subagents = /* @__PURE__ */ new Map();
9402
- pendingTasks = [];
9403
- completedResults = [];
9404
- totalIterations = 0;
9405
- inFlight = 0;
9406
- /**
9407
- * Subagents currently being stopped. Set on entry to `stop()`, cleared
9408
- * once `recordCompletion` lands the terminal TaskResult. Used by
9409
- * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
9410
- * to a subagent the caller has already asked to terminate — closes the
9411
- * assign+terminate race where a fresh task could land on a worker that
9412
- * was about to be killed.
9413
- */
9414
- terminating = /* @__PURE__ */ new Set();
9415
- constructor(config, options = {}) {
9416
- super();
9417
- this.coordinatorId = config.coordinatorId;
9418
- this.config = config;
9419
- this.runner = options.runner;
9420
- }
9421
- /**
9422
- * Replace the runner after construction. Used when the runner depends
9423
- * on infrastructure (e.g. FleetBus) that isn't available until after
9424
- * the coordinator's owning Director is built.
9425
- */
9426
- setRunner(runner) {
9427
- this.runner = runner;
9428
- }
9429
- /**
9430
- * Wire a FleetBus for director-mode event emission. Call after the
9431
- * FleetManager is constructed so the coordinator can emit lifecycle
9432
- * events the TUI and monitoring tools subscribe to.
9433
- */
9434
- setFleetBus(fleet) {
9435
- this.fleetBus = fleet;
9436
- }
9437
- /**
9438
- * Change the in-flight dispatch ceiling at runtime. Lowering does NOT
9439
- * preempt running tasks already-dispatched subagents finish their
9440
- * current task; only future dispatches respect the new cap. Raising
9441
- * immediately tries to fill the freed slots from the pending queue.
9442
- */
9443
- setMaxConcurrent(n) {
9444
- if (!Number.isFinite(n) || n < 1) {
9445
- throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
9446
- }
9447
- this.config.maxConcurrent = Math.floor(n);
9448
- this.tryDispatchNext();
9449
- }
9450
- async spawn(subagent) {
9451
- const id = subagent.id || randomUUID();
9452
- if (this.subagents.has(id)) {
9453
- throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
9454
- }
9455
- const context = {
9456
- subagentId: id,
9457
- tasks: [],
9458
- // Wired later by the caller via setSubagentBridge() once the
9459
- // bidirectional bridge is created. Readers must null-check / use
9460
- // hasParentBridge() the type now reflects this.
9461
- parentBridge: null,
9462
- doneCondition: this.config.doneCondition,
9836
+ // src/coordination/subagent-nicknames.ts
9837
+ var NICKNAME_POOL = {
9838
+ // Physics & fundamental sciences
9839
+ "einstein": { name: "Einstein", domain: "physics" },
9840
+ "newton": { name: "Newton", domain: "physics" },
9841
+ "feynman": { name: "Feynman", domain: "physics" },
9842
+ "dirac": { name: "Dirac", domain: "physics" },
9843
+ "bohr": { name: "Bohr", domain: "physics" },
9844
+ "planck": { name: "Planck", domain: "physics" },
9845
+ "curie": { name: "Curie", domain: "physics" },
9846
+ "fermi": { name: "Fermi", domain: "physics" },
9847
+ "heisenberg": { name: "Heisenberg", domain: "physics" },
9848
+ "schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
9849
+ // Mathematics
9850
+ "euclid": { name: "Euclid", domain: "math" },
9851
+ "gauss": { name: "Gauss", domain: "math" },
9852
+ "turing": { name: "Turing", domain: "math" },
9853
+ "poincare": { name: "Poincar\xE9", domain: "math" },
9854
+ "riemann": { name: "Riemann", domain: "math" },
9855
+ "hilbert": { name: "Hilbert", domain: "math" },
9856
+ "pythagoras": { name: "Pythagoras", domain: "math" },
9857
+ // Computing & information theory
9858
+ "von-neumann": { name: "Von Neumann", domain: "computing" },
9859
+ "shannon": { name: "Shannon", domain: "computing" },
9860
+ "hopper": { name: "Hopper", domain: "computing" },
9861
+ "backus": { name: "Backus", domain: "computing" },
9862
+ "knuth": { name: "Knuth", domain: "computing" },
9863
+ "torvalds": { name: "Torvalds", domain: "computing" },
9864
+ "stallman": { name: "Stallman", domain: "computing" },
9865
+ "berners-lee": { name: "Berners-Lee", domain: "computing" },
9866
+ "babbage": { name: "Babbage", domain: "computing" },
9867
+ "lovelace": { name: "Lovelace", domain: "computing" },
9868
+ "klein": { name: "Klein", domain: "computing" },
9869
+ // Electronics & electrical engineering
9870
+ "edison": { name: "Edison", domain: "ee" },
9871
+ "tesla": { name: "Tesla", domain: "ee" },
9872
+ "faraday": { name: "Faraday", domain: "ee" },
9873
+ "maxwell": { name: "Maxwell", domain: "ee" },
9874
+ "ohm": { name: "Ohm", domain: "ee" },
9875
+ "bell": { name: "Bell", domain: "ee" },
9876
+ "marconi": { name: "Marconi", domain: "ee" },
9877
+ "lamarr": { name: "Lamarr", domain: "ee" },
9878
+ // General science / multi-disciplinary
9879
+ "darwin": { name: "Darwin", domain: "biology" },
9880
+ "mendel": { name: "Mendel", domain: "biology" },
9881
+ "pasteur": { name: "Pasteur", domain: "biology" },
9882
+ "hawking": { name: "Hawking", domain: "cosmology" },
9883
+ "sagan": { name: "Sagan", domain: "cosmology" },
9884
+ // Chemistry / materials
9885
+ "lavoisier": { name: "Lavoisier", domain: "chemistry" },
9886
+ "mendeleev": { name: "Mendeleev", domain: "chemistry" }
9887
+ };
9888
+ var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
9889
+ var DOMAIN_PREFERENCES = {
9890
+ "security": ["shannon", "turing", "lamarr", "stallman"],
9891
+ "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
9892
+ "refactor": ["gauss", "hilbert", "euclid", "planck"],
9893
+ "audit-log": ["sagan", "hawking", "poincare", "newton"],
9894
+ "planner": ["hilbert", "gauss", "turing", "euclid"],
9895
+ "researcher": ["sagan", "hawking", "darwin", "pasteur"],
9896
+ "explorer": ["marconi", "bell", "columbus", "polo"],
9897
+ "testing": ["pasteur", "curie", "fermi", "bohr"],
9898
+ "frontend": ["lovelace", "hopper", "babbage", "backus"],
9899
+ "backend": ["torvalds", "stallman", "von-neumann", "backus"],
9900
+ "database": ["turing", "shannon", "backus", "knuth"],
9901
+ "devops": ["tesla", "edison", "faraday", "bell"],
9902
+ "security-scanner": ["shannon", "turing", "lamarr", "stallman"],
9903
+ "refactor-planner": ["gauss", "hilbert", "planck", "newton"],
9904
+ "architect": ["von-neumann", "turing", "gauss", "hilbert"],
9905
+ "critic": ["einstein", "feynman", "dirac", "bohr"],
9906
+ "e2e": ["hopper", "bell", "marconi", "tesla"],
9907
+ "performance": ["knuth", "gauss", "planck", "feynman"],
9908
+ "chaos": ["tesla", "edison", "curie", "fermi"],
9909
+ "cost": ["ohm", "bell", "marconi", "tesla"],
9910
+ // default fallback
9911
+ "default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
9912
+ };
9913
+ function assignNickname(role, used) {
9914
+ const preferences = [
9915
+ ...DOMAIN_PREFERENCES[role] ?? [],
9916
+ ...DOMAIN_PREFERENCES["default"] ?? []
9917
+ ];
9918
+ for (const key of preferences) {
9919
+ const entry = NICKNAME_POOL[key];
9920
+ if (entry && !used.has(key)) {
9921
+ return `${entry.name} (${formatRole(role)})`;
9922
+ }
9923
+ }
9924
+ for (const entry of ALL_NICKNAMES) {
9925
+ const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
9926
+ if (key && !used.has(key)) {
9927
+ return `${entry.name} (${formatRole(role)})`;
9928
+ }
9929
+ }
9930
+ const counter = used.size + 1;
9931
+ return `Scientist #${counter} (${formatRole(role)})`;
9932
+ }
9933
+ function formatRole(role) {
9934
+ return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
9935
+ }
9936
+
9937
+ // src/coordination/multi-agent-coordinator.ts
9938
+ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9939
+ coordinatorId;
9940
+ config;
9941
+ runner;
9942
+ fleetBus;
9943
+ subagents = /* @__PURE__ */ new Map();
9944
+ /**
9945
+ * Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
9946
+ * Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
9947
+ * (parallel/eternal engine, SDD parallel run) don't go through
9948
+ * `Director.spawn()` where nicknames are normally assigned, so the
9949
+ * coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
9950
+ * names) to memorable ones here — that's what surfaces in the fleet monitor.
9951
+ */
9952
+ usedNicknames = /* @__PURE__ */ new Set();
9953
+ pendingTasks = [];
9954
+ completedResults = [];
9955
+ totalIterations = 0;
9956
+ inFlight = 0;
9957
+ /**
9958
+ * Subagents currently being stopped. Set on entry to `stop()`, cleared
9959
+ * once `recordCompletion` lands the terminal TaskResult. Used by
9960
+ * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
9961
+ * to a subagent the caller has already asked to terminate — closes the
9962
+ * assign+terminate race where a fresh task could land on a worker that
9963
+ * was about to be killed.
9964
+ */
9965
+ terminating = /* @__PURE__ */ new Set();
9966
+ constructor(config, options = {}) {
9967
+ super();
9968
+ this.coordinatorId = config.coordinatorId;
9969
+ this.config = config;
9970
+ this.runner = options.runner;
9971
+ }
9972
+ /**
9973
+ * Replace the runner after construction. Used when the runner depends
9974
+ * on infrastructure (e.g. FleetBus) that isn't available until after
9975
+ * the coordinator's owning Director is built.
9976
+ */
9977
+ setRunner(runner) {
9978
+ this.runner = runner;
9979
+ }
9980
+ /**
9981
+ * Wire a FleetBus for director-mode event emission. Call after the
9982
+ * FleetManager is constructed so the coordinator can emit lifecycle
9983
+ * events the TUI and monitoring tools subscribe to.
9984
+ */
9985
+ setFleetBus(fleet) {
9986
+ this.fleetBus = fleet;
9987
+ }
9988
+ /**
9989
+ * Change the in-flight dispatch ceiling at runtime. Lowering does NOT
9990
+ * preempt running tasks — already-dispatched subagents finish their
9991
+ * current task; only future dispatches respect the new cap. Raising
9992
+ * immediately tries to fill the freed slots from the pending queue.
9993
+ */
9994
+ setMaxConcurrent(n) {
9995
+ if (!Number.isFinite(n) || n < 1) {
9996
+ throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
9997
+ }
9998
+ this.config.maxConcurrent = Math.floor(n);
9999
+ this.tryDispatchNext();
10000
+ }
10001
+ /**
10002
+ * Upgrade a placeholder/role-derived name to a memorable scientist nickname
10003
+ * (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
10004
+ * empty, equals the role (case-insensitive), is a generic default
10005
+ * ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
10006
+ * Explicit, human-chosen names — including nicknames already assigned by
10007
+ * `Director.spawn()` — are left untouched, so this never double-assigns.
10008
+ */
10009
+ withNickname(subagent) {
10010
+ const role = subagent.role ?? "subagent";
10011
+ const name = subagent.name?.trim() ?? "";
10012
+ const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
10013
+ if (!isPlaceholder) return subagent;
10014
+ const nickname = assignNickname(role, this.usedNicknames);
10015
+ const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
10016
+ this.usedNicknames.add(baseKey);
10017
+ return { ...subagent, name: nickname };
10018
+ }
10019
+ async spawn(subagent) {
10020
+ subagent = this.withNickname(subagent);
10021
+ const id = subagent.id || randomUUID();
10022
+ if (this.subagents.has(id)) {
10023
+ throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
10024
+ }
10025
+ const context = {
10026
+ subagentId: id,
10027
+ tasks: [],
10028
+ // Wired later by the caller via setSubagentBridge() once the
10029
+ // bidirectional bridge is created. Readers must null-check / use
10030
+ // hasParentBridge() — the type now reflects this.
10031
+ parentBridge: null,
10032
+ doneCondition: this.config.doneCondition,
9463
10033
  maxConcurrent: this.config.maxConcurrent ?? 16
9464
10034
  };
9465
10035
  this.subagents.set(id, {
@@ -9755,13 +10325,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9755
10325
  const rawMaxTokens = subagent.config.maxTokens;
9756
10326
  const rawMaxCostUsd = subagent.config.maxCostUsd;
9757
10327
  const rawTimeoutMs = subagent.config.timeoutMs;
10328
+ const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
9758
10329
  const configWithRosterDefaults = applyRosterBudget(subagent.config);
9759
10330
  const budget = new SubagentBudget({
9760
10331
  maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
9761
10332
  maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
9762
10333
  maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
9763
10334
  maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
9764
- timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs
10335
+ // Wall-clock cap is opt-in (explicit config / defaultBudget only); the
10336
+ // roster no longer supplies one. Idle is the default reaper.
10337
+ timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
10338
+ idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
9765
10339
  });
9766
10340
  subagent.activeBudget = budget;
9767
10341
  if (!this.runner) {
@@ -9808,63 +10382,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
9808
10382
  }
9809
10383
  async executeWithTimeout(runner, task, ctx, budget) {
9810
10384
  const initialTimeoutMs = budget.limits.timeoutMs;
9811
- if (initialTimeoutMs === void 0) return runner(task, ctx);
10385
+ const idleLimitMs = budget.limits.idleTimeoutMs;
10386
+ if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
10387
+ return runner(task, ctx);
10388
+ }
9812
10389
  const start = Date.now();
9813
10390
  let timer = null;
9814
10391
  const timeoutPromise = new Promise((_, reject) => {
9815
10392
  const armFor = (ms) => {
9816
10393
  if (timer) clearTimeout(timer);
9817
- timer = setTimeout(async () => {
9818
- const elapsed = Date.now() - start;
9819
- const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
9820
- if (!budget.onThreshold) {
9821
- this.subagents.get(ctx.subagentId)?.abortController.abort();
9822
- reject(new BudgetExceededError("timeout", limit, elapsed));
10394
+ timer = setTimeout(onTick, Math.max(0, ms));
10395
+ };
10396
+ const scheduleNext = () => {
10397
+ const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
10398
+ const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
10399
+ armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
10400
+ };
10401
+ const onTick = async () => {
10402
+ const elapsed = Date.now() - start;
10403
+ const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
10404
+ const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
10405
+ const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
10406
+ const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
10407
+ if (idleExceeded && !wallExceeded) {
10408
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10409
+ reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
10410
+ return;
10411
+ }
10412
+ if (!wallExceeded) {
10413
+ scheduleNext();
10414
+ return;
10415
+ }
10416
+ const limit = wallLimit;
10417
+ if (!budget.onThreshold) {
10418
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10419
+ reject(new BudgetExceededError("timeout", limit, elapsed));
10420
+ return;
10421
+ }
10422
+ try {
10423
+ const result = budget.onThreshold({
10424
+ kind: "timeout",
10425
+ used: elapsed,
10426
+ limit,
10427
+ requestDecision: () => new Promise((resolveDecision) => {
10428
+ budget._events?.emit("budget.threshold_reached", {
10429
+ kind: "timeout",
10430
+ used: elapsed,
10431
+ limit,
10432
+ timeoutMs: 6e4,
10433
+ extend: (extra) => resolveDecision({ extend: extra }),
10434
+ deny: () => resolveDecision("stop")
10435
+ });
10436
+ })
10437
+ });
10438
+ const decision = typeof result === "string" ? result : await result;
10439
+ if (decision === "continue" || decision === "throw" || decision === "stop") {
10440
+ armFor(Math.max(1e3, limit));
9823
10441
  return;
9824
10442
  }
9825
- try {
9826
- const result = budget.onThreshold({
9827
- kind: "timeout",
9828
- used: elapsed,
9829
- limit,
9830
- requestDecision: () => new Promise((resolveDecision) => {
9831
- budget._events?.emit("budget.threshold_reached", {
9832
- kind: "timeout",
9833
- used: elapsed,
9834
- limit,
9835
- timeoutMs: 6e4,
9836
- extend: (extra) => resolveDecision({ extend: extra }),
9837
- deny: () => resolveDecision("stop")
9838
- });
9839
- })
9840
- });
9841
- const decision = typeof result === "string" ? result : await result;
9842
- if (decision === "continue") {
9843
- armFor(Math.max(1e3, limit));
9844
- return;
9845
- }
9846
- if (decision === "throw" || decision === "stop") {
9847
- armFor(Math.max(1e3, limit));
9848
- return;
9849
- }
9850
- if (decision.extend.timeoutMs !== void 0) {
9851
- budget.limits.timeoutMs = decision.extend.timeoutMs;
9852
- const newLimit = decision.extend.timeoutMs;
9853
- const remaining = Math.max(1e3, newLimit - elapsed);
9854
- armFor(remaining);
9855
- return;
9856
- }
9857
- this.subagents.get(ctx.subagentId)?.abortController.abort();
9858
- reject(new BudgetExceededError("timeout", limit, elapsed));
9859
- } catch (err) {
9860
- this.subagents.get(ctx.subagentId)?.abortController.abort();
9861
- reject(
9862
- err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
9863
- );
10443
+ if (decision.extend.timeoutMs !== void 0) {
10444
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
10445
+ scheduleNext();
10446
+ return;
9864
10447
  }
9865
- }, ms);
10448
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10449
+ reject(new BudgetExceededError("timeout", limit, elapsed));
10450
+ } catch (err) {
10451
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
10452
+ reject(
10453
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
10454
+ );
10455
+ }
9866
10456
  };
9867
- armFor(initialTimeoutMs);
10457
+ scheduleNext();
9868
10458
  });
9869
10459
  try {
9870
10460
  return await Promise.race([runner(task, ctx), timeoutPromise]);
@@ -10041,12 +10631,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
10041
10631
  var ParallelEternalEngine = class {
10042
10632
  constructor(opts) {
10043
10633
  this.opts = opts;
10044
- this.goalPath = goalFilePath(opts.projectRoot);
10634
+ this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
10045
10635
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
10046
10636
  this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
10047
10637
  this.dispatchEnabled = opts.dispatch !== false;
10048
10638
  this.dispatchClassifier = opts.dispatchClassifier;
10049
- this.agentFactory = opts.subagentFactory ?? (async (config) => ({
10639
+ this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
10050
10640
  agent: this.opts.agent,
10051
10641
  events: this.opts.agent.events
10052
10642
  }));
@@ -10140,7 +10730,6 @@ var ParallelEternalEngine = class {
10140
10730
  }
10141
10731
  const tasks = await this.decomposeGoal(goal);
10142
10732
  if (!tasks || tasks.length === 0) {
10143
- await sleep2(5e3);
10144
10733
  return false;
10145
10734
  }
10146
10735
  const fanOut = await this.fanOut(goal, tasks);
@@ -10161,6 +10750,7 @@ var ParallelEternalEngine = class {
10161
10750
  });
10162
10751
  if (fanOut.goalComplete) {
10163
10752
  this.stopRequested = true;
10753
+ this.state = "stopped";
10164
10754
  return true;
10165
10755
  }
10166
10756
  await this.maybeCompact();
@@ -10341,7 +10931,11 @@ ${lastFew}` : "No prior iterations.",
10341
10931
  } finally {
10342
10932
  clearTimeout(timer);
10343
10933
  }
10344
- } catch {
10934
+ } catch (err) {
10935
+ this.opts.onError?.(
10936
+ err instanceof Error ? err : new Error(String(err)),
10937
+ this.consecutiveFailures
10938
+ );
10345
10939
  return [];
10346
10940
  }
10347
10941
  }
@@ -10702,7 +11296,11 @@ Working rules:
10702
11296
  thrashing, terminate it rather than letting cost climb silently.
10703
11297
  6. Never claim a subagent's work as your own without verifying it. If a
10704
11298
  result looks wrong, ask_subagent for clarification before passing it
10705
- to the user.`;
11299
+ to the user.
11300
+ 7. Wind down when satisfied. When the results are good enough, call
11301
+ work_complete \u2014 no new subagents will spawn and queued tasks complete
11302
+ as aborted. Running subagents finish naturally. Call terminate_subagent
11303
+ only for ones you need to stop immediately.`;
10706
11304
  var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
10707
11305
  a specific slice of a larger plan \u2014 do that slice well and report back.
10708
11306
 
@@ -10847,14 +11445,12 @@ var FleetBus = class {
10847
11445
  };
10848
11446
  var FleetUsageAggregator = class {
10849
11447
  constructor(bus, priceLookup, metaLookup) {
10850
- this.bus = bus;
10851
11448
  this.priceLookup = priceLookup;
10852
11449
  this.metaLookup = metaLookup;
10853
11450
  bus.filter("provider.response", (e) => this.onProviderResponse(e));
10854
11451
  bus.filter("tool.executed", (e) => this.onToolExecuted(e));
10855
11452
  bus.filter("iteration.started", (e) => this.onIterationStarted(e));
10856
11453
  }
10857
- bus;
10858
11454
  priceLookup;
10859
11455
  metaLookup;
10860
11456
  perSubagent = /* @__PURE__ */ new Map();
@@ -10900,128 +11496,28 @@ var FleetUsageAggregator = class {
10900
11496
  snap.cacheRead += usage.cacheRead ?? 0;
10901
11497
  snap.cacheWrite += usage.cacheWrite ?? 0;
10902
11498
  this.total.input += usage.input ?? 0;
10903
- this.total.output += usage.output ?? 0;
10904
- this.total.cacheRead += usage.cacheRead ?? 0;
10905
- this.total.cacheWrite += usage.cacheWrite ?? 0;
10906
- const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
10907
- if (price) {
10908
- const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
10909
- snap.cost += delta;
10910
- this.total.cost += delta;
10911
- }
10912
- snap.lastEventAt = e.ts;
10913
- }
10914
- onToolExecuted(e) {
10915
- const snap = this.ensure(e.subagentId);
10916
- snap.toolCalls += 1;
10917
- snap.lastEventAt = e.ts;
10918
- }
10919
- onIterationStarted(e) {
10920
- const snap = this.ensure(e.subagentId);
10921
- snap.iterations += 1;
10922
- snap.lastEventAt = e.ts;
10923
- }
10924
- };
10925
-
10926
- // src/coordination/subagent-nicknames.ts
10927
- var NICKNAME_POOL = {
10928
- // Physics & fundamental sciences
10929
- "einstein": { name: "Einstein", domain: "physics" },
10930
- "newton": { name: "Newton", domain: "physics" },
10931
- "feynman": { name: "Feynman", domain: "physics" },
10932
- "dirac": { name: "Dirac", domain: "physics" },
10933
- "bohr": { name: "Bohr", domain: "physics" },
10934
- "planck": { name: "Planck", domain: "physics" },
10935
- "curie": { name: "Curie", domain: "physics" },
10936
- "fermi": { name: "Fermi", domain: "physics" },
10937
- "heisenberg": { name: "Heisenberg", domain: "physics" },
10938
- "schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
10939
- // Mathematics
10940
- "euclid": { name: "Euclid", domain: "math" },
10941
- "gauss": { name: "Gauss", domain: "math" },
10942
- "turing": { name: "Turing", domain: "math" },
10943
- "poincare": { name: "Poincar\xE9", domain: "math" },
10944
- "riemann": { name: "Riemann", domain: "math" },
10945
- "hilbert": { name: "Hilbert", domain: "math" },
10946
- "pythagoras": { name: "Pythagoras", domain: "math" },
10947
- // Computing & information theory
10948
- "von-neumann": { name: "Von Neumann", domain: "computing" },
10949
- "shannon": { name: "Shannon", domain: "computing" },
10950
- "hopper": { name: "Hopper", domain: "computing" },
10951
- "backus": { name: "Backus", domain: "computing" },
10952
- "knuth": { name: "Knuth", domain: "computing" },
10953
- "torvalds": { name: "Torvalds", domain: "computing" },
10954
- "stallman": { name: "Stallman", domain: "computing" },
10955
- "berners-lee": { name: "Berners-Lee", domain: "computing" },
10956
- "babbage": { name: "Babbage", domain: "computing" },
10957
- "lovelace": { name: "Lovelace", domain: "computing" },
10958
- "klein": { name: "Klein", domain: "computing" },
10959
- // Electronics & electrical engineering
10960
- "edison": { name: "Edison", domain: "ee" },
10961
- "tesla": { name: "Tesla", domain: "ee" },
10962
- "faraday": { name: "Faraday", domain: "ee" },
10963
- "maxwell": { name: "Maxwell", domain: "ee" },
10964
- "ohm": { name: "Ohm", domain: "ee" },
10965
- "bell": { name: "Bell", domain: "ee" },
10966
- "marconi": { name: "Marconi", domain: "ee" },
10967
- "lamarr": { name: "Lamarr", domain: "ee" },
10968
- // General science / multi-disciplinary
10969
- "darwin": { name: "Darwin", domain: "biology" },
10970
- "mendel": { name: "Mendel", domain: "biology" },
10971
- "pasteur": { name: "Pasteur", domain: "biology" },
10972
- "hawking": { name: "Hawking", domain: "cosmology" },
10973
- "sagan": { name: "Sagan", domain: "cosmology" },
10974
- // Chemistry / materials
10975
- "lavoisier": { name: "Lavoisier", domain: "chemistry" },
10976
- "mendeleev": { name: "Mendeleev", domain: "chemistry" }
10977
- };
10978
- var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
10979
- var DOMAIN_PREFERENCES = {
10980
- "security": ["shannon", "turing", "lamarr", "stallman"],
10981
- "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
10982
- "refactor": ["gauss", "hilbert", "euclid", "planck"],
10983
- "audit-log": ["sagan", "hawking", "poincare", "newton"],
10984
- "planner": ["hilbert", "gauss", "turing", "euclid"],
10985
- "researcher": ["sagan", "hawking", "darwin", "pasteur"],
10986
- "explorer": ["marconi", "bell", "columbus", "polo"],
10987
- "testing": ["pasteur", "curie", "fermi", "bohr"],
10988
- "frontend": ["lovelace", "hopper", "babbage", "backus"],
10989
- "backend": ["torvalds", "stallman", "von-neumann", "backus"],
10990
- "database": ["turing", "shannon", "backus", "knuth"],
10991
- "devops": ["tesla", "edison", "faraday", "bell"],
10992
- "security-scanner": ["shannon", "turing", "lamarr", "stallman"],
10993
- "refactor-planner": ["gauss", "hilbert", "planck", "newton"],
10994
- "architect": ["von-neumann", "turing", "gauss", "hilbert"],
10995
- "critic": ["einstein", "feynman", "dirac", "bohr"],
10996
- "e2e": ["hopper", "bell", "marconi", "tesla"],
10997
- "performance": ["knuth", "gauss", "planck", "feynman"],
10998
- "chaos": ["tesla", "edison", "curie", "fermi"],
10999
- "cost": ["oh", "bell", "marconi", "tesla"],
11000
- // default fallback
11001
- "default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
11002
- };
11003
- function assignNickname(role, used) {
11004
- const preferences = [
11005
- ...DOMAIN_PREFERENCES[role] ?? [],
11006
- ...DOMAIN_PREFERENCES["default"] ?? []
11007
- ];
11008
- for (const key of preferences) {
11009
- if (!used.has(key)) {
11010
- return `${NICKNAME_POOL[key].name} (${formatRole(role)})`;
11499
+ this.total.output += usage.output ?? 0;
11500
+ this.total.cacheRead += usage.cacheRead ?? 0;
11501
+ this.total.cacheWrite += usage.cacheWrite ?? 0;
11502
+ const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
11503
+ if (price) {
11504
+ const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
11505
+ snap.cost += delta;
11506
+ this.total.cost += delta;
11011
11507
  }
11508
+ snap.lastEventAt = e.ts;
11012
11509
  }
11013
- for (const entry of ALL_NICKNAMES) {
11014
- const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
11015
- if (key && !used.has(key)) {
11016
- return `${entry.name} (${formatRole(role)})`;
11017
- }
11510
+ onToolExecuted(e) {
11511
+ const snap = this.ensure(e.subagentId);
11512
+ snap.toolCalls += 1;
11513
+ snap.lastEventAt = e.ts;
11018
11514
  }
11019
- const counter = used.size + 1;
11020
- return `Scientist #${counter} (${formatRole(role)})`;
11021
- }
11022
- function formatRole(role) {
11023
- return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
11024
- }
11515
+ onIterationStarted(e) {
11516
+ const snap = this.ensure(e.subagentId);
11517
+ snap.iterations += 1;
11518
+ snap.lastEventAt = e.ts;
11519
+ }
11520
+ };
11025
11521
  function makeSpawnTool(director, roster) {
11026
11522
  const inputSchema = {
11027
11523
  type: "object",
@@ -11192,7 +11688,7 @@ function makeRollUpTool(director) {
11192
11688
  function makeTerminateTool(director) {
11193
11689
  return {
11194
11690
  name: "terminate_subagent",
11195
- description: "Forcibly abort a subagent.",
11691
+ description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
11196
11692
  permission: "auto",
11197
11693
  mutating: true,
11198
11694
  inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
@@ -11203,6 +11699,19 @@ function makeTerminateTool(director) {
11203
11699
  }
11204
11700
  };
11205
11701
  }
11702
+ function makeTerminateAllTool(director) {
11703
+ return {
11704
+ name: "terminate_all",
11705
+ 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.',
11706
+ permission: "auto",
11707
+ mutating: true,
11708
+ inputSchema: { type: "object", properties: {}, required: [] },
11709
+ async execute() {
11710
+ await director.terminateAll();
11711
+ return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
11712
+ }
11713
+ };
11714
+ }
11206
11715
  function makeFleetStatusTool(director) {
11207
11716
  return {
11208
11717
  name: "fleet_status",
@@ -11345,7 +11854,7 @@ function makeCollabDebugTool(director) {
11345
11854
  function makeFleetEmitTool(director) {
11346
11855
  return {
11347
11856
  name: "fleet_emit",
11348
- description: "Emit a structured event on the FleetBus. Use this when you find a bug (bug.found), complete a refactor plan (refactor.plan), or finish an evaluation (critic.evaluation). Events are routed to other agents in the collab session.",
11857
+ 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.",
11349
11858
  permission: "auto",
11350
11859
  mutating: false,
11351
11860
  inputSchema: {
@@ -11353,14 +11862,14 @@ function makeFleetEmitTool(director) {
11353
11862
  properties: {
11354
11863
  type: {
11355
11864
  type: "string",
11356
- description: "Event type: bug.found | refactor.plan | critic.evaluation"
11865
+ description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
11357
11866
  },
11358
11867
  payload: {
11359
11868
  type: "object",
11360
- description: "Event payload matching the event type."
11869
+ description: "Event payload. Structure depends on event type. Use null if no payload."
11361
11870
  }
11362
11871
  },
11363
- required: ["type", "payload"]
11872
+ required: ["type"]
11364
11873
  },
11365
11874
  async execute(input) {
11366
11875
  const i = input;
@@ -11368,12 +11877,25 @@ function makeFleetEmitTool(director) {
11368
11877
  subagentId: director.id,
11369
11878
  ts: Date.now(),
11370
11879
  type: i.type,
11371
- payload: i.payload
11880
+ payload: i.payload ?? {}
11372
11881
  });
11373
11882
  return { ok: true, event: i.type };
11374
11883
  }
11375
11884
  };
11376
11885
  }
11886
+ function makeWorkCompleteTool(director) {
11887
+ return {
11888
+ name: "work_complete",
11889
+ 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.",
11890
+ permission: "auto",
11891
+ mutating: false,
11892
+ inputSchema: { type: "object", properties: {}, required: [] },
11893
+ async execute() {
11894
+ director.workComplete();
11895
+ return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
11896
+ }
11897
+ };
11898
+ }
11377
11899
  var CollabSession = class extends EventEmitter {
11378
11900
  sessionId;
11379
11901
  options;
@@ -11388,6 +11910,14 @@ var CollabSession = class extends EventEmitter {
11388
11910
  disposers = new Array();
11389
11911
  settled = false;
11390
11912
  timeoutMs;
11913
+ cancelled = false;
11914
+ alerts = [];
11915
+ /** Tracks tool call counts per subagent for progress-based timeout decisions. */
11916
+ progressBySubagent = /* @__PURE__ */ new Map();
11917
+ /** Last tool call count when a timeout warning was handled. */
11918
+ lastTimeoutProgress = /* @__PURE__ */ new Map();
11919
+ /** Session-level timeout timer handle (cleared on cancel or natural completion). */
11920
+ _timeoutTimer;
11391
11921
  constructor(director, fleetBus, options) {
11392
11922
  super();
11393
11923
  this.sessionId = randomUUID();
@@ -11405,11 +11935,23 @@ var CollabSession = class extends EventEmitter {
11405
11935
  };
11406
11936
  }
11407
11937
  }
11938
+ get id() {
11939
+ return this.sessionId;
11940
+ }
11941
+ getSessionAlerts() {
11942
+ return [...this.alerts];
11943
+ }
11944
+ isCancelled() {
11945
+ return this.cancelled;
11946
+ }
11408
11947
  /**
11409
- * Read the target files from disk and populate the snapshot.
11410
- * Call this after construction if you did not provide a prebuiltSnapshot
11411
- * and want the session to operate on real file contents.
11948
+ * Snapshot of role subagentId map. The Director calls coordinator.stop()
11949
+ * for each agent when cancelling the session, using this map to enumerate
11950
+ * all three collab agents.
11412
11951
  */
11952
+ getSubagentIds() {
11953
+ return new Map(this.subagentIds);
11954
+ }
11413
11955
  async buildSnapshot() {
11414
11956
  if (this.snapshot.files.length > 0) return this.snapshot;
11415
11957
  for (const filePath of this.options.targetPaths) {
@@ -11425,9 +11967,30 @@ var CollabSession = class extends EventEmitter {
11425
11967
  return this.snapshot;
11426
11968
  }
11427
11969
  /**
11428
- * Start the collaborative session: snapshot files, spawn all three agents,
11429
- * wire up event routing, and wait for completion.
11970
+ * Cancel the session. Emits director.cancel_collab on the FleetBus so all
11971
+ * collab agents finish early. The session-level timeout timer is also cleared.
11972
+ * Safe to call multiple times (idempotent after first call).
11430
11973
  */
11974
+ cancel(reason = "Director cancelled collab session") {
11975
+ if (this.settled) return;
11976
+ this.cancelled = true;
11977
+ if (this._timeoutTimer) {
11978
+ clearTimeout(this._timeoutTimer);
11979
+ this._timeoutTimer = void 0;
11980
+ }
11981
+ this.fleetBus.emit({
11982
+ subagentId: this.director.id,
11983
+ ts: Date.now(),
11984
+ type: "director.cancel_collab",
11985
+ payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
11986
+ });
11987
+ this.fleetBus.emit({
11988
+ subagentId: this.director.id,
11989
+ ts: Date.now(),
11990
+ type: "collab.cancelled",
11991
+ payload: { sessionId: this.sessionId, reason }
11992
+ });
11993
+ }
11431
11994
  async start() {
11432
11995
  if (this.settled) throw new Error("session already settled");
11433
11996
  this.settled = true;
@@ -11442,10 +12005,10 @@ var CollabSession = class extends EventEmitter {
11442
12005
  this.subagentIds.set("refactor-planner", refactorPlannerId);
11443
12006
  this.subagentIds.set("critic", criticId);
11444
12007
  const timeout = new Promise((_, reject) => {
11445
- setTimeout(
11446
- () => reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`)),
11447
- this.timeoutMs
11448
- );
12008
+ this._timeoutTimer = setTimeout(() => {
12009
+ this.cancel("Session-level timeout reached");
12010
+ reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
12011
+ }, this.timeoutMs);
11449
12012
  });
11450
12013
  let results;
11451
12014
  try {
@@ -11458,6 +12021,10 @@ var CollabSession = class extends EventEmitter {
11458
12021
  timeout
11459
12022
  ]);
11460
12023
  } catch (err) {
12024
+ if (this._timeoutTimer) {
12025
+ clearTimeout(this._timeoutTimer);
12026
+ this._timeoutTimer = void 0;
12027
+ }
11461
12028
  this.cleanup();
11462
12029
  const error = err instanceof Error ? err : new Error(String(err));
11463
12030
  this.emit("session.error", error);
@@ -11471,17 +12038,6 @@ var CollabSession = class extends EventEmitter {
11471
12038
  this.emit("session.done", report);
11472
12039
  return report;
11473
12040
  }
11474
- /**
11475
- * Parse a completed agent's final text output and route the structured
11476
- * findings onto the FleetBus. Agents are instructed to emit one JSON object
11477
- * per finding / plan / evaluation; we scan the output for balanced JSON
11478
- * objects (tolerating prose, markdown, and ```json fences around them) and
11479
- * dispatch each by its discriminating key. The wired FleetBus listeners then
11480
- * collect them into the bugs / plans / evaluations maps.
11481
- *
11482
- * Failed or empty results are ignored, and a single malformed object never
11483
- * aborts the collation pass — see {@link extractJsonObjects}.
11484
- */
11485
12041
  async parseAndEmit(result) {
11486
12042
  if (result.status !== "success" || result.result == null) return;
11487
12043
  const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
@@ -11497,12 +12053,6 @@ var CollabSession = class extends EventEmitter {
11497
12053
  });
11498
12054
  }
11499
12055
  }
11500
- /**
11501
- * Extract every top-level JSON object embedded in free-form agent text.
11502
- * Scans for balanced braces while respecting string literals and escape
11503
- * sequences, so prose, markdown headers, and code fences around the JSON
11504
- * are skipped. Malformed spans are dropped silently rather than thrown.
11505
- */
11506
12056
  extractJsonObjects(text) {
11507
12057
  const objects = [];
11508
12058
  let depth = 0;
@@ -11539,25 +12089,33 @@ var CollabSession = class extends EventEmitter {
11539
12089
  }
11540
12090
  return objects;
11541
12091
  }
12092
+ budgetForRole(role) {
12093
+ if (this.options.budgetOverrides?.[role]) {
12094
+ return this.options.budgetOverrides[role];
12095
+ }
12096
+ const defaults = {
12097
+ "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
12098
+ "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
12099
+ "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
12100
+ };
12101
+ return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
12102
+ }
11542
12103
  async spawnAgent(role, taskBrief) {
12104
+ const budget = this.budgetForRole(role);
11543
12105
  const cfg = {
11544
12106
  id: `${role}-${this.sessionId}`,
11545
12107
  name: role,
11546
- role
11547
- // No fleet_emit tool agents output structured JSON, the Director parses
11548
- // results and emits FleetBus events. This keeps the agent simple and the
11549
- // routing deterministic.
12108
+ role,
12109
+ tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
12110
+ maxIterations: budget.maxIterations,
12111
+ maxToolCalls: budget.maxToolCalls,
12112
+ timeoutMs: budget.timeoutMs
11550
12113
  };
11551
12114
  const subagentId = await this.director.spawn(cfg);
11552
- await this.director.assign({
11553
- id: randomUUID(),
11554
- subagentId,
11555
- description: taskBrief
11556
- });
12115
+ await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
11557
12116
  return subagentId;
11558
12117
  }
11559
12118
  buildBugHunterTask() {
11560
- this.options.targetPaths.join(", ");
11561
12119
  const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
11562
12120
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
11563
12121
  ${f.content}`).join("\n\n");
@@ -11566,13 +12124,17 @@ ${f.content}`).join("\n\n");
11566
12124
  Target files:
11567
12125
  ${fileContents}
11568
12126
 
11569
- For each bug found, write ONE valid JSON line to the scratchpad with this exact structure:
11570
- { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } }.
11571
- After scanning all files, write your full markdown bug report to the shared scratchpad at:
11572
- ${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
12127
+ For each bug found, emit it using the fleet_emit tool immediately:
12128
+ { "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
12129
+
12130
+ After scanning all files, write your full markdown bug report to:
12131
+ ${scratchpad}/bug-hunter-report-${this.sessionId}.md
12132
+
12133
+ Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
11573
12134
  }
11574
12135
  buildRefactorPlannerTask() {
11575
12136
  const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
12137
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
11576
12138
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
11577
12139
  ${f.content}`).join("\n\n");
11578
12140
  return `You are RefactorPlanner. Plan refactorings for the following files.
@@ -11580,26 +12142,146 @@ ${f.content}`).join("\n\n");
11580
12142
  Target files:
11581
12143
  ${fileContents}
11582
12144
 
11583
- Listen for bug.found events from BugHunter on the fleet bus. For each bug, create a refactor plan and write one JSON line per plan:
11584
- { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [...], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } }.
11585
- After planning, write your full markdown plan to:
11586
- ${scratchpad}/refactor-plan-${this.sessionId}.md`;
12145
+ Read the BugHunter report at: ${bugHunterReportPath}
12146
+
12147
+ For each bug you can address, emit a refactor plan using fleet_emit:
12148
+ { "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>" } } }
12149
+
12150
+ Also write your full markdown plan to:
12151
+ ${scratchpad}/refactor-plan-${this.sessionId}.md
12152
+
12153
+ Emit each plan immediately. Do not wait until planning is complete.`;
11587
12154
  }
11588
12155
  buildCriticTask() {
11589
12156
  const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
12157
+ const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
12158
+ const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
11590
12159
  const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
11591
12160
  ${f.content}`).join("\n\n");
11592
- return `You are Critic. Evaluate bug findings and refactor plans as they arrive via fleet bus events.
12161
+ return `You are Critic. Evaluate bug findings and refactor plans.
11593
12162
 
11594
12163
  Target files:
11595
12164
  ${fileContents}
11596
12165
 
11597
- Subscribe to bug.found events (from BugHunter) and refactor.plan events (from RefactorPlanner). For each subject, write one JSON line per evaluation:
11598
- { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": [...], "weaknesses": [...], "concerns": [...] } }.
11599
- After all evaluations, write your markdown critic report to:
11600
- ${scratchpad}/critic-report-${this.sessionId}.md`;
12166
+ Read the BugHunter report at: ${bugHunterReportPath}
12167
+ Read the RefactorPlanner report at: ${refactorPlanPath}
12168
+
12169
+ For each bug and refactor plan, emit your evaluation using fleet_emit:
12170
+ { "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>" }] } } }
12171
+
12172
+ After all evaluations, write your markdown report to:
12173
+ ${scratchpad}/critic-report-${this.sessionId}.md
12174
+
12175
+ Emit each evaluation immediately. Do not wait until you have read all reports.`;
11601
12176
  }
11602
12177
  wireFleetBus() {
12178
+ const dTool = this.fleetBus.filter("tool.executed", (e) => {
12179
+ this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
12180
+ });
12181
+ this.disposers.push(dTool);
12182
+ const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
12183
+ const payload = e.payload;
12184
+ const role = this.roleFromSubagentId(e.subagentId);
12185
+ if (!role) return;
12186
+ const btwNotes = this.director.getLeaderBtwNotes();
12187
+ const alert = {
12188
+ sessionId: this.sessionId,
12189
+ subagentId: e.subagentId,
12190
+ role,
12191
+ level: "warning" /* WARNING */,
12192
+ message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
12193
+ budgetKind: payload.kind,
12194
+ elapsedMs: payload.timeoutMs,
12195
+ limit: payload.limit,
12196
+ btwNotes
12197
+ };
12198
+ this.alerts.push(alert);
12199
+ this.fleetBus.emit({
12200
+ subagentId: e.subagentId,
12201
+ ts: Date.now(),
12202
+ type: "collab.warning",
12203
+ payload: alert
12204
+ });
12205
+ const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
12206
+ if (decision === "cancel") {
12207
+ this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
12208
+ return;
12209
+ }
12210
+ if (payload.kind === "timeout") {
12211
+ const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
12212
+ const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
12213
+ if (progress <= lastProgress) {
12214
+ payload.deny();
12215
+ return;
12216
+ }
12217
+ this.lastTimeoutProgress.set(e.subagentId, progress);
12218
+ const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
12219
+ setImmediate(() => {
12220
+ payload.extend({ timeoutMs: newLimit });
12221
+ });
12222
+ return;
12223
+ }
12224
+ if (decision === "extend") {
12225
+ setImmediate(() => {
12226
+ const base = Math.max(payload.limit, payload.used);
12227
+ const extra = {};
12228
+ switch (payload.kind) {
12229
+ case "iterations":
12230
+ extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
12231
+ break;
12232
+ case "tool_calls":
12233
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
12234
+ break;
12235
+ case "tokens":
12236
+ extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
12237
+ break;
12238
+ case "cost":
12239
+ extra.maxCostUsd = Math.min(base * 1.5, 100);
12240
+ break;
12241
+ }
12242
+ payload.extend(extra);
12243
+ });
12244
+ return;
12245
+ }
12246
+ if (payload.kind !== "timeout") {
12247
+ setImmediate(() => {
12248
+ const base = Math.max(payload.limit, payload.used);
12249
+ const extra = {};
12250
+ switch (payload.kind) {
12251
+ case "iterations":
12252
+ extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
12253
+ break;
12254
+ case "tool_calls":
12255
+ extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
12256
+ break;
12257
+ case "tokens":
12258
+ extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
12259
+ break;
12260
+ case "cost":
12261
+ extra.maxCostUsd = Math.min(base * 1.25, 100);
12262
+ break;
12263
+ }
12264
+ payload.extend(extra);
12265
+ });
12266
+ }
12267
+ });
12268
+ this.disposers.push(dBudget);
12269
+ const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
12270
+ const payload = e.payload;
12271
+ if (payload.sessionId !== this.sessionId) return;
12272
+ this.cancelled = true;
12273
+ if (this._timeoutTimer) {
12274
+ clearTimeout(this._timeoutTimer);
12275
+ this._timeoutTimer = void 0;
12276
+ }
12277
+ this.fleetBus.emit({
12278
+ subagentId: this.director.id,
12279
+ ts: Date.now(),
12280
+ type: "collab.cancelled",
12281
+ payload: { sessionId: this.sessionId, reason: payload.reason }
12282
+ });
12283
+ });
12284
+ this.disposers.push(dCancel);
11603
12285
  const d1 = this.fleetBus.filter("bug.found", (e) => {
11604
12286
  const payload = e.payload;
11605
12287
  if (payload?.finding) {
@@ -11625,10 +12307,19 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
11625
12307
  });
11626
12308
  this.disposers.push(d3);
11627
12309
  }
12310
+ roleFromSubagentId(subagentId) {
12311
+ for (const [role, id] of this.subagentIds) {
12312
+ if (id === subagentId) return role;
12313
+ }
12314
+ const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
12315
+ return match?.[1] ?? null;
12316
+ }
11628
12317
  assembleReport() {
11629
12318
  const bugList = Array.from(this.bugs.values());
11630
12319
  const planList = Array.from(this.plans.values());
11631
12320
  const evalList = Array.from(this.evaluations.values());
12321
+ let disposition = "completed";
12322
+ if (this.cancelled) disposition = "cancelled";
11632
12323
  const verdictOrder = {
11633
12324
  approve: 0,
11634
12325
  needs_revision: 1,
@@ -11642,33 +12333,41 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
11642
12333
  },
11643
12334
  "approve"
11644
12335
  );
11645
- const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict);
12336
+ const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
11646
12337
  return {
11647
12338
  sessionId: this.sessionId,
11648
12339
  startedAt: this.snapshot.createdAt,
11649
12340
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
11650
12341
  targetPaths: this.options.targetPaths,
12342
+ disposition,
11651
12343
  bugs: bugList,
11652
12344
  refactorPlans: planList,
11653
12345
  evaluations: evalList,
12346
+ alerts: [...this.alerts],
11654
12347
  overallVerdict,
11655
12348
  summary
11656
12349
  };
11657
12350
  }
11658
- buildMarkdownSummary(bugs, plans, evals, overallVerdict) {
12351
+ buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
11659
12352
  const lines = [
11660
12353
  `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
11661
12354
  "",
11662
12355
  `**Target:** ${this.options.targetPaths.join(", ")}`,
12356
+ `**Disposition:** ${disposition.toUpperCase()}`,
11663
12357
  `**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
11664
12358
  ""
11665
12359
  ];
12360
+ if (this.alerts.length > 0) {
12361
+ lines.push("### Alerts", "");
12362
+ for (const alert of this.alerts) {
12363
+ lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
12364
+ }
12365
+ lines.push("");
12366
+ }
11666
12367
  if (bugs.length > 0) {
11667
12368
  lines.push("### Bugs Found", "");
11668
12369
  for (const b of bugs) {
11669
- lines.push(
11670
- `- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
11671
- );
12370
+ lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
11672
12371
  }
11673
12372
  lines.push("");
11674
12373
  }
@@ -11687,9 +12386,7 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
11687
12386
  for (const e of evals) {
11688
12387
  lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
11689
12388
  for (const c of e.concerns) {
11690
- if (c.severity === "blocking") {
11691
- lines.push(` - ${c.description}`);
11692
- }
12389
+ if (c.severity === "blocking") lines.push(` - ${c.description}`);
11693
12390
  }
11694
12391
  }
11695
12392
  lines.push("");
@@ -11707,10 +12404,9 @@ var FleetSpawnBudgetError = class extends Error {
11707
12404
  kind;
11708
12405
  limit;
11709
12406
  observed;
11710
- constructor(kind, limit, observed) {
11711
- super(
11712
- 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}`
11713
- );
12407
+ constructor(kind, limit, observed, message) {
12408
+ 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}`;
12409
+ super(message ?? defaultMsg);
11714
12410
  this.name = "FleetSpawnBudgetError";
11715
12411
  this.kind = kind;
11716
12412
  this.limit = limit;
@@ -11731,6 +12427,20 @@ var FleetCostCapError = class extends Error {
11731
12427
  this.observed = observed;
11732
12428
  }
11733
12429
  };
12430
+ var FleetContextOverflowError = class extends Error {
12431
+ kind;
12432
+ limit;
12433
+ observed;
12434
+ constructor(limit, observed) {
12435
+ super(
12436
+ `Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
12437
+ );
12438
+ this.name = "FleetContextOverflowError";
12439
+ this.kind = "max_context_load";
12440
+ this.limit = limit;
12441
+ this.observed = observed;
12442
+ }
12443
+ };
11734
12444
  var Director = class {
11735
12445
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
11736
12446
  get coordinatorId() {
@@ -11747,6 +12457,25 @@ var Director = class {
11747
12457
  * injected; otherwise own FleetUsageAggregator.
11748
12458
  */
11749
12459
  usage;
12460
+ /**
12461
+ * Update the leader agent's current context pressure (full request tokens:
12462
+ * messages + systemPrompt + toolDefs). The director checks this before every
12463
+ * spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
12464
+ * spawning is refused with a `FleetContextOverflowError`.
12465
+ *
12466
+ * Call this after each leader agent iteration to keep the pressure current.
12467
+ * The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
12468
+ */
12469
+ setLeaderContextPressure(tokens) {
12470
+ this.leaderContextPressure = tokens;
12471
+ this.fleetManager?.setLeaderContextPressure(tokens);
12472
+ }
12473
+ /**
12474
+ * Read the leader agent's current context pressure.
12475
+ */
12476
+ getLeaderContextPressure() {
12477
+ return this.leaderContextPressure;
12478
+ }
11750
12479
  /**
11751
12480
  * Optional fleet-level policy container. When provided the Director
11752
12481
  * delegates spawn budgeting, manifest entries, and checkpointing to it
@@ -11832,6 +12561,23 @@ var Director = class {
11832
12561
  taskCompletedListener = null;
11833
12562
  /** Optional LLM classifier for smart dispatch. Passed from options. */
11834
12563
  dispatchClassifier;
12564
+ /** Leader agent's current context pressure (full request tokens). */
12565
+ leaderContextPressure = 0;
12566
+ /** Maximum context load fraction before spawn is refused. */
12567
+ maxLeaderContextLoad;
12568
+ /** Provider's max context window in tokens. */
12569
+ maxContext;
12570
+ /**
12571
+ * When set by `workComplete()`, the director stops dispatching new tasks
12572
+ * and terminates all running subagents. Used when the director's LLM decides
12573
+ * the goal is satisfied and no further spawns are needed — prevents the
12574
+ * coordinator from keeping workers alive for tasks that will never arrive.
12575
+ */
12576
+ workCompleteFlag = false;
12577
+ /** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
12578
+ _leaderBtwNotes = [];
12579
+ /** Active collab sessions tracked by sessionId (see spawnCollab). */
12580
+ _activeCollabSessions = /* @__PURE__ */ new Map();
11835
12581
  constructor(opts) {
11836
12582
  this.id = opts.config.coordinatorId || randomUUID();
11837
12583
  this.manifestPath = opts.manifestPath;
@@ -11847,6 +12593,8 @@ var Director = class {
11847
12593
  this.dispatchClassifier = opts.dispatchClassifier;
11848
12594
  this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
11849
12595
  this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
12596
+ this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
12597
+ this.maxContext = opts.maxContext ?? 128e3;
11850
12598
  this.sessionsRoot = opts.sessionsRoot;
11851
12599
  this.directorRunId = opts.directorRunId ?? this.id;
11852
12600
  this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
@@ -11874,7 +12622,7 @@ var Director = class {
11874
12622
  this.fleet = new FleetBus();
11875
12623
  this.usage = new FleetUsageAggregator(
11876
12624
  this.fleet,
11877
- (id, provider, model) => {
12625
+ (_id, provider, model) => {
11878
12626
  if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
11879
12627
  return void 0;
11880
12628
  },
@@ -11936,6 +12684,9 @@ var Director = class {
11936
12684
  });
11937
12685
  this.fleet.filter("budget.threshold_reached", (e) => {
11938
12686
  const payload = e.payload;
12687
+ if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
12688
+ return;
12689
+ }
11939
12690
  if (payload.kind === "timeout") {
11940
12691
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
11941
12692
  const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
@@ -12014,6 +12765,107 @@ var Director = class {
12014
12765
  extensionsFor(subagentId) {
12015
12766
  return this.extendTotals.get(subagentId) ?? 0;
12016
12767
  }
12768
+ /**
12769
+ * Signal that the director's work is done. Once called:
12770
+ * - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
12771
+ * subagents can be created
12772
+ * - Running subagents are NOT forcibly stopped — they finish naturally,
12773
+ * but no new tasks are dispatched to them
12774
+ *
12775
+ * This lets the director LLM say "I'm satisfied with the results, stop
12776
+ * spawning and wind down" — without killing in-flight work mid-execution.
12777
+ * Call `terminateAll()` separately if you need immediate teardown.
12778
+ *
12779
+ * Idempotent — calling twice is a no-op.
12780
+ */
12781
+ workComplete() {
12782
+ this.workCompleteFlag = true;
12783
+ this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
12784
+ }
12785
+ /** Returns true if `workComplete()` has been called on this director. */
12786
+ isWorkComplete() {
12787
+ return this.workCompleteFlag;
12788
+ }
12789
+ /**
12790
+ * Stashes a /btw note on the leader agent's context. The leader's agent loop
12791
+ * calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
12792
+ * into the conversation as a visible block — no abort, no restart, just a
12793
+ * "by the way" nudge the model picks up on its next turn.
12794
+ *
12795
+ * This is the entry point for the host (CLI, TUI) to inject /btw notes
12796
+ * programmatically without going through the slash-command path.
12797
+ */
12798
+ setLeaderBtwNote(note) {
12799
+ const trimmed = note.trim();
12800
+ if (!trimmed) return this._leaderBtwNotes.length;
12801
+ this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
12802
+ return this._leaderBtwNotes.length;
12803
+ }
12804
+ /**
12805
+ * Read and clear all pending /btw notes the leader has stashed.
12806
+ * Returns them in FIFO order (empty array when none).
12807
+ *
12808
+ * Called by CollabSession when a budget threshold event fires so the
12809
+ * Director can inspect accumulated /btw notes before deciding whether
12810
+ * to cancel the collab session or let it continue.
12811
+ */
12812
+ getLeaderBtwNotes() {
12813
+ const notes = this._leaderBtwNotes;
12814
+ this._leaderBtwNotes = [];
12815
+ return notes;
12816
+ }
12817
+ /**
12818
+ * Peek at pending /btw notes without consuming them.
12819
+ * Useful for UI to show "N pending notes" without clearing them.
12820
+ */
12821
+ peekLeaderBtwNotes() {
12822
+ return [...this._leaderBtwNotes];
12823
+ }
12824
+ /**
12825
+ * Drain (read + clear) all /btw notes in one call.
12826
+ * Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
12827
+ * in the agent's btw.ts. The Director calls this at the point where it
12828
+ * makes a cancellation decision, not on every budget event.
12829
+ */
12830
+ drainLeaderBtwNotes() {
12831
+ return this.getLeaderBtwNotes();
12832
+ }
12833
+ /**
12834
+ * Cancel an active collab session by its id.
12835
+ * Emits `director.cancel_collab` on the FleetBus so the session's agents
12836
+ * finish early with a 'cancelled' disposition.
12837
+ *
12838
+ * Returns silently if the session id is not tracked or already settled.
12839
+ * The CollabDebugReport will reflect 'cancelled' disposition when awaited.
12840
+ */
12841
+ cancelCollabSession(sessionId, reason = "Director cancelled") {
12842
+ const session = this._activeCollabSessions.get(sessionId);
12843
+ if (!session || session.isCancelled()) return;
12844
+ session.cancel(reason);
12845
+ for (const [_role, subagentId] of session.getSubagentIds()) {
12846
+ this.coordinator.stop(subagentId).catch(() => {
12847
+ });
12848
+ }
12849
+ }
12850
+ /**
12851
+ * Subscribe a callback to be notified whenever a collab session raises
12852
+ * an alert (warning level). The callback receives the full DirectorAlert
12853
+ * payload so the host (CLI, TUI) can display it to the user.
12854
+ * Returns an unsubscribe function.
12855
+ */
12856
+ onCollabAlert(handler) {
12857
+ return this.fleet.filter("collab.warning", (e) => {
12858
+ handler(e.payload);
12859
+ });
12860
+ }
12861
+ /**
12862
+ * Returns all active (not yet settled) collab session ids.
12863
+ * Useful for the TUI to render a "N active sessions" badge and for
12864
+ * the host to know what can be cancelled.
12865
+ */
12866
+ activeCollabSessions() {
12867
+ return Array.from(this._activeCollabSessions.keys());
12868
+ }
12017
12869
  /** Best-effort session-writer append. Swallows failures — the director
12018
12870
  * must not break a fleet run because the session JSONL handle closed. */
12019
12871
  async appendSessionEvent(event) {
@@ -12052,12 +12904,21 @@ var Director = class {
12052
12904
  * it the `cost` column in `usage.snapshot()` stays at 0.
12053
12905
  */
12054
12906
  async spawn(config, priceLookup) {
12907
+ if (this.workCompleteFlag) {
12908
+ throw new FleetSpawnBudgetError(
12909
+ "max_spawns",
12910
+ this.maxSpawns,
12911
+ this.spawnCount + 1,
12912
+ "workComplete() has been called \u2014 director closed further spawning"
12913
+ );
12914
+ }
12055
12915
  if (this.fleetManager) {
12056
12916
  const rejection = this.fleetManager.canSpawn(config);
12057
12917
  if (rejection) {
12058
12918
  if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
12059
12919
  if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
12060
12920
  if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
12921
+ if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
12061
12922
  }
12062
12923
  } else {
12063
12924
  if (this.spawnDepth >= this.maxSpawnDepth) {
@@ -12072,9 +12933,15 @@ var Director = class {
12072
12933
  throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
12073
12934
  }
12074
12935
  }
12936
+ if (this.maxLeaderContextLoad < 1) {
12937
+ const threshold = this.maxContext * this.maxLeaderContextLoad;
12938
+ if (this.leaderContextPressure >= threshold) {
12939
+ throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
12940
+ }
12941
+ }
12075
12942
  }
12076
12943
  let result;
12077
- const needsNickname = config.name === config.role || !config.name || config.name === "subagent";
12944
+ const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
12078
12945
  if (needsNickname) {
12079
12946
  const role = config.role ?? "subagent";
12080
12947
  if (this.fleetManager) {
@@ -12299,6 +13166,24 @@ var Director = class {
12299
13166
  */
12300
13167
  async assign(task) {
12301
13168
  const taskWithId = task.id ? task : { ...task, id: randomUUID() };
13169
+ if (this.workCompleteFlag) {
13170
+ const synthetic = {
13171
+ subagentId: taskWithId.subagentId ?? "unassigned",
13172
+ taskId: taskWithId.id,
13173
+ status: "stopped",
13174
+ error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
13175
+ iterations: 0,
13176
+ toolCalls: 0,
13177
+ durationMs: 0
13178
+ };
13179
+ this.completed.set(taskWithId.id, synthetic);
13180
+ const waiter = this.taskWaiters.get(taskWithId.id);
13181
+ if (waiter) {
13182
+ waiter.resolve(synthetic);
13183
+ this.taskWaiters.delete(taskWithId.id);
13184
+ }
13185
+ return taskWithId.id;
13186
+ }
12302
13187
  if (task.subagentId) {
12303
13188
  if (this.fleetManager) {
12304
13189
  this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
@@ -12521,12 +13406,14 @@ var Director = class {
12521
13406
  makeAskTool(this),
12522
13407
  makeRollUpTool(this),
12523
13408
  makeTerminateTool(this),
13409
+ makeTerminateAllTool(this),
12524
13410
  makeFleetStatusTool(this),
12525
13411
  makeFleetUsageTool(this),
12526
13412
  makeFleetSessionTool(this),
12527
13413
  makeFleetHealthTool(this),
12528
13414
  makeCollabDebugTool(this),
12529
- makeFleetEmitTool(this)
13415
+ makeFleetEmitTool(this),
13416
+ makeWorkCompleteTool(this)
12530
13417
  ];
12531
13418
  return t;
12532
13419
  }
@@ -12546,7 +13433,15 @@ var Director = class {
12546
13433
  * three agents complete or the session times out.
12547
13434
  */
12548
13435
  async spawnCollab(options) {
12549
- const session = new CollabSession(this, this.fleet, options);
13436
+ const session = new CollabSession(this, this.fleet, {
13437
+ ...options,
13438
+ onBudgetWarning: (alert) => {
13439
+ return options.onBudgetWarning?.(alert) ?? "ignore";
13440
+ }
13441
+ });
13442
+ this._activeCollabSessions.set(session.sessionId, session);
13443
+ session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
13444
+ session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
12550
13445
  return session.start();
12551
13446
  }
12552
13447
  /**
@@ -12963,6 +13858,71 @@ var NULL_FLEET_BUS = new FleetBus();
12963
13858
 
12964
13859
  // src/models/models-registry.ts
12965
13860
  init_atomic_write();
13861
+
13862
+ // src/utils/merge-models-payload.ts
13863
+ function mergeModelsPayload(base, overlay) {
13864
+ const out = {};
13865
+ for (const [id, provider] of Object.entries(base)) {
13866
+ out[id] = cloneProvider(provider);
13867
+ }
13868
+ for (const [id, ovProvider] of Object.entries(overlay)) {
13869
+ const existing = out[id];
13870
+ out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
13871
+ }
13872
+ return out;
13873
+ }
13874
+ function mergeProvider(base, overlay) {
13875
+ const models = {};
13876
+ for (const [mid, m] of Object.entries(base.models ?? {})) {
13877
+ models[mid] = { ...m };
13878
+ }
13879
+ for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
13880
+ const existing = models[mid];
13881
+ models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
13882
+ }
13883
+ return {
13884
+ ...base,
13885
+ // Overlay scalar fields win when explicitly provided; otherwise keep base.
13886
+ ...stripUndefined({
13887
+ id: overlay.id,
13888
+ name: overlay.name,
13889
+ npm: overlay.npm,
13890
+ api: overlay.api,
13891
+ env: overlay.env,
13892
+ doc: overlay.doc
13893
+ }),
13894
+ models
13895
+ };
13896
+ }
13897
+ function mergeModel(base, overlay) {
13898
+ const merged = { ...base, ...overlay };
13899
+ if (base.limit || overlay.limit) {
13900
+ merged.limit = { ...base.limit, ...overlay.limit };
13901
+ }
13902
+ if (base.cost || overlay.cost) {
13903
+ merged.cost = { ...base.cost, ...overlay.cost };
13904
+ }
13905
+ if (base.modalities || overlay.modalities) {
13906
+ merged.modalities = { ...base.modalities, ...overlay.modalities };
13907
+ }
13908
+ return merged;
13909
+ }
13910
+ function cloneProvider(p) {
13911
+ const models = {};
13912
+ for (const [mid, m] of Object.entries(p.models ?? {})) {
13913
+ models[mid] = { ...m };
13914
+ }
13915
+ return { ...p, models };
13916
+ }
13917
+ function stripUndefined(obj) {
13918
+ const out = {};
13919
+ for (const [k, v] of Object.entries(obj)) {
13920
+ if (v !== void 0) out[k] = v;
13921
+ }
13922
+ return out;
13923
+ }
13924
+
13925
+ // src/models/models-registry.ts
12966
13926
  var DEFAULT_URL = "https://models.dev/api.json";
12967
13927
  var DEFAULT_TTL_SECONDS = 24 * 3600;
12968
13928
  var FAMILY_BY_NPM = {
@@ -12990,7 +13950,10 @@ function classifyFamily(npm) {
12990
13950
  return FAMILY_BY_NPM[npm] ?? "unsupported";
12991
13951
  }
12992
13952
  var DefaultModelsRegistry = class {
13953
+ /** Merged (base + overlay) payload — what every reader sees. */
12993
13954
  payload;
13955
+ /** Memoised overlay payload (in-memory / fetched / file). */
13956
+ overlayPayload;
12994
13957
  fetchedAt;
12995
13958
  cacheFile;
12996
13959
  url;
@@ -12998,6 +13961,10 @@ var DefaultModelsRegistry = class {
12998
13961
  fetchImpl;
12999
13962
  seed;
13000
13963
  maxStaleAgeMs;
13964
+ overlay;
13965
+ overlayUrl;
13966
+ overlayFile;
13967
+ overlayCacheFile;
13001
13968
  constructor(opts) {
13002
13969
  this.cacheFile = opts.cacheFile;
13003
13970
  this.url = opts.url ?? DEFAULT_URL;
@@ -13006,6 +13973,10 @@ var DefaultModelsRegistry = class {
13006
13973
  this.seed = opts.seed;
13007
13974
  const maxStaleSeconds = opts.maxStaleAgeSeconds ?? 7 * 24 * 3600;
13008
13975
  this.maxStaleAgeMs = maxStaleSeconds * 1e3;
13976
+ this.overlay = opts.overlay;
13977
+ this.overlayUrl = opts.overlayUrl;
13978
+ this.overlayFile = opts.overlayFile;
13979
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path15.join(path15.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
13009
13980
  }
13010
13981
  async load(opts = {}) {
13011
13982
  if (this.payload && !opts.force) return this.payload;
@@ -13014,27 +13985,45 @@ var DefaultModelsRegistry = class {
13014
13985
  this.fetchedAt = /* @__PURE__ */ new Date();
13015
13986
  return this.payload;
13016
13987
  }
13988
+ const overlay = await this.loadOverlay(opts);
13989
+ const base = await this.loadBase(opts, Object.keys(overlay).length > 0);
13990
+ this.payload = mergeModelsPayload(base, overlay);
13991
+ return this.payload;
13992
+ }
13993
+ /**
13994
+ * Load the models.dev base payload: fresh cache → network → stale cache.
13995
+ * On total failure, degrade to `{}` (so a non-empty overlay still drives
13996
+ * the catalog) rather than throwing — unless there's no curated overlay to
13997
+ * fall back on, in which case the original error propagates so pure-
13998
+ * models.dev setups still surface the problem.
13999
+ */
14000
+ async loadBase(opts = {}, overlayAvailable = false) {
13017
14001
  if (!opts.force) {
13018
- const cached = await this.readCache();
14002
+ const cached = await this.readCacheAt(this.cacheFile);
13019
14003
  if (cached && this.isFresh(cached.fetchedAt)) {
13020
- this.payload = cached.payload;
13021
14004
  this.fetchedAt = new Date(cached.fetchedAt);
13022
14005
  return cached.payload;
13023
14006
  }
13024
14007
  }
13025
14008
  try {
13026
- return await this.refresh();
14009
+ return await this.refreshBase();
13027
14010
  } catch (err) {
13028
- const cached = await this.readCache();
14011
+ const cached = await this.readCacheAt(this.cacheFile);
13029
14012
  if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) {
13030
- this.payload = cached.payload;
13031
14013
  this.fetchedAt = new Date(cached.fetchedAt);
13032
14014
  return cached.payload;
13033
14015
  }
14016
+ if (overlayAvailable) {
14017
+ console.warn(
14018
+ `ModelsRegistry: models.dev unavailable (${err instanceof Error ? err.message : String(err)}); serving curated overlay only.`
14019
+ );
14020
+ return {};
14021
+ }
13034
14022
  throw err;
13035
14023
  }
13036
14024
  }
13037
- async refresh() {
14025
+ /** Fetch + cache the models.dev base. Throws on failure (used by `refresh`). */
14026
+ async refreshBase() {
13038
14027
  const res = await this.fetchImpl(this.url, {
13039
14028
  method: "GET",
13040
14029
  headers: { accept: "application/json" }
@@ -13043,7 +14032,6 @@ var DefaultModelsRegistry = class {
13043
14032
  throw new Error(`ModelsRegistry: HTTP ${res.status} fetching ${this.url}`);
13044
14033
  }
13045
14034
  const json = await res.json();
13046
- this.payload = json;
13047
14035
  this.fetchedAt = /* @__PURE__ */ new Date();
13048
14036
  const envelope = {
13049
14037
  fetchedAt: this.fetchedAt.toISOString(),
@@ -13053,6 +14041,68 @@ var DefaultModelsRegistry = class {
13053
14041
  await atomicWrite(this.cacheFile, JSON.stringify(envelope));
13054
14042
  return json;
13055
14043
  }
14044
+ /**
14045
+ * Resolve the curated overlay, memoised. Order: in-memory `overlay` →
14046
+ * fetched `overlayUrl` (cached, same TTL/stale rules) → `overlayFile` on
14047
+ * disk. Never throws — a missing/broken overlay yields `{}`.
14048
+ */
14049
+ async loadOverlay(opts = {}) {
14050
+ if (this.overlayPayload && !opts.force) return this.overlayPayload;
14051
+ if (this.overlay) {
14052
+ this.overlayPayload = this.overlay;
14053
+ return this.overlayPayload;
14054
+ }
14055
+ const fetched = await this.loadOverlayFromUrl(opts);
14056
+ if (fetched) {
14057
+ this.overlayPayload = fetched;
14058
+ return fetched;
14059
+ }
14060
+ const fromFile = await this.readOverlayFile();
14061
+ this.overlayPayload = fromFile ?? {};
14062
+ return this.overlayPayload;
14063
+ }
14064
+ async loadOverlayFromUrl(opts) {
14065
+ if (!this.overlayUrl || !this.overlayCacheFile) return void 0;
14066
+ if (!opts.force) {
14067
+ const cached = await this.readCacheAt(this.overlayCacheFile);
14068
+ if (cached && this.isFresh(cached.fetchedAt)) return cached.payload;
14069
+ }
14070
+ try {
14071
+ const res = await this.fetchImpl(this.overlayUrl, {
14072
+ method: "GET",
14073
+ headers: { accept: "application/json" }
14074
+ });
14075
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
14076
+ const json = await res.json();
14077
+ const envelope = {
14078
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
14079
+ url: this.overlayUrl,
14080
+ payload: json
14081
+ };
14082
+ await atomicWrite(this.overlayCacheFile, JSON.stringify(envelope)).catch(() => {
14083
+ });
14084
+ return json;
14085
+ } catch {
14086
+ const cached = await this.readCacheAt(this.overlayCacheFile);
14087
+ if (cached && this.isWithinMaxStaleAge(cached.fetchedAt)) return cached.payload;
14088
+ return void 0;
14089
+ }
14090
+ }
14091
+ async readOverlayFile() {
14092
+ if (!this.overlayFile) return void 0;
14093
+ try {
14094
+ const raw = await fsp.readFile(this.overlayFile, "utf8");
14095
+ return JSON.parse(raw);
14096
+ } catch {
14097
+ return void 0;
14098
+ }
14099
+ }
14100
+ async refresh() {
14101
+ const base = await this.refreshBase();
14102
+ const overlay = await this.loadOverlay({ force: true });
14103
+ this.payload = mergeModelsPayload(base, overlay);
14104
+ return this.payload;
14105
+ }
13056
14106
  async listProviders() {
13057
14107
  const payload = await this.load();
13058
14108
  return Object.values(payload).map((p) => this.resolveProvider(p));
@@ -13093,7 +14143,7 @@ var DefaultModelsRegistry = class {
13093
14143
  }
13094
14144
  async ageSeconds() {
13095
14145
  if (!this.fetchedAt) {
13096
- const cached = await this.readCache();
14146
+ const cached = await this.readCacheAt(this.cacheFile);
13097
14147
  if (!cached) return Number.POSITIVE_INFINITY;
13098
14148
  return (Date.now() - new Date(cached.fetchedAt).getTime()) / 1e3;
13099
14149
  }
@@ -13117,9 +14167,9 @@ var DefaultModelsRegistry = class {
13117
14167
  isWithinMaxStaleAge(fetchedAtIso) {
13118
14168
  return Date.now() - new Date(fetchedAtIso).getTime() < this.maxStaleAgeMs;
13119
14169
  }
13120
- async readCache() {
14170
+ async readCacheAt(file) {
13121
14171
  try {
13122
- const raw = await fsp.readFile(this.cacheFile, "utf8");
14172
+ const raw = await fsp.readFile(file, "utf8");
13123
14173
  return JSON.parse(raw);
13124
14174
  } catch {
13125
14175
  return void 0;
@@ -15437,7 +16487,7 @@ function analyzeCriticalPath(graph) {
15437
16487
  blockedTasks
15438
16488
  };
15439
16489
  }
15440
- function getTransitiveBlocked(graph, taskId, blocksMap) {
16490
+ function getTransitiveBlocked(_graph, taskId, blocksMap) {
15441
16491
  const visited = /* @__PURE__ */ new Set();
15442
16492
  const queue = [taskId];
15443
16493
  while (queue.length > 0) {
@@ -15453,7 +16503,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
15453
16503
  }
15454
16504
  return visited;
15455
16505
  }
15456
- function computeCriticalPath(graph, topoOrder, blockedByMap) {
16506
+ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
15457
16507
  const allIds = Array.from(graph.nodes.keys());
15458
16508
  if (allIds.length === 0) return [];
15459
16509
  const dist = /* @__PURE__ */ new Map();
@@ -15871,13 +16921,11 @@ function createAutoExecutor(opts) {
15871
16921
 
15872
16922
  // src/sdd/sdd-task-decomposer.ts
15873
16923
  var SddTaskDecomposer = class {
15874
- constructor(tracker, graph, opts = {}) {
16924
+ constructor(tracker, _graph, opts = {}) {
15875
16925
  this.tracker = tracker;
15876
- this.graph = graph;
15877
16926
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
15878
16927
  }
15879
16928
  tracker;
15880
- graph;
15881
16929
  slots;
15882
16930
  wave = 0;
15883
16931
  // -------------------------------------------------------------------
@@ -16030,7 +17078,7 @@ var SddParallelRun = class {
16030
17078
  this.coordinator.setRunner?.(runner);
16031
17079
  }
16032
17080
  defaultFactory() {
16033
- return async (config) => ({
17081
+ return async (_config) => ({
16034
17082
  agent: this.opts.agent,
16035
17083
  events: this.opts.agent.events
16036
17084
  });
@@ -16844,6 +17892,12 @@ function roughEstimate(messages) {
16844
17892
  return total;
16845
17893
  }
16846
17894
  function createContextManagerTool(opts = {}) {
17895
+ const minCompactThreshold = opts.minCompactThreshold ?? 0;
17896
+ const noopRetryDeltaTokens = opts.noopRetryDeltaTokens ?? 2e3;
17897
+ const maxContext = opts.maxContext ?? 128e3;
17898
+ const compactThresholdFraction = opts.compactThresholdFraction ?? 0.5;
17899
+ const effectiveThreshold = minCompactThreshold > 0 ? minCompactThreshold : Math.floor(maxContext * compactThresholdFraction);
17900
+ let lastNoopTokens = 0;
16847
17901
  return {
16848
17902
  name: CONTEXT_MANAGER_TOOL_NAME,
16849
17903
  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.',
@@ -16934,11 +17988,41 @@ function createContextManagerTool(opts = {}) {
16934
17988
  notes: "No compactor registered. Use /compact aggressive via slash command instead."
16935
17989
  };
16936
17990
  }
17991
+ const fullEstimate = input.systemPrompt != null && Array.isArray(input.tools) ? estimateRequestTokens(messages, input.systemPrompt, input.tools) : { total: beforeTokens};
17992
+ const currentTokens = fullEstimate.total;
17993
+ if (lastNoopTokens > 0) {
17994
+ const delta = currentTokens - lastNoopTokens;
17995
+ if (delta < noopRetryDeltaTokens) {
17996
+ return {
17997
+ action: "compact",
17998
+ beforeTokens,
17999
+ afterTokens: beforeTokens,
18000
+ messageCount: messages.length,
18001
+ notes: `Compact is a NOOP retry: context grew only ${delta} tokens since the last no-op attempt (threshold: ${noopRetryDeltaTokens}). Skip until more content accumulates.`
18002
+ };
18003
+ }
18004
+ }
18005
+ if (currentTokens < effectiveThreshold) {
18006
+ return {
18007
+ action: "compact",
18008
+ beforeTokens,
18009
+ afterTokens: beforeTokens,
18010
+ messageCount: messages.length,
18011
+ notes: `Context tokens (${currentTokens}) below compact threshold (${effectiveThreshold}). Skipping.`
18012
+ };
18013
+ }
16937
18014
  const report = await opts.compactor.compact(ctx);
16938
18015
  ctx.clearFileTracking();
16939
18016
  const repair = applyMessages([...ctx.messages]);
16940
18017
  const afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
16941
18018
  const repaired = report.repaired ?? (repair.changed ? repair : void 0);
18019
+ const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
18020
+ const repairedSomething = !!report.repaired;
18021
+ if (reduced || repairedSomething) {
18022
+ lastNoopTokens = 0;
18023
+ } else {
18024
+ lastNoopTokens = currentTokens;
18025
+ }
16942
18026
  return {
16943
18027
  action: "compact",
16944
18028
  beforeTokens,
@@ -17177,6 +18261,6 @@ var allServers = () => ({
17177
18261
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false }
17178
18262
  });
17179
18263
 
17180
- 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 };
18264
+ 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, createSessionEventBridge, 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, resolveAuditLevel, resolveSessionLoggingConfig, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
17181
18265
  //# sourceMappingURL=index.js.map
17182
18266
  //# sourceMappingURL=index.js.map