nexus-agents 2.83.0 → 2.84.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 (126) hide show
  1. package/dist/{child-mcp-config-XVPIO5HV.js → child-mcp-config-VSGPMVLL.js} +3 -3
  2. package/dist/{chunk-5T6SYCH2.js → chunk-2PKAWFEU.js} +3 -2
  3. package/dist/chunk-2PKAWFEU.js.map +1 -0
  4. package/dist/{chunk-I2HMWH4R.js → chunk-2WCYG75B.js} +13 -2
  5. package/dist/chunk-2WCYG75B.js.map +1 -0
  6. package/dist/{chunk-PMLVZXAE.js → chunk-3PFESR6I.js} +1864 -1633
  7. package/dist/chunk-3PFESR6I.js.map +1 -0
  8. package/dist/{chunk-54UXPJVM.js → chunk-4IXQ6IXI.js} +2 -2
  9. package/dist/{chunk-FI77TGBY.js → chunk-5LFUZ5IC.js} +2 -2
  10. package/dist/{chunk-633WH2ML.js → chunk-6T3EPABN.js} +1 -1
  11. package/dist/chunk-6T3EPABN.js.map +1 -0
  12. package/dist/{chunk-ZM5JKJHI.js → chunk-BX3MUA26.js} +2 -2
  13. package/dist/{chunk-3ASED5PR.js → chunk-CJHIKBWI.js} +4 -4
  14. package/dist/{chunk-Z64MSLEK.js → chunk-D5BKSM6H.js} +2 -2
  15. package/dist/{chunk-3WUVWZUC.js → chunk-GBGMTN7O.js} +2 -2
  16. package/dist/{chunk-RXVH52UI.js → chunk-HLNMWGW3.js} +27 -4
  17. package/dist/chunk-HLNMWGW3.js.map +1 -0
  18. package/dist/{chunk-3GJAAENS.js → chunk-I3O5WOGW.js} +4 -4
  19. package/dist/{chunk-AFPHR72D.js → chunk-KK2RZVPU.js} +2 -2
  20. package/dist/{chunk-PPBZQKRP.js → chunk-KRRBRT7B.js} +6 -6
  21. package/dist/{chunk-YO27YAEX.js → chunk-KVYQOOML.js} +8 -24
  22. package/dist/chunk-KVYQOOML.js.map +1 -0
  23. package/dist/{chunk-BZUEUD4G.js → chunk-MBD5EJBO.js} +3 -3
  24. package/dist/{chunk-FHGDJKCZ.js → chunk-OE7OL2BE.js} +155 -47
  25. package/dist/chunk-OE7OL2BE.js.map +1 -0
  26. package/dist/{chunk-WJFUTRHX.js → chunk-OROUYVUX.js} +2 -2
  27. package/dist/{chunk-RVRXKNND.js → chunk-QIRZSDJK.js} +2 -2
  28. package/dist/{chunk-L362KRSU.js → chunk-REH5BB67.js} +78 -63
  29. package/dist/chunk-REH5BB67.js.map +1 -0
  30. package/dist/{chunk-NLPMYBIV.js → chunk-RZVEVT3M.js} +7 -7
  31. package/dist/{chunk-WGSRH5EQ.js → chunk-T6B6CKU6.js} +4 -4
  32. package/dist/chunk-T6B6CKU6.js.map +1 -0
  33. package/dist/{chunk-Q6PZMGLU.js → chunk-UJMIGLZI.js} +2 -2
  34. package/dist/{chunk-ZIWEEVMI.js → chunk-V35FWPHD.js} +2 -2
  35. package/dist/{chunk-4BXQ2OZY.js → chunk-V7RC2PIY.js} +20 -38
  36. package/dist/chunk-V7RC2PIY.js.map +1 -0
  37. package/dist/{chunk-AINOJRZX.js → chunk-VQIWH6X4.js} +9 -9
  38. package/dist/{chunk-2W4QJ27D.js → chunk-VXHDZULO.js} +3 -3
  39. package/dist/{chunk-U6LDVJS7.js → chunk-XK523BNC.js} +2 -2
  40. package/dist/chunk-YVG32OQF.js +27 -0
  41. package/dist/chunk-YVG32OQF.js.map +1 -0
  42. package/dist/{chunk-6WDJ3IE4.js → chunk-ZJGCOJC6.js} +4 -4
  43. package/dist/{cli-circuit-breaker-Y26NPPNO.js → cli-circuit-breaker-6EQKYNAA.js} +5 -5
  44. package/dist/cli.js +37 -34
  45. package/dist/cli.js.map +1 -1
  46. package/dist/{composite-router-X2ZYIEHH.js → composite-router-SA4XHXA3.js} +3 -3
  47. package/dist/consensus-vote-QKUHNHS4.js +31 -0
  48. package/dist/context-retriever-XX4TQBIB.js +19 -0
  49. package/dist/{doctor-deep-2E2GBMYR.js → doctor-deep-NB7IDWJE.js} +4 -4
  50. package/dist/expert-bridge-4MHM77VW.js +13 -0
  51. package/dist/{factory-STNVY3Y3.js → factory-FKOPQXRS.js} +6 -5
  52. package/dist/factory-UHH4ZZ64.js +14 -0
  53. package/dist/index.d.ts +114 -149
  54. package/dist/index.js +63 -128
  55. package/dist/index.js.map +1 -1
  56. package/dist/{init-opencode-6LVZ4CAQ.js → init-opencode-AVYAC33Q.js} +6 -6
  57. package/dist/issue-triage-7PYND7PY.js +15 -0
  58. package/dist/{learning-persistence-NX2KHL5T.js → learning-persistence-5ALG6XRC.js} +2 -2
  59. package/dist/{registry-command-P5VIAEOL.js → registry-command-7SFNZRG5.js} +3 -3
  60. package/dist/{repo-security-plan-RHSLO7H6.js → repo-security-plan-MTVL5B73.js} +4 -4
  61. package/dist/{research-helpers-synthesize-PVP6JRZV.js → research-helpers-synthesize-6CJ5P4WL.js} +4 -4
  62. package/dist/{routing-memory-QRIJPRVD.js → routing-memory-Y577PXFP.js} +3 -3
  63. package/dist/{session-memory-WARRGYY7.js → session-memory-6YFGMEMP.js} +4 -4
  64. package/dist/{setup-command-GMP5FI7F.js → setup-command-YBTD6D53.js} +13 -12
  65. package/dist/setup-config-BQYLUJHY.js +10 -0
  66. package/dist/{setup-custom-api-B63X7ISD.js → setup-custom-api-ZB7LOQC5.js} +5 -5
  67. package/dist/{tool-memory-E7JW4YLT.js → tool-memory-GFYQFRQI.js} +7 -6
  68. package/dist/{weather-report-RACZWJQL.js → weather-report-CN6WPJI4.js} +3 -3
  69. package/package.json +1 -1
  70. package/dist/chunk-4BXQ2OZY.js.map +0 -1
  71. package/dist/chunk-5T6SYCH2.js.map +0 -1
  72. package/dist/chunk-633WH2ML.js.map +0 -1
  73. package/dist/chunk-FHGDJKCZ.js.map +0 -1
  74. package/dist/chunk-I2HMWH4R.js.map +0 -1
  75. package/dist/chunk-L362KRSU.js.map +0 -1
  76. package/dist/chunk-PMLVZXAE.js.map +0 -1
  77. package/dist/chunk-RXVH52UI.js.map +0 -1
  78. package/dist/chunk-WGSRH5EQ.js.map +0 -1
  79. package/dist/chunk-YO27YAEX.js.map +0 -1
  80. package/dist/consensus-vote-KZ6UURUI.js +0 -30
  81. package/dist/context-retriever-QY4FNTDZ.js +0 -18
  82. package/dist/expert-bridge-FHPWDFJX.js +0 -13
  83. package/dist/factory-W6KROBFN.js +0 -14
  84. package/dist/issue-triage-Y77JI7WF.js +0 -15
  85. package/dist/setup-config-G3KKZM7O.js +0 -10
  86. /package/dist/{child-mcp-config-XVPIO5HV.js.map → child-mcp-config-VSGPMVLL.js.map} +0 -0
  87. /package/dist/{chunk-54UXPJVM.js.map → chunk-4IXQ6IXI.js.map} +0 -0
  88. /package/dist/{chunk-FI77TGBY.js.map → chunk-5LFUZ5IC.js.map} +0 -0
  89. /package/dist/{chunk-ZM5JKJHI.js.map → chunk-BX3MUA26.js.map} +0 -0
  90. /package/dist/{chunk-3ASED5PR.js.map → chunk-CJHIKBWI.js.map} +0 -0
  91. /package/dist/{chunk-Z64MSLEK.js.map → chunk-D5BKSM6H.js.map} +0 -0
  92. /package/dist/{chunk-3WUVWZUC.js.map → chunk-GBGMTN7O.js.map} +0 -0
  93. /package/dist/{chunk-3GJAAENS.js.map → chunk-I3O5WOGW.js.map} +0 -0
  94. /package/dist/{chunk-AFPHR72D.js.map → chunk-KK2RZVPU.js.map} +0 -0
  95. /package/dist/{chunk-PPBZQKRP.js.map → chunk-KRRBRT7B.js.map} +0 -0
  96. /package/dist/{chunk-BZUEUD4G.js.map → chunk-MBD5EJBO.js.map} +0 -0
  97. /package/dist/{chunk-WJFUTRHX.js.map → chunk-OROUYVUX.js.map} +0 -0
  98. /package/dist/{chunk-RVRXKNND.js.map → chunk-QIRZSDJK.js.map} +0 -0
  99. /package/dist/{chunk-NLPMYBIV.js.map → chunk-RZVEVT3M.js.map} +0 -0
  100. /package/dist/{chunk-Q6PZMGLU.js.map → chunk-UJMIGLZI.js.map} +0 -0
  101. /package/dist/{chunk-ZIWEEVMI.js.map → chunk-V35FWPHD.js.map} +0 -0
  102. /package/dist/{chunk-AINOJRZX.js.map → chunk-VQIWH6X4.js.map} +0 -0
  103. /package/dist/{chunk-2W4QJ27D.js.map → chunk-VXHDZULO.js.map} +0 -0
  104. /package/dist/{chunk-U6LDVJS7.js.map → chunk-XK523BNC.js.map} +0 -0
  105. /package/dist/{chunk-6WDJ3IE4.js.map → chunk-ZJGCOJC6.js.map} +0 -0
  106. /package/dist/{cli-circuit-breaker-Y26NPPNO.js.map → cli-circuit-breaker-6EQKYNAA.js.map} +0 -0
  107. /package/dist/{composite-router-X2ZYIEHH.js.map → composite-router-SA4XHXA3.js.map} +0 -0
  108. /package/dist/{consensus-vote-KZ6UURUI.js.map → consensus-vote-QKUHNHS4.js.map} +0 -0
  109. /package/dist/{context-retriever-QY4FNTDZ.js.map → context-retriever-XX4TQBIB.js.map} +0 -0
  110. /package/dist/{doctor-deep-2E2GBMYR.js.map → doctor-deep-NB7IDWJE.js.map} +0 -0
  111. /package/dist/{expert-bridge-FHPWDFJX.js.map → expert-bridge-4MHM77VW.js.map} +0 -0
  112. /package/dist/{factory-STNVY3Y3.js.map → factory-FKOPQXRS.js.map} +0 -0
  113. /package/dist/{factory-W6KROBFN.js.map → factory-UHH4ZZ64.js.map} +0 -0
  114. /package/dist/{init-opencode-6LVZ4CAQ.js.map → init-opencode-AVYAC33Q.js.map} +0 -0
  115. /package/dist/{issue-triage-Y77JI7WF.js.map → issue-triage-7PYND7PY.js.map} +0 -0
  116. /package/dist/{learning-persistence-NX2KHL5T.js.map → learning-persistence-5ALG6XRC.js.map} +0 -0
  117. /package/dist/{registry-command-P5VIAEOL.js.map → registry-command-7SFNZRG5.js.map} +0 -0
  118. /package/dist/{repo-security-plan-RHSLO7H6.js.map → repo-security-plan-MTVL5B73.js.map} +0 -0
  119. /package/dist/{research-helpers-synthesize-PVP6JRZV.js.map → research-helpers-synthesize-6CJ5P4WL.js.map} +0 -0
  120. /package/dist/{routing-memory-QRIJPRVD.js.map → routing-memory-Y577PXFP.js.map} +0 -0
  121. /package/dist/{session-memory-WARRGYY7.js.map → session-memory-6YFGMEMP.js.map} +0 -0
  122. /package/dist/{setup-command-GMP5FI7F.js.map → setup-command-YBTD6D53.js.map} +0 -0
  123. /package/dist/{setup-config-G3KKZM7O.js.map → setup-config-BQYLUJHY.js.map} +0 -0
  124. /package/dist/{setup-custom-api-B63X7ISD.js.map → setup-custom-api-ZB7LOQC5.js.map} +0 -0
  125. /package/dist/{tool-memory-E7JW4YLT.js.map → tool-memory-GFYQFRQI.js.map} +0 -0
  126. /package/dist/{weather-report-RACZWJQL.js.map → weather-report-CN6WPJI4.js.map} +0 -0
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  GitHubProvider,
3
3
  ScmError
