@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
package/dist/sdd/index.js CHANGED
@@ -2058,7 +2058,7 @@ function analyzeCriticalPath(graph) {
2058
2058
  blockedTasks
2059
2059
  };
2060
2060
  }
2061
- function getTransitiveBlocked(graph, taskId, blocksMap) {
2061
+ function getTransitiveBlocked(_graph, taskId, blocksMap) {
2062
2062
  const visited = /* @__PURE__ */ new Set();
2063
2063
  const queue = [taskId];
2064
2064
  while (queue.length > 0) {
@@ -2074,7 +2074,7 @@ function getTransitiveBlocked(graph, taskId, blocksMap) {
2074
2074
  }
2075
2075
  return visited;
2076
2076
  }
2077
- function computeCriticalPath(graph, topoOrder, blockedByMap) {
2077
+ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
2078
2078
  const allIds = Array.from(graph.nodes.keys());
2079
2079
  if (allIds.length === 0) return [];
2080
2080
  const dist = /* @__PURE__ */ new Map();
@@ -2492,13 +2492,11 @@ function createAutoExecutor(opts) {
2492
2492
 
2493
2493
  // src/sdd/sdd-task-decomposer.ts
2494
2494
  var SddTaskDecomposer = class {
2495
- constructor(tracker, graph, opts = {}) {
2495
+ constructor(tracker, _graph, opts = {}) {
2496
2496
  this.tracker = tracker;
2497
- this.graph = graph;
2498
2497
  this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 4));
2499
2498
  }
2500
2499
  tracker;
2501
- graph;
2502
2500
  slots;
2503
2501
  wave = 0;
2504
2502
  // -------------------------------------------------------------------
@@ -2616,19 +2614,16 @@ var SubagentBudget = class _SubagentBudget {
2616
2614
  tokenOutput = 0;
2617
2615
  costUsd = 0;
2618
2616
  startTime = null;
2619
- _onThreshold;
2620
2617
  /**
2621
- * Tracks which budget kinds currently have an extension request
2622
- * in flight. While a kind is here, further `checkLimit` calls for the
2623
- * same kind are no-ops without this dedup, every `recordIteration`
2624
- * after the limit is reached spawns a fresh decision Promise (until
2625
- * the first one lands and patches limits), flooding the FleetBus
2626
- * with redundant threshold events. Cleared in `negotiateExtension`'s
2627
- * `finally`.
2618
+ * Timestamp of the most recent activity (iteration / tool call / token
2619
+ * usage / streamed progress). Drives the idle timeout reset by
2620
+ * `markActivity()`. Initialised to `start()` time so a never-active agent
2621
+ * still eventually trips its idle window.
2628
2622
  */
2629
- pendingExtensions = /* @__PURE__ */ new Set();
2623
+ lastActivityTime = null;
2624
+ _onThreshold;
2630
2625
  /**
2631
- * Hard cap on how long `negotiateExtension` waits for the coordinator to
2626
+ * Hard cap on how long `_negotiateExtension` waits for the coordinator to
2632
2627
  * respond before defaulting to 'stop'. Without this fallback an absent
2633
2628
  * or hung listener (Director not built / event filter detached mid-run)
2634
2629
  * leaves the budget over-limit and never enforces anything.
@@ -2669,6 +2664,24 @@ var SubagentBudget = class _SubagentBudget {
2669
2664
  }
2670
2665
  start() {
2671
2666
  this.startTime = Date.now();
2667
+ this.lastActivityTime = this.startTime;
2668
+ }
2669
+ /**
2670
+ * Reset the idle clock. Called on any sign of forward progress —
2671
+ * iterations, tool calls, token usage, and streamed tool/text progress —
2672
+ * so a long-but-productive subagent never trips its `idleTimeoutMs`.
2673
+ */
2674
+ markActivity() {
2675
+ this.lastActivityTime = Date.now();
2676
+ }
2677
+ /**
2678
+ * Milliseconds since the last activity signal. Returns 0 before `start()`
2679
+ * (nothing to measure yet). Used by the coordinator watchdog to decide
2680
+ * whether to re-arm (still active) or reap (genuinely idle).
2681
+ */
2682
+ idleMs() {
2683
+ const since = this.lastActivityTime ?? this.startTime;
2684
+ return since === null ? 0 : Date.now() - since;
2672
2685
  }
2673
2686
  /** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
2674
2687
  isNearLimit() {
@@ -2690,38 +2703,88 @@ var SubagentBudget = class _SubagentBudget {
2690
2703
  * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
2691
2704
  * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
2692
2705
  */
2693
- checkLimit(kind, used, limit) {
2706
+ /**
2707
+ * Collects all exceeded budget kinds into a single NOOP-free negotiation.
2708
+ * Called by recordIteration / recordToolCall / recordUsage — each may call
2709
+ * this for its own kind. The first call starts the negotiation and stores
2710
+ * the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
2711
+ * kinds (while a negotiation is in flight) are NOOPs — they don't start
2712
+ * new conversations with the coordinator. This prevents an EventBus flood
2713
+ * when multiple budget kinds are exceeded simultaneously in one iteration.
2714
+ *
2715
+ * Returns the kinds that were found to be exceeded (for logging/debugging).
2716
+ */
2717
+ checkLimits(elapsedMs) {
2718
+ const exceeded = [];
2719
+ if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2720
+ exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
2721
+ }
2722
+ if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2723
+ exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
2724
+ }
2725
+ const totalTokens = this.tokenInput + this.tokenOutput;
2726
+ if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2727
+ exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
2728
+ }
2729
+ if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2730
+ exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
2731
+ }
2732
+ if (elapsedMs !== void 0) {
2733
+ const idle = this.idleMs();
2734
+ if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
2735
+ exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
2736
+ } else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
2737
+ exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
2738
+ }
2739
+ }
2740
+ if (exceeded.length === 0) return [];
2694
2741
  if (!this._onThreshold) {
2695
- throw new BudgetExceededError(kind, limit, used);
2742
+ const first2 = exceeded[0];
2743
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2696
2744
  }
2697
2745
  if (this._mode === "sync") {
2698
- throw new BudgetExceededError(kind, limit, used);
2746
+ const first2 = exceeded[0];
2747
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2699
2748
  }
2700
2749
  const bus = this._events;
2701
2750
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
2702
- throw new BudgetExceededError(kind, limit, used);
2751
+ const first2 = exceeded[0];
2752
+ throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2753
+ }
2754
+ for (const entry of exceeded) {
2755
+ if (this._pendingNegotiations.has(entry.kind)) continue;
2756
+ const decision2 = this._negotiateExtension(entry.kind, exceeded);
2757
+ this._pendingNegotiations.set(entry.kind, decision2);
2703
2758
  }
