@wrongstack/core 0.264.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 (87) hide show
  1. package/dist/{agent-bridge-D8sa1vtv.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-c9DLkaas.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +113 -11
  3. package/dist/{brain-O1IdKPaK.d.ts → brain-BXd_61kQ.d.ts} +31 -2
  4. package/dist/{compactor-BBy0rCtB.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
  5. package/dist/{config-Dz2F3H2K.d.ts → config-BMCj_XDs.d.ts} +80 -12
  6. package/dist/{context-BGSpZNSE.d.ts → context-MRk5PhNv.d.ts} +26 -12
  7. package/dist/coordination/index.d.ts +77 -21
  8. package/dist/coordination/index.js +557 -159
  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 +609 -195
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/execution/index.d.ts +16 -16
  15. package/dist/execution/index.js +394 -155
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/execution/prompt-enhancer.d.ts +2 -2
  18. package/dist/execution/prompt-enhancer.js +1 -1
  19. package/dist/execution/prompt-enhancer.js.map +1 -1
  20. package/dist/extension/index.d.ts +6 -6
  21. package/dist/{goal-preamble-DzjFuN3p.d.ts → goal-preamble-DvHDSKSe.d.ts} +14 -10
  22. package/dist/{goal-store-CxWmCGbH.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
  23. package/dist/{index-CYIQrXVF.d.ts → index-B-ch8K9C.d.ts} +8 -8
  24. package/dist/{index-CbLSI66_.d.ts → index-CEDeNodM.d.ts} +5 -5
  25. package/dist/index.d.ts +183 -52
  26. package/dist/index.js +1779 -673
  27. package/dist/index.js.map +1 -1
  28. package/dist/infrastructure/index.d.ts +6 -6
  29. package/dist/infrastructure/index.js +12 -8
  30. package/dist/infrastructure/index.js.map +1 -1
  31. package/dist/kernel/index.d.ts +9 -9
  32. package/dist/kernel/index.js +1 -1
  33. package/dist/kernel/index.js.map +1 -1
  34. package/dist/{llm-selector-DzxuZnNz.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
  35. package/dist/{mcp-servers-DC4QRPUI.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
  36. package/dist/models/index.d.ts +5 -5
  37. package/dist/models/index.js +74 -30
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-B_siPxqN.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
  40. package/dist/{multi-agent-coordinator-CK5Jdj9K.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +1 -1
  41. package/dist/{null-fleet-bus-DgvD4SCO.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
  42. package/dist/observability/index.d.ts +2 -2
  43. package/dist/{parallel-eternal-engine-bK0JQBR_.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
  44. package/dist/{path-resolver-BPEDlN38.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
  45. package/dist/{permission-4yvGmMRB.d.ts → permission-B9SB45lp.d.ts} +1 -1
  46. package/dist/{permission-policy-C6XpsBOy.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
  47. package/dist/{pipeline-CXCeMz8J.d.ts → pipeline-DPDxH_7m.d.ts} +3 -3
  48. package/dist/{plan-templates-BvzRBkJc.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
  49. package/dist/{provider-runner-C5aQpDWE.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
  50. package/dist/{retry-policy-CFhdtRzz.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
  51. package/dist/sdd/index.d.ts +8 -8
  52. package/dist/sdd/index.js +274 -93
  53. package/dist/sdd/index.js.map +1 -1
  54. package/dist/{secret-vault-CxiVLbt1.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
  55. package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
  56. package/dist/security/index.d.ts +5 -5
  57. package/dist/security/index.js +204 -23
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-gIuhRTkN.d.ts → selector-CzHh_igB.d.ts} +1 -1
  60. package/dist/{session-event-bridge-DkvvrpDt.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +1 -1
  61. package/dist/{session-reader-KdfVwkKP.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
  62. package/dist/storage/index.d.ts +112 -15
  63. package/dist/storage/index.js +419 -81
  64. package/dist/storage/index.js.map +1 -1
  65. package/dist/tools/index.d.ts +2 -2
  66. package/dist/types/index.d.ts +21 -21
  67. package/dist/types/index.js +261 -53
  68. package/dist/types/index.js.map +1 -1
  69. package/dist/utils/index.d.ts +3 -3
  70. package/dist/utils/index.js +3 -5
  71. package/dist/utils/index.js.map +1 -1
  72. package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
  73. package/package.json +1 -1
  74. package/skills/api-design/SKILL.md +1 -1
  75. package/skills/audit-log/SKILL.md +6 -6
  76. package/skills/bug-hunter/SKILL.md +5 -5
  77. package/skills/chimera/SKILL.md +4 -4
  78. package/skills/docker-deploy/SKILL.md +1 -1
  79. package/skills/git-flow/SKILL.md +3 -3
  80. package/skills/multi-agent/SKILL.md +3 -3
  81. package/skills/node-modern/SKILL.md +1 -0
  82. package/skills/observability/SKILL.md +2 -2
  83. package/skills/output-standards/SKILL.md +51 -28
  84. package/skills/refactor-planner/SKILL.md +3 -3
  85. package/skills/security-scanner/SKILL.md +4 -3
  86. package/skills/tech-stack/SKILL.md +1 -2
  87. package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/sdd/index.js CHANGED
@@ -2792,6 +2792,7 @@ var SddTaskDecomposer = class {
2792
2792
 
2793
2793
  // src/coordination/subagent-budget.ts
2794
2794
  var TIMEOUT_PREEMPT_FRACTION = 0.85;
2795
+ var DECISION_TIMEOUT_MS = 6e4;
2795
2796
  var BudgetExceededError = class extends Error {
2796
2797
  kind;
2797
2798
  limit;
@@ -2821,6 +2822,31 @@ var BudgetThresholdSignal = class extends Error {
2821
2822
  };
2822
2823
  var SubagentBudget = class _SubagentBudget {
2823
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
+ }
2824
2850
  iterations = 0;
2825
2851
  toolCalls = 0;
2826
2852
  tokenInput = 0;
@@ -2841,12 +2867,44 @@ var SubagentBudget = class _SubagentBudget {
2841
2867
  * or hung listener (Director not built / event filter detached mid-run)
2842
2868
  * leaves the budget over-limit and never enforces anything.
2843
2869
  */
2844
- static DECISION_TIMEOUT_MS = 6e4;
2870
+ static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
2845
2871
  /**
2846
2872
  * Injected by the runner when wiring the budget to its EventBus.
2847
2873
  * Used to emit `budget.threshold_reached` events in `'auto'` mode.
2848
2874
  */
2849
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
+ }
2850
2908
  /**
2851
2909
  * Negotiation mode — controls whether a threshold hit tries to emit
2852
2910
  * `budget.threshold_reached` and wait for a coordinator decision, or
@@ -2947,7 +3005,8 @@ var SubagentBudget = class _SubagentBudget {
2947
3005
  if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
2948
3006
  exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
2949
3007
  }
2950
- 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) {
2951
3010
  exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
2952
3011
  }
2953
3012
  }
@@ -2961,19 +3020,99 @@ var SubagentBudget = class _SubagentBudget {
2961
3020
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2962
3021
  }
2963
3022
  const bus = this._events;
2964
- if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
3023
+ if (!bus) {
2965
3024
  const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
2966
3025
  throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
2967
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;
2968
3038
  for (const entry of exceeded) {
2969
3039
  if (this._pendingNegotiations.has(entry.kind)) continue;
2970
- const decision2 = this._negotiateExtension(entry.kind, exceeded);
2971
- 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);
2972
3045
  }
2973
- const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
2974
- const decision = this._pendingNegotiations.get(first.kind);
2975
- if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
2976
- 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
+ });
2977
3116
  }
2978
3117
  /**
2979
3118
  * Per-kind in-flight negotiation Promises. Each budget kind can have its
@@ -2993,77 +3132,33 @@ var SubagentBudget = class _SubagentBudget {
2993
3132
  * `{ extend: {} }` — keep going without patching; next overrun fires
2994
3133
  * a fresh signal.
2995
3134
  */
2996
- async _negotiateExtension(kind, exceeded) {
3135
+ async _negotiateExtension(entry) {
2997
3136
  if (!this._onThreshold) {
2998
3137
  return "stop";
2999
3138
  }
3000
3139
  try {
3001
- const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
3002
3140
  const result = this._onThreshold({
3003
- kind: first.kind,
3004
- used: first.used,
3005
- limit: first.limit,
3006
- requestDecision: () => {
3007
- const bus = this._events;
3008
- if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
3009
- return Promise.resolve("stop");
3010
- }
3011
- return new Promise((resolve) => {
3012
- let resolved = false;
3013
- const respond = (d) => {
3014
- if (resolved) return;
3015
- resolved = true;
3016
- resolve(d);
3017
- };
3018
- const fallback = setTimeout(
3019
- () => respond("stop"),
3020
- _SubagentBudget.DECISION_TIMEOUT_MS
3021
- );
3022
- for (const { kind: kind2, used, limit } of exceeded) {
3023
- bus.emit("budget.threshold_reached", {
3024
- kind: kind2,
3025
- used,
3026
- limit,
3027
- timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
3028
- extend: (extra) => {
3029
- clearTimeout(fallback);
3030
- respond({ extend: extra });
3031
- },
3032
- deny: () => {
3033
- clearTimeout(fallback);
3034
- respond("stop");
3035
- }
3036
- });
3037
- }
3038
- });
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: () => {
3039
3152
  }
3040
3153
  });
3041
3154
  if (result === "throw") return "stop";
3042
3155
  if (result === "continue") return { extend: {} };
3043
3156
  const decision = await result;
3044
3157
  if (decision === "stop") return "stop";
3045
- const ext = decision.extend;
3046
- if (ext.maxIterations !== void 0) {
3047
- this.limits.maxIterations = ext.maxIterations;
3048
- }
3049
- if (ext.maxToolCalls !== void 0) {
3050
- this.limits.maxToolCalls = ext.maxToolCalls;
3051
- }
3052
- if (ext.maxTokens !== void 0) {
3053
- this.limits.maxTokens = ext.maxTokens;
3054
- }
3055
- if (ext.maxCostUsd !== void 0) {
3056
- this.limits.maxCostUsd = ext.maxCostUsd;
3057
- }
3058
- if (ext.timeoutMs !== void 0) {
3059
- this.limits.timeoutMs = ext.timeoutMs;
3060
- }
3061
- if (ext.idleTimeoutMs !== void 0) {
3062
- this.limits.idleTimeoutMs = ext.idleTimeoutMs;
3063
- }
3158
+ this.patchLimits(decision.extend);
3064
3159
  return decision;
3065
3160
  } finally {
3066
- this._pendingNegotiations.delete(kind);
3161
+ this._pendingNegotiations.delete(entry.kind);
3067
3162
  }
3068
3163
  }
3069
3164
  recordIteration() {
@@ -3106,7 +3201,8 @@ var SubagentBudget = class _SubagentBudget {
3106
3201
  const { timeoutMs, idleTimeoutMs } = this.limits;
3107
3202
  if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
3108
3203
  const elapsed = Date.now() - this.startTime;
3109
- 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;
3110
3206
  const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
3111
3207
  if (!wallTripped && !idleTripped) return;
3112
3208
  void this.checkLimits(elapsed);
@@ -6326,6 +6422,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6326
6422
  terminating = /* @__PURE__ */ new Set();
6327
6423
  constructor(config, options = {}) {
6328
6424
  super();
6425
+ this.setMaxListeners(0);
6329
6426
  this.coordinatorId = config.coordinatorId;
6330
6427
  this.config = config;
6331
6428
  this.runner = options.runner;
@@ -6720,7 +6817,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6720
6817
  let result;
6721
6818
  budget.start();
6722
6819
  try {
6723
- 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
+ );
6724
6827
  result = {
6725
6828
  subagentId,
6726
6829
  taskId: task.id,
@@ -6747,7 +6850,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6747
6850
  }
6748
6851
  this.recordCompletion(result);
6749
6852
  }
6750
- async executeWithTimeout(runner, task, ctx, budget) {
6853
+ async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
6751
6854
  const initialTimeoutMs = budget.limits.timeoutMs;
6752
6855
  const idleLimitMs = budget.limits.idleTimeoutMs;
6753
6856
  if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
@@ -6755,8 +6858,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6755
6858
  }
6756
6859
  const start = Date.now();
6757
6860
  let timer = null;
6758
- 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;
6759
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
+ };
6760
6876
  const armFor = (ms) => {
6761
6877
  if (timer) clearTimeout(timer);
6762
6878
  timer = setTimeout(onTick, Math.max(0, ms));
@@ -6765,7 +6881,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6765
6881
  const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
6766
6882
  const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
6767
6883
  const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
6768
- 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);
6769
6885
  armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
6770
6886
  };
6771
6887
  const negotiateTimeout = async (used, limit) => {
@@ -6775,16 +6891,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6775
6891
  kind: "timeout",
6776
6892
  used,
6777
6893
  limit,
6778
- requestDecision: () => new Promise((resolveDecision) => {
6779
- budget._events?.emit("budget.threshold_reached", {
6780
- kind: "timeout",
6781
- used,
6782
- limit,
6783
- timeoutMs: 6e4,
6784
- extend: (extra) => resolveDecision({ extend: extra }),
6785
- 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
+ });
6786
6928
  });
6787
- })
6929
+ }
6788
6930
  });
6789
6931
  return typeof result === "string" ? result : await result;
6790
6932
  };
@@ -6795,21 +6937,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6795
6937
  const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
6796
6938
  const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
6797
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
+ });
6798
6950
  this.subagents.get(ctx.subagentId)?.abortController.abort();
6799
- reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
6951
+ reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
6800
6952
  return;
6801
6953
  }
6802
- 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);
6803
6963
  try {
6804
6964
  const decision = await negotiateTimeout(elapsed, wallLimit);
6805
6965
  if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
6806
- budget.limits.timeoutMs = decision.extend.timeoutMs;
6807
- preemptedForLimit = null;
6966
+ budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
6967
+ lastGrantActivityTs = Date.now() - budget.idleMs();
6968
+ preemptState = "active" /* ACTIVE */;
6969
+ preemptedCeiling = null;
6808
6970
  } else {
6809
- preemptedForLimit = wallLimit;
6971
+ preemptState = "locked" /* LOCKED */;
6972
+ preemptedCeiling = wallLimit;
6810
6973
  }
6811
6974
  } catch {
6812
- preemptedForLimit = wallLimit;
6975
+ preemptState = "locked" /* LOCKED */;
6976
+ preemptedCeiling = wallLimit;
6977
+ } finally {
6978
+ budget.clearWatchdogNegotiation();
6813
6979
  }
6814
6980
  scheduleNext();
6815
6981
  return;
@@ -6824,26 +6990,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6824
6990
  reject(new BudgetExceededError("timeout", limit, elapsed));
6825
6991
  return;
6826
6992
  }
6993
+ budget.setWatchdogNegotiation(limit);
6827
6994
  try {
6828
6995
  const decision = await negotiateTimeout(elapsed, limit);
6829
- if (decision === "continue" || decision === "throw" || decision === "stop") {
6830
- 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;
6831
7003
  armFor(Math.max(1e3, limit));
6832
7004
  return;
6833
7005
  }
7006
+ if (decision === "stop") {
7007
+ terminate("timeout", limit, elapsed);
7008
+ return;
7009
+ }
6834
7010
  if (decision.extend.timeoutMs !== void 0) {
6835
- budget.limits.timeoutMs = decision.extend.timeoutMs;
6836
- preemptedForLimit = null;
7011
+ budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
7012
+ lastGrantActivityTs = Date.now() - budget.idleMs();
7013
+ preemptState = "active" /* ACTIVE */;
7014
+ preemptedCeiling = null;
6837
7015
  scheduleNext();
6838
7016
  return;
6839
7017
  }
6840
- this.subagents.get(ctx.subagentId)?.abortController.abort();
6841
- reject(new BudgetExceededError("timeout", limit, elapsed));
7018
+ terminate("timeout", limit, elapsed);
7019
+ return;
6842
7020
  } catch (err) {
6843
7021
  this.subagents.get(ctx.subagentId)?.abortController.abort();
6844
7022
  reject(
6845
7023
  err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
6846
7024
  );
7025
+ return;
7026
+ } finally {
7027
+ budget.clearWatchdogNegotiation();
6847
7028
  }
6848
7029
  };
6849
7030
  scheduleNext();