sandbox-agent 0.3.2 → 0.4.0-rc.2

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.
@@ -0,0 +1,15 @@
1
+ // src/providers/shared.ts
2
+ var DEFAULT_SANDBOX_AGENT_IMAGE = "rivetdev/sandbox-agent:0.4.0-rc.2-full";
3
+ var SANDBOX_AGENT_INSTALL_SCRIPT = "https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh";
4
+ var DEFAULT_AGENTS = ["claude", "codex"];
5
+ function buildServerStartCommand(port) {
6
+ return `nohup sandbox-agent server --no-token --host 0.0.0.0 --port ${port} >/tmp/sandbox-agent.log 2>&1 &`;
7
+ }
8
+
9
+ export {
10
+ DEFAULT_SANDBOX_AGENT_IMAGE,
11
+ SANDBOX_AGENT_INSTALL_SCRIPT,
12
+ DEFAULT_AGENTS,
13
+ buildServerStartCommand
14
+ };
15
+ //# sourceMappingURL=chunk-LSO36KRX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/providers/shared.ts"],"sourcesContent":["export const DEFAULT_SANDBOX_AGENT_IMAGE = \"rivetdev/sandbox-agent:0.4.0-rc.2-full\";\nexport const SANDBOX_AGENT_INSTALL_SCRIPT = \"https://releases.rivet.dev/sandbox-agent/0.3.x/install.sh\";\nexport const DEFAULT_AGENTS = [\"claude\", \"codex\"] as const;\n\nexport function buildServerStartCommand(port: number): string {\n return `nohup sandbox-agent server --no-token --host 0.0.0.0 --port ${port} >/tmp/sandbox-agent.log 2>&1 &`;\n}\n"],"mappings":";AAAO,IAAM,8BAA8B;AACpC,IAAM,+BAA+B;AACrC,IAAM,iBAAiB,CAAC,UAAU,OAAO;AAEzC,SAAS,wBAAwB,MAAsB;AAC5D,SAAO,+DAA+D,IAAI;AAC5E;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,17 +1,7 @@
1
1
  import { NewSessionRequest, SessionConfigOption, SessionModeState, AnyMessage, AcpEnvelopeDirection, RequestPermissionRequest, RequestPermissionResponse, NewSessionResponse, PermissionOptionKind, PromptRequest, PromptResponse, SetSessionModeResponse, SetSessionConfigOptionResponse } from 'acp-http-client';
2
2
  export { AcpRpcError } from 'acp-http-client';
3
-
4
- type SandboxAgentSpawnLogMode = "inherit" | "pipe" | "silent";
5
- type SandboxAgentSpawnOptions = {
6
- enabled?: boolean;
7
- host?: string;
8
- port?: number;
9
- token?: string;
10
- binaryPath?: string;
11
- timeoutMs?: number;
12
- log?: SandboxAgentSpawnLogMode;
13
- env?: Record<string, string>;
14
- };
3
+ import { S as SandboxProvider } from './types-DLlJOfyX.js';
4
+ export { S as SandboxAgentSpawnLogMode, a as SandboxAgentSpawnOptions } from './spawn-76JDF5d3.js';
15
5
 