2704
- if (this.pendingExtensions.has(kind)) return;
2705
- this.pendingExtensions.add(kind);
2706
- const decision = this.negotiateExtension(kind, used, limit);
2707
- throw new BudgetThresholdSignal(kind, limit, used, decision);
2759
+ const first = exceeded[0];
2760
+ const decision = this._pendingNegotiations.get(first.kind);
2761
+ throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
2708
2762
  }
2763
+ /**
2764
+ * Per-kind in-flight negotiation Promises. Each budget kind can have its
2765
+ * own concurrent negotiation — e.g. iterations and timeout can both
2766
+ * be exceeded simultaneously without blocking each other. The same kind
2767
+ * cannot start two concurrent negotiations (dedup guard).
2768
+ * Cleared in `_negotiateExtension`'s `finally` block.
2769
+ */
2770
+ _pendingNegotiations = /* @__PURE__ */ new Map();
2709
2771
  /**
2710
2772
  * Drive the threshold handler to a decision. Resolves with `'stop'`
2711
2773
  * (signal the runner to abort) or `{ extend: ... }` (limits already
2712
- * patched in-place; the runner should not abort). Always releases the
2713
- * `pendingExtensions` slot in `finally`.
2774
+ * patched in-place; the runner should not abort). Clears the
2775
+ * per-kind slot in `_pendingNegotiations` in `finally`.
2714
2776
  *
2715
2777
  * The 'continue' return from a sync handler is treated as
2716
2778
  * `{ extend: {} }` — keep going without patching; next overrun fires
2717
2779
  * a fresh signal.
2718
2780
  */
