@sema-agent/core 1.317.0 → 1.319.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/brain/anthropic.js +5 -5
- package/dist/brain/anthropic.js.map +1 -1
- package/dist/brain/circuit-breaker.d.ts.map +1 -1
- package/dist/brain/circuit-breaker.js +4 -2
- package/dist/brain/circuit-breaker.js.map +1 -1
- package/dist/brain/errors.d.ts +3 -1
- package/dist/brain/errors.d.ts.map +1 -1
- package/dist/brain/errors.js +3 -1
- package/dist/brain/errors.js.map +1 -1
- package/dist/brain/openai.js +5 -5
- package/dist/brain/openai.js.map +1 -1
- package/dist/core/exec-output-tail.d.ts +1 -0
- package/dist/core/exec-output-tail.d.ts.map +1 -1
- package/dist/core/exec-output-tail.js +4 -0
- package/dist/core/exec-output-tail.js.map +1 -1
- package/dist/core/tool-errors.d.ts.map +1 -1
- package/dist/core/tool-errors.js +3 -0
- package/dist/core/tool-errors.js.map +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +1 -0
- package/dist/engine/execution-env/node-execution-env.d.ts.map +1 -1
- package/dist/engine/execution-env/node-execution-env.js +269 -53
- package/dist/engine/execution-env/node-execution-env.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { closeSync, constants, createReadStream, mkdtempSync, openSync, readSync, statSync } from "node:fs";
|
|
3
|
+
import { closeSync, constants, createReadStream, mkdtempSync, openSync, readSync, statSync, truncateSync, unlinkSync } from "node:fs";
|
|
4
4
|
import { access, appendFile, lstat, mkdir, mkdtemp, readdir, readFile, readlink, realpath, rm, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { isAbsolute, join, resolve } from "node:path";
|
|
@@ -203,14 +203,19 @@ function getShellEnv(inheritEnv, baseEnv, extraEnv) {
|
|
|
203
203
|
...extraEnv,
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
|
+
const EXIT_SETTLE_DRAIN_MS = 500;
|
|
207
|
+
const EXEC_SPOOL_PUMP_MS = 200;
|
|
208
|
+
const EXEC_SPOOL_ROTATE_BYTES = 8 * 1024 * 1024;
|
|
209
|
+
const EXEC_SPOOL_FINAL_TAIL_BYTES = 8 * 1024 * 1024;
|
|
210
|
+
const EXEC_SPOOL_HARD_CAP_BYTES = 16 * 1024 * 1024;
|
|
206
211
|
const SIGNUM = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGABRT: 6, SIGKILL: 9, SIGSEGV: 11, SIGPIPE: 13, SIGTERM: 15 };
|
|
207
212
|
export function openSpoolPair(base) {
|
|
208
213
|
const outPath = `${base}.out.log`;
|
|
209
214
|
const errPath = `${base}.err.log`;
|
|
210
|
-
const fdOut = openSync(outPath, "
|
|
215
|
+
const fdOut = openSync(outPath, "ax", 0o600);
|
|
211
216
|
let fdErr;
|
|
212
217
|
try {
|
|
213
|
-
fdErr = openSync(errPath, "
|
|
218
|
+
fdErr = openSync(errPath, "ax", 0o600);
|
|
214
219
|
}
|
|
215
220
|
catch (e) {
|
|
216
221
|
try {
|
|
@@ -297,6 +302,10 @@ export class NodeExecutionEnv {
|
|
|
297
302
|
let child;
|
|
298
303
|
const timeoutRef = {};
|
|
299
304
|
const forceSettleRef = {};
|
|
305
|
+
const spoolPumpRef = {};
|
|
306
|
+
const execSpoolRef = {};
|
|
307
|
+
const pumpSpoolRef = {};
|
|
308
|
+
const drainSpoolRef = {};
|
|
300
309
|
const armForceSettle = (result) => {
|
|
301
310
|
if (forceSettleRef.current || settled)
|
|
302
311
|
return;
|
|
@@ -312,6 +321,7 @@ export class NodeExecutionEnv {
|
|
|
312
321
|
forceSettleRef.current.unref?.();
|
|
313
322
|
};
|
|
314
323
|
const partialOutput = () => {
|
|
324
|
+
drainSpoolRef.current?.();
|
|
315
325
|
const so = stdoutTail.result();
|
|
316
326
|
const se = stderrTail.result();
|
|
317
327
|
return { stdout: markTruncated(so.text, so.droppedBytes), stderr: markTruncated(se.text, se.droppedBytes) };
|
|
@@ -343,22 +353,62 @@ export class NodeExecutionEnv {
|
|
|
343
353
|
return;
|
|
344
354
|
}
|
|
345
355
|
settled = true;
|
|
356
|
+
if (spoolPumpRef.current)
|
|
357
|
+
clearInterval(spoolPumpRef.current);
|
|
358
|
+
if (execSpoolRef.current && !(result.ok && result.value.detached !== undefined)) {
|
|
359
|
+
for (const path of [execSpoolRef.current.outPath, execSpoolRef.current.errPath]) {
|
|
360
|
+
try {
|
|
361
|
+
unlinkSync(path);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
execSpoolRef.current = undefined;
|
|
367
|
+
}
|
|
346
368
|
resolvePromise(result);
|
|
347
369
|
};
|
|
370
|
+
let execSpoolFds;
|
|
371
|
+
if (process.platform !== "win32") {
|
|
372
|
+
try {
|
|
373
|
+
if (!this.bgSpoolDir)
|
|
374
|
+
this.bgSpoolDir = mkdtempSync(join(tmpdir(), "sema-bg-"));
|
|
375
|
+
const pair = openSpoolPair(join(this.bgSpoolDir, `fg_${randomUUID()}`));
|
|
376
|
+
execSpoolRef.current = { outPath: pair.outPath, errPath: pair.errPath };
|
|
377
|
+
execSpoolFds = pair.fds;
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
execSpoolRef.current = undefined;
|
|
381
|
+
execSpoolFds = undefined;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const closeSpoolFds = () => {
|
|
385
|
+
if (!execSpoolFds)
|
|
386
|
+
return;
|
|
387
|
+
for (const fd of execSpoolFds) {
|
|
388
|
+
try {
|
|
389
|
+
closeSync(fd);
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
execSpoolFds = undefined;
|
|
395
|
+
};
|
|
348
396
|
try {
|
|
349
397
|
child = spawn(shellConfig.value.shell, [...shellConfig.value.args, command], {
|
|
350
398
|
cwd,
|
|
351
399
|
detached: process.platform !== "win32",
|
|
352
400
|
env: getShellEnv(this.inheritEnv, this.shellEnv, options?.env),
|
|
353
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
401
|
+
stdio: execSpoolFds ? ["ignore", execSpoolFds[0], execSpoolFds[1]] : ["ignore", "pipe", "pipe"],
|
|
354
402
|
windowsHide: true,
|
|
355
403
|
});
|
|
356
404
|
}
|
|
357
405
|
catch (error) {
|
|
406
|
+
closeSpoolFds();
|
|
358
407
|
const cause = toError(error);
|
|
359
408
|
settle(err(new ExecutionError("spawn_error", cause.message, cause)));
|
|
360
409
|
return;
|
|
361
410
|
}
|
|
411
|
+
closeSpoolFds();
|
|
362
412
|
if (options?.onSpawn && child.pid !== undefined) {
|
|
363
413
|
const spawnedPid = child.pid;
|
|
364
414
|
const spawnedChild = child;
|
|
@@ -392,12 +442,116 @@ export class NodeExecutionEnv {
|
|
|
392
442
|
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
393
443
|
}
|
|
394
444
|
}
|
|
445
|
+
const spoolCursor = { out: 0, err: 0 };
|
|
446
|
+
const readSpoolIncrement = (path, cursor) => {
|
|
447
|
+
try {
|
|
448
|
+
const size = statSync(path).size;
|
|
449
|
+
if (size <= spoolCursor[cursor])
|
|
450
|
+
return undefined;
|
|
451
|
+
const fd = openSync(path, "r");
|
|
452
|
+
try {
|
|
453
|
+
const want = Math.min(size - spoolCursor[cursor], 8 * 1024 * 1024 + 4096);
|
|
454
|
+
const buf = Buffer.alloc(want);
|
|
455
|
+
const got = readSync(fd, buf, 0, want, spoolCursor[cursor]);
|
|
456
|
+
if (got <= 0)
|
|
457
|
+
return undefined;
|
|
458
|
+
spoolCursor[cursor] += got;
|
|
459
|
+
return buf.subarray(0, got);
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
closeSync(fd);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
const feed = (tail, cb, buf) => {
|
|
470
|
+
tail.push(buf);
|
|
471
|
+
if (cb === undefined || settled)
|
|
472
|
+
return;
|
|
473
|
+
try {
|
|
474
|
+
cb(buf.toString("utf8"));
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
const cause = toError(error);
|
|
478
|
+
callbackError = new ExecutionError("callback_error", cause.message, cause);
|
|
479
|
+
onAbort();
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
const spoolLanes = () => {
|
|
483
|
+
const sp = execSpoolRef.current;
|
|
484
|
+
if (sp === undefined)
|
|
485
|
+
return [];
|
|
486
|
+
return [
|
|
487
|
+
{ path: sp.outPath, cursor: "out", tail: stdoutTail, cb: options?.onStdout },
|
|
488
|
+
{ path: sp.errPath, cursor: "err", tail: stderrTail, cb: options?.onStderr },
|
|
489
|
+
];
|
|
490
|
+
};
|
|
491
|
+
const skipAhead = (lane, keep) => {
|
|
492
|
+
try {
|
|
493
|
+
const behind = statSync(lane.path).size - spoolCursor[lane.cursor];
|
|
494
|
+
if (behind <= keep)
|
|
495
|
+
return;
|
|
496
|
+
lane.tail.recordSkippedBytes(behind - keep);
|
|
497
|
+
spoolCursor[lane.cursor] += behind - keep;
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
const reclaimSpool = (lane) => {
|
|
503
|
+
if (spoolCursor[lane.cursor] < EXEC_SPOOL_ROTATE_BYTES)
|
|
504
|
+
return;
|
|
505
|
+
try {
|
|
506
|
+
const size = statSync(lane.path).size;
|
|
507
|
+
if (size === spoolCursor[lane.cursor]) {
|
|
508
|
+
truncateSync(lane.path, 0);
|
|
509
|
+
spoolCursor[lane.cursor] = 0;
|
|
510
|
+
}
|
|
511
|
+
else if (size >= EXEC_SPOOL_HARD_CAP_BYTES) {
|
|
512
|
+
lane.tail.recordSkippedBytes(size - spoolCursor[lane.cursor]);
|
|
513
|
+
truncateSync(lane.path, 0);
|
|
514
|
+
spoolCursor[lane.cursor] = 0;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const pumpSpool = () => {
|
|
521
|
+
for (const lane of spoolLanes()) {
|
|
522
|
+
skipAhead(lane, EXEC_SPOOL_ROTATE_BYTES);
|
|
523
|
+
const inc = readSpoolIncrement(lane.path, lane.cursor);
|
|
524
|
+
if (inc)
|
|
525
|
+
feed(lane.tail, lane.cb, inc);
|
|
526
|
+
reclaimSpool(lane);
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
pumpSpoolRef.current = pumpSpool;
|
|
530
|
+
const drainSpoolFinal = () => {
|
|
531
|
+
for (const lane of spoolLanes()) {
|
|
532
|
+
skipAhead(lane, EXEC_SPOOL_FINAL_TAIL_BYTES);
|
|
533
|
+
for (let i = 0; i < 4; i++) {
|
|
534
|
+
const inc = readSpoolIncrement(lane.path, lane.cursor);
|
|
535
|
+
if (!inc)
|
|
536
|
+
break;
|
|
537
|
+
feed(lane.tail, lane.cb, inc);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
drainSpoolRef.current = drainSpoolFinal;
|
|
542
|
+
if (execSpoolRef.current) {
|
|
543
|
+
spoolPumpRef.current = setInterval(pumpSpool, EXEC_SPOOL_PUMP_MS);
|
|
544
|
+
spoolPumpRef.current.unref?.();
|
|
545
|
+
}
|
|
395
546
|
const onDetach = () => {
|
|
396
547
|
if (settled || !child)
|
|
397
548
|
return;
|
|
398
|
-
|
|
549
|
+
pumpSpoolRef.current?.();
|
|
550
|
+
const shellId = this.adoptRunningChild(child, stdoutTail.result(), stderrTail.result(), execSpoolRef.current !== undefined ? { spool: execSpoolRef.current, cursors: { out: spoolCursor.out, err: spoolCursor.err } } : undefined);
|
|
399
551
|
if (shellId === undefined)
|
|
400
552
|
return;
|
|
553
|
+
if (spoolPumpRef.current)
|
|
554
|
+
clearInterval(spoolPumpRef.current);
|
|
401
555
|
try {
|
|
402
556
|
options?.onDetachAdopted?.();
|
|
403
557
|
}
|
|
@@ -405,7 +559,9 @@ export class NodeExecutionEnv {
|
|
|
405
559
|
}
|
|
406
560
|
if (timeoutRef.current)
|
|
407
561
|
clearTimeout(timeoutRef.current);
|
|
408
|
-
|
|
562
|
+
const dso = stdoutTail.result();
|
|
563
|
+
const dse = stderrTail.result();
|
|
564
|
+
settle(ok({ stdout: markTruncated(dso.text, dso.droppedBytes), stderr: markTruncated(dse.text, dse.droppedBytes), exitCode: 0, detached: { shellId } }));
|
|
409
565
|
};
|
|
410
566
|
if (options?.detachSignal) {
|
|
411
567
|
if (options.detachSignal.aborted)
|
|
@@ -415,30 +571,32 @@ export class NodeExecutionEnv {
|
|
|
415
571
|
options.detachSignal.addEventListener("abort", dl, { once: true });
|
|
416
572
|
}
|
|
417
573
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
574
|
+
if (!execSpoolRef.current) {
|
|
575
|
+
child.stdout?.setEncoding("utf8");
|
|
576
|
+
child.stderr?.setEncoding("utf8");
|
|
577
|
+
child.stdout?.on("data", (chunk) => {
|
|
578
|
+
stdoutTail.push(Buffer.from(chunk, "utf8"));
|
|
579
|
+
try {
|
|
580
|
+
options?.onStdout?.(chunk);
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
const cause = toError(error);
|
|
584
|
+
callbackError = new ExecutionError("callback_error", cause.message, cause);
|
|
585
|
+
onAbort();
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
child.stderr?.on("data", (chunk) => {
|
|
589
|
+
stderrTail.push(Buffer.from(chunk, "utf8"));
|
|
590
|
+
try {
|
|
591
|
+
options?.onStderr?.(chunk);
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
const cause = toError(error);
|
|
595
|
+
callbackError = new ExecutionError("callback_error", cause.message, cause);
|
|
596
|
+
onAbort();
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
442
600
|
child.stdout?.on("error", () => { });
|
|
443
601
|
child.stderr?.on("error", () => { });
|
|
444
602
|
child.on("error", (error) => {
|
|
@@ -448,7 +606,8 @@ export class NodeExecutionEnv {
|
|
|
448
606
|
}
|
|
449
607
|
settle(err(new ExecutionError("spawn_error", error.message, error)));
|
|
450
608
|
});
|
|
451
|
-
|
|
609
|
+
const settleFromTermination = (code, signal) => {
|
|
610
|
+
drainSpoolFinal();
|
|
452
611
|
if (callbackError) {
|
|
453
612
|
const p = partialOutput();
|
|
454
613
|
callbackError.partialStdout = p.stdout;
|
|
@@ -476,6 +635,16 @@ export class NodeExecutionEnv {
|
|
|
476
635
|
return;
|
|
477
636
|
}
|
|
478
637
|
settle(ok({ stdout: markTruncated(so.text, so.droppedBytes), stderr: markTruncated(se.text, se.droppedBytes), exitCode: code ?? 0 }));
|
|
638
|
+
};
|
|
639
|
+
child.on("close", (code, signal) => settleFromTermination(code, signal));
|
|
640
|
+
child.on("exit", (code, signal) => {
|
|
641
|
+
if (execSpoolRef.current === undefined)
|
|
642
|
+
return;
|
|
643
|
+
const drain = setTimeout(() => {
|
|
644
|
+
settleFromTermination(code, signal);
|
|
645
|
+
}, EXIT_SETTLE_DRAIN_MS);
|
|
646
|
+
drain.unref?.();
|
|
647
|
+
child.once("close", () => clearTimeout(drain));
|
|
479
648
|
});
|
|
480
649
|
});
|
|
481
650
|
}
|
|
@@ -714,7 +883,7 @@ export class NodeExecutionEnv {
|
|
|
714
883
|
return err(toFileError(error, filePath));
|
|
715
884
|
}
|
|
716
885
|
}
|
|
717
|
-
adoptRunningChild(child, stdoutSoFar, stderrSoFar) {
|
|
886
|
+
adoptRunningChild(child, stdoutSoFar, stderrSoFar, handover) {
|
|
718
887
|
const liveCount = [...this.bgShells.values()].filter((e) => e.status === "running").length;
|
|
719
888
|
if (liveCount >= this.backgroundCapabilities.maxConcurrent)
|
|
720
889
|
return undefined;
|
|
@@ -724,13 +893,18 @@ export class NodeExecutionEnv {
|
|
|
724
893
|
stdout: { tail: new RollingTailBuffer(), totalBytes: 0, cursorBytes: 0 },
|
|
725
894
|
stderr: { tail: new RollingTailBuffer(), totalBytes: 0, cursorBytes: 0 },
|
|
726
895
|
status: "running",
|
|
896
|
+
...(handover !== undefined
|
|
897
|
+
? { spool: { outPath: handover.spool.outPath, errPath: handover.spool.errPath, fileCursor: { ...handover.cursors }, ephemeral: true } }
|
|
898
|
+
: {}),
|
|
727
899
|
};
|
|
728
|
-
const seedOut = Buffer.from(stdoutSoFar, "utf8");
|
|
729
|
-
const seedErr = Buffer.from(stderrSoFar, "utf8");
|
|
900
|
+
const seedOut = Buffer.from(stdoutSoFar.text, "utf8");
|
|
901
|
+
const seedErr = Buffer.from(stderrSoFar.text, "utf8");
|
|
902
|
+
entry.stdout.tail.recordSkippedBytes(stdoutSoFar.droppedBytes);
|
|
903
|
+
entry.stderr.tail.recordSkippedBytes(stderrSoFar.droppedBytes);
|
|
730
904
|
entry.stdout.tail.push(seedOut);
|
|
731
|
-
entry.stdout.totalBytes += seedOut.length;
|
|
732
905
|
entry.stderr.tail.push(seedErr);
|
|
733
|
-
entry.
|
|
906
|
+
entry.stdout.totalBytes += stdoutSoFar.droppedBytes + seedOut.length;
|
|
907
|
+
entry.stderr.totalBytes += stderrSoFar.droppedBytes + seedErr.length;
|
|
734
908
|
child.stdout?.on("data", (chunk) => {
|
|
735
909
|
const buf = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
736
910
|
entry.stdout.tail.push(buf);
|
|
@@ -750,6 +924,7 @@ export class NodeExecutionEnv {
|
|
|
750
924
|
child.on("close", (code, signal) => {
|
|
751
925
|
if (entry.timer)
|
|
752
926
|
clearTimeout(entry.timer);
|
|
927
|
+
this.reclaimAdoptedSpool(entry);
|
|
753
928
|
if (entry.status === "killed")
|
|
754
929
|
return;
|
|
755
930
|
entry.status = "exited";
|
|
@@ -758,6 +933,7 @@ export class NodeExecutionEnv {
|
|
|
758
933
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
759
934
|
entry.status = "exited";
|
|
760
935
|
entry.exitCode = child.exitCode ?? (child.signalCode ? 128 + (SIGNUM[child.signalCode] ?? 9) : 0);
|
|
936
|
+
this.reclaimAdoptedSpool(entry);
|
|
761
937
|
}
|
|
762
938
|
else {
|
|
763
939
|
const timeoutSec = this.backgroundCapabilities.defaultBgTimeoutSec;
|
|
@@ -789,7 +965,7 @@ export class NodeExecutionEnv {
|
|
|
789
965
|
if (!this.bgSpoolDir)
|
|
790
966
|
this.bgSpoolDir = mkdtempSync(join(tmpdir(), "sema-bg-"));
|
|
791
967
|
const pair = openSpoolPair(join(this.bgSpoolDir, `bg_${this.bgCounter + 1}_${randomUUID()}`));
|
|
792
|
-
spool = { outPath: pair.outPath, errPath: pair.errPath };
|
|
968
|
+
spool = { outPath: pair.outPath, errPath: pair.errPath, fileCursor: { out: 0, err: 0 } };
|
|
793
969
|
spoolFds = pair.fds;
|
|
794
970
|
}
|
|
795
971
|
catch {
|
|
@@ -922,35 +1098,74 @@ export class NodeExecutionEnv {
|
|
|
922
1098
|
return ok(undefined);
|
|
923
1099
|
}
|
|
924
1100
|
syncSpool(entry) {
|
|
925
|
-
|
|
1101
|
+
const spool = entry.spool;
|
|
1102
|
+
if (!spool)
|
|
926
1103
|
return;
|
|
927
1104
|
const streams = [
|
|
928
|
-
[entry.stdout,
|
|
929
|
-
[entry.stderr,
|
|
1105
|
+
[entry.stdout, spool.outPath, "out"],
|
|
1106
|
+
[entry.stderr, spool.errPath, "err"],
|
|
930
1107
|
];
|
|
931
|
-
for (const [stream, path] of streams) {
|
|
1108
|
+
for (const [stream, path, lane] of streams) {
|
|
932
1109
|
try {
|
|
933
1110
|
const size = statSync(path).size;
|
|
934
|
-
if (
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (got > 0) {
|
|
942
|
-
stream.tail.push(buf.subarray(0, got));
|
|
943
|
-
stream.totalBytes += got;
|
|
1111
|
+
if (spool.ephemeral) {
|
|
1112
|
+
const behind = size - spool.fileCursor[lane];
|
|
1113
|
+
if (behind > EXEC_SPOOL_ROTATE_BYTES) {
|
|
1114
|
+
const skipped = behind - EXEC_SPOOL_ROTATE_BYTES;
|
|
1115
|
+
stream.tail.recordSkippedBytes(skipped);
|
|
1116
|
+
stream.totalBytes += skipped;
|
|
1117
|
+
spool.fileCursor[lane] += skipped;
|
|
944
1118
|
}
|
|
945
1119
|
}
|
|
946
|
-
|
|
947
|
-
|
|
1120
|
+
if (size > spool.fileCursor[lane]) {
|
|
1121
|
+
const fd = openSync(path, "r");
|
|
1122
|
+
try {
|
|
1123
|
+
const want = Math.min(size - spool.fileCursor[lane], 8 * 1024 * 1024 + 4096);
|
|
1124
|
+
const buf = Buffer.alloc(want);
|
|
1125
|
+
const got = readSync(fd, buf, 0, want, spool.fileCursor[lane]);
|
|
1126
|
+
if (got > 0) {
|
|
1127
|
+
stream.tail.push(buf.subarray(0, got));
|
|
1128
|
+
stream.totalBytes += got;
|
|
1129
|
+
spool.fileCursor[lane] += got;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
finally {
|
|
1133
|
+
closeSync(fd);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (spool.ephemeral && spool.fileCursor[lane] >= EXEC_SPOOL_ROTATE_BYTES) {
|
|
1137
|
+
const size2 = statSync(path).size;
|
|
1138
|
+
if (size2 === spool.fileCursor[lane]) {
|
|
1139
|
+
truncateSync(path, 0);
|
|
1140
|
+
spool.fileCursor[lane] = 0;
|
|
1141
|
+
}
|
|
1142
|
+
else if (size2 >= EXEC_SPOOL_HARD_CAP_BYTES) {
|
|
1143
|
+
const skipped = size2 - spool.fileCursor[lane];
|
|
1144
|
+
stream.tail.recordSkippedBytes(skipped);
|
|
1145
|
+
stream.totalBytes += skipped;
|
|
1146
|
+
truncateSync(path, 0);
|
|
1147
|
+
spool.fileCursor[lane] = 0;
|
|
1148
|
+
}
|
|
948
1149
|
}
|
|
949
1150
|
}
|
|
950
1151
|
catch {
|
|
951
1152
|
}
|
|
952
1153
|
}
|
|
953
1154
|
}
|
|
1155
|
+
reclaimAdoptedSpool(entry) {
|
|
1156
|
+
const spool = entry.spool;
|
|
1157
|
+
if (!spool?.ephemeral)
|
|
1158
|
+
return;
|
|
1159
|
+
this.syncSpool(entry);
|
|
1160
|
+
entry.spool = undefined;
|
|
1161
|
+
for (const path of [spool.outPath, spool.errPath]) {
|
|
1162
|
+
try {
|
|
1163
|
+
unlinkSync(path);
|
|
1164
|
+
}
|
|
1165
|
+
catch {
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
954
1169
|
async disposeBackgroundShells(opts) {
|
|
955
1170
|
if (this.retainBackgroundProcesses)
|
|
956
1171
|
return;
|
|
@@ -965,6 +1180,7 @@ export class NodeExecutionEnv {
|
|
|
965
1180
|
entry.status = "killed";
|
|
966
1181
|
killProcessTree(entry.child.pid, { force: true });
|
|
967
1182
|
}
|
|
1183
|
+
this.reclaimAdoptedSpool(entry);
|
|
968
1184
|
}
|
|
969
1185
|
catch {
|
|
970
1186
|
}
|