@anvia/sandbox 0.3.7 → 0.4.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.
package/dist/index.js CHANGED
@@ -1,5 +1,15 @@
1
+ // src/capabilities.ts
2
+ function isSandboxPortSession(session) {
3
+ const candidate = session;
4
+ return Array.isArray(candidate.publishedPorts) && typeof candidate.waitForPort === "function";
5
+ }
6
+ function isSandboxProcessSession(session) {
7
+ const candidate = session;
8
+ return typeof candidate.startProcess === "function" && typeof candidate.listProcesses === "function" && typeof candidate.readProcessLogs === "function" && typeof candidate.stopProcess === "function";
9
+ }
10
+
1
11
  // src/docker-sandbox.ts
2
- import { randomUUID } from "crypto";
12
+ import { randomUUID as randomUUID2 } from "crypto";
3
13
  import { mkdtemp, rm, writeFile } from "fs/promises";
4
14
  import os from "os";
5
15
  import path2 from "path";
@@ -35,6 +45,10 @@ var SandboxFileSizeError = class extends SandboxError {
35
45
  };
36
46
  var SandboxToolPolicyError = class extends SandboxError {
37
47
  };
48
+ var SandboxPortError = class extends SandboxError {
49
+ };
50
+ var SandboxProcessError = class extends SandboxError {
51
+ };
38
52
 
39
53
  // src/docker-cli.ts
40
54
  var defaultMaxOutputBytes = 1024 * 1024;
@@ -138,6 +152,10 @@ function createOutputCollector(maxBytes, onChunk) {
138
152
  };
139
153
  }
140
154
 
155
+ // src/docker-process.ts
156
+ import { spawn as spawn2 } from "child_process";
157
+ import { randomUUID } from "crypto";
158
+
141
159
  // src/path.ts
142
160
  import path from "path";