2719
- async negotiateExtension(kind, used, limit) {
2781
+ async _negotiateExtension(kind, exceeded) {
2720
2782
  try {
2783
+ const first = exceeded[0];
2721
2784
  const result = this._onThreshold({
2722
- kind,
2723
- used,
2724
- limit,
2785
+ kind: first.kind,
2786
+ used: first.used,
2787
+ limit: first.limit,
2725
2788
  requestDecision: () => {
2726
2789
  const bus = this._events;
2727
2790
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
@@ -2738,20 +2801,22 @@ var SubagentBudget = class _SubagentBudget {
2738
2801
  () => respond("stop"),
2739
2802
  _SubagentBudget.DECISION_TIMEOUT_MS
2740
2803
  );
2741
- bus.emit("budget.threshold_reached", {
2742
- kind,
2743
- used,
2744
- limit,
2745
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2746
- extend: (extra) => {
2747
- clearTimeout(fallback);
2748
- respond({ extend: extra });
2749
- },
2750
- deny: () => {
2751
- clearTimeout(fallback);
2752
- respond("stop");
2753
- }
2754
- });
2804
+ for (const { kind: kind2, used, limit } of exceeded) {
2805
+ bus.emit("budget.threshold_reached", {
2806
+ kind: kind2,
2807
+ used,
2808
+ limit,
2809
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
2810
+ extend: (extra) => {
2811
+ clearTimeout(fallback);
2812
+ respond({ extend: extra });
2813
+ },
2814
+ deny: () => {
2815
+ clearTimeout(fallback);
2816
+ respond("stop");
2817
+ }
2818
+ });
2819
+ }
2755
2820
  });
2756
2821
  }
2757
2822
  });
@@ -2775,54 +2840,59 @@ var SubagentBudget = class _SubagentBudget {
2775
2840
  if (ext.timeoutMs !== void 0) {
2776
2841
  this.limits.timeoutMs = ext.timeoutMs;
2777
2842
  }
2843
+ if (ext.idleTimeoutMs !== void 0) {
2844
+ this.limits.idleTimeoutMs = ext.idleTimeoutMs;
2845
+ }
2778
2846
  return decision;
2779
2847
  } finally {
2780
- this.pendingExtensions.delete(kind);
2848
+ this._pendingNegotiations.delete(kind);
2781
2849
  }
2782
2850
  }
2783
2851
  recordIteration() {
2784
2852
  this.iterations++;
2785
- if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
2786
- void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
2787
- }
2853
+ this.markActivity();
2854
+ void this.checkLimits();
2788
2855
  }
2789
2856
  recordToolCall() {
2790
2857
  this.toolCalls++;
2791
- if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
2792
- void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
2793
- }
2858
+ this.markActivity();
2859
+ void this.checkLimits();
2794
2860
  }
2795
2861
  recordUsage(usage, costUsd = 0) {
2796
2862
  this.tokenInput += usage.input;
2797
2863
  this.tokenOutput += usage.output;
2798
2864
  this.costUsd += costUsd;
2799
- const totalTokens = this.tokenInput + this.tokenOutput;
2800
- if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
2801
- void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
2802
- }
2803
- if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
2804
- void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
2805
- }
2865
+ this.markActivity();
2866
+ void this.checkLimits();
2806
2867
  }
2807
2868
  /**
2808
- * Wall-clock budget check. Unlike other limits, timeout check passes through
2809
- * `checkLimit` and is subject to the same negotiation-mode decision table.
2810
- * In practice, `'sync'` mode (the usual test configuration) means a timeout
2811
- * immediately throws `BudgetExceededError`. In production with a coordinator,
2812
- * a timeout emits `budget.threshold_reached` so the Director can decide whether
2813
- * to extend or abort.
2869
+ * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
2870
+ * wall-clock time cannot be "extended" by the coordinator, so it throws
2871
+ * synchronously rather than entering the negotiation flow.
2872
+ *
2873
+ * Decision table:
2874
+ * - no `onThreshold` handler → throw `BudgetExceededError`
2875
+ * - `mode === 'sync'` → throw `BudgetExceededError`
2876
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
2877
+ * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
2814
2878
  */
2815
2879
  checkTimeout() {
2816
- if (this.startTime === null || this.limits.timeoutMs === void 0) return;
2880
+ if (this.startTime === null) return;
2881
+ const { timeoutMs, idleTimeoutMs } = this.limits;
2882
+ if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
2817
2883
  const elapsed = Date.now() - this.startTime;
2818
- if (elapsed > this.limits.timeoutMs) {
2819
- void this.checkLimit("timeout", elapsed, this.limits.timeoutMs);
2820
- }
2884
+ const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
2885
+ const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
2886
+ if (!wallTripped && !idleTripped) return;
2887
+ void this.checkLimits(elapsed);
2821
2888
  }
