@pellux/goodvibes-sdk 0.18.40 → 0.18.42

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,4 +1,5 @@
1
1
  import { PersistentStore } from '@pellux/goodvibes-sdk/platform/state/persistent-store';
2
+ import type { RuntimeEventBus } from '../runtime/events/index.js';
2
3
  import { RouteBindingManager } from '../channels/index.js';
3
4
  import type { AutomationRouteBinding } from '@pellux/goodvibes-sdk/platform/automation/routes';
4
5
  import type { SharedSessionCompletion, SharedSessionContinuationRunner, SharedSessionInputRecord } from '@pellux/goodvibes-sdk/platform/control-plane/session-intents';
@@ -15,14 +16,38 @@ export declare class SharedSessionBroker {
15
16
  private eventPublisher;
16
17
  private continuationRunner;
17
18
  private loaded;
19
+ private _gcInterval;
20
+ private _busUnsubs;
21
+ /** Default idle threshold for zero-message sessions (ms). */
22
+ private readonly _idleEmptyMs;
23
+ /** Default idle threshold for sessions with content (ms). */
24
+ private readonly _idleLongMs;
25
+ /**
26
+ * @param config.idleEmptyMs - Idle timeout for empty (0-message) sessions (default: 10 minutes).
27
+ * @param config.idleLongMs - Idle timeout for sessions with content (default: 24 hours).
28
+ */
18
29
  constructor(config: {
19
30
  readonly store?: PersistentStore<SharedSessionStoreSnapshot>;
20
31
  readonly storePath?: string;
21
32
  readonly routeBindings: RouteBindingManager;
22
33
  readonly agentStatusProvider: SharedSessionAgentStatusProvider;
23
34
  readonly messageSender: SharedSessionMessageSender;
35
+ readonly idleEmptyMs?: number;
36
+ readonly idleLongMs?: number;
24
37
  });
25
38
  setEventPublisher(publisher: SharedSessionEventPublisher | null): void;
39
+ /**
40
+ * Wire the broker to a RuntimeEventBus so agent terminal events automatically
41
+ * reconcile session inputs and task state.
42
+ *
43
+ * Call once after both the broker and the bus are constructed. Returns an
44
+ * unsubscribe function that tears down the subscriptions.
45
+ *
46
+ * @param bus - The active RuntimeEventBus.
47
+ * @param sessionResolver - Maps agentId → sessionId for the active session.
48
+ * Return `null` when the agent is not associated with a shared session.
49
+ */
50
+ attachRuntimeBus(bus: RuntimeEventBus, sessionResolver: (agentId: string) => string | null): () => void;
26
51
  setContinuationRunner(runner: SharedSessionContinuationRunner | null): void;
27
52
  start(): Promise<void>;
28
53
  listSessions(limit?: number): SharedSessionRecord[];
@@ -69,5 +94,18 @@ export declare class SharedSessionBroker {
69
94
  private finalizeAgentInputs;
70
95
  private runQueuedFollowUp;
71
96
  private refreshPendingInputCount;
97
+ /**
98
+ * Periodic idle-session GC sweep.
99
+ *
100
+ * Policy:
101
+ * - Sessions with messageCount === 0 AND no active agent AND idle longer than
102
+ * `_idleEmptyMs` (default 10min) are closed with reason `idle-empty`.
103
+ * - Sessions with messageCount > 0 AND no active agent AND idle longer than
104
+ * `_idleLongMs` (default 24h) are closed with reason `idle-long`.
105
+ *
106
+ * "Idle" is measured from `lastActivityAt` (updated on createInput/bindAgent/
107
+ * appendMessage). Sessions with an active agent are never GC'd.
108
+ */
109
+ private _gcSweep;
72
110
  }
73
111
  //# sourceMappingURL=session-broker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-broker.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/control-plane/session-broker.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-broker.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/control-plane/session-broker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uDAAuD,CAAC;AACxF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kDAAkD,CAAC;AAE/F,OAAO,KAAK,EACV,uBAAuB,EACvB,+BAA+B,EAE/B,wBAAwB,EACzB,MAAM,8DAA8D,CAAC;AACtE,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EAEpB,wBAAwB,EACxB,mBAAmB,EACnB,uBAAuB,EACvB,8BAA8B,EAC9B,+BAA+B,EAChC,MAAM,4DAA4D,CAAC;AACpE,OAAO,EAEL,KAAK,gCAAgC,EACrC,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAChC,MAAM,uEAAuE,CAAC;AAc/E,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8C;IACpE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAmC;IACvE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA0C;IACnE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6C;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiD;IACxE,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,kBAAkB,CAAgD;IAC1E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,UAAU,CAAyB;IAE3C,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC;;;OAGG;gBACS,MAAM,EAAE;QAClB,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC,0BAA0B,CAAC,CAAC;QAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,aAAa,EAAE,mBAAmB,CAAC;QAC5C,QAAQ,CAAC,mBAAmB,EAAE,gCAAgC,CAAC;QAC/D,QAAQ,CAAC,aAAa,EAAE,0BAA0B,CAAC;QACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC9B;IAaD,iBAAiB,CAAC,SAAS,EAAE,2BAA2B,GAAG,IAAI,GAAG,IAAI;IAItE;;;;;;;;;;OAUG;IACH,gBAAgB,CACd,GAAG,EAAE,eAAe,EACpB,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAClD,MAAM,IAAI;IAmDb,qBAAqB,CAAC,MAAM,EAAE,+BAA+B,GAAG,IAAI,GAAG,IAAI;IAIrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B,YAAY,CAAC,KAAK,SAAM,GAAG,mBAAmB,EAAE;IAIhD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI;IAInD,oBAAoB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAWjG,aAAa,CAAC,KAAK,GAAE;QACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAC;QAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,wBAAwB,CAAC;KAC5C,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoBrC,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,oBAAoB,EAAE;IAKnE,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,wBAAwB,EAAE;IAK/D,aAAa,CAAC,KAAK,GAAE;QACzB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5C,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAC;QAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,wBAAwB,CAAC;KAC5C,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAqC/B,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAiBpE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAgBrE,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAuBlF,aAAa,CAAC,KAAK,EAAE,+BAA+B,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAIvF,YAAY,CAAC,KAAK,EAAE,8BAA8B,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAIrF,eAAe,CAAC,KAAK,EAAE,+BAA+B,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAIzF,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IASlI,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAyChJ,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC;IAiBzF,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;YAiB9E,aAAa;YAsDb,yBAAyB;IA2CvC,OAAO,CAAC,oBAAoB;YAOd,cAAc;IAQ5B,OAAO,CAAC,qBAAqB;YAoBf,OAAO;IAQrB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,0BAA0B;YAiBpB,YAAY;IA8I1B,OAAO,CAAC,WAAW;IAmCnB,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,mBAAmB;YA8Bb,iBAAiB;IAyB/B,OAAO,CAAC,wBAAwB;IAWhC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,QAAQ;CA+BjB"}
@@ -16,6 +16,16 @@ export class SharedSessionBroker {
16
16
  eventPublisher = null;
17
17
  continuationRunner = null;
18
18
  loaded = false;
19
+ _gcInterval = null;
20
+ _busUnsubs = [];
21
+ /** Default idle threshold for zero-message sessions (ms). */
22
+ _idleEmptyMs;
23
+ /** Default idle threshold for sessions with content (ms). */
24
+ _idleLongMs;
25
+ /**
26
+ * @param config.idleEmptyMs - Idle timeout for empty (0-message) sessions (default: 10 minutes).
27
+ * @param config.idleLongMs - Idle timeout for sessions with content (default: 24 hours).
28
+ */
19
29
  constructor(config) {
20
30
  if (!config.store && !config.storePath) {
21
31
  throw new Error('SharedSessionBroker requires an explicit store or storePath.');
@@ -25,10 +35,50 @@ export class SharedSessionBroker {
25
35
  this.routeBindings = config.routeBindings;
26
36
  this.agentStatusProvider = config.agentStatusProvider;
27
37
  this.messageSender = config.messageSender;
38
+ this._idleEmptyMs = config.idleEmptyMs ?? 10 * 60 * 1000; // 10 min
39
+ this._idleLongMs = config.idleLongMs ?? 24 * 60 * 60 * 1000; // 24 h
28
40
  }
29
41
  setEventPublisher(publisher) {
30
42
  this.eventPublisher = publisher;
31
43
  }
44
+ /**
45
+ * Wire the broker to a RuntimeEventBus so agent terminal events automatically
46
+ * reconcile session inputs and task state.
47
+ *
48
+ * Call once after both the broker and the bus are constructed. Returns an
49
+ * unsubscribe function that tears down the subscriptions.
50
+ *
51
+ * @param bus - The active RuntimeEventBus.
52
+ * @param sessionResolver - Maps agentId → sessionId for the active session.
53
+ * Return `null` when the agent is not associated with a shared session.
54
+ */
55
+ attachRuntimeBus(bus, sessionResolver) {
56
+ const onCompleted = bus.on('AGENT_COMPLETED', (envelope) => {
57
+ const sessionId = sessionResolver(envelope.payload.agentId);
58
+ if (!sessionId)
59
+ return;
60
+ void this.completeAgent(sessionId, envelope.payload.agentId, envelope.payload.output ?? '', { status: 'completed', durationMs: envelope.payload.durationMs });
61
+ });
62
+ const onFailed = bus.on('AGENT_FAILED', (envelope) => {
63
+ const sessionId = sessionResolver(envelope.payload.agentId);
64
+ if (!sessionId)
65
+ return;
66
+ void this.completeAgent(sessionId, envelope.payload.agentId, envelope.payload.error, { status: 'failed', durationMs: envelope.payload.durationMs });
67
+ });
68
+ const onCancelled = bus.on('AGENT_CANCELLED', (envelope) => {
69
+ const sessionId = sessionResolver(envelope.payload.agentId);
70
+ if (!sessionId)
71
+ return;
72
+ void this.completeAgent(sessionId, envelope.payload.agentId, envelope.payload.reason ?? 'cancelled', { status: 'cancelled' });
73
+ });
74
+ this._busUnsubs.push(onCompleted, onFailed, onCancelled);
75
+ return () => {
76
+ onCompleted();
77
+ onFailed();
78
+ onCancelled();
79
+ this._busUnsubs = this._busUnsubs.filter((fn) => fn !== onCompleted && fn !== onFailed && fn !== onCancelled);
80
+ };
81
+ }
32
82
  setContinuationRunner(runner) {
33
83
  this.continuationRunner = runner;
34
84
  }
@@ -50,6 +100,9 @@ export class SharedSessionBroker {
50
100
  this.inputs.set(sessionId, bucket);
51
101
  }
52
102
  this.loaded = true;
103
+ if (!this._gcInterval) {
104
+ this._gcInterval = setInterval(() => { this._gcSweep(); }, 60_000);
105
+ }
53
106
  }
54
107
  listSessions(limit = 100) {
55
108
  return sortSessions(this.sessions.values()).slice(0, Math.max(1, limit));
@@ -112,6 +165,7 @@ export class SharedSessionBroker {
112
165
  updatedAt: now,
113
166
  lastMessageAt: undefined,
114
167
  closedAt: undefined,
168
+ lastActivityAt: now,
115
169
  messageCount: 0,
116
170
  pendingInputCount: 0,
117
171
  routeIds,
@@ -175,6 +229,7 @@ export class SharedSessionBroker {
175
229
  activeAgentId: agentId,
176
230
  lastAgentId: agentId,
177
231
  updatedAt: Date.now(),
232
+ lastActivityAt: Date.now(),
178
233
  };
179
234
  this.sessions.set(sessionId, updated);
180
235
  const claimed = this.claimNextQueuedInput(sessionId, agentId);
@@ -306,6 +361,7 @@ export class SharedSessionBroker {
306
361
  messageCount: bucket.length,
307
362
  lastMessageAt: message.createdAt,
308
363
  updatedAt: message.createdAt,
364
+ lastActivityAt: message.createdAt,
309
365
  };
310
366
  this.sessions.set(sessionId, updated);
311
367
  }
@@ -665,4 +721,49 @@ export class SharedSessionBroker {
665
721
  updatedAt: Date.now(),
666
722
  });
667
723
  }
724
+ /**
725
+ * Periodic idle-session GC sweep.
726
+ *
727
+ * Policy:
728
+ * - Sessions with messageCount === 0 AND no active agent AND idle longer than
729
+ * `_idleEmptyMs` (default 10min) are closed with reason `idle-empty`.
730
+ * - Sessions with messageCount > 0 AND no active agent AND idle longer than
731
+ * `_idleLongMs` (default 24h) are closed with reason `idle-long`.
732
+ *
733
+ * "Idle" is measured from `lastActivityAt` (updated on createInput/bindAgent/
734
+ * appendMessage). Sessions with an active agent are never GC'd.
735
+ */
736
+ _gcSweep() {
737
+ const now = Date.now();
738
+ for (const [sessionId, session] of this.sessions.entries()) {
739
+ if (session.status !== 'active')
740
+ continue;
741
+ if (session.activeAgentId)
742
+ continue; // live agent — leave it
743
+ const idle = now - session.lastActivityAt;
744
+ let reason = null;
745
+ if (session.messageCount === 0 && idle >= this._idleEmptyMs) {
746
+ reason = 'idle-empty';
747
+ }
748
+ else if (session.messageCount > 0 && idle >= this._idleLongMs) {
749
+ reason = 'idle-long';
750
+ }
751
+ if (!reason)
752
+ continue;
753
+ const closed = {
754
+ ...session,
755
+ status: 'closed',
756
+ activeAgentId: undefined,
757
+ updatedAt: now,
758
+ closedAt: now,
759
+ };
760
+ this.sessions.set(sessionId, closed);
761
+ this.publishUpdate('session-closed', { ...closed, reason });
762
+ }
763
+ // Persist after a sweep only when something changed.
764
+ const anyChanged = [...this.sessions.values()].some((s) => s.status === 'closed' && s.closedAt && now - s.closedAt < 2_000);
765
+ if (anyChanged) {
766
+ void this.persist();
767
+ }
768
+ }
668
769
  }
@@ -34,6 +34,12 @@ export interface SharedSessionRecord {
34
34
  readonly updatedAt: number;
35
35
  readonly lastMessageAt?: number;
36
36
  readonly closedAt?: number;
37
+ /**
38
+ * Epoch ms of the most recent activity on this session.
39
+ * Updated on every createInput, message post, bindAgent, and heartbeat.
40
+ * Used by the idle-session GC sweep to decide when to close ghost sessions.
41
+ */
42
+ readonly lastActivityAt: number;
37
43
  readonly messageCount: number;
38
44
  readonly pendingInputCount: number;
39
45
  readonly routeIds: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"session-types.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/control-plane/session-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-types.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/control-plane/session-types.ts"],"names":[],"mappings":"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"}
