@wrongstack/core 0.260.0 → 0.265.1

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 (99) hide show
  1. package/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
  3. package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
  4. package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
  5. package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
  6. package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
  7. package/dist/coordination/index.d.ts +1737 -15
  8. package/dist/coordination/index.js +3152 -494
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
  11. package/dist/defaults/index.d.ts +28 -28
  12. package/dist/defaults/index.js +1804 -1363
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  15. package/dist/execution/index.d.ts +16 -16
  16. package/dist/execution/index.js +933 -672
  17. package/dist/execution/index.js.map +1 -1
  18. package/dist/execution/prompt-enhancer.d.ts +1 -1
  19. package/dist/execution/prompt-enhancer.js +7 -1
  20. package/dist/execution/prompt-enhancer.js.map +1 -1
  21. package/dist/extension/index.d.ts +6 -6
  22. package/dist/extension/index.js.map +1 -1
  23. package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
  24. package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
  25. package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
  26. package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
  27. package/dist/index.d.ts +189 -104
  28. package/dist/index.js +24693 -21162
  29. package/dist/index.js.map +1 -1
  30. package/dist/infrastructure/index.d.ts +6 -6
  31. package/dist/infrastructure/index.js +12 -8
  32. package/dist/infrastructure/index.js.map +1 -1
  33. package/dist/kernel/index.d.ts +9 -9
  34. package/dist/kernel/index.js +7 -2
  35. package/dist/kernel/index.js.map +1 -1
  36. package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
  37. package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +80 -31
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
  43. package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/observability/index.js +8 -3
  46. package/dist/observability/index.js.map +1 -1
  47. package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
  48. package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
  49. package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
  50. package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
  51. package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
  52. package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
  53. package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
  54. package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
  55. package/dist/sdd/index.d.ts +8 -8
  56. package/dist/sdd/index.js +313 -122
  57. package/dist/sdd/index.js.map +1 -1
  58. package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
  59. package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
  60. package/dist/security/index.d.ts +5 -5
  61. package/dist/security/index.js +411 -225
  62. package/dist/security/index.js.map +1 -1
  63. package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
  64. package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
  65. package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
  66. package/dist/skills/index.js +67 -64
  67. package/dist/skills/index.js.map +1 -1
  68. package/dist/storage/index.d.ts +132 -16
  69. package/dist/storage/index.js +851 -432
  70. package/dist/storage/index.js.map +1 -1
  71. package/dist/tools/index.d.ts +57 -0
  72. package/dist/tools/index.js +411 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/types/index.d.ts +21 -21
  75. package/dist/types/index.js +928 -711
  76. package/dist/types/index.js.map +1 -1
  77. package/dist/utils/error.d.ts +7 -0
  78. package/dist/utils/error.js +8 -0
  79. package/dist/utils/error.js.map +1 -0
  80. package/dist/utils/index.d.ts +8 -68
  81. package/dist/utils/index.js +20 -10
  82. package/dist/utils/index.js.map +1 -1
  83. package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
  84. package/package.json +5 -1
  85. package/skills/api-design/SKILL.md +1 -1
  86. package/skills/audit-log/SKILL.md +6 -6
  87. package/skills/bug-hunter/SKILL.md +5 -5
  88. package/skills/chimera/SKILL.md +4 -4
  89. package/skills/docker-deploy/SKILL.md +1 -1
  90. package/skills/git-flow/SKILL.md +3 -3
  91. package/skills/multi-agent/SKILL.md +3 -3
  92. package/skills/node-modern/SKILL.md +1 -0
  93. package/skills/observability/SKILL.md +2 -2
  94. package/skills/output-standards/SKILL.md +51 -28
  95. package/skills/refactor-planner/SKILL.md +3 -3
  96. package/skills/security-scanner/SKILL.md +4 -3
  97. package/skills/tech-stack/SKILL.md +1 -2
  98. package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
  99. package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/sdd/index.js CHANGED
