@opengeni/runtime 0.11.0 → 0.13.3

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.
@@ -13274,8 +13274,8 @@ async function sweepModalOrphanSandboxes(settings, liveLeases, options = {}) {
13274
13274
  if (liveByInstance) {
13275
13275
  if (leaseId !== liveByInstance.leaseId || workspaceId !== liveByInstance.workspaceId || sandboxGroupId !== liveByInstance.sandboxGroupId) {
13276
13276
  try {
13277
- const sandbox = await modal.sandboxes.fromId(info.id);
13278
- await sandbox.setTags(
13277
+ const sandbox2 = await modal.sandboxes.fromId(info.id);
13278
+ await sandbox2.setTags(
13279
13279
  modalSandboxAttributionTags({
13280
13280
  leaseId: liveByInstance.leaseId,
13281
13281
  workspaceId: liveByInstance.workspaceId,
@@ -13306,10 +13306,28 @@ async function sweepModalOrphanSandboxes(settings, liveLeases, options = {}) {
13306
13306
  skipped += 1;
13307
13307
  continue;
13308
13308
  }
13309
+ const candidate = { sandboxId: info.id, reason, tags };
13310
+ let sandbox;
13311
+ try {
13312
+ sandbox = await modal.sandboxes.fromId(info.id);
13313
+ } catch {
13314
+ skipped += 1;
13315
+ continue;
13316
+ }
13317
+ if (options.revalidateTermination) {
13318
+ try {
13319
+ if (!await options.revalidateTermination(candidate)) {
13320
+ skipped += 1;
13321
+ continue;
13322
+ }
13323
+ } catch {
13324
+ skipped += 1;
13325
+ continue;
13326
+ }
13327
+ }
13309
13328
  try {
13310
- const sandbox = await modal.sandboxes.fromId(info.id);
13311
13329
  await sandbox.terminate();
13312
- terminated.push({ sandboxId: info.id, reason, tags });
13330
+ terminated.push(candidate);
13313
13331
  } catch {
13314
13332
  skipped += 1;
13315
13333
  }
@@ -14823,11 +14841,11 @@ var NatsControlRpc = class {
14823
14841
  }
14824
14842
  }
14825
14843
  };
14826
- function offlineControlResponse(requestId, neverSent = false) {
14827
- return { requestId, error: offlineAgentError(void 0, neverSent), result: void 0 };
14844
+ function offlineControlResponse(requestId2, neverSent = false) {
14845
+ return { requestId: requestId2, error: offlineAgentError(void 0, neverSent), result: void 0 };
14828
14846
  }
14829
- function timeoutControlResponse(requestId) {
14830
- return { requestId, error: timeoutAgentError(), result: void 0 };
14847
+ function timeoutControlResponse(requestId2) {
14848
+ return { requestId: requestId2, error: timeoutAgentError(), result: void 0 };
14831
14849
  }
14832
14850
 
14833
14851
  // src/sandbox/selfhosted/session.ts
@@ -15407,10 +15425,10 @@ var OpConsumer = class {
15407
15425
  * `SelfhostedControlError` on any AgentError. `requestId` is overridable
15408
15426
  * because OpStart's REQUEST ID IS THE OP ID (its idempotency key) and must
15409
15427
  * be STABLE across retries — every other op uses a fresh UUID per attempt. */
15410
- async controlOp(op, requestId = crypto.randomUUID()) {
15428
+ async controlOp(op, requestId2 = crypto.randomUUID()) {
15411
15429
  const res = await this.deps.controlRpc.request(
15412
15430
  this.deps.rpcSubject,
15413
- { requestId, epoch: this.deps.epoch, op },
15431
+ { requestId: requestId2, epoch: this.deps.epoch, op },
15414
15432
  { timeoutMs: this.deps.controlTimeoutMs }
15415
15433
  );
15416
15434
  if (res.error || !res.result) {
@@ -17474,6 +17492,295 @@ function assertProviderRegistryInvariants() {
17474
17492
  }
17475
17493
  assertProviderRegistryInvariants();
17476
17494
 
17495
+ // src/sandbox/provider-errors.ts
17496
+ var GRPC_TRANSIENT = /* @__PURE__ */ new Set([1, 2, 4, 13, 14]);
17497
+ var TRANSIENT_CODES = /* @__PURE__ */ new Set([
17498
+ "CANCELLED",
17499
+ "UNKNOWN",
17500
+ "DEADLINE_EXCEEDED",
17501
+ "INTERNAL",
17502
+ "UNAVAILABLE",
17503
+ "ECONNRESET",
17504
+ "ECONNREFUSED",
17505
+ "ETIMEDOUT",
17506
+ "EAI_AGAIN",
17507
+ "ENOTFOUND"
17508
+ ]);
17509
+ var NOT_FOUND_CODES = /* @__PURE__ */ new Set(["404", "NOT_FOUND", "RESOURCE_NOT_FOUND", "SANDBOX_NOT_FOUND"]);
17510
+ var SAFE_CODE = /^[A-Z0-9._:-]{1,64}$/;
17511
+ function safeRead(record, key) {
17512
+ try {
17513
+ return Reflect.get(record, key);
17514
+ } catch {
17515
+ return void 0;
17516
+ }
17517
+ }
17518
+ function collectSignals(value) {
17519
+ const out = { numbers: [], codes: [], messages: [] };
17520
+ const seen = /* @__PURE__ */ new Set();
17521
+ const visit = (current, depth) => {
17522
+ if (depth > 3 || current === null || current === void 0) return;
17523
+ if (typeof current === "string") {
17524
+ if (current.length <= 512) out.messages.push(current);
17525
+ return;
17526
+ }
17527
+ if (typeof current !== "object" || seen.has(current)) return;
17528
+ seen.add(current);
17529
+ for (const key of [
17530
+ "status",
17531
+ "statusCode",
17532
+ "httpStatus",
17533
+ "httpStatusCode",
17534
+ "responseStatus",
17535
+ "errorCode",
17536
+ "code"
17537
+ ]) {
17538
+ const signal = safeRead(current, key);
17539
+ if (typeof signal === "number" && Number.isFinite(signal)) out.numbers.push(signal);
17540
+ if (typeof signal === "string") {
17541
+ const code = signal.trim().toUpperCase();
17542
+ if (SAFE_CODE.test(code)) out.codes.push(code);
17543
+ }
17544
+ }
17545
+ for (const key of ["name", "message", "details", "cause"]) {
17546
+ const signal = safeRead(current, key);
17547
+ if (typeof signal === "string" && signal.length <= 512) out.messages.push(signal);
17548
+ else visit(signal, depth + 1);
17549
+ }
17550
+ visit(safeRead(current, "response"), depth + 1);
17551
+ visit(safeRead(current, "error"), depth + 1);
17552
+ };
17553
+ visit(value, 0);
17554
+ return out;
17555
+ }
17556
+ function safeDiagnostic(signals) {
17557
+ const code = signals.codes.find(Boolean);
17558
+ const numeric = signals.numbers.find((value) => Number.isFinite(value));
17559
+ return [code ? `code=${code}` : "", numeric !== void 0 ? `status=${numeric}` : ""].filter(Boolean).join(" ");
17560
+ }
17561
+ function hasTransientSignal(signals) {
17562
+ return signals.codes.some((code) => TRANSIENT_CODES.has(code)) || signals.numbers.some((status) => GRPC_TRANSIENT.has(status));
17563
+ }
17564
+ function hasModalTerminalSandboxMessage(signals) {
17565
+ return signals.messages.some(
17566
+ (message) => /^Modal sandbox [A-Za-z0-9_-]+ is no longer running\.$/.test(message) || /^Modal sandbox [A-Za-z0-9_-]+ not found \(has been terminated\)$/.test(message)
17567
+ );
17568
+ }
17569
+ function classifyProviderSandboxFailure(backendId, error) {
17570
+ if (backendId === "selfhosted" || !error) {
17571
+ return { kind: "other", diagnostic: "selfhosted-or-empty" };
17572
+ }
17573
+ const signals = collectSignals(error);
17574
+ const diagnostic = safeDiagnostic(signals) || "unclassified_provider_failure";
17575
+ if (hasTransientSignal(signals)) {
17576
+ return { kind: "transient_transport", diagnostic };
17577
+ }
17578
+ if (signals.numbers.some((status) => status === 404) || signals.codes.some((code) => NOT_FOUND_CODES.has(code))) {
17579
+ return { kind: "not_found", diagnostic };
17580
+ }
17581
+ if (backendId === "modal" && hasModalTerminalSandboxMessage(signals)) {
17582
+ return {
17583
+ kind: "not_found",
17584
+ diagnostic: diagnostic === "unclassified_provider_failure" ? "modal_terminal_message" : `${diagnostic} modal_terminal_message`
17585
+ };
17586
+ }
17587
+ return { kind: "other", diagnostic };
17588
+ }
17589
+ function isProviderSandboxNotFoundError2(backendId, error) {
17590
+ return classifyProviderSandboxFailure(backendId, error).kind === "not_found";
17591
+ }
17592
+ function isProviderSandboxGoneDuringRoutedOperation(backendId, error) {
17593
+ if (backendId === "selfhosted" || !error) return false;
17594
+ const signals = collectSignals(error);
17595
+ if (hasTransientSignal(signals)) return false;
17596
+ if (signals.codes.includes("SANDBOX_NOT_FOUND")) return true;
17597
+ return backendId === "modal" && hasModalTerminalSandboxMessage(signals);
17598
+ }
17599
+ function isProviderSandboxTransientError(backendId, error) {
17600
+ return classifyProviderSandboxFailure(backendId, error).kind === "transient_transport";
17601
+ }
17602
+
17603
+ // src/sandbox/workspace-archive.ts
17604
+ import { createHash as createHash4 } from "crypto";
17605
+ var WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION = 1;
17606
+ var WorkspaceArchiveIntegrityError = class extends Error {
17607
+ constructor(code, message, options = {}) {
17608
+ super(message);
17609
+ this.code = code;
17610
+ this.retryable = options.retryable ?? false;
17611
+ }
17612
+ name = "WorkspaceArchiveIntegrityError";
17613
+ retryable;
17614
+ };
17615
+ var SHA256 = /^[a-f0-9]{64}$/;
17616
+ var REVISION = /^wa1:[0-9]{13}:[a-f0-9]{64}$/;
17617
+ function sha256(bytes) {
17618
+ return createHash4("sha256").update(bytes).digest("hex");
17619
+ }
17620
+ function nonnegativeInteger(value) {
17621
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
17622
+ }
17623
+ function parseWorkspaceArchiveDescriptor(value) {
17624
+ if (!value || typeof value !== "object") return null;
17625
+ const candidate = value;
17626
+ const workspace = candidate.workspace;
17627
+ if (candidate.version !== WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION || typeof candidate.revision !== "string" || !REVISION.test(candidate.revision) || typeof candidate.archiveSha256 !== "string" || !SHA256.test(candidate.archiveSha256) || !nonnegativeInteger(candidate.archiveBytes) || candidate.archiveBytes === 0 || typeof candidate.capturedAt !== "string" || !Number.isFinite(Date.parse(candidate.capturedAt)) || !workspace || workspace.algorithm !== "sha256" || typeof workspace.sha256 !== "string" || !SHA256.test(workspace.sha256) || !nonnegativeInteger(workspace.entryCount) || !nonnegativeInteger(workspace.fileCount) || !nonnegativeInteger(workspace.totalFileBytes)) {
17628
+ return null;
17629
+ }
17630
+ return candidate;
17631
+ }
17632
+ function decodeBase64Strict(value) {
17633
+ if (value.length === 0 || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(value)) {
17634
+ return null;
17635
+ }
17636
+ const bytes = Uint8Array.from(Buffer.from(value, "base64"));
17637
+ return Buffer.from(bytes).toString("base64") === value ? bytes : null;
17638
+ }
17639
+ function readVerifiedWorkspaceArchive(base642, metadata) {
17640
+ if (base642 === void 0 || base642 === null || base642 === "") return null;
17641
+ if (typeof base642 !== "string") {
17642
+ throw new WorkspaceArchiveIntegrityError(
17643
+ "archive_base64_invalid",
17644
+ "selected workspace archive is not a base64 string"
17645
+ );
17646
+ }
17647
+ if (metadata === void 0 || metadata === null) {
17648
+ throw new WorkspaceArchiveIntegrityError(
17649
+ "archive_metadata_missing",
17650
+ "selected workspace archive has no durable revision/fingerprint metadata"
17651
+ );
17652
+ }
17653
+ const descriptor = parseWorkspaceArchiveDescriptor(metadata);
17654
+ if (!descriptor) {
17655
+ throw new WorkspaceArchiveIntegrityError(
17656
+ "archive_metadata_invalid",
17657
+ "selected workspace archive metadata is invalid"
17658
+ );
17659
+ }
17660
+ const bytes = decodeBase64Strict(base642);
17661
+ if (!bytes) {
17662
+ throw new WorkspaceArchiveIntegrityError(
17663
+ "archive_base64_invalid",
17664
+ `workspace archive revision ${descriptor.revision} has invalid base64 bytes`
17665
+ );
17666
+ }
17667
+ const actualHash = sha256(bytes);
17668
+ if (bytes.length !== descriptor.archiveBytes || actualHash !== descriptor.archiveSha256) {
17669
+ throw new WorkspaceArchiveIntegrityError(
17670
+ "archive_hash_mismatch",
17671
+ `workspace archive revision ${descriptor.revision} failed SHA-256/size verification`
17672
+ );
17673
+ }
17674
+ return { bytes, base64: base642, descriptor };
17675
+ }
17676
+ function stdoutFromExecResult(value) {
17677
+ if (typeof value === "string") return value;
17678
+ if (!value || typeof value !== "object") return "";
17679
+ const output = value;
17680
+ return typeof output.stdout === "string" ? output.stdout : typeof output.output === "string" ? output.output : "";
17681
+ }
17682
+ var WORKSPACE_FINGERPRINT_COMMAND = String.raw`bash -o pipefail -c '
17683
+ set -eu
17684
+ cd /workspace
17685
+ digest=$(LC_ALL=C tar --sort=name --mtime="@0" --owner=0 --group=0 --numeric-owner --format=gnu -cf - . | sha256sum | awk "{print \$1}")
17686
+ entries=$(find . -xdev -mindepth 1 -printf x | wc -c | tr -d " ")
17687
+ files=$(find . -xdev -type f -printf x | wc -c | tr -d " ")
17688
+ bytes=$(find . -xdev -type f -printf "%s\n" | awk "{s+=\$1} END {printf \"%.0f\", s+0}")
17689
+ printf "OPENGENI_WORKSPACE_FINGERPRINT_V1 %s %s %s %s\n" "$digest" "$entries" "$files" "$bytes"
17690
+ '`;
17691
+ async function fingerprintSandboxWorkspace(session) {
17692
+ const target = session;
17693
+ const run2 = target.exec ?? target.execCommand;
17694
+ if (!run2) {
17695
+ throw new WorkspaceArchiveIntegrityError(
17696
+ "workspace_fingerprint_unavailable",
17697
+ "sandbox session cannot execute the workspace fingerprint probe"
17698
+ );
17699
+ }
17700
+ const result = await run2.call(target, {
17701
+ cmd: WORKSPACE_FINGERPRINT_COMMAND,
17702
+ yieldTimeMs: 12e4,
17703
+ maxOutputTokens: 1e3
17704
+ });
17705
+ const match = stdoutFromExecResult(result).trim().match(/^OPENGENI_WORKSPACE_FINGERPRINT_V1 ([a-f0-9]{64}) ([0-9]+) ([0-9]+) ([0-9]+)$/);
17706
+ if (!match) {
17707
+ throw new WorkspaceArchiveIntegrityError(
17708
+ "workspace_fingerprint_unavailable",
17709
+ "sandbox workspace fingerprint probe returned no valid digest",
17710
+ { retryable: true }
17711
+ );
17712
+ }
17713
+ const entryCount = Number(match[2]);
17714
+ const fileCount = Number(match[3]);
17715
+ const totalFileBytes = Number(match[4]);
17716
+ if (![entryCount, fileCount, totalFileBytes].every(Number.isSafeInteger)) {
17717
+ throw new WorkspaceArchiveIntegrityError(
17718
+ "workspace_fingerprint_unavailable",
17719
+ "sandbox workspace fingerprint counters exceeded the supported range"
17720
+ );
17721
+ }
17722
+ return {
17723
+ algorithm: "sha256",
17724
+ sha256: match[1],
17725
+ entryCount,
17726
+ fileCount,
17727
+ totalFileBytes
17728
+ };
17729
+ }
17730
+ function fingerprintsEqual(a, b) {
17731
+ return a.sha256 === b.sha256 && a.entryCount === b.entryCount && a.fileCount === b.fileCount && a.totalFileBytes === b.totalFileBytes;
17732
+ }
17733
+ async function captureVerifiedWorkspaceArchive(session, capturedAtMs = Date.now()) {
17734
+ const target = session;
17735
+ if (typeof target.persistWorkspace !== "function") {
17736
+ throw new WorkspaceArchiveIntegrityError(
17737
+ "workspace_fingerprint_unavailable",
17738
+ "sandbox session does not support workspace persistence"
17739
+ );
17740
+ }
17741
+ const before = await fingerprintSandboxWorkspace(target);
17742
+ const bytes = await target.persistWorkspace();
17743
+ if (!bytes || bytes.length === 0) {
17744
+ throw new WorkspaceArchiveIntegrityError(
17745
+ "archive_base64_invalid",
17746
+ "sandbox workspace persistence returned an empty archive",
17747
+ { retryable: true }
17748
+ );
17749
+ }
17750
+ const after = await fingerprintSandboxWorkspace(target);
17751
+ if (!fingerprintsEqual(before, after)) {
17752
+ throw new WorkspaceArchiveIntegrityError(
17753
+ "workspace_changed_during_capture",
17754
+ "workspace changed while its durable archive was being captured",
17755
+ { retryable: true }
17756
+ );
17757
+ }
17758
+ const archiveSha256 = sha256(bytes);
17759
+ const descriptor = {
17760
+ version: WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION,
17761
+ revision: `wa1:${String(capturedAtMs).padStart(13, "0")}:${archiveSha256}`,
17762
+ archiveSha256,
17763
+ archiveBytes: bytes.length,
17764
+ capturedAt: new Date(capturedAtMs).toISOString(),
17765
+ workspace: before
17766
+ };
17767
+ return {
17768
+ bytes,
17769
+ base64: Buffer.from(bytes).toString("base64"),
17770
+ descriptor
17771
+ };
17772
+ }
17773
+ async function verifyRestoredWorkspace(session, descriptor) {
17774
+ const actual = await fingerprintSandboxWorkspace(session);
17775
+ if (!fingerprintsEqual(actual, descriptor.workspace)) {
17776
+ throw new WorkspaceArchiveIntegrityError(
17777
+ "workspace_fingerprint_mismatch",
17778
+ `restored workspace does not match selected archive revision ${descriptor.revision}`
17779
+ );
17780
+ }
17781
+ return actual;
17782
+ }
17783
+
17477
17784
  // src/sandbox/index.ts
17478
17785
  import { collectSandboxEnvironment as collectSandboxEnvironment2, parseExposedPorts as parseExposedPorts2 } from "@opengeni/config";
17479
17786
 
@@ -17690,6 +17997,9 @@ function negotiateCapabilities(ctx) {
17690
17997
  os: ctx.os,
17691
17998
  liveness: ctx.liveness,
17692
17999
  leaseEpoch: ctx.leaseEpoch,
18000
+ workspaceGeneration: ctx.workspaceGeneration ?? null,
18001
+ archiveGeneration: ctx.archiveGeneration ?? null,
18002
+ archiveComplete: ctx.archiveComplete ?? false,
17693
18003
  viewerHeartbeatIntervalMs: 3e4,
17694
18004
  FileSystem: fileSystem,
17695
18005
  Terminal: terminal,
@@ -17732,8 +18042,67 @@ async function verifyStreamToken(secret, token, nowSeconds = Math.floor(Date.now
17732
18042
 
17733
18043
  // src/sandbox/display-stack.ts
17734
18044
  import { DESKTOP_STREAM_PORT as DESKTOP_STREAM_PORT4 } from "@opengeni/contracts";
18045
+
18046
+ // src/sandbox/exec-banner.ts
18047
+ var EXEC_BANNER_HEADER_MAX_CHARS = 16 * 1024;
18048
+ var OUTPUT_DELIMITER = /\r?\nOutput:\r?\n/u;
18049
+ var SDK_METADATA_LINE = /^(?:Chunk ID:|Wall time:|Original token count:|Output:|Process (?:running with session ID|exited with code))/mu;
18050
+ var RUNNING_LINE = /^Process running with session ID (\d+)$/u;
18051
+ var EXITED_LINE = /^Process exited with code (-?\d+)$/u;
18052
+ var PROCESS_STATUS_PREFIX = /^Process (?:running with session ID|exited with code)/u;
18053
+ function parseExecResponseBanner(raw) {
18054
+ const delimiter = OUTPUT_DELIMITER.exec(raw);
18055
+ if (!delimiter) {
18056
+ return SDK_METADATA_LINE.test(raw) ? { kind: "invalid" } : { kind: "absent" };
18057
+ }
18058
+ if (delimiter.index > EXEC_BANNER_HEADER_MAX_CHARS) return { kind: "invalid" };
18059
+ const header = raw.slice(0, delimiter.index);
18060
+ const status = [];
18061
+ for (const line of header.split(/\r?\n/u)) {
18062
+ const running = RUNNING_LINE.exec(line);
18063
+ if (running) {
18064
+ status.push({ kind: "running", value: running[1] });
18065
+ continue;
18066
+ }
18067
+ const exited = EXITED_LINE.exec(line);
18068
+ if (exited) {
18069
+ status.push({ kind: "exited", value: exited[1] });
18070
+ continue;
18071
+ }
18072
+ if (PROCESS_STATUS_PREFIX.test(line)) return { kind: "invalid" };
18073
+ }
18074
+ if (status.length !== 1) return { kind: "invalid" };
18075
+ const terminal = status[0];
18076
+ const value = Number(terminal.value);
18077
+ if (!Number.isSafeInteger(value)) return { kind: "invalid" };
18078
+ if (terminal.kind === "running") {
18079
+ return value >= 0 ? { kind: "running", sessionId: value } : { kind: "invalid" };
18080
+ }
18081
+ return { kind: "exited", exitCode: value };
18082
+ }
18083
+ function parseExecBannerSessionId(raw) {
18084
+ const banner = parseExecResponseBanner(raw);
18085
+ return banner.kind === "running" ? banner.sessionId : null;
18086
+ }
18087
+ function parseExecBannerExitCode(raw) {
18088
+ const banner = parseExecResponseBanner(raw);
18089
+ return banner.kind === "exited" ? banner.exitCode : null;
18090
+ }
18091
+ function isExecSessionLostBanner(out, execSessionId) {
18092
+ if (!out || !Number.isSafeInteger(execSessionId) || execSessionId < 0) return false;
18093
+ const match = out.match(/^(?:write_stdin failed: )?session not found: (\d+)$/);
18094
+ if (!match || match[0] !== out) return false;
18095
+ return match[1] === String(execSessionId);
18096
+ }
18097
+
18098
+ // src/sandbox/display-stack.ts
17735
18099
  var STREAM_PORT = DESKTOP_STREAM_PORT4;
17736
18100
  var DISPLAY_STACK_TIMEOUT_MS = 9e4;
18101
+ var DISPLAY_STACK_PROVIDER_YIELD_MS = 15e3;
18102
+ var DISPLAY_STACK_PROVIDER_POLL_MS = 5e3;
18103
+ var DISPLAY_STACK_TERMINATION_GRACE_MS = 5e3;
18104
+ var DISPLAY_STACK_CLEANUP_CALL_MS = 250;
18105
+ var DISPLAY_STACK_DRAIN_GRACE_MS = 100;
17737
18106
  var PAINT_PROBE_ATTEMPTS = 150;
17738
18107
  var PAINT_PROBE_INTERVAL_S = 0.2;
17739
18108
  var PAINT_MIN_BYTES = 6e4;
@@ -17743,7 +18112,7 @@ var DisplayStackError = class extends Error {
17743
18112
  exitCode;
17744
18113
  stage;
17745
18114
  constructor(exitCode, output) {
17746
- const stage = exitCode === 11 ? "xvfb" : exitCode === 12 ? "x11vnc" : exitCode === 13 ? "websockify" : exitCode === 14 ? "paint" : "unknown";
18115
+ const stage = exitCode === 11 ? "xvfb" : exitCode === 12 ? "x11vnc" : exitCode === 13 ? "websockify" : exitCode === 14 ? "paint" : exitCode === 15 ? "lock" : exitCode === 124 || /OPENGENI_DISPLAY_TIMEOUT/u.test(output) ? "timeout" : "unknown";
17747
18116
  super(
17748
18117
  `desktop display stack failed at stage "${stage}" (exit ${exitCode})${output ? `:
17749
18118
  ${output}` : ""}`
@@ -17763,9 +18132,11 @@ function buildDisplayStackScript(options = {}) {
17763
18132
  const geometry = options.geometry ?? DEFAULT_DESKTOP_GEOMETRY;
17764
18133
  const port = options.port ?? DESKTOP_STREAM_PORT4;
17765
18134
  const env = `DESKTOP_W=${geometry.width} DESKTOP_H=${geometry.height} DESKTOP_DPI=${geometry.dpi} STREAM_PORT=${port}`;
17766
- const bringUp = `if nc -z 127.0.0.1 ${port} >/dev/null 2>&1 && nc -z 127.0.0.1 5900 >/dev/null 2>&1; then echo "OPENGENI_DESKTOP_UP port=${port} geometry=${geometry.width}x${geometry.height} dpi=${geometry.dpi} (precheck)"; else mkdir -p /tmp/opengeni-desktop && flock -w 45 /tmp/opengeni-desktop/up.outer.lock env ${env} opengeni-desktop-up; fi`;
17767
- const paintProbe = `p=/tmp/opengeni-desktop/paint-probe.png; prev=0; for i in $(seq 1 ${PAINT_PROBE_ATTEMPTS}); do if DISPLAY=:0 scrot -o "$p" >/dev/null 2>&1; then sz=$(wc -c < "$p" 2>/dev/null || echo 0); else sz=0; fi; rm -f "$p"; if [ "$sz" -ge ${PAINT_MIN_BYTES} ] && [ "$prev" -ge ${PAINT_MIN_BYTES} ]; then d=$((sz-prev)); [ "$d" -lt 0 ] && d=$((0-d)); [ "$d" -le ${PAINT_SETTLE_DELTA_BYTES} ] && break; fi; prev=$sz; if [ "$i" = "${PAINT_PROBE_ATTEMPTS}" ]; then echo "OPENGENI_DESKTOP_NOT_PAINTING scrot below ${PAINT_MIN_BYTES}B or unsettled after warmup (last=$sz)"; exit 14; fi; sleep ${PAINT_PROBE_INTERVAL_S}; done`;
17768
- return `mkdir -p /tmp/opengeni-desktop; { ${bringUp} ; } && { ${paintProbe} ; }`;
18135
+ const lockedLauncher = `ds_started=$1; fw=$2; ds_class=cold; ds_stage() { ds_now=$(date +%s%3N); echo "OPENGENI_DISPLAY_STAGE stage=$1 elapsed_ms=$((ds_now-ds_started)) classification=$ds_class"; }; fa=$(date +%s%3N); fd=$((fa-fw)); [ "$fd" -ge 50 ] && ds_class=contention; ds_stage outer_flock_acquired; ds_stage launcher_issued; env ${env} opengeni-desktop-up; urc=$?; ds_stage launcher_complete; exit "$urc"`;
18136
+ const bringUp = `if nc -z 127.0.0.1 ${port} >/dev/null 2>&1 && nc -z 127.0.0.1 5900 >/dev/null 2>&1; then ds_class=already_ready; ds_stage precheck_ready; echo "OPENGENI_DESKTOP_UP port=${port} geometry=${geometry.width}x${geometry.height} dpi=${geometry.dpi} (precheck)"; else ds_class=cold; ds_stage precheck_miss; mkdir -p /tmp/opengeni-desktop; ds_stage outer_flock_wait; fw=$(date +%s%3N); flock --close --conflict-exit-code 75 --wait 45 /tmp/opengeni-desktop/up.outer.lock bash -c ${shellQuote3(lockedLauncher)} _ "$ds_started" "$fw"; frc=$?; if [ "$frc" -eq 75 ]; then ds_stage outer_flock_timeout; echo "OPENGENI_DESKTOP_LOCK_TIMEOUT"; exit 15; fi; if [ "$frc" -ne 0 ]; then exit "$frc"; fi; fi`;
18137
+ const paintProbe = `ds_stage paint_probe_started; p=/tmp/opengeni-desktop/paint-probe.png; prev=0; for i in $(seq 1 ${PAINT_PROBE_ATTEMPTS}); do if DISPLAY=:0 scrot -o "$p" >/dev/null 2>&1; then sz=$(wc -c < "$p" 2>/dev/null || echo 0); else sz=0; fi; rm -f "$p"; if [ "$sz" -ge ${PAINT_MIN_BYTES} ] && [ "$prev" -ge ${PAINT_MIN_BYTES} ]; then d=$((sz-prev)); [ "$d" -lt 0 ] && d=$((0-d)); if [ "$d" -le ${PAINT_SETTLE_DELTA_BYTES} ]; then ds_stage paint_ready; break; fi; fi; prev=$sz; if [ "$i" = "${PAINT_PROBE_ATTEMPTS}" ]; then ds_stage paint_failed; echo "OPENGENI_DESKTOP_NOT_PAINTING scrot below ${PAINT_MIN_BYTES}B or unsettled after warmup (last=$sz)"; exit 14; fi; sleep ${PAINT_PROBE_INTERVAL_S}; done`;
18138
+ const trace = `ds_started=$(date +%s%3N); ds_class=unknown; ds_stage() { ds_now=$(date +%s%3N); echo "OPENGENI_DISPLAY_STAGE stage=$1 elapsed_ms=$((ds_now-ds_started)) classification=$ds_class"; }; ds_stage script_entry`;
18139
+ return `${trace}; mkdir -p /tmp/opengeni-desktop; { ${bringUp} ; } && { ${paintProbe} ; }`;
17769
18140
  }
17770
18141
  function execResultOutput(result) {
17771
18142
  if (typeof result === "string") {
@@ -17775,11 +18146,417 @@ function execResultOutput(result) {
17775
18146
  }
17776
18147
  function execResultExitCode(result) {
17777
18148
  if (typeof result === "string") {
17778
- return null;
18149
+ const banner = parseExecResponseBanner(result);
18150
+ if (banner.kind === "exited") return banner.exitCode;
18151
+ return banner.kind === "invalid" ? -1 : null;
17779
18152
  }
17780
18153
  return typeof result.exitCode === "number" ? result.exitCode : null;
17781
18154
  }
18155
+ function execResultSessionId(result) {
18156
+ if (typeof result === "string") {
18157
+ const banner = parseExecResponseBanner(result);
18158
+ return banner.kind === "running" ? banner.sessionId : null;
18159
+ }
18160
+ const value = result.sessionId ?? result.session_id;
18161
+ return typeof value === "number" ? value : null;
18162
+ }
18163
+ function shellQuote3(value) {
18164
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
18165
+ }
18166
+ function boundedDisplayStackCommand(command, timeoutMs, markerPath, markerToken) {
18167
+ const graceMs = Math.min(
18168
+ DISPLAY_STACK_TERMINATION_GRACE_MS,
18169
+ Math.max(20, Math.floor(timeoutMs / 4))
18170
+ );
18171
+ const commandTimeoutMs = Math.max(1, timeoutMs - graceMs);
18172
+ const killAfterMs = Math.max(1, Math.floor(graceMs / 2));
18173
+ const commandTimeoutSeconds = (commandTimeoutMs / 1e3).toFixed(3);
18174
+ const killAfterSeconds = (killAfterMs / 1e3).toFixed(3);
18175
+ const managedCommand = `marker=${shellQuote3(markerPath)}; token=${shellQuote3(markerToken)}; cancel="$marker.cancelled"; if [ -e "$cancel" ]; then echo "OPENGENI_DISPLAY_CANCELLED"; exit 124; fi; pid=$$; pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' '); start=$(awk '{print $22}' "/proc/$pid/stat" 2>/dev/null || true); printf '%s %s %s %s\\n' "$pid" "$pgid" "$start" "$token" >"$marker"; if [ -e "$cancel" ]; then rm -f "$marker"; echo "OPENGENI_DISPLAY_CANCELLED"; exit 124; fi; bash -c ${shellQuote3(command)}; rc=$?; rm -f "$marker"; exit "$rc"`;
18176
+ return (
18177
+ // Do not use timeout --foreground here. The display command contains flock
18178
+ // supervisors and launcher children; default process-group mode is required
18179
+ // so a deadline cannot leave a waiter alive to acquire the lock and launch
18180
+ // after the caller has already failed.
18181
+ `timeout --signal=TERM --kill-after=${killAfterSeconds}s ${commandTimeoutSeconds}s bash -c ${shellQuote3(managedCommand)}; rc=$?; if [ "$rc" -eq 124 ]; then echo "OPENGENI_DISPLAY_TIMEOUT elapsed_ms=${commandTimeoutMs}"; fi; exit "$rc"`
18182
+ );
18183
+ }
18184
+ var DisplayStackWallTimeoutError = class extends Error {
18185
+ };
18186
+ var DisplayStackOperationFencedError = class extends Error {
18187
+ };
18188
+ var stableDisplayStackOperationStates = /* @__PURE__ */ new Map();
18189
+ var stableSessionStateOperationStates = /* @__PURE__ */ new WeakMap();
18190
+ var displayStackOperationStates = /* @__PURE__ */ new WeakMap();
18191
+ function asRecord(value) {
18192
+ return value !== null && typeof value === "object" ? value : null;
18193
+ }
18194
+ function nonEmptyString(value) {
18195
+ return typeof value === "string" && value.length > 0 ? value : null;
18196
+ }
18197
+ function readSessionState(session) {
18198
+ try {
18199
+ return asRecord(session)?.state;
18200
+ } catch {
18201
+ return void 0;
18202
+ }
18203
+ }
18204
+ function stableSandboxIdentity(session, options) {
18205
+ const explicit = nonEmptyString(options.telemetryContext?.sandboxId);
18206
+ if (explicit) return `sandbox:${explicit}`;
18207
+ const sessionRecord = asRecord(session);
18208
+ const state = asRecord(readSessionState(session));
18209
+ const providerState = state ? asRecord(state.providerState) : null;
18210
+ const candidates = [sessionRecord, state, providerState];
18211
+ const fields = [
18212
+ "sandboxId",
18213
+ "instanceId",
18214
+ "agentId",
18215
+ "hostId",
18216
+ "containerId",
18217
+ "id",
18218
+ "workspaceRootPath"
18219
+ ];
18220
+ for (const candidate of candidates) {
18221
+ if (!candidate) continue;
18222
+ for (const field of fields) {
18223
+ const value = nonEmptyString(candidate[field]);
18224
+ if (value) return `sandbox:${value}`;
18225
+ }
18226
+ }
18227
+ return null;
18228
+ }
18229
+ function newOperationState() {
18230
+ return {
18231
+ epoch: 0,
18232
+ owner: null,
18233
+ waiters: [],
18234
+ inFlight: /* @__PURE__ */ new Set(),
18235
+ drain: null,
18236
+ pendingCleanup: /* @__PURE__ */ new Map(),
18237
+ physicallyQuiescent: true
18238
+ };
18239
+ }
18240
+ function operationState(session, options) {
18241
+ if ((typeof session !== "object" || session === null) && typeof session !== "function") {
18242
+ return null;
18243
+ }
18244
+ const stableKey = stableSandboxIdentity(session, options);
18245
+ if (stableKey) {
18246
+ let state2 = stableDisplayStackOperationStates.get(stableKey);
18247
+ if (!state2) {
18248
+ state2 = newOperationState();
18249
+ stableDisplayStackOperationStates.set(stableKey, state2);
18250
+ }
18251
+ return state2;
18252
+ }
18253
+ const providerState = readSessionState(session);
18254
+ if (providerState && (typeof providerState === "object" || typeof providerState === "function")) {
18255
+ const stateKey = providerState;
18256
+ let state2 = stableSessionStateOperationStates.get(stateKey);
18257
+ if (!state2) {
18258
+ state2 = newOperationState();
18259
+ stableSessionStateOperationStates.set(stateKey, state2);
18260
+ }
18261
+ return state2;
18262
+ }
18263
+ const key = session;
18264
+ let state = displayStackOperationStates.get(key);
18265
+ if (!state) {
18266
+ state = newOperationState();
18267
+ displayStackOperationStates.set(key, state);
18268
+ }
18269
+ return state;
18270
+ }
18271
+ function settleOperationWaiter(waiter, error) {
18272
+ if (waiter.settled) return;
18273
+ waiter.settled = true;
18274
+ if (waiter.timer) clearTimeout(waiter.timer);
18275
+ if (error === void 0) waiter.resolve();
18276
+ else waiter.reject(error);
18277
+ }
18278
+ function pumpOperationWaiters(state) {
18279
+ if (state.owner) return;
18280
+ while (state.waiters.length > 0) {
18281
+ const waiter = state.waiters.shift();
18282
+ if (waiter.settled) continue;
18283
+ if (waiter.epoch !== state.epoch) {
18284
+ settleOperationWaiter(waiter, new DisplayStackOperationFencedError());
18285
+ continue;
18286
+ }
18287
+ state.owner = waiter;
18288
+ settleOperationWaiter(waiter);
18289
+ return;
18290
+ }
18291
+ }
18292
+ function acquireOperation(state, deadline) {
18293
+ return new Promise((resolve5, reject) => {
18294
+ const waiter = {
18295
+ epoch: state.epoch,
18296
+ settled: false,
18297
+ resolve: () => {
18298
+ resolve5(() => {
18299
+ if (state.owner !== waiter) return;
18300
+ state.owner = null;
18301
+ pumpOperationWaiters(state);
18302
+ });
18303
+ },
18304
+ reject
18305
+ };
18306
+ state.waiters.push(waiter);
18307
+ const remainingMs = deadline - monotonicNow();
18308
+ if (remainingMs <= 0) {
18309
+ waiter.settled = true;
18310
+ state.waiters = state.waiters.filter((candidate) => candidate !== waiter);
18311
+ reject(new DisplayStackWallTimeoutError());
18312
+ return;
18313
+ }
18314
+ waiter.timer = setTimeout(() => {
18315
+ if (waiter.settled) return;
18316
+ waiter.settled = true;
18317
+ state.waiters = state.waiters.filter((candidate) => candidate !== waiter);
18318
+ reject(new DisplayStackWallTimeoutError());
18319
+ }, remainingMs);
18320
+ waiter.timer.unref?.();
18321
+ pumpOperationWaiters(state);
18322
+ });
18323
+ }
18324
+ function invalidateQueuedOperations(state, epoch) {
18325
+ const retained = [];
18326
+ for (const waiter of state.waiters) {
18327
+ if (waiter.epoch === epoch)
18328
+ settleOperationWaiter(waiter, new DisplayStackOperationFencedError());
18329
+ else retained.push(waiter);
18330
+ }
18331
+ state.waiters = retained;
18332
+ }
18333
+ function monotonicNow() {
18334
+ return performance.now();
18335
+ }
18336
+ async function waitForFencedOperation(state, deadline) {
18337
+ const drain = state.drain;
18338
+ if (!drain) return true;
18339
+ try {
18340
+ await beforeDeadline(drain, deadline);
18341
+ } catch {
18342
+ return false;
18343
+ }
18344
+ if (state.drain === drain) state.drain = null;
18345
+ return true;
18346
+ }
18347
+ function trackProviderCall(state, promise) {
18348
+ if (!state) return promise;
18349
+ state.inFlight.add(promise);
18350
+ void promise.then(
18351
+ () => state.inFlight.delete(promise),
18352
+ () => state.inFlight.delete(promise)
18353
+ );
18354
+ return promise;
18355
+ }
18356
+ function boundedProviderDrain(pending) {
18357
+ if (pending.length === 0) return Promise.resolve();
18358
+ return new Promise((resolve5) => {
18359
+ let remaining = pending.length;
18360
+ let timer;
18361
+ const finish = () => {
18362
+ if (timer) clearTimeout(timer);
18363
+ resolve5();
18364
+ };
18365
+ timer = setTimeout(finish, DISPLAY_STACK_DRAIN_GRACE_MS);
18366
+ timer.unref?.();
18367
+ for (const promise of pending) {
18368
+ void promise.then(
18369
+ () => {
18370
+ remaining -= 1;
18371
+ if (remaining === 0) finish();
18372
+ },
18373
+ () => {
18374
+ remaining -= 1;
18375
+ if (remaining === 0) finish();
18376
+ }
18377
+ );
18378
+ }
18379
+ });
18380
+ }
18381
+ function fenceOperation(state, epoch, abortController, cleanup) {
18382
+ abortController.abort(new DisplayStackWallTimeoutError());
18383
+ if (!state) return;
18384
+ if (state.epoch !== epoch) return;
18385
+ state.pendingCleanup.set(cleanup.markerToken, cleanup);
18386
+ state.physicallyQuiescent = false;
18387
+ invalidateQueuedOperations(state, epoch);
18388
+ state.epoch += 1;
18389
+ const pending = [...state.inFlight];
18390
+ const drain = boundedProviderDrain(pending);
18391
+ state.drain = state.drain ? Promise.all([state.drain, drain]).then(() => void 0) : drain;
18392
+ void drain.then(() => {
18393
+ for (const promise of pending) state.inFlight.delete(promise);
18394
+ });
18395
+ }
18396
+ function assertCurrentOperation(state, epoch) {
18397
+ if (state && state.epoch !== epoch) throw new DisplayStackOperationFencedError();
18398
+ }
18399
+ async function beforeDeadline(promise, deadline, now = monotonicNow) {
18400
+ const remainingMs = deadline - now();
18401
+ if (remainingMs <= 0) throw new DisplayStackWallTimeoutError();
18402
+ let timer;
18403
+ try {
18404
+ return await Promise.race([
18405
+ promise,
18406
+ new Promise((_resolve, reject) => {
18407
+ timer = setTimeout(() => reject(new DisplayStackWallTimeoutError()), remainingMs);
18408
+ timer.unref?.();
18409
+ })
18410
+ ]);
18411
+ } finally {
18412
+ if (timer) clearTimeout(timer);
18413
+ }
18414
+ }
18415
+ function displayStackCleanupCommand(markerPath, markerToken) {
18416
+ const marker = shellQuote3(markerPath);
18417
+ const token = shellQuote3(markerToken);
18418
+ const script = `marker=${marker}; token=${token}; cancel="$marker.cancelled"; : >"$cancel" || { echo "OPENGENI_DISPLAY_CLEANUP status=invalid"; exit 75; }; if [ ! -r "$marker" ]; then echo "OPENGENI_DISPLAY_CLEANUP status=stopped"; exit 0; fi; read -r pid pgid start actual <"$marker" || { echo "OPENGENI_DISPLAY_CLEANUP status=invalid"; exit 75; }; [ "$actual" = "$token" ] || { echo "OPENGENI_DISPLAY_CLEANUP status=invalid"; exit 75; }; case "$pid" in ''|*[!0-9]*) echo "OPENGENI_DISPLAY_CLEANUP status=invalid"; exit 75;; esac; case "$pgid" in ''|*[!0-9]*) echo "OPENGENI_DISPLAY_CLEANUP status=invalid"; exit 75;; esac; case "$start" in ''|*[!0-9]*) echo "OPENGENI_DISPLAY_CLEANUP status=invalid"; exit 75;; esac; current=$(awk '{print $22}' "/proc/$pid/stat" 2>/dev/null || true); if [ -z "$current" ] || [ "$current" != "$start" ]; then rm -f "$marker"; echo "OPENGENI_DISPLAY_CLEANUP status=stopped"; exit 0; fi; kill -TERM -- "-$pgid" 2>/dev/null || true; for i in $(seq 1 10); do if ! kill -0 -- "-$pgid" 2>/dev/null; then rm -f "$marker"; echo "OPENGENI_DISPLAY_CLEANUP status=stopped"; exit 0; fi; sleep 0.05; done; kill -KILL -- "-$pgid" 2>/dev/null || true; for i in $(seq 1 20); do if ! kill -0 -- "-$pgid" 2>/dev/null; then rm -f "$marker"; echo "OPENGENI_DISPLAY_CLEANUP status=stopped"; exit 0; fi; sleep 0.05; done; echo "OPENGENI_DISPLAY_CLEANUP status=alive"; exit 75`;
18419
+ return `timeout --signal=TERM --kill-after=0.2s 1.5s bash -c ${shellQuote3(script)}`;
18420
+ }
18421
+ async function runProviderCommandToDeadline(session, command, deadline) {
18422
+ const controller = new AbortController();
18423
+ let result = await beforeDeadline(
18424
+ Promise.resolve(
18425
+ typeof session.exec === "function" ? session.exec({
18426
+ cmd: command,
18427
+ yieldTimeMs: Math.min(500, DISPLAY_STACK_CLEANUP_CALL_MS),
18428
+ maxOutputTokens: 4e3,
18429
+ signal: controller.signal
18430
+ }) : session.execCommand({
18431
+ cmd: command,
18432
+ yieldTimeMs: Math.min(500, DISPLAY_STACK_CLEANUP_CALL_MS),
18433
+ maxOutputTokens: 4e3,
18434
+ signal: controller.signal
18435
+ })
18436
+ ),
18437
+ deadline
18438
+ );
18439
+ let providerSessionId = execResultSessionId(result);
18440
+ while (providerSessionId !== null) {
18441
+ if (typeof session.writeStdin !== "function") {
18442
+ throw new Error("display cleanup provider yielded without writeStdin");
18443
+ }
18444
+ const remainingMs = deadline - monotonicNow();
18445
+ if (remainingMs <= 1) throw new DisplayStackWallTimeoutError();
18446
+ result = await beforeDeadline(
18447
+ session.writeStdin({
18448
+ sessionId: providerSessionId,
18449
+ chars: "",
18450
+ yieldTimeMs: Math.max(1, Math.min(250, remainingMs - 1)),
18451
+ maxOutputTokens: 4e3,
18452
+ signal: controller.signal
18453
+ }),
18454
+ deadline
18455
+ );
18456
+ providerSessionId = execResultSessionId(result);
18457
+ }
18458
+ return result;
18459
+ }
18460
+ async function interruptProviderProcess(session, providerSessionId, deadline) {
18461
+ if (providerSessionId === null || typeof session.writeStdin !== "function") return;
18462
+ try {
18463
+ await beforeDeadline(
18464
+ session.writeStdin({
18465
+ sessionId: providerSessionId,
18466
+ chars: "",
18467
+ yieldTimeMs: 100,
18468
+ maxOutputTokens: 256
18469
+ }),
18470
+ deadline
18471
+ );
18472
+ } catch {
18473
+ }
18474
+ }
18475
+ async function cleanupDisplayStackProcess(session, pending, deadline) {
18476
+ try {
18477
+ const result = await runProviderCommandToDeadline(
18478
+ session,
18479
+ displayStackCleanupCommand(pending.markerPath, pending.markerToken),
18480
+ deadline
18481
+ );
18482
+ const output = execResultOutput(result);
18483
+ const status = /OPENGENI_DISPLAY_CLEANUP status=(stopped|absent|alive|invalid)/u.exec(
18484
+ output
18485
+ )?.[1];
18486
+ return status === "stopped";
18487
+ } catch {
18488
+ return false;
18489
+ }
18490
+ }
18491
+ async function reconcilePendingCleanup(session, state, deadline) {
18492
+ const entries = [...state.pendingCleanup.values()];
18493
+ if (entries.length === 0) {
18494
+ state.physicallyQuiescent = true;
18495
+ return true;
18496
+ }
18497
+ const results = await Promise.all(
18498
+ entries.map(async (pending) => ({
18499
+ pending,
18500
+ stopped: await cleanupDisplayStackProcess(session, pending, deadline)
18501
+ }))
18502
+ );
18503
+ for (const { pending, stopped } of results) {
18504
+ if (stopped) state.pendingCleanup.delete(pending.markerToken);
18505
+ }
18506
+ state.physicallyQuiescent = state.pendingCleanup.size === 0;
18507
+ return state.physicallyQuiescent;
18508
+ }
18509
+ function requestId() {
18510
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
18511
+ }
18512
+ function telemetryReporter(options, started, now = monotonicNow) {
18513
+ const id = requestId();
18514
+ const context = options.telemetryContext;
18515
+ const sink = options.onTelemetry ?? (context ? (event) => console.info(`[display-stack] ${JSON.stringify(event)}`) : void 0);
18516
+ const emittedSandboxStages = /* @__PURE__ */ new Set();
18517
+ const emit = (stage, status, fields = {}) => {
18518
+ if (!sink) return;
18519
+ const event = {
18520
+ type: "display_stack.stage",
18521
+ requestId: id,
18522
+ stage,
18523
+ status,
18524
+ elapsedMs: fields.elapsedMs ?? now() - started,
18525
+ source: fields.source ?? "host",
18526
+ classification: fields.classification ?? "unknown",
18527
+ callerKind: context?.callerKind ?? "unknown",
18528
+ ...context?.sandboxId ? { sandboxId: context.sandboxId } : {},
18529
+ ...context?.leaseEpoch !== void 0 ? { leaseEpoch: context.leaseEpoch } : {},
18530
+ ...fields.providerSessionId !== void 0 ? { providerSessionId: fields.providerSessionId } : {}
18531
+ };
18532
+ try {
18533
+ void Promise.resolve(sink(event)).catch(() => void 0);
18534
+ } catch {
18535
+ }
18536
+ };
18537
+ const emitSandboxStages = (output) => {
18538
+ for (const match of output.matchAll(
18539
+ /OPENGENI_(?:DISPLAY|DESKTOP)_STAGE stage=([a-z0-9_]+) elapsed_ms=(\d+)(?: classification=(cold|already_ready|contention|unknown))?/gu
18540
+ )) {
18541
+ const [line, stage, elapsed, classification] = match;
18542
+ if (!stage || !elapsed || emittedSandboxStages.has(line)) continue;
18543
+ emittedSandboxStages.add(line);
18544
+ emit(stage, "completed", {
18545
+ source: "sandbox",
18546
+ elapsedMs: Number(elapsed),
18547
+ classification: classification ?? "unknown"
18548
+ });
18549
+ }
18550
+ };
18551
+ return { emit, emitSandboxStages };
18552
+ }
17782
18553
  function inferExitFromOutput(output) {
18554
+ if (/OPENGENI_DISPLAY_TIMEOUT/.test(output)) {
18555
+ return 124;
18556
+ }
18557
+ if (/OPENGENI_DESKTOP_LOCK_TIMEOUT/.test(output)) {
18558
+ return 15;
18559
+ }
17783
18560
  if (/OPENGENI_DESKTOP_NOT_PAINTING/.test(output)) {
17784
18561
  return 14;
17785
18562
  }
@@ -17807,15 +18584,163 @@ async function ensureDisplayStack(session, options = {}) {
17807
18584
  const geometry = options.geometry ?? DEFAULT_DESKTOP_GEOMETRY;
17808
18585
  const port = options.port ?? DESKTOP_STREAM_PORT4;
17809
18586
  const timeoutMs = options.timeoutMs ?? DISPLAY_STACK_TIMEOUT_MS;
17810
- const cmd = buildDisplayStackScript({ geometry, port });
17811
- const result = typeof s.exec === "function" ? await s.exec({ cmd, yieldTimeMs: timeoutMs, maxOutputTokens: 2e4 }) : await s.execCommand({ cmd, yieldTimeMs: timeoutMs, maxOutputTokens: 2e4 });
17812
- const output = execResultOutput(result);
17813
- const exitCode = execResultExitCode(result) ?? inferExitFromOutput(output);
17814
- if (exitCode !== 0) {
17815
- throw new DisplayStackError(exitCode, output);
18587
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
18588
+ throw new RangeError("ensureDisplayStack timeoutMs must be a positive finite number");
18589
+ }
18590
+ const started = monotonicNow();
18591
+ const deadline = started + timeoutMs;
18592
+ const telemetry = telemetryReporter(options, started, monotonicNow);
18593
+ const operationToken = requestId();
18594
+ const markerPath = `/tmp/opengeni-desktop/display-stack-${operationToken}.pid`;
18595
+ const cmd = boundedDisplayStackCommand(
18596
+ buildDisplayStackScript({ geometry, port }),
18597
+ timeoutMs,
18598
+ markerPath,
18599
+ operationToken
18600
+ );
18601
+ const outputParts = [];
18602
+ const state = operationState(session, options);
18603
+ let release;
18604
+ if (state) {
18605
+ try {
18606
+ release = await acquireOperation(state, deadline);
18607
+ } catch (error) {
18608
+ if (error instanceof DisplayStackWallTimeoutError || error instanceof DisplayStackOperationFencedError) {
18609
+ throw new DisplayStackError(
18610
+ 124,
18611
+ "OPENGENI_DISPLAY_TIMEOUT display operation admission was fenced"
18612
+ );
18613
+ }
18614
+ throw error;
18615
+ }
18616
+ }
18617
+ try {
18618
+ if (state && !state.physicallyQuiescent) {
18619
+ const cleanupDeadline = monotonicNow() + DISPLAY_STACK_CLEANUP_CALL_MS;
18620
+ const quiesced = await reconcilePendingCleanup(s, state, cleanupDeadline);
18621
+ if (!quiesced) {
18622
+ throw new DisplayStackError(
18623
+ 124,
18624
+ "OPENGENI_DISPLAY_TIMEOUT physical cleanup is still pending; retry is fenced"
18625
+ );
18626
+ }
18627
+ }
18628
+ const epoch = state?.epoch ?? 0;
18629
+ const abortController = new AbortController();
18630
+ let activeProviderSessionId = null;
18631
+ telemetry.emit("request_entry", "started");
18632
+ telemetry.emit("exec_issued", "started");
18633
+ try {
18634
+ const initialYieldMs = Math.max(1, Math.min(DISPLAY_STACK_PROVIDER_YIELD_MS, timeoutMs - 1));
18635
+ const initialExec = trackProviderCall(
18636
+ state,
18637
+ Promise.resolve(
18638
+ typeof s.exec === "function" ? s.exec({
18639
+ cmd,
18640
+ yieldTimeMs: initialYieldMs,
18641
+ maxOutputTokens: 2e4,
18642
+ signal: abortController.signal
18643
+ }) : s.execCommand({
18644
+ cmd,
18645
+ yieldTimeMs: initialYieldMs,
18646
+ maxOutputTokens: 2e4,
18647
+ signal: abortController.signal
18648
+ })
18649
+ )
18650
+ );
18651
+ let result = await beforeDeadline(
18652
+ initialExec,
18653
+ deadline,
18654
+ monotonicNow
18655
+ );
18656
+ assertCurrentOperation(state, epoch);
18657
+ let chunk = execResultOutput(result);
18658
+ outputParts.push(chunk);
18659
+ telemetry.emitSandboxStages(chunk);
18660
+ telemetry.emit("provider_first_result", "completed");
18661
+ let providerSessionId = execResultSessionId(result);
18662
+ activeProviderSessionId = providerSessionId;
18663
+ while (providerSessionId !== null) {
18664
+ telemetry.emit("provider_yield", "waiting", { providerSessionId });
18665
+ if (typeof s.writeStdin !== "function") {
18666
+ throw new DisplayStackError(
18667
+ -1,
18668
+ `${outputParts.join("\n")}
18669
+ provider yielded process ${providerSessionId} but exposes no writeStdin poll surface`
18670
+ );
18671
+ }
18672
+ assertCurrentOperation(state, epoch);
18673
+ const remainingMs = deadline - monotonicNow();
18674
+ if (remainingMs <= 1) throw new DisplayStackWallTimeoutError();
18675
+ const pollYieldMs = Math.max(1, Math.min(DISPLAY_STACK_PROVIDER_POLL_MS, remainingMs - 1));
18676
+ result = await beforeDeadline(
18677
+ trackProviderCall(
18678
+ state,
18679
+ s.writeStdin({
18680
+ sessionId: providerSessionId,
18681
+ chars: "",
18682
+ yieldTimeMs: pollYieldMs,
18683
+ maxOutputTokens: 2e4,
18684
+ signal: abortController.signal
18685
+ })
18686
+ ),
18687
+ deadline,
18688
+ monotonicNow
18689
+ );
18690
+ assertCurrentOperation(state, epoch);
18691
+ chunk = execResultOutput(result);
18692
+ outputParts.push(chunk);
18693
+ telemetry.emitSandboxStages(chunk);
18694
+ providerSessionId = execResultSessionId(result);
18695
+ activeProviderSessionId = providerSessionId;
18696
+ }
18697
+ const output = outputParts.join("\n");
18698
+ const exitCode = execResultExitCode(result) ?? inferExitFromOutput(output);
18699
+ telemetry.emit("process_complete", exitCode === 0 ? "completed" : "failed");
18700
+ if (exitCode !== 0) {
18701
+ throw new DisplayStackError(exitCode, output);
18702
+ }
18703
+ const marker = (output.match(/OPENGENI_DESKTOP_UP[^\n]*/) ?? [""])[0];
18704
+ telemetry.emit("readiness_complete", "completed");
18705
+ return { port, geometry, marker };
18706
+ } catch (error) {
18707
+ if (error instanceof DisplayStackWallTimeoutError || error instanceof DisplayStackOperationFencedError) {
18708
+ const pendingCleanup = {
18709
+ markerPath,
18710
+ markerToken: operationToken
18711
+ };
18712
+ fenceOperation(state, epoch, abortController, pendingCleanup);
18713
+ const cleanupDeadline = monotonicNow() + DISPLAY_STACK_CLEANUP_CALL_MS;
18714
+ telemetry.emit("process_cleanup", "started");
18715
+ await interruptProviderProcess(
18716
+ s,
18717
+ activeProviderSessionId,
18718
+ Math.min(cleanupDeadline, monotonicNow() + 50)
18719
+ );
18720
+ let quiesced = false;
18721
+ if (state) {
18722
+ quiesced = await reconcilePendingCleanup(s, state, cleanupDeadline);
18723
+ if (!quiesced) {
18724
+ await waitForFencedOperation(
18725
+ state,
18726
+ Math.min(cleanupDeadline, monotonicNow() + DISPLAY_STACK_DRAIN_GRACE_MS)
18727
+ );
18728
+ }
18729
+ } else {
18730
+ quiesced = await cleanupDisplayStackProcess(s, pendingCleanup, cleanupDeadline);
18731
+ }
18732
+ telemetry.emit("process_cleanup", quiesced ? "completed" : "failed");
18733
+ const output = `${outputParts.join("\n")}
18734
+ OPENGENI_DISPLAY_TIMEOUT wall_ms=${timeoutMs}`;
18735
+ telemetry.emit("wall_deadline", "failed");
18736
+ throw new DisplayStackError(124, output);
18737
+ }
18738
+ telemetry.emit("request_failed", "failed");
18739
+ throw error;
18740
+ }
18741
+ } finally {
18742
+ release?.();
17816
18743
  }
17817
- const marker = (output.match(/OPENGENI_DESKTOP_UP[^\n]*/) ?? [""])[0];
17818
- return { port, geometry, marker };
17819
18744
  }
17820
18745
  async function tearDownDisplayStack(session) {
17821
18746
  const s = session;
@@ -18235,7 +19160,7 @@ function runCredentialRoot(sessionId) {
18235
19160
  function runCredentialPointerFile(sessionId) {
18236
19161
  return `${runCredentialRoot(sessionId)}/current`;
18237
19162
  }
18238
- function shellQuote3(value) {
19163
+ function shellQuote4(value) {
18239
19164
  return `'${value.replace(/'/g, "'\\''")}'`;
18240
19165
  }
18241
19166
  function base64(value) {
@@ -18253,8 +19178,8 @@ function portableBase64DecodeCommand() {
18253
19178
  ].join("\n");
18254
19179
  }
18255
19180
  function pointerLockAcquireCommands(root) {
18256
- const lockFile = shellQuote3(`${root}/.pointer.lock`);
18257
- const lockDirectory = shellQuote3(`${root}/.pointer.lockdir`);
19181
+ const lockFile = shellQuote4(`${root}/.pointer.lock`);
19182
+ const lockDirectory = shellQuote4(`${root}/.pointer.lockdir`);
18258
19183
  return [
18259
19184
  `if command -v flock >/dev/null 2>&1; then`,
18260
19185
  ` exec 9>${lockFile}`,
@@ -18298,7 +19223,7 @@ async function runCredentialCommand(session, cmd, commandRunner) {
18298
19223
  ${cmd}
18299
19224
  printf '\\n${COMMAND_OK_MARKER}\\n'`;
18300
19225
  const args = {
18301
- cmd: `env -u BASH_ENV bash --noprofile --norc -c ${shellQuote3(marked)}`,
19226
+ cmd: `env -u BASH_ENV bash --noprofile --norc -c ${shellQuote4(marked)}`,
18302
19227
  yieldTimeMs: 12e4,
18303
19228
  maxOutputTokens: 4e3
18304
19229
  };
@@ -18308,19 +19233,19 @@ printf '\\n${COMMAND_OK_MARKER}\\n'`;
18308
19233
  }
18309
19234
  }
18310
19235
  async function writeCredentialFile(session, path, content, commandRunner) {
18311
- await runCredentialCommand(session, `: > ${shellQuote3(path)}`, commandRunner);
19236
+ await runCredentialCommand(session, `: > ${shellQuote4(path)}`, commandRunner);
18312
19237
  const encoded = base64(content);
18313
19238
  for (let offset = 0; offset < encoded.length; offset += WRITE_CHUNK_BYTES) {
18314
19239
  const chunk = encoded.slice(offset, offset + WRITE_CHUNK_BYTES);
18315
19240
  await runCredentialCommand(
18316
19241
  session,
18317
- `printf %s ${shellQuote3(chunk)} | ${portableBase64DecodeCommand()} >> ${shellQuote3(path)}`,
19242
+ `printf %s ${shellQuote4(chunk)} | ${portableBase64DecodeCommand()} >> ${shellQuote4(path)}`,
18318
19243
  commandRunner
18319
19244
  );
18320
19245
  }
18321
19246
  await runCredentialCommand(
18322
19247
  session,
18323
- `[ "$(wc -c < ${shellQuote3(path)} | tr -d '[:space:]')" = ${shellQuote3(String(byteLength(content)))} ]`,
19248
+ `[ "$(wc -c < ${shellQuote4(path)} | tr -d '[:space:]')" = ${shellQuote4(String(byteLength(content)))} ]`,
18324
19249
  commandRunner
18325
19250
  );
18326
19251
  }
@@ -18345,19 +19270,19 @@ async function materializeRunCredentials(session, material, options) {
18345
19270
  session,
18346
19271
  [
18347
19272
  "umask 077",
18348
- `mkdir -p -- ${shellQuote3(`${root}/versions`)}`,
18349
- `chmod 0700 -- ${shellQuote3(root)} ${shellQuote3(`${root}/versions`)}`,
18350
- `rm -rf -- ${shellQuote3(stage)}`,
18351
- `mkdir -p -- ${shellQuote3(`${stage}/files`)}`
19273
+ `mkdir -p -- ${shellQuote4(`${root}/versions`)}`,
19274
+ `chmod 0700 -- ${shellQuote4(root)} ${shellQuote4(`${root}/versions`)}`,
19275
+ `rm -rf -- ${shellQuote4(stage)}`,
19276
+ `mkdir -p -- ${shellQuote4(`${stage}/files`)}`
18352
19277
  ].join("\n"),
18353
19278
  options.commandRunner
18354
19279
  );
18355
19280
  const environmentLines = [
18356
19281
  ...Object.entries(material.environment).map(
18357
- ([name, value]) => `export ${name}=${shellQuote3(value)}`
19282
+ ([name, value]) => `export ${name}=${shellQuote4(value)}`
18358
19283
  ),
18359
19284
  ...Object.entries(material.fileEnvironment).map(
18360
- ([name, path]) => `export ${name}=${shellQuote3(`${version}/files/${path}`)}`
19285
+ ([name, path]) => `export ${name}=${shellQuote4(`${version}/files/${path}`)}`
18361
19286
  )
18362
19287
  ];
18363
19288
  await writeCredentialFile(
@@ -18369,17 +19294,17 @@ async function materializeRunCredentials(session, material, options) {
18369
19294
  );
18370
19295
  await runCredentialCommand(
18371
19296
  session,
18372
- `chmod 0600 -- ${shellQuote3(`${stage}/env`)}`,
19297
+ `chmod 0600 -- ${shellQuote4(`${stage}/env`)}`,
18373
19298
  options.commandRunner
18374
19299
  );
18375
19300
  for (const file of material.files) {
18376
19301
  const target = `${stage}/files/${file.path}`;
18377
19302
  const parent = target.slice(0, target.lastIndexOf("/"));
18378
- await runCredentialCommand(session, `mkdir -p -- ${shellQuote3(parent)}`, options.commandRunner);
19303
+ await runCredentialCommand(session, `mkdir -p -- ${shellQuote4(parent)}`, options.commandRunner);
18379
19304
  await writeCredentialFile(session, target, file.content, options.commandRunner);
18380
19305
  await runCredentialCommand(
18381
19306
  session,
18382
- `chmod ${shellQuote3(file.mode ?? "0600")} -- ${shellQuote3(target)}`,
19307
+ `chmod ${shellQuote4(file.mode ?? "0600")} -- ${shellQuote4(target)}`,
18383
19308
  options.commandRunner
18384
19309
  );
18385
19310
  }
@@ -18387,19 +19312,19 @@ async function materializeRunCredentials(session, material, options) {
18387
19312
  session,
18388
19313
  [
18389
19314
  ...pointerLockAcquireCommands(root),
18390
- `previous=$(cat -- ${shellQuote3(`${root}/current`)} 2>/dev/null || :)`,
18391
- `mv -- ${shellQuote3(stage)} ${shellQuote3(version)}`,
18392
- `printf '%s\\n' ${shellQuote3(versionName)} > ${shellQuote3(`${root}/.next`)}`,
18393
- `mv -f -- ${shellQuote3(`${root}/.next`)} ${shellQuote3(`${root}/current`)}`,
19315
+ `previous=$(cat -- ${shellQuote4(`${root}/current`)} 2>/dev/null || :)`,
19316
+ `mv -- ${shellQuote4(stage)} ${shellQuote4(version)}`,
19317
+ `printf '%s\\n' ${shellQuote4(versionName)} > ${shellQuote4(`${root}/.next`)}`,
19318
+ `mv -f -- ${shellQuote4(`${root}/.next`)} ${shellQuote4(`${root}/current`)}`,
18394
19319
  ...options.prunePreviousGenerations ? [
18395
- `find ${shellQuote3(`${root}/versions`)} -mindepth 1 -maxdepth 1 ! -name ${shellQuote3(versionName)} -exec rm -rf -- {} +`
19320
+ `find ${shellQuote4(`${root}/versions`)} -mindepth 1 -maxdepth 1 ! -name ${shellQuote4(versionName)} -exec rm -rf -- {} +`
18396
19321
  ] : options.pruneOtherAttempts ? [
18397
- `find ${shellQuote3(`${root}/versions`)} -mindepth 1 -maxdepth 1 ! -name ${shellQuote3(`${attemptId}-${options.executionGeneration}-*`)} -exec rm -rf -- {} +`
19322
+ `find ${shellQuote4(`${root}/versions`)} -mindepth 1 -maxdepth 1 ! -name ${shellQuote4(`${attemptId}-${options.executionGeneration}-*`)} -exec rm -rf -- {} +`
18398
19323
  ] : options.pruneSupersededGenerations ? [
18399
- `for candidate in ${shellQuote3(`${root}/versions`)}/${prefix}*; do`,
19324
+ `for candidate in ${shellQuote4(`${root}/versions`)}/${prefix}*; do`,
18400
19325
  ` [ -e "$candidate" ] || continue`,
18401
19326
  ` name=\${candidate##*/}`,
18402
- ` [ "$name" = ${shellQuote3(versionName)} ] || [ "$name" = "$previous" ] || rm -rf -- "$candidate"`,
19327
+ ` [ "$name" = ${shellQuote4(versionName)} ] || [ "$name" = "$previous" ] || rm -rf -- "$candidate"`,
18403
19328
  "done"
18404
19329
  ] : [],
18405
19330
  ...pointerLockReleaseCommands()
@@ -18412,10 +19337,10 @@ async function clearRunCredentials(session, sessionId, commandRunner) {
18412
19337
  await runCredentialCommand(
18413
19338
  session,
18414
19339
  [
18415
- `if [ -d ${shellQuote3(root)} ]; then`,
19340
+ `if [ -d ${shellQuote4(root)} ]; then`,
18416
19341
  ...pointerLockAcquireCommands(root).map((line) => ` ${line}`),
18417
- ` rm -f -- ${shellQuote3(`${root}/current`)} ${shellQuote3(`${root}/.next`)}`,
18418
- ` rm -rf -- ${shellQuote3(`${root}/versions`)}`,
19342
+ ` rm -f -- ${shellQuote4(`${root}/current`)} ${shellQuote4(`${root}/.next`)}`,
19343
+ ` rm -rf -- ${shellQuote4(`${root}/versions`)}`,
18419
19344
  ...pointerLockReleaseCommands().map((line) => ` ${line}`),
18420
19345
  "fi"
18421
19346
  ].join("\n"),
@@ -18432,15 +19357,15 @@ async function clearRunCredentialsForAttempt(session, options, commandRunner) {
18432
19357
  await runCredentialCommand(
18433
19358
  session,
18434
19359
  [
18435
- `if [ -d ${shellQuote3(root)} ]; then`,
19360
+ `if [ -d ${shellQuote4(root)} ]; then`,
18436
19361
  ...pointerLockAcquireCommands(root).map((line) => ` ${line}`),
18437
- ` if [ -r ${shellQuote3(`${root}/current`)} ]; then`,
18438
- ` current=$(cat -- ${shellQuote3(`${root}/current`)} 2>/dev/null || :)`,
18439
- ` case "$current" in ${shellQuote3(prefix)}*) rm -f -- ${shellQuote3(`${root}/current`)} ;; esac`,
19362
+ ` if [ -r ${shellQuote4(`${root}/current`)} ]; then`,
19363
+ ` current=$(cat -- ${shellQuote4(`${root}/current`)} 2>/dev/null || :)`,
19364
+ ` case "$current" in ${shellQuote4(prefix)}*) rm -f -- ${shellQuote4(`${root}/current`)} ;; esac`,
18440
19365
  " fi",
18441
- ` if [ -d ${shellQuote3(`${root}/versions`)} ]; then`,
18442
- ` find ${shellQuote3(`${root}/versions`)} -mindepth 1 -maxdepth 1 -name ${shellQuote3(`${prefix}*`)} -exec rm -rf -- {} +`,
18443
- ` find ${shellQuote3(`${root}/versions`)} -mindepth 1 -maxdepth 1 -name ${shellQuote3(`.stage-${prefix}*`)} -exec rm -rf -- {} +`,
19366
+ ` if [ -d ${shellQuote4(`${root}/versions`)} ]; then`,
19367
+ ` find ${shellQuote4(`${root}/versions`)} -mindepth 1 -maxdepth 1 -name ${shellQuote4(`${prefix}*`)} -exec rm -rf -- {} +`,
19368
+ ` find ${shellQuote4(`${root}/versions`)} -mindepth 1 -maxdepth 1 -name ${shellQuote4(`.stage-${prefix}*`)} -exec rm -rf -- {} +`,
18444
19369
  " fi",
18445
19370
  ...pointerLockReleaseCommands().map((line) => ` ${line}`),
18446
19371
  "fi"
@@ -18452,12 +19377,12 @@ function withRunCredentialEnvironment(cmd, sessionId) {
18452
19377
  const root = runCredentialRoot(sessionId);
18453
19378
  const pointerFile = runCredentialPointerFile(sessionId);
18454
19379
  return [
18455
- `if [ -r ${shellQuote3(pointerFile)} ]; then`,
18456
- ` _opengeni_credential_version=$(cat -- ${shellQuote3(pointerFile)} 2>/dev/null || :)`,
19380
+ `if [ -r ${shellQuote4(pointerFile)} ]; then`,
19381
+ ` _opengeni_credential_version=$(cat -- ${shellQuote4(pointerFile)} 2>/dev/null || :)`,
18457
19382
  ' case "$_opengeni_credential_version" in',
18458
19383
  " ''|*[!A-Za-z0-9_-]*) ;;",
18459
19384
  " *)",
18460
- ` _opengeni_credential_env=${shellQuote3(`${root}/versions`)}/"$_opengeni_credential_version/env"`,
19385
+ ` _opengeni_credential_env=${shellQuote4(`${root}/versions`)}/"$_opengeni_credential_version/env"`,
18461
19386
  ' if [ -r "$_opengeni_credential_env" ]; then',
18462
19387
  " set -a",
18463
19388
  ' . "$_opengeni_credential_env"',
@@ -18525,7 +19450,7 @@ function withRunCredentialsClient(client, sessionId, onSessionReady) {
18525
19450
  }
18526
19451
 
18527
19452
  // src/sandbox/toolspace-token.ts
18528
- import { createHash as createHash4 } from "crypto";
19453
+ import { createHash as createHash5 } from "crypto";
18529
19454
  import { posix as posixPath } from "path";
18530
19455
  var MAX_TOOLSPACE_SESSION_ID_BYTES = 512;
18531
19456
  var ToolspaceTokenPathError = class extends Error {
@@ -18534,7 +19459,7 @@ var ToolspaceTokenPathError = class extends Error {
18534
19459
  this.name = "ToolspaceTokenPathError";
18535
19460
  }
18536
19461
  };
18537
- function shellQuote4(value) {
19462
+ function shellQuote5(value) {
18538
19463
  return `'${value.replace(/'/g, "'\\''")}'`;
18539
19464
  }
18540
19465
  function assertToolspaceSessionId(sessionId) {
@@ -18548,7 +19473,7 @@ function toolspaceTokenFileForSession(manifestTokenFile, sessionId) {
18548
19473
  throw new ToolspaceTokenPathError("Toolspace manifest token file must be absolute");
18549
19474
  }
18550
19475
  const normalizedManifestFile = posixPath.normalize(manifestTokenFile);
18551
- const digest = createHash4("sha256").update(assertToolspaceSessionId(sessionId)).digest("hex");
19476
+ const digest = createHash5("sha256").update(assertToolspaceSessionId(sessionId)).digest("hex");
18552
19477
  return posixPath.join(posixPath.dirname(normalizedManifestFile), "toolspace-tokens", digest);
18553
19478
  }
18554
19479
  function toolspaceTokenFileFromEnvironment(environment, sessionId) {
@@ -18559,7 +19484,7 @@ function withToolspaceTokenEnvironment(cmd, tokenFile) {
18559
19484
  if (!tokenFile || tokenFile.includes("\0") || !posixPath.isAbsolute(tokenFile)) {
18560
19485
  throw new ToolspaceTokenPathError("Toolspace token file must be absolute");
18561
19486
  }
18562
- return [`export OPENGENI_TOOLSPACE_TOKEN_FILE=${shellQuote4(tokenFile)}`, cmd].join("\n");
19487
+ return [`export OPENGENI_TOOLSPACE_TOKEN_FILE=${shellQuote5(tokenFile)}`, cmd].join("\n");
18563
19488
  }
18564
19489
  function withToolspaceTokenSession(session, tokenFile) {
18565
19490
  return new Proxy(session, {
@@ -18828,6 +19753,804 @@ function recordingStorageKey(workspaceId, sessionId, recordingId, codec) {
18828
19753
  return `recordings/${workspaceId}/${sessionId}/${recordingId}.${extForCodec(codec)}`;
18829
19754
  }
18830
19755
 
19756
+ // src/sandbox/turn-tool-cancellation.ts
19757
+ var TURN_EXEC_YIELD_MS = 250;
19758
+ var TURN_WRITE_YIELD_MS = 250;
19759
+ var SHELL_HELPER_YIELD_MS = 1e3;
19760
+ var SHELL_GRACEFUL_POLLS = 2;
19761
+ var SHELL_POLL_MS = 100;
19762
+ var SHELL_MARKER_DIR = "/tmp/opengeni-turn-shell";
19763
+ var delay = async (milliseconds) => await new Promise((resolve5) => setTimeout(resolve5, milliseconds));
19764
+ var TurnSandboxCommandCancelledError = class extends Error {
19765
+ name = "TurnSandboxCommandCancelledError";
19766
+ constructor(reason) {
19767
+ super(
19768
+ reason instanceof Error ? reason.message : String(reason ?? "Turn sandbox tools were cancelled"),
19769
+ {
19770
+ ...reason instanceof Error ? { cause: reason } : {}
19771
+ }
19772
+ );
19773
+ }
19774
+ };
19775
+ function cancellationError(reason) {
19776
+ return new TurnSandboxCommandCancelledError(reason);
19777
+ }
19778
+ function parsedObject(input) {
19779
+ try {
19780
+ const parsed = JSON.parse(input);
19781
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
19782
+ } catch {
19783
+ return null;
19784
+ }
19785
+ }
19786
+ function cappedYield(value, cap) {
19787
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? Math.min(value, cap) : cap;
19788
+ }
19789
+ function execOutput(raw) {
19790
+ const marker = "\nOutput:\n";
19791
+ const index = raw.indexOf(marker);
19792
+ return index >= 0 ? raw.slice(index + marker.length) : "";
19793
+ }
19794
+ function nativeCommandBanner(result) {
19795
+ if (typeof result === "string") return result;
19796
+ if (!result || typeof result !== "object") {
19797
+ throw new Error("Sandbox command returned an invalid result");
19798
+ }
19799
+ const value = result;
19800
+ const sessionId = typeof value.sessionId === "number" ? value.sessionId : typeof value.session_id === "number" ? value.session_id : null;
19801
+ const exitCode = typeof value.exitCode === "number" ? value.exitCode : typeof value.exit_code === "number" ? value.exit_code : null;
19802
+ const output = [value.output, value.stderr, value.stdout].filter(
19803
+ (candidate) => typeof candidate === "string" && candidate.length > 0
19804
+ ).join("\n");
19805
+ if (sessionId !== null) {
19806
+ return `Process running with session ID ${sessionId}
19807
+
19808
+ Output:
19809
+ ${output}`;
19810
+ }
19811
+ if (exitCode !== null) {
19812
+ return `Process exited with code ${exitCode}
19813
+
19814
+ Output:
19815
+ ${output}`;
19816
+ }
19817
+ throw new Error("Sandbox command did not report a session id or exit code");
19818
+ }
19819
+ function nativeCommandResult(result) {
19820
+ if (typeof result === "string") {
19821
+ return {
19822
+ stdout: execOutput(result),
19823
+ stderr: "",
19824
+ exitCode: parseExecBannerExitCode(result),
19825
+ sessionId: parseExecBannerSessionId(result),
19826
+ wallTimeSeconds: 0
19827
+ };
19828
+ }
19829
+ if (!result || typeof result !== "object") {
19830
+ throw new Error("Sandbox command returned an invalid result");
19831
+ }
19832
+ const value = result;
19833
+ return {
19834
+ stdout: typeof value.stdout === "string" ? value.stdout : typeof value.output === "string" ? value.output : "",
19835
+ stderr: typeof value.stderr === "string" ? value.stderr : "",
19836
+ exitCode: typeof value.exitCode === "number" ? value.exitCode : typeof value.exit_code === "number" ? value.exit_code : null,
19837
+ sessionId: typeof value.sessionId === "number" ? value.sessionId : typeof value.session_id === "number" ? value.session_id : null,
19838
+ wallTimeSeconds: typeof value.wallTimeSeconds === "number" && value.wallTimeSeconds >= 0 ? value.wallTimeSeconds : 0
19839
+ };
19840
+ }
19841
+ function completedCommandBanner(exitCode, output) {
19842
+ return `Process exited with code ${exitCode}
19843
+
19844
+ Output:
19845
+ ${output}`;
19846
+ }
19847
+ function appendBoundedOutput3(current, chunk, maxOutputTokens) {
19848
+ const maxChars = Math.max(512, maxOutputTokens * 4);
19849
+ const combined = current + chunk;
19850
+ return combined.length <= maxChars ? combined : combined.slice(-maxChars);
19851
+ }
19852
+ function singleQuote(value) {
19853
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
19854
+ }
19855
+ function shellMarkerPath(token) {
19856
+ return `${SHELL_MARKER_DIR}/${token}`;
19857
+ }
19858
+ function cancellableGroupLeaderCommand(command, markerPath) {
19859
+ const marker = singleQuote(markerPath);
19860
+ const markerDir = singleQuote(SHELL_MARKER_DIR);
19861
+ return [
19862
+ `__opengeni_marker=${marker}`,
19863
+ "umask 077",
19864
+ `command mkdir -p ${markerDir} || exit 125`,
19865
+ '__opengeni_pid="$$"',
19866
+ `__opengeni_pgid="$(command ps -o pgid= -p "$__opengeni_pid" 2>/dev/null | command tr -d '[:space:]')"`,
19867
+ 'case "$__opengeni_pid:$__opengeni_pgid" in *[!0-9:]*|*:|:*) exit 125 ;; esac',
19868
+ '[ "$__opengeni_pid" -gt 1 ] && [ "$__opengeni_pgid" -gt 1 ] || exit 125',
19869
+ // Never signal a provider/container-wide process group. A cancellable PTY
19870
+ // command must be its group's leader; fail the command closed otherwise.
19871
+ '[ "$__opengeni_pid" = "$__opengeni_pgid" ] || exit 125',
19872
+ `command printf '%s %s\\n' "$__opengeni_pid" "$__opengeni_pgid" > "$__opengeni_marker" || exit 125`,
19873
+ `trap 'command rm -f "$__opengeni_marker"' EXIT`,
19874
+ "(",
19875
+ command,
19876
+ ")",
19877
+ "__opengeni_status=$?",
19878
+ 'exit "$__opengeni_status"'
19879
+ ].join("\n");
19880
+ }
19881
+ function cancellableShellCommand(command, markerPath) {
19882
+ const groupLeaderCommand = cancellableGroupLeaderCommand(command, markerPath);
19883
+ return [
19884
+ '__opengeni_outer_pid="$$"',
19885
+ `__opengeni_outer_pgid="$(command ps -o pgid= -p "$__opengeni_outer_pid" 2>/dev/null | command tr -d '[:space:]')"`,
19886
+ 'case "$__opengeni_outer_pid:$__opengeni_outer_pgid" in *[!0-9:]*|*:|:*) exit 125 ;; esac',
19887
+ 'if [ "$__opengeni_outer_pid" != "$__opengeni_outer_pgid" ]; then',
19888
+ ' __opengeni_setsid="$(command -v setsid 2>/dev/null)"',
19889
+ ' [ -n "$__opengeni_setsid" ] || exit 125',
19890
+ ` exec "$__opengeni_setsid" /bin/sh -c ${singleQuote(groupLeaderCommand)}`,
19891
+ "fi",
19892
+ groupLeaderCommand
19893
+ ].join("\n");
19894
+ }
19895
+ function shellHelperInput(command) {
19896
+ const args = shellHelperArgs(command);
19897
+ return JSON.stringify({
19898
+ cmd: args.cmd,
19899
+ login: args.login,
19900
+ tty: args.tty,
19901
+ yield_time_ms: args.yieldTimeMs,
19902
+ max_output_tokens: args.maxOutputTokens
19903
+ });
19904
+ }
19905
+ function shellHelperArgs(command) {
19906
+ return {
19907
+ cmd: command,
19908
+ login: false,
19909
+ tty: false,
19910
+ yieldTimeMs: SHELL_HELPER_YIELD_MS,
19911
+ maxOutputTokens: 128
19912
+ };
19913
+ }
19914
+ function retainedProcessSession(session, providerSessionId) {
19915
+ return session?.hasRetainedProcess?.(providerSessionId) === true ? session : null;
19916
+ }
19917
+ function safeProcessIdentity(raw) {
19918
+ if (parseExecBannerExitCode(raw) !== 0) return null;
19919
+ const match = execOutput(raw).trim().match(/^(\d+)\s+(\d+)$/);
19920
+ if (!match) return null;
19921
+ const pid = Number.parseInt(match[1], 10);
19922
+ const processGroupId = Number.parseInt(match[2], 10);
19923
+ return Number.isSafeInteger(pid) && Number.isSafeInteger(processGroupId) && pid > 1 && processGroupId > 1 ? { pid, processGroupId } : null;
19924
+ }
19925
+ function identityGuardScript(state, body, missingIdentityExitCode) {
19926
+ const identity = state.identity;
19927
+ if (!identity || !state.token) return `exit ${missingIdentityExitCode}`;
19928
+ const token = singleQuote(state.token);
19929
+ return [
19930
+ `__opengeni_pid=${identity.pid}`,
19931
+ `__opengeni_pgid=${identity.processGroupId}`,
19932
+ `__opengeni_token=${token}`,
19933
+ // The randomized token lands late in the wrapped command line. Procps
19934
+ // otherwise truncates `args` to the terminal width, which can make a live
19935
+ // group look stale and open the quiescence fence without signalling it.
19936
+ '__opengeni_args="$(command ps -ww -o args= -p "$__opengeni_pid" 2>/dev/null)"',
19937
+ 'case "$__opengeni_args" in *"$__opengeni_token"*) ;; *) exit 0 ;; esac',
19938
+ `__opengeni_live_pgid="$(command ps -o pgid= -p "$__opengeni_pid" 2>/dev/null | command tr -d '[:space:]')"`,
19939
+ '[ "$__opengeni_live_pgid" = "$__opengeni_pgid" ] || exit 0',
19940
+ body
19941
+ ].join("\n");
19942
+ }
19943
+ function wrapComputer(computer, track) {
19944
+ const wrapped = Object.create(Object.getPrototypeOf(computer));
19945
+ const wrappedRecord = wrapped;
19946
+ Object.assign(wrapped, computer);
19947
+ for (const method of [
19948
+ "initRun",
19949
+ "screenshot",
19950
+ "click",
19951
+ "doubleClick",
19952
+ "scroll",
19953
+ "type",
19954
+ "wait",
19955
+ "move",
19956
+ "keypress",
19957
+ "drag"
19958
+ ]) {
19959
+ const original = computer[method];
19960
+ if (typeof original !== "function") continue;
19961
+ wrappedRecord[method] = (...args) => track(async () => await original.apply(computer, args));
19962
+ }
19963
+ return wrapped;
19964
+ }
19965
+ var TurnToolCancellationControllerImpl = class {
19966
+ cancelled = false;
19967
+ reason;
19968
+ inFlight = /* @__PURE__ */ new Set();
19969
+ shellSessions = /* @__PURE__ */ new Map();
19970
+ remoteExecs = /* @__PURE__ */ new Set();
19971
+ rawExecInvoke = null;
19972
+ rawWriteInvoke = null;
19973
+ drainPromise = null;
19974
+ signal;
19975
+ onAbort = () => this.cancel(this.signal?.reason);
19976
+ constructor(signal) {
19977
+ this.signal = signal;
19978
+ if (signal?.aborted) {
19979
+ this.cancel(signal.reason);
19980
+ } else {
19981
+ signal?.addEventListener("abort", this.onAbort, { once: true });
19982
+ }
19983
+ }
19984
+ cancel(reason) {
19985
+ if (!this.cancelled) {
19986
+ this.cancelled = true;
19987
+ this.reason = reason;
19988
+ this.signal?.removeEventListener("abort", this.onAbort);
19989
+ }
19990
+ void this.ensureDrain().catch(() => void 0);
19991
+ }
19992
+ async waitForQuiescence() {
19993
+ this.cancel(this.reason);
19994
+ await this.ensureDrain();
19995
+ }
19996
+ runSandboxCommand(session, args) {
19997
+ return this.runSandboxCommandInternal(session, args, false);
19998
+ }
19999
+ async runSandboxCommandStructured(session, args) {
20000
+ return await this.runSandboxCommandInternal(session, args, true);
20001
+ }
20002
+ runSandboxCommandInternal(session, args, structured) {
20003
+ return this.track(async () => {
20004
+ const startedAt = performance.now();
20005
+ if (!session.exec && !session.execCommand) {
20006
+ throw new Error("Sandbox session does not support command execution");
20007
+ }
20008
+ const lifecycleRunContext = {};
20009
+ const invokeExecNative = async (input) => {
20010
+ const parsed = parsedObject(input);
20011
+ if (!parsed || typeof parsed.cmd !== "string") {
20012
+ throw new Error("Sandbox lifecycle command input was invalid");
20013
+ }
20014
+ const commandArgs = {
20015
+ cmd: parsed.cmd,
20016
+ ...typeof parsed.workdir === "string" ? { workdir: parsed.workdir } : {},
20017
+ ...typeof parsed.shell === "string" ? { shell: parsed.shell } : {},
20018
+ ...typeof parsed.login === "boolean" ? { login: parsed.login } : {},
20019
+ ...typeof parsed.tty === "boolean" ? { tty: parsed.tty } : {},
20020
+ ...typeof parsed.yield_time_ms === "number" ? { yieldTimeMs: parsed.yield_time_ms } : {},
20021
+ ...typeof parsed.max_output_tokens === "number" ? { maxOutputTokens: parsed.max_output_tokens } : {},
20022
+ ...typeof parsed.run_as === "string" ? { runAs: parsed.run_as } : {}
20023
+ };
20024
+ const result = session.supportsPty?.() !== false && session.execCommand ? await session.execCommand(commandArgs) : session.exec ? await session.exec(commandArgs) : await session.execCommand(commandArgs);
20025
+ return result;
20026
+ };
20027
+ const invokeExec = async (_runContext, input) => nativeCommandBanner(await invokeExecNative(input));
20028
+ const invokeWrite = session.writeStdin || session.writeStdinForProcessControl ? async (_runContext, input) => {
20029
+ const parsed = parsedObject(input);
20030
+ if (!parsed || typeof parsed.session_id !== "number") {
20031
+ throw new Error("Sandbox lifecycle stdin input was invalid");
20032
+ }
20033
+ const directArgs = {
20034
+ sessionId: parsed.session_id,
20035
+ ...typeof parsed.chars === "string" ? { chars: parsed.chars } : {},
20036
+ ...typeof parsed.yield_time_ms === "number" ? { yieldTimeMs: parsed.yield_time_ms } : {},
20037
+ ...typeof parsed.max_output_tokens === "number" ? { maxOutputTokens: parsed.max_output_tokens } : {}
20038
+ };
20039
+ if (session.hasRetainedProcess?.(parsed.session_id) === true && session.writeStdinForProcessControl) {
20040
+ return await session.writeStdinForProcessControl(directArgs);
20041
+ }
20042
+ if (!session.writeStdin) {
20043
+ throw new Error(
20044
+ "Sandbox lifecycle command yielded an unretained session without stdin support"
20045
+ );
20046
+ }
20047
+ return await session.writeStdin(directArgs);
20048
+ } : null;
20049
+ const correlationId = `turn_lifecycle_${crypto.randomUUID()}`;
20050
+ const useRemoteOpCancellation = Boolean(session.cancelExecCommand) && session.supportsPty?.() === false;
20051
+ const remoteExec = session.cancelExecCommand && useRemoteOpCancellation ? this.registerRemoteExec(
20052
+ { cancelExecCommand: session.cancelExecCommand.bind(session) },
20053
+ `${sanitizeOpIdToken(correlationId)}:0`
20054
+ ) : null;
20055
+ const markerPath = shellMarkerPath(crypto.randomUUID());
20056
+ const commandInput = JSON.stringify({
20057
+ cmd: useRemoteOpCancellation ? args.cmd : cancellableShellCommand(args.cmd, markerPath),
20058
+ ...args.workdir ? { workdir: args.workdir } : {},
20059
+ ...args.shell ? { shell: args.shell } : {},
20060
+ ...args.login !== void 0 ? { login: args.login } : {},
20061
+ ...args.runAs ? { run_as: args.runAs } : {},
20062
+ tty: useRemoteOpCancellation ? args.tty : true,
20063
+ yield_time_ms: useRemoteOpCancellation ? args.yieldTimeMs : cappedYield(args.yieldTimeMs, TURN_EXEC_YIELD_MS),
20064
+ ...args.maxOutputTokens !== void 0 ? { max_output_tokens: args.maxOutputTokens } : {}
20065
+ });
20066
+ let initialNative;
20067
+ try {
20068
+ initialNative = await runWithToolCallCorrelation(
20069
+ correlationId,
20070
+ async () => await invokeExecNative(commandInput)
20071
+ );
20072
+ } finally {
20073
+ remoteExec?.settle();
20074
+ }
20075
+ const initial = nativeCommandBanner(initialNative);
20076
+ const initialResult = nativeCommandResult(initialNative);
20077
+ if (useRemoteOpCancellation) {
20078
+ if (!structured) return initial;
20079
+ if (initialResult.exitCode === null) {
20080
+ throw new Error("Sandbox command did not report a terminal exit code");
20081
+ }
20082
+ return {
20083
+ stdout: initialResult.stdout,
20084
+ stderr: initialResult.stderr,
20085
+ exitCode: initialResult.exitCode,
20086
+ wallTimeSeconds: Math.max(
20087
+ initialResult.wallTimeSeconds,
20088
+ (performance.now() - startedAt) / 1e3
20089
+ )
20090
+ };
20091
+ }
20092
+ const sessionId = parseExecBannerSessionId(initial);
20093
+ if (sessionId === null) {
20094
+ if (!structured) return initial;
20095
+ if (initialResult.exitCode === null) {
20096
+ throw new Error("Sandbox command did not report a terminal exit code");
20097
+ }
20098
+ return {
20099
+ stdout: initialResult.stdout,
20100
+ stderr: initialResult.stderr,
20101
+ exitCode: initialResult.exitCode,
20102
+ wallTimeSeconds: Math.max(
20103
+ initialResult.wallTimeSeconds,
20104
+ (performance.now() - startedAt) / 1e3
20105
+ )
20106
+ };
20107
+ }
20108
+ if (!invokeWrite) {
20109
+ throw new Error("Sandbox lifecycle command yielded without stdin support");
20110
+ }
20111
+ const token = markerPath.slice(markerPath.lastIndexOf("/") + 1);
20112
+ const state = {
20113
+ sessionId,
20114
+ markerPath,
20115
+ token,
20116
+ runContext: lifecycleRunContext,
20117
+ execInvoke: invokeExec,
20118
+ writeInvoke: invokeWrite,
20119
+ processSession: retainedProcessSession(session, sessionId),
20120
+ identity: null,
20121
+ identityValidated: false,
20122
+ cancellation: null
20123
+ };
20124
+ this.shellSessions.set(sessionId, state);
20125
+ const maxOutputTokens = args.maxOutputTokens ?? 2e4;
20126
+ const initialOutput = execOutput(initial);
20127
+ let output = initialOutput ? appendBoundedOutput3("", initialOutput, maxOutputTokens) : "";
20128
+ let stdout = appendBoundedOutput3("", initialResult.stdout, maxOutputTokens);
20129
+ const stderr = appendBoundedOutput3("", initialResult.stderr, maxOutputTokens);
20130
+ while (true) {
20131
+ if (this.cancelled) throw cancellationError(this.reason);
20132
+ const next = await invokeWrite(
20133
+ lifecycleRunContext,
20134
+ JSON.stringify({
20135
+ session_id: sessionId,
20136
+ chars: "",
20137
+ yield_time_ms: TURN_WRITE_YIELD_MS,
20138
+ max_output_tokens: maxOutputTokens
20139
+ }),
20140
+ void 0
20141
+ );
20142
+ if (typeof next !== "string") {
20143
+ throw new Error("Sandbox lifecycle stdin returned an invalid result");
20144
+ }
20145
+ const nextOutput = execOutput(next);
20146
+ if (nextOutput) {
20147
+ output = appendBoundedOutput3(output, nextOutput, maxOutputTokens);
20148
+ stdout = appendBoundedOutput3(stdout, nextOutput, maxOutputTokens);
20149
+ }
20150
+ const exitCode = parseExecBannerExitCode(next);
20151
+ if (exitCode !== null) {
20152
+ this.shellSessions.delete(sessionId);
20153
+ if (!structured) return completedCommandBanner(exitCode, output);
20154
+ return {
20155
+ stdout,
20156
+ stderr,
20157
+ exitCode,
20158
+ wallTimeSeconds: (performance.now() - startedAt) / 1e3
20159
+ };
20160
+ }
20161
+ await delay(SHELL_POLL_MS);
20162
+ }
20163
+ });
20164
+ }
20165
+ wrapTools(tools, session) {
20166
+ const structuralTools = tools;
20167
+ for (const tool of structuralTools) {
20168
+ if (tool.type !== "function") continue;
20169
+ const functionTool = tool;
20170
+ if (functionTool.name === "exec_command") this.rawExecInvoke = functionTool.invoke;
20171
+ if (functionTool.name === "write_stdin") this.rawWriteInvoke = functionTool.invoke;
20172
+ }
20173
+ return structuralTools.map((tool) => {
20174
+ if (tool.type === "function") return this.wrapFunctionTool(tool, session);
20175
+ if (tool.type === "apply_patch") return this.wrapApplyPatchTool(tool);
20176
+ if (tool.type === "computer") return this.wrapComputerTool(tool);
20177
+ return tool;
20178
+ });
20179
+ }
20180
+ wrapFunctionTool(tool, cancellationSession) {
20181
+ if (tool.name === "exec_command") {
20182
+ return {
20183
+ ...tool,
20184
+ invoke: (runContext, input, details) => this.track(async () => {
20185
+ const parsed = parsedObject(input);
20186
+ if (!parsed || typeof parsed.cmd !== "string") {
20187
+ return await this.correlatedInvoke(tool.invoke, runContext, input, details);
20188
+ }
20189
+ const token = crypto.randomUUID();
20190
+ const markerPath = shellMarkerPath(token);
20191
+ const correlationId = details?.toolCall?.callId ?? `turn_exec_${token}`;
20192
+ const cancelExecCommand = cancellationSession?.cancelExecCommand;
20193
+ const useRemoteOpCancellation = Boolean(cancelExecCommand) && cancellationSession?.supportsPty?.() === false;
20194
+ const remoteExec = cancelExecCommand && useRemoteOpCancellation ? this.registerRemoteExec(
20195
+ { cancelExecCommand: cancelExecCommand.bind(cancellationSession) },
20196
+ `${sanitizeOpIdToken(correlationId)}:0`
20197
+ ) : null;
20198
+ const cancellableInput = useRemoteOpCancellation ? input : JSON.stringify({
20199
+ ...parsed,
20200
+ cmd: cancellableShellCommand(parsed.cmd, markerPath),
20201
+ // The SDK's only provider-neutral live-process interrupt is a PTY.
20202
+ // The process marker provides TERM/KILL escalation when Ctrl-C is
20203
+ // ignored; the short yield exposes the provider session promptly.
20204
+ tty: true,
20205
+ yield_time_ms: cappedYield(parsed.yield_time_ms, TURN_EXEC_YIELD_MS)
20206
+ });
20207
+ let output;
20208
+ try {
20209
+ output = await runWithToolCallCorrelation(
20210
+ correlationId,
20211
+ async () => await tool.invoke(runContext, cancellableInput, details)
20212
+ );
20213
+ } finally {
20214
+ remoteExec?.settle();
20215
+ }
20216
+ if (typeof output !== "string") return output;
20217
+ const sessionId = useRemoteOpCancellation ? null : parseExecBannerSessionId(output);
20218
+ if (sessionId !== null) {
20219
+ this.shellSessions.set(sessionId, {
20220
+ sessionId,
20221
+ markerPath,
20222
+ token,
20223
+ runContext,
20224
+ execInvoke: tool.invoke,
20225
+ writeInvoke: this.rawWriteInvoke,
20226
+ processSession: retainedProcessSession(cancellationSession, sessionId),
20227
+ identity: null,
20228
+ identityValidated: false,
20229
+ cancellation: null
20230
+ });
20231
+ }
20232
+ return output;
20233
+ })
20234
+ };
20235
+ }
20236
+ if (tool.name === "write_stdin") {
20237
+ return {
20238
+ ...tool,
20239
+ invoke: (runContext, input, details) => this.track(async () => {
20240
+ const parsed = parsedObject(input);
20241
+ const sessionId = parsed && typeof parsed.session_id === "number" && Number.isSafeInteger(parsed.session_id) ? parsed.session_id : null;
20242
+ const cappedInput = parsed ? JSON.stringify({
20243
+ ...parsed,
20244
+ yield_time_ms: cappedYield(parsed.yield_time_ms, TURN_WRITE_YIELD_MS)
20245
+ }) : input;
20246
+ const directProcessSession = sessionId === null ? null : retainedProcessSession(cancellationSession, sessionId);
20247
+ const output = sessionId !== null && directProcessSession?.writeStdinForProcessMutation ? await directProcessSession.writeStdinForProcessMutation({
20248
+ sessionId,
20249
+ ...typeof parsed?.chars === "string" ? { chars: parsed.chars } : {},
20250
+ yieldTimeMs: cappedYield(parsed?.yield_time_ms, TURN_WRITE_YIELD_MS),
20251
+ ...typeof parsed?.max_output_tokens === "number" ? { maxOutputTokens: parsed.max_output_tokens } : {}
20252
+ }) : await tool.invoke(runContext, cappedInput, details);
20253
+ if (sessionId !== null && typeof output === "string") {
20254
+ if (isExecSessionLostBanner(output, sessionId)) {
20255
+ this.shellSessions.delete(sessionId);
20256
+ } else if (parseExecBannerSessionId(output) === sessionId) {
20257
+ if (!this.shellSessions.has(sessionId)) {
20258
+ this.shellSessions.set(sessionId, {
20259
+ sessionId,
20260
+ markerPath: null,
20261
+ token: null,
20262
+ runContext,
20263
+ execInvoke: this.rawExecInvoke ?? tool.invoke,
20264
+ writeInvoke: tool.invoke,
20265
+ processSession: directProcessSession,
20266
+ identity: null,
20267
+ identityValidated: false,
20268
+ cancellation: null
20269
+ });
20270
+ }
20271
+ } else if (parseExecBannerExitCode(output) !== null) {
20272
+ this.shellSessions.delete(sessionId);
20273
+ }
20274
+ }
20275
+ return output;
20276
+ })
20277
+ };
20278
+ }
20279
+ return {
20280
+ ...tool,
20281
+ invoke: (runContext, input, details) => this.track(async () => await tool.invoke(runContext, input, details))
20282
+ };
20283
+ }
20284
+ wrapApplyPatchTool(tool) {
20285
+ const editor = tool.editor;
20286
+ const wrappedEditor = {
20287
+ createFile: (operation, context) => this.track(async () => await editor.createFile(operation, context)),
20288
+ updateFile: (operation, context) => this.track(async () => await editor.updateFile(operation, context)),
20289
+ deleteFile: (operation, context) => this.track(async () => await editor.deleteFile(operation, context))
20290
+ };
20291
+ return { ...tool, editor: wrappedEditor };
20292
+ }
20293
+ wrapComputerTool(tool) {
20294
+ const computer = tool.computer;
20295
+ if (typeof computer === "function") {
20296
+ const createComputer = computer;
20297
+ return {
20298
+ ...tool,
20299
+ computer: async (...args) => wrapComputer(
20300
+ await this.track(async () => await createComputer(...args)),
20301
+ (operation) => this.track(operation)
20302
+ )
20303
+ };
20304
+ }
20305
+ if (computer && typeof computer === "object" && "create" in computer) {
20306
+ const provider = computer;
20307
+ if (typeof provider.create !== "function") return tool;
20308
+ const createComputer = provider.create;
20309
+ return {
20310
+ ...tool,
20311
+ computer: {
20312
+ ...provider,
20313
+ create: async (...args) => wrapComputer(
20314
+ await this.track(async () => await createComputer(...args)),
20315
+ (operation) => this.track(operation)
20316
+ )
20317
+ }
20318
+ };
20319
+ }
20320
+ if (!computer || typeof computer !== "object") return tool;
20321
+ return {
20322
+ ...tool,
20323
+ computer: wrapComputer(computer, (operation) => this.track(operation))
20324
+ };
20325
+ }
20326
+ async correlatedInvoke(invoke, runContext, input, details) {
20327
+ const callId = details?.toolCall?.callId;
20328
+ return callId ? await runWithToolCallCorrelation(
20329
+ callId,
20330
+ async () => await invoke(runContext, input, details)
20331
+ ) : await invoke(runContext, input, details);
20332
+ }
20333
+ track(operation) {
20334
+ if (this.cancelled) return Promise.reject(cancellationError(this.reason));
20335
+ const promise = Promise.resolve().then(async () => {
20336
+ if (this.cancelled) throw cancellationError(this.reason);
20337
+ return await operation();
20338
+ });
20339
+ this.inFlight.add(promise);
20340
+ void promise.finally(() => {
20341
+ this.inFlight.delete(promise);
20342
+ }).catch(() => void 0);
20343
+ return promise;
20344
+ }
20345
+ ensureDrain() {
20346
+ this.drainPromise ??= this.drain();
20347
+ return this.drainPromise;
20348
+ }
20349
+ async drain() {
20350
+ while (true) {
20351
+ const remoteCancellations = [...this.remoteExecs].map(async (exec) => await exec.cancel());
20352
+ const inFlight = [...this.inFlight];
20353
+ if (inFlight.length > 0 || remoteCancellations.length > 0) {
20354
+ await Promise.allSettled([...inFlight, ...remoteCancellations]);
20355
+ }
20356
+ const sessions = [...this.shellSessions.values()];
20357
+ if (sessions.length > 0) {
20358
+ await Promise.all(sessions.map(async (session) => await this.cancelShellSession(session)));
20359
+ }
20360
+ if (this.inFlight.size === 0 && this.shellSessions.size === 0 && this.remoteExecs.size === 0)
20361
+ return;
20362
+ }
20363
+ }
20364
+ registerRemoteExec(session, opId) {
20365
+ let resolveSettled;
20366
+ const settledPromise = new Promise((resolve5) => {
20367
+ resolveSettled = resolve5;
20368
+ });
20369
+ let cancellation = null;
20370
+ const entry = {
20371
+ settled: false,
20372
+ settledPromise,
20373
+ settle: () => {
20374
+ if (entry.settled) return;
20375
+ entry.settled = true;
20376
+ resolveSettled();
20377
+ this.remoteExecs.delete(entry);
20378
+ },
20379
+ cancel: () => {
20380
+ cancellation ??= (async () => {
20381
+ while (!entry.settled) {
20382
+ const attempt = session.cancelExecCommand(opId).catch(() => false);
20383
+ await Promise.race([attempt, entry.settledPromise]);
20384
+ await Promise.race([entry.settledPromise, delay(SHELL_POLL_MS)]);
20385
+ }
20386
+ })();
20387
+ return cancellation;
20388
+ }
20389
+ };
20390
+ this.remoteExecs.add(entry);
20391
+ return entry;
20392
+ }
20393
+ cancelShellSession(state) {
20394
+ state.cancellation ??= this.cancelShellSessionOnce(state).finally(() => {
20395
+ state.cancellation = null;
20396
+ });
20397
+ return state.cancellation;
20398
+ }
20399
+ async cancelShellSessionOnce(state) {
20400
+ if (!state.markerPath) {
20401
+ while (true) {
20402
+ if ((state.writeInvoke || state.processSession?.writeStdinForProcessControl) && await this.rawWrite(state, "", SHELL_POLL_MS)) {
20403
+ await this.forgetShellSessionAfterExactSettlement(state);
20404
+ return;
20405
+ }
20406
+ await delay(SHELL_POLL_MS);
20407
+ }
20408
+ }
20409
+ while (!state.identity) {
20410
+ state.identity = await this.readIdentity(state);
20411
+ if (state.identity) break;
20412
+ if (state.writeInvoke || state.processSession?.writeStdinForProcessControl) {
20413
+ const completed = await this.rawWrite(state, "", SHELL_POLL_MS);
20414
+ if (completed) {
20415
+ await this.forgetShellSessionAfterExactSettlement(state);
20416
+ return;
20417
+ }
20418
+ }
20419
+ await delay(SHELL_POLL_MS);
20420
+ }
20421
+ if (!await this.identityAlive(state)) {
20422
+ await this.forgetShellSessionAfterExactSettlement(state);
20423
+ return;
20424
+ }
20425
+ if (!state.processSession && state.writeInvoke) {
20426
+ await this.rawWrite(state, "", SHELL_POLL_MS);
20427
+ if (!await this.identityAlive(state)) {
20428
+ await this.forgetShellSessionAfterExactSettlement(state);
20429
+ return;
20430
+ }
20431
+ }
20432
+ await this.signalIdentity(state, "TERM");
20433
+ for (let attempt = 0; attempt < SHELL_GRACEFUL_POLLS; attempt++) {
20434
+ if (!state.processSession && state.writeInvoke) {
20435
+ await this.rawWrite(state, "", SHELL_POLL_MS);
20436
+ }
20437
+ if (!await this.identityAlive(state)) {
20438
+ await this.forgetShellSessionAfterExactSettlement(state);
20439
+ return;
20440
+ }
20441
+ }
20442
+ await this.signalIdentity(state, "KILL");
20443
+ while (await this.identityAlive(state)) {
20444
+ if (!state.processSession && state.writeInvoke) {
20445
+ await this.rawWrite(state, "", SHELL_POLL_MS);
20446
+ }
20447
+ await this.signalIdentity(state, "KILL");
20448
+ await delay(SHELL_POLL_MS);
20449
+ }
20450
+ await this.forgetShellSessionAfterExactSettlement(state);
20451
+ }
20452
+ async forgetShellSessionAfterExactSettlement(state) {
20453
+ while (state.processSession?.hasRetainedProcess?.(state.sessionId) === true) {
20454
+ if (await this.rawWrite(state, "", SHELL_POLL_MS)) break;
20455
+ await delay(SHELL_POLL_MS);
20456
+ }
20457
+ this.shellSessions.delete(state.sessionId);
20458
+ }
20459
+ async rawWrite(state, chars, yieldTimeMs) {
20460
+ if (!state.writeInvoke && !state.processSession?.writeStdinForProcessControl) return false;
20461
+ try {
20462
+ const output = state.processSession?.writeStdinForProcessControl ? await state.processSession.writeStdinForProcessControl({
20463
+ sessionId: state.sessionId,
20464
+ chars,
20465
+ yieldTimeMs,
20466
+ maxOutputTokens: 128
20467
+ }) : await state.writeInvoke(
20468
+ state.runContext,
20469
+ JSON.stringify({
20470
+ session_id: state.sessionId,
20471
+ chars,
20472
+ yield_time_ms: yieldTimeMs,
20473
+ max_output_tokens: 128
20474
+ }),
20475
+ void 0
20476
+ );
20477
+ if (typeof output !== "string") return false;
20478
+ if (isExecSessionLostBanner(output, state.sessionId)) return true;
20479
+ if (parseExecBannerSessionId(output) === state.sessionId) return false;
20480
+ if (parseExecBannerExitCode(output) !== null) return true;
20481
+ } catch {
20482
+ }
20483
+ return false;
20484
+ }
20485
+ async readIdentity(state) {
20486
+ if (!state.markerPath) return null;
20487
+ try {
20488
+ const command = `command cat ${singleQuote(state.markerPath)} 2>/dev/null`;
20489
+ const output = state.processSession?.execCommandForProcessControl ? await state.processSession.execCommandForProcessControl(
20490
+ state.sessionId,
20491
+ shellHelperArgs(command)
20492
+ ) : await state.execInvoke(state.runContext, shellHelperInput(command), void 0);
20493
+ return typeof output === "string" ? safeProcessIdentity(output) : null;
20494
+ } catch {
20495
+ return null;
20496
+ }
20497
+ }
20498
+ async identityAlive(state) {
20499
+ const script = state.identityValidated ? [
20500
+ `__opengeni_pgid=${state.identity?.processGroupId ?? 0}`,
20501
+ 'command kill -0 "-$__opengeni_pgid" 2>/dev/null && exit 75',
20502
+ "exit 0"
20503
+ ].join("\n") : identityGuardScript(
20504
+ state,
20505
+ 'command kill -0 "-$__opengeni_pgid" 2>/dev/null && exit 75\nexit 0',
20506
+ 76
20507
+ );
20508
+ try {
20509
+ const output = state.processSession?.execCommandForProcessControl ? await state.processSession.execCommandForProcessControl(
20510
+ state.sessionId,
20511
+ shellHelperArgs(script)
20512
+ ) : await state.execInvoke(state.runContext, shellHelperInput(script), void 0);
20513
+ if (typeof output !== "string") return true;
20514
+ const exitCode = parseExecBannerExitCode(output);
20515
+ if (exitCode === 75) {
20516
+ state.identityValidated = true;
20517
+ return true;
20518
+ }
20519
+ return exitCode !== 0;
20520
+ } catch {
20521
+ return true;
20522
+ }
20523
+ }
20524
+ async signalIdentity(state, signal) {
20525
+ const script = state.identityValidated ? `command kill -${signal} "-${state.identity?.processGroupId ?? 0}" 2>/dev/null || exit 76` : identityGuardScript(
20526
+ state,
20527
+ `command kill -${signal} "-$__opengeni_pgid" 2>/dev/null || exit 76
20528
+ exit 0`,
20529
+ 76
20530
+ );
20531
+ try {
20532
+ if (state.processSession?.execCommandForProcessControl) {
20533
+ await state.processSession.execCommandForProcessControl(
20534
+ state.sessionId,
20535
+ shellHelperArgs(script)
20536
+ );
20537
+ } else {
20538
+ await state.execInvoke(state.runContext, shellHelperInput(script), void 0);
20539
+ }
20540
+ } catch {
20541
+ }
20542
+ }
20543
+ };
20544
+ function createTurnToolCancellationController(signal) {
20545
+ return new TurnToolCancellationControllerImpl(signal);
20546
+ }
20547
+ function wrapCapabilityToolsForTurnCancellation(capability, controller) {
20548
+ const originalTools = capability.tools;
20549
+ capability.tools = function() {
20550
+ return controller.wrapTools(originalTools.call(this), this._session);
20551
+ };
20552
+ }
20553
+
18831
20554
  // src/sandbox/channel-a.ts
18832
20555
  var ChannelAValidationError = class extends Error {
18833
20556
  constructor(message) {
@@ -18953,11 +20676,11 @@ var SandboxChannelAService = class {
18953
20676
  const depthArg = Math.max(1, req.depth);
18954
20677
  const maxCommandEntries = req.maxEntries + 1;
18955
20678
  const gnuPrint = `-printf '%y\\t%s\\t%T@\\t%m\\t%p\\0'`;
18956
- const gnuSelector = pruneNames.length ? `\\( -type d \\( ${pruneNames.map((name) => `-name ${shellQuote5(name)}`).join(" -o ")} \\) ${gnuPrint} -prune \\) -o ${gnuPrint}` : gnuPrint;
20679
+ const gnuSelector = pruneNames.length ? `\\( -type d \\( ${pruneNames.map((name) => `-name ${shellQuote6(name)}`).join(" -o ")} \\) ${gnuPrint} -prune \\) -o ${gnuPrint}` : gnuPrint;
18957
20680
  const gnuVisibleSelector = req.includeHidden ? gnuSelector : `\\( -path '*/.*' -prune \\) -o \\( ${gnuSelector} \\)`;
18958
20681
  const gnuFind = `find ${findRoot} -mindepth 1 -maxdepth ${depthArg} \\( ${gnuVisibleSelector} \\) 2>/dev/null`;
18959
20682
  const portableHiddenGuard = req.includeHidden ? "" : `if [[ "$base" == .* ]]; then continue; fi;`;
18960
- const portablePruneCase = pruneNames.length ? `case "$base" in ${pruneNames.map(shellQuote5).join("|")}) pruned=1 ;; *) pruned=0 ;; esac;` : "pruned=0;";
20683
+ const portablePruneCase = pruneNames.length ? `case "$base" in ${pruneNames.map(shellQuote6).join("|")}) pruned=1 ;; *) pruned=0 ;; esac;` : "pruned=0;";
18961
20684
  const portableWalk = [
18962
20685
  "shopt -s nullglob dotglob; count=0; stop=0;",
18963
20686
  'walk() { local dir="$1" level="$2" p base t size mtime mode pruned;',
@@ -18986,12 +20709,12 @@ var SandboxChannelAService = class {
18986
20709
  `if [ "$records" -ge ${req.maxEntries} ] || [ $((bytes + record_bytes + 1)) -gt ${FS_LIST_MAX_OUTPUT_BYTES} ]; then truncated=1; break; fi;`,
18987
20710
  `printf '%s\\0' "$record"; bytes=$((bytes + record_bytes + 1)); records=$((records + 1));`,
18988
20711
  "done;",
18989
- `if [ "$truncated" -eq 1 ]; then printf '%s\\0' ${shellQuote5(FS_LIST_TRUNCATED_MARKER)}; fi`
20712
+ `if [ "$truncated" -eq 1 ]; then printf '%s\\0' ${shellQuote6(FS_LIST_TRUNCATED_MARKER)}; fi`
18990
20713
  ].join(" ");
18991
20714
  const findCommand = [
18992
20715
  `{ ${rawFindCommand}; } | { ${boundedOutput}; };`,
18993
20716
  "producer_status=${PIPESTATUS[0]};",
18994
- `if [ "$producer_status" -ne 0 ] && [ "$producer_status" -ne 141 ]; then printf '%s\\0' ${shellQuote5(FS_LIST_TRUNCATED_MARKER)}; fi`
20717
+ `if [ "$producer_status" -ne 0 ] && [ "$producer_status" -ne 141 ]; then printf '%s\\0' ${shellQuote6(FS_LIST_TRUNCATED_MARKER)}; fi`
18995
20718
  ].join(" ");
18996
20719
  const { stdout, exitCode } = await this.runInConfinedDirectory(root, {
18997
20720
  cmd: internalBashCommand(findCommand),
@@ -19084,11 +20807,11 @@ var SandboxChannelAService = class {
19084
20807
  async assertConfinedExistingDirectory(path) {
19085
20808
  const root = this.workspaceRoot || ".";
19086
20809
  const abs = this.joinRoot(path);
19087
- const rejectLink = path ? `test ! -L ${shellQuote5(abs)} || { printf '__OPENGENI_FS_SYMLINK__'; exit 68; }` : ":";
20810
+ const rejectLink = path ? `test ! -L ${shellQuote6(abs)} || { printf '__OPENGENI_FS_SYMLINK__'; exit 68; }` : ":";
19088
20811
  const script = [
19089
- `root=$(realpath -e -- ${shellQuote5(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
20812
+ `root=$(realpath -e -- ${shellQuote6(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19090
20813
  rejectLink,
19091
- `target=$(realpath -e -- ${shellQuote5(abs)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
20814
+ `target=$(realpath -e -- ${shellQuote6(abs)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19092
20815
  `case "$target" in "$root"|"$root"/*) ;; *) printf '__OPENGENI_FS_ESCAPE__'; exit 67 ;; esac`,
19093
20816
  `test -d "$target" || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19094
20817
  `printf '__OPENGENI_FS_CONFINED_OK__'`
@@ -19099,13 +20822,13 @@ var SandboxChannelAService = class {
19099
20822
  async assertConfinedMutationParent(path, options) {
19100
20823
  const root = this.workspaceRoot || ".";
19101
20824
  const abs = this.joinRoot(path);
19102
- const rejectLink = options.rejectFinalSymlink ? `test ! -L ${shellQuote5(abs)} || { printf '__OPENGENI_FS_SYMLINK__'; exit 68; }` : ":";
20825
+ const rejectLink = options.rejectFinalSymlink ? `test ! -L ${shellQuote6(abs)} || { printf '__OPENGENI_FS_SYMLINK__'; exit 68; }` : ":";
19103
20826
  const locateParent = options.allowMissingParents ? 'probe="$parent"; while [ ! -e "$probe" ] && [ ! -L "$probe" ]; do next=$(dirname -- "$probe"); [ "$next" != "$probe" ] || break; probe="$next"; done' : 'probe="$parent"';
19104
20827
  const requireParent = options.allowMissingParents ? ":" : 'test -d "$parent" || { printf "__OPENGENI_FS_NOT_FOUND__"; exit 66; }';
19105
20828
  const script = [
19106
- `root=$(realpath -e -- ${shellQuote5(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
20829
+ `root=$(realpath -e -- ${shellQuote6(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19107
20830
  rejectLink,
19108
- `parent=$(dirname -- ${shellQuote5(abs)})`,
20831
+ `parent=$(dirname -- ${shellQuote6(abs)})`,
19109
20832
  locateParent,
19110
20833
  `target=$(realpath -e -- "$probe") || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19111
20834
  `case "$target" in "$root"|"$root"/*) ;; *) printf '__OPENGENI_FS_ESCAPE__'; exit 67 ;; esac`,
@@ -19142,8 +20865,8 @@ var SandboxChannelAService = class {
19142
20865
  const root = this.workspaceRoot || ".";
19143
20866
  const abs = this.joinRoot(path);
19144
20867
  const script = [
19145
- `root=$(realpath -e -- ${shellQuote5(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19146
- `exec 3<${shellQuote5(abs)} || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
20868
+ `root=$(realpath -e -- ${shellQuote6(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
20869
+ `exec 3<${shellQuote6(abs)} || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19147
20870
  `target=$(readlink -f -- "/proc/$$/fd/3") || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19148
20871
  `case "$target" in "$root"|"$root"/*) ;; *) printf '__OPENGENI_FS_ESCAPE__'; exit 67 ;; esac`,
19149
20872
  `test -f "$target" || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
@@ -19212,7 +20935,7 @@ var SandboxChannelAService = class {
19212
20935
  });
19213
20936
  if (!req.overwrite) {
19214
20937
  const { exitCode: exitCode2 } = await this.run({
19215
- cmd: `test -e ${shellQuote5(abs)} || test -L ${shellQuote5(abs)}`
20938
+ cmd: `test -e ${shellQuote6(abs)} || test -L ${shellQuote6(abs)}`
19216
20939
  });
19217
20940
  if (exitCode2 === 0) {
19218
20941
  throw new ChannelAConflictError(`path exists and overwrite is false: ${path}`);
@@ -19220,7 +20943,7 @@ var SandboxChannelAService = class {
19220
20943
  }
19221
20944
  if (req.createParents) {
19222
20945
  const dir = dirnameAbs(abs);
19223
- if (dir) await this.run({ cmd: `mkdir -p ${shellQuote5(dir)}` });
20946
+ if (dir) await this.run({ cmd: `mkdir -p ${shellQuote6(dir)}` });
19224
20947
  await this.assertConfinedMutationParent(path, {
19225
20948
  allowMissingParents: false,
19226
20949
  rejectFinalSymlink: true
@@ -19228,7 +20951,7 @@ var SandboxChannelAService = class {
19228
20951
  }
19229
20952
  const b64 = bytes.toString("base64");
19230
20953
  const { exitCode, stderr } = await this.run({
19231
- cmd: `printf %s ${shellQuote5(b64)} | base64 -d > ${shellQuote5(abs)}`
20954
+ cmd: `printf %s ${shellQuote6(b64)} | base64 -d > ${shellQuote6(abs)}`
19232
20955
  });
19233
20956
  if (exitCode !== null && exitCode !== 0) {
19234
20957
  if (req.encoding !== "base64" && this.session.createEditor) {
@@ -19269,7 +20992,7 @@ var SandboxChannelAService = class {
19269
20992
  rejectFinalSymlink: false
19270
20993
  });
19271
20994
  const flag = req.recursive ? "-rf" : "-f";
19272
- const { exitCode, stderr } = await this.run({ cmd: `rm ${flag} ${shellQuote5(abs)}` });
20995
+ const { exitCode, stderr } = await this.run({ cmd: `rm ${flag} ${shellQuote6(abs)}` });
19273
20996
  if (exitCode !== null && exitCode !== 0) {
19274
20997
  throw new ChannelAValidationError(
19275
20998
  `failed to delete ${path}: ${stderr || `exit ${exitCode}`}`
@@ -19294,7 +21017,7 @@ var SandboxChannelAService = class {
19294
21017
  });
19295
21018
  if (!req.overwrite) {
19296
21019
  const { exitCode: exitCode2 } = await this.run({
19297
- cmd: `test -e ${shellQuote5(newAbs)} || test -L ${shellQuote5(newAbs)}`
21020
+ cmd: `test -e ${shellQuote6(newAbs)} || test -L ${shellQuote6(newAbs)}`
19298
21021
  });
19299
21022
  if (exitCode2 === 0) {
19300
21023
  throw new ChannelAConflictError(`destination exists and overwrite is false: ${newPath}`);
@@ -19302,7 +21025,7 @@ var SandboxChannelAService = class {
19302
21025
  }
19303
21026
  if (req.createParents) {
19304
21027
  const dir = dirnameAbs(newAbs);
19305
- if (dir) await this.run({ cmd: `mkdir -p ${shellQuote5(dir)}` });
21028
+ if (dir) await this.run({ cmd: `mkdir -p ${shellQuote6(dir)}` });
19306
21029
  await this.assertConfinedMutationParent(newPath, {
19307
21030
  allowMissingParents: false,
19308
21031
  rejectFinalSymlink: true
@@ -19310,7 +21033,7 @@ var SandboxChannelAService = class {
19310
21033
  }
19311
21034
  const flag = req.overwrite ? "-f " : "";
19312
21035
  const { exitCode, stderr } = await this.run({
19313
- cmd: `mv ${flag}${shellQuote5(abs)} ${shellQuote5(newAbs)}`
21036
+ cmd: `mv ${flag}${shellQuote6(abs)} ${shellQuote6(newAbs)}`
19314
21037
  });
19315
21038
  if (exitCode !== null && exitCode !== 0) {
19316
21039
  throw new ChannelAValidationError(
@@ -19335,7 +21058,7 @@ var SandboxChannelAService = class {
19335
21058
  rejectFinalSymlink: true
19336
21059
  });
19337
21060
  const flag = req.recursive ? "-p " : "";
19338
- const { exitCode, stderr } = await this.run({ cmd: `mkdir ${flag}${shellQuote5(abs)}` });
21061
+ const { exitCode, stderr } = await this.run({ cmd: `mkdir ${flag}${shellQuote6(abs)}` });
19339
21062
  if (exitCode !== null && exitCode !== 0) {
19340
21063
  throw new ChannelAValidationError(`failed to mkdir ${path}: ${stderr || `exit ${exitCode}`}`);
19341
21064
  }
@@ -19371,10 +21094,10 @@ var SandboxChannelAService = class {
19371
21094
  const repo = assertSafeRelPathOrRoot(req.path);
19372
21095
  const ctx = req.contextLines;
19373
21096
  let range = "";
19374
- if (req.fromRef && req.toRef) range = `${shellQuote5(req.fromRef)} ${shellQuote5(req.toRef)}`;
19375
- else if (req.fromRef) range = `${shellQuote5(req.fromRef)}`;
21097
+ if (req.fromRef && req.toRef) range = `${shellQuote6(req.fromRef)} ${shellQuote6(req.toRef)}`;
21098
+ else if (req.fromRef) range = `${shellQuote6(req.fromRef)}`;
19376
21099
  else if (req.staged) range = "--cached";
19377
- const pathspec = req.pathspec.length ? ` -- ${req.pathspec.map(shellQuote5).join(" ")}` : "";
21100
+ const pathspec = req.pathspec.length ? ` -- ${req.pathspec.map(shellQuote6).join(" ")}` : "";
19378
21101
  const numstat = await this.runInConfinedDirectory(repo, {
19379
21102
  cmd: `git -c core.quotePath=false diff --no-color -z --numstat ${range}${pathspec}`.trim()
19380
21103
  });
@@ -19398,7 +21121,7 @@ var SandboxChannelAService = class {
19398
21121
  continue;
19399
21122
  }
19400
21123
  const patch = await this.runInConfinedDirectory(repo, {
19401
- cmd: `git -c core.quotePath=false diff --no-color -U${ctx} ${range} -- ${shellQuote5(target)}`.trim()
21124
+ cmd: `git -c core.quotePath=false diff --no-color -U${ctx} ${range} -- ${shellQuote6(target)}`.trim()
19402
21125
  });
19403
21126
  const oversized = Buffer.byteLength(patch.stdout, "utf8") > req.maxBytesPerFile;
19404
21127
  const parsed = oversized ? { hunks: [], status: "modified" } : parseUnifiedPatch(patch.stdout);
@@ -19422,7 +21145,7 @@ var SandboxChannelAService = class {
19422
21145
  const fileArg = target.startsWith("./") ? target : `./${target}`;
19423
21146
  const read = await this.runInConfinedDirectory(repo, {
19424
21147
  cmd: [
19425
- `file=${shellQuote5(fileArg)}`,
21148
+ `file=${shellQuote6(fileArg)}`,
19426
21149
  'if [ -L "$file" ]; then printf "L\\n"; readlink -z -- "$file" | base64 | tr -d "\\n"; exit; fi',
19427
21150
  'size=$(wc -c < "$file") || exit 66',
19428
21151
  'line_count=$(wc -l < "$file") || exit 66',
@@ -19486,9 +21209,9 @@ var SandboxChannelAService = class {
19486
21209
  async gitLog(req) {
19487
21210
  const repo = assertSafeRelPathOrRoot(req.path);
19488
21211
  const fmt = `%H${US}%h${US}%P${US}%an${US}%ae${US}%at${US}%cn${US}%ce${US}%ct${US}%s${US}%b${RS}`;
19489
- const pathspec = req.pathspec.length ? ` -- ${req.pathspec.map(shellQuote5).join(" ")}` : "";
21212
+ const pathspec = req.pathspec.length ? ` -- ${req.pathspec.map(shellQuote6).join(" ")}` : "";
19490
21213
  const { stdout, exitCode } = await this.runInConfinedDirectory(repo, {
19491
- cmd: `git log --format=${shellQuote5(fmt)} -n${req.maxCount + 1} --skip=${req.skip} ${shellQuote5(req.ref)}${pathspec}`
21214
+ cmd: `git log --format=${shellQuote6(fmt)} -n${req.maxCount + 1} --skip=${req.skip} ${shellQuote6(req.ref)}${pathspec}`
19492
21215
  });
19493
21216
  if (exitCode !== null && exitCode !== 0) {
19494
21217
  return { commits: [], hasMore: false };
@@ -19515,7 +21238,7 @@ var SandboxChannelAService = class {
19515
21238
  const repo = assertSafeRelPathOrRoot(req.path);
19516
21239
  if (req.filePath) {
19517
21240
  const { stdout, exitCode } = await this.runInConfinedDirectory(repo, {
19518
- cmd: `git cat-file blob ${shellQuote5(`${req.ref}:${req.filePath}`)} 2>/dev/null | base64`
21241
+ cmd: `git cat-file blob ${shellQuote6(`${req.ref}:${req.filePath}`)} 2>/dev/null | base64`
19519
21242
  });
19520
21243
  if (exitCode !== null && exitCode !== 0 && stdout.trim() === "") {
19521
21244
  throw new ChannelANotFoundError(`blob not found: ${req.ref}:${req.filePath}`);
@@ -19646,17 +21369,49 @@ var SandboxChannelAService = class {
19646
21369
  return (await this.detectReposDetailed()).repos;
19647
21370
  }
19648
21371
  // ════════════════════════ Terminal exec + PTY (A2) ════════════════════════
19649
- /** Run a bounded command, return buffered stdout/stderr + exit code inline. The
19650
- * long-running tail (when the process hasn't exited within timeoutMs) keeps
19651
- * running in-box; if emitStream is set the buffered output is also published as
19652
- * the agent firehose so other viewers see it. */
21372
+ /** Run a bounded command to physical completion and return buffered output.
21373
+ * A deadline cancels the exact provider operation, proves its process group
21374
+ * absent, and settles any retained-process admission before returning 124. */
19653
21375
  async terminalExec(req) {
19654
- const r = await this.run({
19655
- cmd: req.command,
19656
- workdir: this.terminalWorkdir(req.cwd),
19657
- yieldTimeMs: req.timeoutMs
19658
- });
19659
- const running = r.exitCode === null && typeof r.sessionId === "number";
21376
+ const abort = new AbortController();
21377
+ const controller = createTurnToolCancellationController(abort.signal);
21378
+ const timeoutReason = new Error(`Terminal command exceeded ${req.timeoutMs}ms`);
21379
+ let timedOut = false;
21380
+ const timer = setTimeout(() => {
21381
+ timedOut = true;
21382
+ abort.abort(timeoutReason);
21383
+ }, req.timeoutMs);
21384
+ const terminalWorkdir = this.terminalWorkdir(req.cwd);
21385
+ let r;
21386
+ try {
21387
+ r = await controller.runSandboxCommandStructured(this.session, {
21388
+ cmd: req.command,
21389
+ ...terminalWorkdir ? { workdir: terminalWorkdir } : {},
21390
+ ...this.runAs ? { runAs: this.runAs } : {},
21391
+ yieldTimeMs: req.timeoutMs
21392
+ });
21393
+ if (timedOut) {
21394
+ await controller.waitForQuiescence();
21395
+ r = {
21396
+ stdout: "",
21397
+ stderr: timeoutReason.message,
21398
+ exitCode: 124,
21399
+ wallTimeSeconds: req.timeoutMs / 1e3
21400
+ };
21401
+ }
21402
+ } catch (error) {
21403
+ controller.cancel(error);
21404
+ await controller.waitForQuiescence();
21405
+ if (!timedOut && !(error instanceof TurnSandboxCommandCancelledError)) throw error;
21406
+ r = {
21407
+ stdout: "",
21408
+ stderr: timeoutReason.message,
21409
+ exitCode: 124,
21410
+ wallTimeSeconds: req.timeoutMs / 1e3
21411
+ };
21412
+ } finally {
21413
+ clearTimeout(timer);
21414
+ }
19660
21415
  if (req.emitStream && (r.stdout || r.stderr)) {
19661
21416
  const events = [];
19662
21417
  const commandId = crypto.randomUUID();
@@ -19676,7 +21431,7 @@ var SandboxChannelAService = class {
19676
21431
  stdout: r.stdout,
19677
21432
  stderr: r.stderr,
19678
21433
  exitCode: r.exitCode,
19679
- running,
21434
+ running: false,
19680
21435
  wallTimeSeconds: r.wallTimeSeconds
19681
21436
  };
19682
21437
  }
@@ -19706,10 +21461,11 @@ var SandboxChannelAService = class {
19706
21461
  * it as terminal.pty.output.delta). Throws ChannelAUnsupportedError when the
19707
21462
  * backend has no writeStdin. */
19708
21463
  async ptyWrite(_req, execSessionId, data) {
19709
- if (!this.session.writeStdin) {
21464
+ const write = this.session.writeStdinForProcessMutation?.bind(this.session) ?? this.session.writeStdin?.bind(this.session);
21465
+ if (!write) {
19710
21466
  throw new ChannelAUnsupportedError("interactive terminal unsupported on this backend");
19711
21467
  }
19712
- const out = await this.session.writeStdin({
21468
+ const out = await write({
19713
21469
  sessionId: execSessionId,
19714
21470
  chars: data,
19715
21471
  yieldTimeMs: 250
@@ -19722,23 +21478,22 @@ var SandboxChannelAService = class {
19722
21478
  /** Resize an open PTY (SIGWINCH via stty against the exec-session). The SDK has
19723
21479
  * no resize method; stty in the same tty session updates the geometry. */
19724
21480
  async ptyResize(req, execSessionId) {
19725
- if (!this.session.writeStdin) return;
19726
- await this.session.writeStdin({
21481
+ const write = this.session.writeStdinForProcessControl?.bind(this.session) ?? this.session.writeStdin?.bind(this.session);
21482
+ if (!write) return;
21483
+ await write({
19727
21484
  sessionId: execSessionId,
19728
21485
  chars: `stty cols ${req.cols} rows ${req.rows}
19729
21486
  `,
19730
21487
  yieldTimeMs: 50
19731
21488
  });
19732
21489
  }
19733
- /** Close an open PTY: write exit/EOF. The caller marks the row closed + emits
19734
- * terminal.pty.exited. */
21490
+ /** Ask an open PTY to exit and return the exact provider banner. The routing
21491
+ * session settles durable process authority only when that banner proves this
21492
+ * locator exited or was lost; callers must keep metadata open otherwise. */
19735
21493
  async ptyClose(_req, execSessionId) {
19736
- if (execSessionId !== null && this.session.writeStdin) {
19737
- try {
19738
- await this.session.writeStdin({ sessionId: execSessionId, chars: "", yieldTimeMs: 50 });
19739
- } catch {
19740
- }
19741
- }
21494
+ const write = this.session.writeStdinForProcessControl?.bind(this.session) ?? this.session.writeStdin?.bind(this.session);
21495
+ if (execSessionId === null || !write) return "";
21496
+ return await write({ sessionId: execSessionId, chars: "", yieldTimeMs: 250 });
19742
21497
  }
19743
21498
  // ──────────────────────────── helpers ──────────────────────────────────────
19744
21499
  /** The current FS revision (for the caller to persist/seed). */
@@ -19756,11 +21511,11 @@ var SandboxChannelAService = class {
19756
21511
  const safe = assertSafeRelPathOrRoot(rel);
19757
21512
  const root = this.workspaceRoot || ".";
19758
21513
  const abs = this.joinRoot(safe);
19759
- const rejectLink = safe ? `test ! -L ${shellQuote5(abs)} || { printf '__OPENGENI_FS_SYMLINK__'; exit 68; }` : ":";
21514
+ const rejectLink = safe ? `test ! -L ${shellQuote6(abs)} || { printf '__OPENGENI_FS_SYMLINK__'; exit 68; }` : ":";
19760
21515
  const script = [
19761
- `root=$(realpath -e -- ${shellQuote5(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
21516
+ `root=$(realpath -e -- ${shellQuote6(root)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19762
21517
  rejectLink,
19763
- `target=$(realpath -e -- ${shellQuote5(abs)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
21518
+ `target=$(realpath -e -- ${shellQuote6(abs)}) || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19764
21519
  `case "$target" in "$root"|"$root"/*) ;; *) printf '__OPENGENI_FS_ESCAPE__'; exit 67 ;; esac`,
19765
21520
  `test -d "$target" || { printf '__OPENGENI_FS_NOT_FOUND__'; exit 66; }`,
19766
21521
  `cd -P -- "$target"`,
@@ -19870,28 +21625,6 @@ function isDefinitePathNotFoundError(error) {
19870
21625
  const candidate = error;
19871
21626
  return candidate.osNotFound === true || candidate.code === "ENOENT" || candidate.errno === "ENOENT" || candidate.errno === -2;
19872
21627
  }
19873
- function isExecSessionLostBanner(out, execSessionId) {
19874
- if (!out || !Number.isSafeInteger(execSessionId) || execSessionId < 0) return false;
19875
- const match = out.match(/^(?:write_stdin failed: )?session not found: (\d+)$/);
19876
- if (!match || match[0] !== out) return false;
19877
- return match[1] === String(execSessionId);
19878
- }
19879
- function parseExecBannerSessionId(raw) {
19880
- const outputIdx = raw.indexOf("\nOutput:\n");
19881
- const banner = outputIdx >= 0 ? raw.slice(0, outputIdx) : raw.startsWith("Output:\n") ? "" : raw;
19882
- const match = banner.match(/Process running with session ID (\d+)/);
19883
- if (!match) return null;
19884
- const n = Number.parseInt(match[1], 10);
19885
- return Number.isFinite(n) ? n : null;
19886
- }
19887
- function parseExecBannerExitCode(raw) {
19888
- const outputIdx = raw.indexOf("\nOutput:\n");
19889
- const banner = outputIdx >= 0 ? raw.slice(0, outputIdx) : raw.startsWith("Output:\n") ? "" : raw;
19890
- const match = banner.match(/Process exited with code (-?\d+)/);
19891
- if (!match) return null;
19892
- const exitCode = Number.parseInt(match[1], 10);
19893
- return Number.isSafeInteger(exitCode) ? exitCode : null;
19894
- }
19895
21628
  function sniffBinary(bytes) {
19896
21629
  const n = Math.min(bytes.byteLength, 8192);
19897
21630
  for (let i = 0; i < n; i++) if (bytes[i] === 0) return true;
@@ -19925,11 +21658,11 @@ function assertSafeRelPath(p) {
19925
21658
  if (norm === "") throw new ChannelAValidationError("path is required");
19926
21659
  return norm;
19927
21660
  }
19928
- function shellQuote5(s) {
21661
+ function shellQuote6(s) {
19929
21662
  return `'${s.replace(/'/g, `'\\''`)}'`;
19930
21663
  }
19931
21664
  function internalBashCommand(script) {
19932
- return `env -u BASH_ENV bash --noprofile --norc -c ${shellQuote5(script)}`;
21665
+ return `env -u BASH_ENV bash --noprofile --norc -c ${shellQuote6(script)}`;
19933
21666
  }
19934
21667
  function assertSafePruneDirectoryName(name) {
19935
21668
  if (name.length > 255 || !/^[A-Za-z0-9._@+-]+$/.test(name) || name === "." || name === "..") {
@@ -20463,12 +22196,12 @@ function defaultEcho(req, hostname) {
20463
22196
  durationMs: "1"
20464
22197
  };
20465
22198
  }
20466
- function ok(requestId, result) {
20467
- return { requestId, error: void 0, result };
22199
+ function ok(requestId2, result) {
22200
+ return { requestId: requestId2, error: void 0, result };
20468
22201
  }
20469
- function errorResponse(requestId, code, message, retryable) {
22202
+ function errorResponse(requestId2, code, message, retryable) {
20470
22203
  const error = { code, message, retryable, detail: {} };
20471
- return { requestId, error, result: void 0 };
22204
+ return { requestId: requestId2, error, result: void 0 };
20472
22205
  }
20473
22206
  function normalize(path) {
20474
22207
  const trimmed = path.replace(/\/+$/, "");
@@ -20488,6 +22221,35 @@ var RoutingUnsupportedError = class extends Error {
20488
22221
  super(`the active sandbox (${kind}) does not support "${op}"`);
20489
22222
  }
20490
22223
  };
22224
+ var RoutingBackendRecoveryRequiredError = class extends Error {
22225
+ constructor(op, leaseEpoch, recovery) {
22226
+ super(
22227
+ `sandbox backend disappeared during ${op}; recovery is ${recovery} at epoch ${leaseEpoch}`
22228
+ );
22229
+ this.op = op;
22230
+ this.leaseEpoch = leaseEpoch;
22231
+ this.recovery = recovery;
22232
+ this.retryable = recovery === "pending" || recovery === "superseded";
22233
+ }
22234
+ name = "RoutingBackendRecoveryRequiredError";
22235
+ retryable;
22236
+ };
22237
+ var RoutingMutationOutcomeUnknownError = class extends Error {
22238
+ constructor(op, message, options) {
22239
+ super(message, options);
22240
+ this.op = op;
22241
+ }
22242
+ name = "RoutingMutationOutcomeUnknownError";
22243
+ retryable = false;
22244
+ };
22245
+ var RoutingRetainedProcessNotFoundError = class extends Error {
22246
+ constructor(providerSessionId) {
22247
+ super(`retained sandbox process ${providerSessionId} is not tracked on its original route`);
22248
+ this.providerSessionId = providerSessionId;
22249
+ }
22250
+ name = "RoutingRetainedProcessNotFoundError";
22251
+ retryable = false;
22252
+ };
20491
22253
  function isFenceError(error) {
20492
22254
  if (!error || typeof error !== "object") {
20493
22255
  return false;
@@ -20500,6 +22262,59 @@ function isFenceError(error) {
20500
22262
  const haystack = `${name} ${message}`.toLowerCase();
20501
22263
  return haystack.includes("fenced") || haystack.includes("epoch") && haystack.includes("super");
20502
22264
  }
22265
+ function positiveProviderSessionId(value) {
22266
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
22267
+ }
22268
+ function canonicalUuid(value) {
22269
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
22270
+ }
22271
+ function providerSessionIdFromResult(result) {
22272
+ if (typeof result === "string") {
22273
+ return positiveProviderSessionId(parseExecBannerSessionId(result));
22274
+ }
22275
+ if (!result || typeof result !== "object") return null;
22276
+ const record = result;
22277
+ return positiveProviderSessionId(record.sessionId) ?? positiveProviderSessionId(record.session_id);
22278
+ }
22279
+ function providerSessionIdFromArgs(args) {
22280
+ if (!args || typeof args !== "object") return null;
22281
+ const record = args;
22282
+ return positiveProviderSessionId(record.sessionId) ?? positiveProviderSessionId(record.session_id);
22283
+ }
22284
+ function retainedProcessTerminalProof(result, providerSessionId) {
22285
+ if (isExecSessionLostBanner(result, providerSessionId)) {
22286
+ return {
22287
+ outcome: "lost",
22288
+ exitCode: null,
22289
+ reason: "provider_session_lost_banner"
22290
+ };
22291
+ }
22292
+ const exitCode = parseExecBannerExitCode(result);
22293
+ return exitCode === null ? null : { outcome: "exited", exitCode, reason: "provider_exit_banner" };
22294
+ }
22295
+ function formatExecResult(result) {
22296
+ if (typeof result === "string") return result;
22297
+ if (!result || typeof result !== "object") {
22298
+ throw new Error("sandbox process-control exec returned an invalid result");
22299
+ }
22300
+ const record = result;
22301
+ const output = [record.output, record.stderr, record.stdout].filter((value) => typeof value === "string" && value.length > 0).join("\n");
22302
+ const sessionId = positiveProviderSessionId(record.sessionId) ?? positiveProviderSessionId(record.session_id);
22303
+ if (sessionId !== null) {
22304
+ return `Process running with session ID ${sessionId}
22305
+
22306
+ Output:
22307
+ ${output}`;
22308
+ }
22309
+ const exitCode = typeof record.exitCode === "number" ? record.exitCode : typeof record.exit_code === "number" ? record.exit_code : null;
22310
+ if (exitCode !== null && Number.isSafeInteger(exitCode)) {
22311
+ return `Process exited with code ${exitCode}
22312
+
22313
+ Output:
22314
+ ${output}`;
22315
+ }
22316
+ throw new Error("sandbox process-control exec reported neither session id nor exit code");
22317
+ }
20503
22318
  var RoutingSandboxSession = class {
20504
22319
  deps;
20505
22320
  maxFenceRetries;
@@ -20518,6 +22333,10 @@ var RoutingSandboxSession = class {
20518
22333
  // The last-resolved backend, exposed via the `state` getter (a method-free read
20519
22334
  // of the active backend's `state`). Updated on every resolve.
20520
22335
  lastResolved;
22336
+ /** Provider session ids are scoped to one backend instance. Each entry copies
22337
+ * that exact resolved route so pointer movement can never redirect stdin,
22338
+ * polling, or process-group helpers to another box. */
22339
+ retainedProcesses = /* @__PURE__ */ new Map();
20521
22340
  // The native-desktop control-plane ops (self-hosted / macOS). Declared as OPTIONAL
20522
22341
  // INSTANCE fields — NOT prototype methods — because their PRESENCE is the selection
20523
22342
  // signal `isNativeDesktopSession` (sandbox-computer.ts) uses to pick the native vs
@@ -20533,13 +22352,13 @@ var RoutingSandboxSession = class {
20533
22352
  this.maxFenceRetries = deps.maxFenceRetries ?? 3;
20534
22353
  const def = deps.defaultResolved?.session;
20535
22354
  if (typeof def?.desktopInput === "function" && typeof def?.screenshot === "function") {
20536
- this.desktopInput = (event) => this.dispatch("desktopInput", async (s) => {
22355
+ this.desktopInput = (event) => this.dispatch("desktopInput", true, async (s) => {
20537
22356
  if (!s.desktopInput) {
20538
22357
  throw new RoutingUnsupportedError("desktopInput", this.cached?.kind ?? "unknown");
20539
22358
  }
20540
22359
  return s.desktopInput(event);
20541
22360
  });
20542
- this.screenshot = () => this.dispatch("screenshot", async (s) => {
22361
+ this.screenshot = () => this.dispatch("screenshot", false, async (s) => {
20543
22362
  if (!s.screenshot) {
20544
22363
  throw new RoutingUnsupportedError("screenshot", this.cached?.kind ?? "unknown");
20545
22364
  }
@@ -20584,18 +22403,256 @@ var RoutingSandboxSession = class {
20584
22403
  "RoutingSandboxSession.resolveActiveBackend returned the proxy itself as the active backend (re-entrancy) \u2014 the resolver must return the underlying box session, not the routing proxy."
20585
22404
  );
20586
22405
  }
22406
+ const routed = {
22407
+ ...resolved,
22408
+ activeEpoch: pointer.activeEpoch
22409
+ };
20587
22410
  this.cachedEpoch = pointer.activeEpoch;
20588
22411
  this.cachedSandboxId = pointer.activeSandboxId;
20589
- this.cached = resolved;
20590
- this.lastResolved = resolved;
22412
+ this.cached = routed;
22413
+ this.lastResolved = routed;
20591
22414
  this.deps.onTransition?.({
20592
22415
  type: this.cachedEpoch !== void 0 && fromEpoch !== pointer.activeEpoch ? "epoch-changed" : "resolved",
20593
22416
  fromEpoch,
20594
22417
  toEpoch: pointer.activeEpoch,
20595
- sandboxId: resolved.sandboxId,
20596
- kind: resolved.kind
22418
+ sandboxId: routed.sandboxId,
22419
+ kind: routed.kind
20597
22420
  });
20598
- return resolved;
22421
+ return routed;
22422
+ }
22423
+ invalidate(backend) {
22424
+ this.cachedEpoch = void 0;
22425
+ this.cachedSandboxId = void 0;
22426
+ this.cached = void 0;
22427
+ this.deps.onTransition?.({
22428
+ type: "fenced-retry",
22429
+ fromEpoch: backend.activeEpoch ?? 0,
22430
+ toEpoch: 0,
22431
+ sandboxId: backend.sandboxId,
22432
+ kind: backend.kind
22433
+ });
22434
+ }
22435
+ registerRetainedProcess(process2, backend) {
22436
+ if (this.retainedProcesses.has(process2.providerSessionId)) {
22437
+ throw new RoutingMutationOutcomeUnknownError(
22438
+ "retainProcess",
22439
+ `Provider session ${process2.providerSessionId} was yielded while that locator was already retained; neither process was rebound`
22440
+ );
22441
+ }
22442
+ const record = {
22443
+ process: process2,
22444
+ backend,
22445
+ durable: !this.deps.afterMutation,
22446
+ pendingParentPromotion: null,
22447
+ pendingMutationSettlement: null,
22448
+ pendingTerminal: null,
22449
+ settlement: null
22450
+ };
22451
+ this.retainedProcesses.set(process2.providerSessionId, record);
22452
+ return record;
22453
+ }
22454
+ retainedProcess(providerSessionId) {
22455
+ const retained = this.retainedProcesses.get(providerSessionId);
22456
+ if (!retained) throw new RoutingRetainedProcessNotFoundError(providerSessionId);
22457
+ return retained;
22458
+ }
22459
+ /** Reconstruct one process that was already promoted durably by an earlier
22460
+ * request. Adoption never reads the active pointer and never repeats parent
22461
+ * promotion. It can bind only to the exact construction-time default backend;
22462
+ * callers must reject non-default routes until they can independently rebuild
22463
+ * that provider session from its copied durable identity. */
22464
+ adoptRetainedProcess(input) {
22465
+ const providerSessionId = positiveProviderSessionId(input.process.providerSessionId);
22466
+ const seed = this.deps.defaultResolved;
22467
+ if (providerSessionId === null || !canonicalUuid(input.process.id) || !seed || seed.sandboxId !== input.backend.sandboxId || seed.leaseEpoch !== input.backend.leaseEpoch || seed.providerInstanceId !== input.backend.providerInstanceId || !Number.isSafeInteger(input.backend.activeEpoch) || input.backend.activeEpoch < 0) {
22468
+ throw new RoutingMutationOutcomeUnknownError(
22469
+ "adoptRetainedProcess",
22470
+ `Retained provider session ${input.process.providerSessionId} did not match the exact default backend identity`
22471
+ );
22472
+ }
22473
+ const existing = this.retainedProcesses.get(providerSessionId);
22474
+ if (existing) {
22475
+ if (existing.process.id === input.process.id && existing.backend.session === seed.session && existing.backend.sandboxId === input.backend.sandboxId && existing.backend.leaseEpoch === input.backend.leaseEpoch && existing.backend.providerInstanceId === input.backend.providerInstanceId && existing.backend.activeEpoch === input.backend.activeEpoch) {
22476
+ return;
22477
+ }
22478
+ throw new RoutingMutationOutcomeUnknownError(
22479
+ "adoptRetainedProcess",
22480
+ `Provider session ${providerSessionId} is already bound to a different retained process identity`
22481
+ );
22482
+ }
22483
+ this.retainedProcesses.set(providerSessionId, {
22484
+ process: { ...input.process },
22485
+ backend: {
22486
+ ...seed,
22487
+ activeEpoch: input.backend.activeEpoch
22488
+ },
22489
+ durable: true,
22490
+ pendingParentPromotion: null,
22491
+ pendingMutationSettlement: null,
22492
+ pendingTerminal: null,
22493
+ settlement: null
22494
+ });
22495
+ }
22496
+ async ensureParentPromotion(record) {
22497
+ if (record.durable) return;
22498
+ const pending = record.pendingParentPromotion;
22499
+ if (!pending || !this.deps.afterMutation) {
22500
+ throw new RoutingMutationOutcomeUnknownError(
22501
+ "retainProcess",
22502
+ `Retained provider session ${record.process.providerSessionId} has no confirmed durable parent admission`
22503
+ );
22504
+ }
22505
+ try {
22506
+ await this.deps.afterMutation(pending);
22507
+ record.pendingParentPromotion = null;
22508
+ record.durable = true;
22509
+ } catch (error) {
22510
+ throw new RoutingMutationOutcomeUnknownError(
22511
+ pending.op,
22512
+ `Yielded sandbox process ${record.process.providerSessionId} could not confirm its durable promotion; tracking remains pinned to the original backend`,
22513
+ { cause: error }
22514
+ );
22515
+ }
22516
+ }
22517
+ async settleRetainedProcess(record, proof, result) {
22518
+ record.pendingTerminal ??= { proof, result };
22519
+ const pending = record.pendingTerminal;
22520
+ if (pending.proof.outcome !== proof.outcome || pending.proof.exitCode !== proof.exitCode || pending.proof.reason !== proof.reason) {
22521
+ throw new RoutingMutationOutcomeUnknownError(
22522
+ "settleProcess",
22523
+ `Retained provider session ${record.process.providerSessionId} produced conflicting terminal proof`
22524
+ );
22525
+ }
22526
+ record.settlement ??= (async () => {
22527
+ await this.ensureParentPromotion(record);
22528
+ await this.deps.settleProcess?.({
22529
+ backend: record.backend,
22530
+ process: record.process,
22531
+ proof: pending.proof
22532
+ });
22533
+ this.retainedProcesses.delete(record.process.providerSessionId);
22534
+ record.pendingTerminal = null;
22535
+ })();
22536
+ try {
22537
+ await record.settlement;
22538
+ } catch (error) {
22539
+ record.settlement = null;
22540
+ throw new RoutingMutationOutcomeUnknownError(
22541
+ "settleProcess",
22542
+ `Retained provider session ${record.process.providerSessionId} reached a terminal state but durable settlement failed; tracking remains pinned`,
22543
+ { cause: error }
22544
+ );
22545
+ }
22546
+ }
22547
+ async flushPendingProcessMutation(record) {
22548
+ const pending = record.pendingMutationSettlement;
22549
+ if (!pending) return null;
22550
+ try {
22551
+ await this.deps.afterProcessMutation?.(pending);
22552
+ record.pendingMutationSettlement = null;
22553
+ } catch (error) {
22554
+ throw new RoutingMutationOutcomeUnknownError(
22555
+ pending.op,
22556
+ `Retained-process mutation ${pending.op} still lacks durable physical settlement; no later process mutation was admitted`,
22557
+ { cause: error }
22558
+ );
22559
+ }
22560
+ if (pending.outcome === "resolved" && typeof pending.result === "string") {
22561
+ const proof = retainedProcessTerminalProof(pending.result, record.process.providerSessionId);
22562
+ if (proof) {
22563
+ await this.settleRetainedProcess(record, proof, pending.result);
22564
+ return pending.result;
22565
+ }
22566
+ }
22567
+ return null;
22568
+ }
22569
+ async dispatchProcessMutation(args) {
22570
+ const providerSessionId = providerSessionIdFromArgs(args);
22571
+ if (providerSessionId === null) throw new RoutingRetainedProcessNotFoundError(-1);
22572
+ const record = this.retainedProcess(providerSessionId);
22573
+ const priorTerminal = await this.flushPendingProcessMutation(record);
22574
+ if (priorTerminal !== null) return priorTerminal;
22575
+ if (record.pendingTerminal) {
22576
+ const terminal = record.pendingTerminal;
22577
+ await this.settleRetainedProcess(record, terminal.proof, terminal.result);
22578
+ return terminal.result;
22579
+ }
22580
+ await this.ensureParentPromotion(record);
22581
+ const op = "writeStdin";
22582
+ const admission = await this.deps.beforeProcessMutation?.({
22583
+ op,
22584
+ backend: record.backend,
22585
+ process: record.process
22586
+ });
22587
+ const write = record.backend.session.writeStdin;
22588
+ if (!write) throw new RoutingUnsupportedError(op, record.backend.kind);
22589
+ let result;
22590
+ try {
22591
+ result = await write.call(record.backend.session, args);
22592
+ } catch (error) {
22593
+ if (this.deps.afterProcessMutation) {
22594
+ const pending = {
22595
+ op,
22596
+ backend: record.backend,
22597
+ process: record.process,
22598
+ admission,
22599
+ outcome: "rejected"
22600
+ };
22601
+ try {
22602
+ await this.deps.afterProcessMutation(pending);
22603
+ } catch (settlementError) {
22604
+ record.pendingMutationSettlement = pending;
22605
+ throw new RoutingMutationOutcomeUnknownError(
22606
+ op,
22607
+ `Retained-process stdin rejected at the provider but lost durable settlement; it was not replayed`,
22608
+ { cause: settlementError }
22609
+ );
22610
+ }
22611
+ }
22612
+ throw error;
22613
+ }
22614
+ if (this.deps.afterProcessMutation) {
22615
+ const pending = {
22616
+ op,
22617
+ backend: record.backend,
22618
+ process: record.process,
22619
+ admission,
22620
+ outcome: "resolved",
22621
+ result
22622
+ };
22623
+ try {
22624
+ await this.deps.afterProcessMutation(pending);
22625
+ } catch (error) {
22626
+ record.pendingMutationSettlement = pending;
22627
+ throw new RoutingMutationOutcomeUnknownError(
22628
+ op,
22629
+ `Retained-process stdin returned from the provider but lost durable settlement; tracking remains pinned and it was not replayed`,
22630
+ { cause: error }
22631
+ );
22632
+ }
22633
+ }
22634
+ const proof = retainedProcessTerminalProof(result, providerSessionId);
22635
+ if (proof) await this.settleRetainedProcess(record, proof, result);
22636
+ return result;
22637
+ }
22638
+ async dispatchProcessControl(args) {
22639
+ const providerSessionId = providerSessionIdFromArgs(args);
22640
+ if (providerSessionId === null) throw new RoutingRetainedProcessNotFoundError(-1);
22641
+ const record = this.retainedProcess(providerSessionId);
22642
+ const priorTerminal = await this.flushPendingProcessMutation(record);
22643
+ if (priorTerminal !== null) return priorTerminal;
22644
+ if (record.pendingTerminal) {
22645
+ const terminal = record.pendingTerminal;
22646
+ await this.settleRetainedProcess(record, terminal.proof, terminal.result);
22647
+ return terminal.result;
22648
+ }
22649
+ await this.ensureParentPromotion(record);
22650
+ const write = record.backend.session.writeStdin;
22651
+ if (!write) throw new RoutingUnsupportedError("writeStdin", record.backend.kind);
22652
+ const result = await write.call(record.backend.session, args);
22653
+ const proof = retainedProcessTerminalProof(result, providerSessionId);
22654
+ if (proof) await this.settleRetainedProcess(record, proof, result);
22655
+ return result;
20599
22656
  }
20600
22657
  /**
20601
22658
  * Dispatch an op to the currently-active backend, retrying on a stale-epoch
@@ -20608,30 +22665,113 @@ var RoutingSandboxSession = class {
20608
22665
  * A non-fence error propagates immediately (it is a real op failure, not a swap
20609
22666
  * race).
20610
22667
  */
20611
- async dispatch(op, fn) {
22668
+ async dispatch(op, mutatesWorkspace, fn) {
20612
22669
  let attempt = 0;
20613
22670
  let lastError;
20614
22671
  while (attempt <= this.maxFenceRetries) {
20615
22672
  const backend = await this.resolve();
22673
+ const admission = mutatesWorkspace ? await this.deps.beforeMutation?.({ op, backend }) : void 0;
22674
+ let result;
20616
22675
  try {
20617
- return await fn(backend.session);
22676
+ result = await fn(backend.session);
20618
22677
  } catch (error) {
22678
+ if (mutatesWorkspace && this.deps.afterMutation) {
22679
+ try {
22680
+ await this.deps.afterMutation({
22681
+ op,
22682
+ backend,
22683
+ admission,
22684
+ outcome: "rejected"
22685
+ });
22686
+ } catch (settlementError) {
22687
+ this.invalidate(backend);
22688
+ throw new RoutingMutationOutcomeUnknownError(
22689
+ op,
22690
+ `Mutating sandbox operation "${op}" rejected at the provider but lost its durable physical settlement; its outcome is unknown and it was not replayed`,
22691
+ { cause: settlementError }
22692
+ );
22693
+ }
22694
+ }
20619
22695
  if (!isFenceError(error)) {
22696
+ if (backend.sandboxId === null && this.deps.onDefaultBackendError) {
22697
+ const loss = await this.deps.onDefaultBackendError({
22698
+ op,
22699
+ error,
22700
+ kind: backend.kind,
22701
+ backend
22702
+ });
22703
+ if (loss) {
22704
+ this.invalidate(backend);
22705
+ throw new RoutingBackendRecoveryRequiredError(op, loss.leaseEpoch, loss.recovery);
22706
+ }
22707
+ }
20620
22708
  throw error;
20621
22709
  }
22710
+ this.invalidate(backend);
22711
+ if (mutatesWorkspace) {
22712
+ throw new RoutingMutationOutcomeUnknownError(
22713
+ op,
22714
+ `Mutating sandbox operation "${op}" was fenced after provider admission; its outcome is unknown and it was not replayed`,
22715
+ { cause: error }
22716
+ );
22717
+ }
20622
22718
  lastError = error;
20623
- this.cachedEpoch = void 0;
20624
- this.cachedSandboxId = void 0;
20625
- this.cached = void 0;
20626
- this.deps.onTransition?.({
20627
- type: "fenced-retry",
20628
- fromEpoch: backend.sandboxId === null ? 0 : 0,
20629
- toEpoch: 0,
20630
- sandboxId: backend.sandboxId,
20631
- kind: backend.kind
20632
- });
20633
22719
  attempt += 1;
22720
+ continue;
20634
22721
  }
22722
+ const yieldedSessionId = mutatesWorkspace && (op === "exec" || op === "execCommand") ? providerSessionIdFromResult(result) : null;
22723
+ const retainedProcess = yieldedSessionId === null ? void 0 : { id: crypto.randomUUID(), providerSessionId: yieldedSessionId };
22724
+ const retainedRecord = retainedProcess ? this.registerRetainedProcess(retainedProcess, backend) : null;
22725
+ if (mutatesWorkspace && this.deps.afterMutation) {
22726
+ const settlement = {
22727
+ op,
22728
+ backend,
22729
+ admission,
22730
+ outcome: "resolved",
22731
+ result,
22732
+ ...retainedProcess ? { retainedProcess } : {}
22733
+ };
22734
+ try {
22735
+ await this.deps.afterMutation(settlement);
22736
+ if (retainedRecord) retainedRecord.durable = true;
22737
+ } catch (error) {
22738
+ if (retainedRecord) retainedRecord.pendingParentPromotion = settlement;
22739
+ this.invalidate(backend);
22740
+ throw new RoutingMutationOutcomeUnknownError(
22741
+ op,
22742
+ retainedRecord ? `Mutating sandbox operation "${op}" yielded provider session ${retainedRecord.process.providerSessionId} but lost durable process promotion; exact-backend tracking remains and the operation was not replayed` : `Mutating sandbox operation "${op}" returned from the provider but lost its durable route settlement; its outcome is unknown and it was not replayed`,
22743
+ { cause: error }
22744
+ );
22745
+ }
22746
+ }
22747
+ if (retainedRecord) return result;
22748
+ let current;
22749
+ try {
22750
+ current = await this.deps.readPointer();
22751
+ } catch (error) {
22752
+ if (mutatesWorkspace) {
22753
+ this.invalidate(backend);
22754
+ throw new RoutingMutationOutcomeUnknownError(
22755
+ op,
22756
+ `Mutating sandbox operation "${op}" returned from the provider but its route could not be revalidated; its outcome is unknown and it was not replayed`,
22757
+ { cause: error }
22758
+ );
22759
+ }
22760
+ throw error;
22761
+ }
22762
+ if (current.activeEpoch !== backend.activeEpoch || current.activeSandboxId !== backend.sandboxId) {
22763
+ this.invalidate(backend);
22764
+ if (mutatesWorkspace) {
22765
+ throw new RoutingMutationOutcomeUnknownError(
22766
+ op,
22767
+ `Mutating sandbox operation "${op}" completed on a superseded route; its output was rejected and it was not replayed`
22768
+ );
22769
+ }
22770
+ lastError = new Error(`sandbox route changed while "${op}" was in flight`);
22771
+ attempt += 1;
22772
+ continue;
22773
+ }
22774
+ return result;
20635
22775
  }
20636
22776
  throw lastError ?? new Error(`routing op "${op}" exhausted fence retries`);
20637
22777
  }
@@ -20664,7 +22804,7 @@ var RoutingSandboxSession = class {
20664
22804
  // dispatches to the active backend at call-time. A missing backend method
20665
22805
  // degrades via the natural fallback or RoutingUnsupportedError.
20666
22806
  async exec(args) {
20667
- return this.dispatch("exec", async (s) => {
22807
+ return this.dispatch("exec", true, async (s) => {
20668
22808
  if (s.exec) {
20669
22809
  return s.exec(args);
20670
22810
  }
@@ -20676,7 +22816,7 @@ var RoutingSandboxSession = class {
20676
22816
  }
20677
22817
  async execCommand(args) {
20678
22818
  try {
20679
- return await this.dispatch("execCommand", async (s) => {
22819
+ return await this.dispatch("execCommand", true, async (s) => {
20680
22820
  if (s.execCommand) {
20681
22821
  return s.execCommand(args);
20682
22822
  }
@@ -20691,21 +22831,72 @@ var RoutingSandboxSession = class {
20691
22831
  }
20692
22832
  }
20693
22833
  async writeStdin(args) {
20694
- return this.dispatch("writeStdin", async (s) => {
22834
+ const providerSessionId = providerSessionIdFromArgs(args);
22835
+ if (providerSessionId !== null && this.retainedProcesses.has(providerSessionId)) {
22836
+ return await this.dispatchProcessMutation(args);
22837
+ }
22838
+ return this.dispatch("writeStdin", true, async (s) => {
20695
22839
  if (!s.writeStdin) {
20696
22840
  throw new RoutingUnsupportedError("writeStdin", this.cached?.kind ?? "unknown");
20697
22841
  }
20698
22842
  return s.writeStdin(args);
20699
22843
  });
20700
22844
  }
22845
+ /** Whether a positive provider session locator is still pinned to the exact
22846
+ * backend that yielded it. This synchronous probe is used only to select the
22847
+ * process-aware routing surface; it is not itself durable authority. */
22848
+ hasRetainedProcess(providerSessionId) {
22849
+ return positiveProviderSessionId(providerSessionId) !== null && this.retainedProcesses.has(providerSessionId);
22850
+ }
22851
+ /** Return only OpenGeni's durable UUID + provider locator. Backend/session
22852
+ * objects remain private so a caller cannot forge route authority from this
22853
+ * diagnostic handoff. */
22854
+ retainedProcessIdentity(providerSessionId) {
22855
+ const record = this.retainedProcesses.get(providerSessionId);
22856
+ return record ? { ...record.process } : null;
22857
+ }
22858
+ /** Model/user-visible stdin is a distinct workspace mutation admission under
22859
+ * the durable retained-process holder. It never re-reads the active pointer. */
22860
+ async writeStdinForProcessMutation(args) {
22861
+ return await this.dispatchProcessMutation(args);
22862
+ }
22863
+ /** Cancellation and drain polling are control operations. They stay pinned to
22864
+ * the process backend and may prove terminal state, but never advance the
22865
+ * workspace generation. */
22866
+ async writeStdinForProcessControl(args) {
22867
+ return await this.dispatchProcessControl(args);
22868
+ }
22869
+ /** Run a PID/PGID marker or signal helper on the retained process's exact
22870
+ * backend. The helper is control-plane work and therefore has no workspace
22871
+ * mutation admission of its own. */
22872
+ async execCommandForProcessControl(providerSessionId, args) {
22873
+ const exactProviderSessionId = positiveProviderSessionId(providerSessionId);
22874
+ if (exactProviderSessionId === null) throw new RoutingRetainedProcessNotFoundError(-1);
22875
+ const record = this.retainedProcess(exactProviderSessionId);
22876
+ const priorTerminal = await this.flushPendingProcessMutation(record);
22877
+ if (priorTerminal !== null) return priorTerminal;
22878
+ if (record.pendingTerminal) {
22879
+ const terminal = record.pendingTerminal;
22880
+ await this.settleRetainedProcess(record, terminal.proof, terminal.result);
22881
+ return terminal.result;
22882
+ }
22883
+ await this.ensureParentPromotion(record);
22884
+ if (record.backend.session.execCommand) {
22885
+ return await record.backend.session.execCommand(args);
22886
+ }
22887
+ if (record.backend.session.exec) {
22888
+ return formatExecResult(await record.backend.session.exec(args));
22889
+ }
22890
+ throw new RoutingUnsupportedError("execCommand", record.backend.kind);
22891
+ }
20701
22892
  async cancelExecCommand(opId) {
20702
- return await this.dispatch("cancelExecCommand", async (session) => {
22893
+ return await this.dispatch("cancelExecCommand", false, async (session) => {
20703
22894
  if (!session.cancelExecCommand) return false;
20704
22895
  return await session.cancelExecCommand(opId);
20705
22896
  });
20706
22897
  }
20707
22898
  async readFile(args) {
20708
- return this.dispatch("readFile", async (s) => {
22899
+ return this.dispatch("readFile", false, async (s) => {
20709
22900
  if (!s.readFile) {
20710
22901
  throw new RoutingUnsupportedError("readFile", this.cached?.kind ?? "unknown");
20711
22902
  }
@@ -20713,7 +22904,7 @@ var RoutingSandboxSession = class {
20713
22904
  });
20714
22905
  }
20715
22906
  async writeFile(args) {
20716
- return this.dispatch("writeFile", async (s) => {
22907
+ return this.dispatch("writeFile", true, async (s) => {
20717
22908
  if (!s.writeFile) {
20718
22909
  throw new RoutingUnsupportedError("writeFile", this.cached?.kind ?? "unknown");
20719
22910
  }
@@ -20721,7 +22912,7 @@ var RoutingSandboxSession = class {
20721
22912
  });
20722
22913
  }
20723
22914
  async listDir(args) {
20724
- return this.dispatch("listDir", async (s) => {
22915
+ return this.dispatch("listDir", false, async (s) => {
20725
22916
  if (!s.listDir) {
20726
22917
  throw new RoutingUnsupportedError("listDir", this.cached?.kind ?? "unknown");
20727
22918
  }
@@ -20729,7 +22920,7 @@ var RoutingSandboxSession = class {
20729
22920
  });
20730
22921
  }
20731
22922
  async pathExists(path, runAs) {
20732
- return this.dispatch("pathExists", async (s) => {
22923
+ return this.dispatch("pathExists", false, async (s) => {
20733
22924
  if (!s.pathExists) {
20734
22925
  throw new RoutingUnsupportedError("pathExists", this.cached?.kind ?? "unknown");
20735
22926
  }
@@ -20737,7 +22928,7 @@ var RoutingSandboxSession = class {
20737
22928
  });
20738
22929
  }
20739
22930
  async viewImage(args) {
20740
- return this.dispatch("viewImage", async (s) => {
22931
+ return this.dispatch("viewImage", false, async (s) => {
20741
22932
  if (!s.viewImage) {
20742
22933
  throw new RoutingUnsupportedError("viewImage", this.cached?.kind ?? "unknown");
20743
22934
  }
@@ -20745,7 +22936,7 @@ var RoutingSandboxSession = class {
20745
22936
  });
20746
22937
  }
20747
22938
  async materializeEntry(args) {
20748
- return this.dispatch("materializeEntry", async (s) => {
22939
+ return this.dispatch("materializeEntry", true, async (s) => {
20749
22940
  if (!s.materializeEntry) {
20750
22941
  throw new RoutingUnsupportedError("materializeEntry", this.cached?.kind ?? "unknown");
20751
22942
  }
@@ -20771,13 +22962,12 @@ var RoutingSandboxSession = class {
20771
22962
  * direct delegate returns undefined and every lazy turn would die at bind. Return a
20772
22963
  * LAZY EDITOR PROXY instead: a non-null editor whose async ops resolve the active
20773
22964
  * backend (establishing the box on first use, via `dispatch`) and delegate to its
20774
- * real editor — mirroring how this proxy defers exec/readFile. */
22965
+ * real editor — mirroring how this proxy defers exec/readFile. Even when an
22966
+ * eager editor already exists, returning it directly would bypass per-edit
22967
+ * route resolution and mutation-generation admission, so every editor is
22968
+ * represented by this dispatching proxy. */
20775
22969
  createEditor(runAs) {
20776
- const eager = (this.lastResolved ?? this.deps.defaultResolved)?.session.createEditor?.(runAs);
20777
- if (eager) {
20778
- return eager;
20779
- }
20780
- const op = (name) => (operation, context) => this.dispatch(`editor.${name}`, async (s) => {
22970
+ const op = (name) => (operation, context) => this.dispatch(`editor.${name}`, true, async (s) => {
20781
22971
  const editor = s.createEditor?.(runAs);
20782
22972
  if (!editor?.[name]) {
20783
22973
  throw new RoutingUnsupportedError(`editor.${name}`, this.cached?.kind ?? "unknown");
@@ -20791,7 +22981,7 @@ var RoutingSandboxSession = class {
20791
22981
  };
20792
22982
  }
20793
22983
  async resolveExposedPort(port) {
20794
- return this.dispatch("resolveExposedPort", async (s) => {
22984
+ return this.dispatch("resolveExposedPort", false, async (s) => {
20795
22985
  if (!s.resolveExposedPort) {
20796
22986
  throw new RoutingUnsupportedError("resolveExposedPort", this.cached?.kind ?? "unknown");
20797
22987
  }
@@ -20801,7 +22991,7 @@ var RoutingSandboxSession = class {
20801
22991
  /** Serialize the active backend's session state. Used by the resume-by-id seam
20802
22992
  * to fold the live box onto the lease. Dispatches to the active backend. */
20803
22993
  async serializeSessionState() {
20804
- return this.dispatch("serializeSessionState", async (s) => {
22994
+ return this.dispatch("serializeSessionState", false, async (s) => {
20805
22995
  if (!s.serializeSessionState) {
20806
22996
  return void 0;
20807
22997
  }
@@ -20853,6 +23043,9 @@ function makeActiveBackendResolver(deps) {
20853
23043
  "the active sandbox was detached to the session default (home), but this turn started pinned to a machine and established no home box; the detach is accepted and takes effect on the next turn \u2014 this turn has no active home box"
20854
23044
  );
20855
23045
  }
23046
+ if (deps.resolveDefaultBackend) {
23047
+ return await deps.resolveDefaultBackend(pointer);
23048
+ }
20856
23049
  return { session: deps.defaultBackend, sandboxId: null, kind: deps.defaultKind };
20857
23050
  }
20858
23051
  if (deps.pinnedSelfhosted && pointer.activeSandboxId === deps.pinnedSelfhosted.sandboxId && pointer.activeEpoch === deps.pinnedSelfhosted.epoch) {
@@ -21035,20 +23228,6 @@ function readWorkspaceArchiveFromEnvelopeSessionState(sessionState) {
21035
23228
  return void 0;
21036
23229
  }
21037
23230
  }
21038
- function readWorkspaceArchivePairFromEnvelopeSessionState(sessionState) {
21039
- if (!sessionState || typeof sessionState !== "object") {
21040
- return {};
21041
- }
21042
- const state = sessionState;
21043
- const current = readWorkspaceArchiveFromEnvelopeSessionState(sessionState);
21044
- const previous = readWorkspaceArchiveFromEnvelopeSessionState({
21045
- workspaceArchive: state.workspaceArchivePrev
21046
- });
21047
- return {
21048
- ...current ? { current } : {},
21049
- ...previous ? { previous } : {}
21050
- };
21051
- }
21052
23231
  var MODAL_SNAPSHOT_REF_PREFIXES = [
21053
23232
  "MODAL_SANDBOX_FS_SNAPSHOT_V1\n",
21054
23233
  "MODAL_SANDBOX_DIR_SNAPSHOT_V1\n"
@@ -21119,39 +23298,73 @@ async function deserializeSandboxSessionStateEnvelope(client, envelope) {
21119
23298
  ...state.exposedPorts ? { exposedPorts: structuredClone(state.exposedPorts) } : {}
21120
23299
  });
21121
23300
  }
21122
- function isProviderSandboxNotFoundError2(backendId, error) {
21123
- if (backendId === "selfhosted") {
21124
- return isSelfhostedProviderNotFoundError(error);
23301
+ var SandboxExecReadinessError = class extends Error {
23302
+ constructor(backend, code, timeoutMs, exitCode = null) {
23303
+ super(
23304
+ code === "exec_probe_timeout" ? `sandbox creation timed out waiting for ${backend} command readiness after ${timeoutMs}ms` : code === "exec_probe_unavailable" ? `${backend} sandbox session does not expose an exec readiness probe` : exitCode === null ? `${backend} sandbox exec readiness probe did not return a command exit code` : `${backend} sandbox exec readiness probe failed with exit code ${exitCode}`
23305
+ );
23306
+ this.backend = backend;
23307
+ this.code = code;
23308
+ this.timeoutMs = timeoutMs;
23309
+ this.exitCode = exitCode;
21125
23310
  }
21126
- if (!error) {
21127
- return false;
23311
+ name = "SandboxExecReadinessError";
23312
+ };
23313
+ function sandboxExecProbeExitCode(result) {
23314
+ if (typeof result === "string") {
23315
+ const match = result.match(/Process exited with code (-?\d+)/);
23316
+ return match ? Number(match[1]) : null;
21128
23317
  }
21129
- const status = error.status ?? error.statusCode;
21130
- if (status === 404) {
21131
- return true;
23318
+ if (!result || typeof result !== "object") return null;
23319
+ const candidate = result;
23320
+ for (const value of [candidate.exitCode, candidate.exit_code, candidate.code, candidate.status]) {
23321
+ if (typeof value === "number") return value;
21132
23322
  }
21133
- const name = typeof error.name === "string" ? error.name : "";
21134
- const code = typeof error.code === "string" ? error.code : "";
21135
- const message = error instanceof Error ? error.message : typeof error === "string" ? error : String(error?.message ?? "");
21136
- const haystack = `${name} ${code} ${message}`.toLowerCase();
21137
- const goneMarkers = [
21138
- "not found",
21139
- "no longer running",
21140
- "no longer exists",
21141
- "does not exist",
21142
- "doesn't exist",
21143
- "has been terminated",
21144
- "was terminated",
21145
- "is terminated",
21146
- "sandbox terminated",
21147
- "notfound",
21148
- "sandbox_not_found",
21149
- "box no longer running"
21150
- ];
21151
- if (haystack.includes("already running") || haystack.includes("still running") || haystack.includes("already exists")) {
21152
- return false;
23323
+ return null;
23324
+ }
23325
+ function sandboxExecProbeStillRunning(result) {
23326
+ if (typeof result === "string") return /Process running with session ID \d+/u.test(result);
23327
+ if (!result || typeof result !== "object") return false;
23328
+ const candidate = result;
23329
+ return typeof candidate.sessionId === "number" || typeof candidate.session_id === "number";
23330
+ }
23331
+ async function verifySandboxExecReadiness(established, timeoutMs = 15e3) {
23332
+ if (established.backendId !== "modal") return;
23333
+ const session = established.session;
23334
+ const run2 = session.exec ?? session.execCommand;
23335
+ if (!run2) {
23336
+ throw new SandboxExecReadinessError(established.backendId, "exec_probe_unavailable", timeoutMs);
23337
+ }
23338
+ let timer;
23339
+ try {
23340
+ const result = await Promise.race([
23341
+ run2.call(session, {
23342
+ cmd: "true",
23343
+ yieldTimeMs: 1e3,
23344
+ maxOutputTokens: 1e3
23345
+ }),
23346
+ new Promise((_, reject) => {
23347
+ timer = setTimeout(
23348
+ () => reject(
23349
+ new SandboxExecReadinessError(established.backendId, "exec_probe_timeout", timeoutMs)
23350
+ ),
23351
+ timeoutMs
23352
+ );
23353
+ if (timer && "unref" in timer && typeof timer.unref === "function") timer.unref();
23354
+ })
23355
+ ]);
23356
+ const exitCode = sandboxExecProbeExitCode(result);
23357
+ if (sandboxExecProbeStillRunning(result) || exitCode !== 0) {
23358
+ throw new SandboxExecReadinessError(
23359
+ established.backendId,
23360
+ "exec_probe_failed",
23361
+ timeoutMs,
23362
+ exitCode
23363
+ );
23364
+ }
23365
+ } finally {
23366
+ if (timer) clearTimeout(timer);
21153
23367
  }
21154
- return goneMarkers.some((marker) => haystack.includes(marker));
21155
23368
  }
21156
23369
  function readInstanceId(session) {
21157
23370
  const state = session.state ?? {};
@@ -21189,9 +23402,14 @@ async function establishSandboxSessionFromEnvelope(settings, envelope, opts) {
21189
23402
  }
21190
23403
  const createManifest = { environment };
21191
23404
  const envelopeSessionState = envelope && typeof envelope === "object" ? envelope.sessionState : void 0;
21192
- const workspaceArchives = readWorkspaceArchivePairFromEnvelopeSessionState(envelopeSessionState);
21193
- const workspaceArchive = workspaceArchives.current;
21194
- const coldRestore = async (resumeFallbackState, skipWorkspaceHydrate = false) => {
23405
+ const archiveState = envelopeSessionState && typeof envelopeSessionState === "object" ? envelopeSessionState : void 0;
23406
+ const workspaceArchiveBase64 = archiveState?.workspaceArchive;
23407
+ const workspaceArchiveMetadata = archiveState?.workspaceArchiveMeta;
23408
+ const coldRestore = async (resumeFallbackState) => {
23409
+ const workspaceArchive = readVerifiedWorkspaceArchive(
23410
+ workspaceArchiveBase64,
23411
+ workspaceArchiveMetadata
23412
+ );
21195
23413
  const createStarted = Date.now();
21196
23414
  let restored;
21197
23415
  try {
@@ -21218,56 +23436,68 @@ async function establishSandboxSessionFromEnvelope(settings, envelope, opts) {
21218
23436
  }
21219
23437
  }
21220
23438
  let hydrationApplied = false;
21221
- if (!skipWorkspaceHydrate && (workspaceArchive || workspaceArchives.previous)) {
23439
+ if (workspaceArchive) {
21222
23440
  const hydrate = restored.hydrateWorkspace;
21223
- if (typeof hydrate === "function") {
21224
- let hydrated = false;
21225
- for (const candidate of [
21226
- { archive: workspaceArchive, label: "current" },
21227
- { archive: workspaceArchives.previous, label: "previous" }
21228
- ]) {
21229
- if (!candidate.archive) {
21230
- continue;
21231
- }
23441
+ if (typeof hydrate !== "function") {
23442
+ await terminateCreatedSandbox(client, restored, restoredState);
23443
+ throw new WorkspaceArchiveIntegrityError(
23444
+ "archive_hydration_failed",
23445
+ `sandbox backend ${client.backendId} cannot hydrate selected archive revision ${workspaceArchive.descriptor.revision}`
23446
+ );
23447
+ }
23448
+ try {
23449
+ await hydrate.call(restored, workspaceArchive.bytes);
23450
+ } catch (error) {
23451
+ await terminateCreatedSandbox(client, restored, restored.state);
23452
+ if (error instanceof WorkspaceArchiveIntegrityError) throw error;
23453
+ throw new WorkspaceArchiveIntegrityError(
23454
+ "archive_hydration_failed",
23455
+ `failed to hydrate selected workspace archive revision ${workspaceArchive.descriptor.revision}`,
23456
+ { retryable: true }
23457
+ );
23458
+ }
23459
+ const hydratedState = restored.state;
23460
+ const hydratedInstanceId = readInstanceId(restored);
23461
+ if (hydratedInstanceId && hydratedInstanceId !== established.instanceId) {
23462
+ established = {
23463
+ client,
23464
+ session: restored,
23465
+ sessionState: hydratedState ?? resumeFallbackState,
23466
+ instanceId: hydratedInstanceId,
23467
+ backendId: client.backendId
23468
+ };
23469
+ if (opts.onSandboxCreated) {
21232
23470
  try {
21233
- await hydrate.call(restored, candidate.archive);
21234
- console.info(
21235
- `[sandbox] cold-restore hydrated workspace from ${candidate.label} archive`
21236
- );
21237
- hydrated = true;
21238
- break;
21239
- } catch (hydrateError) {
21240
- console.warn(
21241
- `[sandbox] cold-restore failed to hydrate workspace from ${candidate.label} archive${candidate.label === "current" && workspaceArchives.previous ? "; trying previous archive" : ""}`,
21242
- hydrateError
21243
- );
23471
+ await opts.onSandboxCreated(established);
23472
+ } catch (createCallbackError) {
23473
+ await terminateCreatedSandbox(client, restored, hydratedState);
23474
+ throw createCallbackError;
21244
23475
  }
21245
23476
  }
21246
- if (!hydrated) {
21247
- await terminateCreatedSandbox(client, restored, restoredState);
21248
- return await coldRestore(resumeFallbackState, true);
21249
- }
21250
- hydrationApplied = true;
21251
- const hydratedState = restored.state;
21252
- const hydratedInstanceId = readInstanceId(restored);
21253
- if (hydratedInstanceId && hydratedInstanceId !== established.instanceId) {
21254
- established = {
21255
- client,
21256
- session: restored,
21257
- sessionState: hydratedState ?? resumeFallbackState,
21258
- instanceId: hydratedInstanceId,
21259
- backendId: client.backendId
21260
- };
21261
- if (opts.onSandboxCreated) {
21262
- try {
21263
- await opts.onSandboxCreated(established);
21264
- } catch (createCallbackError) {
21265
- await terminateCreatedSandbox(client, restored, hydratedState);
21266
- throw createCallbackError;
21267
- }
21268
- }
23477
+ }
23478
+ if (opts.onWorkspaceRestoreVerifying) {
23479
+ try {
23480
+ await opts.onWorkspaceRestoreVerifying(workspaceArchive.descriptor);
23481
+ } catch (error) {
23482
+ await terminateCreatedSandbox(client, restored, restored.state);
23483
+ throw error;
21269
23484
  }
21270
23485
  }
23486
+ try {
23487
+ await verifyRestoredWorkspace(restored, workspaceArchive.descriptor);
23488
+ } catch (error) {
23489
+ await terminateCreatedSandbox(client, restored, restored.state);
23490
+ if (error instanceof WorkspaceArchiveIntegrityError) throw error;
23491
+ throw new WorkspaceArchiveIntegrityError(
23492
+ "workspace_fingerprint_unavailable",
23493
+ `failed to verify selected workspace archive revision ${workspaceArchive.descriptor.revision}`,
23494
+ { retryable: true }
23495
+ );
23496
+ }
23497
+ hydrationApplied = true;
23498
+ console.info(
23499
+ `[sandbox] cold-restore verified workspace archive revision ${workspaceArchive.descriptor.revision}`
23500
+ );
21271
23501
  }
21272
23502
  restoredState = restored.state;
21273
23503
  return {
@@ -21276,7 +23506,8 @@ async function establishSandboxSessionFromEnvelope(settings, envelope, opts) {
21276
23506
  sessionState: restoredState ?? resumeFallbackState,
21277
23507
  instanceId: readInstanceId(restored),
21278
23508
  backendId: client.backendId,
21279
- origin: hydrationApplied ? "restored" : "created"
23509
+ origin: hydrationApplied ? "restored" : "created",
23510
+ ...workspaceArchive ? { restoredArchive: workspaceArchive.descriptor } : {}
21280
23511
  };
21281
23512
  };
21282
23513
  const envelopeProviderState = envelopeSessionState && typeof envelopeSessionState === "object" ? envelopeSessionState.providerState : void 0;
@@ -21362,6 +23593,79 @@ async function serializeEstablishedSandboxEnvelope(established) {
21362
23593
  return null;
21363
23594
  }
21364
23595
  }
23596
+ var PROVIDER_IDENTITY_FIELDS = [
23597
+ "sandboxId",
23598
+ "instanceId",
23599
+ "id",
23600
+ "hostId",
23601
+ "containerId",
23602
+ "workspaceRootPath",
23603
+ "agentId"
23604
+ ];
23605
+ function hasPersistableSandboxProviderIdentity(envelope) {
23606
+ const sessionState = envelope?.sessionState && typeof envelope.sessionState === "object" ? envelope.sessionState : null;
23607
+ const providerState = sessionState?.providerState && typeof sessionState.providerState === "object" ? sessionState.providerState : null;
23608
+ return Boolean(
23609
+ providerState && PROVIDER_IDENTITY_FIELDS.some(
23610
+ (field) => typeof providerState[field] === "string" && providerState[field].length > 0
23611
+ )
23612
+ );
23613
+ }
23614
+ var SandboxReplacementProviderStateError = class extends Error {
23615
+ constructor(backend) {
23616
+ super(
23617
+ `sandbox backend "${backend}" replacement provider state could not be serialized; refusing to publish an attachable lease`
23618
+ );
23619
+ this.backend = backend;
23620
+ this.name = "SandboxReplacementProviderStateError";
23621
+ }
23622
+ code = "replacement_provider_state_unpersistable";
23623
+ };
23624
+ function requirePersistableReplacementSandboxEnvelope(envelope, backend) {
23625
+ if (!hasPersistableSandboxProviderIdentity(envelope)) {
23626
+ throw new SandboxReplacementProviderStateError(backend);
23627
+ }
23628
+ return envelope;
23629
+ }
23630
+ var DURABLE_WORKSPACE_ARCHIVE_FIELDS = [
23631
+ "workspaceArchive",
23632
+ "workspaceArchiveMeta",
23633
+ "workspaceArchivePrev",
23634
+ "workspaceArchivePrevMeta",
23635
+ "workspaceArchiveAt"
23636
+ ];
23637
+ function durableWorkspaceArchiveFields(envelope) {
23638
+ const sessionState = envelope?.sessionState && typeof envelope.sessionState === "object" ? envelope.sessionState : null;
23639
+ if (!sessionState || typeof sessionState.workspaceArchive !== "string" || sessionState.workspaceArchive.length === 0) {
23640
+ return null;
23641
+ }
23642
+ const fields = {};
23643
+ for (const key of DURABLE_WORKSPACE_ARCHIVE_FIELDS) {
23644
+ if (sessionState[key] !== void 0 && sessionState[key] !== null) {
23645
+ fields[key] = sessionState[key];
23646
+ }
23647
+ }
23648
+ return fields;
23649
+ }
23650
+ async function serializeReplacementSandboxEnvelope(established, archiveSource) {
23651
+ const serialized = await serializeEstablishedSandboxEnvelope(established);
23652
+ const archiveFields = durableWorkspaceArchiveFields(archiveSource);
23653
+ if (!serialized && !archiveFields) {
23654
+ return null;
23655
+ }
23656
+ const serializedSessionState = serialized?.sessionState && typeof serialized.sessionState === "object" ? serialized.sessionState : {};
23657
+ return {
23658
+ ...serialized ?? {},
23659
+ // Never inherit a historical backend marker when the replacement could not
23660
+ // serialize. The separately-persisted resume_backend_id and this envelope
23661
+ // must both describe the replacement.
23662
+ backendId: established.backendId,
23663
+ sessionState: {
23664
+ ...serializedSessionState,
23665
+ ...archiveFields ?? {}
23666
+ }
23667
+ };
23668
+ }
21365
23669
 
21366
23670
  export {
21367
23671
  localDirLazySkillSource,
@@ -21429,6 +23733,17 @@ export {
21429
23733
  isSelfhostedProviderNotFoundError,
21430
23734
  PROVIDER_REGISTRY,
21431
23735
  assertProviderRegistryInvariants,
23736
+ classifyProviderSandboxFailure,
23737
+ isProviderSandboxNotFoundError2 as isProviderSandboxNotFoundError,
23738
+ isProviderSandboxGoneDuringRoutedOperation,
23739
+ isProviderSandboxTransientError,
23740
+ WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION,
23741
+ WorkspaceArchiveIntegrityError,
23742
+ parseWorkspaceArchiveDescriptor,
23743
+ readVerifiedWorkspaceArchive,
23744
+ fingerprintSandboxWorkspace,
23745
+ captureVerifiedWorkspaceArchive,
23746
+ verifyRestoredWorkspace,
21432
23747
  selectBackend,
21433
23748
  backendSupportsOs,
21434
23749
  desktopCapableBackend,
@@ -21437,6 +23752,9 @@ export {
21437
23752
  mintStreamToken,
21438
23753
  verifyStreamToken,
21439
23754
  StreamTokenPayload2 as StreamTokenPayload,
23755
+ parseExecBannerSessionId,
23756
+ parseExecBannerExitCode,
23757
+ isExecSessionLostBanner,
21440
23758
  STREAM_PORT,
21441
23759
  DISPLAY_STACK_TIMEOUT_MS,
21442
23760
  DEFAULT_DESKTOP_GEOMETRY,
@@ -21481,6 +23799,9 @@ export {
21481
23799
  uploadRecordingArtifact,
21482
23800
  deleteRecordingArtifacts,
21483
23801
  recordingStorageKey,
23802
+ TurnSandboxCommandCancelledError,
23803
+ createTurnToolCancellationController,
23804
+ wrapCapabilityToolsForTurnCancellation,
21484
23805
  ChannelAValidationError,
21485
23806
  ChannelAUnavailableError,
21486
23807
  ChannelAConflictError,
@@ -21489,9 +23810,6 @@ export {
21489
23810
  REPOSITORY_DISCOVERY_LIMIT,
21490
23811
  SandboxChannelAService,
21491
23812
  stripExecBanner2 as stripExecBanner,
21492
- isExecSessionLostBanner,
21493
- parseExecBannerSessionId,
21494
- parseExecBannerExitCode,
21495
23813
  assertSafeRelPath,
21496
23814
  parsePorcelainV2,
21497
23815
  parseNumstatZ,
@@ -21501,6 +23819,9 @@ export {
21501
23819
  negotiateSelfhostedCapabilities,
21502
23820
  MockAgentResponder,
21503
23821
  RoutingUnsupportedError,
23822
+ RoutingBackendRecoveryRequiredError,
23823
+ RoutingMutationOutcomeUnknownError,
23824
+ RoutingRetainedProcessNotFoundError,
21504
23825
  RoutingSandboxSession,
21505
23826
  ActiveBackendUnresolvableError,
21506
23827
  swapTargetEstablishability,
@@ -21513,10 +23834,15 @@ export {
21513
23834
  decodeModalSnapshotId,
21514
23835
  deletePriorPersistedSnapshot,
21515
23836
  deserializeSandboxSessionStateEnvelope,
21516
- isProviderSandboxNotFoundError2 as isProviderSandboxNotFoundError,
23837
+ SandboxExecReadinessError,
23838
+ verifySandboxExecReadiness,
21517
23839
  establishSandboxSessionFromEnvelope,
21518
23840
  serializeEstablishedSandboxEnvelope,
23841
+ hasPersistableSandboxProviderIdentity,
23842
+ SandboxReplacementProviderStateError,
23843
+ requirePersistableReplacementSandboxEnvelope,
23844
+ serializeReplacementSandboxEnvelope,
21519
23845
  collectSandboxEnvironment2 as collectSandboxEnvironment,
21520
23846
  parseExposedPorts2 as parseExposedPorts
21521
23847
  };
21522
- //# sourceMappingURL=chunk-YUAFQEFY.js.map
23848
+ //# sourceMappingURL=chunk-JGUQSAG5.js.map