@pellux/goodvibes-sdk 0.18.40 → 0.18.41

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.
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/core/orchestrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/core/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAG7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAQpD,OAAO,EACL,KAAK,wBAAwB,EAC9B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAK9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAkBlE,OAAO,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAQnC,iFAAiF;AACjF,UAAU,kBAAkB;IAC1B,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC7C;AAMD,UAAU,4BAA4B;IACpC,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;GAGG;AACH,qBAAa,YAAY;IAuFrB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,iBAAiB;IACzB,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,cAAc;IA5FjB,UAAU,UAAS;IACnB,aAAa,SAAK;IAClB,KAAK;;;;;MAAwD;IACpE;;;;OAIG;IACI,eAAe,SAAK;IAC3B,kGAAkG;IAC3F,sBAAsB,SAAK;IAClC,4FAA4F;IACrF,oBAAoB,SAAK;IAChC,yFAAyF;IAClF,qBAAqB,SAAK;IAC1B,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAA;KAAE,EAAE,CAAM;IAEtE,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,UAAU,CAA2B;IAC7C,wEAAwE;IACxE,OAAO,CAAC,qBAAqB,CAAK;IAClC,4EAA4E;IAC5E,OAAO,CAAC,WAAW,CAAS;IAC5B,4FAA4F;IAC5F,OAAO,CAAC,kBAAkB,CAAK;IAC/B,4EAA4E;IAC5E,OAAO,CAAC,YAAY,CAAS;IAE7B,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAE1C;;;;;;;;;OASG;IACI,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElD;;;OAGG;IACH,OAAO,CAAC,WAAW,CAAS;IAE5B,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAE/C,uEAAuE;IACvE,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuC;IACpE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqC;IACpE,OAAO,CAAC,YAAY,CAAgC;IACpD,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAA+B;IAC1E,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA0B;IAChE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAyB;IAE9D;;;;;;;;OAQG;IACH,OAAO,CAAC,WAAW,CAAmC;IAEtD;;;;OAIG;IACH,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAC3C,OAAO,CAAC,mBAAmB,CAA6C;IACxE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA8B;gBAGpD,YAAY,EAAE,mBAAmB,EACjC,iBAAiB,EAAE,MAAM,MAAM,EAC/B,WAAW,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EACtC,YAAY,EAAE,YAAY,EAC1B,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,GAAE,MAAM,MAAM,aAAW,EACxC,cAAc,GAAE,kBAAkB,GAAG,IAAI,aAAO,EACxD,WAAW,GAAE,kBAAkB,GAAG,IAAI,aAAO,EAC7C,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,YAAO,EACzC,UAAU,GAAE,eAAe,GAAG,IAAI,aAAO,EACzC,QAAQ,EAAE;QACR,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;QAC5D,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;KAC7D;IAoCI,eAAe,CAAC,QAAQ,EAAE,wBAAwB,GAAG,IAAI;IAOhE;;;OAGG;IACI,oBAAoB,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAkE/C,UAAU,IAAI,MAAM;IAIpB,sBAAsB,CAAC,MAAM,EAAE,4BAA4B,GAAG,IAAI,GAAG,IAAI;IAIzE,2BAA2B,CAAC,IAAI,EAAE,wBAAwB,GAAG,IAAI;IAIxE,uDAAuD;IAChD,KAAK,IAAI,IAAI;IAkBpB;;;;;OAKG;IACI,OAAO,IAAI,IAAI;IAgBtB;;;;;OAKG;IACU,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBlF,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,YAAY;YAWN,OAAO;IAuOrB;;;;;;;;;;;;OAYG;YACW,2BAA2B;IAyBzC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoB7B;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAK/B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,4BAA4B;YAetB,gBAAgB;CAU/B"}
@@ -243,6 +243,16 @@ export class Orchestrator {
243
243
  clearTimeout(this.autoSpawnTimeout);
244
244
  this.autoSpawnTimeout = null;
245
245
  }