@@ -80,7 +80,12 @@ async function withFileLock(targetPath, fn, opts = {}) {
80
80
  await handle.writeFile(`${process.pid}:${Date.now()}`);
81
81
  break;
82
82
  } catch (err) {
83
- if (err.code !== "EEXIST") throw err;
83
+ const code = err.code;
84
+ if (code === "ENOENT") {
85
+ await fs.mkdir(dir, { recursive: true });
86
+ continue;
87
+ }
88
+ if (code !== "EEXIST") throw err;
84
89
  try {
85
90
  const stat2 = await fs.stat(lockPath);
86
91
  if (Date.now() - stat2.mtimeMs > staleMs) {
@@ -622,6 +627,35 @@ function topologicalSort(graph) {
622
627
  return result;
623
628
  }
624
629
 
630
+ // src/utils/error.ts
631
+ function toErrorMessage(err) {
632
+ return err instanceof Error ? err.message : String(err);
633
+ }
634
+
635
+ // src/utils/string.ts
636
+ function truncate(s, max) {
637
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
638
+ }
639
+
640
+ // src/utils/expect-defined.ts
641
+ function expectDefined(value, label) {
642
+ if (value === null || value === void 0) {
643
+ const err = new Error("Expected value to be defined");
644
+ err.name = "ExpectDefinedError";
645
+ throw err;
646
+ }
647
+ return value;
648
+ }
649
+
650
+ // src/utils/assert-never.ts
651
+ function assertNever(x, message) {
652
+ const err = new Error(
653
+ `Unhandled case: ${JSON.stringify(x)}`
654
+ );
655
+ err.name = "AssertNeverError";
656
+ throw err;
657
+ }
658
+
625
659
  // src/types/errors.ts
626
660
  var ERROR_CODES = {
627
661
  // Provider
@@ -929,7 +963,7 @@ var TaskTracker = class {
929
963
  this.opts.onPersistError ? this.opts.onPersistError(err) : console.warn(JSON.stringify({
930
964
  level: "warn",
931
965
  event: "task_tracker.save_graph_failed",
932
- message: err instanceof Error ? err.message : String(err),
966
+ message: toErrorMessage(err),
933
967
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
934
968
  }));
935
969
  });
@@ -1358,16 +1392,6 @@ var TaskGraphStore = class {
1358
1392
  }
1359
1393
  };
1360
1394
 
1361
- // src/utils/expect-defined.ts
1362
- function expectDefined(value, label) {
1363
- if (value === null || value === void 0) {
1364
- const err = new Error("Expected value to be defined");
1365
- err.name = "ExpectDefinedError";
1366
- throw err;
1367
- }
1368
- return value;
1369
- }
1370
-
1371
1395
  // src/sdd/spec-builder.ts
1372
1396
  function buildQuestioningPrompt(session, min, max) {
1373
1397
  const answered = session.answers.length;
@@ -1609,7 +1633,7 @@ var AISpecBuilder = class {
1609
1633
  * ENOSPC / EACCES doesn't silently strand session edits in memory. */
1610
1634
  autoSave() {
1611
1635
  this.saveSession().catch((err) => {
1612
- const detail = err instanceof Error ? err.message : String(err);
1636
+ const detail = toErrorMessage(err);
1613
1637
  process.emitWarning(
1614
1638
  `SpecBuilder autoSave failed: ${detail}`,
1615
1639
  "SpecBuilderWarning"
@@ -2021,11 +2045,6 @@ function templateToMarkdown(template, title) {
2021
2045
  return lines.join("\n");
2022
2046
  }
2023
2047
 
2024
- // src/utils/string.ts
2025
- function truncate(s, max) {
2026
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
2027
- }
2028
-
2029
2048
  // src/sdd/task-visualizer.ts
2030
2049
  var STATUS_ICON = {
2031
2050
  pending: "\u25CB",
@@ -2348,15 +2367,6 @@ function computeParallelGroups(graph, blockedByMap) {
2348
2367
  return groups;
2349
2368
  }
2350
2369
 
2351
- // src/utils/assert-never.ts
2352
- function assertNever(x, message) {
2353
- const err = new Error(
2354
- `Unhandled case: ${JSON.stringify(x)}`
2355
- );
2356
- err.name = "AssertNeverError";
2357
- throw err;
2358
- }
2359
-
2360
2370
  // src/sdd/spec-versioning.ts
2361
2371
  var SpecVersioning = class {
2362
2372
  versions = /* @__PURE__ */ new Map();
@@ -2782,6 +2792,7 @@ var SddTaskDecomposer = class {
2782
2792
 
2783
2793
  // src/coordination/subagent-budget.ts
2784
2794
  var TIMEOUT_PREEMPT_FRACTION = 0.85;
2795
+ var DECISION_TIMEOUT_MS = 6e4;
2785
2796
  var BudgetExceededError = class extends Error {
2786
2797
  kind;
2787
2798
  limit;
@@ -2811,6 +2822,31 @@ var BudgetThresholdSignal = class extends Error {
2811
2822
  };
2812
2823
  var SubagentBudget = class _SubagentBudget {
2813
2824
  limits;
2825
+ /** Patch one or more budget limits in-place after construction.
2826
+ * Used by the coordinator watchdog when granting an extension.
2827
+ * All fields are optional — only provided fields are updated.
2828
+ * This is the single write path for limit mutations so that future
2829
+ * validation or side-effects live in one place (M1). */
2830
+ patchLimits(ext) {
2831
+ if (ext.maxIterations !== void 0) {
2832
+ this.limits.maxIterations = ext.maxIterations;
2833
+ }
2834
+ if (ext.maxToolCalls !== void 0) {
2835
+ this.limits.maxToolCalls = ext.maxToolCalls;
2836
+ }
2837
+ if (ext.maxTokens !== void 0) {
2838
+ this.limits.maxTokens = ext.maxTokens;
2839
+ }
2840
+ if (ext.maxCostUsd !== void 0) {
2841
+ this.limits.maxCostUsd = ext.maxCostUsd;
2842
+ }
2843
+ if (ext.timeoutMs !== void 0) {
2844
+ this.limits.timeoutMs = ext.timeoutMs;
2845
+ }
2846
+ if (ext.idleTimeoutMs !== void 0) {
2847
+ this.limits.idleTimeoutMs = ext.idleTimeoutMs;
2848
+ }
2849
+ }
2814
2850
  iterations = 0;
2815
2851
  toolCalls = 0;
2816
2852
  tokenInput = 0;
@@ -2831,12 +2867,44 @@ var SubagentBudget = class _SubagentBudget {
2831
2867
  * or hung listener (Director not built / event filter detached mid-run)
2832
2868
  * leaves the budget over-limit and never enforces anything.
2833
2869
  */
2834
- static DECISION_TIMEOUT_MS = 6e4;
2870
+ static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
2835
2871
  /**
2836
2872
  * Injected by the runner when wiring the budget to its EventBus.
2837
2873
  * Used to emit `budget.threshold_reached` events in `'auto'` mode.
2838
2874
  */
2839
2875
  _events;
2876
+ /**
2877
+ * Guard against dual-path races between the coordinator watchdog
2878
+ * (`executeWithTimeout`) and the budget's own `checkTimeout()`.
2879
+ * Both paths detect `elapsed >= timeoutMs` and can emit
2880
+ * `budget.threshold_reached` for kind `'timeout'` simultaneously.
2881
+ * Set to the current `timeoutMs` ceiling by the coordinator BEFORE
2882
+ * calling `onThreshold`, and cleared after the negotiation resolves.
2883
+ * `checkTimeout()` skips its wall-clock check while this is set so
2884
+ * the coordinator's watchdog is the sole source of wall-clock timeout
2885
+ * events — `checkTimeout()` focuses exclusively on `idle_timeout`.
2886
+ */
2887
+ _watchdogActive;
2888
+ /** Returns the timeout ceiling currently being negotiated by the watchdog,
2889
+ * or `undefined` when no wall-clock negotiation is in flight.
2890
+ * Used by `executeWithTimeout` to detect a stale lock (M3). */
2891
+ get watchdogActive() {
2892
+ return this._watchdogActive;
2893
+ }
2894
+ /** Called by the coordinator watchdog BEFORE calling `onThreshold` so that
2895
+ * `checkTimeout()` skips its wall-clock check for this ceiling. Prevents
2896
+ * the budget's own `checkTimeout()` from emitting a second
2897
+ * `budget.threshold_reached` event while the watchdog is already
2898
+ * negotiating the same wall-clock deadline (C1). */
2899
+ setWatchdogNegotiation(timeoutMs) {
2900
+ this._watchdogActive = timeoutMs;
2901
+ }
2902
+ /** Clears the watchdog guard after negotiation resolves. Called in the
2903
+ * `finally` block of both the pre-empt and deadline branches so it fires
2904
+ * on every exit path: grant, deny, throw, or error. */
2905
+ clearWatchdogNegotiation() {
2906
+ this._watchdogActive = void 0;
2907
+ }
2840
2908
  /**
2841
2909
  * Negotiation mode — controls whether a threshold hit tries to emit
2842
2910
  * `budget.threshold_reached` and wait for a coordinator decision, or
@@ -2937,7 +3005,8 @@ var SubagentBudget = class _SubagentBudget {
2937
3005
  if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
2938
3006
  exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
2939
3007
  }
2940
- if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
3008
+ const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
3009
+ if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
2941
3010
  exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
2942
3011
  }
2943
3012
  }
@@ -2951,19 +3020,99 @@ var SubagentBudget = class _SubagentBudget {
2951
3020
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2952
3021
  }
2953
3022
  const bus = this._events;
2954
- if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
3023
+ if (!bus) {
2955
3024
  const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
2956
3025
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2957
3026
  }
3027
+ const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
3028
+ if (bus.hasListenerFor("budget.threshold_reached")) {
3029
+ for (const entry of exceeded) {
3030
+ if (this._pendingNegotiations.has(entry.kind)) continue;
3031
+ this._pendingNegotiations.set(entry.kind, this._negotiateExtension(entry));
3032
+ }
3033
+ const decision = this._pendingNegotiations.get(first.kind);
3034
+ if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
3035
+ throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
3036
+ }
3037
+ let hardStop = null;
2958
3038
  for (const entry of exceeded) {
2959
3039
  if (this._pendingNegotiations.has(entry.kind)) continue;
2960
- const decision2 = this._negotiateExtension(entry.kind, exceeded);
2961
- this._pendingNegotiations.set(entry.kind, decision2);
3040
+ const marker = Promise.resolve("stop");
3041
+ this._pendingNegotiations.set(entry.kind, marker);
3042
+ void marker.finally(() => this._pendingNegotiations.delete(entry.kind));
3043
+ const sync = this._invokeHandlerSync(entry);
3044
+ if (!sync) hardStop ??= new BudgetExceededError(entry.kind, entry.limit, entry.used);
2962
3045
  }
2963
- const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
2964
- const decision = this._pendingNegotiations.get(first.kind);
2965
- if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
2966
- throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
3046
+ if (hardStop) throw hardStop;
3047
+ return exceeded;
3048
+ }
3049
+ /**
3050
+ * Invoke `onThreshold` once for `entry` on the NO-LISTENER path and report
3051
+ * whether it decided synchronously. Returns `true` when the handler returned
3052
+ * a synchronous decision (already honored — an `extend` patched the limits),
3053
+ * or `false` when it returned a Promise (async; the caller hard-stops, since
3054
+ * there is no listener to resolve the negotiation). The handler is given the
3055
+ * full info shape (`requestDecision` plus direct `extend`/`deny`) so both
3056
+ * recording handlers and policy handlers work without a wired listener.
3057
+ */
3058
+ _invokeHandlerSync(entry) {
3059
+ const handler = this._onThreshold;
3060
+ if (!handler) return false;
3061
+ let extendArg;
3062
+ const result = handler({
3063
+ kind: entry.kind,
3064
+ used: entry.used,
3065
+ limit: entry.limit,
3066
+ requestDecision: () => this._busRequestDecision(entry),
3067
+ // Direct hooks for synchronous policy/recording handlers.
3068
+ extend: (extra) => {
3069
+ extendArg = extra;
3070
+ },
3071
+ deny: () => {
3072
+ }
3073
+ });
3074
+ if (result && typeof result.then === "function") return false;
3075
+ if (result === "throw") return false;
3076
+ if (result && typeof result === "object" && "extend" in result) {
3077
+ extendArg = result.extend;
3078
+ }
3079
+ if (extendArg) this.patchLimits(extendArg);
3080
+ return true;
3081
+ }
3082
+ /**
3083
+ * Emit `budget.threshold_reached` and resolve to the listener's verdict.
3084
+ * Resolves to `'stop'` immediately when there is no listener (or no bus) so
3085
+ * no negotiation can hang and no fallback timer leaks. Mirrors the
3086
+ * coordinator watchdog's own request path so both agree on the no-listener
3087
+ * default.
3088
+ */
3089
+ _busRequestDecision(entry) {
3090
+ const bus = this._events;
3091
+ if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
3092
+ return Promise.resolve("stop");
3093
+ }
3094
+ return new Promise((resolve) => {
3095
+ let resolved = false;
3096
+ const respond = (d) => {
3097
+ if (resolved) return;
3098
+ resolved = true;
3099
+ clearTimeout(fallback);
3100
+ resolve(d);
3101
+ };
3102
+ const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
3103
+ bus.emit("budget.threshold_reached", {
3104
+ kind: entry.kind,
3105
+ used: entry.used,
3106
+ limit: entry.limit,
3107
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
3108
+ // deny() wins over a same-dispatch extend(): a listener that both grants
3109
+ // and denies (or two listeners disagreeing) is resolved as a stop. The
3110
+ // grant is deferred a microtask so a synchronous deny in the same emit
3111
+ // pre-empts it; async grants still resolve normally.
3112
+ extend: (extra) => queueMicrotask(() => respond({ extend: extra })),
3113
+ deny: () => respond("stop")
3114
+ });
3115
+ });
2967
3116
  }
2968
3117
  /**
2969
3118
  * Per-kind in-flight negotiation Promises. Each budget kind can have its
@@ -2983,77 +3132,33 @@ var SubagentBudget = class _SubagentBudget {
2983
3132
  * `{ extend: {} }` — keep going without patching; next overrun fires
2984
3133
  * a fresh signal.
2985
3134
  */
2986
- async _negotiateExtension(kind, exceeded) {
3135
+ async _negotiateExtension(entry) {
2987
3136
  if (!this._onThreshold) {
2988
3137
  return "stop";
2989
3138
  }
2990
3139
  try {
2991
- const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
2992
3140
  const result = this._onThreshold({
2993
- kind: first.kind,
2994
- used: first.used,
2995
- limit: first.limit,
2996
- requestDecision: () => {
2997
- const bus = this._events;
2998
- if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
2999
- return Promise.resolve("stop");
3000
- }
3001
- return new Promise((resolve) => {
3002
- let resolved = false;
3003
- const respond = (d) => {
3004
- if (resolved) return;
3005
- resolved = true;
3006
- resolve(d);
3007
- };
3008
- const fallback = setTimeout(
3009
- () => respond("stop"),
3010
- _SubagentBudget.DECISION_TIMEOUT_MS
3011
- );
3012
- for (const { kind: kind2, used, limit } of exceeded) {
3013
- bus.emit("budget.threshold_reached", {
3014
- kind: kind2,
3015
- used,
3016
- limit,
3017
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
3018
- extend: (extra) => {
3019
- clearTimeout(fallback);
3020
- respond({ extend: extra });
3021
- },
3022
- deny: () => {
3023
- clearTimeout(fallback);
3024
- respond("stop");
3025
- }
3026
- });
3027
- }
3028
- });
3141
+ kind: entry.kind,
3142
+ used: entry.used,
3143
+ limit: entry.limit,
3144
+ // One event for THIS kind only — each exceeded kind has its own
3145
+ // negotiation (and its own resolve), so there is no cross-kind
3146
+ // first-wins drop and no O(N^2) re-emission.
3147
+ requestDecision: () => this._busRequestDecision(entry),
3148
+ extend: (extra) => {
3149
+ this.patchLimits(extra);
3150
+ },
3151
+ deny: () => {
3029
3152
  }
3030
3153
  });
3031
3154
  if (result === "throw") return "stop";
3032
3155
  if (result === "continue") return { extend: {} };
3033
3156
  const decision = await result;
3034
3157
  if (decision === "stop") return "stop";
3035
- const ext = decision.extend;
3036
- if (ext.maxIterations !== void 0) {
3037
- this.limits.maxIterations = ext.maxIterations;
3038
- }
3039
- if (ext.maxToolCalls !== void 0) {
3040
- this.limits.maxToolCalls = ext.maxToolCalls;
3041
- }
3042
- if (ext.maxTokens !== void 0) {
3043
- this.limits.maxTokens = ext.maxTokens;
3044
- }
3045
- if (ext.maxCostUsd !== void 0) {
3046
- this.limits.maxCostUsd = ext.maxCostUsd;
3047
- }
3048
- if (ext.timeoutMs !== void 0) {
3049
- this.limits.timeoutMs = ext.timeoutMs;
3050
- }
3051
- if (ext.idleTimeoutMs !== void 0) {
3052
- this.limits.idleTimeoutMs = ext.idleTimeoutMs;
3053
- }
3158
+ this.patchLimits(decision.extend);
3054
3159
  return decision;
3055
3160
  } finally {
3056
- this._pendingNegotiations.delete(kind);
3161
+ this._pendingNegotiations.delete(entry.kind);
3057
3162
  }
3058
3163
  }
3059
3164
  recordIteration() {
@@ -3096,7 +3201,8 @@ var SubagentBudget = class _SubagentBudget {
3096
3201
  const { timeoutMs, idleTimeoutMs } = this.limits;
3097
3202
  if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
3098
3203
  const elapsed = Date.now() - this.startTime;
3099
- const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
3204
+ const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
3205
+ const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
3100
3206
  const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
3101
3207
  if (!wallTripped && !idleTripped) return;
3102
3208
  void this.checkLimits(elapsed);
@@ -3416,7 +3522,7 @@ function classifySubagentError(err, hints = {}) {
3416
3522
  const baseMessage2 = err.describe();
3417
3523
  return providerErrorToSubagentError(err, baseMessage2, cause);
3418
3524
  }
3419
- const baseMessage = err instanceof Error ? err.message : String(err);
3525
+ const baseMessage = toErrorMessage(err);
3420
3526
  if (err instanceof BudgetExceededError) {
3421
3527
  const map = {
3422
3528
  iterations: "budget_iterations",
@@ -6316,6 +6422,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6316
6422
  terminating = /* @__PURE__ */ new Set();
6317
6423
  constructor(config, options = {}) {
6318
6424
  super();
6425
+ this.setMaxListeners(0);
6319
6426
  this.coordinatorId = config.coordinatorId;
6320
6427
  this.config = config;
6321
6428
  this.runner = options.runner;
@@ -6710,7 +6817,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6710
6817
  let result;
6711
6818
  budget.start();
6712
6819
  try {
6713
- const outcome = await this.executeWithTimeout(this.runner, task, runCtx, budget);
6820
+ const outcome = await this.executeWithTimeout(
6821
+ this.runner,
6822
+ task,
6823
+ runCtx,
6824
+ budget,
6825
+ subagent.config.preemptFraction
6826
+ );
6714
6827
  result = {
6715
6828
  subagentId,
6716
6829
  taskId: task.id,
@@ -6737,7 +6850,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6737
6850
  }
6738
6851
  this.recordCompletion(result);
6739
6852
  }
6740
- async executeWithTimeout(runner, task, ctx, budget) {
6853
+ async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
6741
6854
  const initialTimeoutMs = budget.limits.timeoutMs;
6742
6855
  const idleLimitMs = budget.limits.idleTimeoutMs;
6743
6856
  if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
@@ -6745,8 +6858,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6745
6858
  }
6746
6859
  const start = Date.now();
6747
6860
  let timer = null;
6748
- let preemptedForLimit = null;
6861
+ let PreemptState;
6862
+ ((PreemptState2) => {
6863
+ PreemptState2["ACTIVE"] = "active";
6864
+ PreemptState2["LOCKED"] = "locked";
6865
+ })(PreemptState || (PreemptState = {}));
6866
+ let preemptedCeiling = null;
6867
+ let preemptState = "active" /* ACTIVE */;
6868
+ let lastGrantActivityTs = -1;
6749
6869
  const timeoutPromise = new Promise((_, reject) => {
6870
+ const terminate = (kind, limit, used) => {
6871
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
6872
+ reject(
6873
+ budget._events?.hasListenerFor("budget.threshold_reached") ? new Error(`subagent stopped: budget ${kind} (limit=${limit}, used=${used})`) : new BudgetExceededError(kind, limit, used)
6874
+ );
6875
+ };
6750
6876
  const armFor = (ms) => {
6751
6877
  if (timer) clearTimeout(timer);
6752
6878
  timer = setTimeout(onTick, Math.max(0, ms));
@@ -6755,7 +6881,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6755
6881
  const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
6756
6882
  const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
6757
6883
  const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
6758
- const preemptRemaining = initialTimeoutMs === void 0 || preemptedForLimit === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * TIMEOUT_PREEMPT_FRACTION - (Date.now() - start);
6884
+ const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
6759
6885
  armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
6760
6886
  };
6761
6887
  const negotiateTimeout = async (used, limit) => {
@@ -6765,16 +6891,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6765
6891
  kind: "timeout",
6766
6892
  used,
6767
6893
  limit,
6768
- requestDecision: () => new Promise((resolveDecision) => {
6769
- budget._events?.emit("budget.threshold_reached", {
6770
- kind: "timeout",
6771
- used,
6772
- limit,
6773
- timeoutMs: 6e4,
6774
- extend: (extra) => resolveDecision({ extend: extra }),
6775
- deny: () => resolveDecision("stop")
6894
+ requestDecision: () => {
6895
+ if (!budget._events?.hasListenerFor("budget.threshold_reached")) {
6896
+ return Promise.resolve("stop");
6897
+ }
6898
+ return new Promise((resolveDecision) => {
6899
+ let settled = false;
6900
+ const resolve = (d) => {
6901
+ if (settled) return;
6902
+ settled = true;
6903
+ resolveDecision(d);
6904
+ };
6905
+ const fallback = setTimeout(() => resolve("stop"), DECISION_TIMEOUT_MS);
6906
+ budget._events?.emit("budget.threshold_reached", {
6907
+ kind: "timeout",
6908
+ used,
6909
+ limit,
6910
+ // Informational: the budget's own decision deadline. Listeners may use
6911
+ // this to display a countdown. The coordinator does NOT enforce it —
6912
+ // it is the budget's own `setTimeout(fallback)` that races against
6913
+ // the listener's `extend()`/`deny()` call to guarantee progress.
6914
+ timeoutMs: DECISION_TIMEOUT_MS,
6915
+ // deny() wins over a same-dispatch extend(): defer the grant a
6916
+ // microtask so a synchronous deny in the same emit pre-empts it
6917
+ // (a listener that both grants and denies, or two listeners
6918
+ // disagreeing, resolves as a stop). Async grants still resolve.
6919
+ extend: (extra) => {
6920
+ clearTimeout(fallback);
6921
+ queueMicrotask(() => resolve({ extend: extra }));
6922
+ },
6923
+ deny: () => {
6924
+ clearTimeout(fallback);
6925
+ resolve("stop");
6926
+ }
6927
+ });
6776
6928
  });
6777
- })
6929
+ }
6778
6930
  });
6779
6931
  return typeof result === "string" ? result : await result;
6780
6932
  };
@@ -6785,21 +6937,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6785
6937
  const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
6786
6938
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
6787
6939
  if (idleExceeded && !wallExceeded) {
6940
+ budget._events?.emit("budget.threshold_reached", {
6941
+ kind: "idle_timeout",
6942
+ used: budget.idleMs(),
6943
+ limit: idleLimit ?? 0,
6944
+ timeoutMs: DECISION_TIMEOUT_MS,
6945
+ extend: () => {
6946
+ },
6947
+ deny: () => {
6948
+ }
6949
+ });
6788
6950
  this.subagents.get(ctx.subagentId)?.abortController.abort();
6789
- reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
6951
+ reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
6790
6952
  return;
6791
6953
  }
6792
- if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptedForLimit !== wallLimit && elapsed >= wallLimit * TIMEOUT_PREEMPT_FRACTION) {
6954
+ if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
6955
+ const activityTs = Date.now() - budget.idleMs();
6956
+ if (activityTs <= lastGrantActivityTs) {
6957
+ preemptState = "locked" /* LOCKED */;
6958
+ preemptedCeiling = wallLimit;
6959
+ scheduleNext();
6960
+ return;
6961
+ }
6962
+ budget.setWatchdogNegotiation(wallLimit);
6793
6963
  try {
6794
6964
  const decision = await negotiateTimeout(elapsed, wallLimit);
6795
6965
  if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
6796
- budget.limits.timeoutMs = decision.extend.timeoutMs;
6797
- preemptedForLimit = null;
6966
+ budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
6967
+ lastGrantActivityTs = Date.now() - budget.idleMs();
6968
+ preemptState = "active" /* ACTIVE */;
6969
+ preemptedCeiling = null;
6798
6970
  } else {
6799
- preemptedForLimit = wallLimit;
6971
+ preemptState = "locked" /* LOCKED */;
6972
+ preemptedCeiling = wallLimit;
6800
6973
  }
6801
6974
  } catch {
6802
- preemptedForLimit = wallLimit;
6975
+ preemptState = "locked" /* LOCKED */;
6976
+ preemptedCeiling = wallLimit;
6977
+ } finally {
6978
+ budget.clearWatchdogNegotiation();
6803
6979
  }
6804
6980
  scheduleNext();
6805
6981
  return;
@@ -6814,26 +6990,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6814
6990
  reject(new BudgetExceededError("timeout", limit, elapsed));
6815
6991
  return;
6816
6992
  }
6993
+ budget.setWatchdogNegotiation(limit);
6817
6994
  try {
6818
6995
  const decision = await negotiateTimeout(elapsed, limit);
6819
- if (decision === "continue" || decision === "throw" || decision === "stop") {
6820
- preemptedForLimit = null;
6996
+ if (decision === "throw") {
6997
+ terminate("timeout", limit, elapsed);
6998
+ return;
6999
+ }
7000
+ if (decision === "continue") {
7001
+ preemptState = "locked" /* LOCKED */;
7002
+ preemptedCeiling = wallLimit;
6821
7003
  armFor(Math.max(1e3, limit));
6822
7004
  return;
6823
7005
  }
7006
+ if (decision === "stop") {
7007
+ terminate("timeout", limit, elapsed);
7008
+ return;
7009
+ }
6824
7010
  if (decision.extend.timeoutMs !== void 0) {
6825
- budget.limits.timeoutMs = decision.extend.timeoutMs;
6826
- preemptedForLimit = null;
7011
+ budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
7012
+ lastGrantActivityTs = Date.now() - budget.idleMs();
7013
+ preemptState = "active" /* ACTIVE */;
7014
+ preemptedCeiling = null;
6827
7015
  scheduleNext();
6828
7016
  return;
6829
7017
  }
6830
- this.subagents.get(ctx.subagentId)?.abortController.abort();
6831
- reject(new BudgetExceededError("timeout", limit, elapsed));
7018
+ terminate("timeout", limit, elapsed);
7019
+ return;
6832
7020
  } catch (err) {
6833
7021
  this.subagents.get(ctx.subagentId)?.abortController.abort();
6834
7022
  reject(
6835
7023
  err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
6836
7024
  );
7025
+ return;
7026
+ } finally {
7027
+ budget.clearWatchdogNegotiation();
6837
7028
  }
6838
7029
  };
6839
7030
  scheduleNext();
@@ -7117,7 +7308,7 @@ var SddParallelRun = class {
7117
7308
  const failCount = results.length - successCount;
7118
7309
  for (let i = 0; i < results.length; i++) {
7119
7310
  const result = expectDefined(results[i]);
7120
- const taskId = expectDefined(taskIds[i]);
7311
+ const taskId = expectDefined(tasks[i]).id;
7121
7312
  if (result.status === "success") {
7122
7313
  this.opts.tracker.updateNodeStatus(taskId, "completed");
7123
7314
  this.retryMap.delete(taskId);