143
161
  function normalizeSandboxPath(input, options = {}) {
@@ -172,11 +190,499 @@ function parentSandboxPath(relativePath) {
172
190
  return parent === "." ? "." : parent;
173
191
  }
174
192
 
193
+ // src/docker-process.ts
194
+ var processMarkerPrefix = "ANVIA_PROCESS";
195
+ var processWrapper = [
196
+ 'marker="$1"',
197
+ "shift",
198
+ "if command -v setsid >/dev/null 2>&1; then",
199
+ ' setsid "$@" &',
200
+ " child=$!",
201
+ " group=$child",
202
+ "else",
203
+ ' "$@" &',
204
+ " child=$!",
205
+ " group=$$",
206
+ "fi",
207
+ `printf '\\036%s:%s:%s:%s\\036' "$marker" "$$" "$child" "$group"`,
208
+ "terminate() {",
209
+ ' wait "$child" 2>/dev/null || true',
210
+ " exit 143",
211
+ "}",
212
+ "trap terminate TERM INT",
213
+ 'wait "$child"',
214
+ "exit $?"
215
+ ].join("\n");
216
+ var DockerProcessManager = class {
217
+ constructor(options) {
218
+ this.options = options;
219
+ if (!Number.isInteger(options.maxProcesses) || options.maxProcesses < 0) {
220
+ throw new SandboxProcessError("Sandbox maxProcesses must be a non-negative integer.");
221
+ }
222
+ if (!Number.isInteger(options.maxOutputBytes) || options.maxOutputBytes < 0) {
223
+ throw new SandboxProcessError("Sandbox maxOutputBytes must be a non-negative integer.");
224
+ }
225
+ if (!Number.isInteger(options.startupTimeoutMs) || options.startupTimeoutMs <= 0) {
226
+ throw new SandboxProcessError("Sandbox process startup timeout must be a positive integer.");
227
+ }
228
+ }
229
+ options;
230
+ records = /* @__PURE__ */ new Map();
231
+ disposed = false;
232
+ async start(options) {
233
+ this.assertActive();
234
+ assertStartOptions(options);
235
+ await this.pruneCompletedRecords();
236
+ const trackedCount = this.records.size;
237
+ if (trackedCount >= this.options.maxProcesses) {
238
+ throw new SandboxProcessError(
239
+ `Sandbox process limit reached (${trackedCount} >= ${this.options.maxProcesses}).`
240
+ );
241
+ }
242
+ const id = randomUUID();
243
+ const marker = `${processMarkerPrefix}:${id}`;
244
+ const dockerArgs = this.createExecArgs(options, marker);
245
+ const child = spawn2(this.options.dockerPath, dockerArgs, {
246
+ stdio: ["pipe", "pipe", "pipe"]
247
+ });
248
+ child.stdin.end();
249
+ let resolveStarted;
250
+ let rejectStarted;
251
+ const started = new Promise((resolve, reject) => {
252
+ resolveStarted = resolve;
253
+ rejectStarted = reject;
254
+ });
255
+ let resolveClosed;
256
+ const closed = new Promise((resolve) => {
257
+ resolveClosed = resolve;
258
+ });
259
+ const info = {
260
+ id,
261
+ command: options.command,
262
+ args: [...options.args ?? []],
263
+ status: "running",
264
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
265
+ };
266
+ if (options.cwd !== void 0) info.cwd = options.cwd;
267
+ const record = {
268
+ info,
269
+ startedAtMs: Date.now(),
270
+ child,
271
+ stdout: new TailOutputCollector(this.options.maxOutputBytes),
272
+ stderr: new TailOutputCollector(this.options.maxOutputBytes),
273
+ markerStart: Buffer.from(`${marker}:`),
274
+ markerBuffer: Buffer.alloc(0),
275
+ spawnFailed: false,
276
+ stopRequested: false,
277
+ startResolved: false,
278
+ exitNotified: false,
279
+ resolveStarted,
280
+ rejectStarted,
281
+ started,
282
+ resolveClosed,
283
+ closed
284
+ };
285
+ this.records.set(id, record);
286
+ this.observe(record);
287
+ try {
288
+ await this.waitForStart(record);
289
+ await this.options.onStart?.(copyProcessInfo(record.info));
290
+ record.startResolved = true;
291
+ if (record.info.status !== "running") this.notifyExit(record);
292
+ return copyProcessInfo(record.info);
293
+ } catch (error) {
294
+ if (await this.cleanupFailedStart(record)) this.records.delete(id);
295
+ throw error;
296
+ }
297
+ }
298
+ list() {
299
+ this.assertActive();
300
+ return [...this.records.values()].map((record) => copyProcessInfo(record.info));
301
+ }
302
+ logs(processId, options = {}) {
303
+ this.assertActive();
304
+ const record = this.getRecord(processId);
305
+ const stdout = record.stdout.snapshot(options.tailBytes);
306
+ const stderr = record.stderr.snapshot(options.tailBytes);
307
+ return {
308
+ stdout: stdout.text,
309
+ stderr: stderr.text,
310
+ stdoutTruncated: stdout.truncated,
311
+ stderrTruncated: stderr.truncated
312
+ };
313
+ }
314
+ async stop(processId, options = {}) {
315
+ this.assertActive();
316
+ const record = this.getRecord(processId);
317
+ const gracePeriodMs = options.gracePeriodMs ?? 5e3;
318
+ if (!Number.isInteger(gracePeriodMs) || gracePeriodMs < 0) {
319
+ throw new SandboxProcessError("Process gracePeriodMs must be a non-negative integer.");
320
+ }
321
+ try {
322
+ if (!await this.terminateRecord(record, gracePeriodMs)) {
323
+ throw new SandboxProcessError(`Sandbox process did not stop: ${processId}`);
324
+ }
325
+ } catch (error) {
326
+ if (record.info.status === "running") record.stopRequested = false;
327
+ throw error;
328
+ }
329
+ return copyProcessInfo(record.info);
330
+ }
331
+ async dispose() {
332
+ if (this.disposed) return;
333
+ this.disposed = true;
334
+ await Promise.all(
335
+ [...this.records.values()].map(async (record) => {
336
+ await this.terminateRecord(record, 1e3).catch(() => false);
337
+ })
338
+ );
339
+ }
340
+ createExecArgs(options, marker) {
341
+ const args = ["exec", "-w", containerPath(this.options.workdir, options.cwd ?? ".")];
342
+ for (const [key, value] of Object.entries({ ...this.options.env, ...options.env })) {
343
+ args.push("-e", `${key}=${value}`);
344
+ }
345
+ args.push(
346
+ this.options.containerName,
347
+ "sh",
348
+ "-c",
349
+ processWrapper,
350
+ "anvia-managed-process",
351
+ marker,
352
+ options.command,
353
+ ...options.args ?? []
354
+ );
355
+ return args;
356
+ }
357
+ observe(record) {
358
+ record.child.stdout.on("data", (chunk) => this.acceptStdout(record, chunk));
359
+ record.child.stderr.on("data", (chunk) => record.stderr.accept(chunk));
360
+ record.child.on("error", (error) => {
361
+ record.spawnFailed = true;
362
+ const normalized = error.code === "ENOENT" ? new SandboxDockerUnavailableError("Docker CLI was not found.", error) : error;
363
+ record.rejectStarted(normalized);
364
+ });
365
+ record.child.on("close", (code) => {
366
+ if (record.markerBuffer.length > 0) {
367
+ record.stdout.accept(record.markerBuffer);
368
+ record.markerBuffer = Buffer.alloc(0);
369
+ }
370
+ record.info.status = record.stopRequested ? "stopped" : "exited";
371
+ record.info.exitCode = code ?? 1;
372
+ record.info.endedAt = (/* @__PURE__ */ new Date()).toISOString();
373
+ record.rejectStarted(
374
+ new SandboxProcessError(
375
+ `Sandbox process exited before startup completed: ${record.info.id}`
376
+ )
377
+ );
378
+ record.resolveClosed();
379
+ if (record.startResolved) this.notifyExit(record);
380
+ });
381
+ }
382
+ acceptStdout(record, chunk) {
383
+ if (record.supervisorPid !== void 0) {
384
+ record.stdout.accept(chunk);
385
+ return;
386
+ }
387
+ record.markerBuffer = Buffer.concat([record.markerBuffer, chunk]);
388
+ const start = record.markerBuffer.indexOf(record.markerStart);
389
+ if (start < 0) {
390
+ const retainedBytes = Math.max(0, record.markerStart.length - 1);
391
+ if (record.markerBuffer.length > retainedBytes) {
392
+ const split = record.markerBuffer.length - retainedBytes;
393
+ record.stdout.accept(record.markerBuffer.subarray(0, split));
394
+ record.markerBuffer = record.markerBuffer.subarray(split);
395
+ }
396
+ return;
397
+ }
398
+ const end = record.markerBuffer.indexOf(30, start + record.markerStart.length);
399
+ if (end < 0) {
400
+ if (start > 0) {
401
+ record.stdout.accept(record.markerBuffer.subarray(0, start));
402
+ record.markerBuffer = record.markerBuffer.subarray(start);
403
+ }
404
+ return;
405
+ }
406
+ const rawPids = record.markerBuffer.subarray(start + record.markerStart.length, end).toString("utf8").split(":");
407
+ const supervisorPid = Number(rawPids[0]);
408
+ const childPid = Number(rawPids[1]);
409
+ const processGroupId = Number(rawPids[2]);
410
+ if (!isProcessId(supervisorPid) || !isProcessId(childPid) || !isProcessId(processGroupId)) {
411
+ record.rejectStarted(
412
+ new SandboxProcessError("Sandbox process returned invalid process IDs.")
413
+ );
414
+ return;
415
+ }
416
+ if (start > 0) record.stdout.accept(record.markerBuffer.subarray(0, start));
417
+ if (end + 1 < record.markerBuffer.length) {
418
+ record.stdout.accept(record.markerBuffer.subarray(end + 1));
419
+ }
420
+ record.markerBuffer = Buffer.alloc(0);
421
+ record.supervisorPid = supervisorPid;
422
+ record.childPid = childPid;
423
+ record.processGroupId = processGroupId;
424
+ record.resolveStarted();
425
+ }
426
+ async waitForStart(record) {
427
+ let timeout;
428
+ const timeoutPromise = new Promise((_, reject) => {
429
+ timeout = setTimeout(() => {
430
+ reject(new SandboxTimeoutError("Starting sandbox process timed out."));
431
+ }, this.options.startupTimeoutMs);
432
+ timeout.unref?.();
433
+ });
434
+ try {
435
+ await Promise.race([record.started, timeoutPromise]);
436
+ } finally {
437
+ if (timeout !== void 0) clearTimeout(timeout);
438
+ }
439
+ }
440
+ async cleanupFailedStart(record) {
441
+ record.stopRequested = true;
442
+ if (record.processGroupId === void 0 && record.info.status === "running") {
443
+ await waitForPromise(record.closed, 250);
444
+ }
445
+ try {
446
+ return await this.terminateRecord(record, 1e3);
447
+ } catch {
448
+ return false;
449
+ }
450
+ }
451
+ async terminateRecord(record, gracePeriodMs) {
452
+ record.stopRequested = true;
453
+ if (record.processGroupId === void 0) {
454
+ if (record.info.status === "running") {
455
+ await waitForPromise(record.closed, Math.min(gracePeriodMs, 250));
456
+ }
457
+ if (record.processGroupId === void 0) {
458
+ return record.spawnFailed && record.info.status !== "running";
459
+ }
460
+ }
461
+ if (!await this.isProcessGroupRunning(record)) {
462
+ return this.finishAfterGroupExit(record);
463
+ }
464
+ await this.signal(record, "TERM");
465
+ if (await this.waitForRecordExit(record, gracePeriodMs)) return true;
466
+ await this.signal(record, "KILL");
467
+ if (await this.waitForRecordExit(record, 1e3)) return true;
468
+ return false;
469
+ }
470
+ async waitForRecordExit(record, timeoutMs) {
471
+ const deadline = Date.now() + timeoutMs;
472
+ while (true) {
473
+ if (!await this.isProcessGroupRunning(record)) {
474
+ return this.finishAfterGroupExit(record);
475
+ }
476
+ const remainingMs = deadline - Date.now();
477
+ if (remainingMs <= 0) return false;
478
+ const intervalMs = Math.min(50, remainingMs);
479
+ if (record.info.status === "running") {
480
+ await waitForPromise(record.closed, intervalMs);
481
+ } else {
482
+ await waitForDelay(intervalMs);
483
+ }
484
+ }
485
+ }
486
+ async finishAfterGroupExit(record) {
487
+ if (record.info.status !== "running") return true;
488
+ record.child.kill("SIGKILL");
489
+ return waitForPromise(record.closed, 1e3);
490
+ }
491
+ async isProcessGroupRunning(record) {
492
+ if (record.processGroupId === void 0) return false;
493
+ const result = await runDockerCli(
494
+ [
495
+ "exec",
496
+ this.options.containerName,
497
+ "sh",
498
+ "-c",
499
+ 'kill -0 "-$1" 2>/dev/null',
500
+ "anvia-process-group-check",
501
+ String(record.processGroupId)
502
+ ],
503
+ {
504
+ dockerPath: this.options.dockerPath,
505
+ timeoutMs: 5e3,
506
+ maxOutputBytes: this.options.maxOutputBytes
507
+ }
508
+ );
509
+ return result.exitCode === 0;
510
+ }
511
+ async signal(record, signal) {
512
+ if (record.processGroupId === void 0) return;
513
+ const result = await runDockerCli(
514
+ [
515
+ "exec",
516
+ this.options.containerName,
517
+ "sh",
518
+ "-c",
519
+ 'kill "-$2" "-$1" 2>/dev/null || ! kill -0 "-$1" 2>/dev/null',
520
+ "anvia-process-signal",
521
+ String(record.processGroupId),
522
+ signal
523
+ ],
524
+ {
525
+ dockerPath: this.options.dockerPath,
526
+ timeoutMs: 5e3,
527
+ maxOutputBytes: this.options.maxOutputBytes
528
+ }
529
+ );
530
+ if (result.exitCode !== 0) {
531
+ throw new SandboxDockerCommandError(
532
+ `Unable to stop sandbox process: ${record.info.id}`,
533
+ result
534
+ );
535
+ }
536
+ }
537
+ getRecord(processId) {
538
+ const record = this.records.get(processId);
539
+ if (record === void 0) {
540
+ throw new SandboxProcessError(`Unknown sandbox process: ${processId}`);
541
+ }
542
+ return record;
543
+ }
544
+ async pruneCompletedRecords() {
545
+ for (const [id, record] of this.records) {
546
+ if (this.records.size < this.options.maxProcesses) return;
547
+ const cleanupConfirmed = record.processGroupId === void 0 ? record.spawnFailed : !await this.isProcessGroupRunning(record);
548
+ if (record.info.status !== "running" && cleanupConfirmed) {
549
+ this.records.delete(id);
550
+ }
551
+ }
552
+ }
553
+ logsUnsafe(record) {
554
+ const stdout = record.stdout.snapshot();
555
+ const stderr = record.stderr.snapshot();
556
+ return {
557
+ stdout: stdout.text,
558
+ stderr: stderr.text,
559
+ stdoutTruncated: stdout.truncated,
560
+ stderrTruncated: stderr.truncated
561
+ };
562
+ }
563
+ notifyExit(record) {
564
+ if (record.exitNotified) return;
565
+ record.exitNotified = true;
566
+ const durationMs = Date.now() - record.startedAtMs;
567
+ const notify = async () => this.options.onExit?.(copyProcessInfo(record.info), this.logsUnsafe(record), durationMs);
568
+ void notify().catch(() => void 0);
569
+ }
570
+ assertActive() {
571
+ if (this.disposed) {
572
+ throw new SandboxProcessError("Sandbox process manager has been disposed.");
573
+ }
574
+ }
575
+ };
576
+ var TailOutputCollector = class {
577
+ constructor(maxBytes) {
578
+ this.maxBytes = maxBytes;
579
+ }
580
+ maxBytes;
581
+ chunks = [];
582
+ length = 0;
583
+ didTruncate = false;
584
+ accept(chunk) {
585
+ if (chunk.length === 0) return;
586
+ if (this.maxBytes <= 0) {
587
+ this.didTruncate = true;
588
+ return;
589
+ }
590
+ if (chunk.length >= this.maxBytes) {
591
+ this.chunks = [chunk.subarray(chunk.length - this.maxBytes)];
592
+ this.length = this.maxBytes;
593
+ this.didTruncate = true;
594
+ return;
595
+ }
596
+ this.chunks.push(chunk);
597
+ this.length += chunk.length;
598
+ while (this.length > this.maxBytes) {
599
+ const first = this.chunks[0];
600
+ if (first === void 0) break;
601
+ const overflow = this.length - this.maxBytes;
602
+ if (first.length <= overflow) {
603
+ this.chunks.shift();
604
+ this.length -= first.length;
605
+ } else {
606
+ this.chunks[0] = first.subarray(overflow);
607
+ this.length -= overflow;
608
+ }
609
+ this.didTruncate = true;
610
+ }
611
+ }
612
+ snapshot(tailBytes) {
613
+ if (tailBytes !== void 0 && (!Number.isInteger(tailBytes) || tailBytes < 0)) {
614
+ throw new SandboxProcessError("Process tailBytes must be a non-negative integer.");
615
+ }
616
+ const bytes = Buffer.concat(this.chunks, this.length);
617
+ const selected = tailBytes === 0 ? Buffer.alloc(0) : tailBytes === void 0 || bytes.length <= tailBytes ? bytes : bytes.subarray(bytes.length - tailBytes);
618
+ return {
619
+ text: selected.toString("utf8"),
620
+ truncated: this.didTruncate || selected.length < bytes.length
621
+ };
622
+ }
623
+ };
624
+ function assertStartOptions(options) {
625
+ if (options.command.trim().length === 0) {
626
+ throw new SandboxProcessError("Sandbox process command cannot be empty.");
627
+ }
628
+ }
629
+ function isProcessId(value) {
630
+ return Number.isSafeInteger(value) && value > 0;
631
+ }
632
+ function copyProcessInfo(info) {
633
+ const copy = {
634
+ id: info.id,
635
+ command: info.command,
636
+ args: [...info.args],
637
+ status: info.status,
638
+ startedAt: info.startedAt
639
+ };
640
+ if (info.cwd !== void 0) copy.cwd = info.cwd;
641
+ if (info.exitCode !== void 0) copy.exitCode = info.exitCode;
642
+ if (info.endedAt !== void 0) copy.endedAt = info.endedAt;
643
+ return copy;
644
+ }
645
+ async function waitForPromise(promise, timeoutMs) {
646
+ let timeout;
647
+ try {
648
+ return await Promise.race([
649
+ promise.then(() => true),
650
+ new Promise((resolve) => {
651
+ timeout = setTimeout(() => resolve(false), timeoutMs);
652
+ timeout.unref?.();
653
+ })
654
+ ]);
655
+ } finally {
656
+ if (timeout !== void 0) clearTimeout(timeout);
657
+ }
658
+ }
659
+ async function waitForDelay(timeoutMs) {
660
+ await new Promise((resolve) => {
661
+ const timeout = setTimeout(resolve, timeoutMs);
662
+ timeout.unref?.();
663
+ });
664
+ }
665
+
175
666
  // src/docker-sandbox.ts
176
667
  var defaultImage = "node:22-bookworm";
177
668
  var defaultWorkdir = "/workspace";
178
669
  var defaultTimeoutMs = 3e4;
179
670
  var defaultMaxOutputBytes2 = 1024 * 1024;
671
+ var defaultMaxProcesses = 4;
672
+ var portProbeScript = [
673
+ `port="$(printf '%04X' "$1")"`,
674
+ "for table in /proc/net/tcp /proc/net/tcp6; do",
675
+ ' [ -r "$table" ] || continue',
676
+ " while read -r _ local _ state _; do",
677
+ ' case "$local" in',
678
+ ' "00000000:$port"|"00000000000000000000000000000000:$port")',
679
+ ' [ "$state" = "0A" ] && exit 0',
680
+ " ;;",
681
+ " esac",
682
+ ' done < "$table"',
683
+ "done",
684
+ "exit 1"
685
+ ].join("\n");
180
686
  var DockerSandbox = class _DockerSandbox {
181
687
  provider = "docker";
182
688
  image;
@@ -226,8 +732,10 @@ var DockerSandbox = class _DockerSandbox {
226
732
  return new _DockerSandbox({ ...options, image: options.image ?? "denoland/deno:debian" });
227
733
  }
228
734
  async createSession(options = {}) {
735
+ const ports = validatePublishedPorts(options.ports ?? []);
736
+ this.assertPortNetworkCompatible(ports);
229
737
  await this.ensureImage();
230
- const id = sanitizeResourceId(options.id ?? randomUUID());
738
+ const id = sanitizeResourceId(options.id ?? randomUUID2());
231
739
  const workspace = options.workspace ?? this.workspace;
232
740
  const workspaceId = getWorkspaceId(workspace, id);
233
741
  const containerName = `anvia-sandbox-${id}`;
@@ -236,13 +744,14 @@ var DockerSandbox = class _DockerSandbox {
236
744
  await assertDockerCli(["volume", "create", volumeName], this.cliOptions());
237
745
  try {
238
746
  await assertDockerCli(
239
- this.createRunArgs(containerName, volumeName, workspace, options.metadata),
747
+ this.createRunArgs(containerName, volumeName, workspace, options.metadata, ports),
240
748
  {
241
749
  ...this.cliOptions(),
242
750
  timeoutMs: this.limits.timeoutMs ?? defaultTimeoutMs
243
751
  }
244
752
  );
245
- const session = new DockerSandboxSession({
753
+ const publishedPorts = await this.inspectPublishedPorts(containerName, ports);
754
+ const session = new DockerSandboxSessionImpl({
246
755
  id,
247
756
  containerName,
248
757
  volumeName,
@@ -252,7 +761,8 @@ var DockerSandbox = class _DockerSandbox {
252
761
  lifecycle: this.lifecycle,
253
762
  removeVolumeOnDestroy,
254
763
  env: options.manifest?.env ?? {},
255
- hooks: this.hooks
764
+ hooks: this.hooks,
765
+ publishedPorts
256
766
  });
257
767
  await session.applyManifest(options.manifest);
258
768
  await this.hooks.onSessionCreate?.(session.event());
@@ -274,7 +784,7 @@ var DockerSandbox = class _DockerSandbox {
274
784
  }
275
785
  }
276
786
  }
277
- createRunArgs(containerName, volumeName, workspace, metadata) {
787
+ createRunArgs(containerName, volumeName, workspace, metadata, ports) {
278
788
  const args = [
279
789
  "run",
280
790
  "-d",
@@ -300,6 +810,9 @@ var DockerSandbox = class _DockerSandbox {
300
810
  }
301
811
  }
302
812
  this.appendNetworkArgs(args);
813
+ for (const port of ports) {
814
+ args.push("--publish", `127.0.0.1::${port}/tcp`);
815
+ }
303
816
  this.appendLimitArgs(args);
304
817
  this.appendSecurityArgs(args);
305
818
  if (this.user !== void 0) {
@@ -323,6 +836,52 @@ var DockerSandbox = class _DockerSandbox {
323
836
  args.push("--network", mode);
324
837
  }
325
838
  }
839
+ assertPortNetworkCompatible(ports) {
840
+ if (ports.length === 0) return;
841
+ const mode = typeof this.network === "object" ? this.network.mode : this.network;
842
+ if (mode === false || mode === "none" || mode === "host" || typeof mode === "string" && mode.startsWith("container:")) {
843
+ throw new SandboxPortError(
844
+ "Published sandbox ports require network: true or a bridge-capable Docker network."
845
+ );
846
+ }
847
+ }
848
+ async inspectPublishedPorts(containerName, ports) {
849
+ if (ports.length === 0) return [];
850
+ const raw = await assertDockerCli(
851
+ ["container", "inspect", "--format", "{{json .NetworkSettings.Ports}}", containerName],
852
+ this.cliOptions()
853
+ );
854
+ let mappings;
855
+ try {
856
+ mappings = JSON.parse(raw);
857
+ } catch (error) {
858
+ throw new SandboxPortError("Docker returned invalid published port metadata.", error);
859
+ }
860
+ if (!isRecord(mappings)) {
861
+ throw new SandboxPortError("Docker returned invalid published port metadata.");
862
+ }
863
+ return ports.map((containerPort) => {
864
+ const entries = mappings[`${containerPort}/tcp`];
865
+ if (!Array.isArray(entries)) {
866
+ throw new SandboxPortError(`Docker did not publish sandbox port ${containerPort}/tcp.`);
867
+ }
868
+ const entry = entries.find(
869
+ (candidate) => isRecord(candidate) && candidate.HostIp === "127.0.0.1" && typeof candidate.HostPort === "string"
870
+ );
871
+ if (!isRecord(entry) || typeof entry.HostPort !== "string") {
872
+ throw new SandboxPortError(
873
+ `Docker did not bind sandbox port ${containerPort}/tcp to 127.0.0.1.`
874
+ );
875
+ }
876
+ const hostPort = Number(entry.HostPort);
877
+ if (!isValidPort(hostPort)) {
878
+ throw new SandboxPortError(
879
+ `Docker returned an invalid host port for ${containerPort}/tcp.`
880
+ );
881
+ }
882
+ return { containerPort, host: "127.0.0.1", hostPort, protocol: "tcp" };
883
+ });
884
+ }
326
885
  appendLimitArgs(args) {
327
886
  if (this.limits.memoryMb !== void 0) {
328
887
  args.push("--memory", `${this.limits.memoryMb}m`);
@@ -360,10 +919,11 @@ var DockerSandbox = class _DockerSandbox {
360
919
  }
361
920
  }
362
921
  };
363
- var DockerSandboxSession = class {
922
+ var DockerSandboxSessionImpl = class {
364
923
  provider = "docker";
365
924
  id;
366
925
  workdir;
926
+ publishedPorts;
367
927
  containerName;
368
928
  volumeName;
369
929
  dockerPath;
@@ -372,6 +932,7 @@ var DockerSandboxSession = class {
372
932
  removeVolumeOnDestroy;
373
933
  env;
374
934
  hooks;
935
+ processManager;
375
936
  ttlTimer;
376
937
  idleTimer;
377
938
  activeOperations = 0;
@@ -387,6 +948,44 @@ var DockerSandboxSession = class {
387
948
  this.removeVolumeOnDestroy = options.removeVolumeOnDestroy;
388
949
  this.env = options.env;
389
950
  this.hooks = options.hooks;
951
+ this.publishedPorts = options.publishedPorts;
952
+ this.processManager = new DockerProcessManager({
953
+ containerName: this.containerName,
954
+ dockerPath: this.dockerPath,
955
+ workdir: this.workdir,
956
+ env: this.env,
957
+ maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes2,
958
+ maxProcesses: this.limits.maxProcesses ?? defaultMaxProcesses,
959
+ startupTimeoutMs: this.limits.timeoutMs ?? defaultTimeoutMs,
960
+ onStart: async (process) => {
961
+ const event = {
962
+ ...this.event(),
963
+ command: process.command,
964
+ args: process.args
965
+ };
966
+ if (process.cwd !== void 0) event.cwd = process.cwd;
967
+ await this.hooks.onExecStart?.(event);
968
+ },
969
+ onExit: async (process, logs, durationMs) => {
970
+ const event = {
971
+ ...this.event(),
972
+ command: process.command,
973
+ args: process.args,
974
+ result: {
975
+ stdout: logs.stdout,
976
+ stderr: logs.stderr,
977
+ exitCode: process.exitCode ?? 1,
978
+ durationMs,
979
+ timedOut: false,
980
+ aborted: process.status === "stopped",
981
+ stdoutTruncated: logs.stdoutTruncated,
982
+ stderrTruncated: logs.stderrTruncated
983
+ }
984
+ };
985
+ if (process.cwd !== void 0) event.cwd = process.cwd;
986
+ await this.hooks.onExecEnd?.(event);
987
+ }
988
+ });
390
989
  this.startLifecycleTimers();
391
990
  }
392
991
  async applyManifest(manifest) {
@@ -468,6 +1067,49 @@ var DockerSandboxSession = class {
468
1067
  await run;
469
1068
  }
470
1069
  }
1070
+ async startProcess(options) {
1071
+ return this.runOperation(async () => this.processManager.start(options));
1072
+ }
1073
+ async listProcesses() {
1074
+ return this.runOperation(async () => this.processManager.list());
1075
+ }
1076
+ async readProcessLogs(processId, options) {
1077
+ return this.runOperation(async () => this.processManager.logs(processId, options));
1078
+ }
1079
+ async stopProcess(processId, options) {
1080
+ return this.runOperation(async () => this.processManager.stop(processId, options));
1081
+ }
1082
+ async waitForPort(containerPort, options = {}) {
1083
+ return this.runOperation(async () => {
1084
+ const publishedPort = this.publishedPorts.find(
1085
+ (candidate) => candidate.containerPort === containerPort
1086
+ );
1087
+ if (publishedPort === void 0) {
1088
+ throw new SandboxPortError(`Sandbox port is not published: ${containerPort}/tcp`);
1089
+ }
1090
+ const timeoutMs = options.timeoutMs ?? this.limits.timeoutMs ?? defaultTimeoutMs;
1091
+ const intervalMs = options.intervalMs ?? 250;
1092
+ assertWaitOptions(timeoutMs, intervalMs);
1093
+ const deadline = Date.now() + timeoutMs;
1094
+ while (true) {
1095
+ this.assertActive();
1096
+ if (options.signal?.aborted === true) throw abortReason(options.signal);
1097
+ const remainingMs = deadline - Date.now();
1098
+ if (remainingMs <= 0) {
1099
+ throw new SandboxTimeoutError(`Waiting for sandbox port ${containerPort}/tcp timed out.`);
1100
+ }
1101
+ const probeTimeoutMs = Math.min(1e3, remainingMs);
1102
+ if (await this.isPortListening(containerPort, probeTimeoutMs)) {
1103
+ return publishedPort;
1104
+ }
1105
+ const remainingAfterProbeMs = deadline - Date.now();
1106
+ if (remainingAfterProbeMs <= 0) {
1107
+ throw new SandboxTimeoutError(`Waiting for sandbox port ${containerPort}/tcp timed out.`);
1108
+ }
1109
+ await waitWithSignal(Math.min(intervalMs, remainingAfterProbeMs), options.signal);
1110
+ }
1111
+ });
1112
+ }
471
1113
  async readFile(filePath) {
472
1114
  return this.runOperation(async () => {
473
1115
  const normalized = normalizeSandboxPath(filePath);
@@ -537,6 +1179,7 @@ var DockerSandboxSession = class {
537
1179
  }
538
1180
  this.destroyed = true;
539
1181
  this.clearLifecycleTimers();
1182
+ await this.processManager.dispose();
540
1183
  await runDockerCli(["rm", "-f", this.containerName], this.cliOptions()).catch(() => void 0);
541
1184
  if (this.removeVolumeOnDestroy) {
542
1185
  await runDockerCli(["volume", "rm", "-f", this.volumeName], this.cliOptions()).catch(
@@ -578,6 +1221,24 @@ var DockerSandboxSession = class {
578
1221
  maxOutputBytes: this.limits.maxOutputBytes ?? defaultMaxOutputBytes2
579
1222
  };
580
1223
  }
1224
+ async isPortListening(containerPort, timeoutMs) {
1225
+ const result = await runDockerCli(
1226
+ [
1227
+ "exec",
1228
+ this.containerName,
1229
+ "sh",
1230
+ "-c",
1231
+ portProbeScript,
1232
+ "anvia-port-probe",
1233
+ String(containerPort)
1234
+ ],
1235
+ {
1236
+ ...this.cliOptions(),
1237
+ timeoutMs: Math.max(1, timeoutMs)
1238
+ }
1239
+ );
1240
+ return result.exitCode === 0;
1241
+ }
581
1242
  async execCommand(options) {
582
1243
  if (options.command.trim().length === 0) {
583
1244
  throw new SandboxDockerCommandError("Sandbox command cannot be empty.", {
@@ -697,7 +1358,7 @@ function shouldDestroyWorkspace(workspace) {
697
1358
  }
698
1359
  function sanitizeResourceId(id) {
699
1360
  const sanitized = id.toLowerCase().replaceAll(/[^a-z0-9_.-]/g, "-").replaceAll(/^-+|-+$/g, "");
700
- return sanitized.length > 0 ? sanitized : randomUUID();
1361
+ return sanitized.length > 0 ? sanitized : randomUUID2();
701
1362
  }
702
1363
  function byteLength(data) {
703
1364
  return typeof data === "string" ? Buffer.byteLength(data) : data.byteLength;
@@ -714,6 +1375,50 @@ function mapFindType(type) {
714
1375
  }
715
1376
  return "other";
716
1377
  }
1378
+ function validatePublishedPorts(ports) {
1379
+ const unique = /* @__PURE__ */ new Set();
1380
+ for (const port of ports) {
1381
+ if (!isValidPort(port)) {
1382
+ throw new SandboxPortError(`Sandbox port must be an integer from 1 to 65535: ${port}`);
1383
+ }
1384
+ if (unique.has(port)) {
1385
+ throw new SandboxPortError(`Sandbox port is duplicated: ${port}`);
1386
+ }
1387
+ unique.add(port);
1388
+ }
1389
+ return [...unique];
1390
+ }
1391
+ function isValidPort(port) {
1392
+ return Number.isInteger(port) && port >= 1 && port <= 65535;
1393
+ }
1394
+ function isRecord(value) {
1395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1396
+ }
1397
+ function assertWaitOptions(timeoutMs, intervalMs) {
1398
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
1399
+ throw new SandboxPortError("Port wait timeoutMs must be a positive integer.");
1400
+ }
1401
+ if (!Number.isInteger(intervalMs) || intervalMs <= 0) {
1402
+ throw new SandboxPortError("Port wait intervalMs must be a positive integer.");
1403
+ }
1404
+ }
1405
+ async function waitWithSignal(timeoutMs, signal) {
1406
+ if (signal?.aborted === true) throw abortReason(signal);
1407
+ await new Promise((resolve, reject) => {
1408
+ const timeout = setTimeout(() => {
1409
+ signal?.removeEventListener("abort", abort);
1410
+ resolve();
1411
+ }, timeoutMs);
1412
+ const abort = () => {
1413
+ clearTimeout(timeout);
1414
+ reject(abortReason(signal));
1415
+ };
1416
+ signal?.addEventListener("abort", abort, { once: true });
1417
+ });
1418
+ }
1419
+ function abortReason(signal) {
1420
+ return signal?.reason ?? new SandboxPortError("Waiting for sandbox port was aborted.");
1421
+ }
717
1422
 
718
1423
  // src/tools.ts
719
1424
  import { createTool } from "@anvia/core/tool";
@@ -736,7 +1441,26 @@ var writeFileInput = z.object({
736
1441
  var listFilesInput = z.object({
737
1442
  path: z.string().optional().describe("Relative directory path inside the sandbox. Defaults to root.")
738
1443
  });
1444
+ var emptyInput = z.object({});
1445
+ var startProcessInput = z.object({
1446
+ command: z.string().min(1).describe("Executable to run as a managed sandbox process."),
1447
+ args: z.array(z.string()).optional().describe("Command arguments."),
1448
+ cwd: z.string().optional().describe("Relative working directory inside the sandbox."),
1449
+ env: z.record(z.string(), z.string()).optional().describe("Environment variables for this process.")
1450
+ });
1451
+ var processIdInput = z.object({
1452
+ processId: z.string().min(1).describe("Managed process identifier.")
1453
+ });
1454
+ var readProcessLogsInput = processIdInput.extend({
1455
+ tailBytes: z.number().int().nonnegative().max(1024 * 1024).optional().describe("Maximum trailing bytes to return from each output stream.")
1456
+ });
1457
+ var waitForPortInput = z.object({
1458
+ containerPort: z.number().int().min(1).max(65535).describe("Pre-authorized TCP port inside the sandbox container."),
1459
+ timeoutMs: z.number().int().positive().max(3e5).optional().describe("Maximum time to wait for the port to accept connections.")
1460
+ });
739
1461
  var textOutput = z.string();
1462
+ var maxToolLogBytes = 1024 * 1024;
1463
+ var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
740
1464
  function createSandboxTools(session, options = {}) {
741
1465
  const include = new Set(
742
1466
  options.allow ?? options.include ?? ["exec_command", "read_file", "write_file", "list_files"]
@@ -754,6 +1478,35 @@ function createSandboxTools(session, options = {}) {
754
1478
  if (include.has("list_files")) {
755
1479
  tools.push(createListFilesTool(session));
756
1480
  }
1481
+ const portToolsRequested = include.has("list_ports") || include.has("wait_for_port");
1482
+ const portSession = portToolsRequested ? requirePortSession(session) : void 0;
1483
+ if (include.has("list_ports") && portSession !== void 0) {
1484
+ tools.push(createListPortsTool(portSession));
1485
+ }
1486
+ const processToolsRequested = include.has("start_process") || include.has("list_processes") || include.has("read_process_logs") || include.has("stop_process");
1487
+ if (include.has("wait_for_port") || processToolsRequested) assertProcessToolPolicy(options);
1488
+ const processSession = processToolsRequested ? requireProcessSession(session) : void 0;
1489
+ if (include.has("start_process") && processSession !== void 0) {
1490
+ tools.push(createStartProcessTool(processSession, options));
1491
+ }
1492
+ if (include.has("list_processes") && processSession !== void 0) {
1493
+ tools.push(createListProcessesTool(processSession));
1494
+ }
1495
+ if (include.has("read_process_logs") && processSession !== void 0) {
1496
+ tools.push(createReadProcessLogsTool(processSession, options));
1497
+ }
1498
+ if (include.has("stop_process") && processSession !== void 0) {
1499
+ tools.push(createStopProcessTool(processSession, options));
1500
+ }
1501
+ if (include.has("wait_for_port") && portSession !== void 0) {
1502
+ tools.push(createWaitForPortTool(portSession, options));
1503
+ }
1504
+ for (const tool of tools) {
1505
+ Object.defineProperty(tool, sandboxToolMetadataKey, {
1506
+ value: { session },
1507
+ enumerable: false
1508
+ });
1509
+ }
757
1510
  return tools;
758
1511
  }
759
1512
  function createExecCommandTool(session, options) {
@@ -834,6 +1587,114 @@ function createListFilesTool(session) {
834
1587
  }
835
1588
  });
836
1589
  }
1590
+ function createListPortsTool(session) {
1591
+ return createTool({
1592
+ name: "list_ports",
1593
+ description: "List pre-authorized sandbox preview ports. Servers must bind to 0.0.0.0 on a listed container port.",
1594
+ input: emptyInput,
1595
+ output: textOutput,
1596
+ execute: async () => {
1597
+ if (session.publishedPorts.length === 0) {
1598
+ return "No sandbox ports are published.";
1599
+ }
1600
+ return session.publishedPorts.map((port) => `${port.containerPort}/${port.protocol} ${port.host}:${port.hostPort}`).join("\n");
1601
+ }
1602
+ });
1603
+ }
1604
+ function createStartProcessTool(session, options) {
1605
+ return createTool({
1606
+ name: "start_process",
1607
+ description: "Start a managed long-running process inside the sandbox. Use list_ports first and bind web servers to 0.0.0.0.",
1608
+ input: startProcessInput,
1609
+ output: textOutput,
1610
+ execute: async ({ command, args, cwd, env }) => {
1611
+ assertCommandAllowed(command, options);
1612
+ const processOptions = { command };
1613
+ if (args !== void 0) processOptions.args = args;
1614
+ if (cwd !== void 0) processOptions.cwd = cwd;
1615
+ if (env !== void 0) processOptions.env = env;
1616
+ return formatProcessInfo(await session.startProcess(processOptions));
1617
+ }
1618
+ });
1619
+ }
1620
+ function createListProcessesTool(session) {
1621
+ return createTool({
1622
+ name: "list_processes",
1623
+ description: "List managed sandbox processes and their current status.",
1624
+ input: emptyInput,
1625
+ output: textOutput,
1626
+ execute: async () => {
1627
+ const processes = await session.listProcesses();
1628
+ if (processes.length === 0) return "No managed processes.";
1629
+ return processes.map(formatProcessInfo).join("\n\n");
1630
+ }
1631
+ });
1632
+ }
1633
+ function createReadProcessLogsTool(session, options) {
1634
+ return createTool({
1635
+ name: "read_process_logs",
1636
+ description: "Read recent stdout and stderr from a managed sandbox process.",
1637
+ input: readProcessLogsInput,
1638
+ output: textOutput,
1639
+ execute: async ({ processId, tailBytes }) => {
1640
+ const configuredMaxLogBytes = options.process?.maxLogBytes ?? 64 * 1024;
1641
+ if (!Number.isInteger(configuredMaxLogBytes) || configuredMaxLogBytes < 0) {
1642
+ throw new SandboxToolPolicyError("Process maxLogBytes must be a non-negative integer.");
1643
+ }
1644
+ const maxLogBytes = Math.min(configuredMaxLogBytes, maxToolLogBytes);
1645
+ const effectiveTailBytes = tailBytes ?? maxLogBytes;
1646
+ if (effectiveTailBytes > maxLogBytes) {
1647
+ throw new SandboxToolPolicyError(
1648
+ `Process log request exceeds sandbox tool policy (${effectiveTailBytes} > ${maxLogBytes}).`
1649
+ );
1650
+ }
1651
+ const logs = await session.readProcessLogs(processId, {
1652
+ tailBytes: effectiveTailBytes
1653
+ });
1654
+ const parts = [];
1655
+ if (logs.stdout.length > 0) parts.push(`stdout:
1656
+ ${logs.stdout.trimEnd()}`);
1657
+ if (logs.stderr.length > 0) parts.push(`stderr:
1658
+ ${logs.stderr.trimEnd()}`);
1659
+ if (logs.stdoutTruncated || logs.stderrTruncated) parts.push("output_truncated: true");
1660
+ return parts.length > 0 ? parts.join("\n\n") : "No process output.";
1661
+ }
1662
+ });
1663
+ }
1664
+ function createStopProcessTool(session, options) {
1665
+ return createTool({
1666
+ name: "stop_process",
1667
+ description: "Stop a managed sandbox process.",
1668
+ input: processIdInput,
1669
+ output: textOutput,
1670
+ execute: async ({ processId }) => formatProcessInfo(
1671
+ await session.stopProcess(processId, {
1672
+ gracePeriodMs: options.process?.stopGracePeriodMs ?? 5e3
1673
+ })
1674
+ )
1675
+ });
1676
+ }
1677
+ function createWaitForPortTool(session, options) {
1678
+ return createTool({
1679
+ name: "wait_for_port",
1680
+ description: "Wait until a pre-authorized sandbox TCP port is accepting connections.",
1681
+ input: waitForPortInput,
1682
+ output: textOutput,
1683
+ execute: async ({ containerPort, timeoutMs }) => {
1684
+ const effectiveTimeoutMs = timeoutMs ?? options.process?.defaultWaitTimeoutMs ?? 3e4;
1685
+ const maxWaitTimeoutMs = options.process?.maxWaitTimeoutMs ?? 3e5;
1686
+ if (effectiveTimeoutMs > maxWaitTimeoutMs) {
1687
+ throw new SandboxToolPolicyError(
1688
+ `Port wait timeout exceeds sandbox tool policy (${effectiveTimeoutMs} > ${maxWaitTimeoutMs}).`
1689
+ );
1690
+ }
1691
+ const port = await session.waitForPort(containerPort, {
1692
+ timeoutMs: effectiveTimeoutMs
1693
+ });
1694
+ return `ready: ${port.containerPort}/${port.protocol} -> ${port.host}:${port.hostPort}`;
1695
+ }
1696
+ });
1697
+ }
837
1698
  function formatExecResult(result) {
838
1699
  const parts = [`exit_code: ${result.exitCode}`];
839
1700
  if (result.timedOut) {
@@ -855,6 +1716,50 @@ ${result.stderr.trimEnd()}`);
855
1716
  }
856
1717
  return parts.join("\n\n");
857
1718
  }
1719
+ function formatProcessInfo(process) {
1720
+ const parts = [
1721
+ `process_id: ${process.id}`,
1722
+ `status: ${process.status}`,
1723
+ `command: ${[process.command, ...process.args].join(" ")}`
1724
+ ];
1725
+ if (process.exitCode !== void 0) parts.push(`exit_code: ${process.exitCode}`);
1726
+ return parts.join("\n");
1727
+ }
1728
+ function requirePortSession(session) {
1729
+ if (!isSandboxPortSession(session)) {
1730
+ throw new SandboxToolPolicyError("The sandbox session does not support published port tools.");
1731
+ }
1732
+ return session;
1733
+ }
1734
+ function requireProcessSession(session) {
1735
+ if (!isSandboxProcessSession(session)) {
1736
+ throw new SandboxToolPolicyError("The sandbox session does not support managed process tools.");
1737
+ }
1738
+ return session;
1739
+ }
1740
+ function assertProcessToolPolicy(options) {
1741
+ const policy = options.process;
1742
+ if (policy === void 0) return;
1743
+ if (policy.maxLogBytes !== void 0 && (!Number.isInteger(policy.maxLogBytes) || policy.maxLogBytes < 0 || policy.maxLogBytes > maxToolLogBytes)) {
1744
+ throw new SandboxToolPolicyError(
1745
+ `Process maxLogBytes must be an integer from 0 to ${maxToolLogBytes}.`
1746
+ );
1747
+ }
1748
+ const maxWaitTimeoutMs = policy.maxWaitTimeoutMs ?? 3e5;
1749
+ if (!Number.isInteger(maxWaitTimeoutMs) || maxWaitTimeoutMs <= 0 || maxWaitTimeoutMs > 3e5) {
1750
+ throw new SandboxToolPolicyError(
1751
+ "Process maxWaitTimeoutMs must be an integer from 1 to 300000."
1752
+ );
1753
+ }
1754
+ if (policy.defaultWaitTimeoutMs !== void 0 && (!Number.isInteger(policy.defaultWaitTimeoutMs) || policy.defaultWaitTimeoutMs <= 0 || policy.defaultWaitTimeoutMs > maxWaitTimeoutMs)) {
1755
+ throw new SandboxToolPolicyError(
1756
+ "Process defaultWaitTimeoutMs must be positive and no greater than maxWaitTimeoutMs."
1757
+ );
1758
+ }
1759
+ if (policy.stopGracePeriodMs !== void 0 && (!Number.isInteger(policy.stopGracePeriodMs) || policy.stopGracePeriodMs < 0)) {
1760
+ throw new SandboxToolPolicyError("Process stopGracePeriodMs must be a non-negative integer.");
1761
+ }
1762
+ }
858
1763
  function assertCommandAllowed(command, options) {
859
1764
  const policy = options.exec;
860
1765
  if (policy?.blockedCommands?.includes(command)) {
@@ -891,9 +1796,13 @@ export {
891
1796
  SandboxError,
892
1797
  SandboxFileSizeError,
893
1798
  SandboxPathError,
1799
+ SandboxPortError,
1800
+ SandboxProcessError,
894
1801
  SandboxSessionDestroyedError,
895
1802
  SandboxTimeoutError,
896
1803
  SandboxToolPolicyError,
897
- createSandboxTools
1804
+ createSandboxTools,
1805
+ isSandboxPortSession,
1806
+ isSandboxProcessSession
898
1807
  };
899
1808
  //# sourceMappingURL=index.js.map