4
- } from "./chunk-L362KRSU.js";
4
+ } from "./chunk-REH5BB67.js";
5
5
  import {
6
6
  CACHE_TIMEOUTS,
7
7
  createLogger,
8
8
  err,
9
9
  getTimeProvider,
10
10
  ok
11
- } from "./chunk-RXVH52UI.js";
11
+ } from "./chunk-HLNMWGW3.js";
12
12
 
13
13
  // src/security/trust-types.ts
14
14
  import { z } from "zod";
@@ -1656,4 +1656,4 @@ export {
1656
1656
  IssueTriage,
1657
1657
  createIssueTriage
1658
1658
  };
1659
- //# sourceMappingURL=chunk-BZUEUD4G.js.map
1659
+ //# sourceMappingURL=chunk-MBD5EJBO.js.map
@@ -1,7 +1,10 @@
1
1
  import {
2
2
  CliCircuitBreaker,
3
3
  DEFAULT_CIRCUIT_BREAKER_CONFIG
4
- } from "./chunk-5T6SYCH2.js";
4
+ } from "./chunk-2PKAWFEU.js";
5
+ import {
6
+ generateHyphenId
7
+ } from "./chunk-YVG32OQF.js";
5
8
  import {
6
9
  CLI_SUBPROCESS_TIMEOUTS,
7
10
  CLI_TIMEOUTS,
@@ -24,7 +27,7 @@ import {
24
27
  getTimeProvider,
25
28
  isRateLimitText,
26
29
  ok
27
- } from "./chunk-RXVH52UI.js";
30
+ } from "./chunk-HLNMWGW3.js";
28
31
 