2822
- /** Returns true if a timeout has occurred without throwing. Useful for races. */
2889
+ /** Returns true if a wall-clock or idle timeout has occurred without throwing. */
2823
2890
  isTimedOut() {
2824
- if (this.startTime === null || this.limits.timeoutMs === void 0) return false;
2825
- return Date.now() - this.startTime > this.limits.timeoutMs;
2891
+ if (this.startTime === null) return false;
2892
+ const { timeoutMs, idleTimeoutMs } = this.limits;
2893
+ if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
2894
+ if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
2895
+ return false;
2826
2896
  }
2827
2897
  usage() {
2828
2898
  return {
@@ -2929,6 +2999,7 @@ function makeAgentSubagentRunner(opts) {
2929
2999
  // Cheap: O(1) per progress event, and the budget short-circuits
2930
3000
  // when timeoutMs is unset (most subagents have one set anyway).
2931
3001
  events.on("tool.progress", () => {
3002
+ ctx.budget.markActivity();
2932
3003
  try {
2933
3004
  ctx.budget.checkTimeout();
2934
3005
  } catch (e) {
@@ -2945,6 +3016,7 @@ function makeAgentSubagentRunner(opts) {
2945
3016
  currentToolName = e.name;
2946
3017
  }),
2947
3018
  events.on("provider.text_delta", (e) => {
3019
+ ctx.budget.markActivity();
2948
3020
  streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
2949
3021
  })
2950
3022
  );
@@ -5282,6 +5354,7 @@ var ALL_AGENT_DEFINITIONS = [
5282
5354
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
5283
5355
  )
5284
5356
  });
5357
+ var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
5285
5358
  var FLEET_ROSTER_BUDGETS = {
5286
5359
  "audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
5287
5360
  "bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
@@ -5293,7 +5366,7 @@ var FLEET_ROSTER_BUDGETS = {
5293
5366
  )
5294
5367
  };
5295
5368
  var GENERIC_SUBAGENT_BUDGET = {
5296
- timeoutMs: 3 * 60 * 60 * 1e3,
5369
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
5297
5370
  maxIterations: 5e3,
5298
5371
  maxToolCalls: 15e3
5299
5372
  };
@@ -5303,7 +5376,13 @@ function applyRosterBudget(cfg) {
5303
5376
  if (!defaultBudget) return cfg;
5304
5377
  return {
5305
5378
  ...cfg,
5306
- timeoutMs: cfg.timeoutMs ?? defaultBudget.timeoutMs,
5379
+ // Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
5380
+ // do NOT impose the roster's historical multi-hour wall-clock default — it
5381
+ // killed agents that were still actively working. Reaping is idle-based.
5382
+ timeoutMs: cfg.timeoutMs,
5383
+ // Idle window is the default reaper. Resets on activity, so a long-but-
5384
+ // productive run is never killed; only a genuine stall is reaped.
5385
+ idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
5307
5386
  maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
5308
5387
  maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
5309
5388
  maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
@@ -5371,6 +5450,107 @@ FLEET_ROSTER_BUDGETS["goose"] = { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations:
5371
5450
  ...Object.fromEntries(ACP_AGENTS.map((a) => [a.role, a]))
5372
5451
  });
5373
5452
 
5453
+ // src/coordination/subagent-nicknames.ts
5454
+ var NICKNAME_POOL = {
5455
+ // Physics & fundamental sciences
5456
+ "einstein": { name: "Einstein", domain: "physics" },
5457
+ "newton": { name: "Newton", domain: "physics" },
5458
+ "feynman": { name: "Feynman", domain: "physics" },
5459
+ "dirac": { name: "Dirac", domain: "physics" },
5460
+ "bohr": { name: "Bohr", domain: "physics" },
5461
+ "planck": { name: "Planck", domain: "physics" },
5462
+ "curie": { name: "Curie", domain: "physics" },
5463
+ "fermi": { name: "Fermi", domain: "physics" },
5464
+ "heisenberg": { name: "Heisenberg", domain: "physics" },
5465
+ "schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
5466
+ // Mathematics
5467
+ "euclid": { name: "Euclid", domain: "math" },
5468
+ "gauss": { name: "Gauss", domain: "math" },
5469
+ "turing": { name: "Turing", domain: "math" },
5470
+ "poincare": { name: "Poincar\xE9", domain: "math" },
5471
+ "riemann": { name: "Riemann", domain: "math" },
5472
+ "hilbert": { name: "Hilbert", domain: "math" },
5473
+ "pythagoras": { name: "Pythagoras", domain: "math" },
5474
+ // Computing & information theory
5475
+ "von-neumann": { name: "Von Neumann", domain: "computing" },
5476
+ "shannon": { name: "Shannon", domain: "computing" },
5477
+ "hopper": { name: "Hopper", domain: "computing" },
5478
+ "backus": { name: "Backus", domain: "computing" },
5479
+ "knuth": { name: "Knuth", domain: "computing" },
5480
+ "torvalds": { name: "Torvalds", domain: "computing" },
5481
+ "stallman": { name: "Stallman", domain: "computing" },
5482
+ "berners-lee": { name: "Berners-Lee", domain: "computing" },
5483
+ "babbage": { name: "Babbage", domain: "computing" },
5484
+ "lovelace": { name: "Lovelace", domain: "computing" },
5485
+ "klein": { name: "Klein", domain: "computing" },
5486
+ // Electronics & electrical engineering
5487
+ "edison": { name: "Edison", domain: "ee" },
5488
+ "tesla": { name: "Tesla", domain: "ee" },
5489
+ "faraday": { name: "Faraday", domain: "ee" },
5490
+ "maxwell": { name: "Maxwell", domain: "ee" },
5491
+ "ohm": { name: "Ohm", domain: "ee" },
5492
+ "bell": { name: "Bell", domain: "ee" },
5493
+ "marconi": { name: "Marconi", domain: "ee" },
5494
+ "lamarr": { name: "Lamarr", domain: "ee" },
5495
+ // General science / multi-disciplinary
5496
+ "darwin": { name: "Darwin", domain: "biology" },
5497
+ "mendel": { name: "Mendel", domain: "biology" },
5498
+ "pasteur": { name: "Pasteur", domain: "biology" },
5499
+ "hawking": { name: "Hawking", domain: "cosmology" },
5500
+ "sagan": { name: "Sagan", domain: "cosmology" },
5501
+ // Chemistry / materials
5502
+ "lavoisier": { name: "Lavoisier", domain: "chemistry" },
5503
+ "mendeleev": { name: "Mendeleev", domain: "chemistry" }
5504
+ };
5505
+ var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
5506
+ var DOMAIN_PREFERENCES = {
5507
+ "security": ["shannon", "turing", "lamarr", "stallman"],
5508
+ "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
5509
+ "refactor": ["gauss", "hilbert", "euclid", "planck"],
5510
+ "audit-log": ["sagan", "hawking", "poincare", "newton"],
5511
+ "planner": ["hilbert", "gauss", "turing", "euclid"],
5512
+ "researcher": ["sagan", "hawking", "darwin", "pasteur"],
5513
+ "explorer": ["marconi", "bell", "columbus", "polo"],
5514
+ "testing": ["pasteur", "curie", "fermi", "bohr"],
5515
+ "frontend": ["lovelace", "hopper", "babbage", "backus"],
5516
+ "backend": ["torvalds", "stallman", "von-neumann", "backus"],
5517
+ "database": ["turing", "shannon", "backus", "knuth"],
5518
+ "devops": ["tesla", "edison", "faraday", "bell"],
5519
+ "security-scanner": ["shannon", "turing", "lamarr", "stallman"],
5520
+ "refactor-planner": ["gauss", "hilbert", "planck", "newton"],
5521
+ "architect": ["von-neumann", "turing", "gauss", "hilbert"],
5522
+ "critic": ["einstein", "feynman", "dirac", "bohr"],
5523
+ "e2e": ["hopper", "bell", "marconi", "tesla"],
5524
+ "performance": ["knuth", "gauss", "planck", "feynman"],
5525
+ "chaos": ["tesla", "edison", "curie", "fermi"],
5526
+ "cost": ["ohm", "bell", "marconi", "tesla"],
5527
+ // default fallback
5528
+ "default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
5529
+ };
5530
+ function assignNickname(role, used) {
5531
+ const preferences = [
5532
+ ...DOMAIN_PREFERENCES[role] ?? [],
5533
+ ...DOMAIN_PREFERENCES["default"] ?? []
5534
+ ];
5535
+ for (const key of preferences) {
5536
+ const entry = NICKNAME_POOL[key];
5537
+ if (entry && !used.has(key)) {
5538
+ return `${entry.name} (${formatRole(role)})`;
5539
+ }
5540
+ }
5541
+ for (const entry of ALL_NICKNAMES) {
5542
+ const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
5543
+ if (key && !used.has(key)) {
5544
+ return `${entry.name} (${formatRole(role)})`;
5545
+ }
5546
+ }
5547
+ const counter = used.size + 1;
5548
+ return `Scientist #${counter} (${formatRole(role)})`;
5549
+ }
5550
+ function formatRole(role) {
5551
+ return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5552
+ }
5553
+
5374
5554
  // src/coordination/multi-agent-coordinator.ts
5375
5555
  var DefaultMultiAgentCoordinator = class extends EventEmitter {
5376
5556
  coordinatorId;
@@ -5378,6 +5558,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5378
5558
  runner;
5379
5559
  fleetBus;
5380
5560
  subagents = /* @__PURE__ */ new Map();
5561
+ /**
5562
+ * Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
5563
+ * Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
5564
+ * (parallel/eternal engine, SDD parallel run) don't go through
5565
+ * `Director.spawn()` where nicknames are normally assigned, so the
5566
+ * coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
5567
+ * names) to memorable ones here — that's what surfaces in the fleet monitor.
5568
+ */
5569
+ usedNicknames = /* @__PURE__ */ new Set();
5381
5570
  pendingTasks = [];
5382
5571
  completedResults = [];
5383
5572
  totalIterations = 0;
@@ -5426,7 +5615,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5426
5615
  this.config.maxConcurrent = Math.floor(n);
5427
5616
  this.tryDispatchNext();
5428
5617
  }
5618
+ /**
5619
+ * Upgrade a placeholder/role-derived name to a memorable scientist nickname
5620
+ * (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
5621
+ * empty, equals the role (case-insensitive), is a generic default
5622
+ * ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
5623
+ * Explicit, human-chosen names — including nicknames already assigned by
5624
+ * `Director.spawn()` — are left untouched, so this never double-assigns.
5625
+ */
5626
+ withNickname(subagent) {
5627
+ const role = subagent.role ?? "subagent";
5628
+ const name = subagent.name?.trim() ?? "";
5629
+ const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
5630
+ if (!isPlaceholder) return subagent;
5631
+ const nickname = assignNickname(role, this.usedNicknames);
5632
+ const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
5633
+ this.usedNicknames.add(baseKey);
5634
+ return { ...subagent, name: nickname };
5635
+ }
5429
5636
  async spawn(subagent) {
5637
+ subagent = this.withNickname(subagent);
5430
5638
  const id = subagent.id || randomUUID();
5431
5639
  if (this.subagents.has(id)) {
5432
5640
  throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
@@ -5734,13 +5942,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5734
5942
  const rawMaxTokens = subagent.config.maxTokens;
5735
5943
  const rawMaxCostUsd = subagent.config.maxCostUsd;
5736
5944
  const rawTimeoutMs = subagent.config.timeoutMs;
5945
+ const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
5737
5946
  const configWithRosterDefaults = applyRosterBudget(subagent.config);
5738
5947
  const budget = new SubagentBudget({
5739
5948
  maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
5740
5949
  maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
5741
5950
  maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
5742
5951
  maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
5743
- timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs
5952
+ // Wall-clock cap is opt-in (explicit config / defaultBudget only); the
5953
+ // roster no longer supplies one. Idle is the default reaper.
5954
+ timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
5955
+ idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
5744
5956
  });
5745
5957
  subagent.activeBudget = budget;
5746
5958
  if (!this.runner) {
@@ -5787,63 +5999,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
5787
5999
  }
5788
6000
  async executeWithTimeout(runner, task, ctx, budget) {
5789
6001
  const initialTimeoutMs = budget.limits.timeoutMs;
5790
- if (initialTimeoutMs === void 0) return runner(task, ctx);
6002
+ const idleLimitMs = budget.limits.idleTimeoutMs;
6003
+ if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
6004
+ return runner(task, ctx);
6005
+ }
5791
6006
  const start = Date.now();
5792
6007
  let timer = null;
5793
6008
  const timeoutPromise = new Promise((_, reject) => {
5794
6009
  const armFor = (ms) => {
5795
6010
  if (timer) clearTimeout(timer);
5796
- timer = setTimeout(async () => {
5797
- const elapsed = Date.now() - start;
5798
- const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
5799
- if (!budget.onThreshold) {
5800
- this.subagents.get(ctx.subagentId)?.abortController.abort();
5801
- reject(new BudgetExceededError("timeout", limit, elapsed));
6011
+ timer = setTimeout(onTick, Math.max(0, ms));
6012
+ };
6013
+ const scheduleNext = () => {
6014
+ const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
6015
+ const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
6016
+ armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
6017
+ };
6018
+ const onTick = async () => {
6019
+ const elapsed = Date.now() - start;
6020
+ const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
6021
+ const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
6022
+ const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
6023
+ const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
6024
+ if (idleExceeded && !wallExceeded) {
6025
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
6026
+ reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
6027
+ return;
6028
+ }
6029
+ if (!wallExceeded) {
6030
+ scheduleNext();
6031
+ return;
6032
+ }
6033
+ const limit = wallLimit;
6034
+ if (!budget.onThreshold) {
6035
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
6036
+ reject(new BudgetExceededError("timeout", limit, elapsed));
6037
+ return;
6038
+ }
6039
+ try {
6040
+ const result = budget.onThreshold({
6041
+ kind: "timeout",
6042
+ used: elapsed,
6043
+ limit,
6044
+ requestDecision: () => new Promise((resolveDecision) => {
6045
+ budget._events?.emit("budget.threshold_reached", {
6046
+ kind: "timeout",
6047
+ used: elapsed,
6048
+ limit,
6049
+ timeoutMs: 6e4,
6050
+ extend: (extra) => resolveDecision({ extend: extra }),
6051
+ deny: () => resolveDecision("stop")
6052
+ });
6053
+ })
6054
+ });
6055
+ const decision = typeof result === "string" ? result : await result;
6056
+ if (decision === "continue" || decision === "throw" || decision === "stop") {
6057
+ armFor(Math.max(1e3, limit));
5802
6058
  return;
5803
6059
  }
5804
- try {
5805
- const result = budget.onThreshold({
5806
- kind: "timeout",
5807
- used: elapsed,
5808
- limit,
5809
- requestDecision: () => new Promise((resolveDecision) => {
5810
- budget._events?.emit("budget.threshold_reached", {
5811
- kind: "timeout",
5812
- used: elapsed,
5813
- limit,
5814
- timeoutMs: 6e4,
5815
- extend: (extra) => resolveDecision({ extend: extra }),
5816
- deny: () => resolveDecision("stop")
5817
- });
5818
- })
5819
- });
5820
- const decision = typeof result === "string" ? result : await result;
5821
- if (decision === "continue") {
5822
- armFor(Math.max(1e3, limit));
5823
- return;
5824
- }
5825
- if (decision === "throw" || decision === "stop") {
5826
- armFor(Math.max(1e3, limit));
5827
- return;
5828
- }
5829
- if (decision.extend.timeoutMs !== void 0) {
5830
- budget.limits.timeoutMs = decision.extend.timeoutMs;
5831
- const newLimit = decision.extend.timeoutMs;
5832
- const remaining = Math.max(1e3, newLimit - elapsed);
5833
- armFor(remaining);
5834
- return;
5835
- }
5836
- this.subagents.get(ctx.subagentId)?.abortController.abort();
5837
- reject(new BudgetExceededError("timeout", limit, elapsed));
5838
- } catch (err) {
5839
- this.subagents.get(ctx.subagentId)?.abortController.abort();
5840
- reject(
5841
- err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
5842
- );
6060
+ if (decision.extend.timeoutMs !== void 0) {
6061
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
6062
+ scheduleNext();
6063
+ return;
5843
6064
  }
5844
- }, ms);
6065
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
6066
+ reject(new BudgetExceededError("timeout", limit, elapsed));
6067
+ } catch (err) {
6068
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
6069
+ reject(
6070
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
6071
+ );
6072
+ }
5845
6073
  };
5846
- armFor(initialTimeoutMs);
6074
+ scheduleNext();
5847
6075
  });
5848
6076
  try {
5849
6077
  return await Promise.race([runner(task, ctx), timeoutPromise]);
@@ -6085,7 +6313,7 @@ var SddParallelRun = class {
6085
6313
  this.coordinator.setRunner?.(runner);
6086
6314
  }
6087
6315
  defaultFactory() {
6088
- return async (config) => ({
6316
+ return async (_config) => ({
6089
6317
  agent: this.opts.agent,
6090
6318
  events: this.opts.agent.events
6091
6319
  });