@rulvar/executor 1.246.0 → 1.247.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/dist/index.js +281 -246
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -231,126 +231,143 @@ function subprocessExecutor(options = {}) {
|
|
|
231
231
|
const maxOutputBytes = options.maxOutputBytes ?? 1024 * 1024;
|
|
232
232
|
const workdirBase = options.workdirBase ?? tmpdir();
|
|
233
233
|
const now = options.now ?? wallClock$2;
|
|
234
|
-
return {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
workdir,
|
|
249
|
-
startedAt,
|
|
250
|
-
attemptId
|
|
251
|
-
});
|
|
252
|
-
} catch (err) {
|
|
253
|
-
await rm(workdir, {
|
|
254
|
-
recursive: true,
|
|
255
|
-
force: true
|
|
256
|
-
});
|
|
257
|
-
throw new ExecutorError("ledger", `tool '${request.tool}' was not dispatched: the two-phase ledger intent write failed (${err instanceof Error ? err.message : String(err)})`);
|
|
258
|
-
}
|
|
259
|
-
let outcome = "error";
|
|
260
|
-
let exitCode = null;
|
|
261
|
-
let signal = null;
|
|
262
|
-
let settled;
|
|
263
|
-
let bodyThrew = false;
|
|
264
|
-
let thrownBody;
|
|
265
|
-
try {
|
|
266
|
-
const env = {};
|
|
267
|
-
for (const name of options.allowEnv ?? []) {
|
|
268
|
-
const value = process.env[name];
|
|
269
|
-
if (value !== void 0) env[name] = value;
|
|
234
|
+
return {
|
|
235
|
+
describeRegulatedPosture: () => ({
|
|
236
|
+
regulatedPosture: 1,
|
|
237
|
+
kind: "tool-executor",
|
|
238
|
+
name: "subprocess",
|
|
239
|
+
ledger: options.ledger !== void 0,
|
|
240
|
+
allowEnv: [...options.allowEnv ?? []],
|
|
241
|
+
bounds: {
|
|
242
|
+
timeoutMs,
|
|
243
|
+
maxOutputBytes
|
|
244
|
+
},
|
|
245
|
+
isolation: {
|
|
246
|
+
flavor: "subprocess",
|
|
247
|
+
sandboxed: options.sandbox !== void 0
|
|
270
248
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
command: spawnCommand,
|
|
290
|
-
args: spawnArgs,
|
|
291
|
-
env,
|
|
292
|
-
cwd: workdir,
|
|
293
|
-
stdinData: JSON.stringify({
|
|
294
|
-
tool: request.tool,
|
|
295
|
-
args: request.args,
|
|
296
|
-
idempotencyKey: request.ctx.idempotencyKey
|
|
297
|
-
}),
|
|
298
|
-
timeoutMs,
|
|
299
|
-
killGraceMs,
|
|
300
|
-
maxOutputBytes,
|
|
301
|
-
signal: request.ctx.signal
|
|
249
|
+
}),
|
|
250
|
+
async run(request) {
|
|
251
|
+
const { command, args } = resolveCommand(request, options);
|
|
252
|
+
const workdir = await mkdtemp(join(workdirBase, `rulvar-exec-${request.tool}-`));
|
|
253
|
+
const startedAt = now();
|
|
254
|
+
const argsHash = hashArgs(request.args);
|
|
255
|
+
const attemptId = randomUUID();
|
|
256
|
+
if (options.ledger?.intent !== void 0) try {
|
|
257
|
+
await options.ledger.intent({
|
|
258
|
+
idempotencyKey: request.ctx.idempotencyKey,
|
|
259
|
+
runId: request.ctx.runId,
|
|
260
|
+
spanId: request.ctx.spanId,
|
|
261
|
+
tool: request.tool,
|
|
262
|
+
argsHash,
|
|
263
|
+
executor: request.executor,
|
|
264
|
+
workdir,
|
|
265
|
+
startedAt,
|
|
266
|
+
attemptId
|
|
302
267
|
});
|
|
303
268
|
} catch (err) {
|
|
304
|
-
|
|
269
|
+
await rm(workdir, {
|
|
270
|
+
recursive: true,
|
|
271
|
+
force: true
|
|
272
|
+
});
|
|
273
|
+
throw new ExecutorError("ledger", `tool '${request.tool}' was not dispatched: the two-phase ledger intent write failed (${err instanceof Error ? err.message : String(err)})`);
|
|
305
274
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
275
|
+
let outcome = "error";
|
|
276
|
+
let exitCode = null;
|
|
277
|
+
let signal = null;
|
|
278
|
+
let settled;
|
|
279
|
+
let bodyThrew = false;
|
|
280
|
+
let thrownBody;
|
|
281
|
+
try {
|
|
282
|
+
const env = {};
|
|
283
|
+
for (const name of options.allowEnv ?? []) {
|
|
284
|
+
const value = process.env[name];
|
|
285
|
+
if (value !== void 0) env[name] = value;
|
|
286
|
+
}
|
|
287
|
+
const creds = options.credentials === void 0 ? {} : await options.credentials(request);
|
|
288
|
+
Object.assign(env, creds);
|
|
289
|
+
env.RULVAR_TOOL = request.tool;
|
|
290
|
+
env.RULVAR_RUN_ID = request.ctx.runId;
|
|
291
|
+
env.RULVAR_IDEMPOTENCY_KEY = request.ctx.idempotencyKey;
|
|
292
|
+
const wrapper = options.sandbox === void 0 ? [] : [...options.sandbox({
|
|
293
|
+
workdir,
|
|
294
|
+
request
|
|
295
|
+
})];
|
|
296
|
+
const [spawnCommand, ...spawnPrefix] = wrapper.length > 0 ? wrapper : [command];
|
|
297
|
+
const spawnArgs = wrapper.length > 0 ? [
|
|
298
|
+
...spawnPrefix,
|
|
299
|
+
command,
|
|
300
|
+
...args
|
|
301
|
+
] : args;
|
|
302
|
+
let child;
|
|
303
|
+
try {
|
|
304
|
+
child = await runChildProcess({
|
|
305
|
+
command: spawnCommand,
|
|
306
|
+
args: spawnArgs,
|
|
307
|
+
env,
|
|
308
|
+
cwd: workdir,
|
|
309
|
+
stdinData: JSON.stringify({
|
|
310
|
+
tool: request.tool,
|
|
311
|
+
args: request.args,
|
|
312
|
+
idempotencyKey: request.ctx.idempotencyKey
|
|
313
|
+
}),
|
|
314
|
+
timeoutMs,
|
|
315
|
+
killGraceMs,
|
|
316
|
+
maxOutputBytes,
|
|
317
|
+
signal: request.ctx.signal
|
|
318
|
+
});
|
|
319
|
+
} catch (err) {
|
|
320
|
+
throw new ExecutorError("spawn", `tool '${request.tool}' could not be spawned: ${err instanceof Error ? err.message : String(err)}`);
|
|
321
|
+
}
|
|
322
|
+
exitCode = child.code;
|
|
323
|
+
signal = child.signal;
|
|
324
|
+
if (child.stopped && child.reason === "timeout") {
|
|
325
|
+
outcome = "timeout";
|
|
326
|
+
throw new ExecutorError("timeout", `tool '${request.tool}' exceeded ${timeoutMs}ms and was killed`);
|
|
327
|
+
}
|
|
328
|
+
if (child.stopped && child.reason === "aborted") throw new ExecutorError("aborted", `tool '${request.tool}' was cancelled`);
|
|
329
|
+
if (child.stopped && child.reason === "output-cap") throw new ExecutorError("output-cap", `tool '${request.tool}' wrote more than ${maxOutputBytes} bytes and was killed`);
|
|
330
|
+
if (child.code !== 0) {
|
|
331
|
+
const tail = child.stderr.trim().slice(-500);
|
|
332
|
+
throw new ExecutorError("exit", `tool '${request.tool}' exited ${child.code ?? "null"}${child.signal === null ? "" : ` (signal ${child.signal})`}${tail === "" ? "" : `: ${tail}`}`);
|
|
333
|
+
}
|
|
334
|
+
const result = parseToolResult(child.stdout, request.tool);
|
|
335
|
+
outcome = "ok";
|
|
336
|
+
settled = result;
|
|
337
|
+
} catch (thrown) {
|
|
338
|
+
bodyThrew = true;
|
|
339
|
+
thrownBody = thrown;
|
|
311
340
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
341
|
+
const durationMs = now() - startedAt;
|
|
342
|
+
let ledgerFailure;
|
|
343
|
+
try {
|
|
344
|
+
if (options.ledger !== void 0) await options.ledger.record({
|
|
345
|
+
idempotencyKey: request.ctx.idempotencyKey,
|
|
346
|
+
runId: request.ctx.runId,
|
|
347
|
+
spanId: request.ctx.spanId,
|
|
348
|
+
tool: request.tool,
|
|
349
|
+
argsHash,
|
|
350
|
+
executor: request.executor,
|
|
351
|
+
workdir,
|
|
352
|
+
startedAt,
|
|
353
|
+
attemptId,
|
|
354
|
+
durationMs,
|
|
355
|
+
outcome,
|
|
356
|
+
exitCode,
|
|
357
|
+
signal
|
|
358
|
+
});
|
|
359
|
+
} catch (recordErr) {
|
|
360
|
+
ledgerFailure = new ExecutorError("ledger", `tool '${request.tool}' outcome was not recorded: the ledger record write failed (${recordErr instanceof Error ? recordErr.message : String(recordErr)})` + (bodyThrew ? `; the dispatch itself had already failed (${thrownBody instanceof Error ? thrownBody.message : String(thrownBody)})` : ""));
|
|
317
361
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
} catch (thrown) {
|
|
322
|
-
bodyThrew = true;
|
|
323
|
-
thrownBody = thrown;
|
|
324
|
-
}
|
|
325
|
-
const durationMs = now() - startedAt;
|
|
326
|
-
let ledgerFailure;
|
|
327
|
-
try {
|
|
328
|
-
if (options.ledger !== void 0) await options.ledger.record({
|
|
329
|
-
idempotencyKey: request.ctx.idempotencyKey,
|
|
330
|
-
runId: request.ctx.runId,
|
|
331
|
-
spanId: request.ctx.spanId,
|
|
332
|
-
tool: request.tool,
|
|
333
|
-
argsHash,
|
|
334
|
-
executor: request.executor,
|
|
335
|
-
workdir,
|
|
336
|
-
startedAt,
|
|
337
|
-
attemptId,
|
|
338
|
-
durationMs,
|
|
339
|
-
outcome,
|
|
340
|
-
exitCode,
|
|
341
|
-
signal
|
|
362
|
+
await rm(workdir, {
|
|
363
|
+
recursive: true,
|
|
364
|
+
force: true
|
|
342
365
|
});
|
|
343
|
-
|
|
344
|
-
|
|
366
|
+
if (ledgerFailure !== void 0) throw ledgerFailure;
|
|
367
|
+
if (bodyThrew) throw thrownBody;
|
|
368
|
+
return settled;
|
|
345
369
|
}
|
|
346
|
-
|
|
347
|
-
recursive: true,
|
|
348
|
-
force: true
|
|
349
|
-
});
|
|
350
|
-
if (ledgerFailure !== void 0) throw ledgerFailure;
|
|
351
|
-
if (bodyThrew) throw thrownBody;
|
|
352
|
-
return settled;
|
|
353
|
-
} };
|
|
370
|
+
};
|
|
354
371
|
}
|
|
355
372
|
/**
|
|
356
373
|
* Defines a tool that runs under a subprocess (or container) executor.
|
|
@@ -431,150 +448,168 @@ function containerExecutor(options) {
|
|
|
431
448
|
const maxOutputBytes = options.maxOutputBytes ?? 1024 * 1024;
|
|
432
449
|
const workdirBase = options.workdirBase ?? tmpdir();
|
|
433
450
|
const now = options.now ?? wallClock$1;
|
|
434
|
-
return {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
tool: request.tool,
|
|
450
|
-
argsHash,
|
|
451
|
-
executor: request.executor,
|
|
452
|
-
workdir,
|
|
453
|
-
startedAt,
|
|
454
|
-
attemptId
|
|
455
|
-
});
|
|
456
|
-
} catch (err) {
|
|
457
|
-
await rm(workdir, {
|
|
458
|
-
recursive: true,
|
|
459
|
-
force: true
|
|
460
|
-
});
|
|
461
|
-
throw new ExecutorError("ledger", `container tool '${request.tool}' was not dispatched: the two-phase ledger intent write failed (${err instanceof Error ? err.message : String(err)})`);
|
|
462
|
-
}
|
|
463
|
-
let outcome = "error";
|
|
464
|
-
let exitCode = null;
|
|
465
|
-
let signal = null;
|
|
466
|
-
let settled;
|
|
467
|
-
let bodyThrew = false;
|
|
468
|
-
let thrownBody;
|
|
469
|
-
try {
|
|
470
|
-
const daemonEnv = options.daemonEnv ?? DEFAULT_DAEMON_ENV;
|
|
471
|
-
const env = {};
|
|
472
|
-
for (const name of daemonEnv) {
|
|
473
|
-
const value = process.env[name];
|
|
474
|
-
if (value !== void 0) env[name] = value;
|
|
451
|
+
return {
|
|
452
|
+
describeRegulatedPosture: () => ({
|
|
453
|
+
regulatedPosture: 1,
|
|
454
|
+
kind: "tool-executor",
|
|
455
|
+
name: "container",
|
|
456
|
+
ledger: options.ledger !== void 0,
|
|
457
|
+
allowEnv: [...options.forwardEnv ?? []],
|
|
458
|
+
bounds: {
|
|
459
|
+
timeoutMs,
|
|
460
|
+
maxOutputBytes
|
|
461
|
+
},
|
|
462
|
+
isolation: {
|
|
463
|
+
flavor: "container",
|
|
464
|
+
network,
|
|
465
|
+
readOnlyRoot: readOnly
|
|
475
466
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
467
|
+
}),
|
|
468
|
+
async run(request) {
|
|
469
|
+
const spec = request.spec ?? {};
|
|
470
|
+
const command = typeof spec.command === "string" ? spec.command : options.command;
|
|
471
|
+
if (command === void 0 || command === "") throw new ExecutorError("config", `tool '${request.tool}' has no command: set executorSpec.command or the executor command`);
|
|
472
|
+
const specArgs = Array.isArray(spec.args) ? spec.args.filter((a) => typeof a === "string") : [];
|
|
473
|
+
const toolArgs = [...options.args ?? [], ...specArgs];
|
|
474
|
+
const workdir = await mkdtemp(join(workdirBase, `rulvar-cexec-${request.tool}-`));
|
|
475
|
+
const startedAt = now();
|
|
476
|
+
const argsHash = hashArgs(request.args);
|
|
477
|
+
const attemptId = randomUUID();
|
|
478
|
+
if (options.ledger?.intent !== void 0) try {
|
|
479
|
+
await options.ledger.intent({
|
|
480
|
+
idempotencyKey: request.ctx.idempotencyKey,
|
|
481
|
+
runId: request.ctx.runId,
|
|
482
|
+
spanId: request.ctx.spanId,
|
|
483
|
+
tool: request.tool,
|
|
484
|
+
argsHash,
|
|
485
|
+
executor: request.executor,
|
|
486
|
+
workdir,
|
|
487
|
+
startedAt,
|
|
488
|
+
attemptId
|
|
489
|
+
});
|
|
490
|
+
} catch (err) {
|
|
491
|
+
await rm(workdir, {
|
|
492
|
+
recursive: true,
|
|
493
|
+
force: true
|
|
494
|
+
});
|
|
495
|
+
throw new ExecutorError("ledger", `container tool '${request.tool}' was not dispatched: the two-phase ledger intent write failed (${err instanceof Error ? err.message : String(err)})`);
|
|
496
|
+
}
|
|
497
|
+
let outcome = "error";
|
|
498
|
+
let exitCode = null;
|
|
499
|
+
let signal = null;
|
|
500
|
+
let settled;
|
|
501
|
+
let bodyThrew = false;
|
|
502
|
+
let thrownBody;
|
|
503
|
+
try {
|
|
504
|
+
const daemonEnv = options.daemonEnv ?? DEFAULT_DAEMON_ENV;
|
|
505
|
+
const env = {};
|
|
506
|
+
for (const name of daemonEnv) {
|
|
507
|
+
const value = process.env[name];
|
|
508
|
+
if (value !== void 0) env[name] = value;
|
|
509
|
+
}
|
|
510
|
+
const forwardNames = /* @__PURE__ */ new Set([
|
|
511
|
+
"RULVAR_TOOL",
|
|
512
|
+
"RULVAR_RUN_ID",
|
|
513
|
+
"RULVAR_IDEMPOTENCY_KEY"
|
|
514
|
+
]);
|
|
515
|
+
for (const name of options.forwardEnv ?? []) {
|
|
516
|
+
const value = process.env[name];
|
|
517
|
+
if (value !== void 0) {
|
|
518
|
+
env[name] = value;
|
|
519
|
+
forwardNames.add(name);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const creds = options.credentials === void 0 ? {} : await options.credentials(request);
|
|
523
|
+
for (const [name, value] of Object.entries(creds)) {
|
|
484
524
|
env[name] = value;
|
|
485
525
|
forwardNames.add(name);
|
|
486
526
|
}
|
|
527
|
+
env.RULVAR_TOOL = request.tool;
|
|
528
|
+
env.RULVAR_RUN_ID = request.ctx.runId;
|
|
529
|
+
env.RULVAR_IDEMPOTENCY_KEY = request.ctx.idempotencyKey;
|
|
530
|
+
const dockerArgs = [
|
|
531
|
+
"run",
|
|
532
|
+
"--rm",
|
|
533
|
+
"-i",
|
|
534
|
+
"--network",
|
|
535
|
+
network
|
|
536
|
+
];
|
|
537
|
+
dockerArgs.push("--memory", memory, "--cpus", cpus, "--pids-limit", String(pidsLimit));
|
|
538
|
+
if (readOnly) dockerArgs.push("--read-only");
|
|
539
|
+
for (const cap of capDrop) dockerArgs.push("--cap-drop", cap);
|
|
540
|
+
for (const name of forwardNames) dockerArgs.push("-e", name);
|
|
541
|
+
dockerArgs.push("-v", `${workdir}:${workMount}`, "-w", workMount);
|
|
542
|
+
dockerArgs.push(...options.extraDockerArgs ?? []);
|
|
543
|
+
dockerArgs.push(options.image, command, ...toolArgs);
|
|
544
|
+
let child;
|
|
545
|
+
try {
|
|
546
|
+
child = await runChildProcess({
|
|
547
|
+
command: docker,
|
|
548
|
+
args: dockerArgs,
|
|
549
|
+
env,
|
|
550
|
+
cwd: workdir,
|
|
551
|
+
stdinData: JSON.stringify({
|
|
552
|
+
tool: request.tool,
|
|
553
|
+
args: request.args,
|
|
554
|
+
idempotencyKey: request.ctx.idempotencyKey
|
|
555
|
+
}),
|
|
556
|
+
timeoutMs,
|
|
557
|
+
killGraceMs,
|
|
558
|
+
maxOutputBytes,
|
|
559
|
+
signal: request.ctx.signal
|
|
560
|
+
});
|
|
561
|
+
} catch (err) {
|
|
562
|
+
throw new ExecutorError("spawn", `container tool '${request.tool}' could not launch '${docker}': ${err instanceof Error ? err.message : String(err)}`);
|
|
563
|
+
}
|
|
564
|
+
exitCode = child.code;
|
|
565
|
+
signal = child.signal;
|
|
566
|
+
if (child.stopped && child.reason === "timeout") {
|
|
567
|
+
outcome = "timeout";
|
|
568
|
+
throw new ExecutorError("timeout", `container tool '${request.tool}' exceeded ${timeoutMs}ms and was killed`);
|
|
569
|
+
}
|
|
570
|
+
if (child.stopped && child.reason === "aborted") throw new ExecutorError("aborted", `container tool '${request.tool}' was cancelled`);
|
|
571
|
+
if (child.stopped && child.reason === "output-cap") throw new ExecutorError("output-cap", `container tool '${request.tool}' wrote more than ${maxOutputBytes} bytes and was killed`);
|
|
572
|
+
if (child.code !== 0) {
|
|
573
|
+
const tail = child.stderr.trim().slice(-500);
|
|
574
|
+
throw new ExecutorError("exit", `container tool '${request.tool}' exited ${child.code ?? "null"}${tail === "" ? "" : `: ${tail}`}`);
|
|
575
|
+
}
|
|
576
|
+
const result = parseToolResult(child.stdout, request.tool);
|
|
577
|
+
outcome = "ok";
|
|
578
|
+
settled = result;
|
|
579
|
+
} catch (thrown) {
|
|
580
|
+
bodyThrew = true;
|
|
581
|
+
thrownBody = thrown;
|
|
487
582
|
}
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
env[name] = value;
|
|
491
|
-
forwardNames.add(name);
|
|
492
|
-
}
|
|
493
|
-
env.RULVAR_TOOL = request.tool;
|
|
494
|
-
env.RULVAR_RUN_ID = request.ctx.runId;
|
|
495
|
-
env.RULVAR_IDEMPOTENCY_KEY = request.ctx.idempotencyKey;
|
|
496
|
-
const dockerArgs = [
|
|
497
|
-
"run",
|
|
498
|
-
"--rm",
|
|
499
|
-
"-i",
|
|
500
|
-
"--network",
|
|
501
|
-
network
|
|
502
|
-
];
|
|
503
|
-
dockerArgs.push("--memory", memory, "--cpus", cpus, "--pids-limit", String(pidsLimit));
|
|
504
|
-
if (readOnly) dockerArgs.push("--read-only");
|
|
505
|
-
for (const cap of capDrop) dockerArgs.push("--cap-drop", cap);
|
|
506
|
-
for (const name of forwardNames) dockerArgs.push("-e", name);
|
|
507
|
-
dockerArgs.push("-v", `${workdir}:${workMount}`, "-w", workMount);
|
|
508
|
-
dockerArgs.push(...options.extraDockerArgs ?? []);
|
|
509
|
-
dockerArgs.push(options.image, command, ...toolArgs);
|
|
510
|
-
let child;
|
|
583
|
+
const durationMs = now() - startedAt;
|
|
584
|
+
let ledgerFailure;
|
|
511
585
|
try {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
signal
|
|
586
|
+
if (options.ledger !== void 0) await options.ledger.record({
|
|
587
|
+
idempotencyKey: request.ctx.idempotencyKey,
|
|
588
|
+
runId: request.ctx.runId,
|
|
589
|
+
spanId: request.ctx.spanId,
|
|
590
|
+
tool: request.tool,
|
|
591
|
+
argsHash,
|
|
592
|
+
executor: request.executor,
|
|
593
|
+
workdir,
|
|
594
|
+
startedAt,
|
|
595
|
+
attemptId,
|
|
596
|
+
durationMs,
|
|
597
|
+
outcome,
|
|
598
|
+
exitCode,
|
|
599
|
+
signal
|
|
526
600
|
});
|
|
527
|
-
} catch (
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
exitCode = child.code;
|
|
531
|
-
signal = child.signal;
|
|
532
|
-
if (child.stopped && child.reason === "timeout") {
|
|
533
|
-
outcome = "timeout";
|
|
534
|
-
throw new ExecutorError("timeout", `container tool '${request.tool}' exceeded ${timeoutMs}ms and was killed`);
|
|
535
|
-
}
|
|
536
|
-
if (child.stopped && child.reason === "aborted") throw new ExecutorError("aborted", `container tool '${request.tool}' was cancelled`);
|
|
537
|
-
if (child.stopped && child.reason === "output-cap") throw new ExecutorError("output-cap", `container tool '${request.tool}' wrote more than ${maxOutputBytes} bytes and was killed`);
|
|
538
|
-
if (child.code !== 0) {
|
|
539
|
-
const tail = child.stderr.trim().slice(-500);
|
|
540
|
-
throw new ExecutorError("exit", `container tool '${request.tool}' exited ${child.code ?? "null"}${tail === "" ? "" : `: ${tail}`}`);
|
|
601
|
+
} catch (recordErr) {
|
|
602
|
+
ledgerFailure = new ExecutorError("ledger", `container tool '${request.tool}' outcome was not recorded: the ledger record write failed (${recordErr instanceof Error ? recordErr.message : String(recordErr)})` + (bodyThrew ? `; the dispatch itself had already failed (${thrownBody instanceof Error ? thrownBody.message : String(thrownBody)})` : ""));
|
|
541
603
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
} catch (thrown) {
|
|
546
|
-
bodyThrew = true;
|
|
547
|
-
thrownBody = thrown;
|
|
548
|
-
}
|
|
549
|
-
const durationMs = now() - startedAt;
|
|
550
|
-
let ledgerFailure;
|
|
551
|
-
try {
|
|
552
|
-
if (options.ledger !== void 0) await options.ledger.record({
|
|
553
|
-
idempotencyKey: request.ctx.idempotencyKey,
|
|
554
|
-
runId: request.ctx.runId,
|
|
555
|
-
spanId: request.ctx.spanId,
|
|
556
|
-
tool: request.tool,
|
|
557
|
-
argsHash,
|
|
558
|
-
executor: request.executor,
|
|
559
|
-
workdir,
|
|
560
|
-
startedAt,
|
|
561
|
-
attemptId,
|
|
562
|
-
durationMs,
|
|
563
|
-
outcome,
|
|
564
|
-
exitCode,
|
|
565
|
-
signal
|
|
604
|
+
await rm(workdir, {
|
|
605
|
+
recursive: true,
|
|
606
|
+
force: true
|
|
566
607
|
});
|
|
567
|
-
|
|
568
|
-
|
|
608
|
+
if (ledgerFailure !== void 0) throw ledgerFailure;
|
|
609
|
+
if (bodyThrew) throw thrownBody;
|
|
610
|
+
return settled;
|
|
569
611
|
}
|
|
570
|
-
|
|
571
|
-
recursive: true,
|
|
572
|
-
force: true
|
|
573
|
-
});
|
|
574
|
-
if (ledgerFailure !== void 0) throw ledgerFailure;
|
|
575
|
-
if (bodyThrew) throw thrownBody;
|
|
576
|
-
return settled;
|
|
577
|
-
} };
|
|
612
|
+
};
|
|
578
613
|
}
|
|
579
614
|
//#endregion
|
|
580
615
|
//#region src/ledger.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/executor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.247.0",
|
|
4
4
|
"description": "Rulvar isolated tool executors: reference ToolExecutorProvider adapters that run tool work out of process (subprocess and container) so hostile or model-generated scripts cannot reach host capabilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"access": "public"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@rulvar/core": "1.
|
|
25
|
+
"@rulvar/core": "1.247.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^22.20.1",
|
|
29
29
|
"tsdown": "^0.22.14",
|
|
30
30
|
"typescript": "~6.0.3",
|
|
31
|
-
"@rulvar/testing": "1.
|
|
31
|
+
"@rulvar/testing": "1.247.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|