@wrongstack/core 0.9.19 → 0.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-Dti3KXGk.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
  3. package/dist/compactor-D7X96RLZ.d.ts +41 -0
  4. package/dist/{config-CWva0qoL.d.ts → config-CLXMDOSs.d.ts} +1 -1
  5. package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
  6. package/dist/coordination/index.d.ts +13 -13
  7. package/dist/coordination/index.js +660 -146
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +20 -20
  10. package/dist/defaults/index.js +918 -350
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{events-CiG9qUM_.d.ts → events-DH-9r-_C.d.ts} +42 -1
  13. package/dist/execution/index.d.ts +41 -30
  14. package/dist/execution/index.js +358 -112
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/extension/index.js.map +1 -1
  18. package/dist/{index-aizK8olO.d.ts → index-BIHJ4uII.d.ts} +11 -8
  19. package/dist/{index-p95HQ22A.d.ts → index-CFO9QmJo.d.ts} +8 -8
  20. package/dist/index.d.ts +311 -35
  21. package/dist/index.js +1933 -512
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -6
  24. package/dist/infrastructure/index.js +36 -0
  25. package/dist/infrastructure/index.js.map +1 -1
  26. package/dist/kernel/index.d.ts +9 -9
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-DkESgh0G.d.ts} +25 -3
  29. package/dist/models/index.d.ts +2 -2
  30. package/dist/models/index.js +1 -1
  31. package/dist/models/index.js.map +1 -1
  32. package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-DNp6lAzg.d.ts} +35 -23
  33. package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
  34. package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +411 -110
  35. package/dist/observability/index.d.ts +2 -2
  36. package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
  37. package/dist/{permission-bPuzAy4x.d.ts → permission-H35s9Evv.d.ts} +1 -1
  38. package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CT-nRmTn.d.ts} +2 -2
  39. package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-Bs8iRwi6.d.ts} +4 -4
  40. package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BZdDrWrS.d.ts} +3 -3
  41. package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-J9N_PM40.d.ts} +1 -1
  42. package/dist/sdd/index.d.ts +9 -10
  43. package/dist/sdd/index.js +224 -68
  44. package/dist/sdd/index.js.map +1 -1
  45. package/dist/security/index.d.ts +3 -3
  46. package/dist/{selector-pox8abg0.d.ts → selector-CFTh3Z6p.d.ts} +1 -1
  47. package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-C7JJlxOw.d.ts} +2 -2
  48. package/dist/skills/index.d.ts +1 -1
  49. package/dist/skills/index.js +1 -1
  50. package/dist/skills/index.js.map +1 -1
  51. package/dist/storage/index.d.ts +390 -6
  52. package/dist/storage/index.js +672 -35
  53. package/dist/storage/index.js.map +1 -1
  54. package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CneIxVip.d.ts} +1 -1
  55. package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
  56. package/dist/types/index.d.ts +14 -14
  57. package/dist/types/index.js +60 -0
  58. package/dist/types/index.js.map +1 -1
  59. package/dist/utils/index.d.ts +18 -6
  60. package/dist/utils/index.js +61 -56
  61. package/dist/utils/index.js.map +1 -1
  62. package/package.json +1 -1
  63. package/dist/compactor-DVTKL7XD.d.ts +0 -23
