@openagents-org/agent-launcher 0.2.185 → 0.2.186
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/package.json
CHANGED
|
@@ -76,7 +76,6 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
76
76
|
|
|
77
77
|
// Max wall-clock for a single headless run, after which the process group is
|
|
78
78
|
// killed and the turn reported as interrupted.
|
|
79
|
-
const TIMEOUT_MS = 600000; // 10 minutes
|
|
80
79
|
|
|
81
80
|
// Idle watchdog: with no stdout frame for this long we nudge the channel, and
|
|
82
81
|
// after MAX consecutive silences we kill a run that is probably wedged.
|
|
@@ -615,7 +614,6 @@ class CodeBuddyAdapter extends BaseAdapter {
|
|
|
615
614
|
if (settled) return;
|
|
616
615
|
settled = true;
|
|
617
616
|
clearInterval(watchdog);
|
|
618
|
-
clearTimeout(timeout);
|
|
619
617
|
if (this._channelProcesses[channel] === proc) delete this._channelProcesses[channel];
|
|
620
618
|
resolve(payload);
|
|
621
619
|
};
|
|
@@ -721,12 +719,6 @@ class CodeBuddyAdapter extends BaseAdapter {
|
|
|
721
719
|
}
|
|
722
720
|
}, WATCHDOG_INTERVAL_MS);
|
|
723
721
|
|
|
724
|
-
const timeout = setTimeout(() => {
|
|
725
|
-
this._log(`Run exceeded ${TIMEOUT_MS}ms — killing`);
|
|
726
|
-
killedByWatchdog = true;
|
|
727
|
-
void this._stopProcess(proc);
|
|
728
|
-
}, TIMEOUT_MS);
|
|
729
|
-
|
|
730
722
|
try {
|
|
731
723
|
proc.stdin.on('error', () => {});
|
|
732
724
|
proc.stdin.write(prompt, 'utf-8');
|
|
@@ -71,7 +71,6 @@ const IS_WINDOWS = process.platform === 'win32';
|
|
|
71
71
|
|
|
72
72
|
// Max wall-clock for a single headless run, after which the process group is
|
|
73
73
|
// killed and the turn reported as interrupted.
|
|
74
|
-
const TIMEOUT_MS = 600000; // 10 minutes
|
|
75
74
|
|
|
76
75
|
// Idle watchdog: with no stdout frame for this long we nudge the channel, and
|
|
77
76
|
// after MAX consecutive silences we kill a run that is probably wedged.
|
|
@@ -567,7 +566,6 @@ class CommandCodeAdapter extends BaseAdapter {
|
|
|
567
566
|
if (settled) return;
|
|
568
567
|
settled = true;
|
|
569
568
|
clearInterval(watchdog);
|
|
570
|
-
clearTimeout(timeout);
|
|
571
569
|
if (this._channelProcesses[channel] === proc) delete this._channelProcesses[channel];
|
|
572
570
|
resolve(payload);
|
|
573
571
|
};
|
|
@@ -653,12 +651,6 @@ class CommandCodeAdapter extends BaseAdapter {
|
|
|
653
651
|
}
|
|
654
652
|
}, WATCHDOG_INTERVAL_MS);
|
|
655
653
|
|
|
656
|
-
const timeout = setTimeout(() => {
|
|
657
|
-
this._log(`Run exceeded ${TIMEOUT_MS}ms — killing`);
|
|
658
|
-
killedByWatchdog = true;
|
|
659
|
-
void this._stopProcess(proc);
|
|
660
|
-
}, TIMEOUT_MS);
|
|
661
|
-
|
|
662
654
|
// Feed the prompt. stdin is closed immediately: Command Code times out a
|
|
663
655
|
// piped stdin after 30s of silence, so an unclosed pipe would stall.
|
|
664
656
|
try {
|
package/src/adapters/copilot.js
CHANGED
|
@@ -76,7 +76,7 @@ const SECRET_ENV_VARS = [
|
|
|
76
76
|
const ACT_ALLOW_TOOLS = ['shell', 'write'];
|
|
77
77
|
|
|
78
78
|
// How long to wait for a turn before giving up (no output / hung CLI).
|
|
79
|
-
const
|
|
79
|
+
const TURN_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
80
80
|
|
|
81
81
|
class CopilotAdapter extends BaseAdapter {
|
|
82
82
|
/**
|
|
@@ -565,10 +565,15 @@ class CopilotAdapter extends BaseAdapter {
|
|
|
565
565
|
let pending = Promise.resolve();
|
|
566
566
|
let settled = false;
|
|
567
567
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
568
|
+
let timeout = null;
|
|
569
|
+
const resetTimeout = () => {
|
|
570
|
+
if (timeout) clearTimeout(timeout);
|
|
571
|
+
timeout = setTimeout(() => {
|
|
572
|
+
this._stopProcess(proc).catch(() => {});
|
|
573
|
+
finish({ timedOut: true, finalText: accumulatedText() });
|
|
574
|
+
}, TURN_IDLE_TIMEOUT_MS);
|
|
575
|
+
};
|
|
576
|
+
resetTimeout();
|
|
572
577
|
|
|
573
578
|
const finish = (summary) => {
|
|
574
579
|
if (settled) return;
|
|
@@ -657,6 +662,9 @@ class CopilotAdapter extends BaseAdapter {
|
|
|
657
662
|
}
|
|
658
663
|
};
|
|
659
664
|
|
|
665
|
+
if (proc.stdout) proc.stdout.on('data', resetTimeout);
|
|
666
|
+
if (proc.stderr) proc.stderr.on('data', resetTimeout);
|
|
667
|
+
|
|
660
668
|
proc.stdout.on('data', (chunk) => {
|
|
661
669
|
for (const ev of parser.push(chunk)) {
|
|
662
670
|
pending = pending.then(() => handle(ev)).catch(() => {});
|
package/src/adapters/opencode.js
CHANGED
|
@@ -27,8 +27,10 @@ const { whichBinary, whereBinary, getEnhancedEnv } = require('../paths');
|
|
|
27
27
|
|
|
28
28
|
const IS_WINDOWS = process.platform === 'win32';
|
|
29
29
|
|
|
30
|
-
// Max
|
|
31
|
-
//
|
|
30
|
+
// Max time an `opencode run` may stay completely silent. This is deliberately
|
|
31
|
+
// an IDLE timeout, not a wall-clock cap: long coding tasks routinely exceed
|
|
32
|
+
// five minutes while continuing to emit text/tool events. A fixed spawn
|
|
33
|
+
// timeout killed those healthy runs midway through their answer.
|
|
32
34
|
const TIMEOUT_MS = 300000; // 5 minutes
|
|
33
35
|
|
|
34
36
|
// Pinned OpenCode CLI version. Do NOT use @latest anywhere (registry.json /
|
|
@@ -158,6 +160,34 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
/** Environment the CLI is spawned with. */
|
|
163
|
+
|
|
164
|
+
/** Test seam for the inactivity watchdog. */
|
|
165
|
+
_runTimeoutMs() {
|
|
166
|
+
return TIMEOUT_MS;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Arm a watchdog that expires only after a continuous period without CLI
|
|
171
|
+
* output. Every stdout/stderr chunk proves the process is still making
|
|
172
|
+
* progress and pushes the deadline out again.
|
|
173
|
+
*/
|
|
174
|
+
_createIdleWatchdog(onTimeout) {
|
|
175
|
+
const timeoutMs = this._runTimeoutMs();
|
|
176
|
+
let timer = null;
|
|
177
|
+
let stopped = false;
|
|
178
|
+
const touch = () => {
|
|
179
|
+
if (stopped) return;
|
|
180
|
+
if (timer) clearTimeout(timer);
|
|
181
|
+
timer = setTimeout(onTimeout, timeoutMs);
|
|
182
|
+
};
|
|
183
|
+
const cancel = () => {
|
|
184
|
+
stopped = true;
|
|
185
|
+
if (timer) clearTimeout(timer);
|
|
186
|
+
timer = null;
|
|
187
|
+
};
|
|
188
|
+
touch();
|
|
189
|
+
return { touch, cancel, timeoutMs };
|
|
190
|
+
}
|
|
161
191
|
_spawnEnv() {
|
|
162
192
|
return { ...(this.agentEnv || process.env) };
|
|
163
193
|
}
|
|
@@ -701,8 +731,8 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
701
731
|
* CRITICAL: opencode-ai does NOT read the OPENCODE_MODEL env var. When no
|
|
702
732
|
* model is given on the command line AND none is set in opencode.json, the
|
|
703
733
|
* non-interactive `run` command hangs forever waiting for interactive
|
|
704
|
-
* provider/model selection — it emits zero output until the
|
|
705
|
-
* kills it with SIGTERM
|
|
734
|
+
* provider/model selection — it emits zero output until the inactivity
|
|
735
|
+
* watchdog kills it with SIGTERM.
|
|
706
736
|
* Passing `--model` explicitly is what makes headless runs actually work.
|
|
707
737
|
*/
|
|
708
738
|
_resolveModel() {
|
|
@@ -843,7 +873,6 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
843
873
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
844
874
|
env: spawnEnv,
|
|
845
875
|
cwd: runCwd,
|
|
846
|
-
timeout: TIMEOUT_MS,
|
|
847
876
|
detached: !IS_WINDOWS,
|
|
848
877
|
// Never let a console window appear. Besides being ugly, an attached
|
|
849
878
|
// console makes opencode start its interactive TUI and hang instead of
|
|
@@ -859,9 +888,16 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
859
888
|
let pendingEvents = Promise.resolve();
|
|
860
889
|
const responseState = { finalText: '', allText: '', seenText: false };
|
|
861
890
|
let settled = false;
|
|
891
|
+
let timedOut = false;
|
|
892
|
+
const watchdog = this._createIdleWatchdog(() => {
|
|
893
|
+
timedOut = true;
|
|
894
|
+
this._log(`opencode idle for ${this._runTimeoutMs() / 1000}s; terminating`);
|
|
895
|
+
this._stopProcess(proc).catch(() => {});
|
|
896
|
+
});
|
|
862
897
|
const finish = (fn, arg) => {
|
|
863
898
|
if (settled) return;
|
|
864
899
|
settled = true;
|
|
900
|
+
watchdog.cancel();
|
|
865
901
|
if (this._channelProcesses[msgChannel] === proc) {
|
|
866
902
|
delete this._channelProcesses[msgChannel];
|
|
867
903
|
}
|
|
@@ -870,6 +906,7 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
870
906
|
|
|
871
907
|
if (proc.stdout) {
|
|
872
908
|
proc.stdout.on('data', (d) => {
|
|
909
|
+
watchdog.touch();
|
|
873
910
|
const chunk = d.toString('utf-8');
|
|
874
911
|
stdout += chunk;
|
|
875
912
|
streamBuffer += chunk;
|
|
@@ -882,7 +919,10 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
882
919
|
}
|
|
883
920
|
});
|
|
884
921
|
}
|
|
885
|
-
if (proc.stderr) proc.stderr.on('data', (d) => {
|
|
922
|
+
if (proc.stderr) proc.stderr.on('data', (d) => {
|
|
923
|
+
watchdog.touch();
|
|
924
|
+
stderr += d;
|
|
925
|
+
});
|
|
886
926
|
|
|
887
927
|
// Send the prompt via stdin, then close it so opencode knows the message
|
|
888
928
|
// is complete and never blocks waiting for more input. Guard the write:
|
|
@@ -897,9 +937,8 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
897
937
|
|
|
898
938
|
proc.on('error', (err) => finish(reject, err));
|
|
899
939
|
// 'close' (not 'exit') fires after stdout/stderr have fully drained, so we
|
|
900
|
-
// never parse a truncated response. The
|
|
901
|
-
//
|
|
902
|
-
// signal name — report that as a timeout rather than "exited with code null".
|
|
940
|
+
// never parse a truncated response. The explicit `timedOut` flag keeps a
|
|
941
|
+
// watchdog termination distinct from an external signal or crash.
|
|
903
942
|
proc.on('close', async (code, signal) => {
|
|
904
943
|
try { await pendingEvents; } catch {}
|
|
905
944
|
|
|
@@ -917,14 +956,18 @@ class OpenCodeAdapter extends BaseAdapter {
|
|
|
917
956
|
// because stderr happened to be empty.
|
|
918
957
|
const stdoutErr = OpenCodeAdapter._extractErrorFromStdout(stdout);
|
|
919
958
|
|
|
920
|
-
if (
|
|
921
|
-
this._log(`opencode
|
|
959
|
+
if (timedOut) {
|
|
960
|
+
this._log(`opencode timed out after ${watchdog.timeoutMs / 1000}s idle (output: ${!!stdout})`);
|
|
922
961
|
const cls = this.constructor._classifyFailure({ code, signal, stdout, stderr, stdoutErr });
|
|
923
|
-
// A signal almost always means our own timeout fired; only override to
|
|
924
|
-
// a more specific provider error if stdout/stderr clearly show one.
|
|
925
962
|
const category = (cls.category === 'unknown_error' || cls.category === 'process_crashed')
|
|
926
963
|
? 'timeout' : cls.category;
|
|
927
|
-
return finish(reject, this._failure(category, cls.diagnostic || `
|
|
964
|
+
return finish(reject, this._failure(category, cls.diagnostic || `idle=${watchdog.timeoutMs}ms`, cls.detail));
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (signal) {
|
|
968
|
+
this._log(`opencode killed by signal ${signal} (output: ${!!stdout})`);
|
|
969
|
+
const cls = this.constructor._classifyFailure({ code, signal, stdout, stderr, stdoutErr });
|
|
970
|
+
return finish(reject, this._failure(cls.category, cls.diagnostic || `signal=${signal}`, cls.detail));
|
|
928
971
|
}
|
|
929
972
|
|
|
930
973
|
if (code !== 0) {
|
|
@@ -98,7 +98,6 @@ const SERVER_READY_TIMEOUT_MS = 90000;
|
|
|
98
98
|
const HEALTH_POLL_INTERVAL_MS = 400;
|
|
99
99
|
|
|
100
100
|
// Max wall-clock for one turn, after which we interrupt and report it.
|
|
101
|
-
const TURN_TIMEOUT_MS = 900000; // 15 minutes
|
|
102
101
|
|
|
103
102
|
// Idle watchdog over the event stream, in the same shape the CLI adapters use.
|
|
104
103
|
const WATCHDOG_INTERVAL_MS = 15000;
|
|
@@ -674,7 +673,6 @@ class OpenWorkerAdapter extends BaseAdapter {
|
|
|
674
673
|
if (settled) return;
|
|
675
674
|
settled = true;
|
|
676
675
|
clearInterval(watchdog);
|
|
677
|
-
clearTimeout(timeout);
|
|
678
676
|
if (this._channelSockets[channel] === socket) delete this._channelSockets[channel];
|
|
679
677
|
try { socket.close(); } catch {}
|
|
680
678
|
const userStopped = this._stoppingChannels.has(channel);
|
|
@@ -821,12 +819,6 @@ class OpenWorkerAdapter extends BaseAdapter {
|
|
|
821
819
|
}
|
|
822
820
|
}, WATCHDOG_INTERVAL_MS);
|
|
823
821
|
|
|
824
|
-
const timeout = setTimeout(() => {
|
|
825
|
-
this._log(`Turn exceeded ${TURN_TIMEOUT_MS}ms — interrupting`);
|
|
826
|
-
send({ type: 'interrupt' });
|
|
827
|
-
if (!error) error = 'The turn ran past its time limit and was stopped.';
|
|
828
|
-
finish({});
|
|
829
|
-
}, TURN_TIMEOUT_MS);
|
|
830
822
|
});
|
|
831
823
|
}
|
|
832
824
|
|