246
+ // Clear the thinking-animation interval immediately on abort so the Node
247
+ // event loop is not kept alive by a leaked timer. stopThinking() also
248
+ // clears this in the finally block, but abort() can be called from
249
+ // outside the turn loop (e.g. user keypress during startup) where
250
+ // stopThinking() may never be reached.
251
+ if (this.animInterval !== null) {
252
+ clearInterval(this.animInterval);
253
+ this.animInterval = null;
254
+ }
255
+ this.isThinking = false;
246
256
  }
247
257
  /**
248
258
  * Dispose long-lived runtime attachments owned by this orchestrator.
@@ -1,7 +1,20 @@
1
1
  import type { Tool } from '../../types/tools.js';
2
2
  import { OverflowHandler } from '../shared/overflow.js';
3
+ import type { ExecCommandResult } from './schema.js';
3
4
  import { ProcessManager } from '../shared/process-manager.js';
4
5
  import type { FeatureFlagManager } from '../../runtime/feature-flags/index.js';
6
+ /**
7
+ * Classify whether a failed exec result is retryable.
8
+ *
9
+ * Retryable: network errors (ECONNRESET, ENOTFOUND, ETIMEDOUT), lock/busy
10
+ * (EBUSY, ENOMEM, ECONNREFUSED), HTTP-gateway-style exit codes (124=timeout,
11
+ * 28=curl timeout). Terminal: permission denied (EACCES), missing binary
12
+ * (ENOENT), syntax errors.
13
+ *
14
+ * @param result - The failed command result.
15
+ * @param allowed - Optional allowlist of error category strings.
16
+ */
17
+ export declare function isRetryableExecResult(result: ExecCommandResult, allowed?: ReadonlyArray<'network' | 'lock' | 'busy' | 'oom'>): boolean;
5
18
  export declare function createExecTool(processManager: ProcessManager, options?: {
6
19
  readonly featureFlags?: Pick<FeatureFlagManager, 'isEnabled'> | null;
7
20
  readonly overflowHandler?: OverflowHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/tools/exec/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAGjD,OAAO,EAAqB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE3E,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAkhB/E,wBAAgB,cAAc,CAC5B,cAAc,EAAE,cAAc,EAC9B,OAAO,GAAE;IACP,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC;IACrE,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;CACvC,GACL,IAAI,CAqEN"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/tools/exec/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAGjD,OAAO,EAAqB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAA+B,iBAAiB,EAAiB,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAG9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAwZ/E;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,GAC3D,OAAO,CA2CT;AA2ID,wBAAgB,cAAc,CAC5B,cAAc,EAAE,cAAc,EAC9B,OAAO,GAAE;IACP,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC;IACrE,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;CACvC,GACL,IAAI,CAqEN"}
@@ -52,10 +52,12 @@ function resolveCwd(cwd, workingDirectory) {
52
52
  function sleep(ms) {
53
53
  return new Promise((r) => setTimeout(r, ms));
54
54
  }
55
- function computeRetryDelay(attempt, delayMs, backoff) {
55
+ function computeRetryDelay(attempt, delayMs, backoff, maxDelayMs = 30_000) {
56
56
  if (backoff === 'fixed')
57
57
  return delayMs;
58
- return delayMs * Math.pow(2, attempt);
58
+ // Full jitter: random in [0, min(base * 2^attempt, maxDelay)] — avoids thundering herd
59
+ const cap = Math.min(delayMs * Math.pow(2, attempt), maxDelayMs);
60
+ return Math.random() * cap;
59
61
  }
60
62
  function buildCleanEnv() {
61
63
  return Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
@@ -110,7 +112,7 @@ function initProgressFile(cmdStr, workingDirectory) {
110
112
  }
111
113
  import { copyFileSync, renameSync, unlinkSync, rmSync, cpSync, writeFileSync, mkdirSync, appendFileSync } from 'node:fs';
112
114
  import { summarizeError } from '../../utils/error-display.js';
113
- function spawnBackground(processManager, cmd, cwd, env) {
115
+ async function spawnBackground(processManager, cmd, cwd, env) {
114
116
  return processManager.spawn(cmd, cwd, env);
115
117
  }
116
118
  function handleBgSpecialCommand(processManager, cmd) {
@@ -356,13 +358,66 @@ async function runUntil(_processManager, overflowHandler, cmdStr, cmdInput, cwd,
356
358
  ...(stderrResult.truncated && { stderr_truncated: true }),
357
359
  };
358
360
  }
361
+ /**
362
+ * Classify whether a failed exec result is retryable.
363
+ *
364
+ * Retryable: network errors (ECONNRESET, ENOTFOUND, ETIMEDOUT), lock/busy
365
+ * (EBUSY, ENOMEM, ECONNREFUSED), HTTP-gateway-style exit codes (124=timeout,
366
+ * 28=curl timeout). Terminal: permission denied (EACCES), missing binary
367
+ * (ENOENT), syntax errors.
368
+ *
369
+ * @param result - The failed command result.
370
+ * @param allowed - Optional allowlist of error category strings.
371
+ */
372
+ export function isRetryableExecResult(result, allowed) {
373
+ // Timed-out commands are never auto-retried — callers must decide
374
+ if (result.timed_out)
375
+ return false;
376
+ const combined = `${result.stdout}\n${result.stderr}`;
377
+ // Terminal errors — always skip retry
378
+ const TERMINAL_PATTERNS = [
379
+ /ENOENT/, // missing binary / file
380
+ /EACCES/, // permission denied
381
+ /Permission denied/, // shell-level perm error
382
+ /command not found/, // bash: command not found
383
+ /syntax error/i, // shell syntax
384
+ /No such file or directory/,
385
+ ];
386
+ for (const pat of TERMINAL_PATTERNS) {
387
+ if (pat.test(combined))
388
+ return false;
389
+ }
390
+ // Map error categories to patterns
391
+ const CATEGORY_PATTERNS = {
392
+ network: [/ECONNRESET/, /ENOTFOUND/, /ETIMEDOUT/, /EHOSTUNREACH/, /ENETUNREACH/],
393
+ lock: [/ECONNREFUSED/, /EAGAIN/],
394
+ busy: [/EBUSY/, /Resource temporarily unavailable/],
395
+ oom: [/ENOMEM/, /Cannot allocate memory/, /Out of memory/],
396
+ };
397
+ const effectiveAllowed = allowed ?? ['network', 'lock', 'busy'];
398
+ for (const category of effectiveAllowed) {
399
+ const patterns = CATEGORY_PATTERNS[category] ?? [];
400
+ for (const pat of patterns) {
401
+ if (pat.test(combined))
402
+ return true;
403
+ }
404
+ }
405
+ // Exit code 124 = timeout via `timeout` command; 75 = tempfail (sysexits.h)
406
+ const retryableExitCodes = [124, 75];
407
+ if (result.exit_code !== null && retryableExitCodes.includes(result.exit_code)) {
408
+ return effectiveAllowed.includes('network') || effectiveAllowed.includes('busy');
409
+ }
410
+ return false;
411
+ }
359
412
  async function runWithRetry(processManager, overflowHandler, featureFlags, cmdStr, cmdInput, workingDirectory, globalTimeout) {
360
413
  if (!cmdInput.retry) {
361
414
  return runCommand(processManager, overflowHandler, featureFlags, cmdStr, cmdInput, workingDirectory, globalTimeout);
362
415
  }
363
416
  const maxRetries = Math.min(cmdInput.retry.max ?? 3, 10);
364
417
  const delayMs = cmdInput.retry.delay_ms ?? 1000;
418
+ const maxDelayMs = cmdInput.retry.max_delay_ms ?? 30_000;
365
419
  const backoff = cmdInput.retry.backoff ?? 'exponential';
420
+ const retryOn = cmdInput.retry.on;
366
421
  let lastResult;
367
422
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
368
423
  lastResult = await runCommand(processManager, overflowHandler, featureFlags, cmdStr, cmdInput, workingDirectory, globalTimeout);
@@ -370,7 +425,13 @@ async function runWithRetry(processManager, overflowHandler, featureFlags, cmdSt
370
425
  return { ...lastResult, retries: attempt };
371
426
  }
372
427
  if (attempt < maxRetries) {
373
- const delay = computeRetryDelay(attempt, delayMs, backoff);
428
+ // Classify error: if we can determine it's terminal, stop immediately
429
+ if (!isRetryableExecResult(lastResult, retryOn)) {
430
+ logger.debug('exec: terminal error — not retrying', { cmd: cmdStr, attempt, stderr: lastResult.stderr.slice(0, 200) });
431
+ return { ...lastResult, retries: attempt };
432
+ }
433
+ const delay = computeRetryDelay(attempt, delayMs, backoff, maxDelayMs);
434
+ logger.debug('exec: retrying after jittered delay', { cmd: cmdStr, attempt, delay: Math.round(delay) });
374
435
  await sleep(delay);
375
436
  }
376
437
  }
@@ -74,11 +74,24 @@ export declare const EXEC_TOOL_SCHEMA: {
74
74
  readonly minimum: 0;
75
75
  readonly description: "Base delay between retries in ms. Default: 1000.";
76
76
  };
77
+ readonly max_delay_ms: {
78
+ readonly type: "integer";
79
+ readonly minimum: 0;
80
+ readonly description: "Max jitter cap for exponential backoff in ms. Default: 30000.";
81
+ };
77
82
  readonly backoff: {
78
83
  readonly type: "string";
79
84
  readonly enum: readonly ["fixed", "exponential"];
80
85
  readonly description: "Backoff strategy. Default: exponential.";
81
86
  };
87
+ readonly on: {
88
+ readonly type: "array";
89
+ readonly items: {
90
+ readonly type: "string";
91
+ readonly enum: readonly ["network", "lock", "busy", "oom"];
92
+ };
93
+ readonly description: "Error categories to retry on. Default: [\"network\", \"lock\", \"busy\"].";
94
+ };
82
95
  };
83
96
  };
84
97
  readonly until: {
@@ -189,7 +202,11 @@ export interface ExecExpect {
189
202
  export interface ExecRetry {
190
203
  max?: number;
191
204
  delay_ms?: number;
205
+ /** Max jitter cap for exponential backoff. Default: 30000. */
206
+ max_delay_ms?: number;
192
207
  backoff?: 'fixed' | 'exponential';
208
+ /** Error categories to retry on. Default: ['network', 'lock', 'busy']. */
209
+ on?: ReadonlyArray<'network' | 'lock' | 'busy' | 'oom'>;
193
210
  }
194
211
  export interface ExecUntil {
195
212
  pattern: string;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/tools/exec/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/tools/exec/schema.ts"],"names":[],"mappings":"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"}
@@ -73,11 +73,21 @@ export const EXEC_TOOL_SCHEMA = {
73
73
  minimum: 0,
74
74
  description: 'Base delay between retries in ms. Default: 1000.',
75
75
  },
76
+ max_delay_ms: {
77
+ type: 'integer',
78
+ minimum: 0,
79
+ description: 'Max jitter cap for exponential backoff in ms. Default: 30000.',
80
+ },
76
81
  backoff: {
77
82
  type: 'string',
78
83
  enum: ['fixed', 'exponential'],
79
84
  description: 'Backoff strategy. Default: exponential.',
80
85
  },
86
+ on: {
87
+ type: 'array',
88
+ items: { type: 'string', enum: ['network', 'lock', 'busy', 'oom'] },
89
+ description: 'Error categories to retry on. Default: ["network", "lock", "busy"].',
90
+ },
81
91
  },
82
92
  },
83
93
  until: {
@@ -14,6 +14,17 @@ export interface BackgroundProcess {
14
14
  stderr: string[];
15
15
  exitCode: number | null;
16
16
  done: boolean;
17
+ /**
18
+ * Timestamp (ms since epoch) when SIGKILL was scheduled after a timeout.
19
+ * Null if the process completed normally or SIGKILL was never scheduled.
20
+ */
21
+ killDeadline: number | null;
22
+ }
23
+ export interface SpawnOptions {
24
+ /** Abort the process if it hasn't completed within this many ms. Default: 60000. */
25
+ timeout_ms?: number;
26
+ /** Grace period (ms) between SIGTERM and SIGKILL after timeout. Default: 5000. */
27
+ sigterm_grace_ms?: number;
17
28
  }
18
29
  export interface BgCommandResult {
19
30
  cmd: string;
@@ -31,9 +42,16 @@ export declare class ProcessManager {
31
42
  private newId;
32
43
  /**
33
44
  * Spawn a background process and start collecting its output.
34
- * Returns a BgCommandResult with the process_id and pid.
45
+ *
46
+ * @param cmd Shell command to run via /bin/sh -c.
47
+ * @param cwd Working directory (undefined = inherit).
48
+ * @param env Extra env vars merged with the current process env.
49
+ * @param opts Timeout and SIGKILL grace configuration.
50
+ *
51
+ * @returns A BgCommandResult with the process_id and pid, or rejects if
52
+ * the binary is missing (ENOENT) or exec permission is denied (EACCES).
35
53
  */
36
- spawn(cmd: string, cwd: string | undefined, env: Record<string, string> | undefined): BgCommandResult;
54
+ spawn(cmd: string, cwd: string | undefined, env: Record<string, string> | undefined, opts?: SpawnOptions): Promise<BgCommandResult>;
37
55
  /** Get the status record for a background process, or undefined if not found. */
38
56
  getStatus(id: string): BackgroundProcess | undefined;
39
57
  /** Get the accumulated stdout/stderr for a background process. */
@@ -1 +1 @@
1
- {"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/tools/shared/process-manager.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;GAKG;AAIH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf;AAID,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAID,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,MAAM,CAAmD;IAIjE,OAAO,CAAC,KAAK;IAMb;;;OAGG;IACH,KAAK,CACH,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GACtC,eAAe;IAqDlB,iFAAiF;IACjF,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIpD,kEAAkE;IAClE,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IASrE;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAczB,yEAAyE;IACzE,IAAI,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IASvE;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;CAmDnD"}
1
+ {"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/tools/shared/process-manager.ts"],"names":[],"mappings":"AAAA,qFAAqF;AAErF;;;;;GAKG;AAIH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAID,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAID,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAID,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAwC;IAC1D,OAAO,CAAC,MAAM,CAAmD;IAIjE,OAAO,CAAC,KAAK;IAMb;;;;;;;;;;OAUG;IACG,KAAK,CACT,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACvC,IAAI,CAAC,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,CAAC;IA2F3B,iFAAiF;IACjF,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIpD,kEAAkE;IAClE,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IASrE;;;OAGG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAczB,yEAAyE;IACzE,IAAI,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IASvE;;;OAGG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI;CAmDnD"}
@@ -11,9 +11,18 @@ export class ProcessManager {
11
11
  // ─── Public API ─────────────────────────────────────────────────────────────
12
12
  /**
13
13
  * Spawn a background process and start collecting its output.
14
- * Returns a BgCommandResult with the process_id and pid.
14
+ *
15
+ * @param cmd Shell command to run via /bin/sh -c.
16
+ * @param cwd Working directory (undefined = inherit).
17
+ * @param env Extra env vars merged with the current process env.
18
+ * @param opts Timeout and SIGKILL grace configuration.
19
+ *
20
+ * @returns A BgCommandResult with the process_id and pid, or rejects if
21
+ * the binary is missing (ENOENT) or exec permission is denied (EACCES).
15
22
  */
16
- spawn(cmd, cwd, env) {
23
+ async spawn(cmd, cwd, env, opts) {
24
+ const timeoutMs = opts?.timeout_ms ?? 60_000;
25
+ const sigtermGraceMs = opts?.sigterm_grace_ms ?? 5_000;
17
26
  const id = this.newId();
18
27
  const entry = {
19
28
  id,
@@ -24,20 +33,31 @@ export class ProcessManager {
24
33
  stderr: [],
25
34
  exitCode: null,
26
35
  done: false,
36
+ killDeadline: null,
27
37
  };
28
38
  this._processes.set(id, entry);
29
39
  const cleanEnv = Object.fromEntries(Object.entries(process.env).filter(([, v]) => v !== undefined));
30
40
  const mergedEnv = { ...cleanEnv, ...env };
31
- const proc = Bun.spawn(['/bin/sh', '-c', cmd], {
32
- cwd,
33
- env: mergedEnv,
34
- stdout: 'pipe',
35
- stderr: 'pipe',
36
- });
41
+ let proc;
42
+ try {
43
+ proc = Bun.spawn(['/bin/sh', '-c', cmd], {
44
+ cwd,
45
+ env: mergedEnv,
46
+ stdout: 'pipe',
47
+ stderr: 'pipe',
48
+ });
49
+ }
50
+ catch (spawnErr) {
51
+ // Surface ENOENT / EACCES immediately — callers should not retry these
52
+ this._processes.delete(id);
53
+ throw spawnErr;
54
+ }
37
55
  entry.pid = proc.pid;
38
56
  this._procs.set(id, proc);
39
- // Async collection fire and forget, stored in entry
40
- void (async () => {
57
+ // Async collection with timeout escalation SIGTERM then SIGKILL
58
+ // Cast stdout/stderr to ReadableStream — Bun guarantees these are ReadableStream
59
+ // when stdout/stderr is set to 'pipe', but the return type is a union.
60
+ const collectionPromise = (async () => {
41
61
  const [stdoutText, stderrText] = await Promise.all([
42
62
  new Response(proc.stdout).text(),
43
63
  new Response(proc.stderr).text(),
@@ -48,6 +68,35 @@ export class ProcessManager {
48
68
  entry.done = true;
49
69
  this._procs.delete(id);
50
70
  })();
71
+ // Timeout watchdog: SIGTERM → wait grace → SIGKILL
72
+ const timeoutHandle = setTimeout(async () => {
73
+ if (entry.done)
74
+ return;
75
+ try {
76
+ proc.kill('SIGTERM');
77
+ }
78
+ catch { /* already exited */ }
79
+ entry.killDeadline = Date.now() + sigtermGraceMs;
80
+ await new Promise((r) => setTimeout(r, sigtermGraceMs));
81
+ if (!entry.done) {
82
+ try {
83
+ proc.kill('SIGKILL');
84
+ }
85
+ catch { /* already exited */ }
86
+ }
87
+ }, timeoutMs);
88
+ // Reject the spawn promise if the process errors immediately (ENOENT/EACCES
89
+ // on the child process level) — the outer try/catch handles Bun.spawn throws;
90
+ // this handles async failures surfaced via proc.exited rejecting.
91
+ void collectionPromise.catch(() => {
92
+ clearTimeout(timeoutHandle);
93
+ entry.done = true;
94
+ this._procs.delete(id);
95
+ });
96
+ // Clear the timeout watchdog once the process completes naturally
97
+ void collectionPromise.then(() => {
98
+ clearTimeout(timeoutHandle);
99
+ });
51
100
  return {
52
101
  cmd,
53
102
  exit_code: null,
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- let version = '0.18.40';
3
+ let version = '0.18.41';
4
4
  try {
5
5
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
6
6
  version = pkg.version ?? version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-sdk",
3
- "version": "0.18.40",
3
+ "version": "0.18.41",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"