@pi-archimedes/subagent 0.7.0 → 0.9.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.
package/src/stream.ts CHANGED
@@ -25,10 +25,29 @@ export function streamEvents(
25
25
  ): Promise<SubagentResult> {
26
26
  return new Promise((resolve, reject) => {
27
27
  const startTime = Date.now();
28
- const timeout = setTimeout(() => {
28
+
29
+ // Startup safeguard: if the child produces no JSON event within
30
+ // STARTUP_TIMEOUT_MS, kill it. This guards against hangs during pi
31
+ // initialization (model never loads, auth fails, etc.) and is the only
32
+ // automatic timeout. Once any event arrives the model is considered
33
+ // active and runtime is controlled entirely by the user's abort
34
+ // signal — a model that is REALLY thinking is left alone until the
35
+ // user explicitly cancels.
36
+ const STARTUP_TIMEOUT_MS = 2 * 60 * 1000;
37
+
38
+ let startupTimer: NodeJS.Timeout | undefined = setTimeout(() => {
29
39
  child.kill("SIGKILL");
30
- reject(new Error("subagent timed out after 5 minutes"));
31
- }, 5 * 60 * 1000);
40
+ reject(new Error(
41
+ `subagent timed out: no model output within ${STARTUP_TIMEOUT_MS / 60_000} minutes of startup`,
42
+ ));
43
+ }, STARTUP_TIMEOUT_MS);
44
+
45
+ const clearStartupTimer = (): void => {
46
+ if (startupTimer) {
47
+ clearTimeout(startupTimer);
48
+ startupTimer = undefined;
49
+ }
50
+ };
32
51
 
33
52
  const state: StreamState = {
34
53
  toolCount: 0,
@@ -97,6 +116,10 @@ export function streamEvents(
97
116
  return; // Skip non-JSON lines
98
117
  }
99
118
 
119
+ // First event received from the child means the model has engaged —
120
+ // from here on, the user controls lifetime via the abort signal.
121
+ clearStartupTimer();
122
+
100
123
  switch (event.type) {
101
124
  case "tool_execution_start": {
102
125
  handleToolStart(state, event);
@@ -131,7 +154,7 @@ export function streamEvents(
131
154
 
132
155
  // Handle process exit
133
156
  child.on("close", (code) => {
134
- clearTimeout(timeout);
157
+ clearStartupTimer();
135
158
  clearInterval(heartbeat);
136
159
  const durationMs = Date.now() - startTime;
137
160
  const exitCode = code ?? 1;
@@ -174,7 +197,7 @@ export function streamEvents(
174
197
  });
175
198
 
176
199
  child.on("error", (err) => {
177
- clearTimeout(timeout);
200
+ clearStartupTimer();
178
201
  clearInterval(heartbeat);
179
202
  reject(err);
180
203
  });