@opengeni/runtime 0.13.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.
package/dist/index.js CHANGED
@@ -29,6 +29,9 @@ import {
29
29
  REPOSITORY_DISCOVERY_LIMIT,
30
30
  RecordingError,
31
31
  RecordingUnavailableError,
32
+ RoutingBackendRecoveryRequiredError,
33
+ RoutingMutationOutcomeUnknownError,
34
+ RoutingRetainedProcessNotFoundError,
32
35
  RoutingSandboxSession,
33
36
  RoutingUnsupportedError,
34
37
  RunCredentialValidationError,
@@ -48,7 +51,9 @@ import {
48
51
  STREAM_TOKEN_DEFAULT_TTL_SECONDS,
49
52
  SandboxChannelAService,
50
53
  SandboxConfigError,
54
+ SandboxExecReadinessError,
51
55
  SandboxProviderUnavailableError,
56
+ SandboxReplacementProviderStateError,
52
57
  SandboxResumeStateUnavailableError,
53
58
  SelfhostedControlError,
54
59
  SelfhostedSandboxClient,
@@ -60,6 +65,9 @@ import {
60
65
  TerminalServerError,
61
66
  TerminalServerUnsupportedError,
62
67
  ToolspaceTokenPathError,
68
+ TurnSandboxCommandCancelledError,
69
+ WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION,
70
+ WorkspaceArchiveIntegrityError,
63
71
  agentErrorToControlError,
64
72
  assertDescriptorRegistryInvariants,
65
73
  assertProviderRegistryInvariants,
@@ -69,12 +77,15 @@ import {
69
77
  buildSelfhostedBackendSession,
70
78
  buildStreamUrl,
71
79
  buildTerminalServerScript,
80
+ captureVerifiedWorkspaceArchive,
81
+ classifyProviderSandboxFailure,
72
82
  clearRunCredentials,
73
83
  clearRunCredentialsForAttempt,
74
84
  collectSandboxEnvironment,
75
85
  contentTypeForCodec,
76
86
  createSandboxClient,
77
87
  createSandboxClientForBackend,
88
+ createTurnToolCancellationController,
78
89
  decideSelfhostedRetry,
79
90
  decodeModalSnapshotId,
80
91
  defaultSelfhostedRetryClock,
@@ -91,9 +102,13 @@ import {
91
102
  execDeadlineHint,
92
103
  exposeStreamPort,
93
104
  extForCodec,
105
+ fingerprintSandboxWorkspace,
106
+ hasPersistableSandboxProviderIdentity,
94
107
  inspectRecordingArtifact,
95
108
  isExecSessionLostBanner,
109
+ isProviderSandboxGoneDuringRoutedOperation,
96
110
  isProviderSandboxNotFoundError,
111
+ isProviderSandboxTransientError,
97
112
  isSelfhostedProviderNotFoundError,
98
113
  localDirLazySkillSource,
99
114
  makeActiveBackendResolver,
@@ -115,10 +130,13 @@ import {
115
130
  parseNumstatZ,
116
131
  parsePorcelainV2,
117
132
  parseUnifiedPatch,
133
+ parseWorkspaceArchiveDescriptor,
118
134
  payloadTooLargeMessage,
135
+ readVerifiedWorkspaceArchive,
119
136
  readWorkspaceArchiveFromEnvelopeSessionState,
120
137
  recordingStorageKey,
121
138
  renderSelfhostedFault,
139
+ requirePersistableReplacementSandboxEnvelope,
122
140
  restoredSandboxSessionStateFromEntry,
123
141
  runCredentialPointerFile,
124
142
  runCredentialRoot,
@@ -130,6 +148,7 @@ import {
130
148
  selfhostedLiveness,
131
149
  selfhostedRetryBackoffMs,
132
150
  serializeEstablishedSandboxEnvelope,
151
+ serializeReplacementSandboxEnvelope,
133
152
  setSelfhostedApplyDiff,
134
153
  startRecording,
135
154
  stopRecording,
@@ -146,14 +165,17 @@ import {
146
165
  toolspaceTokenFileForSession,
147
166
  toolspaceTokenFileFromEnvironment,
148
167
  uploadRecordingArtifact,
168
+ verifyRestoredWorkspace,
169
+ verifySandboxExecReadiness,
149
170
  verifyStreamToken,
150
171
  withRunCredentialEnvironment,
151
172
  withRunCredentialsClient,
152
173
  withRunCredentialsSession,
153
174
  withToolspaceTokenClient,
154
175
  withToolspaceTokenEnvironment,
155
- withToolspaceTokenSession
156
- } from "./chunk-65UDUFUO.js";
176
+ withToolspaceTokenSession,
177
+ wrapCapabilityToolsForTurnCancellation
178
+ } from "./chunk-JGUQSAG5.js";
157
179
  import {
158
180
  getSkillLibraryEntry,
159
181
  isSkillLibraryEntryId,
@@ -172,9 +194,10 @@ import {
172
194
  boundedParallelMap,
173
195
  cancelMcpResponseBody,
174
196
  guardedMcpFetch,
197
+ mcpOuterConnectTimeoutMs,
175
198
  mcpSerializedSizeBytes,
176
199
  undiciFetch
177
- } from "./chunk-HAN5D27J.js";
200
+ } from "./chunk-SSB7VOLE.js";
178
201
  import {
179
202
  MemorySession,
180
203
  connectMcpServers
@@ -5110,7 +5133,7 @@ var OpenAITracingExporter = class {
5110
5133
  const payloadItems = groupedItems.map((entry) => entry.toJSON()).filter((item) => !!item).map((item) => isRecord5(item) ? sanitizePayloadItemForTracesIngest(item) : item);
5111
5134
  const payload = { data: payloadItems };
5112
5135
  let attempts = 0;
5113
- let delay2 = this.#options.baseDelay;
5136
+ let delay = this.#options.baseDelay;
5114
5137
  while (attempts < this.#options.maxRetries) {
5115
5138
  try {
5116
5139
  const response = await fetch(this.#options.endpoint, {
@@ -5144,13 +5167,13 @@ var OpenAITracingExporter = class {
5144
5167
  if (attempts >= this.#options.maxRetries) {
5145
5168
  break;
5146
5169
  }
5147
- const sleepTime = delay2 + Math.random() * 0.1 * delay2;
5170
+ const sleepTime = delay + Math.random() * 0.1 * delay;
5148
5171
  const shouldContinue = await sleepWithAbort(sleepTime, signal);
5149
5172
  if (!shouldContinue) {
5150
5173
  logger_default.error("Tracing: request aborted");
5151
5174
  break;
5152
5175
  }
5153
- delay2 = Math.min(delay2 * 2, this.#options.maxDelay);
5176
+ delay = Math.min(delay * 2, this.#options.maxDelay);
5154
5177
  }
5155
5178
  if (attempts >= this.#options.maxRetries) {
5156
5179
  logger_default.error(`Tracing: failed to export traces after ${this.#options.maxRetries} attempts`);
@@ -7601,668 +7624,6 @@ function safeStringify(value) {
7601
7624
  }
7602
7625
  }
7603
7626
 
7604
- // src/sandbox/turn-tool-cancellation.ts
7605
- var TURN_EXEC_YIELD_MS = 250;
7606
- var TURN_WRITE_YIELD_MS = 250;
7607
- var SHELL_HELPER_YIELD_MS = 1e3;
7608
- var SHELL_GRACEFUL_POLLS = 2;
7609
- var SHELL_POLL_MS = 100;
7610
- var SHELL_MARKER_DIR = "/tmp/opengeni-turn-shell";
7611
- var delay = async (milliseconds) => await new Promise((resolve) => setTimeout(resolve, milliseconds));
7612
- var TurnSandboxCommandCancelledError = class extends Error {
7613
- name = "TurnSandboxCommandCancelledError";
7614
- constructor(reason) {
7615
- super(
7616
- reason instanceof Error ? reason.message : String(reason ?? "Turn sandbox tools were cancelled"),
7617
- {
7618
- ...reason instanceof Error ? { cause: reason } : {}
7619
- }
7620
- );
7621
- }
7622
- };
7623
- function cancellationError(reason) {
7624
- return new TurnSandboxCommandCancelledError(reason);
7625
- }
7626
- function parsedObject(input) {
7627
- try {
7628
- const parsed = JSON.parse(input);
7629
- return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
7630
- } catch {
7631
- return null;
7632
- }
7633
- }
7634
- function cappedYield(value, cap) {
7635
- return typeof value === "number" && Number.isInteger(value) && value >= 0 ? Math.min(value, cap) : cap;
7636
- }
7637
- function execOutput(raw) {
7638
- const marker = "\nOutput:\n";
7639
- const index = raw.indexOf(marker);
7640
- return index >= 0 ? raw.slice(index + marker.length) : "";
7641
- }
7642
- function nativeCommandBanner(result) {
7643
- if (typeof result === "string") return result;
7644
- if (!result || typeof result !== "object") {
7645
- throw new Error("Sandbox command returned an invalid result");
7646
- }
7647
- const value = result;
7648
- const sessionId = typeof value.sessionId === "number" ? value.sessionId : typeof value.session_id === "number" ? value.session_id : null;
7649
- const exitCode = typeof value.exitCode === "number" ? value.exitCode : typeof value.exit_code === "number" ? value.exit_code : null;
7650
- const output = [value.output, value.stderr, value.stdout].filter(
7651
- (candidate) => typeof candidate === "string" && candidate.length > 0
7652
- ).join("\n");
7653
- if (sessionId !== null) {
7654
- return `Process running with session ID ${sessionId}
7655
-
7656
- Output:
7657
- ${output}`;
7658
- }
7659
- if (exitCode !== null) {
7660
- return `Process exited with code ${exitCode}
7661
-
7662
- Output:
7663
- ${output}`;
7664
- }
7665
- throw new Error("Sandbox command did not report a session id or exit code");
7666
- }
7667
- function completedCommandBanner(exitCode, output) {
7668
- return `Process exited with code ${exitCode}
7669
-
7670
- Output:
7671
- ${output}`;
7672
- }
7673
- function appendBoundedOutput(current, chunk, maxOutputTokens) {
7674
- const maxChars = Math.max(512, maxOutputTokens * 4);
7675
- const combined = current + chunk;
7676
- return combined.length <= maxChars ? combined : combined.slice(-maxChars);
7677
- }
7678
- function singleQuote(value) {
7679
- return `'${value.replaceAll("'", `'"'"'`)}'`;
7680
- }
7681
- function shellMarkerPath(token) {
7682
- return `${SHELL_MARKER_DIR}/${token}`;
7683
- }
7684
- function cancellableGroupLeaderCommand(command, markerPath) {
7685
- const marker = singleQuote(markerPath);
7686
- const markerDir = singleQuote(SHELL_MARKER_DIR);
7687
- return [
7688
- `__opengeni_marker=${marker}`,
7689
- "umask 077",
7690
- `command mkdir -p ${markerDir} || exit 125`,
7691
- '__opengeni_pid="$$"',
7692
- `__opengeni_pgid="$(command ps -o pgid= -p "$__opengeni_pid" 2>/dev/null | command tr -d '[:space:]')"`,
7693
- 'case "$__opengeni_pid:$__opengeni_pgid" in *[!0-9:]*|*:|:*) exit 125 ;; esac',
7694
- '[ "$__opengeni_pid" -gt 1 ] && [ "$__opengeni_pgid" -gt 1 ] || exit 125',
7695
- // Never signal a provider/container-wide process group. A cancellable PTY
7696
- // command must be its group's leader; fail the command closed otherwise.
7697
- '[ "$__opengeni_pid" = "$__opengeni_pgid" ] || exit 125',
7698
- `command printf '%s %s\\n' "$__opengeni_pid" "$__opengeni_pgid" > "$__opengeni_marker" || exit 125`,
7699
- `trap 'command rm -f "$__opengeni_marker"' EXIT`,
7700
- "(",
7701
- command,
7702
- ")",
7703
- "__opengeni_status=$?",
7704
- 'exit "$__opengeni_status"'
7705
- ].join("\n");
7706
- }
7707
- function cancellableShellCommand(command, markerPath) {
7708
- const groupLeaderCommand = cancellableGroupLeaderCommand(command, markerPath);
7709
- return [
7710
- '__opengeni_outer_pid="$$"',
7711
- `__opengeni_outer_pgid="$(command ps -o pgid= -p "$__opengeni_outer_pid" 2>/dev/null | command tr -d '[:space:]')"`,
7712
- 'case "$__opengeni_outer_pid:$__opengeni_outer_pgid" in *[!0-9:]*|*:|:*) exit 125 ;; esac',
7713
- 'if [ "$__opengeni_outer_pid" != "$__opengeni_outer_pgid" ]; then',
7714
- ' __opengeni_setsid="$(command -v setsid 2>/dev/null)"',
7715
- ' [ -n "$__opengeni_setsid" ] || exit 125',
7716
- ` exec "$__opengeni_setsid" /bin/sh -c ${singleQuote(groupLeaderCommand)}`,
7717
- "fi",
7718
- groupLeaderCommand
7719
- ].join("\n");
7720
- }
7721
- function shellHelperInput(command) {
7722
- return JSON.stringify({
7723
- cmd: command,
7724
- login: false,
7725
- tty: false,
7726
- yield_time_ms: SHELL_HELPER_YIELD_MS,
7727
- max_output_tokens: 128
7728
- });
7729
- }
7730
- function safeProcessIdentity(raw) {
7731
- if (parseExecBannerExitCode(raw) !== 0) return null;
7732
- const match = execOutput(raw).trim().match(/^(\d+)\s+(\d+)$/);
7733
- if (!match) return null;
7734
- const pid = Number.parseInt(match[1], 10);
7735
- const processGroupId = Number.parseInt(match[2], 10);
7736
- return Number.isSafeInteger(pid) && Number.isSafeInteger(processGroupId) && pid > 1 && processGroupId > 1 ? { pid, processGroupId } : null;
7737
- }
7738
- function identityGuardScript(state, body, missingIdentityExitCode) {
7739
- const identity = state.identity;
7740
- if (!identity || !state.token) return `exit ${missingIdentityExitCode}`;
7741
- const token = singleQuote(state.token);
7742
- return [
7743
- `__opengeni_pid=${identity.pid}`,
7744
- `__opengeni_pgid=${identity.processGroupId}`,
7745
- `__opengeni_token=${token}`,
7746
- // The randomized token lands late in the wrapped command line. Procps
7747
- // otherwise truncates `args` to the terminal width, which can make a live
7748
- // group look stale and open the quiescence fence without signalling it.
7749
- '__opengeni_args="$(command ps -ww -o args= -p "$__opengeni_pid" 2>/dev/null)"',
7750
- 'case "$__opengeni_args" in *"$__opengeni_token"*) ;; *) exit 0 ;; esac',
7751
- `__opengeni_live_pgid="$(command ps -o pgid= -p "$__opengeni_pid" 2>/dev/null | command tr -d '[:space:]')"`,
7752
- '[ "$__opengeni_live_pgid" = "$__opengeni_pgid" ] || exit 0',
7753
- body
7754
- ].join("\n");
7755
- }
7756
- function wrapComputer(computer, track) {
7757
- const wrapped = Object.create(Object.getPrototypeOf(computer));
7758
- Object.assign(wrapped, computer);
7759
- for (const method of [
7760
- "initRun",
7761
- "screenshot",
7762
- "click",
7763
- "doubleClick",
7764
- "scroll",
7765
- "type",
7766
- "wait",
7767
- "move",
7768
- "keypress",
7769
- "drag"
7770
- ]) {
7771
- const original = computer[method];
7772
- if (typeof original !== "function") continue;
7773
- wrapped[method] = (...args) => track(async () => await original.apply(computer, args));
7774
- }
7775
- return wrapped;
7776
- }
7777
- var TurnToolCancellationControllerImpl = class {
7778
- cancelled = false;
7779
- reason;
7780
- inFlight = /* @__PURE__ */ new Set();
7781
- shellSessions = /* @__PURE__ */ new Map();
7782
- remoteExecs = /* @__PURE__ */ new Set();
7783
- rawExecInvoke = null;
7784
- rawWriteInvoke = null;
7785
- drainPromise = null;
7786
- signal;
7787
- onAbort = () => this.cancel(this.signal?.reason);
7788
- constructor(signal) {
7789
- this.signal = signal;
7790
- if (signal?.aborted) {
7791
- this.cancel(signal.reason);
7792
- } else {
7793
- signal?.addEventListener("abort", this.onAbort, { once: true });
7794
- }
7795
- }
7796
- cancel(reason) {
7797
- if (!this.cancelled) {
7798
- this.cancelled = true;
7799
- this.reason = reason;
7800
- this.signal?.removeEventListener("abort", this.onAbort);
7801
- }
7802
- void this.ensureDrain().catch(() => void 0);
7803
- }
7804
- async waitForQuiescence() {
7805
- this.cancel(this.reason);
7806
- await this.ensureDrain();
7807
- }
7808
- runSandboxCommand(session, args) {
7809
- return this.track(async () => {
7810
- if (!session.exec && !session.execCommand) {
7811
- throw new Error("Sandbox session does not support command execution");
7812
- }
7813
- const lifecycleRunContext = {};
7814
- const invokeExec = async (_runContext, input) => {
7815
- const parsed = parsedObject(input);
7816
- if (!parsed || typeof parsed.cmd !== "string") {
7817
- throw new Error("Sandbox lifecycle command input was invalid");
7818
- }
7819
- const commandArgs = {
7820
- cmd: parsed.cmd,
7821
- ...typeof parsed.workdir === "string" ? { workdir: parsed.workdir } : {},
7822
- ...typeof parsed.shell === "string" ? { shell: parsed.shell } : {},
7823
- ...typeof parsed.login === "boolean" ? { login: parsed.login } : {},
7824
- ...typeof parsed.tty === "boolean" ? { tty: parsed.tty } : {},
7825
- ...typeof parsed.yield_time_ms === "number" ? { yieldTimeMs: parsed.yield_time_ms } : {},
7826
- ...typeof parsed.max_output_tokens === "number" ? { maxOutputTokens: parsed.max_output_tokens } : {},
7827
- ...typeof parsed.run_as === "string" ? { runAs: parsed.run_as } : {}
7828
- };
7829
- const result = session.supportsPty?.() !== false && session.execCommand ? await session.execCommand(commandArgs) : session.exec ? await session.exec(commandArgs) : await session.execCommand(commandArgs);
7830
- return nativeCommandBanner(result);
7831
- };
7832
- const invokeWrite = session.writeStdin ? async (_runContext, input) => {
7833
- const parsed = parsedObject(input);
7834
- if (!parsed || typeof parsed.session_id !== "number") {
7835
- throw new Error("Sandbox lifecycle stdin input was invalid");
7836
- }
7837
- return await session.writeStdin({
7838
- sessionId: parsed.session_id,
7839
- ...typeof parsed.chars === "string" ? { chars: parsed.chars } : {},
7840
- ...typeof parsed.yield_time_ms === "number" ? { yieldTimeMs: parsed.yield_time_ms } : {},
7841
- ...typeof parsed.max_output_tokens === "number" ? { maxOutputTokens: parsed.max_output_tokens } : {}
7842
- });
7843
- } : null;
7844
- const correlationId = `turn_lifecycle_${crypto.randomUUID()}`;
7845
- const useRemoteOpCancellation = Boolean(session.cancelExecCommand) && session.supportsPty?.() === false;
7846
- const remoteExec = session.cancelExecCommand && useRemoteOpCancellation ? this.registerRemoteExec(
7847
- { cancelExecCommand: session.cancelExecCommand.bind(session) },
7848
- `${sanitizeOpIdToken(correlationId)}:0`
7849
- ) : null;
7850
- const markerPath = shellMarkerPath(crypto.randomUUID());
7851
- const commandInput = JSON.stringify({
7852
- cmd: useRemoteOpCancellation ? args.cmd : cancellableShellCommand(args.cmd, markerPath),
7853
- ...args.workdir ? { workdir: args.workdir } : {},
7854
- ...args.shell ? { shell: args.shell } : {},
7855
- ...args.login !== void 0 ? { login: args.login } : {},
7856
- ...args.runAs ? { run_as: args.runAs } : {},
7857
- tty: useRemoteOpCancellation ? args.tty : true,
7858
- yield_time_ms: useRemoteOpCancellation ? args.yieldTimeMs : cappedYield(args.yieldTimeMs, TURN_EXEC_YIELD_MS),
7859
- ...args.maxOutputTokens !== void 0 ? { max_output_tokens: args.maxOutputTokens } : {}
7860
- });
7861
- let initial;
7862
- try {
7863
- initial = await runWithToolCallCorrelation(
7864
- correlationId,
7865
- async () => await invokeExec(lifecycleRunContext, commandInput, void 0)
7866
- );
7867
- } finally {
7868
- remoteExec?.settle();
7869
- }
7870
- if (typeof initial !== "string" || useRemoteOpCancellation) return initial;
7871
- const sessionId = parseExecBannerSessionId(initial);
7872
- if (sessionId === null) return initial;
7873
- if (!invokeWrite) {
7874
- throw new Error("Sandbox lifecycle command yielded without stdin support");
7875
- }
7876
- const token = markerPath.slice(markerPath.lastIndexOf("/") + 1);
7877
- const state = {
7878
- sessionId,
7879
- markerPath,
7880
- token,
7881
- runContext: lifecycleRunContext,
7882
- execInvoke: invokeExec,
7883
- writeInvoke: invokeWrite,
7884
- identity: null,
7885
- identityValidated: false,
7886
- cancellation: null
7887
- };
7888
- this.shellSessions.set(sessionId, state);
7889
- const maxOutputTokens = args.maxOutputTokens ?? 2e4;
7890
- const initialOutput = execOutput(initial);
7891
- let output = initialOutput ? appendBoundedOutput("", initialOutput, maxOutputTokens) : "";
7892
- while (true) {
7893
- if (this.cancelled) throw cancellationError(this.reason);
7894
- const next = await invokeWrite(
7895
- lifecycleRunContext,
7896
- JSON.stringify({
7897
- session_id: sessionId,
7898
- chars: "",
7899
- yield_time_ms: TURN_WRITE_YIELD_MS,
7900
- max_output_tokens: maxOutputTokens
7901
- }),
7902
- void 0
7903
- );
7904
- if (typeof next !== "string") {
7905
- throw new Error("Sandbox lifecycle stdin returned an invalid result");
7906
- }
7907
- const nextOutput = execOutput(next);
7908
- if (nextOutput) output = appendBoundedOutput(output, nextOutput, maxOutputTokens);
7909
- const exitCode = parseExecBannerExitCode(next);
7910
- if (exitCode !== null) {
7911
- this.shellSessions.delete(sessionId);
7912
- return completedCommandBanner(exitCode, output);
7913
- }
7914
- await delay(SHELL_POLL_MS);
7915
- }
7916
- });
7917
- }
7918
- wrapTools(tools, session) {
7919
- for (const tool2 of tools) {
7920
- if (tool2.type !== "function") continue;
7921
- if (tool2.name === "exec_command") this.rawExecInvoke = tool2.invoke;
7922
- if (tool2.name === "write_stdin") this.rawWriteInvoke = tool2.invoke;
7923
- }
7924
- return tools.map((tool2) => {
7925
- if (tool2.type === "function") return this.wrapFunctionTool(tool2, session);
7926
- if (tool2.type === "apply_patch") return this.wrapApplyPatchTool(tool2);
7927
- if (tool2.type === "computer") return this.wrapComputerTool(tool2);
7928
- return tool2;
7929
- });
7930
- }
7931
- wrapFunctionTool(tool2, cancellationSession) {
7932
- if (tool2.name === "exec_command") {
7933
- return {
7934
- ...tool2,
7935
- invoke: (runContext, input, details) => this.track(async () => {
7936
- const parsed = parsedObject(input);
7937
- if (!parsed || typeof parsed.cmd !== "string") {
7938
- return await this.correlatedInvoke(tool2.invoke, runContext, input, details);
7939
- }
7940
- const token = crypto.randomUUID();
7941
- const markerPath = shellMarkerPath(token);
7942
- const correlationId = details?.toolCall?.callId ?? `turn_exec_${token}`;
7943
- const cancelExecCommand = cancellationSession?.cancelExecCommand;
7944
- const useRemoteOpCancellation = Boolean(cancelExecCommand) && cancellationSession?.supportsPty?.() === false;
7945
- const remoteExec = cancelExecCommand && useRemoteOpCancellation ? this.registerRemoteExec(
7946
- { cancelExecCommand: cancelExecCommand.bind(cancellationSession) },
7947
- `${sanitizeOpIdToken(correlationId)}:0`
7948
- ) : null;
7949
- const cancellableInput = useRemoteOpCancellation ? input : JSON.stringify({
7950
- ...parsed,
7951
- cmd: cancellableShellCommand(parsed.cmd, markerPath),
7952
- // The SDK's only provider-neutral live-process interrupt is a PTY.
7953
- // The process marker provides TERM/KILL escalation when Ctrl-C is
7954
- // ignored; the short yield exposes the provider session promptly.
7955
- tty: true,
7956
- yield_time_ms: cappedYield(parsed.yield_time_ms, TURN_EXEC_YIELD_MS)
7957
- });
7958
- let output;
7959
- try {
7960
- output = await runWithToolCallCorrelation(
7961
- correlationId,
7962
- async () => await tool2.invoke(runContext, cancellableInput, details)
7963
- );
7964
- } finally {
7965
- remoteExec?.settle();
7966
- }
7967
- if (typeof output !== "string") return output;
7968
- const sessionId = useRemoteOpCancellation ? null : parseExecBannerSessionId(output);
7969
- if (sessionId !== null) {
7970
- this.shellSessions.set(sessionId, {
7971
- sessionId,
7972
- markerPath,
7973
- token,
7974
- runContext,
7975
- execInvoke: tool2.invoke,
7976
- writeInvoke: this.rawWriteInvoke,
7977
- identity: null,
7978
- identityValidated: false,
7979
- cancellation: null
7980
- });
7981
- }
7982
- return output;
7983
- })
7984
- };
7985
- }
7986
- if (tool2.name === "write_stdin") {
7987
- return {
7988
- ...tool2,
7989
- invoke: (runContext, input, details) => this.track(async () => {
7990
- const parsed = parsedObject(input);
7991
- const sessionId = parsed && typeof parsed.session_id === "number" && Number.isSafeInteger(parsed.session_id) ? parsed.session_id : null;
7992
- const cappedInput = parsed ? JSON.stringify({
7993
- ...parsed,
7994
- yield_time_ms: cappedYield(parsed.yield_time_ms, TURN_WRITE_YIELD_MS)
7995
- }) : input;
7996
- const output = await tool2.invoke(runContext, cappedInput, details);
7997
- if (sessionId !== null && typeof output === "string") {
7998
- if (isExecSessionLostBanner(output, sessionId)) {
7999
- this.shellSessions.delete(sessionId);
8000
- } else if (parseExecBannerSessionId(output) === sessionId) {
8001
- if (!this.shellSessions.has(sessionId)) {
8002
- this.shellSessions.set(sessionId, {
8003
- sessionId,
8004
- markerPath: null,
8005
- token: null,
8006
- runContext,
8007
- execInvoke: this.rawExecInvoke ?? tool2.invoke,
8008
- writeInvoke: tool2.invoke,
8009
- identity: null,
8010
- identityValidated: false,
8011
- cancellation: null
8012
- });
8013
- }
8014
- } else if (parseExecBannerExitCode(output) !== null) {
8015
- this.shellSessions.delete(sessionId);
8016
- }
8017
- }
8018
- return output;
8019
- })
8020
- };
8021
- }
8022
- return {
8023
- ...tool2,
8024
- invoke: (runContext, input, details) => this.track(async () => await tool2.invoke(runContext, input, details))
8025
- };
8026
- }
8027
- wrapApplyPatchTool(tool2) {
8028
- const editor = tool2.editor;
8029
- const wrappedEditor = {
8030
- createFile: (operation, context) => this.track(async () => await editor.createFile(operation, context)),
8031
- updateFile: (operation, context) => this.track(async () => await editor.updateFile(operation, context)),
8032
- deleteFile: (operation, context) => this.track(async () => await editor.deleteFile(operation, context))
8033
- };
8034
- return { ...tool2, editor: wrappedEditor };
8035
- }
8036
- wrapComputerTool(tool2) {
8037
- const computer = tool2.computer;
8038
- if (typeof computer === "function") {
8039
- return {
8040
- ...tool2,
8041
- computer: async (...args) => wrapComputer(
8042
- await this.track(async () => await computer(...args)),
8043
- (operation) => this.track(operation)
8044
- )
8045
- };
8046
- }
8047
- if ("create" in computer && typeof computer.create === "function") {
8048
- return {
8049
- ...tool2,
8050
- computer: {
8051
- ...computer,
8052
- create: async (...args) => wrapComputer(
8053
- await this.track(async () => await computer.create(...args)),
8054
- (operation) => this.track(operation)
8055
- )
8056
- }
8057
- };
8058
- }
8059
- return {
8060
- ...tool2,
8061
- computer: wrapComputer(computer, (operation) => this.track(operation))
8062
- };
8063
- }
8064
- async correlatedInvoke(invoke, runContext, input, details) {
8065
- const callId = details?.toolCall?.callId;
8066
- return callId ? await runWithToolCallCorrelation(
8067
- callId,
8068
- async () => await invoke(runContext, input, details)
8069
- ) : await invoke(runContext, input, details);
8070
- }
8071
- track(operation) {
8072
- if (this.cancelled) return Promise.reject(cancellationError(this.reason));
8073
- const promise = Promise.resolve().then(async () => {
8074
- if (this.cancelled) throw cancellationError(this.reason);
8075
- return await operation();
8076
- });
8077
- this.inFlight.add(promise);
8078
- void promise.finally(() => {
8079
- this.inFlight.delete(promise);
8080
- }).catch(() => void 0);
8081
- return promise;
8082
- }
8083
- ensureDrain() {
8084
- this.drainPromise ??= this.drain();
8085
- return this.drainPromise;
8086
- }
8087
- async drain() {
8088
- while (true) {
8089
- const remoteCancellations = [...this.remoteExecs].map(async (exec) => await exec.cancel());
8090
- const inFlight = [...this.inFlight];
8091
- if (inFlight.length > 0 || remoteCancellations.length > 0) {
8092
- await Promise.allSettled([...inFlight, ...remoteCancellations]);
8093
- }
8094
- const sessions = [...this.shellSessions.values()];
8095
- if (sessions.length > 0) {
8096
- await Promise.all(sessions.map(async (session) => await this.cancelShellSession(session)));
8097
- }
8098
- if (this.inFlight.size === 0 && this.shellSessions.size === 0 && this.remoteExecs.size === 0)
8099
- return;
8100
- }
8101
- }
8102
- registerRemoteExec(session, opId) {
8103
- let resolveSettled;
8104
- const settledPromise = new Promise((resolve) => {
8105
- resolveSettled = resolve;
8106
- });
8107
- let cancellation = null;
8108
- const entry = {
8109
- settled: false,
8110
- settledPromise,
8111
- settle: () => {
8112
- if (entry.settled) return;
8113
- entry.settled = true;
8114
- resolveSettled();
8115
- this.remoteExecs.delete(entry);
8116
- },
8117
- cancel: () => {
8118
- cancellation ??= (async () => {
8119
- while (!entry.settled) {
8120
- const attempt = session.cancelExecCommand(opId).catch(() => false);
8121
- await Promise.race([attempt, entry.settledPromise]);
8122
- await Promise.race([entry.settledPromise, delay(SHELL_POLL_MS)]);
8123
- }
8124
- })();
8125
- return cancellation;
8126
- }
8127
- };
8128
- this.remoteExecs.add(entry);
8129
- return entry;
8130
- }
8131
- cancelShellSession(state) {
8132
- state.cancellation ??= this.cancelShellSessionOnce(state).finally(() => {
8133
- state.cancellation = null;
8134
- });
8135
- return state.cancellation;
8136
- }
8137
- async cancelShellSessionOnce(state) {
8138
- if (!state.markerPath) {
8139
- while (true) {
8140
- if (state.writeInvoke && await this.rawWrite(state, "", SHELL_POLL_MS)) {
8141
- this.shellSessions.delete(state.sessionId);
8142
- return;
8143
- }
8144
- await delay(SHELL_POLL_MS);
8145
- }
8146
- }
8147
- while (!state.identity) {
8148
- state.identity = await this.readIdentity(state);
8149
- if (state.identity) break;
8150
- if (state.writeInvoke) {
8151
- const completed = await this.rawWrite(state, "", SHELL_POLL_MS);
8152
- if (completed) {
8153
- this.shellSessions.delete(state.sessionId);
8154
- return;
8155
- }
8156
- }
8157
- await delay(SHELL_POLL_MS);
8158
- }
8159
- if (!await this.identityAlive(state)) {
8160
- this.shellSessions.delete(state.sessionId);
8161
- return;
8162
- }
8163
- if (state.writeInvoke) {
8164
- await this.rawWrite(state, "", SHELL_POLL_MS);
8165
- if (!await this.identityAlive(state)) {
8166
- this.shellSessions.delete(state.sessionId);
8167
- return;
8168
- }
8169
- }
8170
- await this.signalIdentity(state, "TERM");
8171
- for (let attempt = 0; attempt < SHELL_GRACEFUL_POLLS; attempt++) {
8172
- if (state.writeInvoke) await this.rawWrite(state, "", SHELL_POLL_MS);
8173
- if (!await this.identityAlive(state)) {
8174
- this.shellSessions.delete(state.sessionId);
8175
- return;
8176
- }
8177
- }
8178
- await this.signalIdentity(state, "KILL");
8179
- while (await this.identityAlive(state)) {
8180
- if (state.writeInvoke) await this.rawWrite(state, "", SHELL_POLL_MS);
8181
- await this.signalIdentity(state, "KILL");
8182
- await delay(SHELL_POLL_MS);
8183
- }
8184
- this.shellSessions.delete(state.sessionId);
8185
- }
8186
- async rawWrite(state, chars, yieldTimeMs) {
8187
- if (!state.writeInvoke) return false;
8188
- try {
8189
- const output = await state.writeInvoke(
8190
- state.runContext,
8191
- JSON.stringify({
8192
- session_id: state.sessionId,
8193
- chars,
8194
- yield_time_ms: yieldTimeMs,
8195
- max_output_tokens: 128
8196
- }),
8197
- void 0
8198
- );
8199
- if (typeof output !== "string") return false;
8200
- if (isExecSessionLostBanner(output, state.sessionId)) return true;
8201
- if (parseExecBannerSessionId(output) === state.sessionId) return false;
8202
- if (parseExecBannerExitCode(output) !== null) return true;
8203
- } catch {
8204
- }
8205
- return false;
8206
- }
8207
- async readIdentity(state) {
8208
- if (!state.markerPath) return null;
8209
- try {
8210
- const output = await state.execInvoke(
8211
- state.runContext,
8212
- shellHelperInput(`command cat ${singleQuote(state.markerPath)} 2>/dev/null`),
8213
- void 0
8214
- );
8215
- return typeof output === "string" ? safeProcessIdentity(output) : null;
8216
- } catch {
8217
- return null;
8218
- }
8219
- }
8220
- async identityAlive(state) {
8221
- const script = state.identityValidated ? [
8222
- `__opengeni_pgid=${state.identity?.processGroupId ?? 0}`,
8223
- 'command kill -0 "-$__opengeni_pgid" 2>/dev/null && exit 75',
8224
- "exit 0"
8225
- ].join("\n") : identityGuardScript(
8226
- state,
8227
- 'command kill -0 "-$__opengeni_pgid" 2>/dev/null && exit 75\nexit 0',
8228
- 76
8229
- );
8230
- try {
8231
- const output = await state.execInvoke(state.runContext, shellHelperInput(script), void 0);
8232
- if (typeof output !== "string") return true;
8233
- const exitCode = parseExecBannerExitCode(output);
8234
- if (exitCode === 75) {
8235
- state.identityValidated = true;
8236
- return true;
8237
- }
8238
- return exitCode !== 0;
8239
- } catch {
8240
- return true;
8241
- }
8242
- }
8243
- async signalIdentity(state, signal) {
8244
- const script = state.identityValidated ? `command kill -${signal} "-${state.identity?.processGroupId ?? 0}" 2>/dev/null || exit 76` : identityGuardScript(
8245
- state,
8246
- `command kill -${signal} "-$__opengeni_pgid" 2>/dev/null || exit 76
8247
- exit 0`,
8248
- 76
8249
- );
8250
- try {
8251
- await state.execInvoke(state.runContext, shellHelperInput(script), void 0);
8252
- } catch {
8253
- }
8254
- }
8255
- };
8256
- function createTurnToolCancellationController(signal) {
8257
- return new TurnToolCancellationControllerImpl(signal);
8258
- }
8259
- function wrapCapabilityToolsForTurnCancellation(capability, controller) {
8260
- const originalTools = capability.tools;
8261
- capability.tools = function() {
8262
- return controller.wrapTools(originalTools.call(this), this._session);
8263
- };
8264
- }
8265
-
8266
7627
  // src/sandbox-computer.ts
8267
7628
  import {
8268
7629
  KeyAction,
@@ -10320,6 +9681,7 @@ async function connectMcpServersInBatches(servers, options) {
10320
9681
  await connectMcpServers(
10321
9682
  servers.slice(offset, offset + MCP_MAX_CONCURRENT_SERVER_OPERATIONS),
10322
9683
  {
9684
+ ...options.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs },
10323
9685
  connectInParallel: true,
10324
9686
  strict: options.strict
10325
9687
  }
@@ -10408,19 +9770,30 @@ async function prepareAgentTools(settings, tools, options = {}) {
10408
9770
  return {
10409
9771
  server,
10410
9772
  bestEffort,
10411
- optional
9773
+ optional,
9774
+ timeoutMs: config.timeoutMs
10412
9775
  };
10413
9776
  }
10414
9777
  );
10415
- const requiredServers = servers.filter((entry) => !entry.bestEffort).map((entry) => entry.server);
10416
- const bestEffortServers = servers.filter((entry) => entry.bestEffort).map((entry) => entry.server);
9778
+ const requiredEntries = servers.filter((entry) => !entry.bestEffort);
9779
+ const bestEffortEntries = servers.filter((entry) => entry.bestEffort);
9780
+ const requiredServers = requiredEntries.map((entry) => entry.server);
9781
+ const bestEffortServers = bestEffortEntries.map((entry) => entry.server);
10417
9782
  const optionalServerNames = new Set(
10418
9783
  servers.filter((entry) => entry.optional).map((entry) => entry.server.name)
10419
9784
  );
10420
- const connectedRequired = await connectMcpServersInBatches(requiredServers, { strict: true });
9785
+ const connectedRequired = await connectMcpServersInBatches(requiredServers, {
9786
+ strict: true,
9787
+ connectTimeoutMs: mcpOuterConnectTimeoutMs(requiredEntries.map((entry) => entry.timeoutMs))
9788
+ });
10421
9789
  let connectedBestEffort = null;
10422
9790
  try {
10423
- connectedBestEffort = bestEffortServers.length ? await connectMcpServersInBatches(bestEffortServers, { strict: false }) : null;
9791
+ connectedBestEffort = bestEffortServers.length ? await connectMcpServersInBatches(bestEffortServers, {
9792
+ strict: false,
9793
+ connectTimeoutMs: mcpOuterConnectTimeoutMs(
9794
+ bestEffortEntries.map((entry) => entry.timeoutMs)
9795
+ )
9796
+ }) : null;
10424
9797
  } catch (error) {
10425
9798
  await connectedRequired.close().catch(() => void 0);
10426
9799
  throw error;
@@ -13810,6 +13183,9 @@ export {
13810
13183
  REPOSITORY_DISCOVERY_LIMIT,
13811
13184
  RecordingError,
13812
13185
  RecordingUnavailableError,
13186
+ RoutingBackendRecoveryRequiredError,
13187
+ RoutingMutationOutcomeUnknownError,
13188
+ RoutingRetainedProcessNotFoundError,
13813
13189
  RoutingSandboxSession,
13814
13190
  RoutingUnsupportedError,
13815
13191
  RunCredentialValidationError,
@@ -13832,7 +13208,9 @@ export {
13832
13208
  SandboxChannelAService,
13833
13209
  SandboxComputer,
13834
13210
  SandboxConfigError,
13211
+ SandboxExecReadinessError,
13835
13212
  SandboxProviderUnavailableError,
13213
+ SandboxReplacementProviderStateError,
13836
13214
  SandboxResumeStateUnavailableError,
13837
13215
  SelfhostedControlError,
13838
13216
  SelfhostedSandboxClient,
@@ -13847,6 +13225,8 @@ export {
13847
13225
  ToolspaceTokenPathError,
13848
13226
  TurnSandboxCommandCancelledError,
13849
13227
  USER_MESSAGE_TRUNCATION_MARKER,
13228
+ WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION,
13229
+ WorkspaceArchiveIntegrityError,
13850
13230
  WorkspaceModelPolicyBlockedError,
13851
13231
  agentErrorToControlError,
13852
13232
  agentsErrorRunState,
@@ -13878,7 +13258,9 @@ export {
13878
13258
  buildSummaryItem,
13879
13259
  buildTerminalServerScript,
13880
13260
  callModelInputFilterForSettings,
13261
+ captureVerifiedWorkspaceArchive,
13881
13262
  clampCompactionThresholdRatio,
13263
+ classifyProviderSandboxFailure,
13882
13264
  clearRunCredentials,
13883
13265
  clearRunCredentialsForAttempt,
13884
13266
  collectSandboxEnvironment,
@@ -13925,16 +13307,20 @@ export {
13925
13307
  extForCodec,
13926
13308
  extractResponseOutputText,
13927
13309
  findCompactionNeededError,
13310
+ fingerprintSandboxWorkspace,
13928
13311
  getSkillLibraryEntry,
13929
13312
  gitCredentialBindingHash,
13930
13313
  gitCredentialBindingTokenRefreshCommand,
13931
13314
  gitProviderTokenRefreshCommand,
13932
13315
  hasModelGeneratedItem,
13316
+ hasPersistableSandboxProviderIdentity,
13933
13317
  inspectRecordingArtifact,
13934
13318
  isCompactionSummary,
13935
13319
  isEphemeralInternalContext,
13936
13320
  isExecSessionLostBanner,
13321
+ isProviderSandboxGoneDuringRoutedOperation,
13937
13322
  isProviderSandboxNotFoundError,
13323
+ isProviderSandboxTransientError,
13938
13324
  isSelfhostedProviderNotFoundError,
13939
13325
  isSkillLibraryEntryId,
13940
13326
  isUserMessage,
@@ -13975,12 +13361,14 @@ export {
13975
13361
  parseNumstatZ,
13976
13362
  parsePorcelainV2,
13977
13363
  parseUnifiedPatch,
13364
+ parseWorkspaceArchiveDescriptor,
13978
13365
  payloadTooLargeMessage,
13979
13366
  pinProvidedSessionManifestEnvironment,
13980
13367
  prefixedMcpToolName,
13981
13368
  prepareAgentTools,
13982
13369
  prepareCompactionPromptInput,
13983
13370
  prepareRunInput,
13371
+ readVerifiedWorkspaceArchive,
13984
13372
  readWorkspaceArchiveFromEnvelopeSessionState,
13985
13373
  recordingStorageKey,
13986
13374
  refreshGitCredentialBindingTokenFiles,
@@ -13990,6 +13378,7 @@ export {
13990
13378
  renderSelfhostedFault,
13991
13379
  repositoryCloneCommand,
13992
13380
  repositoryUsesSandboxClone,
13381
+ requirePersistableReplacementSandboxEnvelope,
13993
13382
  resolveTurnModel,
13994
13383
  restoredSandboxSessionStateFromEntry,
13995
13384
  rigInstructions,
@@ -14021,6 +13410,7 @@ export {
14021
13410
  serializeApprovals,
14022
13411
  serializeEstablishedSandboxEnvelope,
14023
13412
  serializeHumanInputRequests,
13413
+ serializeReplacementSandboxEnvelope,
14024
13414
  setSelfhostedApplyDiff,
14025
13415
  startRecording,
14026
13416
  stopRecording,
@@ -14042,6 +13432,8 @@ export {
14042
13432
  toolspaceTokenFileFromEnvironment,
14043
13433
  toolspaceTokenSeedCommand,
14044
13434
  uploadRecordingArtifact,
13435
+ verifyRestoredWorkspace,
13436
+ verifySandboxExecReadiness,
14045
13437
  verifyStreamToken,
14046
13438
  withManifestRefreshOnResume,
14047
13439
  withRunCredentialEnvironment,