runline 0.27.0 → 0.27.1

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.
@@ -46,6 +46,13 @@ export interface EngineHooks {
46
46
  * ignores resourceLimits, so the watchdog is the only memory backstop.
47
47
  */
48
48
  export declare function shouldArmRssWatchdog(versions?: Partial<Record<string, string>>): boolean;
49
+ /**
50
+ * Ceiling on an armed timeout: setTimeout stores its delay in an int32,
51
+ * so past ~24.8 days it overflows and fires at 0ms. Oversized values are
52
+ * rejected with an error that teaches the caller to pass 0 — the explicit
53
+ * "no timeout" — instead of overflowing into an instant kill.
54
+ */
55
+ export declare const MAX_TIMEOUT_MS: number;
49
56
  /**
50
57
  * Executes agent code in a node:worker_threads worker.
51
58
  *
@@ -48,6 +48,13 @@ function workerShape(pluginNames, helpData, memoryLimitMb) {
48
48
  export function shouldArmRssWatchdog(versions = process.versions) {
49
49
  return Boolean(versions.bun);
50
50
  }
51
+ /**
52
+ * Ceiling on an armed timeout: setTimeout stores its delay in an int32,
53
+ * so past ~24.8 days it overflows and fires at 0ms. Oversized values are
54
+ * rejected with an error that teaches the caller to pass 0 — the explicit
55
+ * "no timeout" — instead of overflowing into an instant kill.
56
+ */
57
+ export const MAX_TIMEOUT_MS = 2 ** 31 - 1;
51
58
  // Extra slack on top of the configured memory limit for the RSS watchdog,
52
59
  // since whole-process RSS includes the host's own working set.
53
60
  const RSS_WATCHDOG_SLACK_BYTES = 128 * 1024 * 1024;
@@ -149,6 +156,16 @@ export class ExecutionEngine {
149
156
  const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs;
150
157
  const memoryLimitBytes = options?.memoryLimitBytes ?? this.config.memoryLimitBytes;
151
158
  const logs = [];
159
+ if (!Number.isFinite(timeoutMs) ||
160
+ timeoutMs < 0 ||
161
+ timeoutMs > MAX_TIMEOUT_MS) {
162
+ return {
163
+ result: null,
164
+ error: `Invalid timeoutMs: ${timeoutMs}. ` +
165
+ `Pass 0 (no timeout) or 1..${MAX_TIMEOUT_MS} ms.`,
166
+ logs,
167
+ };
168
+ }
152
169
  const plugins = this.registry.listPlugins();
153
170
  const pluginNames = plugins.map((p) => p.name);
154
171
  const helpData = buildHelpData(plugins);
@@ -190,21 +207,28 @@ export class ExecutionEngine {
190
207
  settled = true;
191
208
  clearTimeout(timeoutTimer);
192
209
  clearInterval(rssTimer);
210
+ worker.unref?.();
193
211
  worker.off("message", onMessage);
194
212
  worker.off("error", onError);
195
213
  worker.off("exit", onExit);
196
214
  this.release(entry, pooled, retire);
197
215
  resolve(r);
198
216
  };
217
+ // The acquire-time unref must not let the host exit mid-run: hold
218
+ // the loop for the run's duration, whether or not a timer is armed.
219
+ worker.ref?.();
199
220
  // A spinning body blocks the worker's event loop, so it can never be
200
221
  // asked to stop — terminate is the only remedy and the worker is gone.
201
- const timeoutTimer = setTimeout(() => {
202
- finish({
203
- result: null,
204
- error: `Execution timed out after ${timeoutMs}ms`,
205
- logs,
206
- }, true);
207
- }, timeoutMs);
222
+ // 0 means no timeout: the caller explicitly opted out of the backstop.
223
+ const timeoutTimer = timeoutMs === 0
224
+ ? undefined
225
+ : setTimeout(() => {
226
+ finish({
227
+ result: null,
228
+ error: `Execution timed out after ${timeoutMs}ms`,
229
+ logs,
230
+ }, true);
231
+ }, timeoutMs);
208
232
  let rssTimer;
209
233
  if (shouldArmRssWatchdog()) {
210
234
  const baselineRss = process.memoryUsage().rss;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runline",
3
- "version": "0.27.0",
3
+ "version": "0.27.1",
4
4
  "description": "Code mode for agents \u2014 turn any API or command into a callable action",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",