runline 0.26.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.
@@ -8,6 +8,8 @@ export interface ExecuteResult {
8
8
  export interface EngineOptions {
9
9
  timeoutMs?: number;
10
10
  memoryLimitBytes?: number;
11
+ /** Delivered to every action's `ctx.context` for this run. */
12
+ context?: unknown;
11
13
  }
12
14
  /**
13
15
  * One resolved action call, observed at the engine's dispatch point.
@@ -44,6 +46,13 @@ export interface EngineHooks {
44
46
  * ignores resourceLimits, so the watchdog is the only memory backstop.
45
47
  */
46
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;
47
56
  /**
48
57
  * Executes agent code in a node:worker_threads worker.
49
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;
@@ -243,7 +267,7 @@ export class ExecutionEngine {
243
267
  logs.push(`[${msg.level}] ${msg.line}`);
244
268
  }
245
269
  else if (msg.t === "invoke") {
246
- this.invokeAction(msg.path, msg.args).then((value) => {
270
+ this.invokeAction(msg.path, msg.args, options?.context).then((value) => {
247
271
  let serialized;
248
272
  try {
249
273
  serialized = toPlainJson(value);
@@ -305,7 +329,7 @@ export class ExecutionEngine {
305
329
  reply({ t: "run", runId, body });
306
330
  });
307
331
  }
308
- async invokeAction(path, rawArgs) {
332
+ async invokeAction(path, rawArgs, context) {
309
333
  // A call written with no arguments reaches the action as `{}`, not
310
334
  // `undefined`, so plugins can read `input.foo` without guarding.
311
335
  const args = normalizeActionInput(rawArgs);
@@ -317,6 +341,7 @@ export class ExecutionEngine {
317
341
  const connection = this.resolveConnection(plugin);
318
342
  const ctx = {
319
343
  connection,
344
+ context,
320
345
  log: {
321
346
  info: (msg) => console.log(`[${plugin.name}] ${msg}`),
322
347
  warn: (msg) => console.warn(`[${plugin.name}] ${msg}`),
@@ -51,6 +51,13 @@ export interface ConnectionConfig {
51
51
  }
52
52
  export interface ActionContext {
53
53
  connection: ConnectionConfig;
54
+ /**
55
+ * Opaque per-run value from the embedder's `execute()` options.
56
+ * Host-supplied and invisible to worker code, so actions may
57
+ * derive authority from it (e.g. per-call identity on a shared
58
+ * engine). Absent when the embedder passed none.
59
+ */
60
+ context?: unknown;
54
61
  log: {
55
62
  info(msg: string): void;
56
63
  warn(msg: string): void;
package/dist/sdk.d.ts CHANGED
@@ -24,6 +24,8 @@ export interface RunlineOptions {
24
24
  export interface RunlineExecuteOptions {
25
25
  timeoutMs?: number;
26
26
  memoryLimitBytes?: number;
27
+ /** Delivered to every action's `ctx.context` for this run. */
28
+ context?: unknown;
27
29
  }
28
30
  export declare class Runline {
29
31
  private _registry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runline",
3
- "version": "0.26.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",