@@ -39,6 +39,54 @@ function estimateToolResultTokens(content) {
39
39
  function estimateTextTokens(text) {
40
40
  return RoughTokenEstimate(text);
41
41
  }
42
+ function estimateToolDefTokens(tool) {
43
+ return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
44
+ }
45
+ function estimateRequestTokens(messages, systemPrompt, tools) {
46
+ let messagesTokens = 0;
47
+ if (typeof messages === "string") {
48
+ messagesTokens = RoughTokenEstimate(messages);
49
+ } else if (Array.isArray(messages)) {
50
+ for (const m of messages) {
51
+ if (typeof m === "object" && m !== null && "content" in m) {
52
+ const content = m.content;
53
+ if (typeof content === "string") {
54
+ messagesTokens += RoughTokenEstimate(content);
55
+ } else if (Array.isArray(content)) {
56
+ for (const b of content) {
57
+ if (typeof b === "object" && b !== null) {
58
+ if (b.type === "text") {
59
+ messagesTokens += RoughTokenEstimate(b.text);
60
+ } else {
61
+ messagesTokens += RoughTokenEstimate(JSON.stringify(b));
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ let systemTokens = 0;
70
+ if (typeof systemPrompt === "string") {
71
+ systemTokens = RoughTokenEstimate(systemPrompt);
72
+ } else if (Array.isArray(systemPrompt)) {
73
+ for (const b of systemPrompt) {
74
+ if (typeof b === "object" && b !== null && b.type === "text") {
75
+ systemTokens += RoughTokenEstimate(b.text);
76
+ }
77
+ }
78
+ }
79
+ let toolsTokens = 0;
80
+ for (const t of tools) {
81
+ toolsTokens += estimateToolDefTokens(t);
82
+ }
83
+ return {
84
+ messages: messagesTokens,
85
+ systemPrompt: systemTokens,
86
+ tools: toolsTokens,
87
+ total: messagesTokens + systemTokens + toolsTokens
88
+ };
89
+ }
42
90
 
43
91
  // src/utils/message-invariants.ts
44
92
  function repairToolUseAdjacency(messages) {
@@ -144,6 +192,7 @@ var HybridCompactor = class {
144
192
  }
145
193
  async compact(ctx, opts = {}) {
146
194
  const beforeTokens = this.estimateMessages(ctx.messages);
195
+ const beforeFull = this.estimateFullRequest(ctx);
147
196
  const reductions = [];
148
197
  const policy = readContextWindowPolicy(ctx);
149
198
  const preserveK = policy?.preserveK ?? this.preserveK;
@@ -159,9 +208,12 @@ var HybridCompactor = class {
159
208
  ctx.state.replaceMessages(repaired.messages);
160
209
  }
161
210
  const afterTokens = this.estimateMessages(ctx.messages);
211
+ const afterFull = this.estimateFullRequest(ctx);
162
212
  return {
163
213
  before: beforeTokens,
164
214
  after: afterTokens,
215
+ fullRequestTokensBefore: beforeFull,
216
+ fullRequestTokensAfter: afterFull,
165
217
  reductions,
166
218
  repaired: repaired.report.changed ? {
167
219
  removedToolUses: repaired.report.removedToolUses,
@@ -170,6 +222,14 @@ var HybridCompactor = class {
170
222
  } : void 0
171
223
  };
172
224
  }
225
+ /**
226
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
227
+ * This is the accurate figure for context-window pressure monitoring.
228
+ */
229
+ estimateFullRequest(ctx) {
230
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
231
+ return breakdown.total;
232
+ }
173
233
  eliseOldToolResults(ctx, preserveK = this.preserveK, eliseThreshold = this.eliseThreshold) {
174
234
  const messages = ctx.messages;
175
235
  let pairCount = 0;
@@ -315,8 +375,9 @@ var IntelligentCompactor = class {
315
375
  }
316
376
  async compact(ctx, opts = {}) {
317
377
  const beforeTokens = this.estimateTokens(ctx.messages);
378
+ const beforeFull = this.estimateFullRequest(ctx);
318
379
  const reductions = [];
319
- const load = beforeTokens / this.maxContext;
380
+ const load = beforeFull / this.maxContext;
320
381
  const aggressive = load >= this.hardThreshold ? true : opts.aggressive ?? load >= this.softThreshold;
321
382
  const saved1 = this.eliseOldToolResults(ctx);
322
383
  if (saved1 > 0) reductions.push({ phase: "elision", saved: saved1 });
@@ -330,9 +391,12 @@ var IntelligentCompactor = class {
330
391
  const repaired = repairToolUseAdjacency(ctx.messages);
331
392
  if (repaired.report.changed) ctx.state.replaceMessages(repaired.messages);
332
393
  const afterTokens = this.estimateTokens(ctx.messages);
394
+ const afterFull = this.estimateFullRequest(ctx);
333
395
  return {
334
396
  before: beforeTokens,
335
397
  after: afterTokens,
398
+ fullRequestTokensBefore: beforeFull,
399
+ fullRequestTokensAfter: afterFull,
336
400
  reductions,
337
401
  repaired: repaired.report.changed ? {
338
402
  removedToolUses: repaired.report.removedToolUses,
@@ -341,6 +405,14 @@ var IntelligentCompactor = class {
341
405
  } : void 0
342
406
  };
343
407
  }
408
+ /**
409
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
410
+ * This is the accurate figure for context-window pressure monitoring.
411
+ */
412
+ estimateFullRequest(ctx) {
413
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
414
+ return breakdown.total;
415
+ }
344
416
  async summarizeAncientTurns(ctx) {
345
417
  const messages = ctx.messages;
346
418
  const cutoff = Math.max(0, messages.length - this.preserveK * 2);
@@ -353,29 +425,26 @@ var IntelligentCompactor = class {
353
425
  try {
354
426
  summaryText = await this.callSummarizer(toSummarize, ctx);
355
427
  } catch {
356
- const toolNames = /* @__PURE__ */ new Set();
357
- const filePaths = /* @__PURE__ */ new Set();
358
- let userTurns = 0;
359
- let assistantTurns = 0;
428
+ const preservedMessages = [];
360
429
  for (const m of toSummarize) {
361
- if (m.role === "user") userTurns++;
362
- else if (m.role === "assistant") {
363
- assistantTurns++;
364
- if (Array.isArray(m.content)) {
365
- for (const b of m.content) {
366
- if (b.type === "tool_use") toolNames.add(b.name ?? "unknown");
367
- }
368
- }
430
+ if (m.role === "system") continue;
431
+ if (typeof m.content === "string") {
432
+ preservedMessages.push(m);
433
+ } else if (Array.isArray(m.content)) {
434
+ const textParts = m.content.filter(isTextBlock);
435
+ const toolParts = m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result");
436
+ const next = {
437
+ role: m.role,
438
+ content: [
439
+ ...textParts,
440
+ ...toolParts.length > 0 ? [{ type: "text", text: `[${toolParts.length} tool call(s) omitted]` }] : []
441
+ ]
442
+ };
443
+ preservedMessages.push(next);
369
444
  }
370
- const text = typeof m.content === "string" ? m.content : "";
371
- const matches = text.matchAll(/(?:[\w.,\-/@]+\/)*[\w.,\-/@]+\.\w+/g);
372
- for (const m_ of matches) filePaths.add(m_[0]);
373
445
  }
374
- const parts = [`${toSummarize.length} turns (${userTurns} user, ${assistantTurns} assistant)`];
375
- if (toolNames.size > 0) parts.push(`tools: ${[...toolNames].join(", ")}`);
376
- if (filePaths.size > 0) parts.push(`files: ${[...filePaths].slice(0, 10).join(", ")}`);
377
- summaryText = parts.join(" | ");
378
- if (!summaryText) summaryText = `${toSummarize.length} earlier turns omitted`;
446
+ summaryText = preservedMessages.map((m) => `[${m.role}]: ${typeof m.content === "string" ? m.content : m.content.filter(isTextBlock).map((b) => b.text).join(" ")}`).join("\n");
447
+ if (!summaryText) summaryText = `${toSummarize.length} earlier turns (semantic content preserved)`;
379
448
  }
380
449
  const summaryMsg = {
381
450
  role: "system",
@@ -621,7 +690,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
621
690
  const res = await this.provider.complete(req, { signal: ac.signal });
622
691
  const textBlocks = res.content.filter(isTextBlock);
623
692
  raw = textBlocks.map((b) => b.text).join("\n").trim();
624
- } catch (err) {
693
+ } catch (_err) {
625
694
  return this.fallbackSelect(messages, effectiveBudget);
626
695
  }
627
696
  return this.parseSelectorOutput(raw, messages.length);
@@ -713,15 +782,24 @@ var SelectiveCompactor = class {
713
782
  }
714
783
  async compact(ctx, opts = {}) {
715
784
  const beforeTokens = this.estimateTokens(ctx.messages);
785
+ const beforeFull = this.estimateFullRequest(ctx);
716
786
  const reductions = [];
717
- const load = beforeTokens / this.maxContext;
787
+ const load = beforeFull / this.maxContext;
718
788
  const shouldCompact = load >= this.warnThreshold || opts.aggressive;
719
789
  if (!shouldCompact) {
720
790
  const saved = this.eliseOldToolResults(ctx);
721
791
  if (saved > 0) reductions.push({ phase: "elision", saved });
722
792
  const repair2 = this.repairProtocolAdjacency(ctx);
723
793
  const afterTokens2 = this.estimateTokens(ctx.messages);
724
- return { before: beforeTokens, after: afterTokens2, reductions, repaired: repair2 };
794
+ const afterFull2 = this.estimateFullRequest(ctx);
795
+ return {
796
+ before: beforeTokens,
797
+ after: afterTokens2,
798
+ fullRequestTokensBefore: beforeFull,
799
+ fullRequestTokensAfter: afterFull2,
800
+ reductions,
801
+ repaired: repair2
802
+ };
725
803
  }
726
804
  const savedElision = this.eliseOldToolResults(ctx);
727
805
  if (savedElision > 0) reductions.push({ phase: "elision", saved: savedElision });
@@ -733,7 +811,23 @@ var SelectiveCompactor = class {
733
811
  }
734
812
  const repair = this.repairProtocolAdjacency(ctx);
735
813
  const afterTokens = this.estimateTokens(ctx.messages);
736
- return { before: beforeTokens, after: afterTokens, reductions, repaired: repair };
814
+ const afterFull = this.estimateFullRequest(ctx);
815
+ return {
816
+ before: beforeTokens,
817
+ after: afterTokens,
818
+ fullRequestTokensBefore: beforeFull,
819
+ fullRequestTokensAfter: afterFull,
820
+ reductions,
821
+ repaired: repair
822
+ };
823
+ }
824
+ /**
825
+ * Estimate the full API request token count: messages + systemPrompt + toolDefs.
826
+ * This is the accurate figure for context-window pressure monitoring.
827
+ */
828
+ estimateFullRequest(ctx) {
829
+ const breakdown = estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []);
830
+ return breakdown.total;
737
831
  }
738
832
  repairProtocolAdjacency(ctx) {
739
833
  const repaired = repairToolUseAdjacency(ctx.messages);
@@ -1008,7 +1102,8 @@ var LEVEL_RANK = { warn: 0, soft: 1, hard: 2 };
1008
1102
  var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1009
1103
  name = "AutoCompaction";
1010
1104
  compactor;
1011
- estimator;
1105
+ /** Deprecated. Kept for backward compat with tests that pass simpleEstimator. */
1106
+ _estimator;
1012
1107
  warnThreshold;
1013
1108
  softThreshold;
1014
1109
  hardThreshold;
@@ -1018,19 +1113,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1018
1113
  events;
1019
1114
  failureMode;
1020
1115
  policyProvider;
1021
- /**
1022
- * Calibration factor applied to the estimator output. The estimator is now
1023
- * expected to already include messages + system prompt + tool definitions
1024
- * (see `estimateRequestTokens.total`), so no overhead boost is needed. Kept
1025
- * as a constant so a future calibration against real provider tokenization
1026
- * (BPE vs the chars/3.5 rough estimator) can dial this without touching the
1027
- * threshold-check math.
1028
- *
1029
- * Historical note: was 1.3 when the estimator only counted messages — that
1030
- * double-counted system+tools once the estimator was upgraded, firing
1031
- * compaction ~30% earlier than intended (e.g. real 56% load showed as 73%).
1032
- */
1033
- static OVERHEAD_FACTOR = 1;
1034
1116
  /**
1035
1117
  * Once a compaction attempt reduces nothing (preserveK protects everything,
1036
1118
  * no oversized tool_results remain to elide), retrying on every iteration
@@ -1043,8 +1125,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1043
1125
  lastNoopAttempt = null;
1044
1126
  /**
1045
1127
  * @param compactor Compactor to use for compaction.
1046
- * @param maxContext Provider's max context window in tokens.
1047
- * @param estimator Token estimation function.
1128
+ * @param maxContext Provider's max context window in tokens.
1129
+ * @param _estimator Deprecated parameter kept for backward compatibility.
1130
+ * The middleware now uses `estimateRequestTokens` internally
1131
+ * for accurate full-request token counting (messages +
1132
+ * systemPrompt + toolDefs).
1048
1133
  * @param thresholds Threshold fractions (0-1) of maxContext.
1049
1134
  * @param opts Optional behavior. By default, failures at the
1050
1135
  * hard threshold throw AGENT_CONTEXT_OVERFLOW so
@@ -1052,11 +1137,11 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1052
1137
  * provider context overflow. Warn/soft failures
1053
1138
  * still emit compaction.failed and continue.
1054
1139
  */
1055
- constructor(compactor, maxContext, estimator, thresholds, optsOrAggressiveOn = {}, events) {
1140
+ constructor(compactor, maxContext, _estimator, thresholds, optsOrAggressiveOn = {}, events) {
1056
1141
  const opts = typeof optsOrAggressiveOn === "string" ? { aggressiveOn: optsOrAggressiveOn, events } : optsOrAggressiveOn;
1057
1142
  this.compactor = compactor;
1058
1143
  this._maxContext = maxContext;
1059
- this.estimator = estimator;
1144
+ this._estimator = _estimator;
1060
1145
  this.warnThreshold = thresholds.warn;
1061
1146
  this.softThreshold = thresholds.soft;
1062
1147
  this.hardThreshold = thresholds.hard;
@@ -1072,8 +1157,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1072
1157
  }
1073
1158
  handler() {
1074
1159
  return async (ctx, next) => {
1075
- const rawTokens = this.estimator(ctx);
1076
- const tokens = Math.ceil(rawTokens * _AutoCompactionMiddleware.OVERHEAD_FACTOR);
1160
+ const tokens = this._estimator ? this._estimator(ctx) : estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total;
1077
1161
  const load = tokens / this._maxContext;
1078
1162
  const policy = this.policyProvider?.(ctx);
1079
1163
  const thresholds = policy?.thresholds ?? {
@@ -1108,7 +1192,7 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1108
1192
  return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
1109
1193
  }
1110
1194
  recordAttempt(level, tokens, report) {
1111
- const reduced = report.before > report.after;
1195
+ const reduced = (report.fullRequestTokensBefore ?? report.before) > (report.fullRequestTokensAfter ?? report.after);
1112
1196
  const repaired = !!report.repaired;
1113
1197
  if (reduced || repaired) {
1114
1198
  this.lastNoopAttempt = null;
@@ -2449,17 +2533,7 @@ var SubagentBudget = class _SubagentBudget {
2449
2533
  startTime = null;
2450
2534
  _onThreshold;
2451
2535
  /**
2452
- * Tracks which budget kinds currently have an extension request
2453
- * in flight. While a kind is here, further `checkLimit` calls for the
2454
- * same kind are no-ops — without this dedup, every `recordIteration`
2455
- * after the limit is reached spawns a fresh decision Promise (until
2456
- * the first one lands and patches limits), flooding the FleetBus
2457
- * with redundant threshold events. Cleared in `negotiateExtension`'s
2458
- * `finally`.
2459
- */
2460
- pendingExtensions = /* @__PURE__ */ new Set();
2461
- /**
2462
- * Hard cap on how long `negotiateExtension` waits for the coordinator to
2536
+ * Hard cap on how long `_negotiateExtension` waits for the coordinator to
2463
2537
  * respond before defaulting to 'stop'. Without this fallback an absent
2464
2538
  * or hung listener (Director not built / event filter detached mid-run)
2465
2539
  * leaves the budget over-limit and never enforces anything.
@@ -2521,38 +2595,83 @@ var SubagentBudget = class _SubagentBudget {
2521
2595
  * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
2522
2596
  * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
2523
2597
  */
2524
- checkLimit(kind, used, limit) {
2598
+ /**
2599
+ * Collects all exceeded budget kinds into a single NOOP-free negotiation.
2600
+ * Called by recordIteration / recordToolCall / recordUsage — each may call
2601
+ * this for its own kind. The first call starts the negotiation and stores
2602
+ * the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
2603
+ * kinds (while a negotiation is in flight) are NOOPs — they don't start
2604
+ * new conversations with the coordinator. This prevents an EventBus flood
2605
+ * when multiple budget kinds are exceeded simultaneously in one iteration.
2606
+ *
2607
+ * Returns the kinds that were found to be exceeded (for logging/debugging).
2608
+ */
2609
+ checkLimits(elapsedMs) {
2610
+ const exceeded = [];
2611
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2612
+ exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
2613
+ }
2614
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2615
+ exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
2616
+ }
2617
+ const totalTokens = this.tokenInput + this.tokenOutput;
2618
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2619
+ exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
2620
+ }
2621
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2622
+ exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
2623
+ }
2624
+ if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
2625
+ exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
2626
+ }
2627
+ if (exceeded.length === 0) return [];
2525
2628
  if (!this._onThreshold) {
2526
- throw new BudgetExceededError(kind, limit, used);
2629
+ const first2 = exceeded[0];
2630
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2527
2631
  }
2528
2632
  if (this._mode === "sync") {
2529
- throw new BudgetExceededError(kind, limit, used);
2633
+ const first2 = exceeded[0];
2634
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2530
2635
  }
2531
2636
  const bus = this._events;
2532
2637
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
2533
- throw new BudgetExceededError(kind, limit, used);
2638
+ const first2 = exceeded[0];
2639
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2534
2640
  }
2535
- if (this.pendingExtensions.has(kind)) return;
2536
- this.pendingExtensions.add(kind);
2537
- const decision = this.negotiateExtension(kind, used, limit);
2538
- throw new BudgetThresholdSignal(kind, limit, used, decision);
2641
+ for (const entry of exceeded) {
2642
+ if (this._pendingNegotiations.has(entry.kind)) continue;
2643
+ const decision2 = this._negotiateExtension(entry.kind, exceeded);
2644
+ this._pendingNegotiations.set(entry.kind, decision2);
2645
+ }
2646
+ const first = exceeded[0];
2647
+ const decision = this._pendingNegotiations.get(first.kind);
2648
+ throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
2539
2649
  }
2650
+ /**
2651
+ * Per-kind in-flight negotiation Promises. Each budget kind can have its
2652
+ * own concurrent negotiation — e.g. iterations and timeout can both
2653
+ * be exceeded simultaneously without blocking each other. The same kind
2654
+ * cannot start two concurrent negotiations (dedup guard).
2655
+ * Cleared in `_negotiateExtension`'s `finally` block.
2656
+ */
2657
+ _pendingNegotiations = /* @__PURE__ */ new Map();
2540
2658
  /**
2541
2659
  * Drive the threshold handler to a decision. Resolves with `'stop'`
2542
2660
  * (signal the runner to abort) or `{ extend: ... }` (limits already
2543
- * patched in-place; the runner should not abort). Always releases the
2544
- * `pendingExtensions` slot in `finally`.
2661
+ * patched in-place; the runner should not abort). Clears the
2662
+ * per-kind slot in `_pendingNegotiations` in `finally`.
2545
2663
  *
2546
2664
  * The 'continue' return from a sync handler is treated as
2547
2665
  * `{ extend: {} }` — keep going without patching; next overrun fires
2548
2666
  * a fresh signal.
2549
2667
  */
2550
- async negotiateExtension(kind, used, limit) {
2668
+ async _negotiateExtension(kind, exceeded) {
2551
2669
  try {
2670
+ const first = exceeded[0];
2552
2671
  const result = this._onThreshold({
2553
- kind,
2554
- used,
2555
- limit,
2672
+ kind: first.kind,
2673
+ used: first.used,
2674
+ limit: first.limit,
2556
2675
  requestDecision: () => {
2557
2676
  const bus = this._events;
2558
2677
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
@@ -2569,20 +2688,22 @@ var SubagentBudget = class _SubagentBudget {
2569
2688
  () => respond("stop"),
2570
2689
  _SubagentBudget.DECISION_TIMEOUT_MS
2571
2690
  );
2572
- bus.emit("budget.threshold_reached", {
2573
- kind,
2574
- used,
2575
- limit,
2576
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2577
- extend: (extra) => {
2578
- clearTimeout(fallback);
2579
- respond({ extend: extra });
2580
- },
2581
- deny: () => {
2582
- clearTimeout(fallback);
2583
- respond("stop");
2584
- }
2585
- });
2691
+ for (const { kind: kind2, used, limit } of exceeded) {
2692
+ bus.emit("budget.threshold_reached", {
2693
+ kind: kind2,
2694
+ used,
2695
+ limit,
2696
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2697
+ extend: (extra) => {
2698
+ clearTimeout(fallback);
2699
+ respond({ extend: extra });
2700
+ },
2701
+ deny: () => {
2702
+ clearTimeout(fallback);
2703
+ respond("stop");
2704
+ }
2705
+ });
2706
+ }
2586
2707
  });
2587
2708
  }
2588
2709
  });
@@ -2608,47 +2729,39 @@ var SubagentBudget = class _SubagentBudget {
2608
2729
  }
2609
2730
  return decision;
2610
2731
  } finally {
2611
- this.pendingExtensions.delete(kind);
2732
+ this._pendingNegotiations.delete(kind);
2612
2733
  }
2613
2734
  }
2614
2735
  recordIteration() {
2615
2736
  this.iterations++;
2616
- if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2617
- void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
2618
- }
2737
+ void this.checkLimits();
2619
2738
  }
2620
2739
  recordToolCall() {
2621
2740
  this.toolCalls++;
2622
- if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2623
- void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
2624
- }
2741
+ void this.checkLimits();
2625
2742
  }
2626
2743
  recordUsage(usage, costUsd = 0) {
2627
2744
  this.tokenInput += usage.input;
2628
2745
  this.tokenOutput += usage.output;
2629
2746
  this.costUsd += costUsd;
2630
- const totalTokens = this.tokenInput + this.tokenOutput;
2631
- if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2632
- void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
2633
- }
2634
- if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2635
- void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
2636
- }
2747
+ void this.checkLimits();
2637
2748
  }
2638
2749
  /**
2639
- * Wall-clock budget check. Unlike other limits, timeout check passes through
2640
- * `checkLimit` and is subject to the same negotiation-mode decision table.
2641
- * In practice, `'sync'` mode (the usual test configuration) means a timeout
2642
- * immediately throws `BudgetExceededError`. In production with a coordinator,
2643
- * a timeout emits `budget.threshold_reached` so the Director can decide whether
2644
- * to extend or abort.
2750
+ * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
2751
+ * wall-clock time cannot be "extended" by the coordinator, so it throws
2752
+ * synchronously rather than entering the negotiation flow.
2753
+ *
2754
+ * Decision table:
2755
+ * - no `onThreshold` handler → throw `BudgetExceededError`
2756
+ * - `mode === 'sync'` → throw `BudgetExceededError`
2757
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
2758
+ * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
2645
2759
  */
2646
2760
  checkTimeout() {
2647
2761
  if (this.startTime === null || this.limits.timeoutMs === void 0) return;
2648
2762
  const elapsed = Date.now() - this.startTime;
2649
- if (elapsed > this.limits.timeoutMs) {
2650
- void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
2651
- }
2763
+ if (elapsed <= this.limits.timeoutMs) return;
2764
+ void this.checkLimits(elapsed);
2652
2765
  }
2653
2766
  /** Returns true if a timeout has occurred without throwing. Useful for races. */
2654
2767
  isTimedOut() {
@@ -5258,6 +5371,107 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
5258
5371
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
5259
5372
  });
5260
5373
 
5374
+ // src/coordination/subagent-nicknames.ts
5375
+ var NICKNAME_POOL = {
5376
+ // Physics & fundamental sciences
5377
+ "einstein": { name: "Einstein", domain: "physics" },
5378
+ "newton": { name: "Newton", domain: "physics" },
5379
+ "feynman": { name: "Feynman", domain: "physics" },
5380
+ "dirac": { name: "Dirac", domain: "physics" },
5381
+ "bohr": { name: "Bohr", domain: "physics" },
5382
+ "planck": { name: "Planck", domain: "physics" },
5383
+ "curie": { name: "Curie", domain: "physics" },
5384
+ "fermi": { name: "Fermi", domain: "physics" },
5385
+ "heisenberg": { name: "Heisenberg", domain: "physics" },
5386
+ "schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
5387
+ // Mathematics
5388
+ "euclid": { name: "Euclid", domain: "math" },
5389
+ "gauss": { name: "Gauss", domain: "math" },
5390
+ "turing": { name: "Turing", domain: "math" },
5391
+ "poincare": { name: "Poincar\xE9", domain: "math" },
5392
+ "riemann": { name: "Riemann", domain: "math" },
5393
+ "hilbert": { name: "Hilbert", domain: "math" },
5394
+ "pythagoras": { name: "Pythagoras", domain: "math" },
5395
+ // Computing & information theory
5396
+ "von-neumann": { name: "Von Neumann", domain: "computing" },
5397
+ "shannon": { name: "Shannon", domain: "computing" },
5398
+ "hopper": { name: "Hopper", domain: "computing" },
5399
+ "backus": { name: "Backus", domain: "computing" },
5400
+ "knuth": { name: "Knuth", domain: "computing" },
5401
+ "torvalds": { name: "Torvalds", domain: "computing" },
5402
+ "stallman": { name: "Stallman", domain: "computing" },
5403
+ "berners-lee": { name: "Berners-Lee", domain: "computing" },
5404
+ "babbage": { name: "Babbage", domain: "computing" },
5405
+ "lovelace": { name: "Lovelace", domain: "computing" },
5406
+ "klein": { name: "Klein", domain: "computing" },
5407
+ // Electronics & electrical engineering
5408
+ "edison": { name: "Edison", domain: "ee" },
5409
+ "tesla": { name: "Tesla", domain: "ee" },
5410
+ "faraday": { name: "Faraday", domain: "ee" },
5411
+ "maxwell": { name: "Maxwell", domain: "ee" },
5412
+ "ohm": { name: "Ohm", domain: "ee" },
5413
+ "bell": { name: "Bell", domain: "ee" },
5414
+ "marconi": { name: "Marconi", domain: "ee" },
5415
+ "lamarr": { name: "Lamarr", domain: "ee" },
5416
+ // General science / multi-disciplinary
5417
+ "darwin": { name: "Darwin", domain: "biology" },
5418
+ "mendel": { name: "Mendel", domain: "biology" },
5419
+ "pasteur": { name: "Pasteur", domain: "biology" },
5420
+ "hawking": { name: "Hawking", domain: "cosmology" },
5421
+ "sagan": { name: "Sagan", domain: "cosmology" },
5422
+ // Chemistry / materials
5423
+ "lavoisier": { name: "Lavoisier", domain: "chemistry" },
5424
+ "mendeleev": { name: "Mendeleev", domain: "chemistry" }
5425
+ };
5426
+ var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
5427
+ var DOMAIN_PREFERENCES = {
5428
+ "security": ["shannon", "turing", "lamarr", "stallman"],
5429
+ "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
5430
+ "refactor": ["gauss", "hilbert", "euclid", "planck"],
5431
+ "audit-log": ["sagan", "hawking", "poincare", "newton"],
5432
+ "planner": ["hilbert", "gauss", "turing", "euclid"],
5433
+ "researcher": ["sagan", "hawking", "darwin", "pasteur"],
5434
+ "explorer": ["marconi", "bell", "columbus", "polo"],
5435
+ "testing": ["pasteur", "curie", "fermi", "bohr"],
5436
+ "frontend": ["lovelace", "hopper", "babbage", "backus"],
5437
+ "backend": ["torvalds", "stallman", "von-neumann", "backus"],
5438
+ "database": ["turing", "shannon", "backus", "knuth"],
5439
+ "devops": ["tesla", "edison", "faraday", "bell"],
5440
+ "security-scanner": ["shannon", "turing", "lamarr", "stallman"],
5441
+ "refactor-planner": ["gauss", "hilbert", "planck", "newton"],
5442
+ "architect": ["von-neumann", "turing", "gauss", "hilbert"],
5443
+ "critic": ["einstein", "feynman", "dirac", "bohr"],
5444
+ "e2e": ["hopper", "bell", "marconi", "tesla"],
5445
+ "performance": ["knuth", "gauss", "planck", "feynman"],
5446
+ "chaos": ["tesla", "edison", "curie", "fermi"],
5447
+ "cost": ["ohm", "bell", "marconi", "tesla"],
5448
+ // default fallback
5449
+ "default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
5450
+ };
5451
+ function assignNickname(role, used) {
5452
+ const preferences = [
5453
+ ...DOMAIN_PREFERENCES[role] ?? [],
5454
+ ...DOMAIN_PREFERENCES["default"] ?? []
5455
+ ];
5456
+ for (const key of preferences) {
5457
+ const entry = NICKNAME_POOL[key];
5458
+ if (entry && !used.has(key)) {
5459
+ return `${entry.name} (${formatRole(role)})`;
5460
+ }
5461
+ }
5462
+ for (const entry of ALL_NICKNAMES) {
5463
+ const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
5464
+ if (key && !used.has(key)) {
5465
+ return `${entry.name} (${formatRole(role)})`;
5466
+ }
5467
+ }
5468
+ const counter = used.size + 1;
5469
+ return `Scientist #${counter} (${formatRole(role)})`;
5470
+ }
5471
+ function formatRole(role) {
5472
+ return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5473
+ }
5474
+
5261
5475
  // src/coordination/multi-agent-coordinator.ts
5262
5476
  var DefaultMultiAgentCoordinator = class extends EventEmitter {
5263
5477
  coordinatorId;
@@ -5265,6 +5479,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5265
5479
  runner;
5266
5480
  fleetBus;
5267
5481
  subagents = /* @__PURE__ */ new Map();
5482
+ /**
5483
+ * Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
5484
+ * Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
5485
+ * (parallel/eternal engine, SDD parallel run) don't go through
5486
+ * `Director.spawn()` where nicknames are normally assigned, so the
5487
+ * coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
5488
+ * names) to memorable ones here — that's what surfaces in the fleet monitor.
5489
+ */
5490
+ usedNicknames = /* @__PURE__ */ new Set();
5268
5491
  pendingTasks = [];
5269
5492
  completedResults = [];
5270
5493
  totalIterations = 0;
@@ -5313,7 +5536,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5313
5536
  this.config.maxConcurrent = Math.floor(n);
5314
5537
  this.tryDispatchNext();
5315
5538
  }
5539
+ /**
5540
+ * Upgrade a placeholder/role-derived name to a memorable scientist nickname
5541
+ * (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
5542
+ * empty, equals the role (case-insensitive), is a generic default
5543
+ * ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
5544
+ * Explicit, human-chosen names — including nicknames already assigned by
5545
+ * `Director.spawn()` — are left untouched, so this never double-assigns.
5546
+ */
5547
+ withNickname(subagent) {
5548
+ const role = subagent.role ?? "subagent";
5549
+ const name = subagent.name?.trim() ?? "";
5550
+ const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
5551
+ if (!isPlaceholder) return subagent;
5552
+ const nickname = assignNickname(role, this.usedNicknames);
5553
+ const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
5554
+ this.usedNicknames.add(baseKey);
5555
+ return { ...subagent, name: nickname };
5556
+ }
5316
5557
  async spawn(subagent) {
5558
+ subagent = this.withNickname(subagent);
5317
5559
  const id = subagent.id || randomUUID();
5318
5560
  if (this.subagents.has(id)) {
5319
5561
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -5907,12 +6149,12 @@ var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
5907
6149
  var ParallelEternalEngine = class {
5908
6150
  constructor(opts) {
5909
6151
  this.opts = opts;
5910
- this.goalPath = goalFilePath(opts.projectRoot);
6152
+ this.goalPath = opts.goalPath ?? goalFilePath(opts.projectRoot);
5911
6153
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
5912
6154
  this.timeoutMs = opts.iterationTimeoutMs ?? 3e5;
5913
6155
  this.dispatchEnabled = opts.dispatch !== false;
5914
6156
  this.dispatchClassifier = opts.dispatchClassifier;
5915
- this.agentFactory = opts.subagentFactory ?? (async (config) => ({
6157
+ this.agentFactory = opts.subagentFactory ?? (async (_config) => ({
5916
6158
  agent: this.opts.agent,
5917
6159
  events: this.opts.agent.events
5918
6160
  }));
@@ -6006,7 +6248,6 @@ var ParallelEternalEngine = class {
6006
6248
  }
6007
6249
  const tasks = await this.decomposeGoal(goal);
6008
6250
  if (!tasks || tasks.length === 0) {
6009
- await sleep2(5e3);
6010
6251
  return false;
6011
6252
  }
6012
6253
  const fanOut = await this.fanOut(goal, tasks);
@@ -6027,6 +6268,7 @@ var ParallelEternalEngine = class {
6027
6268
  });
6028
6269
  if (fanOut.goalComplete) {
6029
6270
  this.stopRequested = true;
6271
+ this.state = "stopped";
6030
6272
  return true;
6031
6273
  }
6032
6274
  await this.maybeCompact();
@@ -6207,7 +6449,11 @@ ${lastFew}` : "No prior iterations.",
6207
6449
  } finally {
6208
6450
  clearTimeout(timer);
6209
6451
  }
6210
- } catch {
6452
+ } catch (err) {
6453
+ this.opts.onError?.(
6454
+ err instanceof Error ? err : new Error(String(err)),
6455
+ this.consecutiveFailures
6456
+ );
6211
6457
  return [];
6212
6458
  }
6213
6459
  }