29
32
  // src/cli-adapters/subprocess-adapter.ts
30
33
  import { spawn } from "child_process";
@@ -150,10 +153,30 @@ var CapacityTracker = class {
150
153
  usageHistory;
151
154
  requestCount;
152
155
  windowStart;
156
+ /**
157
+ * Timestamps of every recorded request (#3026 finding 4).
158
+ *
159
+ * Pre-fix, `requestCount` was a plain counter reset only by the
160
+ * tumbling-window branch of `pruneOldEntries`. Under continuous
161
+ * traffic across a window boundary, that branch drops requests that
162
+ * are still inside the *sliding* window — e.g. with windowMs=60s, a
163
+ * request at t=59s followed by one at t=61s would tumbling-reset
164
+ * the counter to 1 even though the t=59s request is still inside
165
+ * the [1s, 61s] window. The downstream `remainingRequests === 0`
166
+ * exhaustion check fired prematurely (or too late) depending on
167
+ * burst patterns.
168
+ *
169
+ * Counting via a per-request timestamp array that's pruned the same
170
+ * way as `usageHistory` keeps the two views consistent. Every
171
+ * `recordUsage` pushes here; `requestCount` is derived from
172
+ * `.length` after pruning.
173
+ */
174
+ requestTimestamps;
153
175
  constructor(config) {
154
176
  this.config = config;
155
177
  this.usageHistory = [];
156
178
  this.requestCount = 0;
179
+ this.requestTimestamps = [];
157
180
  this.windowStart = getTimeProvider().now();
158
181
  }
159
182
  /**
@@ -162,7 +185,8 @@ var CapacityTracker = class {
162
185
  recordUsage(usage) {
163
186
  const now = getTimeProvider().now();
164
187
  this.pruneOldEntries(now);
165
- this.requestCount++;
188
+ this.requestTimestamps.push(now);
189
+ this.requestCount = this.requestTimestamps.length;
166
190
  if (usage !== void 0) {
167
191
  const tokens = usage.totalTokens ?? usage.inputTokens + usage.outputTokens;
168
192
  this.usageHistory.push({
@@ -206,6 +230,7 @@ var CapacityTracker = class {
206
230
  */
207
231
  reset() {
208
232
  this.usageHistory.length = 0;
233
+ this.requestTimestamps.length = 0;
209
234
  this.requestCount = 0;
210
235
  this.windowStart = getTimeProvider().now();
211
236
  }
@@ -226,16 +251,19 @@ var CapacityTracker = class {
226
251
  */
227
252
  pruneOldEntries(now) {
228
253
  const cutoff = now - this.config.windowMs;
229
- if (this.windowStart < cutoff) {
230
- this.usageHistory.length = 0;
231
- this.requestCount = 0;
232
- this.windowStart = now;
233
- return;
234
- }
235
- let firstEntry = this.usageHistory[0];
236
- while (firstEntry !== void 0 && firstEntry.timestamp < cutoff) {
254
+ while (this.usageHistory[0] !== void 0 && this.usageHistory[0].timestamp < cutoff) {
237
255
  this.usageHistory.shift();
238
- firstEntry = this.usageHistory[0];
256
+ }
257
+ while (this.requestTimestamps[0] !== void 0 && this.requestTimestamps[0] < cutoff) {
258
+ this.requestTimestamps.shift();
259
+ }
260
+ this.requestCount = this.requestTimestamps.length;
261
+ const earliestRequest = this.requestTimestamps[0];
262
+ const earliestUsage = this.usageHistory[0]?.timestamp;
263
+ if (earliestRequest === void 0 && earliestUsage === void 0) {
264
+ this.windowStart = now;
265
+ } else {
266
+ this.windowStart = Math.min(earliestRequest ?? Infinity, earliestUsage ?? Infinity);
239
267
  }
240
268
  }
241
269
  };
@@ -842,6 +870,7 @@ var TRANSIENT_RETRY_DELAYS_MS = [500, 1e3];
842
870
  var MAX_TRANSIENT_RETRIES = TRANSIENT_RETRY_DELAYS_MS.length;
843
871
  var MAX_PARSE_RETRIES = 1;
844
872
  var TIMEOUT_RETRY_MULTIPLIER = 1.5;
873
+ var SIGKILL_GRACE_MS = 5e3;
845
874
  function isTransientError(code) {
846
875
  return TRANSIENT_ERROR_CODES.has(code);
847
876
  }
@@ -885,6 +914,42 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
885
914
  shouldOuterRetry(opts) {
886
915
  return opts.allowRetry && !this.transientRetry.enabled;
887
916
  }
917
+ /**
918
+ * Invokes the optional cleanup hook supplied by getCommand(). Sync
919
+ * throws are swallowed (warn-only) and async rejections are caught
920
+ * so cleanup failures never bubble up and mask the real subprocess
921
+ * result.
922
+ */
923
+ runSubprocessCleanup(cleanup) {
924
+ if (cleanup === void 0) return;
925
+ try {
926
+ const r = cleanup();
927
+ if (r instanceof Promise) {
928
+ r.catch((e) => {
929
+ this.logger.warn("Subprocess cleanup failed", { error: String(e) });
930
+ });
931
+ }
932
+ } catch (e) {
933
+ this.logger.warn("Subprocess cleanup threw", { error: String(e) });
934
+ }
935
+ }
936
+ /**
937
+ * #3026 finding 2: SIGTERM the child when the caller's AbortSignal
938
+ * aborts mid-execution. Listener auto-detaches on `'close'` so we
939
+ * don't leak across child lifetimes.
940
+ */
941
+ attachAbortSignal(child, signal, resolve) {
942
+ const onAbort = () => {
943
+ if (child.exitCode === null && child.signalCode === null) {
944
+ child.kill("SIGTERM");
945
+ }
946
+ resolve(err(this.createError("TIMEOUT", "Aborted by caller signal")));
947
+ };
948
+ signal.addEventListener("abort", onAbort, { once: true });
949
+ child.once("close", () => {
950
+ signal.removeEventListener("abort", onAbort);
951
+ });
952
+ }
888
953
  /**
889
954
  * Executes a task via subprocess, with optional transient-error retry.
890
955
  * When `transientRetry.enabled` is true, transient errors (timeout,
@@ -892,15 +957,16 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
892
957
  * (500ms, 1000ms). Parse errors get max 1 retry (#1533); others get 2.
893
958
  */
894
959
  async executeTask(task, options) {
895
- const result = await this.spawnSubprocess(task, options);
960
+ const requestId = generateHyphenId("cli-req", 8);
961
+ const result = await this.spawnSubprocess(task, options, requestId);
896
962
  if (result.ok || !this.transientRetry.enabled) return result;
897
963
  if (!isTransientError(result.error.code)) return result;
898
- return this.retryTransient(task, options, result, 0);
964
+ return this.retryTransient(task, options, result, 0, requestId);
899
965
  }
900
966
  /**
901
967
  * Retries a transient error with bounded exponential backoff.
902
968
  */
903
- async retryTransient(task, options, lastResult, attempt) {
969
+ async retryTransient(task, options, lastResult, attempt, requestId) {
904
970
  const isParseError = !lastResult.ok && lastResult.error.code === "PARSE_ERROR";
905
971
  const maxRetries = isParseError ? MAX_PARSE_RETRIES : MAX_TRANSIENT_RETRIES;
906
972
  if (attempt >= maxRetries) return lastResult;
@@ -908,40 +974,38 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
908
974
  const isTimeout = !lastResult.ok && lastResult.error.code === "TIMEOUT";
909
975
  subprocessLogger.debug("Retrying transient error", {
910
976
  cli: this.name,
977
+ requestId,
911
978
  attempt: attempt + 1,
912
979
  delayMs,
913
980
  errorCode: !lastResult.ok ? lastResult.error.code : void 0
914
981
  });
915
982
  await this.delay(delayMs);
916
983
  const retryOptions = isTimeout ? { ...options, timeoutMs: Math.round(options.timeoutMs * TIMEOUT_RETRY_MULTIPLIER) } : options;
917
- const result = await this.spawnSubprocess(task, retryOptions);
984
+ const result = await this.spawnSubprocess(task, retryOptions, requestId);
918
985
  if (result.ok) return result;
919
986
  if (!isTransientError(result.error.code)) return result;
920
- return this.retryTransient(task, retryOptions, result, attempt + 1);
987
+ return this.retryTransient(task, retryOptions, result, attempt + 1, requestId);
921
988
  }
922
989
  /**
923
990
  * Spawns a single subprocess execution (no retry).
924
- */
925
- spawnSubprocess(task, options) {
991
+ *
992
+ * `requestId` (#2963 site 3) correlates the timing-breakdown log emitted
993
+ * on subprocess close back to the parent `executeTask` invocation —
994
+ * essential when multiple subprocesses for the same CLI run concurrently
995
+ * (pipelines, votes) and the JSDoc's stated goal ("group by cli + provider
996
+ * + model and surface tail-latency outliers") requires a way to
997
+ * disambiguate which timing row belongs to which call.
998
+ */
999
+ spawnSubprocess(task, options, requestId) {
926
1000
  const cmdConfig = this.getCommand(task);
927
1001
  const startTime = getTimeProvider().now();
928
- const cleanup = cmdConfig.cleanup;
929
- const runCleanup = () => {
930
- if (cleanup === void 0) return;
931
- try {
932
- const r = cleanup();
933
- if (r instanceof Promise) {
934
- r.catch((e) => {
935
- this.logger.warn("Subprocess cleanup failed", { error: String(e) });
936
- });
937
- }
938
- } catch (e) {
939
- this.logger.warn("Subprocess cleanup threw", { error: String(e) });
940
- }
941
- };
1002
+ const signal = options.signal;
1003
+ if (signal?.aborted === true) {
1004
+ return Promise.resolve(err(this.createError("TIMEOUT", "Aborted before spawn")));
1005
+ }
942
1006
  return new Promise((resolveOuter) => {
943
1007
  const resolve = (result) => {
944
- runCleanup();
1008
+ this.runSubprocessCleanup(cmdConfig.cleanup);
945
1009
  resolveOuter(result);
946
1010
  };
947
1011
  const childEnv = buildChildEnv(this.name);
@@ -950,13 +1014,17 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
950
1014
  env: childEnv
951
1015
  });
952
1016
  const onProgress = options.onProgress;
953
- const state = this.setupChildProcessHandlers(
1017
+ const state = this.setupChildProcessHandlers({
954
1018
  child,
955
1019
  startTime,
956
- options.timeoutMs,
1020
+ timeoutMs: options.timeoutMs,
957
1021
  resolve,
958
- onProgress
959
- );
1022
+ requestId,
1023
+ ...onProgress !== void 0 ? { onProgress } : {}
1024
+ });
1025
+ if (signal !== void 0) {
1026
+ this.attachAbortSignal(child, signal, resolve);
1027
+ }
960
1028
  if (cmdConfig.stdin !== void 0) {
961
1029
  child.stdin.write(cmdConfig.stdin);
962
1030
  }
@@ -967,7 +1035,8 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
967
1035
  /**
968
1036
  * Sets up child process event handlers for output collection and error handling.
969
1037
  */
970
- setupChildProcessHandlers(child, startTime, timeoutMs, resolve, onProgress) {
1038
+ setupChildProcessHandlers(args) {
1039
+ const { child, startTime, timeoutMs, resolve, requestId, onProgress } = args;
971
1040
  const state = {
972
1041
  stdout: "",
973
1042
  stderr: "",
@@ -988,17 +1057,55 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
988
1057
  child.on("error", (error) => {
989
1058
  resolveOnce(this.handleSubprocessError(error));
990
1059
  });
991
- const timeoutId = setTimeout(() => {
992
- child.kill("SIGTERM");
993
- resolveOnce(err(this.createError("TIMEOUT", "Execution timed out")));
994
- }, timeoutMs);
1060
+ const timers = this.scheduleTimeoutWithSigkillEscalation({
1061
+ child,
1062
+ timeoutMs,
1063
+ requestId,
1064
+ resolveOnce
1065
+ });
995
1066
  child.on("close", (code) => {
996
- clearTimeout(timeoutId);
997
- this.logTimingBreakdown(state, startTime, code);
1067
+ clearTimeout(timers.timeoutId);
1068
+ if (timers.sigkillTimerId !== void 0) clearTimeout(timers.sigkillTimerId);
1069
+ this.logTimingBreakdown(state, startTime, code, requestId);
998
1070
  resolveOnce(this.classifyCloseResult(code, state, startTime));
999
1071
  });
1000
1072
  return state;
1001
1073
  }
1074
+ /**
1075
+ * Schedules the SIGTERM-on-timeout + SIGKILL-on-grace escalation
1076
+ * (#3026 finding 1).
1077
+ *
1078
+ * The primary timer fires SIGTERM and resolves the caller's promise
1079
+ * immediately so it doesn't wait on a hung child. The escalation
1080
+ * timer (`SIGKILL_GRACE_MS` later) checks whether the child actually
1081
+ * exited and force-reaps it with SIGKILL if not — preventing
1082
+ * zombie accumulation when a child ignores SIGTERM (Node CLIs that
1083
+ * install graceful-shutdown handlers can hang on a broken stream).
1084
+ * Both timers are cleared from the `'close'` handler so a child
1085
+ * that exits within the grace window doesn't see the second signal.
1086
+ */
1087
+ scheduleTimeoutWithSigkillEscalation(opts) {
1088
+ const { child, timeoutMs, requestId, resolveOnce } = opts;
1089
+ const timers = {
1090
+ timeoutId: setTimeout(() => {
1091
+ child.kill("SIGTERM");
1092
+ resolveOnce(err(this.createError("TIMEOUT", "Execution timed out")));
1093
+ timers.sigkillTimerId = setTimeout(() => {
1094
+ if (child.exitCode === null && child.signalCode === null) {
1095
+ this.logger.warn("Child ignored SIGTERM, escalating to SIGKILL", {
1096
+ cli: this.name,
1097
+ requestId,
1098
+ sigkillGraceMs: SIGKILL_GRACE_MS
1099
+ });
1100
+ child.kill("SIGKILL");
1101
+ }
1102
+ }, SIGKILL_GRACE_MS);
1103
+ timers.sigkillTimerId.unref();
1104
+ }, timeoutMs),
1105
+ sigkillTimerId: void 0
1106
+ };
1107
+ return timers;
1108
+ }
1002
1109
  /** Attach stdout/stderr data handlers + capture first-byte time (#2472). */
1003
1110
  attachStdoutHandlers(child, state, onProgress) {
1004
1111
  if (child.stdout !== null) {
@@ -1029,13 +1136,14 @@ var SubprocessCliAdapter = class extends BaseCliAdapter {
1029
1136
  * Structured fields chosen so existing query_trace tooling can group by
1030
1137
  * cli + provider + model and surface tail-latency outliers.
1031
1138
  */
1032
- logTimingBreakdown(state, startTime, code) {
1139
+ logTimingBreakdown(state, startTime, code, requestId) {
1033
1140
  const now = getTimeProvider().now();
1034
1141
  const totalMs = now - startTime;
1035
1142
  const spawnLatencyMs = state.firstByteTime === null ? null : state.firstByteTime - startTime;
1036
1143
  const streamingMs = state.firstByteTime === null ? null : now - state.firstByteTime;
1037
1144
  this.logger.info("Subprocess timing", {
1038
1145
  cli: this.name,
1146
+ requestId,
1039
1147
  totalMs,
1040
1148
  spawnLatencyMs,
1041
1149
  streamingMs,
@@ -3183,4 +3291,4 @@ export {
3183
3291
  isCliAvailable,
3184
3292
  getAvailableClis
3185
3293
  };
3186
- //# sourceMappingURL=chunk-FHGDJKCZ.js.map
3294
+ //# sourceMappingURL=chunk-OE7OL2BE.js.map