@@ -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":"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"}
@@ -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.
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { RuntimeStore } from '../../store/index.js';
9
9
  import type { AgentLifecycleState } from '@pellux/goodvibes-sdk/platform/runtime/store/domains/agents';
10
+ import type { RuntimeEventBus } from '../../events/index.js';
10
11
  /** Owner context for an agent task. */
11
12
  export interface AgentOwner {
12
13
  /** Session ID that spawned this agent. */
@@ -31,6 +32,18 @@ export declare class AgentTaskAdapter {
31
32
  private readonly _taskToAgent;
32
33
  private readonly _dispatch;
33
34
  constructor(_store: RuntimeStore);
35
+ /**
36
+ * Subscribe to AGENT_COMPLETED / AGENT_FAILED / AGENT_CANCELLED events on the
37
+ * RuntimeEventBus and propagate them into the task registry.
38
+ *
39
+ * This is the authoritative wire that ensures task records reach terminal state
40
+ * once their backing agent finishes — without it, tasks stay stuck in 'running'
41
+ * indefinitely (the bug observed in daemon state at 192.168.0.61:3421).
42
+ *
43
+ * @param bus - The active RuntimeEventBus instance.
44
+ * @returns An unsubscribe function that removes all three listeners.
45
+ */
46
+ attachRuntimeBus(bus: RuntimeEventBus): () => void;
34
47
  /**
35
48
  * Wrap an agent session as a RuntimeTask.
36
49
  *
@@ -1 +1 @@
1
- {"version":3,"file":"agent-adapter.d.ts","sourceRoot":"","sources":["../../../../../../src/_internal/platform/runtime/tasks/adapters/agent-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6DAA6D,CAAC;AAEvG,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAkCD;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAQf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,+BAA+B;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,+BAA+B;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;gBAEd,MAAM,EAAE,YAAY;IAMjD;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM;IA4BnE;;;;;OAKG;IACH,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAmBzE;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASjC;;;;;OAKG;IACH,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,GAAE,UAAoC,GAAG,IAAI;IAyBlG,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,eAAe;CAkBxB"}
1
+ {"version":3,"file":"agent-adapter.d.ts","sourceRoot":"","sources":["../../../../../../src/_internal/platform/runtime/tasks/adapters/agent-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6DAA6D,CAAC;AACvG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAkCD;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAQf,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,+BAA+B;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,+BAA+B;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAE1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;gBAEd,MAAM,EAAE,YAAY;IAMjD;;;;;;;;;;OAUG;IACH,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,IAAI;IAkClD;;;;;;;OAOG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM;IA4BnE;;;;;OAKG;IACH,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAmBzE;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IASjC;;;;;OAKG;IACH,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,GAAE,UAAoC,GAAG,IAAI;IAyBlG,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,eAAe;CAkBxB"}
@@ -61,6 +61,43 @@ export class AgentTaskAdapter {
61
61
  this._store = _store;
62
62
  this._dispatch = createDomainDispatch(_store);
63
63
  }
64
+ // ── Runtime bus wiring ──────────────────────────────────────────────────────
65
+ /**
66
+ * Subscribe to AGENT_COMPLETED / AGENT_FAILED / AGENT_CANCELLED events on the
67
+ * RuntimeEventBus and propagate them into the task registry.
68
+ *
69
+ * This is the authoritative wire that ensures task records reach terminal state
70
+ * once their backing agent finishes — without it, tasks stay stuck in 'running'
71
+ * indefinitely (the bug observed in daemon state at 192.168.0.61:3421).
72
+ *
73
+ * @param bus - The active RuntimeEventBus instance.
74
+ * @returns An unsubscribe function that removes all three listeners.
75
+ */
76
+ attachRuntimeBus(bus) {
77
+ const onCompleted = bus.on('AGENT_COMPLETED', (envelope) => {
78
+ const taskId = this._agentToTask.get(envelope.payload.agentId);
79
+ if (taskId === undefined)
80
+ return; // not a tracked agent — no-op
81
+ this.handleAgentStateChange(envelope.payload.agentId, 'completed');
82
+ });
83
+ const onFailed = bus.on('AGENT_FAILED', (envelope) => {
84
+ const taskId = this._agentToTask.get(envelope.payload.agentId);
85
+ if (taskId === undefined)
86
+ return;
87
+ this.handleAgentStateChange(envelope.payload.agentId, 'failed');
88
+ });
89
+ const onCancelled = bus.on('AGENT_CANCELLED', (envelope) => {
90
+ const taskId = this._agentToTask.get(envelope.payload.agentId);
91
+ if (taskId === undefined)
92
+ return;
93
+ this.handleAgentStateChange(envelope.payload.agentId, 'cancelled');
94
+ });
95
+ return () => {
96
+ onCompleted();
97
+ onFailed();
98
+ onCancelled();
99
+ };
100
+ }
64
101
  // ── Core API ────────────────────────────────────────────────────────────────
65
102
  /**
66
103
  * Wrap an agent session as a RuntimeTask.
@@ -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.42';
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.42",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"