16
6
  interface components {
17
7
  schemas: {
@@ -1368,6 +1358,7 @@ interface SessionRecord {
1368
1358
  lastConnectionId: string;
1369
1359
  createdAt: number;
1370
1360
  destroyedAt?: number;
1361
+ sandboxId?: string;
1371
1362
  sessionInit?: Omit<NewSessionRequest, "_meta">;
1372
1363
  configOptions?: SessionConfigOption[];
1373
1364
  modes?: SessionModeState | null;
@@ -1394,11 +1385,11 @@ interface ListEventsRequest extends ListPageRequest {
1394
1385
  sessionId: string;
1395
1386
  }
1396
1387
  interface SessionPersistDriver {
1397
- getSession(id: string): Promise<SessionRecord | null>;
1388
+ getSession(id: string): Promise<SessionRecord | undefined>;
1398
1389
  listSessions(request?: ListPageRequest): Promise<ListPage<SessionRecord>>;
1399
1390
  updateSession(session: SessionRecord): Promise<void>;
1400
1391
  listEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>>;
1401
- insertEvent(event: SessionEvent): Promise<void>;
1392
+ insertEvent(sessionId: string, event: SessionEvent): Promise<void>;
1402
1393
  }
1403
1394
  interface InMemorySessionPersistDriverOptions {
1404
1395
  maxSessions?: number;
@@ -1410,11 +1401,11 @@ declare class InMemorySessionPersistDriver implements SessionPersistDriver {
1410
1401
  private readonly sessions;
1411
1402
  private readonly eventsBySession;
1412
1403
  constructor(options?: InMemorySessionPersistDriverOptions);
1413
- getSession(id: string): Promise<SessionRecord | null>;
1404
+ getSession(id: string): Promise<SessionRecord | undefined>;
1414
1405
  listSessions(request?: ListPageRequest): Promise<ListPage<SessionRecord>>;
1415
1406
  updateSession(session: SessionRecord): Promise<void>;
1416
1407
  listEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>>;
1417
- insertEvent(event: SessionEvent): Promise<void>;
1408
+ insertEvent(sessionId: string, event: SessionEvent): Promise<void>;
1418
1409
  }
1419
1410
  type ResponsesOf<T> = T extends {
1420
1411
  responses: infer R;
@@ -1451,6 +1442,8 @@ interface SandboxAgentConnectCommonOptions {
1451
1442
  replayMaxChars?: number;
1452
1443
  signal?: AbortSignal;
1453
1444
  token?: string;
1445
+ skipHealthCheck?: boolean;
1446
+ /** @deprecated Use skipHealthCheck instead. */
1454
1447
  waitForHealth?: boolean | SandboxAgentHealthWaitOptions;
1455
1448
  }
1456
1449
  type SandboxAgentConnectOptions = (SandboxAgentConnectCommonOptions & {
@@ -1461,16 +1454,23 @@ type SandboxAgentConnectOptions = (SandboxAgentConnectCommonOptions & {
1461
1454
  baseUrl?: string;
1462
1455
  });
1463
1456
  interface SandboxAgentStartOptions {
1457
+ sandbox: SandboxProvider;
1458
+ sandboxId?: string;
1459
+ skipHealthCheck?: boolean;
1464
1460
  fetch?: typeof fetch;
1465
1461
  headers?: HeadersInit;
1466
1462
  persist?: SessionPersistDriver;
1467
1463
  replayMaxEvents?: number;
1468
1464
  replayMaxChars?: number;
1469
- spawn?: SandboxAgentSpawnOptions | boolean;
1465
+ signal?: AbortSignal;
1466
+ token?: string;
1470
1467
  }
1471
1468
  interface SessionCreateRequest {
1472
1469
  id?: string;
1473
1470
  agent: string;
1471
+ /** Shorthand for `sessionInit.cwd`. Ignored when `sessionInit` is provided. */
1472
+ cwd?: string;
1473
+ /** Full session init. When omitted, built from `cwd` (or default) with empty `mcpServers`. */
1474
1474
  sessionInit?: Omit<NewSessionRequest, "_meta">;
1475
1475
  model?: string;
1476
1476
  mode?: string;
@@ -1479,6 +1479,9 @@ interface SessionCreateRequest {
1479
1479
  interface SessionResumeOrCreateRequest {
1480
1480
  id: string;
1481
1481
  agent: string;
1482
+ /** Shorthand for `sessionInit.cwd`. Ignored when `sessionInit` is provided. */
1483
+ cwd?: string;
1484
+ /** Full session init. When omitted, built from `cwd` (or default) with empty `mcpServers`. */
1482
1485
  sessionInit?: Omit<NewSessionRequest, "_meta">;
1483
1486
  model?: string;
1484
1487
  mode?: string;
@@ -1647,10 +1650,12 @@ declare class SandboxAgent {
1647
1650
  private readonly defaultHeaders?;
1648
1651
  private readonly healthWait;
1649
1652
  private readonly healthWaitAbortController;
1653
+ private sandboxProvider?;
1654
+ private sandboxProviderId?;
1655
+ private sandboxProviderRawId?;
1650
1656
  private readonly persist;
1651
1657
  private readonly replayMaxEvents;
1652
1658
  private readonly replayMaxChars;
1653
- private spawnHandle?;
1654
1659
  private healthPromise?;
1655
1660
  private healthError?;
1656
1661
  private disposed;
@@ -1662,10 +1667,15 @@ declare class SandboxAgent {
1662
1667
  private readonly pendingPermissionRequests;
1663
1668
  private readonly nextSessionEventIndexBySession;
1664
1669
  private readonly seedSessionEventIndexBySession;
1670
+ private readonly pendingObservedEnvelopePersistenceBySession;
1665
1671
  constructor(options: SandboxAgentConnectOptions);
1666
1672
  static connect(options: SandboxAgentConnectOptions): Promise<SandboxAgent>;
1667
- static start(options?: SandboxAgentStartOptions): Promise<SandboxAgent>;
1673
+ static start(options: SandboxAgentStartOptions): Promise<SandboxAgent>;
1674
+ get sandboxId(): string | undefined;
1675
+ get sandbox(): SandboxProvider | undefined;
1676
+ get inspectorUrl(): string;
1668
1677
  dispose(): Promise<void>;
1678
+ destroySandbox(): Promise<void>;
1669
1679
  listSessions(request?: ListPageRequest): Promise<ListPage<Session>>;
1670
1680
  getSession(id: string): Promise<Session | null>;
1671
1681
  getEvents(request: ListEventsRequest): Promise<ListPage<SessionEvent>>;
@@ -1740,6 +1750,7 @@ declare class SandboxAgent {
1740
1750
  connectProcessTerminal(id: string, options?: ProcessTerminalSessionOptions): ProcessTerminalSession;
1741
1751
  private getLiveConnection;
1742
1752
  private persistObservedEnvelope;
1753
+ private enqueueObservedEnvelopePersistence;
1743
1754
  private persistSessionStateFromEvent;
1744
1755
  private allocateSessionEventIndex;
1745
1756
  private ensureSessionEventIndexSeeded;
@@ -1781,4 +1792,4 @@ interface InspectorUrlOptions {
1781
1792
  */
1782
1793
  declare function buildInspectorUrl(options: InspectorUrlOptions): string;
1783
1794
 
1784
- export { type AcpEnvelope, type AcpServerInfo, type AcpServerListResponse, type AgentInfo, type AgentInstallRequest, type AgentInstallResponse, type AgentListResponse, type AgentQuery, type AgentQueryOptions, type FsActionResponse, type FsDeleteQuery, type FsEntriesQuery, type FsEntry, type FsMoveRequest, type FsMoveResponse, type FsPathQuery, type FsStat, type FsUploadBatchQuery, type FsUploadBatchResponse, type FsWriteResponse, type HealthResponse, InMemorySessionPersistDriver, type InMemorySessionPersistDriverOptions, type InspectorUrlOptions, type ListEventsRequest, type ListPage, type ListPageRequest, LiveAcpConnection, type McpConfigQuery, type McpServerConfig, type PermissionReply, type PermissionRequestListener, type ProblemDetails, type ProcessConfig, type ProcessCreateRequest, type ProcessInfo, type ProcessInputRequest, type ProcessInputResponse, type ProcessListResponse, type ProcessLogEntry, type ProcessLogFollowQuery, type ProcessLogListener, type ProcessLogSubscription, type ProcessLogsQuery, type ProcessLogsResponse, type ProcessLogsStream, type ProcessRunRequest, type ProcessRunResponse, type ProcessSignalQuery, type ProcessState, type ProcessTerminalClientFrame, type ProcessTerminalConnectOptions, type ProcessTerminalErrorFrame, type ProcessTerminalExitFrame, type ProcessTerminalReadyFrame, type ProcessTerminalResizeRequest, type ProcessTerminalResizeResponse, type ProcessTerminalServerFrame, ProcessTerminalSession, type ProcessTerminalSessionOptions, type ProcessTerminalWebSocketUrlOptions, SandboxAgent, type SandboxAgentConnectOptions, SandboxAgentError, type SandboxAgentHealthWaitOptions, type SandboxAgentSpawnLogMode, type SandboxAgentSpawnOptions, type SandboxAgentStartOptions, Session, type SessionCreateRequest, type SessionEvent, type SessionEventListener, type SessionPermissionRequest, type SessionPermissionRequestOption, type SessionPersistDriver, type SessionRecord, type SessionResumeOrCreateRequest, type SessionSendOptions, type SkillsConfig, type SkillsConfigQuery, type TerminalErrorStatus, type TerminalExitStatus, type TerminalReadyStatus, type TerminalResizePayload, type TerminalStatusMessage, UnsupportedPermissionReplyError, UnsupportedSessionCategoryError, UnsupportedSessionConfigOptionError, UnsupportedSessionValueError, buildInspectorUrl };
1795
+ export { type AcpEnvelope, type AcpServerInfo, type AcpServerListResponse, type AgentInfo, type AgentInstallRequest, type AgentInstallResponse, type AgentListResponse, type AgentQuery, type AgentQueryOptions, type FsActionResponse, type FsDeleteQuery, type FsEntriesQuery, type FsEntry, type FsMoveRequest, type FsMoveResponse, type FsPathQuery, type FsStat, type FsUploadBatchQuery, type FsUploadBatchResponse, type FsWriteResponse, type HealthResponse, InMemorySessionPersistDriver, type InMemorySessionPersistDriverOptions, type InspectorUrlOptions, type ListEventsRequest, type ListPage, type ListPageRequest, LiveAcpConnection, type McpConfigQuery, type McpServerConfig, type PermissionReply, type PermissionRequestListener, type ProblemDetails, type ProcessConfig, type ProcessCreateRequest, type ProcessInfo, type ProcessInputRequest, type ProcessInputResponse, type ProcessListResponse, type ProcessLogEntry, type ProcessLogFollowQuery, type ProcessLogListener, type ProcessLogSubscription, type ProcessLogsQuery, type ProcessLogsResponse, type ProcessLogsStream, type ProcessRunRequest, type ProcessRunResponse, type ProcessSignalQuery, type ProcessState, type ProcessTerminalClientFrame, type ProcessTerminalConnectOptions, type ProcessTerminalErrorFrame, type ProcessTerminalExitFrame, type ProcessTerminalReadyFrame, type ProcessTerminalResizeRequest, type ProcessTerminalResizeResponse, type ProcessTerminalServerFrame, ProcessTerminalSession, type ProcessTerminalSessionOptions, type ProcessTerminalWebSocketUrlOptions, SandboxAgent, type SandboxAgentConnectOptions, SandboxAgentError, type SandboxAgentHealthWaitOptions, type SandboxAgentStartOptions, SandboxProvider, Session, type SessionCreateRequest, type SessionEvent, type SessionEventListener, type SessionPermissionRequest, type SessionPermissionRequestOption, type SessionPersistDriver, type SessionRecord, type SessionResumeOrCreateRequest, type SessionSendOptions, type SkillsConfig, type SkillsConfigQuery, type TerminalErrorStatus, type TerminalExitStatus, type TerminalReadyStatus, type TerminalResizePayload, type TerminalStatusMessage, UnsupportedPermissionReplyError, UnsupportedSessionCategoryError, UnsupportedSessionConfigOptionError, UnsupportedSessionValueError, buildInspectorUrl };
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ var InMemorySessionPersistDriver = class {
20
20
  }
21
21
  async getSession(id) {
22
22
  const session = this.sessions.get(id);
23
- return session ? cloneSessionRecord(session) : null;
23
+ return session ? cloneSessionRecord(session) : void 0;
24
24
  }
25
25
  async listSessions(request = {}) {
26
26
  const sorted = [...this.sessions.values()].sort((a, b) => {
@@ -68,13 +68,13 @@ var InMemorySessionPersistDriver = class {
68
68
  nextCursor: page.nextCursor
69
69
  };
70
70
  }
71
- async insertEvent(event) {
72
- const events = this.eventsBySession.get(event.sessionId) ?? [];
71
+ async insertEvent(sessionId, event) {
72
+ const events = this.eventsBySession.get(sessionId) ?? [];
73
73
  events.push(cloneSessionEvent(event));
74
74
  if (events.length > this.maxEventsPerSession) {
75
75
  events.splice(0, events.length - this.maxEventsPerSession);
76
76
  }
77
- this.eventsBySession.set(event.sessionId, events);
77
+ this.eventsBySession.set(sessionId, events);
78
78
  }
79
79
  };
80
80
  function cloneSessionRecord(session) {
@@ -125,12 +125,14 @@ var DEFAULT_BASE_URL = "http://sandbox-agent";
125
125
  var DEFAULT_REPLAY_MAX_EVENTS = 50;
126
126
  var DEFAULT_REPLAY_MAX_CHARS = 12e3;
127
127
  var EVENT_INDEX_SCAN_EVENTS_LIMIT = 500;
128
+ var MAX_EVENT_INDEX_INSERT_RETRIES = 3;
128
129
  var SESSION_CANCEL_METHOD = "session/cancel";
129
130
  var MANUAL_CANCEL_ERROR = "Manual session/cancel calls are not allowed. Use destroySession(sessionId) instead.";
130
131
  var HEALTH_WAIT_MIN_DELAY_MS = 500;
131
132
  var HEALTH_WAIT_MAX_DELAY_MS = 15e3;
132
133
  var HEALTH_WAIT_LOG_AFTER_MS = 5e3;
133
134
  var HEALTH_WAIT_LOG_EVERY_MS = 1e4;
135
+ var HEALTH_WAIT_ENSURE_SERVER_AFTER_FAILURES = 3;
134
136
  var SandboxAgentError = class extends Error {
135
137
  status;
136
138
  problem;
@@ -638,10 +640,12 @@ var SandboxAgent = class _SandboxAgent {
638
640
  defaultHeaders;
639
641
  healthWait;
640
642
  healthWaitAbortController = new AbortController();
643
+ sandboxProvider;
644
+ sandboxProviderId;
645
+ sandboxProviderRawId;
641
646
  persist;
642
647
  replayMaxEvents;
643
648
  replayMaxChars;
644
- spawnHandle;
645
649
  healthPromise;
646
650
  healthError;
647
651
  disposed = false;
@@ -653,6 +657,7 @@ var SandboxAgent = class _SandboxAgent {
653
657
  pendingPermissionRequests = /* @__PURE__ */ new Map();
654
658
  nextSessionEventIndexBySession = /* @__PURE__ */ new Map();
655
659
  seedSessionEventIndexBySession = /* @__PURE__ */ new Map();
660
+ pendingObservedEnvelopePersistenceBySession = /* @__PURE__ */ new Map();
656
661
  constructor(options) {
657
662
  const baseUrl = options.baseUrl?.trim();
658
663
  if (!baseUrl && !options.fetch) {
@@ -666,7 +671,7 @@ var SandboxAgent = class _SandboxAgent {
666
671
  }
667
672
  this.fetcher = resolvedFetch;
668
673
  this.defaultHeaders = options.headers;
669
- this.healthWait = normalizeHealthWaitOptions(options.waitForHealth, options.signal);
674
+ this.healthWait = normalizeHealthWaitOptions(options.skipHealthCheck, options.waitForHealth, options.signal);
670
675
  this.persist = options.persist ?? new InMemorySessionPersistDriver();
671
676
  this.replayMaxEvents = normalizePositiveInt(options.replayMaxEvents, DEFAULT_REPLAY_MAX_EVENTS);
672
677
  this.replayMaxChars = normalizePositiveInt(options.replayMaxChars, DEFAULT_REPLAY_MAX_CHARS);
@@ -675,26 +680,66 @@ var SandboxAgent = class _SandboxAgent {
675
680
  static async connect(options) {
676
681
  return new _SandboxAgent(options);
677
682
  }
678
- static async start(options = {}) {
679
- const spawnOptions = normalizeSpawnOptions(options.spawn, true);
680
- if (!spawnOptions.enabled) {
681
- throw new Error("SandboxAgent.start requires spawn to be enabled.");
683
+ static async start(options) {
684
+ const provider = options.sandbox;
685
+ if (!provider.getUrl && !provider.getFetch) {
686
+ throw new Error(`Sandbox provider '${provider.name}' must implement getUrl() or getFetch().`);
682
687
  }
683
- const { spawnSandboxAgent } = await import("./spawn-ROM6CN74.js");
684
- const resolvedFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);
685
- const handle = await spawnSandboxAgent(spawnOptions, resolvedFetch);
686
- const client = new _SandboxAgent({
687
- baseUrl: handle.baseUrl,
688
- token: handle.token,
689
- fetch: options.fetch,
690
- headers: options.headers,
691
- waitForHealth: false,
692
- persist: options.persist,
693
- replayMaxEvents: options.replayMaxEvents,
694
- replayMaxChars: options.replayMaxChars
695
- });
696
- client.spawnHandle = handle;
697
- return client;
688
+ const existingSandbox = options.sandboxId ? parseSandboxProviderId(options.sandboxId) : null;
689
+ if (existingSandbox && existingSandbox.provider !== provider.name) {
690
+ throw new Error(
691
+ `SandboxAgent.start received sandboxId '${options.sandboxId}' for provider '${existingSandbox.provider}', but the configured provider is '${provider.name}'.`
692
+ );
693
+ }
694
+ const rawSandboxId = existingSandbox?.rawId ?? await provider.create();
695
+ const prefixedSandboxId = `${provider.name}/${rawSandboxId}`;
696
+ const createdSandbox = !existingSandbox;
697
+ if (existingSandbox) {
698
+ await provider.ensureServer?.(rawSandboxId);
699
+ }
700
+ try {
701
+ const fetcher = await resolveProviderFetch(provider, rawSandboxId);
702
+ const baseUrl = provider.getUrl ? await provider.getUrl(rawSandboxId) : void 0;
703
+ const providerFetch = options.fetch ?? fetcher;
704
+ const commonConnectOptions = {
705
+ headers: options.headers,
706
+ persist: options.persist,
707
+ replayMaxEvents: options.replayMaxEvents,
708
+ replayMaxChars: options.replayMaxChars,
709
+ signal: options.signal,
710
+ skipHealthCheck: options.skipHealthCheck,
711
+ token: options.token ?? await resolveProviderToken(provider, rawSandboxId)
712
+ };
713
+ const client = providerFetch ? new _SandboxAgent({
714
+ ...commonConnectOptions,
715
+ baseUrl,
716
+ fetch: providerFetch
717
+ }) : new _SandboxAgent({
718
+ ...commonConnectOptions,
719
+ baseUrl: requireSandboxBaseUrl(baseUrl, provider.name)
720
+ });
721
+ client.sandboxProvider = provider;
722
+ client.sandboxProviderId = prefixedSandboxId;
723
+ client.sandboxProviderRawId = rawSandboxId;
724
+ return client;
725
+ } catch (error) {
726
+ if (createdSandbox) {
727
+ try {
728
+ await provider.destroy(rawSandboxId);
729
+ } catch {
730
+ }
731
+ }
732
+ throw error;
733
+ }
734
+ }
735
+ get sandboxId() {
736
+ return this.sandboxProviderId;
737
+ }
738
+ get sandbox() {
739
+ return this.sandboxProvider;
740
+ }
741
+ get inspectorUrl() {
742
+ return `${this.baseUrl.replace(/\/+$/, "")}/ui/`;
698
743
  }
699
744
  async dispose() {
700
745
  this.disposed = true;
@@ -707,6 +752,7 @@ var SandboxAgent = class _SandboxAgent {
707
752
  this.liveConnections.clear();
708
753
  const pending = [...this.pendingLiveConnections.values()];
709
754
  this.pendingLiveConnections.clear();
755
+ this.pendingObservedEnvelopePersistenceBySession.clear();
710
756
  const pendingSettled = await Promise.allSettled(pending);
711
757
  for (const item of pendingSettled) {
712
758
  if (item.status === "fulfilled") {
@@ -718,9 +764,21 @@ var SandboxAgent = class _SandboxAgent {
718
764
  await connection.close();
719
765
  })
720
766
  );
721
- if (this.spawnHandle) {
722
- await this.spawnHandle.dispose();
723
- this.spawnHandle = void 0;
767
+ }
768
+ async destroySandbox() {
769
+ const provider = this.sandboxProvider;
770
+ const rawSandboxId = this.sandboxProviderRawId;
771
+ try {
772
+ if (provider && rawSandboxId) {
773
+ await provider.destroy(rawSandboxId);
774
+ } else if (!provider || !rawSandboxId) {
775
+ throw new Error("SandboxAgent is not attached to a provisioned sandbox.");
776
+ }
777
+ } finally {
778
+ await this.dispose();
779
+ this.sandboxProvider = void 0;
780
+ this.sandboxProviderId = void 0;
781
+ this.sandboxProviderRawId = void 0;
724
782
  }
725
783
  }
726
784
  async listSessions(request = {}) {
@@ -746,7 +804,7 @@ var SandboxAgent = class _SandboxAgent {
746
804
  }
747
805
  const localSessionId = request.id?.trim() || randomId();
748
806
  const live = await this.getLiveConnection(request.agent.trim());
749
- const sessionInit = normalizeSessionInit(request.sessionInit);
807
+ const sessionInit = normalizeSessionInit(request.sessionInit, request.cwd);
750
808
  const response = await live.createRemoteSession(localSessionId, sessionInit);
751
809
  const record = {
752
810
  id: localSessionId,
@@ -754,12 +812,12 @@ var SandboxAgent = class _SandboxAgent {
754
812
  agentSessionId: response.sessionId,
755
813
  lastConnectionId: live.connectionId,
756
814
  createdAt: nowMs(),
815
+ sandboxId: this.sandboxProviderId,
757
816
  sessionInit,
758
817
  configOptions: cloneConfigOptions(response.configOptions),
759
818
  modes: cloneModes(response.modes)
760
819
  };
761
820
  await this.persist.updateSession(record);
762
- this.nextSessionEventIndexBySession.set(record.id, 1);
763
821
  live.bindSession(record.id, record.agentSessionId);
764
822
  let session = this.upsertSessionHandle(record);
765
823
  try {
@@ -1299,7 +1357,9 @@ var SandboxAgent = class _SandboxAgent {
1299
1357
  agent,
1300
1358
  serverId,
1301
1359
  onObservedEnvelope: (connection, envelope, direction, localSessionId) => {
1302
- void this.persistObservedEnvelope(connection, envelope, direction, localSessionId);
1360
+ void this.enqueueObservedEnvelopePersistence(connection, envelope, direction, localSessionId).catch((error) => {
1361
+ console.error("Failed to persist observed sandbox-agent envelope", error);
1362
+ });
1303
1363
  },
1304
1364
  onPermissionRequest: async (connection, localSessionId, agentSessionId, request) => this.enqueuePermissionRequest(connection, localSessionId, agentSessionId, request)
1305
1365
  });
@@ -1324,16 +1384,29 @@ var SandboxAgent = class _SandboxAgent {
1324
1384
  if (!localSessionId) {
1325
1385
  return;
1326
1386
  }
1327
- const event = {
1328
- id: randomId(),
1329
- eventIndex: await this.allocateSessionEventIndex(localSessionId),
1330
- sessionId: localSessionId,
1331
- createdAt: nowMs(),
1332
- connectionId: connection.connectionId,
1333
- sender: direction === "outbound" ? "client" : "agent",
1334
- payload: cloneEnvelope(envelope)
1335
- };
1336
- await this.persist.insertEvent(event);
1387
+ let event = null;
1388
+ for (let attempt = 0; attempt < MAX_EVENT_INDEX_INSERT_RETRIES; attempt += 1) {
1389
+ event = {
1390
+ id: randomId(),
1391
+ eventIndex: await this.allocateSessionEventIndex(localSessionId),
1392
+ sessionId: localSessionId,
1393
+ createdAt: nowMs(),
1394
+ connectionId: connection.connectionId,
1395
+ sender: direction === "outbound" ? "client" : "agent",
1396
+ payload: cloneEnvelope(envelope)
1397
+ };
1398
+ try {
1399
+ await this.persist.insertEvent(localSessionId, event);
1400
+ break;
1401
+ } catch (error) {
1402
+ if (!isSessionEventIndexConflict(error) || attempt === MAX_EVENT_INDEX_INSERT_RETRIES - 1) {
1403
+ throw error;
1404
+ }
1405
+ }
1406
+ }
1407
+ if (!event) {
1408
+ return;
1409
+ }
1337
1410
  await this.persistSessionStateFromEvent(localSessionId, envelope, direction);
1338
1411
  const listeners = this.eventListeners.get(localSessionId);
1339
1412
  if (!listeners || listeners.size === 0) {
@@ -1343,6 +1416,22 @@ var SandboxAgent = class _SandboxAgent {
1343
1416
  listener(event);
1344
1417
  }
1345
1418
  }
1419
+ async enqueueObservedEnvelopePersistence(connection, envelope, direction, localSessionId) {
1420
+ if (!localSessionId) {
1421
+ return;
1422
+ }
1423
+ const previous = this.pendingObservedEnvelopePersistenceBySession.get(localSessionId) ?? Promise.resolve();
1424
+ const current = previous.catch(() => {
1425
+ }).then(() => this.persistObservedEnvelope(connection, envelope, direction, localSessionId));
1426
+ this.pendingObservedEnvelopePersistenceBySession.set(localSessionId, current);
1427
+ try {
1428
+ await current;
1429
+ } finally {
1430
+ if (this.pendingObservedEnvelopePersistenceBySession.get(localSessionId) === current) {
1431
+ this.pendingObservedEnvelopePersistenceBySession.delete(localSessionId);
1432
+ }
1433
+ }
1434
+ }
1346
1435
  async persistSessionStateFromEvent(sessionId, envelope, direction) {
1347
1436
  if (direction !== "inbound") {
1348
1437
  return;
@@ -1586,6 +1675,7 @@ var SandboxAgent = class _SandboxAgent {
1586
1675
  let delayMs = HEALTH_WAIT_MIN_DELAY_MS;
1587
1676
  let nextLogAt = startedAt + HEALTH_WAIT_LOG_AFTER_MS;
1588
1677
  let lastError;
1678
+ let consecutiveFailures = 0;
1589
1679
  while (!this.disposed && (deadline === void 0 || Date.now() < deadline)) {
1590
1680
  throwIfAborted(signal);
1591
1681
  try {
@@ -1594,11 +1684,20 @@ var SandboxAgent = class _SandboxAgent {
1594
1684
  return;
1595
1685
  }
1596
1686
  lastError = new Error(`Unexpected health response: ${JSON.stringify(health)}`);
1687
+ consecutiveFailures++;
1597
1688
  } catch (error) {
1598
1689
  if (isAbortError(error)) {
1599
1690
  throw error;
1600
1691
  }
1601
1692
  lastError = error;
1693
+ consecutiveFailures++;
1694
+ }
1695
+ if (consecutiveFailures >= HEALTH_WAIT_ENSURE_SERVER_AFTER_FAILURES && this.sandboxProvider?.ensureServer && this.sandboxProviderRawId) {
1696
+ try {
1697
+ await this.sandboxProvider.ensureServer(this.sandboxProviderRawId);
1698
+ } catch {
1699
+ }
1700
+ consecutiveFailures = 0;
1602
1701
  }
1603
1702
  const now = Date.now();
1604
1703
  if (now >= nextLogAt) {
@@ -1644,6 +1743,12 @@ var SandboxAgent = class _SandboxAgent {
1644
1743
  });
1645
1744
  }
1646
1745
  };
1746
+ function isSessionEventIndexConflict(error) {
1747
+ if (!(error instanceof Error)) {
1748
+ return false;
1749
+ }
1750
+ return /UNIQUE constraint failed: .*session_id, .*event_index/.test(error.message);
1751
+ }
1647
1752
  function parseProcessTerminalServerFrame(payload) {
1648
1753
  try {
1649
1754
  const parsed = JSON.parse(payload);
@@ -1725,16 +1830,16 @@ function toAgentQuery(options) {
1725
1830
  no_cache: options.noCache
1726
1831
  };
1727
1832
  }
1728
- function normalizeSessionInit(value) {
1833
+ function normalizeSessionInit(value, cwdShorthand) {
1729
1834
  if (!value) {
1730
1835
  return {
1731
- cwd: defaultCwd(),
1836
+ cwd: cwdShorthand ?? defaultCwd(),
1732
1837
  mcpServers: []
1733
1838
  };
1734
1839
  }
1735
1840
  return {
1736
1841
  ...value,
1737
- cwd: value.cwd ?? defaultCwd(),
1842
+ cwd: value.cwd ?? cwdShorthand ?? defaultCwd(),
1738
1843
  mcpServers: value.mcpServers ?? []
1739
1844
  };
1740
1845
  }
@@ -1848,32 +1953,50 @@ function normalizePositiveInt(value, fallback) {
1848
1953
  }
1849
1954
  return Math.floor(value);
1850
1955
  }
1851
- function normalizeHealthWaitOptions(value, signal) {
1852
- if (value === false) {
1956
+ function normalizeHealthWaitOptions(skipHealthCheck, waitForHealth, signal) {
1957
+ if (skipHealthCheck === true || waitForHealth === false) {
1853
1958
  return { enabled: false };
1854
1959
  }
1855
- if (value === true || value === void 0) {
1960
+ if (waitForHealth === true || waitForHealth === void 0) {
1856
1961
  return { enabled: true, signal };
1857
1962
  }
1858
- const timeoutMs = typeof value.timeoutMs === "number" && Number.isFinite(value.timeoutMs) && value.timeoutMs > 0 ? Math.floor(value.timeoutMs) : void 0;
1963
+ const timeoutMs = typeof waitForHealth.timeoutMs === "number" && Number.isFinite(waitForHealth.timeoutMs) && waitForHealth.timeoutMs > 0 ? Math.floor(waitForHealth.timeoutMs) : void 0;
1859
1964
  return {
1860
1965
  enabled: true,
1861
1966
  signal,
1862
1967
  timeoutMs
1863
1968
  };
1864
1969
  }
1865
- function normalizeSpawnOptions(spawn, defaultEnabled) {
1866
- if (spawn === false) {
1867
- return { enabled: false };
1868
- }
1869
- if (spawn === true || spawn === void 0) {
1870
- return { enabled: defaultEnabled };
1970
+ function parseSandboxProviderId(sandboxId) {
1971
+ const slashIndex = sandboxId.indexOf("/");
1972
+ if (slashIndex < 1 || slashIndex === sandboxId.length - 1) {
1973
+ throw new Error(`Sandbox IDs must be prefixed as "{provider}/{id}". Received '${sandboxId}'.`);
1871
1974
  }
1872
1975
  return {
1873
- ...spawn,
1874
- enabled: spawn.enabled ?? defaultEnabled
1976
+ provider: sandboxId.slice(0, slashIndex),
1977
+ rawId: sandboxId.slice(slashIndex + 1)
1875
1978
  };
1876
1979
  }
1980
+ function requireSandboxBaseUrl(baseUrl, providerName) {
1981
+ if (!baseUrl) {
1982
+ throw new Error(`Sandbox provider '${providerName}' did not return a base URL.`);
1983
+ }
1984
+ return baseUrl;
1985
+ }
1986
+ async function resolveProviderFetch(provider, rawSandboxId) {
1987
+ if (provider.getFetch) {
1988
+ return await provider.getFetch(rawSandboxId);
1989
+ }
1990
+ return void 0;
1991
+ }
1992
+ async function resolveProviderToken(provider, rawSandboxId) {
1993
+ const maybeGetToken = provider.getToken;
1994
+ if (typeof maybeGetToken !== "function") {
1995
+ return void 0;
1996
+ }
1997
+ const token = await maybeGetToken.call(provider, rawSandboxId);
1998
+ return typeof token === "string" && token ? token : void 0;
1999
+ }
1877
2000
  async function readProblem(response) {
1878
2001
  try {
1879
2002
  const text = await response.clone().text();
@@ -1938,7 +2061,7 @@ function deriveModesFromConfigOptions(configOptions) {
1938
2061
  return null;
1939
2062
  }
1940
2063
  const modeOption = findConfigOptionByCategory(configOptions, "mode");
1941
- if (!modeOption || !Array.isArray(modeOption.options)) {
2064
+ if (!modeOption || modeOption.type !== "select" || !Array.isArray(modeOption.options)) {
1942
2065
  return null;
1943
2066
  }
1944
2067
  const availableModes = modeOption.options.flatMap((entry) => flattenConfigOptions(entry)).map((entry) => ({