@sema-agent/server 1.225.0 → 1.227.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/http/server.d.ts +1 -0
- package/dist/http/server.d.ts.map +1 -1
- package/dist/http/server.js +7 -0
- package/dist/http/server.js.map +1 -1
- package/dist/main.js +2 -1
- package/dist/main.js.map +1 -1
- package/dist/plugins/background-shell-support.d.ts +1 -0
- package/dist/plugins/background-shell-support.d.ts.map +1 -1
- package/dist/plugins/background-shell-support.js +3 -0
- package/dist/plugins/background-shell-support.js.map +1 -1
- package/dist/plugins/remote-env-host.d.ts +6 -0
- package/dist/plugins/remote-env-host.d.ts.map +1 -1
- package/dist/plugins/remote-env-host.js +373 -7
- package/dist/plugins/remote-env-host.js.map +1 -1
- package/dist/spec-fields.d.ts +1 -0
- package/dist/spec-fields.d.ts.map +1 -1
- package/dist/spec-fields.js +3 -0
- package/dist/spec-fields.js.map +1 -1
- package/package.json +2 -2
|
@@ -27,6 +27,8 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
27
27
|
private readonly cfg;
|
|
28
28
|
private prepared;
|
|
29
29
|
private destroyed;
|
|
30
|
+
private spoolRootDir?;
|
|
31
|
+
private ensureSpoolRoot;
|
|
30
32
|
private readonly persistent;
|
|
31
33
|
private preparing?;
|
|
32
34
|
private readonly handle;
|
|
@@ -90,6 +92,10 @@ export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, Sched
|
|
|
90
92
|
shellId: string;
|
|
91
93
|
};
|
|
92
94
|
}, ExecutionError>>;
|
|
95
|
+
private execViaPipes;
|
|
96
|
+
private openFgSpool;
|
|
97
|
+
private execViaSpool;
|
|
98
|
+
private adoptRunningExecSpool;
|
|
93
99
|
execStream(command: string, options?: ExecStreamOptions): AsyncIterable<OutputChunk>;
|
|
94
100
|
absolutePath(p: string): Promise<Result<string, FileError>>;
|
|
95
101
|
joinPath(parts: string[]): Promise<Result<string, FileError>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-env-host.d.ts","sourceRoot":"","sources":["../../src/plugins/remote-env-host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"remote-env-host.d.ts","sourceRoot":"","sources":["../../src/plugins/remote-env-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,8 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { openSync, closeSync, readSync, statSync, truncateSync, unlinkSync, mkdirSync, rmdirSync, mkdtempSync } from "node:fs";
|
|
7
|
+
import { StringDecoder } from "node:string_decoder";
|
|
6
8
|
import { armPipeDestroyGrace } from "./remote-shell.js";
|
|
7
9
|
import { resolveHostShell, hostShell, spawnGroupOptions, killTreeHard, killTreeSoft, collapseWin32EnvKeys } from "./host-platform.js";
|
|
8
10
|
import { BackgroundShellManager, seedMemStream, feedMemStream, drainMemStream } from "./background-shell-support.js";
|
|
@@ -53,6 +55,12 @@ export class RemoteHostExecutionEnv {
|
|
|
53
55
|
cfg;
|
|
54
56
|
prepared = false;
|
|
55
57
|
destroyed = false;
|
|
58
|
+
spoolRootDir;
|
|
59
|
+
ensureSpoolRoot() {
|
|
60
|
+
if (!this.spoolRootDir)
|
|
61
|
+
this.spoolRootDir = mkdtempSync(path.join(os.tmpdir(), "sema-host-spool-"));
|
|
62
|
+
return this.spoolRootDir;
|
|
63
|
+
}
|
|
56
64
|
persistent;
|
|
57
65
|
preparing;
|
|
58
66
|
handle;
|
|
@@ -119,6 +127,13 @@ export class RemoteHostExecutionEnv {
|
|
|
119
127
|
await p?.catch(() => { });
|
|
120
128
|
}
|
|
121
129
|
finally {
|
|
130
|
+
if (this.spoolRootDir && !this.bgManager.hasShells()) {
|
|
131
|
+
try {
|
|
132
|
+
await fs.rm(this.spoolRootDir, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
this.spoolRootDir = undefined;
|
|
136
|
+
}
|
|
122
137
|
if (this.persistent)
|
|
123
138
|
return;
|
|
124
139
|
try {
|
|
@@ -151,7 +166,7 @@ export class RemoteHostExecutionEnv {
|
|
|
151
166
|
const ready = await self.shellReady();
|
|
152
167
|
if (!ready.ok)
|
|
153
168
|
throw new BackgroundShellError("spawn_failed", ready.error.message);
|
|
154
|
-
const dir = path.join(self.
|
|
169
|
+
const dir = path.join(self.ensureSpoolRoot(), "bg-" + randomBytes(8).toString("hex"));
|
|
155
170
|
await fs.mkdir(dir, { recursive: true });
|
|
156
171
|
const outPath = path.join(dir, "out");
|
|
157
172
|
const errPath = path.join(dir, "err");
|
|
@@ -198,7 +213,7 @@ export class RemoteHostExecutionEnv {
|
|
|
198
213
|
bytesDroppedBeforeCursor: o.dropped + e.dropped,
|
|
199
214
|
};
|
|
200
215
|
}
|
|
201
|
-
const readForward = async (p, cursor) => {
|
|
216
|
+
const readForward = async (p, cursor, dec) => {
|
|
202
217
|
let fh;
|
|
203
218
|
try {
|
|
204
219
|
fh = await fs.open(p, "r");
|
|
@@ -213,14 +228,16 @@ export class RemoteHostExecutionEnv {
|
|
|
213
228
|
const n = Math.min(size - cursor, HOST_BG_READ_CAP);
|
|
214
229
|
const buf = Buffer.alloc(n);
|
|
215
230
|
await fh.read(buf, 0, n, cursor);
|
|
216
|
-
return { chunk:
|
|
231
|
+
return { chunk: dec.write(buf), cursor: cursor + n, size };
|
|
217
232
|
}
|
|
218
233
|
finally {
|
|
219
234
|
await fh.close().catch(() => { });
|
|
220
235
|
}
|
|
221
236
|
};
|
|
222
|
-
|
|
223
|
-
|
|
237
|
+
state.decOut ??= new StringDecoder("utf8");
|
|
238
|
+
state.decErr ??= new StringDecoder("utf8");
|
|
239
|
+
const o = await readForward(state.outPath, state.stdoutCursor, state.decOut);
|
|
240
|
+
const e = await readForward(state.errPath, state.stderrCursor, state.decErr);
|
|
224
241
|
state.stdoutCursor = o.cursor;
|
|
225
242
|
state.stderrCursor = e.cursor;
|
|
226
243
|
let terminal;
|
|
@@ -231,11 +248,14 @@ export class RemoteHostExecutionEnv {
|
|
|
231
248
|
catch { }
|
|
232
249
|
terminal = { failed: true };
|
|
233
250
|
}
|
|
251
|
+
const preBase = state.preSpoolBase ?? 0;
|
|
252
|
+
const pending = state.pendingDropped ?? 0;
|
|
253
|
+
state.pendingDropped = 0;
|
|
234
254
|
return {
|
|
235
255
|
stdout: o.chunk,
|
|
236
256
|
stderr: e.chunk,
|
|
237
|
-
bytesFromStart: o.size + e.size,
|
|
238
|
-
bytesDroppedBeforeCursor:
|
|
257
|
+
bytesFromStart: o.size + e.size + preBase,
|
|
258
|
+
bytesDroppedBeforeCursor: pending,
|
|
239
259
|
...(terminal ? { terminal } : {}),
|
|
240
260
|
};
|
|
241
261
|
},
|
|
@@ -310,6 +330,14 @@ export class RemoteHostExecutionEnv {
|
|
|
310
330
|
const cwd = this.resolve(options?.cwd ?? this.cwd);
|
|
311
331
|
const timeoutMs = options?.timeout != null ? options.timeout * 1000 : this.cfg.commandTimeoutMs;
|
|
312
332
|
const detachSignal = options?.detachSignal;
|
|
333
|
+
if (process.platform !== "win32" && process.env.HOST_EXEC_SPOOL_DISABLED !== "true") {
|
|
334
|
+
const spool = this.openFgSpool();
|
|
335
|
+
if (spool)
|
|
336
|
+
return this.execViaSpool(command, options, cwd, timeoutMs, detachSignal, spool);
|
|
337
|
+
}
|
|
338
|
+
return this.execViaPipes(command, options, cwd, timeoutMs, detachSignal);
|
|
339
|
+
}
|
|
340
|
+
execViaPipes(command, options, cwd, timeoutMs, detachSignal) {
|
|
313
341
|
return new Promise((resolve) => {
|
|
314
342
|
const outBuf = new RollingTailBuffer();
|
|
315
343
|
const errBuf = new RollingTailBuffer();
|
|
@@ -322,6 +350,25 @@ export class RemoteHostExecutionEnv {
|
|
|
322
350
|
env: this.mergeEnv(options?.env),
|
|
323
351
|
...spawnGroupOptions(),
|
|
324
352
|
});
|
|
353
|
+
if (options?.onSpawn && child.pid != null) {
|
|
354
|
+
const spawnedPid = child.pid;
|
|
355
|
+
try {
|
|
356
|
+
options.onSpawn({ pid: spawnedPid, kill: () => {
|
|
357
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
358
|
+
return;
|
|
359
|
+
try {
|
|
360
|
+
killTreeHard(spawnedPid);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
try {
|
|
364
|
+
child.kill("SIGKILL");
|
|
365
|
+
}
|
|
366
|
+
catch { }
|
|
367
|
+
}
|
|
368
|
+
} });
|
|
369
|
+
}
|
|
370
|
+
catch { }
|
|
371
|
+
}
|
|
325
372
|
const finish = (r) => {
|
|
326
373
|
if (settled)
|
|
327
374
|
return;
|
|
@@ -375,6 +422,10 @@ export class RemoteHostExecutionEnv {
|
|
|
375
422
|
clearTimeout(forceSettleTimer);
|
|
376
423
|
options?.abortSignal?.removeEventListener("abort", onAbort);
|
|
377
424
|
settled = true;
|
|
425
|
+
try {
|
|
426
|
+
options?.onDetachAdopted?.();
|
|
427
|
+
}
|
|
428
|
+
catch { }
|
|
378
429
|
resolve(ok({ stdout: seedOut, stderr: seedErr, exitCode: 0, detached: { shellId } }));
|
|
379
430
|
};
|
|
380
431
|
const onExecOut = (d) => { outBuf.push(d); if (tee)
|
|
@@ -404,6 +455,321 @@ export class RemoteHostExecutionEnv {
|
|
|
404
455
|
});
|
|
405
456
|
});
|
|
406
457
|
}
|
|
458
|
+
openFgSpool() {
|
|
459
|
+
try {
|
|
460
|
+
const dir = path.join(this.ensureSpoolRoot(), "fg-" + randomBytes(8).toString("hex"));
|
|
461
|
+
mkdirSync(dir, { recursive: true });
|
|
462
|
+
const outPath = path.join(dir, "out");
|
|
463
|
+
const errPath = path.join(dir, "err");
|
|
464
|
+
const fdOut = openSync(outPath, "ax", 0o600);
|
|
465
|
+
let fdErr;
|
|
466
|
+
try {
|
|
467
|
+
fdErr = openSync(errPath, "ax", 0o600);
|
|
468
|
+
}
|
|
469
|
+
catch (e) {
|
|
470
|
+
try {
|
|
471
|
+
closeSync(fdOut);
|
|
472
|
+
}
|
|
473
|
+
catch { }
|
|
474
|
+
throw e;
|
|
475
|
+
}
|
|
476
|
+
return { dir, outPath, errPath, fds: [fdOut, fdErr] };
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
execViaSpool(command, options, cwd, timeoutMs, detachSignal, spool) {
|
|
483
|
+
const EXIT_SETTLE_DRAIN_MS = 500;
|
|
484
|
+
const PUMP_MS = 200;
|
|
485
|
+
const ROTATE_BYTES = 8 * 1024 * 1024;
|
|
486
|
+
const FINAL_TAIL_BYTES = 8 * 1024 * 1024;
|
|
487
|
+
const HARD_CAP_BYTES = 16 * 1024 * 1024;
|
|
488
|
+
return new Promise((resolve) => {
|
|
489
|
+
const outBuf = new RollingTailBuffer();
|
|
490
|
+
const errBuf = new RollingTailBuffer();
|
|
491
|
+
let settled = false;
|
|
492
|
+
let handedOver = false;
|
|
493
|
+
let pump;
|
|
494
|
+
let drainTimer;
|
|
495
|
+
const cursor = { out: 0, err: 0 };
|
|
496
|
+
const spoolRemoved = { out: 0, err: 0 };
|
|
497
|
+
const lanes = [
|
|
498
|
+
{ path: spool.outPath, key: "out", buf: outBuf, cb: options?.onStdout },
|
|
499
|
+
{ path: spool.errPath, key: "err", buf: errBuf, cb: options?.onStderr },
|
|
500
|
+
];
|
|
501
|
+
const readIncrement = (lanePath, key) => {
|
|
502
|
+
try {
|
|
503
|
+
const size = statSync(lanePath).size;
|
|
504
|
+
if (size <= cursor[key])
|
|
505
|
+
return undefined;
|
|
506
|
+
const fd = openSync(lanePath, "r");
|
|
507
|
+
try {
|
|
508
|
+
const want = Math.min(size - cursor[key], ROTATE_BYTES + 4096);
|
|
509
|
+
const buf = Buffer.alloc(want);
|
|
510
|
+
const got = readSync(fd, buf, 0, want, cursor[key]);
|
|
511
|
+
if (got <= 0)
|
|
512
|
+
return undefined;
|
|
513
|
+
cursor[key] += got;
|
|
514
|
+
return buf.subarray(0, got);
|
|
515
|
+
}
|
|
516
|
+
finally {
|
|
517
|
+
closeSync(fd);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
catch {
|
|
521
|
+
return undefined;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const skipAhead = (lane, keep) => {
|
|
525
|
+
try {
|
|
526
|
+
const behind = statSync(lane.path).size - cursor[lane.key];
|
|
527
|
+
if (behind <= keep)
|
|
528
|
+
return;
|
|
529
|
+
const skipped = behind - keep;
|
|
530
|
+
lane.buf.recordSkippedBytes(skipped);
|
|
531
|
+
cursor[lane.key] += skipped;
|
|
532
|
+
}
|
|
533
|
+
catch { }
|
|
534
|
+
};
|
|
535
|
+
const reclaim = (lane) => {
|
|
536
|
+
if (cursor[lane.key] < ROTATE_BYTES)
|
|
537
|
+
return;
|
|
538
|
+
try {
|
|
539
|
+
const size = statSync(lane.path).size;
|
|
540
|
+
if (size === cursor[lane.key] || size >= HARD_CAP_BYTES) {
|
|
541
|
+
const size2 = statSync(lane.path).size;
|
|
542
|
+
truncateSync(lane.path, 0);
|
|
543
|
+
if (size2 > cursor[lane.key])
|
|
544
|
+
lane.buf.recordSkippedBytes(size2 - cursor[lane.key]);
|
|
545
|
+
spoolRemoved[lane.key] += size2;
|
|
546
|
+
cursor[lane.key] = 0;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch { }
|
|
550
|
+
};
|
|
551
|
+
const feed = (lane, inc) => {
|
|
552
|
+
lane.buf.push(inc);
|
|
553
|
+
if (!settled) {
|
|
554
|
+
try {
|
|
555
|
+
lane.cb?.(inc.toString("utf8"));
|
|
556
|
+
}
|
|
557
|
+
catch { }
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
const pumpOnce = () => {
|
|
561
|
+
for (const lane of lanes) {
|
|
562
|
+
skipAhead(lane, ROTATE_BYTES);
|
|
563
|
+
const inc = readIncrement(lane.path, lane.key);
|
|
564
|
+
if (inc)
|
|
565
|
+
feed(lane, inc);
|
|
566
|
+
reclaim(lane);
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const drainFinal = () => {
|
|
570
|
+
for (const lane of lanes) {
|
|
571
|
+
skipAhead(lane, FINAL_TAIL_BYTES);
|
|
572
|
+
for (let i = 0; i < 4; i++) {
|
|
573
|
+
const inc = readIncrement(lane.path, lane.key);
|
|
574
|
+
if (!inc)
|
|
575
|
+
break;
|
|
576
|
+
feed(lane, inc);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
const cleanupSpool = () => {
|
|
581
|
+
if (pump) {
|
|
582
|
+
clearInterval(pump);
|
|
583
|
+
pump = undefined;
|
|
584
|
+
}
|
|
585
|
+
if (drainTimer) {
|
|
586
|
+
clearTimeout(drainTimer);
|
|
587
|
+
drainTimer = undefined;
|
|
588
|
+
}
|
|
589
|
+
if (!handedOver) {
|
|
590
|
+
for (const lane of lanes) {
|
|
591
|
+
try {
|
|
592
|
+
unlinkSync(lane.path);
|
|
593
|
+
}
|
|
594
|
+
catch { }
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
rmdirSync(spool.dir);
|
|
598
|
+
}
|
|
599
|
+
catch { }
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
let removeDetachListener;
|
|
603
|
+
const finish = (r) => {
|
|
604
|
+
if (settled)
|
|
605
|
+
return;
|
|
606
|
+
settled = true;
|
|
607
|
+
clearTimeout(timer);
|
|
608
|
+
options?.abortSignal?.removeEventListener("abort", onAbort);
|
|
609
|
+
removeDetachListener?.();
|
|
610
|
+
cleanupSpool();
|
|
611
|
+
resolve(r);
|
|
612
|
+
};
|
|
613
|
+
const closeSpoolFds = () => {
|
|
614
|
+
for (const fd of spool.fds) {
|
|
615
|
+
try {
|
|
616
|
+
closeSync(fd);
|
|
617
|
+
}
|
|
618
|
+
catch { }
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
const sh = hostShell();
|
|
622
|
+
let child;
|
|
623
|
+
try {
|
|
624
|
+
child = spawn(sh.shell, [...sh.args, command], {
|
|
625
|
+
cwd,
|
|
626
|
+
env: this.mergeEnv(options?.env),
|
|
627
|
+
...spawnGroupOptions(),
|
|
628
|
+
stdio: ["ignore", spool.fds[0], spool.fds[1]],
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
catch (e) {
|
|
632
|
+
closeSpoolFds();
|
|
633
|
+
cleanupSpool();
|
|
634
|
+
throw e;
|
|
635
|
+
}
|
|
636
|
+
closeSpoolFds();
|
|
637
|
+
if (options?.onSpawn && child.pid != null) {
|
|
638
|
+
const spawnedPid = child.pid;
|
|
639
|
+
try {
|
|
640
|
+
options.onSpawn({ pid: spawnedPid, kill: () => {
|
|
641
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
642
|
+
return;
|
|
643
|
+
try {
|
|
644
|
+
killTreeHard(spawnedPid);
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
try {
|
|
648
|
+
child.kill("SIGKILL");
|
|
649
|
+
}
|
|
650
|
+
catch { }
|
|
651
|
+
}
|
|
652
|
+
} });
|
|
653
|
+
}
|
|
654
|
+
catch { }
|
|
655
|
+
}
|
|
656
|
+
child.on("error", (e) => {
|
|
657
|
+
finish({ ok: false, error: new ExecutionError("spawn_error", `host spawn failed: ${e.message}`) });
|
|
658
|
+
});
|
|
659
|
+
const partial = () => {
|
|
660
|
+
pumpOnce();
|
|
661
|
+
const o = outBuf.result();
|
|
662
|
+
const e = errBuf.result();
|
|
663
|
+
return { stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes) };
|
|
664
|
+
};
|
|
665
|
+
let exited = false;
|
|
666
|
+
const timer = setTimeout(() => {
|
|
667
|
+
if (exited)
|
|
668
|
+
return;
|
|
669
|
+
try {
|
|
670
|
+
if (child.pid != null)
|
|
671
|
+
killTreeHard(child.pid);
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
try {
|
|
675
|
+
child.kill("SIGKILL");
|
|
676
|
+
}
|
|
677
|
+
catch { }
|
|
678
|
+
}
|
|
679
|
+
const p = partial();
|
|
680
|
+
finish({ ok: false, error: new ExecutionError("timeout", `host exec exceeded ${timeoutMs}ms; partial stdout: ${p.stdout.slice(-2048)}`) });
|
|
681
|
+
}, timeoutMs);
|
|
682
|
+
const onAbort = () => {
|
|
683
|
+
if (exited)
|
|
684
|
+
return;
|
|
685
|
+
try {
|
|
686
|
+
if (child.pid != null)
|
|
687
|
+
killTreeHard(child.pid);
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
try {
|
|
691
|
+
child.kill("SIGKILL");
|
|
692
|
+
}
|
|
693
|
+
catch { }
|
|
694
|
+
}
|
|
695
|
+
finish({ ok: false, error: new ExecutionError("aborted", "host exec aborted") });
|
|
696
|
+
};
|
|
697
|
+
if (options?.abortSignal?.aborted) {
|
|
698
|
+
onAbort();
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (options?.abortSignal)
|
|
702
|
+
options.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
703
|
+
pump = setInterval(pumpOnce, PUMP_MS);
|
|
704
|
+
pump.unref?.();
|
|
705
|
+
const onDetach = () => {
|
|
706
|
+
if (settled || exited)
|
|
707
|
+
return;
|
|
708
|
+
pumpOnce();
|
|
709
|
+
const fgDropped = spoolRemoved.out + spoolRemoved.err;
|
|
710
|
+
const shellId = this.adoptRunningExecSpool(child, spool, { out: 0, err: 0 }, fgDropped);
|
|
711
|
+
if (shellId === undefined)
|
|
712
|
+
return;
|
|
713
|
+
handedOver = true;
|
|
714
|
+
try {
|
|
715
|
+
options?.onDetachAdopted?.();
|
|
716
|
+
}
|
|
717
|
+
catch { }
|
|
718
|
+
const o = outBuf.result();
|
|
719
|
+
const e = errBuf.result();
|
|
720
|
+
finish(ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode: 0, detached: { shellId } }));
|
|
721
|
+
};
|
|
722
|
+
if (detachSignal) {
|
|
723
|
+
if (detachSignal.aborted)
|
|
724
|
+
onDetach();
|
|
725
|
+
else {
|
|
726
|
+
detachSignal.addEventListener("abort", onDetach, { once: true });
|
|
727
|
+
removeDetachListener = () => detachSignal.removeEventListener("abort", onDetach);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const settleFromTermination = (code, signal) => {
|
|
731
|
+
if (settled)
|
|
732
|
+
return;
|
|
733
|
+
drainFinal();
|
|
734
|
+
const o = outBuf.result();
|
|
735
|
+
const e = errBuf.result();
|
|
736
|
+
const exitCode = code ?? (signal ? 128 + (signalNumber(signal) ?? 0) : 1);
|
|
737
|
+
finish(ok({ stdout: markTruncated(o.text, o.droppedBytes), stderr: markTruncated(e.text, e.droppedBytes), exitCode }));
|
|
738
|
+
};
|
|
739
|
+
child.on("close", (code, signal) => {
|
|
740
|
+
if (drainTimer) {
|
|
741
|
+
clearTimeout(drainTimer);
|
|
742
|
+
drainTimer = undefined;
|
|
743
|
+
}
|
|
744
|
+
settleFromTermination(code, signal);
|
|
745
|
+
});
|
|
746
|
+
child.on("exit", (code, signal) => {
|
|
747
|
+
exited = true;
|
|
748
|
+
drainTimer = setTimeout(() => settleFromTermination(code, signal), EXIT_SETTLE_DRAIN_MS);
|
|
749
|
+
drainTimer.unref?.();
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
adoptRunningExecSpool(child, spool, cursors, fgRemovedBytes = 0) {
|
|
754
|
+
const pid = child.pid;
|
|
755
|
+
if (pid == null)
|
|
756
|
+
return undefined;
|
|
757
|
+
const adopted = this.bgManager.adoptSync((ctx) => {
|
|
758
|
+
const onClose = (code, signal) => ctx.onTerminal(false, code ?? (signal ? 128 + (signalNumber(signal) ?? 9) : 0));
|
|
759
|
+
const onErrEvt = () => ctx.onTerminal(true);
|
|
760
|
+
child.on("close", onClose);
|
|
761
|
+
child.on("error", onErrEvt);
|
|
762
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
763
|
+
ctx.onTerminal(false, child.exitCode ?? (child.signalCode ? 128 + (signalNumber(child.signalCode) ?? 9) : 0));
|
|
764
|
+
child.unref?.();
|
|
765
|
+
const detachCleanup = () => {
|
|
766
|
+
child.off("close", onClose);
|
|
767
|
+
child.off("error", onErrEvt);
|
|
768
|
+
};
|
|
769
|
+
return { pid, dir: spool.dir, outPath: spool.outPath, errPath: spool.errPath, stdoutCursor: cursors.out, stderrCursor: cursors.err, detachCleanup, ...(fgRemovedBytes > 0 ? { preSpoolBase: fgRemovedBytes, pendingDropped: fgRemovedBytes } : {}) };
|
|
770
|
+
});
|
|
771
|
+
return adopted?.shellId;
|
|
772
|
+
}
|
|
407
773
|
async *execStream(command, options) {
|
|
408
774
|
const ready = await this.shellReady();
|
|
409
775
|
if (!ready.ok)
|