@opengeni/runtime 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2PO56VAL.js +3478 -0
- package/dist/chunk-2PO56VAL.js.map +1 -0
- package/dist/index.d.ts +912 -0
- package/dist/index.js +3663 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +1738 -0
- package/dist/sandbox/index.js +187 -0
- package/dist/sandbox/index.js.map +1 -0
- package/package.json +49 -0
- package/src/bundled_hashicorp_terraform_skills/LICENSE +373 -0
- package/src/bundled_hashicorp_terraform_skills/README.md +18 -0
- package/src/bundled_hashicorp_terraform_skills/UPSTREAM_GIT_SHA +1 -0
- package/src/bundled_hashicorp_terraform_skills/azure-verified-modules/SKILL.md +613 -0
- package/src/bundled_hashicorp_terraform_skills/checkov/SKILL.md +43 -0
- package/src/bundled_hashicorp_terraform_skills/refactor-module/SKILL.md +538 -0
- package/src/bundled_hashicorp_terraform_skills/social-media-marketing/SKILL.md +35 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-search-import/SKILL.md +372 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-search-import/references/MANUAL-IMPORT.md +113 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-search-import/scripts/list_resources.sh +38 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/SKILL.md +480 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/api-monitoring.md +543 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/component-blocks.md +476 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/deployment-blocks.md +391 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/examples.md +1529 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/linked-stacks.md +187 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-stacks/references/troubleshooting.md +671 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-style-guide/SKILL.md +353 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/SKILL.md +451 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/references/CI_CD.md +80 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/references/EXAMPLES.md +314 -0
- package/src/bundled_hashicorp_terraform_skills/terraform-test/references/MOCK_PROVIDERS.md +171 -0
- package/src/codex-tool-search.ts +267 -0
- package/src/context-compaction.ts +538 -0
- package/src/history-sanitizer.ts +719 -0
- package/src/index.ts +3299 -0
- package/src/sandbox/capabilities.ts +69 -0
- package/src/sandbox/channel-a.ts +1031 -0
- package/src/sandbox/display-stack.ts +231 -0
- package/src/sandbox/errors.ts +34 -0
- package/src/sandbox/index.ts +832 -0
- package/src/sandbox/providers/blaxel.ts +35 -0
- package/src/sandbox/providers/cloudflare.ts +24 -0
- package/src/sandbox/providers/daytona.ts +34 -0
- package/src/sandbox/providers/docker.ts +17 -0
- package/src/sandbox/providers/e2b.ts +36 -0
- package/src/sandbox/providers/index.ts +107 -0
- package/src/sandbox/providers/local.ts +13 -0
- package/src/sandbox/providers/modal.ts +55 -0
- package/src/sandbox/providers/none.ts +13 -0
- package/src/sandbox/providers/runloop.ts +32 -0
- package/src/sandbox/providers/selfhosted.ts +96 -0
- package/src/sandbox/providers/types.ts +38 -0
- package/src/sandbox/providers/vercel.ts +29 -0
- package/src/sandbox/recording.ts +286 -0
- package/src/sandbox/routing/backend-resolver.ts +189 -0
- package/src/sandbox/routing/routing-session.ts +455 -0
- package/src/sandbox/select.ts +371 -0
- package/src/sandbox/selfhosted/capabilities.ts +255 -0
- package/src/sandbox/selfhosted/control-rpc.ts +351 -0
- package/src/sandbox/selfhosted/session.ts +930 -0
- package/src/sandbox/selfhosted/testing.ts +230 -0
- package/src/sandbox/stream-port.ts +185 -0
- package/src/sandbox/stream-token.ts +90 -0
- package/src/sandbox/terminal-server.ts +203 -0
- package/src/sandbox-computer.ts +835 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sandbox/index.ts","../src/sandbox/providers/index.ts","../src/sandbox/capabilities.ts","../src/sandbox/providers/blaxel.ts","../src/sandbox/errors.ts","../src/sandbox/providers/cloudflare.ts","../src/sandbox/providers/daytona.ts","../src/sandbox/providers/docker.ts","../src/sandbox/providers/e2b.ts","../src/sandbox/providers/local.ts","../src/sandbox/providers/modal.ts","../src/sandbox/providers/none.ts","../src/sandbox/providers/runloop.ts","../src/sandbox/selfhosted/control-rpc.ts","../src/sandbox/selfhosted/session.ts","../src/sandbox/providers/selfhosted.ts","../src/sandbox/providers/vercel.ts","../src/sandbox/select.ts","../src/sandbox/stream-token.ts","../src/sandbox/display-stack.ts","../src/sandbox/terminal-server.ts","../src/sandbox/stream-port.ts","../src/sandbox/recording.ts","../src/sandbox/channel-a.ts","../src/sandbox/selfhosted/capabilities.ts","../src/sandbox/selfhosted/testing.ts","../src/sandbox/routing/routing-session.ts","../src/sandbox/routing/backend-resolver.ts"],"sourcesContent":["// @opengeni/runtime/sandbox — the agent-loop-free sandbox leaf.\n//\n// This module is the load-bearing pre-req for the API-direct control plane\n// (docs/design/sandbox-surfacing). It exposes the sandbox client factory plus\n// the resume / recovery-envelope helpers that the API needs to touch a box by\n// id (resume-by-id, file/exec/port ops) WITHOUT importing the @openai/agents\n// agent-loop graph.\n//\n// IMPORT DISCIPLINE (enforced by packages/runtime/test/sandbox-leaf-no-agent-loop.test.ts):\n// - ALLOWED: the per-provider sandbox SDK build imports\n// `@openai/agents/sandbox`, `@openai/agents/sandbox/local`,\n// `@openai/agents-extensions/sandbox/modal`\n// and the workspace `@opengeni/config` / `@opengeni/contracts` packages.\n// - FORBIDDEN: the agent-loop entrypoints — the bare `@openai/agents`,\n// `@openai/agents-extensions`, or `@openai/agents-core` roots, and the\n// loop symbols (`Agent`, `run`, `Runner`, `RunState`).\n// The barrel `packages/runtime/src/index.ts` re-exports everything here via\n// `export * from \"./sandbox\"`, so existing consumers (apps/worker) are\n// unchanged.\n\nimport type { Settings } from \"@opengeni/config\";\nimport { collectSandboxEnvironment, parseExposedPorts } from \"@opengeni/config\";\nimport { DESKTOP_STREAM_PORT, TERMINAL_STREAM_PORT, type SandboxBackend } from \"@opengeni/contracts\";\nimport type {\n SandboxClient,\n SandboxSessionLike,\n SandboxSessionState,\n} from \"@openai/agents/sandbox\";\nimport { PROVIDER_REGISTRY } from \"./providers\";\nimport { SandboxConfigError } from \"./errors\";\nimport { isSelfhostedProviderNotFoundError } from \"./selfhosted/session\";\n\n// Re-export the config-owned environment/port helpers from the leaf so the\n// API-direct control plane can pull its full sandbox-construction surface from\n// a single agent-loop-free entrypoint. They physically live in @opengeni/config\n// (moving them into runtime would create a config→runtime cycle — ledger CR8).\nexport { collectSandboxEnvironment, parseExposedPorts } from \"@opengeni/config\";\n\n// The provider registry surface — the descriptor table self-test, the per-\n// provider registrations, selection + capability negotiation, and the typed\n// construction errors. All agent-loop-free, so the API-direct control plane\n// imports them from this one leaf.\nexport {\n CAPABILITY_DESCRIPTORS,\n DESKTOP_STREAM_PORT,\n assertDescriptorRegistryInvariants,\n type CapabilityDescriptor,\n} from \"./capabilities\";\nexport { SandboxConfigError, SandboxProviderUnavailableError } from \"./errors\";\nexport {\n PROVIDER_REGISTRY,\n assertProviderRegistryInvariants,\n type ProviderRegistration,\n type ProviderConstructionContext,\n} from \"./providers\";\nexport {\n selectBackend,\n backendSupportsOs,\n desktopCapableBackend,\n negotiateCapabilities,\n type NegotiationContext,\n} from \"./select\";\n\n// Scoped data-plane stream-token mint/verify (P3.1). Agent-loop-free; the API\n// pulls these from this leaf to authorize the desktop pixel plane.\nexport {\n STREAM_TOKEN_DEFAULT_TTL_SECONDS,\n mintStreamToken,\n verifyStreamToken,\n StreamTokenPayload,\n type MintStreamTokenInput,\n type StreamTokenPayloadType,\n} from \"./stream-token\";\n\n// The Channel-B desktop display-stack launcher (P4.1). Exec-launched,\n// flock-idempotent; the worker (per-turn) and the API (per viewer op) both drive\n// it from this leaf to bring up Xvfb -> XFCE -> x11vnc -viewonly -> websockify:6080.\nexport {\n STREAM_PORT,\n DISPLAY_STACK_TIMEOUT_MS,\n DEFAULT_DESKTOP_GEOMETRY,\n DisplayStackError,\n DisplayStackUnsupportedError,\n buildDisplayStackScript,\n ensureDisplayStack,\n tearDownDisplayStack,\n type DesktopGeometry,\n type EnsureDisplayStackOptions,\n type EnsureDisplayStackResult,\n} from \"./display-stack\";\n\n// The Channel-B REAL PTY terminal-server launcher (P5.t). Exec-launched,\n// flock-idempotent twin of ensureDisplayStack; brings up ttyd PTY-over-websocket\n// (bash -l per ws client) on 7681 over the SAME tunnel the desktop noVNC uses.\nexport {\n TERMINAL_STREAM_PORT,\n TERMINAL_SERVER_TIMEOUT_MS,\n TerminalServerError,\n TerminalServerUnsupportedError,\n buildTerminalServerScript,\n ensureTerminalServer,\n tearDownTerminalServer,\n type EnsureTerminalServerOptions,\n type EnsureTerminalServerResult,\n} from \"./terminal-server\";\n\n// The Channel-B pixel DATA PLANE (P4.2). Resolves the provider's scoped tunnel\n// for port 6080 (client → provider-tunnel direct), assembles the WS URL, and\n// mints the scoped stream token. Called API-direct on the resumed handle.\nexport {\n exposeStreamPort,\n buildStreamUrl,\n StreamPortUnavailableError,\n type ExposedPortEndpoint,\n type ExposeStreamPortInput,\n type ExposeStreamPortResult,\n} from \"./stream-port\";\n\n// P4.3 recording loop — plain functions over a live session handle (no agent\n// loop); finalize reads bytes + PUTs to storage in the holding process (F10).\nexport {\n startRecording,\n stopRecording,\n readRecordingBytes,\n deleteRecordingArtifacts,\n recordingStorageKey,\n contentTypeForCodec,\n extForCodec,\n RecordingUnavailableError,\n RecordingError,\n type RecordingCodec,\n type RecordingContentType,\n type StartRecordingInput,\n type RecordingProcess,\n type FinalizeRecordingResult,\n} from \"./recording\";\n\n// P4.4 Channel-A structured services — the provider-agnostic SandboxChannelAService\n// (FileSystem + Git + Terminal) over a live, resumed-by-id session handle. The\n// API constructs one per request around the box it just resumed; no ownership.\n// Agent-loop-free, so the API-direct control plane imports it from this leaf.\nexport {\n SandboxChannelAService,\n ChannelAValidationError,\n ChannelAConflictError,\n ChannelANotFoundError,\n ChannelAUnsupportedError,\n stripExecBanner,\n parseExecBannerSessionId,\n isWorkspaceEscapeError,\n isExecSessionLostBanner,\n assertSafeRelPath,\n parsePorcelainV2,\n parseNumstatZ,\n parseUnifiedPatch,\n type ChannelASession,\n type ChannelAExecArgs,\n type ChannelAExecResult,\n type ChannelAEmitter,\n type SandboxChannelAServiceOptions,\n type NumstatEntry,\n} from \"./channel-a\";\n\n// The selfhosted (bring-your-own-compute) control surface (M3). The NATS-backed\n// `SelfhostedSession` presents the SAME structural exec/fs/git surface as Modal\n// over a `ControlRpc` seam (request/reply on `agent.<ws>.<id>.rpc`, encoded via\n// `@opengeni/agent-proto`). agent-offline is NEVER a NotFound — the lease never\n// cold-creates a rival for a user's real machine. The real NATS transport +\n// Accounts land in M4 behind the SAME `ControlRpc`.\nexport {\n type ControlRpc,\n NatsControlRpc,\n SelfhostedControlError,\n agentErrorToControlError,\n subjectFor,\n offlineControlResponse,\n timeoutControlResponse,\n offlineAgentError,\n timeoutAgentError,\n type NatsRequestConnection,\n type SelfhostedUnavailableReason,\n} from \"./selfhosted/control-rpc\";\nexport {\n SelfhostedSession,\n SelfhostedSandboxClient,\n buildSelfhostedBackendSession,\n isSelfhostedProviderNotFoundError,\n setSelfhostedApplyDiff,\n SELFHOSTED_DEFAULT_TIMEOUT_MS,\n SELFHOSTED_RELAY_STREAM_PATH,\n type SelfhostedSessionState,\n type SelfhostedSessionDeps,\n type SelfhostedSessionBuild,\n type SelfhostedRelayConfig,\n type SelfhostedExecArgs,\n type SelfhostedExecResult,\n type SelfhostedApplyDiff,\n type SelfhostedEditor,\n type SelfhostedImageOutput,\n} from \"./selfhosted/session\";\nexport {\n negotiateSelfhostedCapabilities,\n selfhostedLiveness,\n SELFHOSTED_RECONNECT_WINDOW_MS,\n type SelfhostedNegotiationInput,\n type SelfhostedLivenessState,\n type SelfhostedEnrollment,\n} from \"./selfhosted/capabilities\";\nexport { MockAgentResponder, type MockAgentResponderOptions, type MockExecHandler } from \"./selfhosted/testing\";\n\n// The hot-swap routing proxy (M7): ONE stable session-shaped object the SDK binds\n// to, which re-reads the per-session active pointer per op and dispatches to the\n// currently-active backend (Modal or selfhosted) — flippable mid-turn, single\n// active at a time, fence-retrying on a swap race.\nexport {\n RoutingSandboxSession,\n RoutingUnsupportedError,\n type ActivePointer,\n type RoutableBackendSession,\n type ResolvedActiveBackend,\n type RoutingSandboxSessionDeps,\n type RoutingTransitionEvent,\n} from \"./routing/routing-session\";\nexport {\n makeActiveBackendResolver,\n ActiveBackendUnresolvableError,\n type ActiveBackendResolverDeps,\n type RoutableSandbox,\n} from \"./routing/backend-resolver\";\n\n/**\n * Construct the raw provider SandboxClient for the configured backend. Registry-\n * driven (the old flat if/else is gone): the backend's ProviderRegistration owns\n * validateCredentials + build, with per-provider units/field-names. Returns\n * undefined for \"none\".\n *\n * The desktop stream port (6080) is merged into exposedPorts for every desktop-\n * capable (backend, os) when desktop is enabled AND the provider cannot expose\n * ports on demand (modal/runloop/e2b pre-declare; blaxel resolves on demand).\n * Existing modal/docker/local construction is behavior-preserved.\n */\nexport function createSandboxClient(settings: Settings, environment = collectSandboxEnvironment(settings)): unknown {\n return createSandboxClientForBackend(settings.sandboxBackend as SandboxBackend, settings, environment);\n}\n\n/**\n * Construct the raw provider SandboxClient for an EXPLICIT backend, independent\n * of settings.sandboxBackend. This is the resume-by-id builder the per-turn\n * resume path (and the API-direct control plane) call: a lease's box was created\n * on a specific backend (the envelope's backendId / the lease's\n * resume_backend_id), and the client that reattaches to it must be built for\n * THAT backend, not the process's currently-configured default. When the backend\n * equals settings.sandboxBackend this is identical to createSandboxClient\n * (behavior-preserved). Returns undefined for \"none\".\n */\nexport function createSandboxClientForBackend(\n backend: SandboxBackend,\n settings: Settings,\n environment = collectSandboxEnvironment(settings),\n): unknown {\n const registration = PROVIDER_REGISTRY[backend];\n if (!registration) {\n throw new SandboxConfigError(backend, `Unknown sandbox backend \"${backend}\"`);\n }\n if (registration.backend === \"none\") {\n return undefined;\n }\n registration.validateCredentials(settings); // fail-fast, typed\n\n const exposedPorts = parseExposedPorts(settings.dockerExposedPorts);\n // 6080 port-merge: a desktop-capable backend that pre-declares ports (not\n // on-demand) must carry the desktop port at construction so resolveExposedPort\n // (6080) succeeds later. runloop is included (it is desktop-capable but NOT\n // on-demand → must pre-declare). blaxel is on-demand → skipped here.\n const desktop = registration.descriptor.capabilities.DesktopStream;\n if (\n desktop.available\n && settings.sandboxDesktopEnabled\n && !registration.descriptor.portExposure.supportsOnDemandPorts\n && !exposedPorts.includes(DESKTOP_STREAM_PORT)\n ) {\n exposedPorts.push(DESKTOP_STREAM_PORT);\n }\n // 7681 port-merge: the REAL PTY terminal (ttyd) rides the SAME tunnel as the\n // desktop, so a desktop-capable pre-declared-port backend must ALSO carry 7681\n // at construction for resolveExposedPort(7681) to succeed later on a fresh box.\n // Same condition as the 6080 merge (a desktop-capable image bakes ttyd too).\n if (\n desktop.available\n && settings.sandboxDesktopEnabled\n && !registration.descriptor.portExposure.supportsOnDemandPorts\n && !exposedPorts.includes(TERMINAL_STREAM_PORT)\n ) {\n exposedPorts.push(TERMINAL_STREAM_PORT);\n }\n\n const raw = registration.build({ settings, environment, exposedPorts });\n // Docker network decoration stays backend-specific (only docker).\n return registration.backend === \"docker\"\n ? withDockerNetwork(raw as SandboxClient, settings.dockerNetwork)\n : raw;\n}\n\nfunction withDockerNetwork(client: SandboxClient, network: string | undefined): SandboxClient {\n const trimmed = network?.trim();\n if (!trimmed) {\n return client;\n }\n const wrapSession = async <T extends SandboxSessionLike>(session: T): Promise<T> => {\n const containerId = (session as { state?: { containerId?: unknown } }).state?.containerId;\n if (typeof containerId === \"string\" && containerId.length > 0) {\n await connectDockerNetwork(trimmed, containerId);\n }\n return session;\n };\n return {\n backendId: client.backendId,\n ...(client.supportsDefaultOptions !== undefined ? { supportsDefaultOptions: client.supportsDefaultOptions } : {}),\n ...(client.create ? { create: async (...args: any[]) => await wrapSession(await (client.create as any)(...args)) } : {}),\n ...(client.resume ? { resume: async (state: SandboxSessionState) => await wrapSession(await client.resume!(state)) } : {}),\n ...(client.delete ? { delete: async (state: SandboxSessionState) => await client.delete!(state) } : {}),\n ...(client.serializeSessionState ? { serializeSessionState: async (state: SandboxSessionState, options) => await client.serializeSessionState!(state, options) } : {}),\n ...(client.canPersistOwnedSessionState ? { canPersistOwnedSessionState: async (state: SandboxSessionState) => await client.canPersistOwnedSessionState!(state) } : {}),\n ...(client.canReusePreservedOwnedSession ? { canReusePreservedOwnedSession: async (state: SandboxSessionState) => await client.canReusePreservedOwnedSession!(state) } : {}),\n ...(client.deserializeSessionState ? { deserializeSessionState: async (state: Record<string, unknown>) => await client.deserializeSessionState!(state) } : {}),\n };\n}\n\nasync function connectDockerNetwork(network: string, containerId: string): Promise<void> {\n const result = Bun.spawnSync([\"docker\", \"network\", \"connect\", network, containerId], {\n stdout: \"pipe\",\n stderr: \"pipe\",\n });\n if (result.exitCode === 0) {\n return;\n }\n const stderr = new TextDecoder().decode(result.stderr);\n if (stderr.includes(\"already exists\")) {\n return;\n }\n throw new Error(`Failed to connect Docker sandbox container to network ${network}: ${stderr.trim()}`);\n}\n\n/**\n * Extract the sandbox recovery entry from a run state as a plain JSON record,\n * for storage decoupled from the RunState blob (issue #35). Encapsulates the\n * underscore-internal `_sandbox` read in exactly one place.\n */\nexport function sandboxStateEntryFromRunState(state: unknown): Record<string, unknown> | null {\n const sandboxState = (state as any)?._sandbox;\n if (!sandboxState) {\n return null;\n }\n const entry = sandboxState.sessionsByAgent?.[sandboxState.currentAgentKey]\n ?? (sandboxState.currentAgentKey && sandboxState.sessionState\n ? {\n backendId: sandboxState.backendId,\n currentAgentKey: sandboxState.currentAgentKey,\n currentAgentName: sandboxState.currentAgentName,\n sessionState: sandboxState.sessionState,\n }\n : null);\n if (!entry || !entry.sessionState) {\n return null;\n }\n return entry as Record<string, unknown>;\n}\n\n/**\n * Items-mode counterpart of restoredSandboxSessionState: rebuild the live\n * sandbox session state from a stored entry (as produced by\n * sandboxStateEntryFromRunState) instead of from a RunState blob.\n */\nexport async function restoredSandboxSessionStateFromEntry(entry: Record<string, unknown>, client: unknown): Promise<SandboxSessionState | undefined> {\n if (!client || !entry || typeof entry !== \"object\" || !(\"sessionState\" in entry)) {\n return undefined;\n }\n if (entry.backendId && (client as SandboxClient).backendId !== entry.backendId) {\n throw new Error(\"Stored sandbox envelope backend does not match the configured sandbox client\");\n }\n return await deserializeSandboxSessionStateEnvelope(client as SandboxClient, entry.sessionState);\n}\n\n/**\n * Read the persisted /workspace snapshot archive off a lease envelope's\n * `sessionState` (sandbox-file-persistence). The reaper (persistDrainSnapshot)\n * folds the base64 archive — a Modal native snapshot-ref or a tar archive, the\n * exact bytes `session.persistWorkspace()` returned — at\n * `sessionState.workspaceArchive`. Cold-restore decodes it and replays it via\n * `session.hydrateWorkspace(archive)` on the freshly-created box so /workspace is\n * restored. Returns undefined when the envelope carries no archive (a box that\n * was never drain-persisted, or a non-persistence config that stored none).\n *\n * It is deliberately read SEPARATELY from deserializeSandboxSessionStateEnvelope:\n * the archive does NOT ride serializeSessionState (it originates at reaper time),\n * and the SDK's deserializeSessionState must NOT receive it (it is an opaque\n * runtime-level field, not provider state).\n */\nexport function readWorkspaceArchiveFromEnvelopeSessionState(sessionState: unknown): Uint8Array | undefined {\n if (!sessionState || typeof sessionState !== \"object\") {\n return undefined;\n }\n const b64 = (sessionState as { workspaceArchive?: unknown }).workspaceArchive;\n if (typeof b64 !== \"string\" || b64.length === 0) {\n return undefined;\n }\n try {\n return Uint8Array.from(Buffer.from(b64, \"base64\"));\n } catch {\n return undefined;\n }\n}\n\n// The native snapshot-ref prefixes the @openai/agents-extensions modal client\n// encodes (snapshots.mjs `NATIVE_SNAPSHOT_PREFIXES`). The ref is\n// `<PREFIX>\\n{\"snapshot_id\":\"...\",...}`. We re-implement the decode here because\n// `@openai/agents-extensions/sandbox/shared` is NOT an exported subpath (the\n// package `exports` map only exposes `./sandbox/<provider>`), so decodeNativeSnapshotRef\n// is unreachable — same reasoning as isProviderSandboxNotFoundError below.\nconst MODAL_SNAPSHOT_REF_PREFIXES = [\n \"MODAL_SANDBOX_FS_SNAPSHOT_V1\\n\",\n \"MODAL_SANDBOX_DIR_SNAPSHOT_V1\\n\",\n];\n\n/** Decode the Modal snapshot id out of a persisted base64 archive ref, or\n * undefined when the archive is a tar payload (no provider snapshot to GC) or\n * is unparseable. Used only for keep-latest-per-lease snapshot GC. */\nexport function decodeModalSnapshotId(archive: Uint8Array): string | undefined {\n let text: string;\n try {\n text = new TextDecoder().decode(archive);\n } catch {\n return undefined;\n }\n for (const prefix of MODAL_SNAPSHOT_REF_PREFIXES) {\n if (!text.startsWith(prefix)) {\n continue;\n }\n try {\n const payload = JSON.parse(text.slice(prefix.length)) as { snapshot_id?: unknown };\n return typeof payload.snapshot_id === \"string\" && payload.snapshot_id.length > 0\n ? payload.snapshot_id\n : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\n/**\n * Best-effort GC of a SUPERSEDED Modal filesystem/directory snapshot\n * (sandbox-file-persistence). restoreSnapshotFilesystem terminates the previous\n * SANDBOX but never deletes the prior SNAPSHOT image, so snapshots accumulate\n * unbounded across warm/cold cycles. The reaper keeps only the latest per lease:\n * when it writes a NEW archive it passes the PRIOR archive here to delete its\n * image via the live session's Modal client (`session.modal.images.delete(id)` —\n * the same API the SDK uses for directory images). Never throws (GC is a\n * best-effort backstop; a leaked snapshot is a cost issue, not a correctness one).\n * A tar archive (no snapshot id) is a no-op. Returns the deleted snapshot id (or\n * undefined when nothing was deleted) for observability.\n */\nexport async function deletePriorPersistedSnapshot(session: unknown, priorArchiveBase64: string | null | undefined): Promise<string | undefined> {\n if (!priorArchiveBase64) {\n return undefined;\n }\n let bytes: Uint8Array;\n try {\n bytes = Uint8Array.from(Buffer.from(priorArchiveBase64, \"base64\"));\n } catch {\n return undefined;\n }\n const snapshotId = decodeModalSnapshotId(bytes);\n if (!snapshotId) {\n return undefined;\n }\n const modal = (session as { modal?: { images?: { delete?: (id: string) => Promise<unknown> } } }).modal;\n const del = modal?.images?.delete;\n if (typeof del !== \"function\") {\n return undefined;\n }\n try {\n await del.call(modal!.images, snapshotId);\n return snapshotId;\n } catch {\n return undefined;\n }\n}\n\nexport async function deserializeSandboxSessionStateEnvelope(client: SandboxClient, envelope: unknown): Promise<SandboxSessionState | undefined> {\n if (!envelope || typeof envelope !== \"object\") {\n return undefined;\n }\n if (!client.deserializeSessionState) {\n throw new Error(\"Sandbox client must implement deserializeSessionState() to resume RunState sandbox state\");\n }\n const state = envelope as {\n providerState?: Record<string, unknown>;\n manifest?: unknown;\n snapshot?: unknown;\n snapshotFingerprint?: unknown;\n snapshotFingerprintVersion?: unknown;\n workspaceReady?: unknown;\n exposedPorts?: unknown;\n };\n return await client.deserializeSessionState({\n ...(state.providerState ?? {}),\n manifest: state.manifest,\n ...(state.snapshot !== undefined ? { snapshot: state.snapshot } : {}),\n ...(state.snapshotFingerprint !== undefined ? { snapshotFingerprint: state.snapshotFingerprint } : {}),\n ...(state.snapshotFingerprintVersion !== undefined ? { snapshotFingerprintVersion: state.snapshotFingerprintVersion } : {}),\n workspaceReady: state.workspaceReady,\n ...(state.exposedPorts ? { exposedPorts: structuredClone(state.exposedPorts) } : {}),\n });\n}\n\n// ============================================================================\n// The ONE resume / recovery primitive (P1.2).\n//\n// establishSandboxSessionFromEnvelope is the single re-establish-from-envelope\n// path the stateless model leans on: a turn (or any API-direct op) resolves the\n// group lease, hands us the recovery envelope, and gets back a LIVE non-owned\n// session. On a warm box this is a no-lock warm reattach by id (Modal fromId,\n// e2b reconnect — R4-safe, a stray second handle never spawns a second box).\n// When the provider reports the box genuinely gone (NotFound) we cold-restore\n// from the snapshot via create(). NEVER create() on any OTHER resume error\n// (only on NotFound) — a resume-conflict means the box is alive and the caller\n// must back off, not spawn a rival.\n// ============================================================================\n\n/** A live, externally-owned sandbox session re-established from the group lease\n * envelope. The caller injects `{client, session, sessionState}` NON-OWNED into\n * the run (or drives session.exec/readFile/resolveExposedPort directly) and\n * drops the handle when done — the lease, not this handle, owns the box. */\nexport type EstablishedSandboxSession = {\n client: unknown;\n session: unknown;\n sessionState: unknown;\n instanceId: string;\n backendId: string;\n};\n\n// The structural slice we need from a provider SandboxClient to resume by id and\n// cold-restore. Narrowed (not the full agent-loop SandboxClient) so the leaf\n// stays agent-loop-free.\ntype ResumeCapableClient = {\n backendId: string;\n deserializeSessionState?: (state: Record<string, unknown>) => Promise<unknown>;\n resume?: (state: unknown, options?: unknown) => Promise<unknown>;\n create?: (manifest?: unknown, options?: unknown) => Promise<unknown>;\n};\n\n/**\n * Per-provider NotFound discriminator. The @openai/agents-extensions\n * `isProviderSandboxNotFoundError` / `assertResumeRecreateAllowed` helpers live\n * under `@openai/agents-extensions/sandbox/shared`, which is NOT an exported\n * subpath (the package `exports` map only exposes `./sandbox/<provider>`), so we\n * re-implement the discrimination here by inspecting the thrown error shape.\n *\n * \"Box no longer running\" (the box was reaped / idled out / 24h-ceiling) is the\n * ONLY error that licenses a cold-restore via create(). Every other resume\n * failure (transient provider error, auth, network) must propagate so the caller\n * backs off — never spawns a rival box. We err on the side of NOT recreating:\n * an unrecognized error is treated as \"not NotFound\" (propagate), because a\n * false-positive recreate is the dangerous direction (double-spawn).\n */\nexport function isProviderSandboxNotFoundError(backendId: string, error: unknown): boolean {\n // selfhosted: agent-offline is NEVER a provider NotFound (the user's machine is\n // not recreatable — a false NotFound would cold-create a RIVAL box). The\n // selfhosted discriminator ALWAYS returns false; short-circuit so no goneMarker\n // string match below can ever flip a selfhosted agent-offline error to true.\n if (backendId === \"selfhosted\") {\n return isSelfhostedProviderNotFoundError(error);\n }\n if (!error) {\n return false;\n }\n const status = (error as { status?: unknown; statusCode?: unknown }).status\n ?? (error as { statusCode?: unknown }).statusCode;\n if (status === 404) {\n return true;\n }\n const name = typeof (error as { name?: unknown }).name === \"string\" ? (error as { name: string }).name : \"\";\n const code = typeof (error as { code?: unknown }).code === \"string\" ? (error as { code: string }).code : \"\";\n const message = error instanceof Error ? error.message : typeof error === \"string\" ? error : String((error as { message?: unknown })?.message ?? \"\");\n const haystack = `${name} ${code} ${message}`.toLowerCase();\n // Provider-agnostic \"gone\" markers (Modal: \"sandbox … not found\" / terminated;\n // e2b/daytona/runloop: \"not found\" / \"no longer running\" / \"terminated\" /\n // \"does not exist\"). Kept broad-but-conservative: it matches box-gone phrasing\n // and never matches generic 5xx/transport errors.\n const goneMarkers = [\n \"not found\",\n \"no longer running\",\n \"no longer exists\",\n \"does not exist\",\n \"doesn't exist\",\n \"has been terminated\",\n \"was terminated\",\n \"is terminated\",\n \"sandbox terminated\",\n \"notfound\",\n \"sandbox_not_found\",\n \"box no longer running\",\n ];\n // A \"running\"/\"already exists\" resume-conflict is explicitly NOT NotFound — the\n // box is alive; recreating would double-spawn.\n if (haystack.includes(\"already running\") || haystack.includes(\"still running\") || haystack.includes(\"already exists\")) {\n return false;\n }\n return goneMarkers.some((marker) => haystack.includes(marker));\n}\n\nfunction readInstanceId(session: unknown): string {\n const state = (session as { state?: Record<string, unknown> }).state ?? {};\n const candidate = state.sandboxId ?? state.instanceId ?? state.id ?? state.hostId ?? state.containerId;\n return typeof candidate === \"string\" && candidate.length > 0 ? candidate : \"\";\n}\n\n/**\n * Resume the one box by id from its recovery envelope, or cold-restore from the\n * snapshot when the provider reports it gone. The envelope is the lease's\n * box-identity descriptor (the same per-turn `_sandbox` envelope upserted by the\n * turn activity). A null envelope means a cold session that was never warmed →\n * create() directly.\n *\n * - `opts.backendOverride ?? envelope.backendId ?? settings.sandboxBackend`\n * selects the backend; the client is built for THAT backend (resume-by-id is\n * fenced to the original provider).\n * - warm reattach: deserialize the envelope sessionState → client.resume(state)\n * (no lock; R4-safe). On a provider NotFound, cold-restore via create().\n * - cold restore / cold session: client.create() — the ONLY create() site.\n */\nexport async function establishSandboxSessionFromEnvelope(\n settings: Settings,\n envelope: Record<string, unknown> | null,\n opts: { sessionId: string; backendOverride?: SandboxBackend; environment?: Record<string, string> },\n): Promise<EstablishedSandboxSession> {\n const envelopeBackend = typeof envelope?.backendId === \"string\" ? (envelope.backendId as SandboxBackend) : undefined;\n const backend = (opts.backendOverride ?? envelopeBackend ?? (settings.sandboxBackend as SandboxBackend));\n const environment = opts.environment ?? collectSandboxEnvironment(settings);\n const client = createSandboxClientForBackend(backend, settings, environment) as ResumeCapableClient | undefined;\n if (!client) {\n throw new SandboxConfigError(backend, `Cannot establish a sandbox session for backend \"${backend}\" (no client; sandboxBackend=none?)`);\n }\n if (!client.create) {\n throw new SandboxConfigError(backend, `Sandbox backend \"${backend}\" does not support create()`);\n }\n\n // The manifest the box is CREATED with. Its `environment` must equal the\n // environment the agent declares for this run (buildManifest's `environment`),\n // because the SDK injects this box NON-OWNED and then applies the agent's\n // manifest as a provided-session delta — `applyManifestToProvidedSession`\n // throws on ANY environment delta (validateNoEnvironmentDelta). The client's\n // constructor `env` materializes the RUNTIME env but does NOT populate\n // `manifest.environment` (a bare create() yields `new Manifest()` with an empty\n // environment), so the manifest env must be set here explicitly. `root` is left\n // to default to \"/workspace\" to match buildManifest's declared root (the\n // root-delta guard). The caller threads `opts.environment` = the SAME object\n // passed to runtime.buildAgent, so current==target and the delta is empty.\n const createManifest = { environment };\n\n // The serialized provider state the box was last persisted as. The envelope\n // shape is the per-turn `_sandbox` entry; its `sessionState` is the provider\n // payload deserializeSandboxSessionStateEnvelope re-hydrates.\n const envelopeSessionState = envelope && typeof envelope === \"object\" ? (envelope as { sessionState?: unknown }).sessionState : undefined;\n\n // The persisted /workspace snapshot the reaper folded onto the lease envelope\n // (sandbox-file-persistence). Present on a re-warm whose box was drain-persisted:\n // - WARM reattach NotFound path (box gone, full envelope still has sandboxId);\n // - COLD lease re-warm (confirmDrainCold preserved a MINIMAL archive-only\n // envelope `{ sessionState: { workspaceArchive } }` — NO sandboxId, so the\n // warm-reattach branch must NOT try resume()-by-id; it cold-creates+hydrates).\n const workspaceArchive = readWorkspaceArchiveFromEnvelopeSessionState(envelopeSessionState);\n\n // create() a FRESH box, THEN replay the persisted /workspace snapshot via\n // session.hydrateWorkspace(archive) when one rode the envelope. hydrateWorkspace\n // decodes the snapshot-ref and swaps the box for one booted from the snapshot\n // image (restoreSnapshotFilesystem); no archive -> a clean empty box. This is the\n // SOLE archive-replay seam, shared by the NotFound warm-reattach path AND the\n // cold-restore branch (b) below.\n const coldRestore = async (resumeFallbackState?: unknown): Promise<EstablishedSandboxSession> => {\n const restored = await client.create!({ manifest: createManifest });\n if (workspaceArchive) {\n const hydrate = (restored as { hydrateWorkspace?: (data: Uint8Array) => Promise<void> }).hydrateWorkspace;\n if (typeof hydrate === \"function\") {\n try {\n // hydrateWorkspace may internally REPLACE the underlying box\n // (restoreSnapshotFilesystem creates a replacement sandbox and terminates\n // the placeholder), so the instanceId must be re-read AFTER.\n await hydrate.call(restored, workspaceArchive);\n } catch (hydrateError) {\n // sandbox-file-persistence: if hydrateWorkspace throws (snapshot GC'd,\n // provider timeout, corrupt archive), the placeholder box created above is\n // live but unhydrated — it would leak up to the full idle/hard lifetime\n // (3600s) if we just re-throw. Best-effort delete/terminate it BEFORE\n // re-throwing so no box leaks. The original error semantics are preserved\n // (the re-throw propagates to the caller). This mirrors the reaper's\n // discipline: NEVER leave an orphaned box running.\n const restoredState = (restored as { state?: unknown }).state;\n const clientWithDelete = client as { delete?: (state: unknown) => Promise<unknown> };\n if (typeof clientWithDelete.delete === \"function\" && restoredState !== undefined) {\n try { await clientWithDelete.delete(restoredState); } catch { /* best-effort; re-throw the hydrate error below */ }\n } else {\n // No delete() — try a session-level close/terminate as a fallback.\n const sess = restored as { close?: () => Promise<unknown>; terminate?: () => Promise<unknown> };\n try { await (sess.terminate ?? sess.close)?.(); } catch { /* best-effort */ }\n }\n throw hydrateError;\n }\n }\n }\n const restoredState = (restored as { state?: unknown }).state;\n return { client, session: restored, sessionState: restoredState ?? resumeFallbackState, instanceId: readInstanceId(restored), backendId: client.backendId };\n };\n\n // Does the envelope carry a RESUMABLE box id (warm reattach), or only a\n // restorable archive (cold lease)? A Modal envelope with no providerState.sandboxId\n // (the minimal archive-only envelope confirmDrainCold preserves) is NOT resumable —\n // client.resume() would throw \"requires a persisted sandboxId\", which is NOT a\n // NotFound, so it would propagate instead of cold-restoring. Gate the resume\n // branch on a present sandbox identity so an archive-only envelope falls straight\n // through to the cold-restore+hydrate path (b).\n const envelopeProviderState = envelopeSessionState && typeof envelopeSessionState === \"object\"\n ? (envelopeSessionState as { providerState?: Record<string, unknown> }).providerState\n : undefined;\n const hasResumableInstance = Boolean(\n envelopeProviderState\n && typeof envelopeProviderState === \"object\"\n && (envelopeProviderState.sandboxId\n || envelopeProviderState.instanceId\n || envelopeProviderState.id\n || envelopeProviderState.containerId),\n );\n\n // (a) WARM REATTACH BY ID — only when the envelope carries a resumable box id.\n if (hasResumableInstance && envelopeSessionState && client.resume && client.deserializeSessionState) {\n let resumedState: unknown;\n try {\n resumedState = await deserializeSandboxSessionStateEnvelope(client as unknown as SandboxClient, envelopeSessionState);\n } catch (error) {\n throw new SandboxConfigError(backend, `Failed to deserialize sandbox resume envelope for backend \"${backend}\": ${error instanceof Error ? error.message : String(error)}`);\n }\n if (resumedState !== undefined) {\n try {\n const session = await client.resume(resumedState);\n return { client, session, sessionState: resumedState, instanceId: readInstanceId(session), backendId: client.backendId };\n } catch (error) {\n // ONLY a provider NotFound (box gone) licenses a cold-restore. Anything\n // else (transient/auth/network/resume-conflict) propagates: the caller\n // backs off and re-fences — NEVER spawns a rival box.\n if (!isProviderSandboxNotFoundError(client.backendId, error)) {\n throw error;\n }\n // COLD-RESTORE: the box is genuinely gone. Modal does NOT restore via\n // create({ snapshot }) — passing `snapshot` to ModalSandboxClient.create()\n // THROWS (assertCoreSnapshotUnsupported). Modal's real persistence is an\n // OPAQUE ARCHIVE captured by session.persistWorkspace() at reaper-drain\n // time and folded onto the lease envelope (sandbox-file-persistence). The\n // shared coldRestore() seam creates a fresh box and replays that archive.\n return await coldRestore(resumedState);\n }\n }\n }\n\n // (b) COLD SESSION / COLD LEASE — no resumable box id. create() a fresh box, and\n // if the envelope carries a persisted /workspace snapshot (the archive-only\n // envelope confirmDrainCold preserves across draining->cold), replay it so\n // /workspace survives the box churn (sandbox-file-persistence). No archive -> a\n // clean empty box (a never-warmed session).\n return await coldRestore();\n}\n\n// A client that can SERIALIZE a live session state back to the persistable\n// envelope form (the inverse of deserializeSessionState). Narrowed so the leaf\n// stays agent-loop-free.\ntype SerializeCapableClient = {\n backendId: string;\n serializeSessionState?: (state: unknown, options?: unknown) => Promise<Record<string, unknown>>;\n};\n\n/**\n * Fold a freshly-established (or resumed) sandbox session into the persistable\n * `resume_state` envelope the lease stores — the SAME `{ backendId, sessionState }`\n * shape `establishSandboxSessionFromEnvelope` consumes to RESUME BY ID. The\n * API-direct control plane (viewer attach / Channel-A) MUST persist this onto the\n * lease at warm-commit time, or a later op (which reads the lease's resume_state)\n * has nothing to resume from and COLD-CREATES A RIVAL BOX — the box-churn the\n * prove-it surfaced (fs.write then fs.read 404'd on a different box; N Channel-A\n * ops leaked N boxes). Returns null when the client cannot serialize (the caller\n * stores null and the box rides the provider idle-timeout — no rival spawn, just\n * no warm-reattach).\n */\nexport async function serializeEstablishedSandboxEnvelope(\n established: EstablishedSandboxSession,\n): Promise<Record<string, unknown> | null> {\n const client = established.client as SerializeCapableClient | undefined;\n if (!client || typeof client.serializeSessionState !== \"function\") {\n return null;\n }\n if (established.sessionState === undefined || established.sessionState === null) {\n return null;\n }\n try {\n // serializeSessionState returns the PERSISTABLE FLAT provider state — for\n // Modal `{ sandboxId, appName, imageTag, manifest(serialized),\n // configuredExposedPorts, ... }` (sandboxId preserved via `...state`).\n const serialized = await client.serializeSessionState(established.sessionState);\n\n // deserializeSandboxSessionStateEnvelope expects the lease-envelope shape\n // `{ providerState, manifest, snapshot?, exposedPorts?, workspaceReady }` and\n // rehydrates `{ ...providerState, manifest, snapshot?, exposedPorts?,\n // workspaceReady }`. So the FLAT serialized state must be nested under\n // `providerState` (and manifest/ports lifted), or sandboxId is dropped on the\n // round-trip and resume() throws \"requires a persisted sandboxId\". We pull\n // manifest/exposedPorts up but leave them in providerState too (harmless; the\n // deserialize spreads providerState first, then overlays manifest/ports).\n const flat = serialized as Record<string, unknown>;\n const manifest = flat.manifest;\n const exposedPorts = flat.configuredExposedPorts ?? flat.exposedPorts;\n const sessionState: Record<string, unknown> = {\n providerState: flat,\n ...(manifest !== undefined ? { manifest } : {}),\n ...(exposedPorts !== undefined ? { exposedPorts } : {}),\n workspaceReady: true,\n };\n return { backendId: established.backendId, sessionState };\n } catch {\n // A serialize failure must NOT fail the attach/op; we just lose warm-reattach\n // for this box (it stays resumable-by-instance only via the next cold path).\n return null;\n }\n}\n","// The provider registry — PROVIDER_REGISTRY maps each SandboxBackend to its\n// ProviderRegistration. This is the data structure createSandboxClient drives\n// (replacing the old flat if/else chain). The module also owns the\n// descriptor.backendId === SDK client.backendId assertion (deferred from P0.1):\n// it must construct the real SDK clients, so it lives here rather than in the\n// contracts-only capabilities self-test.\n\nimport type { Settings } from \"@opengeni/config\";\nimport { SandboxBackend } from \"@opengeni/contracts\";\nimport { assertDescriptorRegistryInvariants } from \"../capabilities\";\nimport { blaxelProvider } from \"./blaxel\";\nimport { cloudflareProvider } from \"./cloudflare\";\nimport { daytonaProvider } from \"./daytona\";\nimport { dockerProvider } from \"./docker\";\nimport { e2bProvider } from \"./e2b\";\nimport { localProvider } from \"./local\";\nimport { modalProvider } from \"./modal\";\nimport { noneProvider } from \"./none\";\nimport { runloopProvider } from \"./runloop\";\nimport { selfhostedProvider } from \"./selfhosted\";\nimport type { ProviderRegistration } from \"./types\";\nimport { vercelProvider } from \"./vercel\";\n\nexport const PROVIDER_REGISTRY: Record<SandboxBackend, ProviderRegistration> = {\n docker: dockerProvider,\n modal: modalProvider,\n local: localProvider,\n none: noneProvider,\n daytona: daytonaProvider,\n runloop: runloopProvider,\n e2b: e2bProvider,\n blaxel: blaxelProvider,\n cloudflare: cloudflareProvider,\n vercel: vercelProvider,\n selfhosted: selfhostedProvider,\n};\n\n// Stub settings carrying every per-provider credential, used ONLY by the\n// boot-time backendId assertion to construct each client without tripping\n// validateCredentials. The SDK client constructors are pure option-stores (the\n// underlying provider SDK is required lazily at create()/resume() time, never at\n// construction — verified against @openai/agents-extensions 0.11.6), so this is\n// safe with no provider peer dep installed and no network.\nconst ASSERTION_STUB_SETTINGS = {\n dockerImage: \"opengeni-sandbox:local\",\n modalAppName: \"opengeni-sandbox\",\n modalTimeoutSeconds: 900,\n daytonaApiKey: \"stub\",\n runloopApiKey: \"stub\",\n runloopTunnel: true,\n e2bApiKey: \"stub\",\n blaxelApiKey: \"stub\",\n cloudflareWorkerUrl: \"https://stub.example.com\",\n vercelToken: \"stub\",\n vercelProjectId: \"stub\",\n} as unknown as Settings;\n\n/**\n * Assert the descriptor table AND that each registered provider's SDK client\n * reports the backendId its descriptor claims. The latter is the\n * deferred-from-P0.1 invariant — it can only run here because it constructs the\n * real clients. Called once at registry build (and from a unit test).\n */\nexport function assertProviderRegistryInvariants(): void {\n assertDescriptorRegistryInvariants();\n for (const backend of SandboxBackend.options) {\n const registration = PROVIDER_REGISTRY[backend];\n if (registration.backend !== backend) {\n throw new Error(`PROVIDER_REGISTRY[\"${backend}\"].backend mismatch (got \"${registration.backend}\")`);\n }\n if (registration.descriptor.backend !== backend) {\n throw new Error(`PROVIDER_REGISTRY[\"${backend}\"].descriptor.backend mismatch (got \"${registration.descriptor.backend}\")`);\n }\n if (backend === \"none\") {\n // \"none\" has no SDK client (build returns undefined); the descriptor\n // backendId \"none\" is self-consistent.\n if (registration.descriptor.backendId !== \"none\") {\n throw new Error(`\"none\" descriptor.backendId must be \"none\" (got \"${registration.descriptor.backendId}\")`);\n }\n continue;\n }\n const client = registration.build({\n settings: ASSERTION_STUB_SETTINGS,\n environment: {},\n exposedPorts: [],\n });\n const sdkBackendId = (client as { backendId?: unknown } | undefined)?.backendId;\n if (typeof sdkBackendId !== \"string\") {\n throw new Error(`Provider \"${backend}\" SDK client has no string backendId`);\n }\n if (sdkBackendId !== registration.descriptor.backendId) {\n throw new Error(\n `Provider \"${backend}\" backendId mismatch: descriptor.backendId=\"${registration.descriptor.backendId}\" but SDK client.backendId=\"${sdkBackendId}\"`,\n );\n }\n }\n}\n\n// Boot-validate the registry once at module load: the descriptor-table self-\n// test PLUS the descriptor.backendId === SDK client.backendId assertion (the\n// deferred-from-P0.1 invariant). The SDK client constructors are pure option-\n// stores (no network, no peer-dep require at construction), so this is a cheap,\n// side-effect-free guard that fails fast on any drift between the static matrix\n// and the installed @openai/agents-extensions.\nassertProviderRegistryInvariants();\n\nexport type { ProviderRegistration, ProviderConstructionContext } from \"./types\";\n","// Capability-descriptor access + boot-time registry invariants (module 03 §2.3).\n//\n// The CapabilityDescriptor type and the CAPABILITY_DESCRIPTORS table-as-data\n// live in @opengeni/contracts (NOT here) so config can read them without an\n// import cycle through runtime (ledger CR8). This module re-exports them for the\n// registry's convenience and owns the boot-time self-test that keeps the table\n// honest as providers are added.\n\nimport {\n CAPABILITY_DESCRIPTORS,\n DESKTOP_STREAM_PORT,\n SandboxBackend,\n type CapabilityDescriptor,\n} from \"@opengeni/contracts\";\n\nexport { CAPABILITY_DESCRIPTORS, DESKTOP_STREAM_PORT };\nexport type { CapabilityDescriptor };\n\n/**\n * Descriptor-table invariants, asserted once at registry build (and from a unit\n * test). This is the guardrail that keeps the static matrix internally coherent.\n * It validates the descriptor data only; the descriptor.backendId === SDK\n * client.backendId assertion (the deferred-from-P0.1 check) lives in\n * providers/index.ts because it must construct the real SDK clients.\n */\nexport function assertDescriptorRegistryInvariants(): void {\n for (const backend of SandboxBackend.options) {\n const descriptor = CAPABILITY_DESCRIPTORS[backend];\n if (!descriptor) {\n throw new Error(`No CapabilityDescriptor for backend \"${backend}\"`);\n }\n if (descriptor.backend !== backend) {\n throw new Error(`Descriptor.backend mismatch for \"${backend}\" (got \"${descriptor.backend}\")`);\n }\n\n // DesktopStream implies a non-\"none\" port-exposure mechanism (split-plane B\n // needs a way to surface 6080 to a viewer).\n if (descriptor.capabilities.DesktopStream.available && descriptor.portExposure.kind === \"none\") {\n throw new Error(`\"${backend}\" claims DesktopStream but portExposure.kind=none`);\n }\n\n // DesktopStream implies a transport, and a desktop transport implies the\n // capability is available (no half-declared desktop rows).\n if (descriptor.capabilities.DesktopStream.available && descriptor.capabilities.DesktopStream.transport === null) {\n throw new Error(`\"${backend}\" claims DesktopStream but transport is null`);\n }\n\n // Recording feasibility is the same Xvfb display as DesktopStream: a desktop\n // backend MUST be able to x11grab; a non-desktop backend MUST NOT claim it\n // (Recording.available == DesktopStream.available && os==linux — Part D).\n if (descriptor.capabilities.DesktopStream.available !== descriptor.capabilities.Recording.available) {\n throw new Error(\n `\"${backend}\" Recording.available (${descriptor.capabilities.Recording.available}) must equal DesktopStream.available (${descriptor.capabilities.DesktopStream.available})`,\n );\n }\n\n // Persistable backends are the only ones the lease can re-establish from an\n // envelope — they must carry a real snapshot mechanism.\n if (descriptor.persistable && descriptor.snapshot.kind === \"none\") {\n throw new Error(`\"${backend}\" persistable but snapshot.kind=none`);\n }\n\n // A nativeBucketMount backend must be desktop/headless-tier real (modal):\n // a dev/none tier cannot own a provider bucket mount.\n if (descriptor.nativeBucketMount && (descriptor.tier === \"dev\" || descriptor.tier === \"none\")) {\n throw new Error(`\"${backend}\" claims nativeBucketMount on tier=${descriptor.tier}`);\n }\n }\n}\n","import { BlaxelSandboxClient } from \"@openai/agents-extensions/sandbox/blaxel\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const blaxelProvider: ProviderRegistration = {\n backend: \"blaxel\",\n descriptor: CAPABILITY_DESCRIPTORS.blaxel,\n validateCredentials(settings) {\n if (!settings.blaxelApiKey) {\n throw new SandboxConfigError(\"blaxel\", \"OPENGENI_BLAXEL_API_KEY is required\");\n }\n },\n build({ settings, environment }) {\n // Blaxel exposes ports ON DEMAND (the only such backend) — its options take\n // no `exposedPorts: number[]` list; 6080 is resolved at handshake time, so\n // we do NOT pre-declare it here (this is why the factory's 6080-merge is\n // gated on !supportsOnDemandPorts).\n const options: NonNullable<ConstructorParameters<typeof BlaxelSandboxClient>[0]> = {\n apiKey: settings.blaxelApiKey!,\n env: environment,\n };\n if (settings.blaxelImage) options.image = settings.blaxelImage;\n if (settings.blaxelRegion) options.region = settings.blaxelRegion;\n if (settings.blaxelExposedPortPublic !== undefined) {\n options.exposedPortPublic = settings.blaxelExposedPortPublic;\n }\n if (settings.blaxelExposedPortUrlTtlSeconds) {\n options.exposedPortUrlTtlS = settings.blaxelExposedPortUrlTtlSeconds;\n }\n if (settings.blaxelMemoryMb) options.memory = settings.blaxelMemoryMb;\n if (settings.blaxelTtl) options.ttl = settings.blaxelTtl;\n return new BlaxelSandboxClient(options);\n },\n};\n","// Typed sandbox-construction errors for the provider registry (module 03 §5.1).\n//\n// SandboxConfigError is thrown by validateCredentials() and the factory on any\n// missing/contradictory provider config — a fail-fast typed error so an\n// unknown/misconfigured backend surfaces clearly instead of failing deep inside\n// the SDK at create() time.\n\nimport type { SandboxBackend } from \"@opengeni/contracts\";\n\nexport class SandboxConfigError extends Error {\n readonly backend: SandboxBackend | string;\n\n constructor(backend: SandboxBackend | string, message: string) {\n super(`[sandbox:${backend}] ${message}`);\n this.name = \"SandboxConfigError\";\n this.backend = backend;\n }\n}\n\n// Thrown by a provider's build() when its SDK client class is genuinely not\n// available in the installed @openai/agents-extensions. Per the P0.3 ruling we\n// NEVER fake a build body; if a provider cannot be constructed we register the\n// descriptor and make build() throw this. (As of @openai/agents-extensions\n// 0.11.6 every provider ships a concrete client, so this is currently unused —\n// it is the documented contract for a future drop that loses a provider.)\nexport class SandboxProviderUnavailableError extends Error {\n readonly backend: SandboxBackend | string;\n\n constructor(backend: SandboxBackend | string) {\n super(`provider ${backend} not available in installed @openai/agents-extensions`);\n this.name = \"SandboxProviderUnavailableError\";\n this.backend = backend;\n }\n}\n","import { CloudflareSandboxClient } from \"@openai/agents-extensions/sandbox/cloudflare\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const cloudflareProvider: ProviderRegistration = {\n backend: \"cloudflare\",\n descriptor: CAPABILITY_DESCRIPTORS.cloudflare,\n validateCredentials(settings) {\n // workerUrl is the addressing root for the Cloudflare Sandbox Worker — there\n // is no construction without it (it is the one non-optional client option).\n if (!settings.cloudflareWorkerUrl) {\n throw new SandboxConfigError(\"cloudflare\", \"OPENGENI_CLOUDFLARE_WORKER_URL is required\");\n }\n },\n build({ settings, exposedPorts }) {\n const options: NonNullable<ConstructorParameters<typeof CloudflareSandboxClient>[0]> = {\n workerUrl: settings.cloudflareWorkerUrl!,\n exposedPorts,\n };\n if (settings.cloudflareApiKey) options.apiKey = settings.cloudflareApiKey;\n return new CloudflareSandboxClient(options);\n },\n};\n","import { DaytonaSandboxClient } from \"@openai/agents-extensions/sandbox/daytona\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const daytonaProvider: ProviderRegistration = {\n backend: \"daytona\",\n descriptor: CAPABILITY_DESCRIPTORS.daytona,\n validateCredentials(settings) {\n if (!settings.daytonaApiKey) {\n throw new SandboxConfigError(\"daytona\", \"OPENGENI_DAYTONA_API_KEY is required\");\n }\n },\n build({ settings, environment, exposedPorts }) {\n const options: NonNullable<ConstructorParameters<typeof DaytonaSandboxClient>[0]> = {\n apiKey: settings.daytonaApiKey!,\n env: environment,\n exposedPorts,\n };\n if (settings.daytonaApiUrl) options.apiUrl = settings.daytonaApiUrl;\n if (settings.daytonaTarget) options.target = settings.daytonaTarget;\n if (settings.daytonaImage) options.image = settings.daytonaImage;\n if (settings.daytonaSnapshotName) options.sandboxSnapshotName = settings.daytonaSnapshotName;\n // autoStopInterval=0 disables the idle-kill, so forward 0 explicitly.\n if (settings.daytonaAutoStopInterval !== undefined) {\n options.autoStopInterval = settings.daytonaAutoStopInterval;\n }\n if (settings.daytonaTimeoutSeconds) options.timeoutSec = settings.daytonaTimeoutSeconds;\n if (settings.daytonaExposedPortUrlTtlSeconds) {\n options.exposedPortUrlTtlS = settings.daytonaExposedPortUrlTtlSeconds;\n }\n return new DaytonaSandboxClient(options);\n },\n};\n","import { DockerSandboxClient } from \"@openai/agents/sandbox/local\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const dockerProvider: ProviderRegistration = {\n backend: \"docker\",\n descriptor: CAPABILITY_DESCRIPTORS.docker,\n // Local dev container — no credentials. (The dockerNetwork decoration is\n // applied by the factory, not here: it wraps the constructed client.)\n validateCredentials() {},\n build({ settings, exposedPorts }) {\n return new DockerSandboxClient({\n image: settings.dockerImage,\n exposedPorts,\n });\n },\n};\n","import { E2BSandboxClient } from \"@openai/agents-extensions/sandbox/e2b\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const e2bProvider: ProviderRegistration = {\n backend: \"e2b\",\n descriptor: CAPABILITY_DESCRIPTORS.e2b,\n validateCredentials(settings) {\n // The underlying e2b SDK reads E2B_API_KEY from process.env; we mirror it to\n // settings so a misconfigured deployment fails fast here instead of deep in\n // the SDK at create() time.\n if (!settings.e2bApiKey) {\n throw new SandboxConfigError(\"e2b\", \"OPENGENI_E2B_API_KEY is required\");\n }\n },\n build({ settings, environment, exposedPorts }) {\n const options: NonNullable<ConstructorParameters<typeof E2BSandboxClient>[0]> = {\n env: environment,\n exposedPorts,\n };\n if (settings.e2bTemplate) options.template = settings.e2bTemplate;\n // e2b's `timeout` is in SECONDS (the SDK multiplies by 1000 internally) —\n // a different unit from Modal's `timeoutMs`.\n if (settings.e2bTimeoutSeconds) options.timeout = settings.e2bTimeoutSeconds;\n if (settings.e2bTimeoutAction) options.timeoutAction = settings.e2bTimeoutAction;\n if (settings.e2bAllowInternetAccess !== undefined) {\n options.allowInternetAccess = settings.e2bAllowInternetAccess;\n }\n if (settings.e2bAutoResume !== undefined) options.autoResume = settings.e2bAutoResume;\n if (settings.e2bWorkspacePersistence) {\n options.workspacePersistence = settings.e2bWorkspacePersistence;\n }\n return new E2BSandboxClient(options);\n },\n};\n","import { UnixLocalSandboxClient } from \"@openai/agents/sandbox/local\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const localProvider: ProviderRegistration = {\n backend: \"local\",\n descriptor: CAPABILITY_DESCRIPTORS.local,\n // UnixLocalSandboxClient runs in-process — no credentials, no options.\n validateCredentials() {},\n build() {\n return new UnixLocalSandboxClient();\n },\n};\n","import { ModalImageSelector, ModalSandboxClient } from \"@openai/agents-extensions/sandbox/modal\";\nimport { effectiveModalIdleTimeoutSeconds } from \"@opengeni/config\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const modalProvider: ProviderRegistration = {\n backend: \"modal\",\n descriptor: CAPABILITY_DESCRIPTORS.modal,\n validateCredentials(settings) {\n // both-or-neither (preserves existing validation at config validateSettings).\n if (Boolean(settings.modalTokenId) !== Boolean(settings.modalTokenSecret)) {\n throw new SandboxConfigError(\n \"modal\",\n \"OPENGENI_MODAL_TOKEN_ID and OPENGENI_MODAL_TOKEN_SECRET must both be set or both omitted\",\n );\n }\n if (!settings.modalAppName) {\n throw new SandboxConfigError(\"modal\", \"OPENGENI_MODAL_APP_NAME is required\");\n }\n },\n build({ settings, environment, exposedPorts }) {\n const options: NonNullable<ConstructorParameters<typeof ModalSandboxClient>[0]> = {\n appName: settings.modalAppName,\n timeoutMs: settings.modalTimeoutSeconds * 1000,\n exposedPorts,\n env: environment,\n };\n // gap-fill (module 03 §4.1): these SDK options were previously unmapped.\n // ALWAYS pin idleTimeoutMs (sandbox-file-persistence): an UNSET idle timeout\n // lets the SDK send idleTimeoutSecs=undefined, so Modal applies its short\n // server-default idle-reap and kills an idle (between-turns) box LONG before\n // OpenGeni's reaper can resume+snapshot it. effectiveModalIdleTimeoutSeconds\n // defaults this to the hard lifetime so the box survives its full warm window\n // and the reaper — not Modal's idle-reap — governs teardown (and snapshots\n // /workspace first).\n options.idleTimeoutMs = effectiveModalIdleTimeoutSeconds(settings) * 1000;\n if (settings.modalWorkspacePersistence) {\n options.workspacePersistence = settings.modalWorkspacePersistence;\n }\n if (settings.modalImageRef) {\n options.image = ModalImageSelector.fromTag(settings.modalImageRef);\n }\n if (settings.modalTokenId) {\n options.tokenId = settings.modalTokenId;\n }\n if (settings.modalTokenSecret) {\n options.tokenSecret = settings.modalTokenSecret;\n }\n if (settings.modalEnvironment) {\n options.environment = settings.modalEnvironment;\n }\n return new ModalSandboxClient(options);\n },\n};\n","import { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const noneProvider: ProviderRegistration = {\n backend: \"none\",\n descriptor: CAPABILITY_DESCRIPTORS.none,\n // No sandbox: nothing to validate, and build() returns undefined. The factory\n // short-circuits on \"none\" before calling build, but we keep build honest.\n validateCredentials() {},\n build() {\n return undefined;\n },\n};\n","import { RunloopSandboxClient } from \"@openai/agents-extensions/sandbox/runloop\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const runloopProvider: ProviderRegistration = {\n backend: \"runloop\",\n descriptor: CAPABILITY_DESCRIPTORS.runloop,\n validateCredentials(settings) {\n if (!settings.runloopApiKey) {\n throw new SandboxConfigError(\"runloop\", \"OPENGENI_RUNLOOP_API_KEY is required\");\n }\n },\n build({ settings, environment, exposedPorts }) {\n const options: NonNullable<ConstructorParameters<typeof RunloopSandboxClient>[0]> = {\n apiKey: settings.runloopApiKey!,\n env: environment,\n exposedPorts,\n // Tunnel v2: one tunnel for all ports. Defaults to true in our config.\n tunnel: settings.runloopTunnel,\n };\n if (settings.runloopBaseUrl) options.baseUrl = settings.runloopBaseUrl;\n if (settings.runloopBlueprintName) options.blueprintName = settings.runloopBlueprintName;\n if (settings.runloopBlueprintId) options.blueprintId = settings.runloopBlueprintId;\n // Runloop's keep-alive lives under the timeouts bag (keepAliveTimeoutMs),\n // NOT a top-level field — the units differ from Modal's idleTimeoutMs.\n if (settings.runloopKeepAliveSeconds) {\n options.timeouts = { keepAliveTimeoutMs: settings.runloopKeepAliveSeconds * 1000 };\n }\n return new RunloopSandboxClient(options);\n },\n};\n","// The selfhosted CONTROL-PLANE transport seam (the M3/M4 layering boundary).\n//\n// `ControlRpc` is the ONE seam the `SelfhostedSession` depends on to reach a\n// user's enrolled machine: request/reply addressed by the subject\n// `agent.<workspaceId>.<agentId>.rpc`, payloads encoded/decoded via\n// `@opengeni/agent-proto` (the single-source-of-truth wire types). The session\n// knows NOTHING about NATS — it speaks only `ControlRpc`.\n//\n// M3 ships TWO implementors behind this interface:\n// - `NatsControlRpc` — a thin wrapper over a NATS request/reply connection\n// (the existing `@opengeni/events` bus connection). Constructed LAZILY: if\n// NATS/the relay is not configured it surfaces `agent_offline` rather than\n// throwing at construction, so boot never requires a live NATS.\n// - `MockAgentResponder` — an in-process test double that answers\n// exec/fs.read/fs.write/ping (and the rest of the op table) without any\n// broker, so the session surface + the AgentError→reason mapping are unit-\n// and integration-testable with no live NATS (that is M4).\n//\n// M4 will HARDEN/REPLACE `NatsControlRpc` (NATS Accounts, real request\n// hardening, retries) behind this SAME `ControlRpc` interface — design for that,\n// do not duplicate.\n\nimport {\n AgentError,\n ControlRequest,\n ControlResponse,\n ErrorCode,\n} from \"@opengeni/agent-proto\";\nimport type { CapabilityUnavailableReason } from \"@opengeni/contracts\";\n\n// Re-export the contracts reason union locally so callers of the mapping below\n// don't have to import from two places. CapabilityUnavailableReason here is the\n// agent-proto mirror of the contracts enum (same string values); the runtime\n// negotiation maps to the contracts type, which is structurally identical.\nexport type SelfhostedUnavailableReason = Extract<\n CapabilityUnavailableReason,\n \"agent_offline\" | \"agent_reconnecting\" | \"consent_required\" | \"display_unavailable\"\n>;\n\n/**\n * The selfhosted control-plane transport seam. ONE method: `request` — send a\n * `ControlRequest` to the agent addressed by subject and await its\n * `ControlResponse`. The subject is `subjectFor(workspaceId, agentId)`.\n *\n * The CONTRACT every implementor MUST honour (the M3 ruling): a\n * no-responder / request-timeout is NOT an exception that means \"not found\" — it\n * is surfaced as a `ControlResponse` carrying an `AgentError` with code\n * `AGENT_OFFLINE` (no responder at all) or, when the caller can distinguish a\n * transient blip, `TIMEOUT` (→ `agent_reconnecting`). The session maps these to\n * the runtime error taxonomy; it NEVER lets agent-offline look like a provider\n * NotFound (which would cold-create a rival box for a user's real machine).\n */\nexport interface ControlRpc {\n request(\n subject: string,\n req: ControlRequest,\n opts: { timeoutMs: number },\n ): Promise<ControlResponse>;\n}\n\n/** The control-plane RPC subject for an enrolled agent — its subscription IS the\n * registry (the binding two-plane decision). */\nexport function subjectFor(workspaceId: string, agentId: string): string {\n return `agent.${workspaceId}.${agentId}.rpc`;\n}\n\n// ── The runtime error taxonomy for a selfhosted control op ────────────────────\n\n/**\n * The runtime-level error a `SelfhostedSession` op throws when the agent returns\n * an `AgentError` (or no responder / timeout maps to one). It carries:\n * - `code` — the wire `ErrorCode` (single-source-of-truth);\n * - `reason` — the negotiated `CapabilityUnavailableReason` the capability /\n * liveness surface uses (`agent_offline` / `agent_reconnecting`\n * / `consent_required`), or null for op-level errors\n * (OS/NOT_FOUND/UNSUPPORTED/STREAM/PROTOCOL) that are not a\n * machine-liveness condition;\n * - `retryable`— whether the caller should re-resolve + retry (DRAINING /\n * FENCED / a reconnecting blip);\n * - `notFound` — ALWAYS the provider-NotFound discriminator value: for\n * selfhosted this is true ONLY for an OS-level NOT_FOUND of a\n * path/ref (a real \"the file does not exist\"), and is FALSE for\n * AGENT_OFFLINE (the machine isn't recreatable — never let the\n * lease cold-create a rival). `isProviderSandboxNotFoundError`\n * reads this.\n */\nexport class SelfhostedControlError extends Error {\n readonly name = \"SelfhostedControlError\";\n readonly code: ErrorCode;\n readonly reason: SelfhostedUnavailableReason | null;\n readonly retryable: boolean;\n readonly fenced: boolean;\n readonly draining: boolean;\n readonly agentOffline: boolean;\n readonly osNotFound: boolean;\n readonly detail: Record<string, string>;\n\n constructor(input: {\n message: string;\n code: ErrorCode;\n reason: SelfhostedUnavailableReason | null;\n retryable: boolean;\n fenced?: boolean;\n draining?: boolean;\n agentOffline?: boolean;\n osNotFound?: boolean;\n detail?: Record<string, string>;\n }) {\n super(input.message);\n this.code = input.code;\n this.reason = input.reason;\n this.retryable = input.retryable;\n this.fenced = input.fenced ?? false;\n this.draining = input.draining ?? false;\n this.agentOffline = input.agentOffline ?? false;\n this.osNotFound = input.osNotFound ?? false;\n this.detail = input.detail ?? {};\n }\n}\n\n/**\n * Map an `AgentError` (from a `ControlResponse`) to the runtime\n * `SelfhostedControlError`. THE load-bearing mapping (the M3 ruling):\n * - AGENT_OFFLINE → reason `agent_offline`, agentOffline=true,\n * osNotFound=FALSE (NEVER a provider NotFound).\n * - TIMEOUT (a transient missed-window / no-responder blip the caller marked\n * retryable) → reason `agent_reconnecting`.\n * - CONSENT_REQUIRED → reason `consent_required`.\n * - DRAINING → no capability reason; retryable (turn pauses + retries).\n * - FENCED → no capability reason; retryable (the existing\n * epoch-fence retry; the caller re-resolves + retries).\n * - NOT_FOUND → an OS-level path/ref NotFound — osNotFound=true (a\n * real \"file does not exist\"), no machine-liveness\n * reason. (This is the ONLY NotFound; it is NOT the\n * box-gone NotFound that licenses a cold restore.)\n * - OS / UNSUPPORTED / STREAM / PROTOCOL / UNSPECIFIED → op-level error, no\n * reason, non-retryable.\n */\nexport function agentErrorToControlError(err: AgentError): SelfhostedControlError {\n const message = err.message || `agent error (${err.code})`;\n const detail = err.detail ?? {};\n switch (err.code) {\n case ErrorCode.ERROR_CODE_AGENT_OFFLINE:\n return new SelfhostedControlError({\n message: message || \"the enrolled agent is offline\",\n code: err.code,\n reason: \"agent_offline\",\n retryable: false,\n agentOffline: true,\n detail,\n });\n case ErrorCode.ERROR_CODE_TIMEOUT:\n // A timeout is a transient blip: the agent may be reconnecting. The turn\n // pauses-with-timeout then retries against the re-resolved active sandbox.\n return new SelfhostedControlError({\n message: message || \"the enrolled agent did not respond in time\",\n code: err.code,\n reason: \"agent_reconnecting\",\n retryable: true,\n detail,\n });\n case ErrorCode.ERROR_CODE_CONSENT_REQUIRED:\n return new SelfhostedControlError({\n message: message || \"the op requires consent that has not been granted\",\n code: err.code,\n reason: \"consent_required\",\n retryable: false,\n detail,\n });\n case ErrorCode.ERROR_CODE_DRAINING:\n return new SelfhostedControlError({\n message: message || \"the agent is draining and cannot accept new work\",\n code: err.code,\n reason: null,\n retryable: true,\n draining: true,\n detail,\n });\n case ErrorCode.ERROR_CODE_FENCED:\n return new SelfhostedControlError({\n message: message || \"a stale op was fenced by the epoch guard; re-resolve and retry\",\n code: err.code,\n reason: null,\n retryable: true,\n fenced: true,\n detail,\n });\n case ErrorCode.ERROR_CODE_NOT_FOUND:\n // An OS-level path/ref NotFound — a real \"the file/ref does not exist\". This\n // is NOT the box-gone NotFound (selfhosted has no box-gone — the machine is\n // not recreatable). osNotFound is surfaced so a fs-layer caller can 404 the\n // path, but isProviderSandboxNotFoundError stays FALSE for selfhosted (see\n // session.ts) — a missing file must never license a cold re-create.\n return new SelfhostedControlError({\n message: message || \"the referenced path or ref does not exist\",\n code: err.code,\n reason: null,\n retryable: Boolean(err.retryable),\n osNotFound: true,\n detail,\n });\n default:\n // OS / UNSUPPORTED / STREAM / PROTOCOL / UNSPECIFIED — an op-level failure.\n return new SelfhostedControlError({\n message,\n code: err.code,\n reason: null,\n retryable: Boolean(err.retryable),\n detail,\n });\n }\n}\n\n/** Build a synthesized AGENT_OFFLINE `AgentError` — the control plane uses this\n * when no agent responds on the subject at all. */\nexport function offlineAgentError(message = \"no agent responded (offline)\"): AgentError {\n return {\n code: ErrorCode.ERROR_CODE_AGENT_OFFLINE,\n message,\n retryable: false,\n detail: {},\n };\n}\n\n/** Build a synthesized TIMEOUT `AgentError` — the control plane uses this when a\n * responder existed but the request timed out (a transient blip → reconnecting). */\nexport function timeoutAgentError(message = \"the agent did not respond in time\"): AgentError {\n return {\n code: ErrorCode.ERROR_CODE_TIMEOUT,\n message,\n retryable: true,\n detail: {},\n };\n}\n\n// ── NatsControlRpc — the thin request/reply wrapper (M4 hardens this) ─────────\n\n/**\n * The minimal NATS request/reply surface `NatsControlRpc` needs. It mirrors the\n * `nats` `NatsConnection.request` signature WITHOUT importing `nats` into the\n * agent-loop-free runtime leaf: the API/worker injects the live connection (the\n * SAME `@opengeni/events` bus connection). A factory may return `null` when NATS\n * is not configured (boot must not require a live NATS) — `NatsControlRpc` then\n * surfaces `agent_offline` for every request rather than throwing.\n */\nexport interface NatsRequestConnection {\n request(\n subject: string,\n payload: Uint8Array,\n opts: { timeout: number },\n ): Promise<{ data: Uint8Array }>;\n}\n\n/** A NATS error whose `code` marks \"no responder on the subject\" (NATS 503). The\n * selfhosted control plane reads this as `agent_offline`, NEVER a NotFound. */\nconst NATS_NO_RESPONDERS_CODE = \"503\";\n\nfunction isNoRespondersError(err: unknown): boolean {\n const code = (err as { code?: unknown })?.code;\n if (typeof code === \"string\" && code === NATS_NO_RESPONDERS_CODE) {\n return true;\n }\n const message = err instanceof Error ? err.message : String(err);\n return /no responders|503/i.test(message);\n}\n\nfunction isRequestTimeoutError(err: unknown): boolean {\n const code = (err as { code?: unknown })?.code;\n // nats.js uses \"TIMEOUT\" for a request timeout.\n if (typeof code === \"string\" && /timeout/i.test(code)) {\n return true;\n }\n const message = err instanceof Error ? err.message : String(err);\n return /timeout|timed out/i.test(message);\n}\n\n/**\n * A thin `ControlRpc` over a NATS request/reply connection. Constructed with a\n * LAZY factory: the connection is resolved on first `request` (so boot never\n * requires a live NATS). A null factory result, a no-responder error, or a\n * request timeout each yield a `ControlResponse` carrying a synthesized\n * `AgentError` (AGENT_OFFLINE / TIMEOUT) — NEVER a thrown transport error and\n * NEVER a NotFound.\n *\n * The factory is async and memoized; it may itself dial the bus. M4 replaces the\n * factory's body with the Accounts-scoped, hardened connection — this class's\n * shape does not change.\n */\nexport class NatsControlRpc implements ControlRpc {\n private readonly connect: () => Promise<NatsRequestConnection | null>;\n private connection: NatsRequestConnection | null | undefined;\n\n constructor(connect: () => Promise<NatsRequestConnection | null>) {\n this.connect = connect;\n }\n\n private async resolveConnection(): Promise<NatsRequestConnection | null> {\n if (this.connection === undefined) {\n try {\n this.connection = await this.connect();\n } catch {\n // A connect failure is an OFFLINE condition, not a crash. Cache null so a\n // later request retries the factory (set undefined to allow retry).\n this.connection = null;\n return null;\n }\n }\n return this.connection ?? null;\n }\n\n async request(\n subject: string,\n req: ControlRequest,\n opts: { timeoutMs: number },\n ): Promise<ControlResponse> {\n const conn = await this.resolveConnection();\n if (!conn) {\n // No NATS configured / not reachable → the agent is unaddressable → offline.\n return offlineControlResponse(req.requestId);\n }\n const payload = ControlRequest.encode(req).finish();\n try {\n const reply = await conn.request(subject, payload, { timeout: opts.timeoutMs });\n return ControlResponse.decode(reply.data);\n } catch (err) {\n // Re-allow a future request to re-dial if the cached conn was torn down.\n if (isNoRespondersError(err)) {\n // No subscriber on the subject at all → the machine is offline.\n return offlineControlResponse(req.requestId);\n }\n if (isRequestTimeoutError(err)) {\n // A responder may exist but the request timed out → a transient blip.\n return timeoutControlResponse(req.requestId);\n }\n // Any other transport error → treat as offline (never a NotFound). The op\n // surfaces agent_offline and the lease never cold-creates a rival.\n this.connection = undefined; // force a re-dial next time\n return offlineControlResponse(req.requestId);\n }\n }\n}\n\n/** A `ControlResponse` carrying a synthesized AGENT_OFFLINE error. */\nexport function offlineControlResponse(requestId: string): ControlResponse {\n return { requestId, error: offlineAgentError(), result: undefined };\n}\n\n/** A `ControlResponse` carrying a synthesized TIMEOUT error (→ reconnecting). */\nexport function timeoutControlResponse(requestId: string): ControlResponse {\n return { requestId, error: timeoutAgentError(), result: undefined };\n}\n","// `SelfhostedSession` + `SelfhostedSandboxClient` — the NATS-backed structural\n// sandbox surface for the `selfhosted` backend (bring-your-own-compute).\n//\n// The insight (dossier §7): every existing seam (Channel-A exec/fs/git, the\n// viewer's `resolveExposedPort`, computer-use) consumes a provider session\n// STRUCTURALLY — `session.exec ?? session.execCommand`, `session.readFile`,\n// `session.resolveExposedPort`, `session.serializeSessionState`. If the\n// selfhosted client's `create()`/`resume()` return a session presenting that\n// EXACT surface — but backed by `ControlRpc` (request/reply to the agent over\n// `agent.<ws>.<id>.rpc`, encoded via `@opengeni/agent-proto`) instead of a\n// provider SDK — then those seams work UNCHANGED. The agent IS the box.\n//\n// The session depends ONLY on `ControlRpc` + `{workspaceId, agentId}` (+ the\n// relay config for the stream-URL SHAPE). It knows nothing about NATS directly\n// (the M3/M4 seam). `serializeSessionState`/`deserializeSessionState` round-trip\n// `{agentId}` ONLY — resume = re-address the live subject, NO provider state.\n\nimport {\n ControlRequest,\n ControlResponse,\n FsEntryKind,\n StreamKind,\n type DesktopInputRequest,\n type ExecRequest,\n type ExecResponse,\n type StreamChannel,\n} from \"@opengeni/agent-proto\";\nimport { DESKTOP_STREAM_PORT } from \"@opengeni/contracts\";\n// `Manifest` from the ALLOWED sandbox-leaf entrypoint (`@openai/agents/sandbox`\n// re-exports `@openai/agents-core/sandbox`, which exports the Manifest class) —\n// NOT the agent-loop `@openai/agents` root the sandbox leaf forbids. The live\n// `state.manifest` slice the @openai/agents SDK reads per turn must be a real\n// Manifest (see the `state` field below); selfhosted exec routes over NATS and\n// does not use the manifest, but the SDK requires it present + well-formed.\nimport { Manifest } from \"@openai/agents/sandbox\";\nimport type { ExposedPortEndpoint } from \"../stream-port\";\nimport {\n agentErrorToControlError,\n subjectFor,\n type ControlRpc,\n} from \"./control-rpc\";\n\nconst decoder = new TextDecoder();\nconst encoder = new TextEncoder();\n\n/**\n * The SDK's VIRTUAL sandbox root. The `@openai/agents` agent loop presents the\n * sandbox to the model rooted at this path — it equals `state.manifest.root`,\n * which is held at \"/workspace\" to match the Modal createManifest root for the\n * provided-session root-delta guard (`validateProvidedSessionManifestUpdate`).\n *\n * On a bring-your-own machine this path DOES NOT EXIST: the machine's real root\n * is the agent's `workspace_root` (reported in Hello, e.g. \"/home/jorge/repo\").\n * The Rust agent's `resolve_cwd` maps an EMPTY cwd / a RELATIVE path onto its\n * `workspace_root`, but takes an ABSOLUTE path AS-IS. So a virtual-root-anchored\n * path the SDK hands us (\"/workspace\" or \"/workspace/sub\", e.g. an exec workdir\n * or a model-relative file the SDK resolved against the manifest root) would hit\n * the machine as a literal absolute \"/workspace/…\" → `current_dir`/open ENOENT\n * (the live-swap exec crash: `spawn hostname: No such file or directory`).\n *\n * `toMachinePath` rewrites the virtual frame onto the machine's: the root itself\n * → the session `workingDir` (empty by default ⇒ \"\", so the agent substitutes its\n * workspace_root); a child → `workingDir`-rooted remainder (the agent joins it onto\n * workspace_root). A genuine machine-ABSOLUTE path the model/agent chose (\"/tmp/x\"),\n * or a real path echoed back by `listDir`, passes through UNTOUCHED. This is the\n * SOLE adapter rule between the SDK's virtual space and the machine's real\n * filesystem; it is applied at every NATS path/cwd boundary below (exec cwd, fs\n * read/write/list/stat, the editor's delete, the terminal's pty cwd). The\n * per-session `workingDir` (default \"\" ⇒ a byte-identical no-op) is the base.\n */\nconst SELFHOSTED_VIRTUAL_ROOT = \"/workspace\";\n\n/**\n * `workingDir` is the session's per-session working directory — the frame's BASE.\n * It is the launch-workspace_root-relative subdir (or an absolute machine path)\n * the agent/terminal/dock operate under. An EMPTY `workingDir` (the default) makes\n * this byte-identical to before: `base === \"\"`, so every branch returns the\n * original value (empty/virtual → \"\", virtual-child → its remainder, a relative or\n * absolute path → itself). A trailing slash on `workingDir` is stripped so a join\n * never doubles; relative stays relative and absolute stays absolute otherwise.\n */\nfunction toMachinePath(p: string | undefined, workingDir: string): string {\n const base = workingDir.replace(/\\/$/, \"\");\n if (!p || p === SELFHOSTED_VIRTUAL_ROOT) return base;\n if (p.startsWith(`${SELFHOSTED_VIRTUAL_ROOT}/`)) {\n const rel = p.slice(SELFHOSTED_VIRTUAL_ROOT.length + 1);\n return base ? `${base}/${rel}` : rel;\n }\n // An ABSOLUTE machine path — a genuine path the model/agent chose (\"/tmp/x\") or\n // a real path echoed back by `listDir` — points anywhere and passes through\n // UNTOUCHED (the agent's `resolve_cwd` takes an absolute path as-is).\n if (p.startsWith(\"/\")) return p;\n // A BARE-RELATIVE path is the structural Channel-A surface's frame: the file dock\n // joins fs read/list/git sub-paths under an EMPTY workspaceRoot (yielding a bare\n // relative), and a model-supplied relative exec workdir is bare too. Root it under\n // the session working dir so those reads/stats stay in the SAME frame as the dock's\n // working-dir-rooted listing/exec (which run with cwd = workingDir). The SDK agent\n // loop never emits a bare-relative path — it anchors everything at the manifest\n // root (\"/workspace/…\") — so this only re-homes the structural surface. With an\n // empty workingDir it is a no-op (base === \"\" ⇒ returns the path unchanged).\n return base ? `${base}/${p}` : p;\n}\n\n// ── The agent-turn provided-session contract (@openai/agents-core) ──────────\n// When the routing proxy resolves a selfhosted ACTIVE backend, the @openai/agents\n// agent loop binds its filesystem/shell/skills capabilities to THIS session and\n// calls a richer method set than the Channel-A structural surface: `createEditor`\n// + `viewImage` (filesystem), `execCommand` + `supportsPty` (shell), `pathExists`\n// + `listDir` + `materializeEntry` + `readFile` (skills). The session must present\n// all of them or the turn crashes (e.g. \"Filesystem sandbox sessions must provide\n// createEditor()\"). These run over the SAME NATS exec/fs primitives; the machine\n// owns its filesystem so source materialization is a no-op.\n\n/** The V4A-diff applier the SDK's apply_patch editor uses. The leaf cannot import\n * `@openai/agents`'s `applyDiff` (the agent-loop root the leaf forbids), so the\n * runtime barrel (`packages/runtime/src/index.ts`, which DOES import that root)\n * injects it via `setSelfhostedApplyDiff` at module load. Until injected,\n * `createEditor()` surfaces a clear error rather than a silent wrong-edit. */\nexport type SelfhostedApplyDiff = (input: string, diff: string, mode?: \"default\" | \"create\") => string;\nlet injectedApplyDiff: SelfhostedApplyDiff | undefined;\n\n/** Register the SDK's `applyDiff` so `SelfhostedSession.createEditor()` can apply\n * V4A diffs over the NATS fs ops. Called once by the runtime barrel. */\nexport function setSelfhostedApplyDiff(fn: SelfhostedApplyDiff): void {\n injectedApplyDiff = fn;\n}\n\n/** The structural Editor surface the SDK's filesystem capability consumes (the\n * three apply_patch operations). Mirrors `@openai/agents-core`'s `Editor`. */\nexport interface SelfhostedEditor {\n createFile(operation: { path: string; diff: string }, context?: unknown): Promise<{ output?: string } | void>;\n updateFile(operation: { path: string; diff: string; moveTo?: string }, context?: unknown): Promise<{ output?: string } | void>;\n deleteFile(operation: { path: string }, context?: unknown): Promise<{ output?: string } | void>;\n}\n\n/** The image tool-output shape the SDK's view_image tool expects (mirror of\n * `ToolOutputImage` — not re-exported by `@openai/agents/sandbox`, so structural). */\nexport interface SelfhostedImageOutput {\n type: \"image\";\n image: { data: Uint8Array; mediaType: string };\n}\n\n/** Default control-op timeout. A transient miss surfaces as `agent_reconnecting`\n * (the turn pauses + retries); it is NOT a hard failure. */\nexport const SELFHOSTED_DEFAULT_TIMEOUT_MS = 30_000;\n\n/** The relay-URL shape config the session needs to build a stream endpoint. M8b\n * wires the real relay deployment behind THIS seam so `buildStreamUrl` works\n * unchanged behind `resolveExposedPort`. */\nexport interface SelfhostedRelayConfig {\n /** The relay edge host (no scheme), e.g. \"relay.opengeni.ai\". */\n host: string;\n /** The relay port. Defaults to 443 (the relay terminates TLS). */\n port?: number;\n /** Whether the relay endpoint is TLS (wss/https). Defaults true. */\n tls?: boolean;\n /** The relay's stream-dial path (the `opengeni-relay` wss route). Defaults to\n * \"/stream\" — the route the relay listens on (M8b). */\n path?: string;\n}\n\n/** The relay's default wss dial path (the `opengeni-relay` server route). */\nexport const SELFHOSTED_RELAY_STREAM_PATH = \"/stream\";\n\nexport interface SelfhostedSessionDeps {\n workspaceId: string;\n agentId: string;\n controlRpc: ControlRpc;\n relay: SelfhostedRelayConfig;\n /** The lease/active epoch this session is fenced under (echoed on every\n * ControlRequest so the agent can reject a stale op with ERROR_CODE_FENCED).\n * Defaults to 0 (no fence) for the negotiation-only / test path. */\n epoch?: number;\n /** Override the control-op timeout (tests). */\n timeoutMs?: number;\n /**\n * The run's declared sandbox environment — the SAME `Record<string,string>` the\n * worker turn passes to `runtime.buildAgent`'s `sandboxEnvironment` (and that the\n * agent's TARGET manifest, `buildManifest`, carries). The SDK injects this\n * selfhosted session NON-OWNED and applies the agent's manifest as a provided-\n * session delta; `validateNoEnvironmentDelta` throws \"Live sandbox sessions cannot\n * change manifest environment variables\" on ANY env mismatch. So `state.manifest`'s\n * `environment` MUST EQUAL the turn's environment for the delta to be empty. The\n * selfhosted exec routes over NATS and does NOT consume the env, but the manifest\n * must carry it for parity. Omitted → `{}` (the negotiation-only / test path,\n * which never applies a turn manifest, so there is no delta to validate).\n */\n environment?: Record<string, string>;\n /**\n * The session's working directory — the BASE every path/cwd is rooted under (see\n * `toMachinePath` / SELFHOSTED_VIRTUAL_ROOT). A launch-workspace_root-relative\n * subdir (resolved under workspace_root by the agent's `resolve_cwd`) or an\n * absolute machine path. Omitted/empty (the default) ⇒ \"\" ⇒ today's behavior\n * exactly (an empty cwd lets the agent substitute its workspace_root).\n */\n workingDir?: string;\n}\n\n/** The Channel-A `exec` result shape (a structural superset of the SDK's). */\nexport interface SelfhostedExecResult {\n output: string;\n stdout: string;\n stderr: string;\n exitCode: number | null;\n}\n\n/** The `exec` args the structural surface accepts (mirrors ChannelAExecArgs). */\nexport interface SelfhostedExecArgs {\n cmd: string;\n workdir?: string | undefined;\n shell?: string | undefined;\n login?: boolean | undefined;\n tty?: boolean | undefined;\n runAs?: string | undefined;\n}\n\n/**\n * The persistable session state. For selfhosted this is `{agentId}` ONLY — there\n * is NO provider box id, no snapshot, no manifest. Resume re-addresses the live\n * subject; the machine itself is the persistence (`persistable:false`).\n */\nexport interface SelfhostedSessionState {\n agentId: string;\n}\n\n/**\n * A live selfhosted session — the structural `SandboxSessionLike` surface over a\n * `ControlRpc`. Mirrors Modal's session shape so Channel-A/viewer/computer-use\n * consume it unchanged.\n */\nexport class SelfhostedSession {\n readonly backendId = \"selfhosted\" as const;\n readonly workspaceId: string;\n readonly agentId: string;\n private readonly controlRpc: ControlRpc;\n private readonly relay: SelfhostedRelayConfig;\n private readonly epoch: number;\n private readonly timeoutMs: number;\n private readonly subject: string;\n /** The session working directory — the path/cwd base every op is rooted under\n * (see `toMachinePath`). \"\" by default ⇒ today's workspace_root behavior. */\n private readonly workingDir: string;\n\n /**\n * The structural `state` slice consumers read. `agentId`/`instanceId` serve the\n * channel-a `readInstanceId` + docker-network decoration (the agentId IS the\n * identity). `manifest` is the slice the @openai/agents SDK reads AND writes per\n * turn (serializeManifestEnvironment / validateProvidedSessionManifestUpdate read\n * `manifest.root` + iterate `manifest.environment`; providedSessionManifest WRITES\n * `state.manifest = next`). It must be a real, MUTABLE Manifest field — when the\n * RoutingSandboxSession proxy resolves THIS as the active backend it returns\n * `session.state` BY REFERENCE, so the SDK's read and write must both land on a\n * well-formed Manifest here (defined `root`, object `environment`). Without it the\n * SDK crashes with `undefined is not an object (evaluating 'current.root')`.\n *\n * `manifest` is intentionally a plain mutable field (not `readonly`) so the SDK's\n * `state.manifest = next` write succeeds. It is NOT part of the persistable state\n * (`serializeSessionState` round-trips `{agentId}` only).\n *\n * `environment` is the SDK `SandboxSessionState.environment` (a `Record<string,\n * string>`). It MUST be present because the GROUP box's client serializes THIS\n * (the active backend's) state at end-of-turn — the non-owned injected session is\n * serialized via the CONFIGURED client (modal in prod), NOT the selfhosted client.\n * Modal's `serializeRemoteSandboxSessionState` does `Object.entries(state.environment)`;\n * an absent field crashes the post-turn RunState serialize with \"Object.entries\n * requires that input parameter not be null or undefined\". It carries the run's\n * threaded environment (or `{}`). The resulting modal-tagged envelope is inert for\n * selfhosted (resume re-addresses the machine by agentId via the lease pointer,\n * never from this SDK envelope), so its only job is to not crash the serialize.\n */\n readonly state: { agentId: string; instanceId: string; manifest: Manifest; environment: Record<string, string> };\n\n constructor(deps: SelfhostedSessionDeps) {\n this.workspaceId = deps.workspaceId;\n this.agentId = deps.agentId;\n this.controlRpc = deps.controlRpc;\n this.relay = deps.relay;\n this.epoch = deps.epoch ?? 0;\n this.timeoutMs = deps.timeoutMs ?? SELFHOSTED_DEFAULT_TIMEOUT_MS;\n this.subject = subjectFor(deps.workspaceId, deps.agentId);\n this.workingDir = deps.workingDir ?? \"\";\n // A valid Manifest mirroring the Modal create-manifest shape (sandbox/index.ts\n // `createManifest`: `new Manifest({ root: \"/workspace\", environment })`). `root`\n // is \"/workspace\" to match `buildManifest`'s declared root (the root-delta guard\n // in validateProvidedSessionManifestUpdate). This is the VIRTUAL root the SDK\n // presents to the model; `toMachinePath` (see SELFHOSTED_VIRTUAL_ROOT) rewrites\n // it onto the machine's real `workspace_root` at every exec/fs NATS boundary,\n // so the manifest never needs to carry the machine's true root. `environment`\n // is the run's declared\n // sandbox environment — the SAME object the worker turn threads into the agent's\n // TARGET manifest — so the SDK's per-turn provided-session delta\n // (validateNoEnvironmentDelta) finds NO mismatch. `entries: {}` because the\n // selfhosted machine already owns its filesystem (no SDK materialization; exec\n // routes over NATS). Omitted env (the negotiation-only / test path) defaults to\n // `{}` — no turn manifest is applied there, so there is no delta to validate.\n this.state = {\n agentId: deps.agentId,\n instanceId: deps.agentId,\n manifest: new Manifest({ root: \"/workspace\", entries: {}, environment: deps.environment ?? {} }),\n // The SDK `SandboxSessionState.environment` — the run's threaded env (or `{}`).\n // The group client's end-of-turn serialize reads `state.environment` directly\n // (Object.entries), so it must be a defined object, not absent.\n environment: deps.environment ?? {},\n };\n }\n\n /** Issue a control op, decoding the agent's reply or throwing the mapped\n * `SelfhostedControlError` on an AgentError (incl. a synthesized offline /\n * timeout error from the transport). */\n private async call(op: NonNullable<ControlRequest[\"op\"]>): Promise<NonNullable<ControlResponse[\"result\"]>> {\n const req: ControlRequest = {\n requestId: crypto.randomUUID(),\n epoch: this.epoch,\n op,\n };\n const res = await this.controlRpc.request(this.subject, req, { timeoutMs: this.timeoutMs });\n if (res.error) {\n throw agentErrorToControlError(res.error);\n }\n if (!res.result) {\n throw agentErrorToControlError({\n code: 7, // ERROR_CODE_PROTOCOL — an empty result is a protocol violation\n message: \"agent returned an empty control response\",\n retryable: false,\n detail: {},\n });\n }\n return res.result;\n }\n\n /** Channel-A `exec`: run a command on the machine and return its output. */\n async exec(args: SelfhostedExecArgs): Promise<SelfhostedExecResult> {\n const execReq: ExecRequest = {\n // The agent does NOT shell-interpret unless `shell` — Channel-A passes a\n // single shell command string, so run it through the platform shell.\n command: [args.cmd],\n shell: true,\n // Rewrite a virtual-root cwd (\"/workspace[/…]\") onto the machine's frame —\n // an absolute \"/workspace\" would ENOENT on a real machine (see\n // SELFHOSTED_VIRTUAL_ROOT). Empty → the session workingDir (itself \"\" by\n // default ⇒ the agent runs in its workspace_root).\n cwd: toMachinePath(args.workdir, this.workingDir),\n env: {},\n stdin: new Uint8Array(0),\n timeoutMs: 0,\n };\n const result = await this.call({ $case: \"exec\", exec: execReq });\n if (result.$case !== \"exec\") {\n throw new Error(`selfhosted exec: unexpected result ${result.$case}`);\n }\n return execResultToChannelA(result.exec);\n }\n\n // ── The agent-turn provided-session contract (over the SAME NATS primitives) ──\n // These are what the @openai/agents shell/filesystem/skills capabilities call on\n // the ACTIVE session once the routing proxy resolves selfhosted. They reuse the\n // exec/fs ops above; the machine owns its filesystem (materialization is a no-op).\n\n /** SDK shell capability `execCommand`: run a command and return its stdout (the\n * `exec_command` tool). Selfhosted exec is non-interactive (no PTY) — `tty` is\n * ignored; `supportsPty()` is false so the SDK never offers a stdin session. */\n async execCommand(args: { cmd: string; workdir?: string; runAs?: string }): Promise<string> {\n const result = await this.exec({ cmd: args.cmd, workdir: args.workdir, runAs: args.runAs });\n return result.output;\n }\n\n /** SDK shell capability never calls this (gated on `supportsPty()` which is\n * false), but the surface advertises it. Selfhosted exec has no interactive PTY\n * session over the structured RPC, so a stdin write is unsupported. */\n supportsPty(): boolean {\n return false;\n }\n\n /** SDK filesystem capability `view_image`: read the image bytes off the machine\n * and wrap them in the tool-output image shape (magic-byte sniff + path fallback,\n * mirroring the SDK's `imageOutputFromBytes`). */\n async viewImage(args: { path: string; runAs?: string }): Promise<SelfhostedImageOutput> {\n const bytes = await this.readFile({ path: args.path, ...(args.runAs ? { runAs: args.runAs } : {}) });\n const mediaType = sniffImageMediaType(bytes, args.path);\n if (!mediaType) {\n throw new Error(`selfhosted view_image: unsupported image format for ${args.path}`);\n }\n return { type: \"image\", image: { data: Uint8Array.from(bytes), mediaType } };\n }\n\n /** SDK skills/filesystem `pathExists`: whether a path exists on the machine. */\n async pathExists(path: string, _runAs?: string): Promise<boolean> {\n const { exists } = await this.statFile({ path });\n return exists;\n }\n\n /** SDK skills `listDir`: list a directory as `{name, path, type}[]`. */\n async listDir(args: { path: string; runAs?: string }): Promise<Array<{ name: string; path: string; type: \"file\" | \"dir\" | \"other\" }>> {\n const result = await this.listFiles({ path: args.path });\n return result.fsList.entries.map((entry) => ({\n name: entry.name,\n path: entry.path,\n type:\n entry.kind === FsEntryKind.FS_ENTRY_KIND_DIRECTORY\n ? (\"dir\" as const)\n : entry.kind === FsEntryKind.FS_ENTRY_KIND_FILE\n ? (\"file\" as const)\n : (\"other\" as const),\n }));\n }\n\n /** SDK manifest-delta `materializeEntry`: a NO-OP for selfhosted. Source\n * materialization (cloning repos / staging files into the box) is how cloud\n * providers prepare a fresh box; a bring-your-own machine already owns its\n * filesystem and is prepared by the agent itself, so there is nothing to stage.\n * Present (not absent) so the SDK's provided-session manifest apply path — which\n * requires `applyManifest()` OR `materializeEntry()` when the agent declares\n * entries — is satisfied without error. The selfhosted manifest declares no\n * entries, so in practice this is never invoked with a real entry. */\n async materializeEntry(_args: { path: string; entry: unknown; runAs?: string }): Promise<void> {\n return;\n }\n\n /** SDK filesystem capability `createEditor`: the apply_patch host. Applies V4A\n * diffs over the NATS fs ops (read → applyDiff → write). `applyDiff` is the SDK's\n * own parser, injected by the runtime barrel (the leaf cannot import it). */\n createEditor(runAs?: string): SelfhostedEditor {\n const applyDiff = injectedApplyDiff;\n if (!applyDiff) {\n throw new Error(\n \"selfhosted createEditor: applyDiff not injected (the runtime barrel must call setSelfhostedApplyDiff before an agent turn binds the filesystem capability)\",\n );\n }\n const pathExists = (path: string): Promise<boolean> => this.pathExists(path, runAs);\n const readText = async (path: string): Promise<string> =>\n decoder.decode(await this.readFile({ path, ...(runAs ? { runAs } : {}) }));\n const writeText = async (path: string, content: string): Promise<void> => {\n await this.writeFile({ path, content, createParents: true });\n };\n const deletePath = async (path: string): Promise<void> => {\n // No fs-delete op in the proto; remove via the shell (the machine's own rm).\n // The path arg is embedded in the command, and this.exec runs it with the\n // DEFAULT cwd = the session workingDir. So target the path RELATIVE to that\n // cwd: strip the virtual root to its bare remainder (toMachinePath with an\n // EMPTY base) — prefixing workingDir here too would DOUBLE it (the cwd is\n // already workingDir). A non-virtual absolute path passes through and rm\n // uses it as-is; an empty workingDir is byte-identical to before.\n await this.exec({ cmd: `rm -rf -- ${shellQuote(toMachinePath(path, \"\"))}`, ...(runAs ? { runAs } : {}) });\n };\n return {\n async createFile(operation) {\n if (await pathExists(operation.path)) {\n throw new Error(`selfhosted createFile: file already exists: ${operation.path}`);\n }\n await writeText(operation.path, applyDiff(\"\", operation.diff, \"create\"));\n return {};\n },\n async updateFile(operation) {\n const current = await readText(operation.path);\n const next = applyDiff(current, operation.diff);\n const destination = operation.moveTo ?? operation.path;\n await writeText(destination, next);\n if (operation.moveTo && destination !== operation.path) {\n await deletePath(operation.path);\n }\n return {};\n },\n async deleteFile(operation) {\n await deletePath(operation.path);\n return {};\n },\n };\n }\n\n /** Channel-A `readFile`: read a file off the machine (binary-safe). */\n async readFile(args: { path: string; runAs?: string; maxBytes?: number }): Promise<Uint8Array> {\n const result = await this.call({\n $case: \"fsRead\",\n fsRead: {\n path: toMachinePath(args.path, this.workingDir),\n offset: \"0\",\n length: args.maxBytes ? String(args.maxBytes) : \"0\",\n },\n });\n if (result.$case !== \"fsRead\") {\n throw new Error(`selfhosted readFile: unexpected result ${result.$case}`);\n }\n return result.fsRead.content;\n }\n\n /** Write a file onto the machine (the fs surface the descriptor advertises). */\n async writeFile(args: { path: string; content: string | Uint8Array; createParents?: boolean; append?: boolean }): Promise<number> {\n const content = typeof args.content === \"string\" ? encoder.encode(args.content) : args.content;\n const result = await this.call({\n $case: \"fsWrite\",\n fsWrite: {\n path: toMachinePath(args.path, this.workingDir),\n content,\n createParents: args.createParents ?? true,\n append: args.append ?? false,\n mode: 0,\n },\n });\n if (result.$case !== \"fsWrite\") {\n throw new Error(`selfhosted writeFile: unexpected result ${result.$case}`);\n }\n return Number(result.fsWrite.bytesWritten);\n }\n\n /** List a directory on the machine. */\n async listFiles(args: { path: string; recursive?: boolean }): Promise<NonNullable<ControlResponse[\"result\"]> & { $case: \"fsList\" }> {\n const result = await this.call({\n $case: \"fsList\",\n fsList: { path: toMachinePath(args.path, this.workingDir), recursive: args.recursive ?? false },\n });\n if (result.$case !== \"fsList\") {\n throw new Error(`selfhosted listFiles: unexpected result ${result.$case}`);\n }\n return result;\n }\n\n /** Stat a path on the machine. */\n async statFile(args: { path: string }): Promise<{ exists: boolean }> {\n const result = await this.call({ $case: \"fsStat\", fsStat: { path: toMachinePath(args.path, this.workingDir) } });\n if (result.$case !== \"fsStat\") {\n throw new Error(`selfhosted statFile: unexpected result ${result.$case}`);\n }\n return { exists: result.fsStat.exists };\n }\n\n // ── Computer-use control plane (the agent drives its OWN screen) ──────────────\n // The CONTROL-PLANE twin of the relay DesktopInput/desktop stream: instead of a\n // human viewer channel, the agent injects synthetic input into — and captures —\n // its own display for the model's computer-use loop. Both route over the SAME\n // `call()` primitive, so a consent/epoch rejection surfaces as the mapped\n // `SelfhostedControlError` exactly like every other op. `NativeDesktopComputer`\n // (sandbox-computer.ts) is the sole consumer.\n\n /** Computer-use WRITE op: inject one synthetic desktop input event (pointer/key/\n * scroll) on the machine's OWN display. The agent injects via CGEvent (macOS) /\n * XTEST (Linux) and CONSENT-GATES it — an unconsented call never touches the OS\n * and surfaces the mapped control error (ERROR_CODE_CONSENT_REQUIRED) via `call()`. */\n async desktopInput(event: DesktopInputRequest[\"event\"]): Promise<void> {\n const result = await this.call({ $case: \"desktopInput\", desktopInput: { event } });\n if (result.$case !== \"desktopInput\") {\n throw new Error(`selfhosted desktopInput: unexpected result ${result.$case}`);\n }\n }\n\n /** Computer-use VIEW op: capture a single PNG screenshot of the machine's desktop\n * plus its geometry (via ScreenCaptureKit / x11). NOT consent-gated (a view op —\n * the view/control decoupling), so it works with a display but no screen-control\n * consent. Returns the raw encoded bytes + width/height. */\n async screenshot(): Promise<{ png: Uint8Array; width: number; height: number }> {\n const result = await this.call({ $case: \"desktopScreenshot\", desktopScreenshot: {} });\n if (result.$case !== \"desktopScreenshot\") {\n throw new Error(`selfhosted screenshot: unexpected result ${result.$case}`);\n }\n return {\n png: result.desktopScreenshot.png,\n width: result.desktopScreenshot.width,\n height: result.desktopScreenshot.height,\n };\n }\n\n /** A cheap liveness probe — request a Ping on the subject; returns true iff a\n * responder answered (no AgentError). Used by `negotiateSelfhostedCapabilities`.\n * The wire `nonce` is a uint64 (a numeric string), so the default is a random\n * numeric value — NOT a UUID (which would fail proto uint64 encoding). */\n async ping(nonce = randomNonce()): Promise<boolean> {\n const req: ControlRequest = {\n requestId: crypto.randomUUID(),\n epoch: this.epoch,\n op: { $case: \"ping\", ping: { nonce } },\n };\n const res = await this.controlRpc.request(this.subject, req, { timeoutMs: this.timeoutMs });\n return !res.error && res.result?.$case === \"ping\";\n }\n\n /**\n * Resolve an exposed port to a relay stream endpoint (the viewer/pty plane).\n * Returns the relay URL SHAPE — `{host:relay, port, tls, query:channel-key}` —\n * after asking the agent to ensure a stream channel for the port. M8b wires the\n * real relay tier (the byte pump) behind THIS seam.\n *\n * THE CHANNEL-KEY QUERY (the M8b relay-dial contract, dossier §10.5): the relay\n * routes by `{workspaceId, agentId, port}` — the EXACT `ChannelKey::query` the\n * agent's relay client (`opengeni-agent-stream`) appends when it registers the\n * producer side: `ws=<workspaceId>&agent=<agentId>&port=<port>`. We append the\n * agent-registered `channel=<channelId>` as a correlation hint. So the viewer\n * dials `wss://<relay>/stream?ws=&agent=&port=&channel=` and presents the minted\n * `ogs_` token in-band (NEVER as a URL param) — the relay pairs it with the\n * producer by the routing key.\n */\n async resolveExposedPort(port: number): Promise<ExposedPortEndpoint> {\n // Ask the agent to ensure a relay PRODUCER channel exists for the port, using the\n // PORT-APPROPRIATE op. The PTY plane (7681) is INDEPENDENT of the desktop display:\n // route it through `ptyOpen` (which spawns/attaches a PTY and NEVER touches X11),\n // and ONLY the desktop framebuffer plane (6080) through `desktopEnsure` (which\n // hard-requires a live virtual display). Earlier M8b used `desktopEnsure` for\n // EVERY port — that wrongly coupled the terminal to the desktop probe, so a\n // headless (or display-degraded) machine could never get a terminal even though\n // `ptyOpen` would have succeeded. The returned channelId is the relay\n // correlation hint; both ops carry a `StreamChannel` on their response.\n let channel: StreamChannel | undefined;\n if (port === DESKTOP_STREAM_PORT) {\n const result = await this.call({\n $case: \"desktopEnsure\",\n desktopEnsure: { width: 0, height: 0 },\n });\n if (result.$case !== \"desktopEnsure\") {\n throw new Error(`selfhosted resolveExposedPort(${port}): unexpected result ${result.$case}`);\n }\n channel = result.desktopEnsure.channel;\n } else {\n // The PTY plane (7681) + any non-desktop stream port. `command: []` => the\n // user's default login shell; the agent's pty_pump bridges the PTY master to\n // the relay channel. Display-INDEPENDENT — works on a headless machine.\n const result = await this.call({\n $case: \"ptyOpen\",\n // Open the terminal in the session workingDir (default \"\" ⇒ the agent's\n // workspace_root, byte-identical to before). A relative workingDir resolves\n // under workspace_root; an absolute one is used as-is by the agent.\n ptyOpen: { command: [], cwd: this.workingDir, env: {}, cols: 0, rows: 0, term: \"xterm-256color\" },\n });\n if (result.$case !== \"ptyOpen\") {\n throw new Error(`selfhosted resolveExposedPort(${port}): unexpected result ${result.$case}`);\n }\n channel = result.ptyOpen.channel;\n }\n const channelId = channel?.channelId ?? channelKey(this.workspaceId, this.agentId, port);\n const tls = this.relay.tls ?? true;\n // The routing key the relay pairs producer↔consumer by — IDENTICAL to the\n // agent's `ChannelKey::query` — plus the channel-id correlation hint.\n const routingQuery =\n `ws=${encodeURIComponent(this.workspaceId)}` +\n `&agent=${encodeURIComponent(this.agentId)}` +\n `&port=${port}` +\n `&channel=${encodeURIComponent(channelId)}`;\n return {\n host: this.relay.host,\n port: this.relay.port ?? (tls ? 443 : 80),\n tls,\n // The relay's wss route (`/stream`); buildStreamUrl honors `path`.\n path: this.relay.path ?? SELFHOSTED_RELAY_STREAM_PATH,\n query: routingQuery,\n protocol: kindToProtocol(channel?.kind),\n };\n }\n\n /** Round-trip the persistable state — `{agentId}` ONLY (resume = re-address). */\n async serializeSessionState(): Promise<SelfhostedSessionState> {\n return { agentId: this.agentId };\n }\n}\n\n/**\n * The selfhosted SDK-client surface the registry builds. `backendId:\"selfhosted\"`\n * (the resume-fence field asserted against the descriptor). `create()`/`resume()`\n * return a `SelfhostedSession` bound to `{workspaceId, agentId, controlRpc}`.\n *\n * `create()` and `resume()` are IDENTICAL for selfhosted — there is no box to\n * provision (the machine already exists); both just bind a session to the live\n * subject. `serializeSessionState`/`deserializeSessionState` round-trip\n * `{agentId}` only.\n *\n * The `controlRpc` is constructed LAZILY via an injected factory (defaulting to\n * `NatsControlRpc`); a session built before NATS is configured surfaces\n * `agent_offline` on its first op rather than failing at construction.\n */\nexport class SelfhostedSandboxClient {\n readonly backendId = \"selfhosted\" as const;\n readonly supportsDefaultOptions = false;\n private readonly workspaceId: string;\n private readonly relay: SelfhostedRelayConfig;\n private readonly controlRpcFactory: () => ControlRpc;\n private readonly defaultAgentId: string | undefined;\n private readonly epoch: number | undefined;\n private readonly timeoutMs: number | undefined;\n private readonly environment: Record<string, string> | undefined;\n private readonly workingDir: string | undefined;\n private controlRpcMemo: ControlRpc | undefined;\n\n constructor(opts: {\n workspaceId: string;\n relay: SelfhostedRelayConfig;\n /** Lazily build the ControlRpc (defaults to NatsControlRpc in the provider). */\n controlRpcFactory: () => ControlRpc;\n /** The agentId a bare create()/resume() (no state) binds to. Optional: the\n * resume path supplies it via deserializeSessionState. */\n agentId?: string;\n epoch?: number;\n timeoutMs?: number;\n /** The run's declared sandbox environment, threaded into every bound session's\n * `state.manifest.environment` so the SDK's per-turn manifest-env delta is\n * empty (validateNoEnvironmentDelta). See SelfhostedSessionDeps.environment.\n * Omitted → `{}` (the negotiation-only path; no turn manifest is applied). */\n environment?: Record<string, string>;\n /** The session working directory threaded into every bound session (the path/\n * cwd base; see SelfhostedSessionDeps.workingDir). Omitted/empty ⇒ the default\n * workspace_root behavior. */\n workingDir?: string;\n }) {\n this.workspaceId = opts.workspaceId;\n this.relay = opts.relay;\n this.controlRpcFactory = opts.controlRpcFactory;\n this.defaultAgentId = opts.agentId;\n this.epoch = opts.epoch;\n this.timeoutMs = opts.timeoutMs;\n this.environment = opts.environment;\n this.workingDir = opts.workingDir;\n }\n\n private controlRpc(): ControlRpc {\n if (!this.controlRpcMemo) {\n this.controlRpcMemo = this.controlRpcFactory();\n }\n return this.controlRpcMemo;\n }\n\n private bind(agentId: string): SelfhostedSession {\n return new SelfhostedSession({\n workspaceId: this.workspaceId,\n agentId,\n controlRpc: this.controlRpc(),\n relay: this.relay,\n ...(this.epoch !== undefined ? { epoch: this.epoch } : {}),\n ...(this.timeoutMs !== undefined ? { timeoutMs: this.timeoutMs } : {}),\n ...(this.environment !== undefined ? { environment: this.environment } : {}),\n ...(this.workingDir !== undefined ? { workingDir: this.workingDir } : {}),\n });\n }\n\n /** Bind a session to the live agent subject. There is no box to provision. */\n async create(_manifest?: unknown, _options?: unknown): Promise<SelfhostedSession> {\n const agentId = this.requireAgentId();\n return this.bind(agentId);\n }\n\n /** Resume = re-address the subject. Identical to create — no provider state. */\n async resume(state: SelfhostedSessionState | Record<string, unknown>, _options?: unknown): Promise<SelfhostedSession> {\n const agentId = readAgentId(state) ?? this.requireAgentId();\n return this.bind(agentId);\n }\n\n /** Serialize a live session's state → `{agentId}` ONLY. */\n async serializeSessionState(state: SelfhostedSessionState | { agentId?: string } | unknown): Promise<SelfhostedSessionState> {\n const agentId = readAgentId(state) ?? this.requireAgentId();\n return { agentId };\n }\n\n /** Deserialize `{agentId}` from the persisted envelope. */\n async deserializeSessionState(state: Record<string, unknown>): Promise<SelfhostedSessionState> {\n const agentId = readAgentId(state) ?? this.requireAgentId();\n return { agentId };\n }\n\n /** selfhosted is NOT persistable — there is no owned session state to preserve\n * (the machine is the persistence). The lease never snapshots it. */\n async canPersistOwnedSessionState(): Promise<boolean> {\n return false;\n }\n\n private requireAgentId(): string {\n if (!this.defaultAgentId) {\n throw new Error(\"selfhosted sandbox client: no agentId bound (create()/resume() need a session state carrying agentId)\");\n }\n return this.defaultAgentId;\n }\n}\n\n/**\n * The dependency shape `buildSelfhostedBackendSession` needs to bind a live\n * selfhosted session to a target machine. A structural superset of the fields the\n * routing resolver (backend-resolver.ts) reads off its deps + pointer, and the\n * fields the WORKER turn's machine-primary establish branch threads in — so a\n * SINGLE build shape is shared by both (never two divergent constructions of the\n * same SelfhostedSandboxClient/resume pair).\n */\nexport interface SelfhostedSessionBuild {\n /** The workspace the machine's control-plane subject is scoped to. */\n workspaceId: string;\n /** The enrollment id == the agent id `agent.<ws>.<id>.rpc` addresses. */\n agentId: string;\n /** The relay-URL shape for stream endpoints. */\n relay: SelfhostedRelayConfig;\n /** Lazily build the live ControlRpc (the request-scoped NATS connection). */\n controlRpcFactory: () => ControlRpc;\n /** The lease/active epoch the session is fenced under (echoed on every op). */\n epoch: number;\n /** The run's declared sandbox environment → the session manifest.environment\n * (env-parity; see SelfhostedSessionDeps.environment). */\n environment?: Record<string, string>;\n /** The session working directory (the path/cwd base). Null/absent ⇒ workspace_root. */\n workingDir?: string | null;\n /** Override the control-op timeout (tests). */\n timeoutMs?: number;\n}\n\n/**\n * Build a live selfhosted session bound to a target machine: construct a request-\n * scoped `SelfhostedSandboxClient` (fenced under `epoch`, carrying the run's env +\n * working dir) and `resume()` it (= re-address the live subject — no provider box\n * is created). Returns BOTH the client (the OWNED-sandbox client the turn injects,\n * whose `serializeSessionState` round-trips `{agentId}`) and the live session.\n *\n * Shared by:\n * - the routing resolver (backend-resolver.ts) — a swap target, where only the\n * session is needed; and\n * - the worker turn's machine-primary establish branch — where the client is the\n * owned-sandbox client AND the session is the pinned routing default.\n * Factoring it here keeps the two builds identical (no divergence in the fence\n * epoch, env threading, or working-dir base).\n */\nexport async function buildSelfhostedBackendSession(\n deps: SelfhostedSessionBuild,\n): Promise<{ client: SelfhostedSandboxClient; session: SelfhostedSession }> {\n const client = new SelfhostedSandboxClient({\n workspaceId: deps.workspaceId,\n relay: deps.relay,\n controlRpcFactory: deps.controlRpcFactory,\n agentId: deps.agentId,\n epoch: deps.epoch,\n ...(deps.timeoutMs !== undefined ? { timeoutMs: deps.timeoutMs } : {}),\n ...(deps.environment !== undefined ? { environment: deps.environment } : {}),\n ...(deps.workingDir ? { workingDir: deps.workingDir } : {}),\n });\n const session = await client.resume({ agentId: deps.agentId });\n return { client, session };\n}\n\nfunction readAgentId(state: unknown): string | undefined {\n if (state && typeof state === \"object\") {\n const candidate = (state as { agentId?: unknown }).agentId\n ?? ((state as { providerState?: { agentId?: unknown } }).providerState?.agentId);\n if (typeof candidate === \"string\" && candidate.length > 0) {\n return candidate;\n }\n }\n return undefined;\n}\n\nfunction execResultToChannelA(res: ExecResponse): SelfhostedExecResult {\n const stdout = decoder.decode(res.stdout);\n const stderr = decoder.decode(res.stderr);\n return {\n output: stdout,\n stdout,\n stderr,\n exitCode: res.exitCode,\n };\n}\n\nfunction channelKey(workspaceId: string, agentId: string, port: number): string {\n return `${workspaceId}:${agentId}:${port}`;\n}\n\n/** Single-quote a string for POSIX shell (the editor's delete uses the machine's\n * own `rm`). Mirrors the standard `'…'` quoting with `'\\''` escaping. */\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\n/** Detect an image media type from magic bytes (with a path-extension fallback),\n * mirroring @openai/agents-core's `sniffImageMediaType` so `viewImage` returns the\n * SAME media types the SDK would. Returns undefined for an unrecognized format. */\nfunction sniffImageMediaType(bytes: Uint8Array, path: string): string | undefined {\n if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) return \"image/png\";\n if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return \"image/jpeg\";\n if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38) return \"image/gif\";\n if (\n bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&\n bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50\n ) return \"image/webp\";\n if (bytes[0] === 0x42 && bytes[1] === 0x4d) return \"image/bmp\";\n if (\n (bytes[0] === 0x49 && bytes[1] === 0x49 && bytes[2] === 0x2a && bytes[3] === 0x00) ||\n (bytes[0] === 0x4d && bytes[1] === 0x4d && bytes[2] === 0x00 && bytes[3] === 0x2a)\n ) return \"image/tiff\";\n if (looksLikeSvg(bytes)) return \"image/svg+xml\";\n return mediaTypeFromPath(path);\n}\n\nfunction looksLikeSvg(bytes: Uint8Array): boolean {\n const prefix = decoder.decode(bytes.subarray(0, Math.min(bytes.byteLength, 512))).trimStart().toLowerCase();\n return prefix.startsWith(\"<svg\") || /^<\\?xml[\\s\\S]*<svg/u.test(prefix);\n}\n\nfunction mediaTypeFromPath(path: string): string | undefined {\n const p = path?.trim().toLowerCase() ?? \"\";\n if (p.endsWith(\".png\")) return \"image/png\";\n if (p.endsWith(\".jpg\") || p.endsWith(\".jpeg\")) return \"image/jpeg\";\n if (p.endsWith(\".gif\")) return \"image/gif\";\n if (p.endsWith(\".webp\")) return \"image/webp\";\n if (p.endsWith(\".bmp\")) return \"image/bmp\";\n if (p.endsWith(\".tif\") || p.endsWith(\".tiff\")) return \"image/tiff\";\n if (p.endsWith(\".svg\") || p.endsWith(\".svgz\")) return \"image/svg+xml\";\n return undefined;\n}\n\n/** A random uint64-safe numeric nonce (the wire `PingRequest.nonce` is a uint64,\n * represented as a numeric string by ts-proto). */\nfunction randomNonce(): string {\n // 2^53-safe random integer as a decimal string.\n return String(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));\n}\n\nfunction kindToProtocol(kind: StreamKind | undefined): string {\n switch (kind) {\n case StreamKind.STREAM_KIND_PTY:\n return \"pty\";\n case StreamKind.STREAM_KIND_DESKTOP:\n return \"vnc\";\n default:\n return \"raw\";\n }\n}\n\n/**\n * The selfhosted NotFound discriminator — THE load-bearing safety property\n * (dossier §10.2/§19): for selfhosted, `agent-offline` (no responder) is NEVER a\n * provider NotFound. A user's real machine is not recreatable; if the lease saw\n * agent-offline as NotFound it would cold-create a RIVAL box (a Modal box) for\n * the user's machine. So this ALWAYS returns FALSE for selfhosted — there is no\n * \"box gone, recreate it\" condition. An OS-level file NotFound is an op-level\n * error the fs layer 404s; it is likewise NOT a session-recreate condition.\n *\n * `establishSandboxSessionFromEnvelope` cold-restores ONLY when the per-backend\n * NotFound discriminator returns true; returning false here guarantees the\n * selfhosted path never cold-creates a rival — the op surfaces agent_offline and\n * the caller backs off / retries.\n */\nexport function isSelfhostedProviderNotFoundError(_error: unknown): false {\n return false;\n}\n","// Bring-your-own-compute: the user's own machine, enrolled via the Rust agent,\n// is reached over the NATS request/reply control plane (the agent subscribes to\n// `agent.<workspace>.<agentId>.rpc`; the subject IS the registry). There is NO\n// provider SDK and NO per-box credential — \"the agent is the box\".\n//\n// M3 ships the REAL `SelfhostedSandboxClient`: its `create()`/`resume()` return a\n// `SelfhostedSession` presenting the structural surface (`exec`/`readFile`/\n// `writeFile`/`resolveExposedPort`/`serializeSessionState`) that Channel-A, the\n// viewer, and computer-use consume unchanged — backed by a `ControlRpc` seam\n// (request/reply encoded via `@opengeni/agent-proto`) instead of a provider SDK.\n// `serializeSessionState`/`deserializeSessionState` round-trip `{agentId}` ONLY:\n// resume = re-address the live subject, never a cold re-create (the machine is\n// not recreatable). The live NATS request/reply transport + Accounts land in M4\n// behind the SAME `ControlRpc`.\n\nimport type { Settings } from \"@opengeni/config\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport {\n NatsControlRpc,\n type ControlRpc,\n type NatsRequestConnection,\n} from \"../selfhosted/control-rpc\";\nimport {\n SelfhostedSandboxClient,\n type SelfhostedRelayConfig,\n} from \"../selfhosted/session\";\nimport type { ProviderRegistration } from \"./types\";\n\n/**\n * Resolve the relay-URL shape config from settings. M8b threads the real relay\n * deployment URL (`OPENGENI_SELFHOSTED_RELAY_URL`, ops-repo IaC) behind this seam:\n * `resolveExposedPort` returns `{host, port, tls, path, query}` so `buildStreamUrl`\n * assembles the relay dial URL. A path-less URL defaults to the relay's `/stream`\n * route; an unconfigured deployment falls back to a placeholder host (the URL shape\n * is still well-formed; the relay is simply unreachable until configured).\n */\nfunction resolveRelayConfig(settings: Settings): SelfhostedRelayConfig {\n const raw = settings.selfhostedRelayUrl?.trim();\n if (!raw) {\n return { host: \"relay.opengeni.local\", port: 443, tls: true, path: \"/stream\" };\n }\n try {\n const url = new URL(raw.includes(\"://\") ? raw : `wss://${raw}`);\n const tls = url.protocol === \"wss:\" || url.protocol === \"https:\";\n const port = url.port ? Number(url.port) : tls ? 443 : 80;\n const path = url.pathname && url.pathname !== \"/\" ? url.pathname : \"/stream\";\n return { host: url.hostname, port, tls, path };\n } catch {\n return { host: raw, port: 443, tls: true, path: \"/stream\" };\n }\n}\n\n/**\n * The default `ControlRpc` factory for a registry-built client: a\n * `NatsControlRpc` whose connection factory returns null — there is NO live NATS\n * connection wired into the agent-loop-free runtime leaf at build() time (the\n * API/worker inject the live `@opengeni/events` connection per request in M4).\n * A null connection surfaces `agent_offline` on every op rather than throwing at\n * construction, so boot never requires a live NATS — exactly the M3 ruling.\n */\nfunction defaultControlRpcFactory(): ControlRpc {\n return new NatsControlRpc(async (): Promise<NatsRequestConnection | null> => null);\n}\n\nexport const selfhostedProvider: ProviderRegistration = {\n backend: \"selfhosted\",\n descriptor: CAPABILITY_DESCRIPTORS.selfhosted,\n /**\n * No per-box credentials: the machine is reached over the agent's own\n * enrollment. The enrollment-signing + relay-token secrets are deployment-level\n * config that lands with the connectivity/enrollment milestones (M4/M5) — and\n * the whole feature is gated by a `sandboxSelfhostedEnabled` flag (default off)\n * that does not yet exist in Settings. So validation is LENIENT (no-op) in M3:\n * boot must never break, and there is nothing per-box to validate. M4/M5 add\n * the (flag-gated) signing/relay presence checks here behind the same seam.\n */\n validateCredentials() {},\n /**\n * Build the registry client. `create()`/`resume()` bind a `SelfhostedSession`\n * to the agent subject; the per-request `{workspaceId, agentId, controlRpc}`\n * are supplied by the resume path (the lease's enrollment) — the registry\n * client carries the relay config + the default (offline-until-M4) ControlRpc\n * factory and a backendId-correct surface for `assertProviderRegistryInvariants`.\n */\n build({ settings }) {\n return new SelfhostedSandboxClient({\n // The workspaceId is bound per-request by the resume path (the API/worker\n // construct a request-scoped client with the lease's workspace + a live\n // ControlRpc). The registry-built client is the boot/assertion shape; an\n // empty workspaceId is fine until a session is bound with a real one.\n workspaceId: \"\",\n relay: resolveRelayConfig(settings),\n controlRpcFactory: defaultControlRpcFactory,\n });\n },\n};\n","import { VercelSandboxClient } from \"@openai/agents-extensions/sandbox/vercel\";\nimport { CAPABILITY_DESCRIPTORS } from \"../capabilities\";\nimport { SandboxConfigError } from \"../errors\";\nimport type { ProviderRegistration } from \"./types\";\n\nexport const vercelProvider: ProviderRegistration = {\n backend: \"vercel\",\n descriptor: CAPABILITY_DESCRIPTORS.vercel,\n validateCredentials(settings) {\n // Vercel needs the access token + the project/team it scopes to.\n if (!settings.vercelToken) {\n throw new SandboxConfigError(\"vercel\", \"OPENGENI_VERCEL_TOKEN is required\");\n }\n if (!settings.vercelProjectId) {\n throw new SandboxConfigError(\"vercel\", \"OPENGENI_VERCEL_PROJECT_ID is required\");\n }\n },\n build({ settings, environment, exposedPorts }) {\n const options: NonNullable<ConstructorParameters<typeof VercelSandboxClient>[0]> = {\n token: settings.vercelToken!,\n projectId: settings.vercelProjectId!,\n env: environment,\n exposedPorts,\n };\n if (settings.vercelTeamId) options.teamId = settings.vercelTeamId;\n if (settings.vercelRuntime) options.runtime = settings.vercelRuntime;\n return new VercelSandboxClient(options);\n },\n};\n","// Backend selection + capability negotiation/degradation (module 03 §0, §5).\n//\n// negotiateCapabilities() turns a static CapabilityDescriptor + runtime context\n// (the selected OS, the lease liveness/epoch, and the deployment's desktop\n// policy) into a coherent SessionCapabilities document. The load-bearing rule\n// (master-spine Part D): a capability cell is ALWAYS present — when unavailable\n// it is `available:false` + a typed `reason`, NEVER absent. Degradation is a\n// value, not a silent drop.\n\nimport {\n CAPABILITY_DESCRIPTORS,\n type CapabilityDescriptor,\n type CapabilityUnavailableReason,\n type SandboxBackend,\n type SandboxOs,\n type SessionCapabilities,\n} from \"@opengeni/contracts\";\n\nexport interface NegotiationContext {\n sessionId: string;\n backend: SandboxBackend;\n os: SandboxOs;\n /** Current lease liveness; cold means nothing is provisioned yet. */\n liveness: \"cold\" | \"warming\" | \"warm\" | \"draining\";\n /** The lease epoch echoed on viewer heartbeats (the split-brain fence). */\n leaseEpoch: number;\n /** The deployment desktop toggle (settings.sandboxDesktopEnabled). */\n desktopEnabled: boolean;\n /**\n * The HUMAN take-control toggle (settings.sandboxDesktopInteractive). When true\n * (default) and the desktop cell is available, the negotiated DesktopStream.mode\n * is \"interactive\" — the noVNC viewer can drive mouse+keyboard into :0 (the box's\n * x11vnc runs without -viewonly). When false the cell reports mode \"read-only\"\n * and the client disables the \"Take control\" affordance (a genuinely read-only\n * deployment). Independent of `computerUseReadOnly`, which gates the AGENT\n * driver, not the human viewer plane. Defaults to true so a caller that never\n * threads it (e.g. headless tests) still gets the interactive plane when the\n * desktop is available.\n */\n desktopInteractive?: boolean;\n /** The deployment computer-use toggle (settings.computerUseEnabled). The agent\n * drives :0 via xdotool/scrot; availability tracks desktop. Defaults to true. */\n computerUseEnabled?: boolean;\n /** Whether the agent computer-use driver is gated to no-op input\n * (settings.computerUseReadOnly). v1 default false (the agent clicks/types). */\n computerUseReadOnly?: boolean;\n /**\n * Whether a scoped-stream-token secret is resolvable (I8/OD-8). When desktop\n * is enabled but this is false (no streamTokenSecret AND no delegationSecret),\n * the desktop plane GRACEFULLY DEGRADES to transport:null — the deployment\n * boots, but the pixel plane cannot mint scoped tokens. Defaults to true so a\n * caller that never threads it (e.g. headless tests) is unaffected.\n */\n streamTokenSecretAvailable?: boolean;\n /** Whether the calling principal has acknowledged the un-redacted desktop\n * pixels (and, for a shared box, the shared-exposure disclosure). When the\n * box is shared this must be the SHARED acknowledgment; a bare un-redacted ack\n * does not satisfy a shared box. */\n desktopAcknowledged?: boolean;\n /** True when the box's group has >1 session: watching this desktop also shows\n * the sibling sessions' agents on the one :0 framebuffer (addendum E.1). */\n shared?: boolean;\n /** The OTHER sessions whose agents may appear on the shared desktop — IDS\n * ONLY, never their conversation/metadata (stress g). Empty for a solo box. */\n sharedSessionIds?: string[];\n /**\n * The minted pixel-plane endpoint (P4.2): the direct-to-provider WS URL + the\n * scoped stream token + its expiry + the framebuffer geometry. Threaded by the\n * API-direct handshake AFTER it has resumed the box, ensured the display stack,\n * and resolved the provider tunnel. When ABSENT (the negotiation-only read, a\n * cold lease, or a degraded desktop) the DesktopStream cell reports url/token/\n * expiresAt as null — the capability is advertised, the live address is not yet\n * minted (the caller POSTs to /viewers to mint it). Presence does NOT override\n * the gates: a degraded/cold/unacked desktop still reports transport:null and\n * the minted endpoint is dropped.\n */\n desktopStream?: {\n url: string;\n token: string;\n expiresAt: string;\n resolution: [number, number];\n };\n /** The deployment terminal toggle (settings.sandboxTerminalEnabled). The REAL\n * PTY (ttyd pty-ws) is gated on this + a real-PTY backend; when off the\n * Terminal cell still advertises the read-only sse-events firehose. Defaults to\n * true so a caller that never threads it is unaffected. */\n terminalEnabled?: boolean;\n /**\n * The minted terminal-plane endpoint (P5.t): the direct-to-provider ttyd\n * PTY-over-websocket URL + the scoped stream token + its expiry. Threaded by the\n * API-direct handshake AFTER it has resumed the box, ensured the terminal\n * server, and resolved the provider tunnel (mintTerminalStream) — SYMMETRIC with\n * `desktopStream`. When ABSENT (the negotiation-only read, a cold lease, or a\n * degraded terminal) the Terminal cell reports url/token/expiresAt as null and\n * falls back to transport \"sse-events\" (the read-only firehose) — the caller\n * POSTs to /viewers to mint the live pty-ws address.\n */\n terminalStream?: {\n url: string;\n token: string;\n expiresAt: string;\n };\n /** Override the negotiation clock (tests). */\n now?: Date;\n}\n\n/**\n * Resolve the descriptor for a backend. Throws on an unknown backend rather than\n * returning a half-formed default (the registry is the single source of truth).\n */\nexport function selectBackend(backend: SandboxBackend): CapabilityDescriptor {\n const descriptor = CAPABILITY_DESCRIPTORS[backend];\n if (!descriptor) {\n throw new Error(`Unknown sandbox backend \"${backend}\"`);\n }\n return descriptor;\n}\n\n/** True iff the descriptor lists the requested OS as supported. */\nexport function backendSupportsOs(descriptor: CapabilityDescriptor, os: SandboxOs): boolean {\n return descriptor.os.supported.includes(os);\n}\n\n/**\n * True iff the backend can serve the Channel-B desktop pixel plane at all — i.e.\n * its static descriptor advertises DesktopStream as available. The gate the\n * worker / API use before launching the display stack (so a headless-only\n * backend like cloudflare/vercel/none never tries). This is the STATIC\n * feasibility only; the runtime `sandboxDesktopEnabled` policy toggle and the\n * stream-token-secret gate are layered on by the caller / negotiateCapabilities.\n *\n * Accepts EITHER the SandboxBackend enum value (e.g. \"local\") OR the SDK\n * client backendId (e.g. \"unix_local\") — they diverge for the local backend —\n * so a caller holding only `established.backendId` resolves correctly.\n */\nexport function desktopCapableBackend(backend: SandboxBackend | string): boolean {\n const direct = CAPABILITY_DESCRIPTORS[backend as SandboxBackend];\n if (direct) {\n return direct.capabilities.DesktopStream.available === true;\n }\n // Fall back to a backendId lookup (the SDK client id, which differs from the\n // enum key for `local`/`unix_local`).\n for (const descriptor of Object.values(CAPABILITY_DESCRIPTORS)) {\n if (descriptor.backendId === backend) {\n return descriptor.capabilities.DesktopStream.available === true;\n }\n }\n return false;\n}\n\n/**\n * Negotiate a coherent SessionCapabilities document for (backend, os). Every\n * capability is reported with availability + a reason-when-unavailable; nothing\n * is ever absent. The reason precedence is: os_unsupported (the OS axis can't be\n * served at all) > the per-capability static feasibility > policy/liveness gates.\n */\nexport function negotiateCapabilities(ctx: NegotiationContext): SessionCapabilities {\n const descriptor = selectBackend(ctx.backend);\n const osSupported = backendSupportsOs(descriptor, ctx.os);\n const negotiatedAt = (ctx.now ?? new Date()).toISOString();\n\n // The dominant degrade: an unsupported OS knocks out every capability with a\n // single coherent reason.\n const osReason: CapabilityUnavailableReason | null = osSupported ? null : \"os_unsupported\";\n\n const fileSystem = (() => {\n if (osReason) {\n return { available: false, readOnly: true, root: descriptor.workspaceRoot, pathSep: \"/\" as const, treeMode: \"lazy\" as const, reason: osReason };\n }\n const cap = descriptor.capabilities.FileSystem;\n return {\n available: cap.available,\n readOnly: cap.readOnly,\n root: descriptor.workspaceRoot,\n pathSep: \"/\" as const,\n treeMode: \"lazy\" as const,\n reason: cap.available ? null : (\"backend_unsupported\" as const),\n };\n })();\n\n const terminal = (() => {\n const cap = descriptor.capabilities.Terminal;\n if (osReason) {\n return { transport: null, ptyCapable: false, shell: \"/bin/bash\", url: null, token: null, expiresAt: null, reason: osReason };\n }\n if (!cap.available) {\n return { transport: null, ptyCapable: false, shell: \"/bin/bash\", url: null, token: null, expiresAt: null, reason: \"backend_unsupported\" as const };\n }\n // The REAL PTY (ttyd pty-ws) rides the SAME tunnel as the desktop, so it is\n // gated identically: a real-PTY backend (cap.pty), the terminal policy toggle\n // ON, and a live box. Until those hold the cell advertises the read-only\n // sse-events firehose (Channel-A command.output still works) with a typed\n // reason — degradation is a value, never an absent capability.\n // - terminal off -> sse-events + disabled_by_policy.\n // - cold lease + NO mint -> sse-events + lease_cold (no live pty-ws address;\n // the caller mints it via mintTerminalStream at\n // viewer attach).\n // - not a real-PTY backend-> sse-events (no reason; the firehose IS the cap).\n // A PRESENT minted pty-ws url (ctx.terminalStream) is ITSELF proof of liveness:\n // the box (Modal-warm OR selfhosted-online) actually served the ttyd port, so a\n // cold MODAL-GROUP lease liveness must NOT degrade it. lease_cold only fires\n // when nothing was minted. A selfhosted-active session has no warm Modal lease\n // (liveness \"cold\") yet mints a valid RELAY pty-ws cell — honour it.\n const ptyCapable = cap.pty;\n let transport: \"pty-ws\" | \"sse-events\" = ptyCapable ? \"pty-ws\" : \"sse-events\";\n let reason: CapabilityUnavailableReason | null = null;\n if (ptyCapable && ctx.terminalEnabled === false) {\n transport = \"sse-events\";\n reason = \"disabled_by_policy\";\n } else if (ptyCapable && ctx.liveness === \"cold\" && !ctx.terminalStream) {\n transport = \"sse-events\";\n reason = \"lease_cold\";\n }\n // The minted pty-ws endpoint is folded in ONLY when the terminal is actually\n // serving pty-ws (the gates passed). When absent the cell advertises the\n // capability with a null live address — the caller mints it via POST /viewers.\n const minted = transport === \"pty-ws\" ? ctx.terminalStream : undefined;\n return {\n transport,\n ptyCapable,\n shell: \"/bin/bash\",\n url: minted?.url ?? null,\n token: minted?.token ?? null,\n expiresAt: minted?.expiresAt ?? null,\n reason,\n };\n })();\n\n const git = (() => {\n const cap = descriptor.capabilities.Git;\n if (osReason) {\n return { available: false, repos: [], reason: osReason };\n }\n return { available: cap.available, repos: [], reason: cap.available ? null : (\"backend_unsupported\" as const) };\n })();\n\n const desktop = (() => {\n const cap = descriptor.capabilities.DesktopStream;\n // Reason precedence: OS > backend-tier feasibility > policy disable >\n // stream-token-secret > cold lease WITHOUT a mint.\n let reason: CapabilityUnavailableReason | null = null;\n let available = cap.available;\n if (osReason) {\n available = false;\n reason = osReason;\n } else if (!cap.available) {\n available = false;\n // Headless tiers expose the typed tier_headless reason; dev/none are\n // backend_unsupported for desktop.\n reason = descriptor.tier === \"headless\" ? \"tier_headless\" : \"backend_unsupported\";\n } else if (!ctx.desktopEnabled) {\n available = false;\n reason = \"disabled_by_policy\";\n } else if (ctx.streamTokenSecretAvailable === false) {\n // Graceful degrade (I8/OD-8): desktop is enabled + backend-capable, but no\n // stream-token secret is resolvable, so no scoped token can be minted. The\n // deployment boots; the desktop cell reports transport:null + a typed\n // reason rather than crashing the API.\n available = false;\n reason = \"disabled_by_policy\";\n } else if (ctx.liveness === \"cold\" && !ctx.desktopStream) {\n // A PRESENT minted pixel url (ctx.desktopStream) is ITSELF proof of liveness:\n // the box (Modal-warm OR selfhosted-online) actually served the noVNC port,\n // so a cold MODAL-GROUP lease liveness must NOT degrade it. lease_cold only\n // fires when nothing was minted. A selfhosted-active session has no warm\n // Modal lease (liveness \"cold\") yet mints a valid RELAY framebuffer cell —\n // honour it (the un-redacted-pixel ack gate below still applies).\n available = false;\n reason = \"lease_cold\";\n }\n const shared = available ? Boolean(ctx.shared) : false;\n // The minted pixel endpoint is handed out ONLY when the desktop is actually\n // available (the gates passed) AND acknowledged: an unacked/cold/degraded\n // desktop never leaks a live URL (the un-redacted-pixel consent gate). When\n // absent the cell advertises the capability with a null live address — the\n // caller mints it via POST /viewers.\n const acknowledged = available ? Boolean(ctx.desktopAcknowledged) : false;\n const minted = available && acknowledged ? ctx.desktopStream : undefined;\n // Human take-control: the cell is \"interactive\" when the desktop is actually\n // available AND the deployment's take-control policy is on (default true). The\n // box's x11vnc runs without -viewonly, so a viewer driving input reaches :0;\n // this mode bit is the CLIENT gate (the \"Take control\" affordance). A\n // deployment that wants a genuinely read-only desktop sets\n // sandboxDesktopInteractive=false → mode \"read-only\" and the client disables\n // take-control. An unavailable cell is always \"read-only\" (nothing to drive).\n // Selfhosted desktop is the RELAY framebuffer: PNG-per-frame protobuf datagrams\n // spliced over the relay, rendered by the \"frames\" canvas client — NOT noVNC/RFB\n // (that x11vnc path exists only for Modal boxes). It is VIEW-ONLY in v1 (the\n // frame client does not forward input yet), so its mode is always read-only\n // regardless of the take-control policy.\n const selfhostedFrames = ctx.backend === \"selfhosted\";\n const interactive = available && !selfhostedFrames && ctx.desktopInteractive !== false;\n const mode = interactive ? (\"interactive\" as const) : (\"read-only\" as const);\n return {\n transport: available ? (selfhostedFrames ? (\"relay-frames\" as const) : cap.transport) : null,\n client: available ? (selfhostedFrames ? (\"frames\" as const) : (\"novnc\" as const)) : null,\n mode,\n url: minted?.url ?? null,\n token: minted?.token ?? null,\n expiresAt: minted?.expiresAt ?? null,\n resolution: minted?.resolution ?? ([1024, 768] as [number, number]),\n // Desktop pixels are ALWAYS un-redacted when present (the literal\n // framebuffer); the acknowledgment gate rests on this.\n unredacted: true,\n requiresAcknowledgment: available,\n acknowledged: available ? Boolean(ctx.desktopAcknowledged) : false,\n // Shared-exposure disclosure (addendum E.1): `shared` when the group has\n // >1 session; `sharedSessionIds` is the OTHER sessions' ids ONLY (never\n // their conversation/metadata). Empty/false for a solo box or when the\n // desktop cell is unavailable.\n shared,\n sharedSessionIds: shared ? (ctx.sharedSessionIds ?? []) : [],\n reason,\n };\n })();\n\n const recording = (() => {\n const cap = descriptor.capabilities.Recording;\n if (osReason) {\n return { available: false, modes: [] as (\"manual\" | \"on-turn\" | \"on-verify\")[], codecs: [] as (\"h264-mp4\" | \"vp9-webm\")[], reason: osReason };\n }\n if (!cap.available) {\n return { available: false, modes: [] as (\"manual\" | \"on-turn\" | \"on-verify\")[], codecs: [] as (\"h264-mp4\" | \"vp9-webm\")[], reason: descriptor.tier === \"headless\" ? (\"tier_headless\" as const) : (\"backend_unsupported\" as const) };\n }\n // Recording feasibility tracks desktop; policy-gate it the same way.\n if (!ctx.desktopEnabled) {\n return { available: false, modes: [] as (\"manual\" | \"on-turn\" | \"on-verify\")[], codecs: [] as (\"h264-mp4\" | \"vp9-webm\")[], reason: \"disabled_by_policy\" as const };\n }\n return {\n available: true,\n modes: [\"manual\", \"on-turn\", \"on-verify\"] as (\"manual\" | \"on-turn\" | \"on-verify\")[],\n codecs: [\"h264-mp4\", \"vp9-webm\"] as (\"h264-mp4\" | \"vp9-webm\")[],\n reason: null,\n };\n })();\n\n const computerUse = (() => {\n // The agent computer-use driver requires the same desktop image (X stack) as\n // the pixel plane: it drives :0 with xdotool/scrot. Availability == desktop-\n // capable backend && desktopEnabled && computerUseEnabled. Degradation is a\n // value, never silent (an unavailable cell carries a reason).\n const desktopCapable = descriptor.capabilities.DesktopStream.available;\n const readOnly = ctx.computerUseReadOnly ?? false;\n if (osReason) {\n return { available: false, readOnly, reason: osReason };\n }\n if (!desktopCapable) {\n return { available: false, readOnly, reason: descriptor.tier === \"headless\" ? (\"tier_headless\" as const) : (\"backend_unsupported\" as const) };\n }\n if (!ctx.desktopEnabled || ctx.computerUseEnabled === false) {\n return { available: false, readOnly, reason: \"disabled_by_policy\" as const };\n }\n return { available: true, readOnly, reason: null };\n })();\n\n return {\n sessionId: ctx.sessionId,\n backend: ctx.backend,\n os: ctx.os,\n liveness: ctx.liveness,\n leaseEpoch: ctx.leaseEpoch,\n viewerHeartbeatIntervalMs: 30_000,\n FileSystem: fileSystem,\n Terminal: terminal,\n Git: git,\n DesktopStream: desktop,\n Recording: recording,\n ComputerUse: computerUse,\n negotiatedAt,\n };\n}\n","// @opengeni/runtime/sandbox — scoped stream-token mint/verify (P3.1).\n//\n// The agent-loop-free home for the scoped data-plane stream token used by the\n// desktop pixel plane (Channel B, master-spine §C.3 / crosscut PART 1.3). It is\n// a THIN wrapper over the contracts HMAC envelope (signStreamToken /\n// verifyStreamToken) — NOT a second crypto: it REUSES the exact base64Url +\n// hmacSha256 construction that backs signDelegatedAccessToken, with the distinct\n// `ogs_` prefix and the hard-narrow StreamTokenPayload claim set.\n//\n// It lives under @opengeni/runtime/sandbox so the API-direct control plane\n// (apps/api) can mint + verify stream tokens from the same single agent-loop-free\n// leaf it already pulls createSandboxClient / resume-by-id from.\n\nimport {\n DESKTOP_STREAM_PORT,\n StreamTokenPayload,\n signStreamToken,\n verifyStreamToken as verifyStreamTokenEnvelope,\n type StreamTokenPayload as StreamTokenPayloadType,\n} from \"@opengeni/contracts\";\n\n// The default stream-token TTL (seconds). The token is short-lived by design:\n// URL rotation is event-driven under the epoch fence (re-resolve recorded on the\n// lease), not on a keepalive clock — so the token never needs a long life.\nexport const STREAM_TOKEN_DEFAULT_TTL_SECONDS = 120;\n\nexport type MintStreamTokenInput = {\n workspaceId: string;\n sessionId: string;\n /** The sandbox_lease_holders viewer row id. */\n viewerId: string;\n /** The epoch the token is fenced to. For a Modal box this is the live LEASE\n * epoch (re-minted on box rollover). For a SELFHOSTED relay stream (M8b) this is\n * the session's swap `active_epoch`: the relay tracks the highest epoch any\n * viewer presented per channel and REJECTS a token with a lower epoch, so a\n * viewer whose token predates a swap-away cannot reach the machine the session\n * swapped off of. One field, two fences — the relay/in-box edge reads it as the\n * stale-viewer floor either way. */\n leaseEpoch: number;\n /** v1 is always \"view\"; \"control\" is the never-granted raw-input plane. */\n mode?: \"view\" | \"control\";\n /** The exposed stream port (noVNC); defaults to 6080. */\n port?: number;\n /** TTL in seconds; defaults to STREAM_TOKEN_DEFAULT_TTL_SECONDS. */\n ttlSeconds?: number;\n /** Override the issue clock (tests). Seconds since the epoch. */\n nowSeconds?: number;\n};\n\n/**\n * Mint a scoped stream token for one viewer holder. Builds the hard-narrow\n * StreamTokenPayload (the claim set the in-box edge / control plane validates)\n * and signs it with the resolved stream-token secret via the contracts HMAC\n * envelope (`ogs_` prefix). The token is RECORDED against the holder row by the\n * caller and is NEVER appended to the data-plane URL as a query param.\n */\nexport async function mintStreamToken(secret: string, input: MintStreamTokenInput): Promise<string> {\n const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1000);\n const ttlSeconds = input.ttlSeconds ?? STREAM_TOKEN_DEFAULT_TTL_SECONDS;\n const payload: StreamTokenPayloadType = StreamTokenPayload.parse({\n workspaceId: input.workspaceId,\n sessionId: input.sessionId,\n viewerId: input.viewerId,\n leaseEpoch: input.leaseEpoch,\n mode: input.mode ?? \"view\",\n port: input.port ?? DESKTOP_STREAM_PORT,\n exp: nowSeconds + ttlSeconds,\n });\n return signStreamToken(secret, payload);\n}\n\n/**\n * Verify a scoped stream token. Returns the parsed claims on success, or null on\n * a bad prefix / malformed envelope / bad HMAC signature / schema-invalid claims\n * / expiry. Re-exports the contracts verify; the leaf is the agent-loop-free\n * import surface the API uses.\n *\n * The epoch fence (claim.leaseEpoch vs the LIVE lease epoch) and the\n * workspace+session scope are enforced at USE by the caller against the live\n * lease + route params — verify proves authenticity + freshness only.\n */\nexport async function verifyStreamToken(\n secret: string,\n token: string,\n nowSeconds = Math.floor(Date.now() / 1000),\n): Promise<StreamTokenPayloadType | null> {\n return verifyStreamTokenEnvelope(secret, token, nowSeconds);\n}\n\nexport { StreamTokenPayload, type StreamTokenPayload as StreamTokenPayloadType } from \"@opengeni/contracts\";\n","// @opengeni/runtime/sandbox — the desktop display-stack launcher (P4.1).\n//\n// The agent-loop-free home for `ensureDisplayStack`: the exec-launched,\n// flock-idempotent procedure that brings up the Channel-B pixel stack\n// (Xvfb :0 -> XFCE -> x11vnc -> websockify:6080 -> noVNC) on a live,\n// externally-owned box. It is driven over the box's `exec`/`execCommand` channel\n// (NOT a container CMD) so it re-establishes after a snapshot rollover / box\n// re-election, and it is safe to call from the API on a viewer op OR from the\n// agent turn — a second concurrent call serializes on the in-box flock and\n// no-ops when the stack is already up.\n//\n// It lives under @opengeni/runtime/sandbox so the API-direct control plane\n// (apps/api) and the worker (apps/worker) both pull it from the same single\n// agent-loop-free leaf.\n//\n// Productionized from the PROVEN spike (spikes/desktop-stack PASSED locally:\n// noVNC 200, WS 101 + RFB banner, OCR'd a secret off the framebuffer) + the\n// gVisor harness (V2 PASSED live on Modal: XTEST input read-back under runsc).\n\nimport { DESKTOP_STREAM_PORT } from \"@opengeni/contracts\";\n\n// Re-export under the canonical name the module spec uses (STREAM_PORT) while\n// keeping DESKTOP_STREAM_PORT as the single source of truth (contracts).\nexport { DESKTOP_STREAM_PORT };\nexport const STREAM_PORT = DESKTOP_STREAM_PORT;\n\n// The whole-stack launch is bounded by the readiness gates inside the script\n// (four loops of 50 * 0.1s = ~5s each, ~20s worst case) PLUS first-boot XFCE/dbus\n// + font-cache warm-up on a cold gVisor box. 60s gives headroom over the spike's\n// observed ~5-10s warm path without masking a genuine wedge.\nexport const DISPLAY_STACK_TIMEOUT_MS = 60_000;\n\n/** Desktop geometry for the framebuffer. v1 has no live RANDR: a resolution\n * change is a full down -> up restart (a separate op). */\nexport type DesktopGeometry = {\n width: number; // default 1280\n height: number; // default 800\n dpi: number; // default 96\n};\n\nexport const DEFAULT_DESKTOP_GEOMETRY: DesktopGeometry = { width: 1280, height: 800, dpi: 96 };\n\n/** Thrown when a stage of the launch script failed. exitCode 11/12/13 map to\n * Xvfb / x11vnc / websockify respectively (the stage that died). Degradation is\n * surfaced as a value to viewers by the caller; this error is for diagnostics. */\nexport class DisplayStackError extends Error {\n readonly exitCode: number;\n readonly stage: \"xvfb\" | \"x11vnc\" | \"websockify\" | \"unknown\";\n\n constructor(exitCode: number, output: string) {\n const stage =\n exitCode === 11 ? \"xvfb\" : exitCode === 12 ? \"x11vnc\" : exitCode === 13 ? \"websockify\" : \"unknown\";\n super(`desktop display stack failed at stage \"${stage}\" (exit ${exitCode})${output ? `:\\n${output}` : \"\"}`);\n this.name = \"DisplayStackError\";\n this.exitCode = exitCode;\n this.stage = stage;\n }\n}\n\n/** Thrown when the provider session cannot run commands (a headless-only\n * backend with neither `exec` nor `execCommand`). The desktop tier degrades to\n * Channel-A-only — the caller maps this to `DesktopStream.transport: null`. */\nexport class DisplayStackUnsupportedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DisplayStackUnsupportedError\";\n }\n}\n\n// The structural slice of a provider session we need: run a command (preferring\n// `exec` for the structured exit code, falling back to `execCommand`).\ntype ExecResultLike = {\n output?: string;\n stdout?: string;\n stderr?: string;\n exitCode?: number | null;\n};\ntype ExecCapableSession = {\n exec?: (args: { cmd: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<ExecResultLike>;\n execCommand?: (args: { cmd: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<string>;\n};\n\nexport type EnsureDisplayStackOptions = {\n geometry?: DesktopGeometry;\n /** The exposed stream port; defaults to 6080. */\n port?: number;\n /** Per-exec timeout; defaults to DISPLAY_STACK_TIMEOUT_MS. */\n timeoutMs?: number;\n};\n\nexport type EnsureDisplayStackResult = {\n /** The exposed port the stack listens on (websockify/noVNC). */\n port: number;\n geometry: DesktopGeometry;\n /** The raw `OPENGENI_DESKTOP_UP …` marker line, for diagnostics. Never\n * surfaced to viewers. */\n marker: string;\n};\n\n/**\n * Build the shell command that runs the idempotent up-script under an in-box\n * `flock`. The script is shipped in the image at /usr/local/bin/opengeni-desktop-up\n * (the canonical desktop image); we set the geometry/port env and wrap the call\n * in `flock` so two concurrent ensureDisplayStack callers (the API viewer op +\n * the agent turn, both racing after a rollover) serialize without a double\n * launch. The up-script's own per-stage PID guards make the second call a no-op.\n *\n * Exported (pure, side-effect-free) so the ensureDisplayStack unit test can\n * assert the exact command sequence without a live box.\n */\nexport function buildDisplayStackScript(options: EnsureDisplayStackOptions = {}): string {\n const geometry = options.geometry ?? DEFAULT_DESKTOP_GEOMETRY;\n const port = options.port ?? DESKTOP_STREAM_PORT;\n const env =\n `DESKTOP_W=${geometry.width} DESKTOP_H=${geometry.height} ` +\n `DESKTOP_DPI=${geometry.dpi} STREAM_PORT=${port}`;\n // FAST PRE-CHECK (lock-free) before the outer flock: if the exposed port and\n // x11vnc are ALREADY listening, the stack is up — print the marker and short-\n // circuit, so a no-op caller (the agent turn re-ensuring after a viewer attach\n // already brought the stack up) never serializes behind a lock holder and never\n // burns the 45s flock -w timeout. `nc -z` to the two loopback ports is the cheap\n // (sub-millisecond) \"already up?\" signal; on a miss we fall through to the\n // flock-wrapped up-script (which ALSO pre-checks under its own lock).\n //\n // flock -w bounds the wait so a wedged holder can't deadlock the caller; the\n // up-script itself ALSO takes the same lock (belt + braces) so this works even\n // against an older image that predates the wrapper.\n return (\n `if nc -z 127.0.0.1 ${port} >/dev/null 2>&1 && nc -z 127.0.0.1 5900 >/dev/null 2>&1; then ` +\n `echo \"OPENGENI_DESKTOP_UP port=${port} geometry=${geometry.width}x${geometry.height} dpi=${geometry.dpi} (precheck)\"; ` +\n `else ` +\n `mkdir -p /tmp/opengeni-desktop && ` +\n `flock -w 45 /tmp/opengeni-desktop/up.outer.lock ` +\n `env ${env} opengeni-desktop-up; ` +\n `fi`\n );\n}\n\nfunction execResultOutput(result: ExecResultLike | string): string {\n if (typeof result === \"string\") {\n return result;\n }\n return [result.output, result.stderr, result.stdout]\n .filter((v): v is string => typeof v === \"string\" && v.length > 0)\n .join(\"\\n\");\n}\n\nfunction execResultExitCode(result: ExecResultLike | string): number | null {\n if (typeof result === \"string\") {\n return null; // execCommand returns a bare string — no exit code available.\n }\n return typeof result.exitCode === \"number\" ? result.exitCode : null;\n}\n\n// Parse the exit code the up-script signals via its trailing marker. When we ran\n// through `exec` we have the real exitCode; when we only had `execCommand` (a\n// bare string), we infer success from the OPENGENI_DESKTOP_UP marker and infer\n// the failing stage from the stage-failure message the script prints to stderr.\nfunction inferExitFromOutput(output: string): number {\n if (/OPENGENI_DESKTOP_UP\\b/.test(output)) {\n return 0;\n }\n if (/Xvfb failed to come up/.test(output)) {\n return 11;\n }\n if (/x11vnc failed on/.test(output)) {\n return 12;\n }\n if (/websockify failed on/.test(output)) {\n return 13;\n }\n return -1;\n}\n\n/**\n * Idempotently bring up the desktop display stack on the live box. Safe to call\n * N times (the in-box flock + the up-script's PID guards make a second call a\n * no-op). Resolves with the exposed port + geometry on success; throws\n * `DisplayStackError` on a stage failure and `DisplayStackUnsupportedError` when\n * the session cannot run commands.\n *\n * `session` is the externally-owned provider session (the `established.session`\n * from establishSandboxSessionFromEnvelope, or any SandboxSessionLike). We\n * prefer `session.exec` (structured `{exitCode}`) and fall back to\n * `session.execCommand` (bare string), inferring success from the up-script's\n * marker line in the fallback case.\n */\nexport async function ensureDisplayStack(\n session: unknown,\n options: EnsureDisplayStackOptions = {},\n): Promise<EnsureDisplayStackResult> {\n const s = session as ExecCapableSession;\n if (typeof s?.exec !== \"function\" && typeof s?.execCommand !== \"function\") {\n throw new DisplayStackUnsupportedError(\n \"provider session cannot run commands (no exec/execCommand) — desktop tier unavailable\",\n );\n }\n\n const geometry = options.geometry ?? DEFAULT_DESKTOP_GEOMETRY;\n const port = options.port ?? DESKTOP_STREAM_PORT;\n const timeoutMs = options.timeoutMs ?? DISPLAY_STACK_TIMEOUT_MS;\n const cmd = buildDisplayStackScript({ geometry, port });\n\n const result =\n typeof s.exec === \"function\"\n ? await s.exec({ cmd, yieldTimeMs: timeoutMs, maxOutputTokens: 20_000 })\n : await s.execCommand!({ cmd, yieldTimeMs: timeoutMs, maxOutputTokens: 20_000 });\n\n const output = execResultOutput(result);\n const exitCode = execResultExitCode(result) ?? inferExitFromOutput(output);\n\n if (exitCode !== 0) {\n throw new DisplayStackError(exitCode, output);\n }\n\n const marker = (output.match(/OPENGENI_DESKTOP_UP[^\\n]*/) ?? [\"\"])[0];\n return { port, geometry, marker };\n}\n\n/** Tear the stack down (down-script). Best-effort; never throws on a missing\n * process. Used by the geometry-change restart and cold/drain. */\nexport async function tearDownDisplayStack(session: unknown): Promise<void> {\n const s = session as ExecCapableSession;\n if (typeof s?.exec === \"function\") {\n await s.exec({ cmd: \"opengeni-desktop-down\", yieldTimeMs: 10_000, maxOutputTokens: 4_000 });\n return;\n }\n if (typeof s?.execCommand === \"function\") {\n await s.execCommand({ cmd: \"opengeni-desktop-down\", yieldTimeMs: 10_000, maxOutputTokens: 4_000 });\n }\n}\n","// @opengeni/runtime/sandbox — the REAL PTY terminal-server launcher (P5.t).\n//\n// The agent-loop-free home for `ensureTerminalServer`: the exec-launched,\n// flock-idempotent procedure that brings up the ttyd PTY-over-websocket server\n// (a live PTY-backed `bash -l` per ws client, listening on 7681) on a live,\n// externally-owned box. It is the SYMMETRIC TWIN of ensureDisplayStack (the\n// Channel-B pixel stack) — same exec/execCommand channel (NOT a container CMD)\n// so it re-establishes after a snapshot rollover / box re-election, same flock\n// idempotency so a second concurrent caller (the API viewer op + the agent turn,\n// both racing after a rollover) serializes and no-ops when ttyd is already up.\n//\n// WHY A REAL PTY OVER THE TUNNEL (not the old ptyWrite-over-HTTP): the stateless\n// HTTP plane builds a fresh provider session per call, so the in-memory live-PTY\n// process handle (Modal's per-call activeProcesses Map) is gone by the next\n// write (\"session not found: 1\"). ttyd holds the live PTY in-box and streams it\n// over the SAME Modal raw-TLS tunnel the desktop noVNC already uses, gated by the\n// SAME scoped stream-token mechanism (the server records the token; the boundary\n// is the unguessable short-TTL tunnel URL + the recorded token — no in-box gate).\n//\n// It lives under @opengeni/runtime/sandbox so the API-direct control plane\n// (apps/api) and the worker (apps/worker) both pull it from the same single\n// agent-loop-free leaf.\n\nimport { TERMINAL_STREAM_PORT } from \"@opengeni/contracts\";\n\n// Re-export the canonical terminal port so callers (exposeStreamPort, the API\n// mint) pull the single source of truth (contracts) from this leaf.\nexport { TERMINAL_STREAM_PORT };\n\n// The ttyd launch is bounded by the readiness gate inside the up-script (50 *\n// 0.1s = ~5s) PLUS first-boot warm-up on a cold gVisor box. 60s gives headroom\n// over the observed warm path (~1-2s; ttyd is a single static binary) without\n// masking a genuine wedge. Symmetric with DISPLAY_STACK_TIMEOUT_MS.\nexport const TERMINAL_SERVER_TIMEOUT_MS = 60_000;\n\n/** Thrown when the ttyd launch failed inside the box. exitCode 14 maps to the\n * up-script's \"ttyd failed to come up\" stage; any other non-zero is unknown.\n * Degradation is surfaced as a value to clients by the caller (Terminal\n * transport falls back to sse-events / null); this error is for diagnostics. */\nexport class TerminalServerError extends Error {\n readonly exitCode: number;\n readonly stage: \"ttyd\" | \"unknown\";\n\n constructor(exitCode: number, output: string) {\n const stage = exitCode === 14 ? \"ttyd\" : \"unknown\";\n super(`terminal server failed at stage \"${stage}\" (exit ${exitCode})${output ? `:\\n${output}` : \"\"}`);\n this.name = \"TerminalServerError\";\n this.exitCode = exitCode;\n this.stage = stage;\n }\n}\n\n/** Thrown when the provider session cannot run commands (a headless-only backend\n * with neither `exec` nor `execCommand`). The terminal tier degrades to the\n * Channel-A sse-events firehose — the caller maps this to a `transport:null`\n * pty-ws (the read-only firehose still works). */\nexport class TerminalServerUnsupportedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TerminalServerUnsupportedError\";\n }\n}\n\n// The structural slice of a provider session we need: run a command (preferring\n// `exec` for the structured exit code, falling back to `execCommand`).\ntype ExecResultLike = {\n output?: string;\n stdout?: string;\n stderr?: string;\n exitCode?: number | null;\n};\ntype ExecCapableSession = {\n exec?: (args: { cmd: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<ExecResultLike>;\n execCommand?: (args: { cmd: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<string>;\n};\n\nexport type EnsureTerminalServerOptions = {\n /** The exposed terminal port; defaults to 7681 (ttyd default). */\n port?: number;\n /** Per-exec timeout; defaults to TERMINAL_SERVER_TIMEOUT_MS. */\n timeoutMs?: number;\n};\n\nexport type EnsureTerminalServerResult = {\n /** The exposed port ttyd listens on (PTY-over-websocket). */\n port: number;\n /** The raw `OPENGENI_TERMINAL_UP …` marker line, for diagnostics. Never\n * surfaced to clients. */\n marker: string;\n};\n\n/**\n * Build the shell command that runs the idempotent up-script under an in-box\n * `flock`. The script is shipped in the image at /usr/local/bin/opengeni-terminal-up\n * (the canonical desktop image, alongside opengeni-desktop-up); we set the port\n * env and wrap the call in `flock` so two concurrent ensureTerminalServer callers\n * (the API viewer op + the agent turn, both racing after a rollover) serialize\n * without a double launch. The up-script's own curl readiness probe makes the\n * second call a no-op.\n *\n * Exported (pure, side-effect-free) so the ensureTerminalServer unit test can\n * assert the exact command sequence without a live box. Mirrors\n * buildDisplayStackScript.\n */\nexport function buildTerminalServerScript(options: EnsureTerminalServerOptions = {}): string {\n const port = options.port ?? TERMINAL_STREAM_PORT;\n // flock -w bounds the wait so a wedged holder can't deadlock the caller; the\n // up-script's curl probe ALSO makes the launch idempotent (belt + braces) so\n // this works even against an older image that predates the wrapper.\n return (\n `mkdir -p /tmp/opengeni-terminal && ` +\n `flock -w 30 /tmp/opengeni-terminal/up.outer.lock ` +\n `env TERMINAL_PORT=${port} opengeni-terminal-up`\n );\n}\n\nfunction execResultOutput(result: ExecResultLike | string): string {\n if (typeof result === \"string\") {\n return result;\n }\n return [result.output, result.stderr, result.stdout]\n .filter((v): v is string => typeof v === \"string\" && v.length > 0)\n .join(\"\\n\");\n}\n\nfunction execResultExitCode(result: ExecResultLike | string): number | null {\n if (typeof result === \"string\") {\n return null; // execCommand returns a bare string — no exit code available.\n }\n return typeof result.exitCode === \"number\" ? result.exitCode : null;\n}\n\n// Parse the exit code the up-script signals via its trailing marker. When we ran\n// through `exec` we have the real exitCode; when we only had `execCommand` (a\n// bare string), we infer success from the OPENGENI_TERMINAL_UP marker and infer\n// the failing stage from the stage-failure message the script prints to stderr.\n// Mirrors inferExitFromOutput (display-stack).\nfunction inferExitFromOutput(output: string): number {\n if (/OPENGENI_TERMINAL_UP\\b/.test(output)) {\n return 0;\n }\n if (/ttyd failed to come up/.test(output)) {\n return 14;\n }\n return -1;\n}\n\n/**\n * Idempotently bring up the ttyd PTY-over-websocket server on the live box. Safe\n * to call N times (the in-box flock + the up-script's curl readiness probe make a\n * second call a no-op). Resolves with the exposed port on success; throws\n * `TerminalServerError` on a launch failure and `TerminalServerUnsupportedError`\n * when the session cannot run commands.\n *\n * `session` is the externally-owned provider session (the `established.session`\n * from establishSandboxSessionFromEnvelope, or any SandboxSessionLike). We prefer\n * `session.exec` (structured `{exitCode}`) and fall back to `session.execCommand`\n * (bare string), inferring success from the up-script's marker line in the\n * fallback case. Mirrors ensureDisplayStack exactly.\n */\nexport async function ensureTerminalServer(\n session: unknown,\n options: EnsureTerminalServerOptions = {},\n): Promise<EnsureTerminalServerResult> {\n const s = session as ExecCapableSession;\n if (typeof s?.exec !== \"function\" && typeof s?.execCommand !== \"function\") {\n throw new TerminalServerUnsupportedError(\n \"provider session cannot run commands (no exec/execCommand) — terminal pty-ws unavailable\",\n );\n }\n\n const port = options.port ?? TERMINAL_STREAM_PORT;\n const timeoutMs = options.timeoutMs ?? TERMINAL_SERVER_TIMEOUT_MS;\n const cmd = buildTerminalServerScript({ port });\n\n const result =\n typeof s.exec === \"function\"\n ? await s.exec({ cmd, yieldTimeMs: timeoutMs, maxOutputTokens: 20_000 })\n : await s.execCommand!({ cmd, yieldTimeMs: timeoutMs, maxOutputTokens: 20_000 });\n\n const output = execResultOutput(result);\n const exitCode = execResultExitCode(result) ?? inferExitFromOutput(output);\n\n if (exitCode !== 0) {\n throw new TerminalServerError(exitCode, output);\n }\n\n const marker = (output.match(/OPENGENI_TERMINAL_UP[^\\n]*/) ?? [\"\"])[0];\n return { port, marker };\n}\n\n/** Tear the terminal server down (down-script). Best-effort; never throws on a\n * missing process. Mirrors tearDownDisplayStack. */\nexport async function tearDownTerminalServer(session: unknown): Promise<void> {\n const s = session as ExecCapableSession;\n if (typeof s?.exec === \"function\") {\n await s.exec({ cmd: \"opengeni-terminal-down\", yieldTimeMs: 10_000, maxOutputTokens: 4_000 });\n return;\n }\n if (typeof s?.execCommand === \"function\") {\n await s.execCommand({ cmd: \"opengeni-terminal-down\", yieldTimeMs: 10_000, maxOutputTokens: 4_000 });\n }\n}\n","// @opengeni/runtime/sandbox — the pixel DATA PLANE: exposeStreamPort (P4.2).\n//\n// This is the heart of Channel B's data plane. exposeStreamPort resolves the\n// provider's scoped tunnel for the ONE exposed stream port (6080), assembles the\n// direct-to-provider WS URL (client → provider-tunnel direct; the pixel socket\n// never traverses OpenGeni), and mints the scoped OpenGeni stream token. It is a\n// plain function over a live, externally-owned `{session}` handle — NO Temporal,\n// NO worker RPC, NO actor. The API-direct handshake handler (apps/api) calls it\n// in-process on a freshly-resumed-by-id box and returns the result as the HTTP\n// response; the worker's per-turn resume path calls the same function when a turn\n// is the first to bring the box up. Both pull it from this single agent-loop-free\n// leaf (@opengeni/runtime/sandbox).\n//\n// THE TOKEN IS NOT A URL QUERY PARAM. The provider's own scoped tunnel URL\n// (Modal raw-TLS host:port, Daytona signed preview, Blaxel preview-token query)\n// carries the reach-the-port boundary; the OpenGeni stream token is RECORDED\n// against the viewer holder and is the in-box websockify edge boundary (P3/P5).\n// Per the master-spine ruling, exposeStreamPort returns the token alongside the\n// URL so the caller records it; it does NOT append it to `url`.\n\nimport { DESKTOP_STREAM_PORT } from \"@opengeni/contracts\";\nimport { mintStreamToken, STREAM_TOKEN_DEFAULT_TTL_SECONDS } from \"./stream-token\";\n\n/** The provider-resolved endpoint for an exposed port. Mirrors the SDK's\n * `ExposedPortEndpoint` (host/port/tls/query/...) WITHOUT importing the\n * agent-loop barrel — the leaf stays agent-loop-free. */\nexport type ExposedPortEndpoint = {\n host: string;\n port: number;\n tls?: boolean;\n query?: string;\n protocol?: string;\n url?: string;\n /** The URL path the socket connects on. Modal/Daytona/Blaxel serve the edge at\n * the root (`/`, the default); the selfhosted relay serves it at `/stream`\n * (M8b). When set, buildStreamUrl uses it instead of the root. */\n path?: string;\n [key: string]: unknown;\n};\n\n/** The structural slice of a provider session we need to resolve a tunnel. */\ntype PortResolvableSession = {\n resolveExposedPort?: (port: number) => Promise<ExposedPortEndpoint>;\n};\n\n/** Thrown when the provider cannot expose the stream port (no resolveExposedPort,\n * or the provider tunnel lookup failed). The caller degrades the desktop cell to\n * `transport:null` (a value, never a crash) — a headless-only provider or a\n * transient tunnel failure must not fail the whole handshake. */\nexport class StreamPortUnavailableError extends Error {\n constructor(message: string, readonly cause?: unknown) {\n super(message);\n this.name = \"StreamPortUnavailableError\";\n }\n}\n\nexport type ExposeStreamPortInput = {\n workspaceId: string;\n sessionId: string;\n /** The sandbox_lease_holders viewer row id the token is scoped to. */\n viewerId: string;\n /** The live lease epoch — the fence the token is pinned to. */\n leaseEpoch: number;\n /** The HMAC secret for the scoped stream token (resolveStreamTokenSecret). */\n streamTokenSecret: string;\n /** The exposed stream port; defaults to 6080. */\n port?: number;\n /** Token TTL in seconds; defaults to STREAM_TOKEN_DEFAULT_TTL_SECONDS. */\n ttlSeconds?: number;\n /** The framebuffer geometry to echo back to the client. */\n resolution?: [number, number];\n /** Override the issue clock (tests). Seconds since the epoch. */\n nowSeconds?: number;\n};\n\nexport type ExposeStreamPortResult = {\n /** The direct-to-provider WS URL the viewer connects to (provider-scoped; the\n * OpenGeni token is NOT appended). */\n url: string;\n /** The scoped OpenGeni stream token — recorded against the holder, NEVER a URL\n * query param. */\n token: string;\n /** ISO absolute expiry of the token (the rotation hot-swap window backstop). */\n expiresAt: string;\n /** The pixel transport the client speaks. */\n transport: \"vnc-ws\";\n /** The reference noVNC client the SDK helper mounts. */\n client: \"novnc\";\n resolution: [number, number];\n leaseEpoch: number;\n};\n\nconst DEFAULT_RESOLUTION: [number, number] = [1280, 800];\n\n/**\n * Assemble the direct-to-provider WS URL from a resolved endpoint. The SDK's\n * `urlForExposedPort(endpoint,'ws')` is the canonical tls-aware, IPv6-bracketing,\n * provider-query-preserving assembler — we reimplement its exact logic here so\n * the leaf stays agent-loop-free (the helper lives behind the bare\n * `@openai/agents-core` root, which the import-discipline test forbids). The\n * provider's own `endpoint.query` (Blaxel `bl_preview_token`, Daytona signed\n * token) is preserved; the OpenGeni token is NOT appended (it is recorded against\n * the holder + validated at the in-box websockify edge).\n */\nexport function buildStreamUrl(endpoint: ExposedPortEndpoint): string {\n if (typeof endpoint.host !== \"string\" || endpoint.host.length === 0 || typeof endpoint.port !== \"number\") {\n throw new StreamPortUnavailableError(\n `provider returned a malformed exposed-port endpoint (host=${String(endpoint.host)}, port=${String(endpoint.port)})`,\n );\n }\n const tls = endpoint.tls ?? false;\n const scheme = tls ? \"wss\" : \"ws\";\n const defaultPort = tls ? 443 : 80;\n // Bracket a bare IPv6 host (urlForExposedPort parity).\n const host = endpoint.host.includes(\":\") && !endpoint.host.startsWith(\"[\") ? `[${endpoint.host}]` : endpoint.host;\n // The path: default the root `/` (Modal/Daytona/Blaxel edge), or the\n // provider-supplied path (the selfhosted relay's `/stream` route, M8b). Always\n // leading-slash-normalized.\n const rawPath = typeof endpoint.path === \"string\" && endpoint.path.length > 0 ? endpoint.path : \"/\";\n const path = rawPath.startsWith(\"/\") ? rawPath : `/${rawPath}`;\n const origin = endpoint.port === defaultPort ? `${scheme}://${host}` : `${scheme}://${host}:${endpoint.port}`;\n const authority = `${origin}${path}`;\n const query = endpoint.query ?? \"\";\n return query ? `${authority}?${query}` : authority;\n}\n\n/**\n * Resolve the provider's scoped tunnel for the stream port and mint the scoped\n * OpenGeni stream token. Returns a coherent `{url, token, expiresAt, transport,\n * client, resolution}` cell the caller records on the lease (data_plane_url) and\n * returns in the DesktopStream handshake.\n *\n * Throws `StreamPortUnavailableError` when the provider session cannot resolve\n * the port (no `resolveExposedPort`, or the tunnel lookup failed) — the caller\n * maps this to a `transport:null` degradation (a value, never a crash).\n */\nexport async function exposeStreamPort(\n session: unknown,\n input: ExposeStreamPortInput,\n): Promise<ExposeStreamPortResult> {\n const s = session as PortResolvableSession;\n const port = input.port ?? DESKTOP_STREAM_PORT;\n if (typeof s?.resolveExposedPort !== \"function\") {\n throw new StreamPortUnavailableError(\n \"provider session cannot resolve exposed ports (no resolveExposedPort) — desktop stream unavailable\",\n );\n }\n\n let endpoint: ExposedPortEndpoint;\n try {\n // (I7/OD-7) per-provider URL re-resolution folds in here: a provider with a\n // preview/signed token (Daytona/Blaxel) re-resolves its own short-TTL token\n // on every call, so a rotation re-mints both planes' freshness in one place.\n endpoint = await s.resolveExposedPort(port);\n } catch (error) {\n throw new StreamPortUnavailableError(\n `provider failed to resolve the stream port ${port}: ${error instanceof Error ? error.message : String(error)}`,\n error,\n );\n }\n\n const url = buildStreamUrl(endpoint);\n const ttlSeconds = input.ttlSeconds ?? STREAM_TOKEN_DEFAULT_TTL_SECONDS;\n const nowSeconds = input.nowSeconds ?? Math.floor(Date.now() / 1000);\n const token = await mintStreamToken(input.streamTokenSecret, {\n workspaceId: input.workspaceId,\n sessionId: input.sessionId,\n viewerId: input.viewerId,\n leaseEpoch: input.leaseEpoch,\n mode: \"view\",\n port,\n ttlSeconds,\n nowSeconds,\n });\n\n return {\n url,\n token,\n expiresAt: new Date((nowSeconds + ttlSeconds) * 1000).toISOString(),\n transport: \"vnc-ws\",\n client: \"novnc\",\n resolution: input.resolution ?? DEFAULT_RESOLUTION,\n leaseEpoch: input.leaseEpoch,\n };\n}\n","// @opengeni/runtime/sandbox — the recording loop (P4.3).\n//\n// ffmpeg x11grab on :0 → mp4/webm artifact on the box → read bytes → PUT to\n// object storage → recording.available. The \"agent films itself proving the fix\"\n// loop: ffmpeg reads exactly the :0 framebuffer the agent's computer-use draws to\n// and the human watches over Channel B (zero projection).\n//\n// These are PLAIN functions over a live, externally-owned session handle — NO\n// Temporal, NO worker RPC, NO actor. They live in the agent-loop-free leaf so the\n// SAME process that already holds the resumed-by-id box (the agent turn's own\n// activity for an on-turn recording, or the API in-process for an off-turn/manual\n// finalize) reads the bytes and PUTs them straight to storage. The bytes go\n// box → process memory → storage PUT and are NEVER serialized as a Temporal\n// activity result — the 256 MB-vs-payload-limit concern dissolves (F10).\n//\n// ── Adversarial-review fixes folded in (module 05 §Adversarial) ──────────────\n// F1 exec is OPTIONAL on Modal (only execCommand) — every command dual-paths.\n// F3 exec/execCommand YIELDS — the SIGINT-and-wait loop is bounded well under\n// the yield window; a direct `base64` exec does the byte transfer.\n// F8 the byte read does NOT assume any over-limit behavior — we cap the ffmpeg\n// file by size on the box first (stat) and fail `max-bytes-exceeded` rather\n// than silently uploading a truncated video.\n// FR the byte transfer is a DIRECT exec (`base64 <abs-path>`), NOT readFile:\n// the recording lives at an absolute /tmp path (never the user's workspace\n// /git tree), and readFile rejects paths outside the manifest workspace\n// root (\"escapes the workspace root\"). The base64 exec passes\n// maxOutputTokens:null so a large recording is never truncated.\n// F9 the box file is deleted ONLY after the storage PUT confirms — never\n// before (so a failed upload leaves the bytes recoverable for a retry).\n// F12 ffmpeg/x11vnc backgrounding does not block the yield (nohup … & echo $!).\n// F14 duration is computed from wall-clock (stop − start), not assumed.\n\nimport { DESKTOP_STREAM_PORT } from \"@opengeni/contracts\";\n\nexport { DESKTOP_STREAM_PORT };\n\nexport type RecordingCodec = \"h264-mp4\" | \"vp9-webm\";\nexport type RecordingContentType = \"video/mp4\" | \"video/webm\";\n\nconst DEFAULT_MAX_SECONDS = 600; // 10 min hard ceiling (the -t bound)\nconst DEFAULT_FRAMERATE = 15;\nconst DEFAULT_MAX_BYTES = 268_435_456; // 256 MB\nconst DEFAULT_DIMENSIONS: [number, number] = [1280, 800];\n// The SIGINT-and-wait loop is bounded well under the command yield window (F3).\nconst STOP_YIELD_MS = 20_000;\nconst EXEC_YIELD_MS = 15_000;\n\nexport function contentTypeForCodec(codec: RecordingCodec): RecordingContentType {\n return codec === \"vp9-webm\" ? \"video/webm\" : \"video/mp4\";\n}\nexport function extForCodec(codec: RecordingCodec): string {\n return codec === \"vp9-webm\" ? \"webm\" : \"mp4\";\n}\n\n/** No exec/execCommand on the session — the box cannot run ffmpeg. */\nexport class RecordingUnavailableError extends Error {\n constructor(message: string) { super(message); this.name = \"RecordingUnavailableError\"; }\n}\n/** ffmpeg failed, the file is missing, or the byte read failed. */\nexport class RecordingError extends Error {\n constructor(message: string, readonly reason: \"ffmpeg-error\" | \"box-death\" | \"max-bytes-exceeded\" | \"display-unavailable\") {\n super(message);\n this.name = \"RecordingError\";\n }\n}\n\n// The structural slice of a provider session the recording loop drives. exec and\n// execCommand are optional (Modal has only execCommand — F1); readFile present on\n// every desktop-capable provider.\ntype ExecResultLike = { output?: string; stdout?: string; stderr?: string; exitCode?: number | null; sessionId?: number };\ntype RecordingSession = {\n exec?: (args: { cmd: string; runAs?: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<ExecResultLike>;\n execCommand?: (args: { cmd: string; runAs?: string; yieldTimeMs?: number; maxOutputTokens?: number }) => Promise<string>;\n readFile?: (args: { path: string; runAs?: string; maxBytes?: number }) => Promise<string | Uint8Array>;\n};\n\nfunction shq(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction resultOutput(result: ExecResultLike | string): string {\n if (typeof result === \"string\") return result;\n return [result.output, result.stderr, result.stdout].filter((v): v is string => typeof v === \"string\" && v.length > 0).join(\"\\n\");\n}\n\n// Default per-command output cap (tokens). The byte-read path overrides this to\n// `null` (no truncation) so a base64-encoded recording is never clipped.\nconst DEFAULT_MAX_OUTPUT_TOKENS = 4_000;\n\nasync function run(\n session: RecordingSession,\n cmd: string,\n runAs?: string,\n yieldTimeMs = EXEC_YIELD_MS,\n maxOutputTokens: number | null = DEFAULT_MAX_OUTPUT_TOKENS,\n): Promise<string> {\n // `maxOutputTokens: null` disables the provider's output truncation entirely\n // (SDK truncateOutput returns the raw text when the cap is nullish).\n const args = { cmd, ...(runAs ? { runAs } : {}), yieldTimeMs, maxOutputTokens } as {\n cmd: string; runAs?: string; yieldTimeMs?: number; maxOutputTokens?: number;\n };\n if (typeof session.exec === \"function\") {\n return resultOutput(await session.exec(args));\n }\n if (typeof session.execCommand === \"function\") {\n return resultOutput(await session.execCommand(args));\n }\n throw new RecordingUnavailableError(\"session cannot run commands (no exec/execCommand) — recording unavailable\");\n}\n\n// Extract the command body from a provider exec banner. Modal's execCommand\n// returns \"<chunk banner>\\nProcess exited…\\nOutput:\\n<body>\"; the body is what\n// follows the last \"Output:\\n\" marker. Plain exec results (no banner) pass\n// through unchanged.\nfunction stripExecBanner(raw: string): string {\n const marker = raw.lastIndexOf(\"\\nOutput:\\n\");\n if (marker >= 0) return raw.slice(marker + \"\\nOutput:\\n\".length);\n if (raw.startsWith(\"Output:\\n\")) return raw.slice(\"Output:\\n\".length);\n return raw;\n}\n\nexport type StartRecordingInput = {\n recordingId: string;\n codec?: RecordingCodec;\n framerate?: number;\n maxSeconds?: number;\n dimensions?: [number, number];\n display?: string; // \":0\"\n runAs?: string;\n tmpDir?: string; // \"/tmp\"\n};\n\nexport type RecordingProcess = {\n recordingId: string;\n codec: RecordingCodec;\n boxPath: string;\n pidFile: string;\n dimensions: [number, number];\n framerate: number;\n /** epoch-ms when ffmpeg was launched (for duration computation, F14). */\n startedAt: number;\n display: string;\n runAs?: string;\n};\n\n/**\n * Launch ffmpeg x11grab on :0 → an mp4/webm file on the box. Backgrounded with\n * `nohup … & echo $!` so the launch returns immediately (F12 — the exec does not\n * block on the recording). A hard `-t <maxSeconds>` ceiling bounds a runaway file\n * across a multi-day turn. Returns the handle the caller carries to stop+finalize.\n */\nexport async function startRecording(session: unknown, input: StartRecordingInput): Promise<RecordingProcess> {\n const s = session as RecordingSession;\n const codec = input.codec ?? \"h264-mp4\";\n const dimensions = input.dimensions ?? DEFAULT_DIMENSIONS;\n const framerate = input.framerate ?? DEFAULT_FRAMERATE;\n const maxSeconds = input.maxSeconds ?? DEFAULT_MAX_SECONDS;\n const display = input.display ?? \":0\";\n const tmp = input.tmpDir ?? \"/tmp\";\n const ext = extForCodec(codec);\n const boxPath = `${tmp}/og-rec-${input.recordingId}.${ext}`;\n const pidFile = `${tmp}/og-rec-${input.recordingId}.pid`;\n const logFile = `${tmp}/og-rec-${input.recordingId}.log`;\n const [w, h] = dimensions;\n const enc = codec === \"vp9-webm\"\n ? `-c:v libvpx-vp9 -b:v 0 -crf 32 -row-mt 1`\n : `-c:v libx264 -preset veryfast -pix_fmt yuv420p -movflags +faststart`;\n const ffmpeg =\n `nohup ffmpeg -hide_banner -loglevel error -f x11grab -draw_mouse 1 -framerate ${framerate} ` +\n `-video_size ${w}x${h} -i ${display}.0 -t ${maxSeconds} ${enc} ${boxPath} ` +\n `</dev/null >${logFile} 2>&1 & echo $! > ${pidFile}`;\n await run(s, `bash -lc ${shq(ffmpeg)}`, input.runAs);\n return {\n recordingId: input.recordingId,\n codec,\n boxPath,\n pidFile,\n dimensions,\n framerate,\n startedAt: Date.now(),\n display,\n ...(input.runAs ? { runAs: input.runAs } : {}),\n };\n}\n\n/**\n * SIGINT ffmpeg (so it writes a clean moov atom / webm trailer) and wait for the\n * pid to exit. Bounded well under the yield window (F3). Idempotent: a missing\n * pid file is a no-op.\n */\nexport async function stopRecording(session: unknown, proc: RecordingProcess): Promise<void> {\n const s = session as RecordingSession;\n const wait = `kill -INT \"$(cat ${proc.pidFile})\" 2>/dev/null; for i in $(seq 1 80); do kill -0 \"$(cat ${proc.pidFile})\" 2>/dev/null || break; sleep 0.1; done`;\n await run(s, `bash -lc ${shq(wait)}`, proc.runAs, STOP_YIELD_MS).catch(() => undefined);\n}\n\nexport type FinalizeRecordingResult = {\n bytes: Uint8Array;\n contentType: RecordingContentType;\n sizeBytes: number;\n durationSeconds: number;\n};\n\n/**\n * Read the finalized recording bytes off the box.\n *\n * TRANSPORT: the bytes are read via a DIRECT exec (`base64 <path>` over stdout),\n * NOT via session.readFile(). The recording artifact lives at an absolute /tmp\n * path on purpose — recordings must never be written inside the user's workspace\n * /git tree — but session.readFile() resolves every path against the manifest\n * workspace root and rejects anything outside it (\"Sandbox path … escapes the\n * workspace root\"), which fataled finalize. Raw exec runs unrestricted shell, so\n * `base64` reads the /tmp file directly; we decode the base64 back to bytes here.\n * The byte-read exec passes `maxOutputTokens: null` so the provider never\n * truncates a large recording's base64.\n *\n * F8: we DO NOT assume any over-limit behavior. First `stat` the file size on the\n * box; if it exceeds maxBytes, fail `max-bytes-exceeded` (never upload a truncated\n * video). Otherwise read the raw bytes.\n *\n * F9: this does NOT delete the box file. The caller deletes it (deleteRecordingArtifacts)\n * ONLY after the storage PUT + `available` commit — so a failed upload leaves the\n * bytes recoverable on the box for a retry.\n *\n * F14: duration is wall-clock (now − startedAt), a close approximation of the\n * SIGINT-flushed video length.\n */\nexport async function readRecordingBytes(session: unknown, proc: RecordingProcess, maxBytes = DEFAULT_MAX_BYTES): Promise<FinalizeRecordingResult> {\n const s = session as RecordingSession;\n if (typeof s.exec !== \"function\" && typeof s.execCommand !== \"function\") {\n throw new RecordingUnavailableError(\"session cannot run commands (no exec/execCommand) — recording finalize unavailable\");\n }\n // F8: size-gate on the box before reading into memory.\n const sizeOut = (await run(s, `bash -lc ${shq(`stat -c %s ${proc.boxPath} 2>/dev/null || echo MISSING`)}`, proc.runAs)).trim();\n const sizeLine = sizeOut.split(\"\\n\").map((l) => l.trim()).filter(Boolean).pop() ?? \"MISSING\";\n if (sizeLine === \"MISSING\" || sizeLine === \"\") {\n throw new RecordingError(`recording file missing on box: ${proc.boxPath}`, \"box-death\");\n }\n const size = Number(sizeLine);\n if (!Number.isFinite(size) || size <= 0) {\n throw new RecordingError(`recording file empty on box: ${proc.boxPath}`, \"ffmpeg-error\");\n }\n if (size > maxBytes) {\n throw new RecordingError(`recording ${size}B exceeds max ${maxBytes}B`, \"max-bytes-exceeded\");\n }\n // Read the bytes via a DIRECT exec (base64, no output truncation), so the\n // absolute /tmp path is NOT run through the workspace-root-scoped readFile guard.\n const STOP_YIELD = 60_000; // a large recording's base64 read may take longer than the default exec yield.\n const encoded = stripExecBanner(\n await run(s, `bash -lc ${shq(`base64 ${proc.boxPath}`)}`, proc.runAs, STOP_YIELD, null),\n );\n const base64 = encoded.replace(/\\s+/g, \"\");\n if (base64.length === 0) {\n throw new RecordingError(`recording read returned 0 bytes: ${proc.boxPath}`, \"ffmpeg-error\");\n }\n let bytes: Uint8Array;\n try {\n bytes = Uint8Array.from(Buffer.from(base64, \"base64\"));\n } catch (error) {\n throw new RecordingError(`recording base64 decode failed: ${error instanceof Error ? error.message : String(error)}`, \"ffmpeg-error\");\n }\n if (bytes.length === 0) {\n throw new RecordingError(`recording read returned 0 bytes: ${proc.boxPath}`, \"ffmpeg-error\");\n }\n return {\n bytes,\n contentType: contentTypeForCodec(proc.codec),\n sizeBytes: bytes.length,\n durationSeconds: Math.max(0, Math.round((Date.now() - proc.startedAt) / 1000)),\n };\n}\n\n/**\n * Delete the box artifacts. F9: call this ONLY after the storage PUT confirmed\n * and the `available` row committed — never before. Best-effort; never throws.\n */\nexport async function deleteRecordingArtifacts(session: unknown, proc: RecordingProcess): Promise<void> {\n const s = session as RecordingSession;\n const logFile = proc.boxPath.replace(/\\.(mp4|webm)$/, \".log\");\n await run(s, `rm -f ${proc.boxPath} ${proc.pidFile} ${logFile}`, proc.runAs).catch(() => undefined);\n}\n\n/** The storage object key for a recording artifact (parallels the file-asset layout). */\nexport function recordingStorageKey(workspaceId: string, sessionId: string, recordingId: string, codec: RecordingCodec): string {\n return `recordings/${workspaceId}/${sessionId}/${recordingId}.${extForCodec(codec)}`;\n}\n","// packages/runtime/src/sandbox/channel-a.ts — the Channel-A structured services\n// (P4.4 / modules/08-channel-a.md §4), provider-agnostic, called API-DIRECT.\n//\n// THE NON-PIXEL SURFACE: file tree + read/write (the Pierre tree), git\n// status/diff hunks (the Pierre diff), and a terminal exec + interactive PTY.\n// Served client -> API -> box IN-PROCESS: the API resumes the box by id, builds\n// ONE service around the live `session` handle for the call's lifetime, runs the\n// op, returns inline JSON, and drops the handle. There is NO ownership/singleton\n// here — the live handle is whatever the caller resumed; it is non-owned and\n// dropped when the call returns. The same module is importable by the worker's\n// agent-turn for the A1 fs.changed side-effect it produces in-process.\n//\n// SDK GROUNDING (the load-bearing reality — see the adversarial review in the\n// module spec). Built on `session.exec(args): Promise<SandboxExecResult>` which\n// returns RAW {stdout,stderr,exitCode} on the agents-core local/docker sessions\n// (and Modal/the extensions providers expose the equivalent). `execCommand`\n// returns a BANNER-DECORATED string (formatExecResponse) — NEVER used for\n// parsing; only as a last-resort fallback when `exec` is absent, with the banner\n// stripped. `readFile` returns string|Uint8Array (binary-safe). Writes go\n// through `exec` (a base64 heredoc — raw + binary capable, unlike createEditor's\n// apply-patch-only path which cannot do binary, C4), falling back to\n// `createEditor` for text when `exec` is absent.\n\nimport type {\n FsChangedPayload,\n FsDeleteRequest,\n FsDeleteResponse,\n FsListRequest,\n FsListResponse,\n FsMkdirRequest,\n FsMkdirResponse,\n FsMoveRequest,\n FsMoveResponse,\n FsReadRequest,\n FsReadResponse,\n FsTreeNode,\n FsWriteRequest,\n FsWriteResponse,\n GitChangedPayload,\n GitCommit,\n GitDiffHunk,\n GitDiffLine,\n GitDiffRequest,\n GitDiffResponse,\n GitFileDiff,\n GitFileStatus,\n GitFileStatusCode,\n GitLogRequest,\n GitLogResponse,\n GitShowRequest,\n GitShowResponse,\n GitStatusRequest,\n GitStatusResponse,\n PtyCloseRequest,\n PtyOpenRequest,\n PtyOpenResponse,\n PtyResizeRequest,\n PtyWriteRequest,\n SessionEventType,\n SessionStructuredCapabilities,\n TerminalExecRequest,\n TerminalExecResponse,\n} from \"@opengeni/contracts\";\n\n// ── The minimal session surface Channel A consumes (a structural subset of the\n// SDK's SandboxSession, all optional — capability-probed before use). ─────────\nexport type ChannelAExecResult = {\n output?: string;\n stdout?: string;\n stderr?: string;\n exitCode?: number | null;\n sessionId?: number;\n wallTimeSeconds?: number;\n};\nexport type ChannelAExecArgs = {\n cmd: string;\n workdir?: string | undefined;\n shell?: string | undefined;\n login?: boolean | undefined;\n tty?: boolean | undefined;\n yieldTimeMs?: number | undefined;\n maxOutputTokens?: number | undefined;\n runAs?: string | undefined;\n};\nexport type ChannelAEditor = {\n createFile?(op: unknown): Promise<unknown>;\n updateFile?(op: unknown): Promise<unknown>;\n deleteFile?(op: unknown): Promise<unknown>;\n};\nexport type ChannelASession = {\n exec?(args: ChannelAExecArgs): Promise<ChannelAExecResult>;\n execCommand?(args: ChannelAExecArgs): Promise<string>;\n readFile?(args: { path: string; runAs?: string; maxBytes?: number }): Promise<string | Uint8Array>;\n writeStdin?(args: { sessionId: number; chars?: string; yieldTimeMs?: number; maxOutputTokens?: number }): Promise<string>;\n createEditor?(runAs?: string): ChannelAEditor;\n supportsPty?(): boolean;\n};\n\n// ── Errors mapped to HTTP status at the route. ───────────────────────────────\nexport class ChannelAValidationError extends Error {\n constructor(message: string) { super(message); this.name = \"ChannelAValidationError\"; }\n}\nexport class ChannelAConflictError extends Error {\n constructor(message: string) { super(message); this.name = \"ChannelAConflictError\"; }\n}\nexport class ChannelANotFoundError extends Error {\n constructor(message: string) { super(message); this.name = \"ChannelANotFoundError\"; }\n}\nexport class ChannelAUnsupportedError extends Error {\n constructor(message: string) { super(message); this.name = \"ChannelAUnsupportedError\"; }\n}\n\nexport type ChannelAEmitter = (events: { type: SessionEventType; payload: unknown }[]) => Promise<void>;\n\nexport type SandboxChannelAServiceOptions = {\n session: ChannelASession;\n // The workspace-relative root the box maps \"\" to (the SDK normalizes against\n // its own workspaceRoot, so \"\" is the workspace root here).\n workspaceRoot?: string;\n // The lease epoch the box was resumed under (paired with `revision` for cache\n // invalidation — H3). 0 when ownership is off / no lease.\n leaseEpoch?: number;\n // The starting FS revision (monotonic; the caller may seed it from a prior\n // value so it doesn't reset to 0 mid-session — H3). Defaults to 0.\n revision?: number;\n // A1 emitter — appendAndPublishEvents bound to the caller's db+bus. Optional:\n // a pure read (fsList/fsRead/gitDiff) needs no emitter; only the mutating /\n // PTY paths emit. When absent the notification is silently skipped.\n emit?: ChannelAEmitter;\n // runAs is omitted unless the backend supports it (modal/daytona/cloudflare);\n // e2b/runloop/blaxel/vercel throw on runAs (SDK survey). Default off — the\n // local/docker test backends are single-user.\n runAs?: string;\n};\n\nconst NUL = String.fromCharCode(0); // \\0 NUL — find/porcelain/numstat -z separator\nconst US = String.fromCharCode(0x1f); // \\x1f unit sep — git-log field separator\nconst RS = String.fromCharCode(0x1e); // \\x1e record sep — git-log record separator\n\nexport class SandboxChannelAService {\n private readonly session: ChannelASession;\n private readonly workspaceRoot: string;\n private readonly leaseEpoch: number;\n private revision: number;\n private readonly emit?: ChannelAEmitter | undefined;\n private readonly runAs?: string | undefined;\n\n constructor(opts: SandboxChannelAServiceOptions) {\n this.session = opts.session;\n this.workspaceRoot = opts.workspaceRoot ?? \"\";\n this.leaseEpoch = opts.leaseEpoch ?? 0;\n this.revision = opts.revision ?? 0;\n this.emit = opts.emit;\n this.runAs = opts.runAs;\n }\n\n /** Capability probe — the compact Channel-A projection. */\n capabilities(repos: string[] = []): SessionStructuredCapabilities {\n const s = this.session;\n const hasExec = Boolean(s.exec || s.execCommand);\n const hasFs = Boolean(s.readFile && (s.exec || s.execCommand || s.createEditor));\n return {\n FileSystem: { available: hasFs, readOnly: !(s.exec || s.createEditor), root: this.workspaceRoot },\n Terminal: {\n events: hasExec,\n exec: hasExec,\n pty: { available: Boolean(s.supportsPty?.() && s.writeStdin) },\n },\n Git: { available: hasExec, repos },\n };\n }\n\n // ════════════════════════════ exec primitive ══════════════════════════════\n // RAW exec — returns {stdout, stderr, exitCode}. Uses session.exec when present\n // (the local/docker sessions return raw output); falls back to execCommand +\n // a banner strip (last resort; banner-truncation can mangle, so exec is always\n // preferred). Throws ChannelAUnsupportedError when neither exists.\n private async run(args: ChannelAExecArgs): Promise<{ stdout: string; stderr: string; exitCode: number | null; sessionId?: number; wallTimeSeconds: number }> {\n const withRunAs = this.runAs ? { ...args, runAs: this.runAs } : args;\n if (this.session.exec) {\n const r = await this.session.exec(withRunAs);\n return {\n stdout: r.stdout ?? r.output ?? \"\",\n stderr: r.stderr ?? \"\",\n exitCode: r.exitCode ?? null,\n ...(typeof r.sessionId === \"number\" ? { sessionId: r.sessionId } : {}),\n wallTimeSeconds: r.wallTimeSeconds ?? 0,\n };\n }\n if (this.session.execCommand) {\n const raw = await this.session.execCommand(withRunAs);\n // The SDK's execCommand returns the formatExecResponse BANNER string. When a\n // command stays running (an interactive `bash` opened with tty:true), the\n // banner carries a `Process running with session ID <N>` line — the numeric\n // exec-session id writeStdin() needs to drive that PTY. The exec() fast-path\n // above surfaces sessionId structurally; this fallback must recover it from\n // the banner or the PTY appears non-interactive (execSessionId=null ->\n // pty/write 409) even on backends (Modal) whose only exec surface is\n // execCommand. We DON'T close over the banner for stdout (that is stripped).\n const sessionId = parseExecBannerSessionId(raw);\n return {\n stdout: stripExecBanner(raw),\n stderr: \"\",\n exitCode: null,\n ...(sessionId !== null ? { sessionId } : {}),\n wallTimeSeconds: 0,\n };\n }\n throw new ChannelAUnsupportedError(\"the box does not support command execution\");\n }\n\n // ════════════════════════════ FileSystem (A2) ═════════════════════════════\n\n async fsList(req: FsListRequest): Promise<FsListResponse> {\n const root = normalizeRelPath(req.path);\n // A single bounded `find` (NUL-delimited) builds the whole subtree in one\n // round-trip. Prefer GNU find's -printf on the Ubuntu-based images, but fall\n // back to a POSIX-ish find+stat loop for unix_local on macOS/BSD.\n const findRoot = root === \"\" ? \".\" : shellQuote(root);\n const depthArg = Math.max(1, req.depth);\n const hidden = req.includeHidden ? \"\" : ` -not -path '*/.*'`;\n const gnuFind = `find ${findRoot} -mindepth 1 -maxdepth ${depthArg}${hidden} -printf '%y\\\\t%s\\\\t%T@\\\\t%m\\\\t%p\\\\0' 2>/dev/null`;\n let { stdout } = await this.run({ cmd: `bash -lc ${shellQuote(gnuFind)}`, workdir: this.workspaceRoot || undefined });\n if (!stdout) {\n const portableFind = [\n `find ${findRoot} -mindepth 1 -maxdepth ${depthArg}${hidden} -print0 2>/dev/null | while IFS= read -r -d '' p; do`,\n `if [ -d \"$p\" ]; then t=d; size=0; elif [ -f \"$p\" ]; then t=f; size=$(wc -c < \"$p\" | tr -d ' '); elif [ -L \"$p\" ]; then t=l; size=0; else t=o; size=0; fi;`,\n `mtime=$(date -r \"$p\" +%s 2>/dev/null || stat -c %Y \"$p\" 2>/dev/null || echo 0);`,\n `mode=$(stat -f %Lp \"$p\" 2>/dev/null || stat -c %a \"$p\" 2>/dev/null || echo 0);`,\n `printf '%s\\\\t%s\\\\t%s\\\\t%s\\\\t%s\\\\0' \"$t\" \"$size\" \"$mtime\" \"$mode\" \"$p\";`,\n `done`,\n ].join(\" \");\n ({ stdout } = await this.run({ cmd: `bash -lc ${shellQuote(portableFind)}`, workdir: this.workspaceRoot || undefined }));\n }\n\n const entries = stdout.split(NUL).filter((s) => s.length > 0);\n const rootNode: FsTreeNode = {\n name: basename(root) || (root === \"\" ? \"\" : root),\n path: root,\n type: \"dir\",\n sizeBytes: null,\n mtimeMs: null,\n mode: null,\n children: [],\n truncated: false,\n };\n // Index nodes by path for O(1) parent attach.\n const byPath = new Map<string, FsTreeNode>();\n byPath.set(root, rootNode);\n let count = 0;\n let truncated = false;\n for (const entry of entries) {\n if (count >= req.maxEntries) { truncated = true; break; }\n const parts = entry.split(\"\\t\");\n if (parts.length < 5) continue;\n const [typeChar, sizeStr, mtimeStr, modeStr, ...pathParts] = parts;\n const rawPath = pathParts.join(\"\\t\");\n const relPath = stripDotSlash(rawPath, root);\n const node: FsTreeNode = {\n name: basename(relPath),\n path: relPath,\n type: findTypeToNode(typeChar ?? \"\"),\n sizeBytes: typeChar === \"d\" ? null : safeInt(sizeStr),\n mtimeMs: mtimeToMs(mtimeStr),\n mode: safeOctal(modeStr),\n ...(typeChar === \"d\" ? { children: [] as FsTreeNode[] } : {}),\n truncated: false,\n };\n byPath.set(relPath, node);\n count++;\n }\n // Second pass: attach each node to its parent (parents always present\n // because find emits ancestors before descendants at increasing depth).\n for (const [path, node] of byPath) {\n if (path === root) continue;\n const parentPath = dirnameRel(path, root);\n const parent = byPath.get(parentPath) ?? rootNode;\n (parent.children ??= []).push(node);\n }\n sortTree(rootNode);\n return { root: rootNode, revision: this.revision, truncated };\n }\n\n async fsRead(req: FsReadRequest): Promise<FsReadResponse> {\n const path = assertSafeRelPath(req.path);\n if (!this.session.readFile) {\n // No native readFile: base64 the file through exec (binary-safe).\n return await this.fsReadViaExec(path, req);\n }\n let raw: string | Uint8Array;\n try {\n raw = await this.session.readFile({ path: this.joinRoot(path), maxBytes: req.maxBytes, ...(this.runAs ? { runAs: this.runAs } : {}) });\n } catch (error) {\n // The provider's native readFile applies a REMOTE workspace-escape guard:\n // a SYMLINK whose target resolves outside /workspace (e.g.\n // `.config/pulse/<id>-runtime -> /tmp/pulse-…`) is rejected with\n // \"Sandbox path failed remote validation: workspace escape: /tmp/…\". That\n // raw 404 surfaced to the user. The path is still legitimately INSIDE the\n // workspace (the symlink node lives there); only its target escapes. Read it\n // via exec instead — `base64 <path>` follows the link and is NOT subject to\n // the provider's path validation — so a symlink-to-/tmp renders cleanly\n // instead of erroring. A genuine not-found falls through to a clean 404.\n if (isWorkspaceEscapeError(error)) {\n return await this.fsReadViaExec(path, req);\n }\n throw new ChannelANotFoundError(`file not found: ${path} (${error instanceof Error ? error.message : String(error)})`);\n }\n const bytes = typeof raw === \"string\" ? Buffer.from(raw, \"utf8\") : Buffer.from(raw);\n return this.shapeRead(path, bytes, req);\n }\n\n /** Read a file by base64-ing it through exec. Binary-safe and — crucially —\n * NOT subject to the provider's native-readFile workspace-escape validation,\n * so it can render a symlink whose target lives outside /workspace (the link\n * node itself is in-workspace). `base64 <path>` follows the symlink. */\n private async fsReadViaExec(path: string, req: FsReadRequest): Promise<FsReadResponse> {\n const abs = this.joinRoot(path);\n const { stdout, exitCode } = await this.run({ cmd: `base64 ${shellQuote(abs)} 2>/dev/null | head -c ${Math.ceil(req.maxBytes * 1.4)}` });\n if (exitCode !== null && exitCode !== 0 && stdout === \"\") {\n // The target may be a dangling symlink or a link to a directory; surface a\n // clean, typed not-found rather than a raw provider validation error.\n throw new ChannelANotFoundError(`file not found: ${path}`);\n }\n const bytes = Buffer.from(stdout.replace(/\\n/g, \"\"), \"base64\");\n return this.shapeRead(path, bytes, req);\n }\n\n private shapeRead(path: string, bytes: Buffer, req: FsReadRequest): FsReadResponse {\n const truncated = bytes.byteLength >= req.maxBytes;\n const isBinary = sniffBinary(bytes);\n const encoding = req.encoding === \"base64\" || isBinary ? \"base64\" : \"utf8\";\n const content = encoding === \"base64\" ? bytes.toString(\"base64\") : bytes.toString(\"utf8\");\n return {\n path,\n encoding,\n content,\n sizeBytes: bytes.byteLength,\n truncated,\n isBinary,\n revision: this.revision,\n };\n }\n\n async fsWrite(req: FsWriteRequest): Promise<FsWriteResponse> {\n const path = assertSafeRelPath(req.path);\n const abs = this.joinRoot(path);\n const bytes = req.encoding === \"base64\" ? Buffer.from(req.content, \"base64\") : Buffer.from(req.content, \"utf8\");\n\n if (!req.overwrite) {\n const { exitCode } = await this.run({ cmd: `test -e ${shellQuote(abs)}` });\n if (exitCode === 0) {\n throw new ChannelAConflictError(`path exists and overwrite is false: ${path}`);\n }\n }\n if (req.createParents) {\n const dir = dirnameAbs(abs);\n if (dir) await this.run({ cmd: `mkdir -p ${shellQuote(dir)}` });\n }\n // base64-decode heredoc — raw + binary capable, single round-trip, last-\n // writer-wins (the I4 default; no read-modify-write race because we write\n // the whole file). A non-existent parent with createParents:false surfaces a\n // non-zero exit -> 400.\n const b64 = bytes.toString(\"base64\");\n const { exitCode, stderr } = await this.run({\n cmd: `printf %s ${shellQuote(b64)} | base64 -d > ${shellQuote(abs)}`,\n });\n if (exitCode !== null && exitCode !== 0) {\n // createEditor fallback for text when exec-write failed and we have a\n // text payload (binary cannot go through apply-patch).\n if (req.encoding !== \"base64\" && this.session.createEditor) {\n const ok = await this.tryEditorWrite(abs, req.content);\n if (!ok) throw new ChannelAValidationError(`failed to write ${path}: ${stderr || `exit ${exitCode}`}`);\n } else {\n throw new ChannelAValidationError(`failed to write ${path}: ${stderr || `exit ${exitCode}`}`);\n }\n }\n this.revision++;\n await this.emitFsChanged([{ path, kind: \"modified\", isDir: false, sizeBytes: bytes.byteLength }], \"write\");\n return { path, sizeBytes: bytes.byteLength, revision: this.revision };\n }\n\n private async tryEditorWrite(absPath: string, content: string): Promise<boolean> {\n const editor = this.session.createEditor?.(this.runAs);\n if (!editor?.createFile) return false;\n try {\n // The apply-patch op shape — a whole-file \"create\" diff (last-writer-wins).\n const diff = content.split(\"\\n\").map((line) => `+${line}`).join(\"\\n\");\n await editor.createFile({ type: \"create_file\", path: absPath, diff });\n return true;\n } catch {\n return false;\n }\n }\n\n async fsDelete(req: FsDeleteRequest): Promise<FsDeleteResponse> {\n const path = assertSafeRelPath(req.path);\n const abs = this.joinRoot(path);\n const flag = req.recursive ? \"-rf\" : \"-f\";\n const { exitCode, stderr } = await this.run({ cmd: `rm ${flag} ${shellQuote(abs)}` });\n if (exitCode !== null && exitCode !== 0) {\n throw new ChannelAValidationError(`failed to delete ${path}: ${stderr || `exit ${exitCode}`}`);\n }\n this.revision++;\n await this.emitFsChanged([{ path, kind: \"deleted\", isDir: false, sizeBytes: null }], \"write\");\n return { revision: this.revision };\n }\n\n async fsMove(req: FsMoveRequest): Promise<FsMoveResponse> {\n const path = assertSafeRelPath(req.path);\n const newPath = assertSafeRelPath(req.newPath);\n const abs = this.joinRoot(path);\n const newAbs = this.joinRoot(newPath);\n\n if (!req.overwrite) {\n const { exitCode } = await this.run({ cmd: `test -e ${shellQuote(newAbs)}` });\n if (exitCode === 0) {\n throw new ChannelAConflictError(`destination exists and overwrite is false: ${newPath}`);\n }\n }\n if (req.createParents) {\n const dir = dirnameAbs(newAbs);\n if (dir) await this.run({ cmd: `mkdir -p ${shellQuote(dir)}` });\n }\n // -f only when overwrite — otherwise a clobber would silently succeed past\n // the guard above on a race. A missing source surfaces a non-zero exit -> 400.\n const flag = req.overwrite ? \"-f \" : \"\";\n const { exitCode, stderr } = await this.run({\n cmd: `mv ${flag}${shellQuote(abs)} ${shellQuote(newAbs)}`,\n });\n if (exitCode !== null && exitCode !== 0) {\n throw new ChannelAValidationError(`failed to move ${path} -> ${newPath}: ${stderr || `exit ${exitCode}`}`);\n }\n this.revision++;\n await this.emitFsChanged(\n [\n { path, kind: \"deleted\", isDir: false, sizeBytes: null },\n { path: newPath, kind: \"created\", isDir: false, sizeBytes: null },\n ],\n \"write\",\n );\n return { path, newPath, revision: this.revision };\n }\n\n async fsMkdir(req: FsMkdirRequest): Promise<FsMkdirResponse> {\n const path = assertSafeRelPath(req.path);\n const abs = this.joinRoot(path);\n // A plain mkdir on an existing path returns non-zero -> 400, matching the\n // write-on-existing semantics; -p makes the create idempotent + builds parents.\n const flag = req.recursive ? \"-p \" : \"\";\n const { exitCode, stderr } = await this.run({ cmd: `mkdir ${flag}${shellQuote(abs)}` });\n if (exitCode !== null && exitCode !== 0) {\n throw new ChannelAValidationError(`failed to mkdir ${path}: ${stderr || `exit ${exitCode}`}`);\n }\n this.revision++;\n await this.emitFsChanged([{ path, kind: \"created\", isDir: true, sizeBytes: null }], \"write\");\n return { path, revision: this.revision };\n }\n\n // ════════════════════════════ Git (A2, read-only) ═════════════════════════\n\n async gitStatus(req: GitStatusRequest): Promise<GitStatusResponse> {\n const repo = this.repoWorkdir(req.path);\n const inside = await this.run({ cmd: \"git rev-parse --is-inside-work-tree 2>/dev/null\", workdir: repo });\n if (inside.stdout.trim() !== \"true\") {\n return { isRepo: false, head: null, detached: false, upstream: null, ahead: 0, behind: 0, files: [], revision: this.revision };\n }\n const { stdout } = await this.run({ cmd: \"git status --porcelain=v2 --branch -z\", workdir: repo });\n return { ...parsePorcelainV2(stdout), revision: this.revision };\n }\n\n async gitDiff(req: GitDiffRequest): Promise<GitDiffResponse> {\n const repo = this.repoWorkdir(req.path);\n const ctx = req.contextLines;\n // Selector precedence: refs > staged > worktree.\n let range = \"\";\n if (req.fromRef && req.toRef) range = `${shellQuote(req.fromRef)} ${shellQuote(req.toRef)}`;\n else if (req.fromRef) range = `${shellQuote(req.fromRef)}`;\n else if (req.staged) range = \"--cached\";\n const pathspec = req.pathspec.length ? ` -- ${req.pathspec.map(shellQuote).join(\" \")}` : \"\";\n\n // Pass 1: numstat (stats + binary detection). -z gives NUL-separated fields;\n // a rename emits old\\0new for that record's path fields.\n const numstat = await this.run({ cmd: `git -c core.quotePath=false diff --no-color -z --numstat ${range}${pathspec}`.trim(), workdir: repo });\n const stats = parseNumstatZ(numstat.stdout);\n\n const files: GitFileDiff[] = [];\n for (const stat of stats) {\n const target = stat.newPath;\n const fileStatus: GitFileStatusCode = stat.binary ? \"modified\" : \"modified\";\n if (stat.binary) {\n files.push({\n path: target,\n oldPath: stat.oldPath,\n status: fileStatus,\n isBinary: true,\n isImage: isImagePath(target),\n additions: 0,\n deletions: 0,\n hunks: [],\n truncated: false,\n });\n continue;\n }\n // Pass 2: the per-file unified patch -> hunks.\n const patch = await this.run({\n cmd: `git -c core.quotePath=false diff --no-color -U${ctx} ${range} -- ${shellQuote(target)}`.trim(),\n workdir: repo,\n });\n const oversized = Buffer.byteLength(patch.stdout, \"utf8\") > req.maxBytesPerFile;\n const parsed = oversized ? { hunks: [] as GitDiffHunk[], status: \"modified\" as GitFileStatusCode } : parseUnifiedPatch(patch.stdout);\n files.push({\n path: target,\n oldPath: stat.oldPath,\n status: parsed.status,\n isBinary: false,\n isImage: isImagePath(target),\n additions: stat.additions,\n deletions: stat.deletions,\n hunks: parsed.hunks,\n truncated: oversized,\n });\n }\n return { files, revision: this.revision };\n }\n\n async gitLog(req: GitLogRequest): Promise<GitLogResponse> {\n const repo = this.repoWorkdir(req.path);\n const fmt = `%H${US}%h${US}%P${US}%an${US}%ae${US}%at${US}%cn${US}%ce${US}%ct${US}%s${US}%b${RS}`;\n const pathspec = req.pathspec.length ? ` -- ${req.pathspec.map(shellQuote).join(\" \")}` : \"\";\n const { stdout, exitCode } = await this.run({\n cmd: `git log --format=${shellQuote(fmt)} -n${req.maxCount + 1} --skip=${req.skip} ${shellQuote(req.ref)}${pathspec}`,\n workdir: repo,\n });\n if (exitCode !== null && exitCode !== 0) {\n return { commits: [], hasMore: false };\n }\n const records = stdout.split(RS).map((r) => r.replace(/^\\n/, \"\")).filter((r) => r.trim().length > 0);\n const commits: GitCommit[] = [];\n for (const rec of records.slice(0, req.maxCount)) {\n const f = rec.split(US);\n if (f.length < 11) continue;\n commits.push({\n sha: f[0]!,\n shortSha: f[1]!,\n parents: (f[2] ?? \"\").trim() ? f[2]!.trim().split(\" \") : [],\n author: { name: f[3]!, email: f[4]!, timestamp: safeInt(f[5]) ?? 0 },\n committer: { name: f[6]!, email: f[7]!, timestamp: safeInt(f[8]) ?? 0 },\n subject: f[9]!,\n body: f.slice(10).join(US),\n refs: [],\n });\n }\n return { commits, hasMore: records.length > req.maxCount };\n }\n\n async gitShow(req: GitShowRequest): Promise<GitShowResponse> {\n const repo = this.repoWorkdir(req.path);\n if (req.filePath) {\n // Raw blob mode: ref:filePath -> bytes.\n const { stdout, exitCode } = await this.run({\n cmd: `git cat-file blob ${shellQuote(`${req.ref}:${req.filePath}`)} 2>/dev/null | base64`,\n workdir: repo,\n });\n if (exitCode !== null && exitCode !== 0 && stdout.trim() === \"\") {\n throw new ChannelANotFoundError(`blob not found: ${req.ref}:${req.filePath}`);\n }\n const bytes = Buffer.from(stdout.replace(/\\n/g, \"\"), \"base64\");\n const truncated = bytes.byteLength > req.maxBytesPerFile;\n const clamped = truncated ? bytes.subarray(0, req.maxBytesPerFile) : bytes;\n const isBinary = sniffBinary(clamped);\n const encoding = req.encoding === \"base64\" || isBinary ? \"base64\" : \"utf8\";\n return {\n commit: null,\n files: [],\n blob: { content: encoding === \"base64\" ? clamped.toString(\"base64\") : clamped.toString(\"utf8\"), encoding, sizeBytes: clamped.byteLength, truncated },\n revision: this.revision,\n };\n }\n // Commit mode: metadata + diff vs first parent.\n const log = await this.gitLog({ path: req.path, ref: req.ref, maxCount: 1, skip: 0, pathspec: [] });\n const commit = log.commits[0] ?? null;\n const diff = await this.gitDiff({ path: req.path, staged: false, fromRef: `${req.ref}^`, toRef: req.ref, pathspec: [], contextLines: 3, maxBytesPerFile: req.maxBytesPerFile });\n return { commit, files: diff.files, blob: null, revision: this.revision };\n }\n\n /** Detect repo roots within the workspace (for the Git.repos capability). */\n async detectRepos(): Promise<string[]> {\n try {\n const { stdout } = await this.run({ cmd: `find . -maxdepth 3 -name .git -type d 2>/dev/null`, workdir: this.workspaceRoot || undefined });\n return stdout.split(\"\\n\").map((l) => l.trim()).filter(Boolean).map((g) => dirnameAbs(stripDotSlash(g, \"\")) || \"\");\n } catch {\n return [];\n }\n }\n\n // ════════════════════════ Terminal exec + PTY (A2) ════════════════════════\n\n /** Run a bounded command, return buffered stdout/stderr + exit code inline. The\n * long-running tail (when the process hasn't exited within timeoutMs) keeps\n * running in-box; if emitStream is set the buffered output is also published as\n * the agent firehose so other viewers see it. */\n async terminalExec(req: TerminalExecRequest): Promise<TerminalExecResponse> {\n const r = await this.run({\n cmd: req.command,\n workdir: this.repoWorkdir(req.cwd),\n yieldTimeMs: req.timeoutMs,\n });\n const running = r.exitCode === null && typeof r.sessionId === \"number\";\n if (req.emitStream && (r.stdout || r.stderr)) {\n const events: { type: SessionEventType; payload: unknown }[] = [];\n const commandId = crypto.randomUUID();\n if (r.stdout) events.push({ type: \"sandbox.command.output.delta\", payload: { stream: \"stdout\", chunk: r.stdout, commandId, seq: 0 } });\n if (r.stderr) events.push({ type: \"sandbox.command.output.delta\", payload: { stream: \"stderr\", chunk: r.stderr, commandId, seq: 1 } });\n await this.emitEvents(events);\n }\n return {\n stdout: r.stdout,\n stderr: r.stderr,\n exitCode: r.exitCode,\n running,\n wallTimeSeconds: r.wallTimeSeconds,\n };\n }\n\n /** Open an interactive PTY: exec the shell with tty:true, yielding the numeric\n * exec-session id the caller persists (ptyId<->execSessionId) so subsequent\n * writeStdin can drive it. Returns the supportsInput gate (false when the\n * backend has no writeStdin). The caller emits terminal.pty.started after it\n * persists the row. */\n async ptyOpen(req: PtyOpenRequest, ptyId: string): Promise<{ response: PtyOpenResponse; execSessionId: number | null; shell: string; initialOutput: string }> {\n const supportsInput = Boolean(this.session.supportsPty?.() && this.session.writeStdin);\n const shell = req.shell ?? \"/bin/bash\";\n const r = await this.run({\n cmd: shell,\n workdir: this.repoWorkdir(req.cwd),\n tty: true,\n login: true,\n yieldTimeMs: 250,\n });\n return {\n response: { ptyId, streamVia: \"sse-events\", supportsInput },\n execSessionId: typeof r.sessionId === \"number\" ? r.sessionId : null,\n shell,\n initialOutput: r.stdout,\n };\n }\n\n /** Drive an open PTY's stdin. Returns the drained output (the caller publishes\n * it as terminal.pty.output.delta). Throws ChannelAUnsupportedError when the\n * backend has no writeStdin. */\n async ptyWrite(_req: PtyWriteRequest, execSessionId: number, data: string): Promise<string> {\n if (!this.session.writeStdin) {\n throw new ChannelAUnsupportedError(\"interactive terminal unsupported on this backend\");\n }\n const out = await this.session.writeStdin({ sessionId: execSessionId, chars: data, yieldTimeMs: 250 });\n // The Modal exec surface reports a vanished exec-session as a NON-throwing\n // string (\"write_stdin failed: session not found: N\") that we used to stream\n // verbatim into the terminal. That happens when the persisted exec-session no\n // longer exists on the live box — historically the box-mismatch (resume_state\n // pointing at a rival box; fixed at the lease layer), or a genuine box\n // rollover after the PTY opened. Surface it as a typed CONFLICT so the route\n // returns 409 and the client cleanly RE-OPENS the PTY against the live box,\n // instead of writing a raw \"session not found: 1\" into the user's xterm.\n if (isExecSessionLostBanner(out, execSessionId)) {\n throw new ChannelAConflictError(\"pty session lost on the live box; reopen the terminal\");\n }\n return stripExecBanner(out);\n }\n\n /** Resize an open PTY (SIGWINCH via stty against the exec-session). The SDK has\n * no resize method; stty in the same tty session updates the geometry. */\n async ptyResize(req: PtyResizeRequest, execSessionId: number): Promise<void> {\n if (!this.session.writeStdin) return;\n // Send a stty in-band on the same pty session.\n await this.session.writeStdin({ sessionId: execSessionId, chars: `stty cols ${req.cols} rows ${req.rows}\\n`, yieldTimeMs: 50 });\n }\n\n /** Close an open PTY: write exit/EOF. The caller marks the row closed + emits\n * terminal.pty.exited. */\n async ptyClose(_req: PtyCloseRequest, execSessionId: number | null): Promise<void> {\n if (execSessionId !== null && this.session.writeStdin) {\n try {\n await this.session.writeStdin({ sessionId: execSessionId, chars: \"\u0004\", yieldTimeMs: 50 }); // EOF\n } catch {\n // best-effort; the row is marked closed regardless.\n }\n }\n }\n\n // ──────────────────────────── helpers ──────────────────────────────────────\n\n /** The current FS revision (for the caller to persist/seed). */\n currentRevision(): number { return this.revision; }\n\n private joinRoot(rel: string): string {\n if (!this.workspaceRoot) return rel === \"\" ? \".\" : rel;\n return rel === \"\" ? this.workspaceRoot : `${this.workspaceRoot}/${rel}`;\n }\n\n private repoWorkdir(rel: string): string | undefined {\n const safe = normalizeRelPath(rel);\n const joined = this.joinRoot(safe);\n return joined === \".\" ? this.workspaceRoot || undefined : joined;\n }\n\n private async emitEvents(events: { type: SessionEventType; payload: unknown }[]): Promise<void> {\n if (!this.emit || events.length === 0) return;\n try { await this.emit(events); } catch { /* durable spine retries; not fatal */ }\n }\n\n private async emitFsChanged(changes: FsChangedPayload[\"changes\"], source: FsChangedPayload[\"source\"]): Promise<void> {\n const payload: FsChangedPayload = { changes, source, revision: this.revision, leaseEpoch: this.leaseEpoch };\n await this.emitEvents([{ type: \"fs.changed\", payload }]);\n }\n\n /** Re-probe git after a mutation and emit git.changed (best-effort, used by the\n * worker agent-turn side after FS-mutating tools). */\n async emitGitChanged(repoPath: string, reason: GitChangedPayload[\"reason\"]): Promise<void> {\n try {\n const status = await this.gitStatus({ path: repoPath });\n const payload: GitChangedPayload = {\n head: status.head,\n dirty: status.files.length > 0,\n ahead: status.ahead,\n behind: status.behind,\n changedFileCount: status.files.length,\n reason,\n revision: this.revision,\n leaseEpoch: this.leaseEpoch,\n };\n await this.emitEvents([{ type: \"git.changed\", payload }]);\n } catch {\n // non-repo / git absent — no notification.\n }\n }\n}\n\n// ════════════════════════════ pure parsers/helpers ══════════════════════════\n\n// Strip the formatExecResponse banner (Chunk ID / Wall time / Process … / Output:)\n// — only used when exec() is absent and we fall back to execCommand's string.\nexport function stripExecBanner(raw: string): string {\n const marker = raw.indexOf(\"\\nOutput:\\n\");\n if (marker >= 0) return raw.slice(marker + \"\\nOutput:\\n\".length);\n if (raw.startsWith(\"Output:\\n\")) return raw.slice(\"Output:\\n\".length);\n return raw;\n}\n\n// Detect the provider's native-readFile workspace-escape rejection — a symlink\n// whose target resolves outside the sandbox root. Modal phrases it \"Sandbox path\n// failed remote validation: workspace escape: <target>\"; we match loosely so a\n// wording tweak still classifies it. Used to fall the read back onto the exec\n// path (which follows the link and isn't path-validated) instead of 404-ing.\nexport function isWorkspaceEscapeError(error: unknown): boolean {\n const msg = error instanceof Error ? error.message : String(error ?? \"\");\n const lower = msg.toLowerCase();\n return lower.includes(\"workspace escape\") || (lower.includes(\"remote validation\") && lower.includes(\"escape\"));\n}\n\n// Detect the Modal \"the exec-session you're writing to no longer exists\" banner.\n// writeStdin reports a vanished session as a non-throwing string of the shape\n// `write_stdin failed: session not found: <N>` (it does NOT raise). We treat that\n// as a lost PTY (the box rolled over / was re-created since the open) so the\n// caller surfaces a clean reconnect instead of writing the raw failure into\n// xterm. Matched loosely (`session not found`) with the id when present so a\n// future wording tweak still classifies it; the command's own output cannot spoof\n// it because the SDK emits this as the whole writeStdin return, not user output.\nexport function isExecSessionLostBanner(out: string, execSessionId: number): boolean {\n if (!out) return false;\n const lower = out.toLowerCase();\n if (!lower.includes(\"session not found\")) return false;\n // When the id is present require it to match ours; when absent, the generic\n // \"session not found\" still classifies (it is never legitimate stdout here).\n return lower.includes(`session not found: ${execSessionId}`) || !/session not found:\\s*\\d+/.test(lower);\n}\n\n// Recover the numeric exec-session id the SDK embeds in a formatExecResponse\n// banner for a STILL-RUNNING process (`Process running with session ID <N>`).\n// A finished command emits `Process exited with code <N>` instead (no session\n// id) — that yields null. Only the banner region (before the `Output:` marker)\n// is scanned so a session-id-looking line in the command's own output can't\n// spoof it. This is what makes the interactive PTY work on backends whose only\n// exec surface is execCommand (Modal): without it ptyOpen reports execSessionId\n// = null and every pty/write 409s (\"interactive terminal unsupported\").\nexport function parseExecBannerSessionId(raw: string): number | null {\n const outputIdx = raw.indexOf(\"\\nOutput:\\n\");\n const banner = outputIdx >= 0 ? raw.slice(0, outputIdx) : raw.startsWith(\"Output:\\n\") ? \"\" : raw;\n const match = banner.match(/Process running with session ID (\\d+)/);\n if (!match) return null;\n const n = Number.parseInt(match[1]!, 10);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction sniffBinary(bytes: Buffer): boolean {\n const n = Math.min(bytes.byteLength, 8192);\n for (let i = 0; i < n; i++) if (bytes[i] === 0) return true;\n return false;\n}\n\nfunction normalizeRelPath(p: string): string {\n const trimmed = (p ?? \"\").replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n return trimmed;\n}\n\n// Reject path traversal / absolute paths (case 4); the box normalizes against\n// the workspace root, so a leading slash or `..` is a 400.\nexport function assertSafeRelPath(p: string): string {\n const norm = normalizeRelPath(p);\n if (norm === \"\") throw new ChannelAValidationError(\"path is required\");\n if (p.startsWith(\"/\")) throw new ChannelAValidationError(`absolute paths are not allowed: ${p}`);\n if (norm.split(\"/\").some((seg) => seg === \"..\")) throw new ChannelAValidationError(`path traversal is not allowed: ${p}`);\n return norm;\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction basename(p: string): string {\n const parts = p.split(\"/\").filter(Boolean);\n return parts.length ? parts[parts.length - 1]! : \"\";\n}\n\nfunction dirnameAbs(p: string): string {\n const idx = p.lastIndexOf(\"/\");\n return idx > 0 ? p.slice(0, idx) : \"\";\n}\n\nfunction dirnameRel(p: string, root: string): string {\n const idx = p.lastIndexOf(\"/\");\n if (idx < 0) return root;\n return p.slice(0, idx);\n}\n\nfunction stripDotSlash(rawPath: string, root: string): string {\n let p = rawPath.startsWith(\"./\") ? rawPath.slice(2) : rawPath;\n p = p.replace(/^\\/+/, \"\");\n // find run with workdir=root and findRoot=\".\" gives paths relative to root,\n // but if root is non-empty the relPath should still be workspace-relative.\n if (root && !p.startsWith(`${root}/`) && p !== root) {\n return root ? `${root}/${p}` : p;\n }\n return p;\n}\n\nfunction findTypeToNode(t: string): FsTreeNode[\"type\"] {\n if (t === \"d\") return \"dir\";\n if (t === \"f\") return \"file\";\n if (t === \"l\") return \"symlink\";\n return \"other\";\n}\n\nfunction safeInt(s: string | undefined): number | null {\n if (s === undefined) return null;\n const n = Number.parseInt(s, 10);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction safeOctal(s: string | undefined): number | null {\n if (s === undefined) return null;\n const n = Number.parseInt(s, 8);\n return Number.isFinite(n) ? n : null;\n}\n\nfunction mtimeToMs(s: string | undefined): number | null {\n if (s === undefined) return null;\n const f = Number.parseFloat(s);\n return Number.isFinite(f) ? Math.round(f * 1000) : null;\n}\n\nfunction sortTree(node: FsTreeNode): void {\n if (!node.children) return;\n node.children.sort((a, b) => {\n if (a.type === \"dir\" && b.type !== \"dir\") return -1;\n if (a.type !== \"dir\" && b.type === \"dir\") return 1;\n return a.name.localeCompare(b.name);\n });\n for (const child of node.children) sortTree(child);\n}\n\nfunction isImagePath(p: string): boolean {\n return /\\.(png|jpe?g|gif|webp|bmp|ico|svg|tiff?)$/i.test(p);\n}\n\n// ── git status --porcelain=v2 --branch -z parser ────────────────────────────\nexport function parsePorcelainV2(z: string): Omit<GitStatusResponse, \"revision\"> {\n const records = z.split(NUL);\n let head: string | null = null;\n let upstream: string | null = null;\n let detached = false;\n let ahead = 0;\n let behind = 0;\n const files: GitFileStatus[] = [];\n for (let i = 0; i < records.length; i++) {\n const rec = records[i]!;\n if (rec === \"\") continue;\n if (rec.startsWith(\"# branch.head \")) {\n const v = rec.slice(\"# branch.head \".length);\n if (v === \"(detached)\") { detached = true; head = null; } else head = v;\n } else if (rec.startsWith(\"# branch.upstream \")) {\n upstream = rec.slice(\"# branch.upstream \".length);\n } else if (rec.startsWith(\"# branch.ab \")) {\n const m = rec.slice(\"# branch.ab \".length).match(/\\+(\\d+)\\s+-(\\d+)/);\n if (m) { ahead = Number(m[1]); behind = Number(m[2]); }\n } else if (rec.startsWith(\"1 \")) {\n // 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>\n const fields = rec.split(\" \");\n const xy = fields[1] ?? \"..\";\n const path = fields.slice(8).join(\" \");\n files.push(statusFromXY(xy, path, null));\n } else if (rec.startsWith(\"2 \")) {\n // 2 <XY> ... <Xscore> <path>\\0<origPath> — the origPath is the NEXT NUL rec\n const fields = rec.split(\" \");\n const xy = fields[1] ?? \"..\";\n const path = fields.slice(9).join(\" \");\n const oldPath = records[i + 1] ?? null;\n i++; // consume the origPath record\n files.push(statusFromXY(xy, path, oldPath));\n } else if (rec.startsWith(\"u \")) {\n const fields = rec.split(\" \");\n const path = fields.slice(10).join(\" \");\n files.push({ path, oldPath: null, index: \"conflicted\", worktree: \"conflicted\", isConflicted: true });\n } else if (rec.startsWith(\"? \")) {\n files.push({ path: rec.slice(2), oldPath: null, index: null, worktree: \"untracked\", isConflicted: false });\n } else if (rec.startsWith(\"! \")) {\n files.push({ path: rec.slice(2), oldPath: null, index: null, worktree: \"ignored\", isConflicted: false });\n }\n }\n return { isRepo: true, head, detached, upstream, ahead, behind, files };\n}\n\nfunction xyCode(c: string): GitFileStatusCode | null {\n switch (c) {\n case \"A\": return \"added\";\n case \"M\": return \"modified\";\n case \"D\": return \"deleted\";\n case \"R\": return \"renamed\";\n case \"C\": return \"copied\";\n case \"T\": return \"typechange\";\n case \"U\": return \"conflicted\";\n case \".\": return null;\n default: return null;\n }\n}\n\nfunction statusFromXY(xy: string, path: string, oldPath: string | null): GitFileStatus {\n const x = xy[0] ?? \".\";\n const y = xy[1] ?? \".\";\n return {\n path,\n oldPath,\n index: xyCode(x),\n worktree: xyCode(y),\n isConflicted: x === \"U\" || y === \"U\",\n };\n}\n\n// ── numstat -z parser (additions/deletions/binary + rename old\\0new) ─────────\nexport type NumstatEntry = { additions: number; deletions: number; binary: boolean; oldPath: string | null; newPath: string };\nexport function parseNumstatZ(z: string): NumstatEntry[] {\n const fields = z.split(NUL);\n const out: NumstatEntry[] = [];\n let i = 0;\n while (i < fields.length) {\n const head = fields[i]!;\n if (head === \"\") { i++; continue; }\n // \"<add>\\t<del>\\t<path>\" OR for a rename \"<add>\\t<del>\\t\" then old\\0new follow.\n const m = head.match(/^(\\d+|-)\\t(\\d+|-)\\t(.*)$/s);\n if (!m) { i++; continue; }\n const addStr = m[1]!;\n const delStr = m[2]!;\n const pathPart = m[3]!;\n const binary = addStr === \"-\" && delStr === \"-\";\n if (pathPart === \"\") {\n // rename: the next two NUL fields are old, new\n const oldPath = fields[i + 1] ?? null;\n const newPath = fields[i + 2] ?? \"\";\n out.push({ additions: binary ? 0 : Number(addStr), deletions: binary ? 0 : Number(delStr), binary, oldPath, newPath });\n i += 3;\n } else {\n out.push({ additions: binary ? 0 : Number(addStr), deletions: binary ? 0 : Number(delStr), binary, oldPath: null, newPath: pathPart });\n i++;\n }\n }\n return out;\n}\n\n// ── unified-diff parser -> GitDiffHunk[] (the Pierre-diff shape) ─────────────\nexport function parseUnifiedPatch(patch: string): { hunks: GitDiffHunk[]; status: GitFileStatusCode } {\n const lines = patch.split(\"\\n\");\n const hunks: GitDiffHunk[] = [];\n let status: GitFileStatusCode = \"modified\";\n let current: GitDiffHunk | null = null;\n let oldNo = 0;\n let newNo = 0;\n for (const line of lines) {\n if (line.startsWith(\"new file mode\")) status = \"added\";\n else if (line.startsWith(\"deleted file mode\")) status = \"deleted\";\n else if (line.startsWith(\"rename from\") || line.startsWith(\"rename to\")) status = \"renamed\";\n if (line.startsWith(\"@@\")) {\n const m = line.match(/^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@(.*)$/);\n if (m) {\n const oldStart = Number(m[1]);\n const oldLines = m[2] !== undefined ? Number(m[2]) : 1;\n const newStart = Number(m[3]);\n const newLines = m[4] !== undefined ? Number(m[4]) : 1;\n current = { oldStart, oldLines, newStart, newLines, header: (m[5] ?? \"\").trim(), lines: [] };\n hunks.push(current);\n oldNo = oldStart;\n newNo = newStart;\n }\n continue;\n }\n if (!current) continue;\n if (line.startsWith(\"\\\\\")) continue; // \"\\"\n const marker = line[0];\n const text = line.slice(1);\n if (marker === \"+\") {\n current.lines.push({ type: \"add\", oldNo: null, newNo, text });\n newNo++;\n } else if (marker === \"-\") {\n current.lines.push({ type: \"del\", oldNo, newNo: null, text });\n oldNo++;\n } else if (marker === \" \") {\n current.lines.push({ type: \"context\", oldNo, newNo, text });\n oldNo++;\n newNo++;\n }\n }\n return { hunks, status };\n}\n","// Selfhosted capability negotiation (M3, dossier §10.2 item 4).\n//\n// `negotiateSelfhostedCapabilities` resolves the selfhosted-specific cells from\n// (a) the M2 enrollment row (consent / display / status / lastSeenAt), and\n// (b) a LIVENESS PROBE (a `ControlRpc` Ping, mockable) — \"is there a responder\n// on the subject right now?\"\n// into the right `SessionCapabilities` cells with the selfhosted reasons:\n// - online → responder + consented: cells available.\n// - offline → no enrollment / revoked / no responder: agent_offline.\n// - reconnecting → a transient blip (a recent lastSeenAt but the probe\n// missed): agent_reconnecting.\n// - consent_required → enrolled but whole-machine / screen-control not acked:\n// consent_required on the desktop/computer-use cells.\n// - display_unavailable → online but the machine has no display (headless, no\n// Xvfb): the desktop/computer-use cells degrade with\n// display_unavailable.\n//\n// It REUSES `negotiateCapabilities` for the descriptor-shaped cells (so the\n// \"every cell present, degradation is a value\" rule and the FS/Terminal/Git\n// surface stay identical to Modal), then overlays the selfhosted liveness/consent\n// /display reasons. The base function stays pure + synchronous; this is the\n// selfhosted-aware entrypoint the API/worker call.\n\nimport type {\n CapabilityUnavailableReason,\n SandboxOs,\n SessionCapabilities,\n} from \"@opengeni/contracts\";\nimport { negotiateCapabilities, type NegotiationContext } from \"../select\";\nimport type { SelfhostedSession } from \"./session\";\n\n/**\n * The structural slice of the M2 `@opengeni/db` `EnrollmentRecord` the selfhosted\n * negotiation reads. Defined STRUCTURALLY (not imported from `@opengeni/db`) so\n * the agent-loop-free sandbox leaf does not couple to the DB package's graph —\n * the API/worker pass an `EnrollmentRecord`, which satisfies this shape. The\n * fields: `status` (active gates reachability), `exposure` +\n * `allowScreenControl` (whole-machine + screen-control consent),`hasDisplay`\n * (the display plane), `lastSeenAt` (the reconnecting-window disambiguator).\n */\nexport interface SelfhostedEnrollment {\n status: string;\n exposure: string;\n allowScreenControl: boolean;\n hasDisplay: boolean;\n lastSeenAt: string | null;\n}\n\n/** The derived liveness state of a selfhosted machine (the online/offline/\n * reconnecting/consent/display matrix). */\nexport interface SelfhostedLivenessState {\n /** The dominant machine state. */\n state: \"online\" | \"reconnecting\" | \"offline\";\n /** Whole-machine + screen-control consent acknowledged (gates desktop input). */\n consented: boolean;\n /** A display (real or Xvfb) is present (gates the desktop pixel plane). */\n hasDisplay: boolean;\n}\n\n/**\n * The window after `lastSeenAt` within which a missed liveness probe is read as a\n * transient BLIP (`reconnecting`) rather than a hard `offline`. Mirrors the\n * resiliency model (§10.6: reconnecting after 1 missed window, offline after\n * ~30s). A probe miss with a lastSeenAt inside this window → reconnecting.\n */\nexport const SELFHOSTED_RECONNECT_WINDOW_MS = 30_000;\n\n/**\n * Derive the selfhosted liveness state from the enrollment row + a liveness probe\n * outcome. The probe is the authoritative \"is the agent answering NOW\" signal;\n * `lastSeenAt` disambiguates a probe-miss into reconnecting (recent) vs offline\n * (stale / never seen).\n *\n * - no enrollment / revoked → offline (the machine isn't enrolled).\n * - probe responded → online.\n * - probe missed, lastSeenAt recent → reconnecting (a transient blip).\n * - probe missed, lastSeenAt stale → offline.\n */\nexport function selfhostedLiveness(input: {\n enrollment: SelfhostedEnrollment | null;\n /** The ControlRpc Ping outcome: true iff a responder answered. */\n probeResponded: boolean;\n /** Override the clock (tests). */\n now?: Date;\n}): SelfhostedLivenessState {\n const { enrollment } = input;\n if (!enrollment || enrollment.status !== \"active\") {\n return { state: \"offline\", consented: false, hasDisplay: false };\n }\n const consented = enrollment.exposure === \"whole-machine\" && enrollment.allowScreenControl;\n const hasDisplay = enrollment.hasDisplay;\n if (input.probeResponded) {\n return { state: \"online\", consented, hasDisplay };\n }\n // Probe missed → reconnecting if we saw it recently, else offline.\n const now = (input.now ?? new Date()).getTime();\n const lastSeen = enrollment.lastSeenAt ? new Date(enrollment.lastSeenAt).getTime() : null;\n if (lastSeen !== null && now - lastSeen <= SELFHOSTED_RECONNECT_WINDOW_MS) {\n return { state: \"reconnecting\", consented, hasDisplay };\n }\n return { state: \"offline\", consented, hasDisplay };\n}\n\nexport interface SelfhostedNegotiationInput {\n sessionId: string;\n os?: SandboxOs;\n leaseEpoch: number;\n /** The M2 enrollment row for the machine (null → never enrolled → offline). */\n enrollment: SelfhostedEnrollment | null;\n /** A live liveness probe — typically `session.ping()`. When a session is\n * provided this is called; otherwise pass `probeResponded` explicitly. */\n session?: Pick<SelfhostedSession, \"ping\">;\n /** Explicit probe outcome (when no session is given, e.g. a pure read). */\n probeResponded?: boolean;\n /** The deployment desktop/terminal/computer-use policy toggles (threaded\n * through to the base negotiation). */\n desktopEnabled?: boolean;\n terminalEnabled?: boolean;\n computerUseEnabled?: boolean;\n /** Whether the calling principal acknowledged the un-redacted desktop. */\n desktopAcknowledged?: boolean;\n shared?: boolean;\n sharedSessionIds?: string[];\n /** Override the clock (tests). */\n now?: Date;\n}\n\n/**\n * Negotiate the full `SessionCapabilities` document for a selfhosted machine,\n * with the online/offline/reconnecting/consent_required/display_unavailable cells\n * correctly decided. Async because it issues the liveness probe.\n */\nexport async function negotiateSelfhostedCapabilities(input: SelfhostedNegotiationInput): Promise<SessionCapabilities> {\n const probeResponded = input.probeResponded ?? (input.session ? await input.session.ping() : false);\n const liveness = selfhostedLiveness({\n enrollment: input.enrollment,\n probeResponded,\n ...(input.now ? { now: input.now } : {}),\n });\n\n // The base context: map the machine state onto the lease `liveness` axis so the\n // descriptor-shaped cells (FS/Terminal/Git/Desktop) negotiate as on a warm box\n // when online, and a cold box when not reachable (no live tunnel). The\n // selfhosted overlay below then stamps the selfhosted-specific reasons.\n const baseLiveness: NegotiationContext[\"liveness\"] = liveness.state === \"online\" ? \"warm\" : \"cold\";\n const base: NegotiationContext = {\n sessionId: input.sessionId,\n backend: \"selfhosted\",\n os: input.os ?? \"linux\",\n liveness: baseLiveness,\n leaseEpoch: input.leaseEpoch,\n desktopEnabled: input.desktopEnabled ?? true,\n terminalEnabled: input.terminalEnabled ?? true,\n computerUseEnabled: input.computerUseEnabled ?? true,\n ...(input.desktopAcknowledged !== undefined ? { desktopAcknowledged: input.desktopAcknowledged } : {}),\n ...(input.shared !== undefined ? { shared: input.shared } : {}),\n ...(input.sharedSessionIds !== undefined ? { sharedSessionIds: input.sharedSessionIds } : {}),\n ...(input.now ? { now: input.now } : {}),\n };\n const caps = negotiateCapabilities(base);\n\n // ── Overlay the selfhosted liveness/consent/display reasons ────────────────\n\n // When the machine is not online, the Channel-A surface (FS/Terminal/Git) and\n // the desktop plane cannot be reached — stamp the machine-liveness reason. This\n // is the dominant degrade (like os_unsupported): an offline/reconnecting agent\n // knocks out every reachable capability with the single coherent reason.\n if (liveness.state !== \"online\") {\n const reason: CapabilityUnavailableReason = liveness.state === \"offline\" ? \"agent_offline\" : \"agent_reconnecting\";\n return {\n ...caps,\n FileSystem: { ...caps.FileSystem, available: false, readOnly: true, reason },\n Terminal: { ...caps.Terminal, transport: null, url: null, token: null, expiresAt: null, reason },\n Git: { ...caps.Git, available: false, reason },\n DesktopStream: {\n ...caps.DesktopStream,\n transport: null,\n client: null,\n mode: \"read-only\",\n url: null,\n token: null,\n expiresAt: null,\n requiresAcknowledgment: false,\n acknowledged: false,\n shared: false,\n sharedSessionIds: [],\n reason,\n },\n Recording: { ...caps.Recording, available: false, modes: [], codecs: [], reason },\n ComputerUse: { ...caps.ComputerUse, available: false, reason },\n };\n }\n\n // Online: FS/Terminal/Git stay as the base negotiated them (the machine is\n // reachable). The desktop plane splits VIEW from CONTROL:\n // - VIEW (a read-only DesktopStream, Recording) requires a DISPLAY only. The\n // agent already holds whole-machine shell exec (it can `screencapture` the\n // screen itself), so passive viewing is within the exposure the user already\n // consented to; a missing display (headless, no Xvfb / no macOS Screen\n // Recording grant) is the only blocker.\n // - CONTROL — driving input (ComputerUse) or an INTERACTIVE stream —\n // additionally requires the explicit allowScreenControl consent (`consented`).\n // Precedence: a headless machine blocks everything (display_unavailable); a\n // displayed-but-unconsented machine can be VIEWED (read-only) + RECORDED but not\n // CONTROLLED (consent_required). (If the base already degraded a cell for a\n // policy reason — desktop disabled / no stream-token secret — that base reason\n // wins; we only stamp a selfhosted reason on a cell the base left AVAILABLE.)\n if (!liveness.hasDisplay) {\n const reason: CapabilityUnavailableReason = \"display_unavailable\";\n return {\n ...caps,\n DesktopStream: caps.DesktopStream.transport !== null\n ? {\n ...caps.DesktopStream,\n transport: null,\n client: null,\n mode: \"read-only\",\n url: null,\n token: null,\n expiresAt: null,\n requiresAcknowledgment: false,\n acknowledged: false,\n shared: false,\n sharedSessionIds: [],\n reason,\n }\n : caps.DesktopStream,\n Recording: caps.Recording.available\n ? { ...caps.Recording, available: false, modes: [], codecs: [], reason }\n : caps.Recording,\n ComputerUse: caps.ComputerUse.available\n ? { ...caps.ComputerUse, available: false, reason }\n : caps.ComputerUse,\n };\n }\n\n if (!liveness.consented) {\n // Displayed but no screen-CONTROL consent: VIEW (read-only) + Recording stay\n // available; only CONTROL (input) is withheld. Force the stream to read-only\n // so no input is forwarded even if the base offered an interactive mode.\n return {\n ...caps,\n DesktopStream: caps.DesktopStream.transport !== null\n ? { ...caps.DesktopStream, mode: \"read-only\" }\n : caps.DesktopStream,\n ComputerUse: caps.ComputerUse.available\n ? { ...caps.ComputerUse, available: false, reason: \"consent_required\" }\n : caps.ComputerUse,\n };\n }\n\n // Fully online + displayed + consented: the base negotiation already produced\n // the correct available cells (desktop vnc-ws, computer-use available, etc.).\n return caps;\n}\n","// `MockAgentResponder` — an in-process `ControlRpc` test double standing in for\n// a real enrolled agent over NATS (the live NATS transport is M4). It answers\n// the op table (ping / exec / fs.read / fs.write / fs.list / fs.stat / git /\n// metrics / desktopEnsure) against an in-memory virtual filesystem + a pluggable\n// exec handler, so the `SelfhostedSession` surface and the mocked-NATS\n// integration tests run with zero broker.\n//\n// It is shipped from the runtime package (a testing util, not test-only-private)\n// because the API/worker integration suites (M4+) reuse it to drive\n// `withChannelA`/viewer/swap end-to-end without a real machine (dossier §16).\n\nimport {\n AgentError,\n ControlRequest,\n ControlResponse,\n ErrorCode,\n FsEntryKind,\n type ExecRequest,\n type ExecResponse,\n type FsListResponse,\n type FsReadResponse,\n type FsStatResponse,\n type FsWriteResponse,\n} from \"@opengeni/agent-proto\";\nimport type { ControlRpc } from \"./control-rpc\";\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/** A pluggable exec handler — given an ExecRequest, return an ExecResponse (or\n * throw to surface a synthesized error). Defaults to a trivial echo. */\nexport type MockExecHandler = (req: ExecRequest) => ExecResponse | Promise<ExecResponse>;\n\nexport interface MockAgentResponderOptions {\n /** Whether a responder exists at all. When false EVERY request yields an\n * AGENT_OFFLINE error (the \"machine is offline\" condition) — used to drive the\n * agent_offline capability + the isProviderSandboxNotFoundError test. */\n online?: boolean;\n /** Whether the agent has acknowledged whole-machine / screen-control consent.\n * When false, an op gated on consent yields CONSENT_REQUIRED. Defaults true. */\n consented?: boolean;\n /** Force the agent into a draining posture (every op → DRAINING). */\n draining?: boolean;\n /** Seed files (path → string|Uint8Array) into the virtual filesystem. */\n files?: Record<string, string | Uint8Array>;\n /** A custom exec handler; defaults to an echo of argv. */\n exec?: MockExecHandler;\n /** The hostname the mock reports (so PTY/exec `$HOSTNAME`-style asserts work). */\n hostname?: string;\n}\n\n/**\n * An in-process `ControlRpc` answering the agent op table against an in-memory\n * virtual filesystem. Drive a `SelfhostedSession` with this to test exec /\n * readFile / writeFile / list / stat round-trips without any NATS.\n */\nexport class MockAgentResponder implements ControlRpc {\n private online: boolean;\n private readonly consented: boolean;\n private readonly draining: boolean;\n private readonly files = new Map<string, Uint8Array>();\n private readonly execHandler: MockExecHandler;\n readonly hostname: string;\n\n /** Every request seen, for assertion (subject + decoded ControlRequest). */\n readonly requests: Array<{ subject: string; req: ControlRequest }> = [];\n\n constructor(opts: MockAgentResponderOptions = {}) {\n this.online = opts.online ?? true;\n this.consented = opts.consented ?? true;\n this.draining = opts.draining ?? false;\n this.hostname = opts.hostname ?? \"mock-machine\";\n this.execHandler = opts.exec ?? ((req) => defaultEcho(req, this.hostname));\n for (const [path, content] of Object.entries(opts.files ?? {})) {\n this.files.set(normalize(path), typeof content === \"string\" ? encoder.encode(content) : content);\n }\n }\n\n /** Flip the responder offline mid-test (a deliberate stop / blip). */\n setOnline(online: boolean): void {\n this.online = online;\n }\n\n /** Read a file the session wrote (test assertion helper). */\n fileText(path: string): string | undefined {\n const bytes = this.files.get(normalize(path));\n return bytes ? decoder.decode(bytes) : undefined;\n }\n\n async request(subject: string, req: ControlRequest, _opts: { timeoutMs: number }): Promise<ControlResponse> {\n this.requests.push({ subject, req });\n if (!this.online) {\n return errorResponse(req.requestId, ErrorCode.ERROR_CODE_AGENT_OFFLINE, \"the enrolled agent is offline\", false);\n }\n if (this.draining) {\n return errorResponse(req.requestId, ErrorCode.ERROR_CODE_DRAINING, \"the agent is draining\", true);\n }\n const op = req.op;\n if (!op) {\n return errorResponse(req.requestId, ErrorCode.ERROR_CODE_PROTOCOL, \"empty op\", false);\n }\n switch (op.$case) {\n case \"ping\":\n return ok(req.requestId, { $case: \"ping\", ping: { nonce: op.ping.nonce, agentMonotonicMs: \"0\" } });\n case \"exec\": {\n const res = await this.execHandler(op.exec);\n return ok(req.requestId, { $case: \"exec\", exec: res });\n }\n case \"fsRead\": {\n const bytes = this.files.get(normalize(op.fsRead.path));\n if (!bytes) {\n return errorResponse(req.requestId, ErrorCode.ERROR_CODE_NOT_FOUND, `no such file: ${op.fsRead.path}`, false);\n }\n const res: FsReadResponse = { content: bytes, totalSize: String(bytes.length) };\n return ok(req.requestId, { $case: \"fsRead\", fsRead: res });\n }\n case \"fsWrite\": {\n const path = normalize(op.fsWrite.path);\n const next = op.fsWrite.append\n ? concat(this.files.get(path) ?? new Uint8Array(0), op.fsWrite.content)\n : op.fsWrite.content;\n this.files.set(path, next);\n const res: FsWriteResponse = { bytesWritten: String(op.fsWrite.content.length) };\n return ok(req.requestId, { $case: \"fsWrite\", fsWrite: res });\n }\n case \"fsList\": {\n const prefix = normalize(op.fsList.path).replace(/\\/?$/, \"/\");\n const res: FsListResponse = {\n entries: [...this.files.keys()]\n .filter((p) => p.startsWith(prefix))\n .map((p) => {\n const bytes = this.files.get(p)!;\n const rel = p.slice(prefix.length);\n return {\n name: rel.split(\"/\").pop() ?? rel,\n path: rel,\n kind: FsEntryKind.FS_ENTRY_KIND_FILE,\n size: String(bytes.length),\n modifiedMs: \"0\",\n mode: 0o644,\n };\n }),\n };\n return ok(req.requestId, { $case: \"fsList\", fsList: res });\n }\n case \"fsStat\": {\n const bytes = this.files.get(normalize(op.fsStat.path));\n const res: FsStatResponse = bytes\n ? {\n exists: true,\n entry: {\n name: normalize(op.fsStat.path).split(\"/\").pop() ?? \"\",\n path: op.fsStat.path,\n kind: FsEntryKind.FS_ENTRY_KIND_FILE,\n size: String(bytes.length),\n modifiedMs: \"0\",\n mode: 0o644,\n },\n }\n : { exists: false, entry: undefined };\n return ok(req.requestId, { $case: \"fsStat\", fsStat: res });\n }\n case \"desktopEnsure\": {\n // The desktop STREAM (view) is DISPLAY-gated, not consent-gated: the real\n // agent registers the channel + captures frames regardless of screen-control\n // consent (`register_desktop` in hub.rs sets `allow_input` from consent and\n // the pump captures anyway) — only INPUT injection is gated. This stub has a\n // (mock) display and does not model input injection, so desktopEnsure always\n // succeeds; `consented` only affects the computer-use INPUT plane.\n return ok(req.requestId, {\n $case: \"desktopEnsure\",\n desktopEnsure: {\n channel: { channelId: \"mock-desktop\", workspaceId: \"\", agentId: \"\", kind: 1, port: 6080 },\n display: { id: \":99\", width: 1024, height: 768, virtual: true },\n },\n });\n }\n case \"ptyOpen\": {\n // The PTY plane is display-INDEPENDENT and has NO consent gate (unlike\n // desktopEnsure) — a terminal works on a headless machine. Returns a PTY\n // StreamChannel on the 7681 port.\n return ok(req.requestId, {\n $case: \"ptyOpen\",\n ptyOpen: {\n ptyId: \"mock-pty\",\n channel: { channelId: \"mock-pty\", workspaceId: \"\", agentId: \"\", kind: 1, port: 7681 },\n },\n });\n }\n default:\n return errorResponse(req.requestId, ErrorCode.ERROR_CODE_UNSUPPORTED, `mock does not implement ${op.$case}`, false);\n }\n }\n}\n\nfunction defaultEcho(req: ExecRequest, hostname: string): ExecResponse {\n // A trivial deterministic exec: echo the joined argv; if argv mentions\n // HOSTNAME, emit the mock hostname so terminal-style asserts work.\n const joined = req.command.join(\" \");\n const stdout = /hostname|HOSTNAME/.test(joined) ? hostname : joined;\n return {\n exitCode: 0,\n stdout: encoder.encode(`${stdout}\\n`),\n stderr: new Uint8Array(0),\n timedOut: false,\n durationMs: \"1\",\n };\n}\n\nfunction ok(requestId: string, result: NonNullable<ControlResponse[\"result\"]>): ControlResponse {\n return { requestId, error: undefined, result };\n}\n\nfunction errorResponse(requestId: string, code: ErrorCode, message: string, retryable: boolean): ControlResponse {\n const error: AgentError = { code, message, retryable, detail: {} };\n return { requestId, error, result: undefined };\n}\n\nfunction normalize(path: string): string {\n // Collapse to a leading-slash absolute form for stable keys.\n const trimmed = path.replace(/\\/+$/, \"\");\n return trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n","// `RoutingSandboxSession` — the per-session hot-swap routing proxy (M7).\n//\n// THE load-bearing SDK finding (dossier §10.3 / §20-M7): when a box is injected\n// NON-OWNED into a turn (`ownedSandbox.session`), the agent SDK's sandbox\n// capabilities bind to that ONE session OBJECT ONCE and call ITS methods\n// (`exec`/`execCommand`/`readFile`/`listDir`/`resolveExposedPort`/…) per tool\n// call WITHOUT re-resolving the session. So to make the active sandbox flippable\n// mid-turn we cannot swap the object the SDK holds — we must give the SDK ONE\n// STABLE session-shaped object that, on EACH method call, re-reads the\n// per-session active pointer `(active_sandbox_id, active_epoch)` and DISPATCHES\n// to the CURRENTLY-active backend session (Modal or selfhosted).\n//\n// The contract (dossier §10.3):\n// - ONE stable object implementing the `SandboxSessionLike` structural surface.\n// - On EVERY op, re-read `(activeSandboxId, activeEpoch)` via `readPointer`.\n// - Cache the resolved backend session keyed by `activeEpoch`; when the epoch\n// changes mid-turn (a swap bumped it), re-resolve so the NEXT op hits the new\n// backend. Single active at a time (NOT parallel multi-attach).\n// - An in-flight op fenced by a STALE `active_epoch` (the backend rejects with a\n// fence error, OR the pointer moved under us between read and dispatch)\n// RETRIES against the new active sandbox — reusing the existing fenced-retry\n// role. Bounded retries so a pathological swap-storm can't loop forever.\n//\n// This module is agent-loop-free (it lives in the sandbox leaf). It depends ONLY\n// on injected closures (`readPointer` + `resolveActiveBackend`), so the API\n// (`withChannelA`) and the worker (`resumeBoxForTurn`/the turn) wire it to the\n// real `readActiveSandbox` DAO + a backend resolver without coupling the leaf to\n// `@opengeni/db`.\n\nimport type { ExposedPortEndpoint } from \"../stream-port\";\n\n/** The per-session active-sandbox pointer the proxy re-reads on every op. Mirror\n * of `@opengeni/db`'s `ActiveSandboxPointer` (structural, so the leaf does not\n * import the DB package). `activeSandboxId === null` == \"use the session's own\n * group sandbox\" (the default/backward-compat target). */\nexport interface ActivePointer {\n activeSandboxId: string | null;\n activeEpoch: number;\n /** The session's working directory — the path/cwd base for a selfhosted backend\n * (threaded into the SelfhostedSession via the resolver). `null`/absent ⇒ the\n * default workspace_root behavior. Optional so the default-pointer fallback\n * (`{ activeSandboxId: null, activeEpoch: 0 }`) the readPointer wiring synthesizes\n * when no row exists needs no extra field. Only the selfhosted branch reads it;\n * the modal/default branches ignore it. */\n workingDir?: string | null;\n}\n\n/**\n * The structural slice of a backend session the routing proxy forwards to. It is\n * a superset-by-optionality of every backend's surface (Modal's `SandboxSession`\n * AND the `SelfhostedSession`): each method is optional because a heterogeneous\n * target may or may not implement it, and the proxy reflects that at call-time.\n */\nexport interface RoutableBackendSession {\n state?: unknown;\n exec?(args: unknown): Promise<unknown>;\n execCommand?(args: unknown): Promise<string>;\n writeStdin?(args: unknown): Promise<string>;\n readFile?(args: unknown): Promise<string | Uint8Array>;\n writeFile?(args: unknown): Promise<unknown>;\n createEditor?(runAs?: string): unknown;\n listDir?(args: unknown): Promise<unknown>;\n pathExists?(path: string, runAs?: string): Promise<boolean>;\n viewImage?(args: unknown): Promise<unknown>;\n materializeEntry?(args: unknown): Promise<void>;\n supportsPty?(): boolean;\n resolveExposedPort?(port: number): Promise<ExposedPortEndpoint>;\n serializeSessionState?(): Promise<unknown>;\n // The native-desktop control-plane surface (self-hosted / macOS): a backend that\n // drives the desktop NATIVELY (input inject + frame capture) instead of shelling\n // xdotool/scrot over `exec`. Optional like the rest — only a `SelfhostedSession`\n // implements these; a Modal box does not. The computer-use capability duck-types\n // on their PRESENCE (`isNativeDesktopSession`) to pick the native vs exec Computer.\n // `event` is kept `unknown` (mirroring the interface's structural style + avoiding\n // a proto import into the leaf); the SelfhostedSession takes `DesktopInputRequest[\"event\"]`.\n desktopInput?(event: unknown): Promise<void>;\n screenshot?(): Promise<{ png: Uint8Array; width: number; height: number }>;\n}\n\n/** The resolved active backend for an epoch: the live session + the sandbox id it\n * belongs to (`null` == the group sandbox) so a fence-retry can detect a move. */\nexport interface ResolvedActiveBackend {\n session: RoutableBackendSession;\n /** The sandbox id this backend serves (`null` == the session's group sandbox). */\n sandboxId: string | null;\n /** A label for diagnostics (\"modal\" | \"selfhosted\" | the sandbox name). */\n kind: string;\n}\n\nexport interface RoutingSandboxSessionDeps {\n /**\n * The DEFAULT backend resolved at construction time (the same shape `resolve()`\n * caches as `lastResolved`). This seeds `session.state` BEFORE the first op so a\n * consumer that reads `session.state.manifest` at turn START — the @openai/agents\n * SDK does, before any tool runs — sees the real default backend's state object\n * (and writes to `session.state.manifest = …` land on it by reference), instead\n * of an empty `{}` that crashes serializeManifestEnvironment /\n * validateProvidedSessionManifestUpdate. The default-pointer case\n * (`activeSandboxId === null`) resolves synchronously to this same backend, so\n * seeding it here is byte-identical to what the first `resolve()` would produce.\n */\n defaultResolved?: ResolvedActiveBackend;\n /** Re-read the per-session active pointer. Called on EVERY op (the per-call\n * re-resolve that makes a mid-turn swap visible to the next tool call). */\n readPointer(): Promise<ActivePointer>;\n /**\n * Resolve the active backend session for a pointer. The proxy memoizes the\n * result by `activeEpoch`, so this is called at most once per epoch (per op the\n * pointer is re-read, but the heavy resolve only re-runs when the epoch moved).\n * For `pointer.activeSandboxId === null` this returns the default/group backend\n * (typically the already-established turn box); for a non-null target it builds\n * the target backend (a sibling Modal box or a selfhosted machine session).\n */\n resolveActiveBackend(pointer: ActivePointer): Promise<ResolvedActiveBackend>;\n /** Max fence/stale retries within a single op before surfacing the error.\n * Defaults to 3 — enough to absorb a couple of concurrent swaps, bounded so a\n * swap-storm cannot loop forever. */\n maxFenceRetries?: number;\n /** Optional structured-log sink for swap/fence transitions (diagnostics). */\n onTransition?: (event: RoutingTransitionEvent) => void;\n}\n\nexport interface RoutingTransitionEvent {\n type: \"resolved\" | \"fenced-retry\" | \"epoch-changed\";\n fromEpoch: number;\n toEpoch: number;\n sandboxId: string | null;\n kind: string;\n}\n\n/** Thrown when the active backend does not implement the requested op (a\n * heterogeneous target whose surface lacks the method the caller reached for). */\nexport class RoutingUnsupportedError extends Error {\n readonly name = \"RoutingUnsupportedError\";\n constructor(op: string, kind: string) {\n super(`the active sandbox (${kind}) does not support \"${op}\"`);\n }\n}\n\n/** Recognize a stale-epoch FENCE error from a backend op so the proxy retries\n * against the re-resolved active sandbox (the existing fenced-retry role). A\n * selfhosted `SelfhostedControlError` carries `.fenced`; a generic fence is\n * matched on the message as a fallback. */\nfunction isFenceError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n if ((error as { fenced?: unknown }).fenced === true) {\n return true;\n }\n const name = typeof (error as { name?: unknown }).name === \"string\" ? (error as { name: string }).name : \"\";\n const message = error instanceof Error ? error.message : String((error as { message?: unknown }).message ?? \"\");\n const haystack = `${name} ${message}`.toLowerCase();\n return haystack.includes(\"fenced\") || haystack.includes(\"epoch\") && haystack.includes(\"super\");\n}\n\n/**\n * ONE stable session-shaped object the SDK binds to. Every method re-reads the\n * pointer, resolves the active backend (cached by epoch), and dispatches. A\n * stale-epoch fence (the pointer moved mid-op) re-resolves and retries.\n *\n * The proxy implements ALL of the consumed surface so the SDK (which binds method\n * presence ONCE) always sees `exec`/`readFile`/`resolveExposedPort`/… present. If\n * the CURRENTLY-active backend lacks a method, the proxy applies the natural\n * fallback (`exec`→`execCommand`) or throws `RoutingUnsupportedError` — degrade is\n * a value, not a crash.\n *\n * `state` is a STABLE getter so a consumer reading `session.state` (channel-a's\n * `readInstanceId`, the docker-network decoration) gets a coherent snapshot of the\n * currently-active backend without a method call.\n */\nexport class RoutingSandboxSession implements RoutableBackendSession {\n private readonly deps: RoutingSandboxSessionDeps;\n private readonly maxFenceRetries: number;\n // The per-epoch resolved-backend cache. Keyed by activeEpoch: a swap bumps the\n // epoch, invalidating the cache so the NEXT op re-resolves the new backend.\n private cachedEpoch: number | undefined;\n private cached: ResolvedActiveBackend | undefined;\n // The last-resolved backend, exposed via the `state` getter (a method-free read\n // of the active backend's `state`). Updated on every resolve.\n private lastResolved: ResolvedActiveBackend | undefined;\n\n // The native-desktop control-plane ops (self-hosted / macOS). Declared as OPTIONAL\n // INSTANCE fields — NOT prototype methods — because their PRESENCE is the selection\n // signal `isNativeDesktopSession` (sandbox-computer.ts) uses to pick the native vs\n // exec-shelling Computer. If they were unconditional prototype methods, this proxy\n // would ALWAYS duck-type as native — misclassifying a Modal-fronting proxy (whose\n // real backend has no native surface) and driving CGEvent/screenshot ops at a box\n // that cannot serve them. So the constructor assigns them ONLY when the\n // construction-time default backend actually implements the native surface (below).\n desktopInput?: (event: unknown) => Promise<void>;\n screenshot?: () => Promise<{ png: Uint8Array; width: number; height: number }>;\n\n constructor(deps: RoutingSandboxSessionDeps) {\n this.deps = deps;\n this.maxFenceRetries = deps.maxFenceRetries ?? 3;\n\n // Conditionally expose the native-desktop surface. Presence = the computer-use\n // native/exec selection signal (isNativeDesktopSession duck-types on\n // desktopInput+screenshot being functions); unconditional presence would\n // misclassify Modal-fronting proxies as native. So we mint these per-INSTANCE\n // arrow properties ONLY when the default backend resolved at construction is\n // itself native-capable — the machine-primary (selfhosted) case. Each dispatches\n // to the ACTIVE backend at call-time; if a mid-turn swap lands on a backend that\n // lacks the op (a cross-kind swap to a Modal box), dispatch throws\n // RoutingUnsupportedError — a legible tool failure, never a silent Linux-tool\n // shell onto a Mac.\n const def = deps.defaultResolved?.session;\n if (typeof def?.desktopInput === \"function\" && typeof def?.screenshot === \"function\") {\n this.desktopInput = (event: unknown) =>\n this.dispatch(\"desktopInput\", async (s) => {\n if (!s.desktopInput) {\n throw new RoutingUnsupportedError(\"desktopInput\", this.cached?.kind ?? \"unknown\");\n }\n return s.desktopInput(event);\n });\n this.screenshot = () =>\n this.dispatch(\"screenshot\", async (s) => {\n if (!s.screenshot) {\n throw new RoutingUnsupportedError(\"screenshot\", this.cached?.kind ?? \"unknown\");\n }\n return s.screenshot();\n });\n }\n }\n\n /**\n * A method-free read of the active backend's `state` (best-effort: the last\n * resolved backend, falling back to the default backend resolved at construction\n * so this is non-empty BEFORE the first op). Consumers that read `session.state`\n * (instanceId/decoration) get the active backend's state.\n *\n * CRITICAL: this returns the underlying backend's `state` OBJECT BY REFERENCE\n * (never a fresh `{}` when a backend exists). The @openai/agents SDK both READS\n * `session.state.manifest` and WRITES `session.state.manifest = nextManifest`\n * (providedSessionManifest); returning the live object by reference means those\n * property writes land on the real backend state and persist. Only when NO\n * backend has been resolved yet (no default seeded, no op dispatched) do we\n * return an empty object — and that path no longer occurs in the turn wiring,\n * which always seeds `defaultResolved`.\n */\n get state(): unknown {\n const backendState = (this.lastResolved ?? this.deps.defaultResolved)?.session.state;\n return backendState ?? {};\n }\n\n /**\n * Re-read the pointer and resolve the active backend, using the per-epoch cache.\n * The cache is keyed by `activeEpoch`: if the epoch is unchanged we return the\n * cached backend; if it moved (a swap) we re-resolve and update the cache. This\n * is THE per-call re-read that makes a mid-turn swap land on the next op.\n */\n private async resolve(): Promise<ResolvedActiveBackend> {\n const pointer = await this.deps.readPointer();\n if (this.cachedEpoch === pointer.activeEpoch && this.cached) {\n return this.cached;\n }\n const fromEpoch = this.cachedEpoch ?? pointer.activeEpoch;\n const resolved = await this.deps.resolveActiveBackend(pointer);\n this.cachedEpoch = pointer.activeEpoch;\n this.cached = resolved;\n this.lastResolved = resolved;\n this.deps.onTransition?.({\n type: this.cachedEpoch !== undefined && fromEpoch !== pointer.activeEpoch ? \"epoch-changed\" : \"resolved\",\n fromEpoch,\n toEpoch: pointer.activeEpoch,\n sandboxId: resolved.sandboxId,\n kind: resolved.kind,\n });\n return resolved;\n }\n\n /**\n * Dispatch an op to the currently-active backend, retrying on a stale-epoch\n * fence. The sequence per attempt:\n * 1. re-read the pointer + resolve the active backend (cached by epoch),\n * 2. run `fn(activeSession)`,\n * 3. on a FENCE error (the pointer moved under us / the backend rejected a\n * stale epoch), INVALIDATE the cache and retry against the re-resolved\n * active sandbox — up to `maxFenceRetries`.\n * A non-fence error propagates immediately (it is a real op failure, not a swap\n * race).\n */\n private async dispatch<T>(op: string, fn: (session: RoutableBackendSession) => Promise<T>): Promise<T> {\n let attempt = 0;\n let lastError: unknown;\n while (attempt <= this.maxFenceRetries) {\n const backend = await this.resolve();\n try {\n return await fn(backend.session);\n } catch (error) {\n if (!isFenceError(error)) {\n throw error;\n }\n // Stale-epoch fence: the active pointer moved mid-op. Drop the cache so\n // the next resolve re-reads the NEW pointer and the op lands on the new\n // active sandbox (the fenced-retry role). Bounded by maxFenceRetries.\n lastError = error;\n this.cachedEpoch = undefined;\n this.cached = undefined;\n this.deps.onTransition?.({\n type: \"fenced-retry\",\n fromEpoch: backend.sandboxId === null ? 0 : 0,\n toEpoch: 0,\n sandboxId: backend.sandboxId,\n kind: backend.kind,\n });\n attempt += 1;\n }\n }\n // Exhausted retries against a relentless swap-storm: surface the fence so the\n // caller (turn) backs off — never loop forever.\n throw lastError ?? new Error(`routing op \"${op}\" exhausted fence retries`);\n }\n\n // ── The forwarded structural surface ──────────────────────────────────────\n // Every method is PRESENT on the proxy (the SDK binds presence once) and\n // dispatches to the active backend at call-time. A missing backend method\n // degrades via the natural fallback or RoutingUnsupportedError.\n\n async exec(args: unknown): Promise<unknown> {\n return this.dispatch(\"exec\", async (s) => {\n if (s.exec) {\n return s.exec(args);\n }\n // Some backends (selfhosted) only expose exec; others only execCommand.\n if (s.execCommand) {\n return s.execCommand(args);\n }\n throw new RoutingUnsupportedError(\"exec\", this.cached?.kind ?? \"unknown\");\n });\n }\n\n async execCommand(args: unknown): Promise<string> {\n return this.dispatch(\"execCommand\", async (s) => {\n if (s.execCommand) {\n return s.execCommand(args);\n }\n if (s.exec) {\n const r = (await s.exec(args)) as { stdout?: string; output?: string };\n return r.stdout ?? r.output ?? \"\";\n }\n throw new RoutingUnsupportedError(\"execCommand\", this.cached?.kind ?? \"unknown\");\n });\n }\n\n async writeStdin(args: unknown): Promise<string> {\n return this.dispatch(\"writeStdin\", async (s) => {\n if (!s.writeStdin) {\n throw new RoutingUnsupportedError(\"writeStdin\", this.cached?.kind ?? \"unknown\");\n }\n return s.writeStdin(args);\n });\n }\n\n async readFile(args: unknown): Promise<string | Uint8Array> {\n return this.dispatch(\"readFile\", async (s) => {\n if (!s.readFile) {\n throw new RoutingUnsupportedError(\"readFile\", this.cached?.kind ?? \"unknown\");\n }\n return s.readFile(args);\n });\n }\n\n async writeFile(args: unknown): Promise<unknown> {\n return this.dispatch(\"writeFile\", async (s) => {\n if (!s.writeFile) {\n throw new RoutingUnsupportedError(\"writeFile\", this.cached?.kind ?? \"unknown\");\n }\n return s.writeFile(args);\n });\n }\n\n async listDir(args: unknown): Promise<unknown> {\n return this.dispatch(\"listDir\", async (s) => {\n if (!s.listDir) {\n throw new RoutingUnsupportedError(\"listDir\", this.cached?.kind ?? \"unknown\");\n }\n return s.listDir(args);\n });\n }\n\n async pathExists(path: string, runAs?: string): Promise<boolean> {\n return this.dispatch(\"pathExists\", async (s) => {\n if (!s.pathExists) {\n throw new RoutingUnsupportedError(\"pathExists\", this.cached?.kind ?? \"unknown\");\n }\n return s.pathExists(path, runAs);\n });\n }\n\n async viewImage(args: unknown): Promise<unknown> {\n return this.dispatch(\"viewImage\", async (s) => {\n if (!s.viewImage) {\n throw new RoutingUnsupportedError(\"viewImage\", this.cached?.kind ?? \"unknown\");\n }\n return s.viewImage(args);\n });\n }\n\n async materializeEntry(args: unknown): Promise<void> {\n return this.dispatch(\"materializeEntry\", async (s) => {\n if (!s.materializeEntry) {\n throw new RoutingUnsupportedError(\"materializeEntry\", this.cached?.kind ?? \"unknown\");\n }\n return s.materializeEntry(args);\n });\n }\n\n /** PTY support reflects the LAST-resolved backend (a synchronous probe; the SDK\n * reads it to decide if the terminal is interactive). It cannot re-read the\n * pointer (synchronous), so it answers from the last resolve — coherent with\n * the resolve the surrounding op already performed. Defaults false before the\n * first resolve. */\n supportsPty(): boolean {\n const s = (this.lastResolved ?? this.deps.defaultResolved)?.session;\n return Boolean(s?.supportsPty?.());\n }\n\n /** createEditor is a synchronous factory in the SDK surface; it binds to the\n * last-resolved backend's editor (or the default backend before the first op).\n * Returns undefined when the active backend has no editor (channel-a falls back\n * to its exec-based write path). */\n createEditor(runAs?: string): unknown {\n return (this.lastResolved ?? this.deps.defaultResolved)?.session.createEditor?.(runAs);\n }\n\n async resolveExposedPort(port: number): Promise<ExposedPortEndpoint> {\n return this.dispatch(\"resolveExposedPort\", async (s) => {\n if (!s.resolveExposedPort) {\n throw new RoutingUnsupportedError(\"resolveExposedPort\", this.cached?.kind ?? \"unknown\");\n }\n return s.resolveExposedPort(port);\n });\n }\n\n /** Serialize the active backend's session state. Used by the resume-by-id seam\n * to fold the live box onto the lease. Dispatches to the active backend. */\n async serializeSessionState(): Promise<unknown> {\n return this.dispatch(\"serializeSessionState\", async (s) => {\n if (!s.serializeSessionState) {\n // No-op for a backend with no serializable state (selfhosted state is\n // re-addressed, not snapshotted) — surface undefined, not an error.\n return undefined;\n }\n return s.serializeSessionState();\n });\n }\n\n /** Force a resolve (priming the proxy before the first op so `state`/`supportsPty`\n * read a real backend). Optional — every op resolves lazily anyway. */\n async prime(): Promise<ResolvedActiveBackend> {\n return this.resolve();\n }\n}\n","// `makeActiveBackendResolver` — builds the `resolveActiveBackend` closure the\n// `RoutingSandboxSession` calls to turn an active pointer into a live backend\n// session (M7). It is the heterogeneous-dispatch core: a pointer's target is\n// EITHER the session's own group sandbox (the default, `activeSandboxId === null`)\n// OR a first-class named sandbox the session swapped to — a sibling Modal box or\n// a selfhosted machine.\n//\n// This lives in the agent-loop-free leaf and depends ONLY on injected closures +\n// the selfhosted session builder, so the API/worker wire it to the real DB\n// (`getSandbox`/`getEnrollment`/`readActiveSandbox`) + the live NATS ControlRpc\n// without coupling the leaf to `@opengeni/db`.\n//\n// The DEFAULT target (the group box) is supplied as an already-established\n// session (the turn box `resumeBoxForTurn` produced, or the Channel-A established\n// handle) — the proxy does NOT re-establish it (the lease owns its lifecycle). A\n// NON-DEFAULT selfhosted target builds a `SelfhostedSession` bound to the target's\n// enrollment agentId, fenced under the swap's active_epoch. A non-default MODAL\n// target is established via the injected `establishModalTarget` resolver (the\n// API/worker pass a resume-by-id closure for the sibling box's lease).\n\nimport { buildSelfhostedBackendSession, type SelfhostedRelayConfig } from \"../selfhosted/session\";\nimport type { ControlRpc } from \"../selfhosted/control-rpc\";\nimport type {\n ActivePointer,\n RoutableBackendSession,\n ResolvedActiveBackend,\n} from \"./routing-session\";\n\n/** The structural slice of a first-class sandbox the resolver reads (mirror of\n * `@opengeni/db`'s `SandboxRecord`; structural so the leaf does not import DB). */\nexport interface RoutableSandbox {\n id: string;\n kind: \"modal\" | \"selfhosted\" | string;\n name: string;\n /** For a selfhosted sandbox this is its enrollment id (== the agent id the\n * control-plane subject `agent.<ws>.<id>.rpc` addresses). Null for modal. */\n enrollmentId: string | null;\n}\n\nexport interface ActiveBackendResolverDeps {\n /** The workspace the session belongs to (the control-plane subject scope). */\n workspaceId: string;\n /** The session's own group sandbox session — the DEFAULT target\n * (`activeSandboxId === null`). Already established (lease-owned); the proxy\n * never re-establishes it. */\n defaultBackend: RoutableBackendSession;\n /** A label for the default backend (its backend id: \"modal\"/\"selfhosted\"/…). */\n defaultKind: string;\n /** Look up a first-class sandbox by id (the swap target). Returns null when the\n * id is unknown or not in this workspace (the caller 409s the swap). */\n getSandbox(sandboxId: string): Promise<RoutableSandbox | null>;\n /** Build a live `ControlRpc` for the selfhosted control plane (the request-\n * scoped NATS connection). Returns a ControlRpc whose offline/timeout maps to\n * agent_offline/agent_reconnecting (never a NotFound). */\n controlRpcFactory(): ControlRpc;\n /** The relay-URL shape config for selfhosted stream endpoints. */\n relay: SelfhostedRelayConfig;\n /** Establish (resume-by-id) a NON-DEFAULT modal target's box session for a swap.\n * Supplied by the API/worker (a closure over the sibling sandbox's lease). When\n * absent, a modal swap target surfaces as unsupported (the caller validated\n * liveness, so this is the \"modal swap not wired in this context\" guard). */\n establishModalTarget?: (sandbox: RoutableSandbox) => Promise<RoutableBackendSession>;\n /** Override the selfhosted control-op timeout (tests). */\n selfhostedTimeoutMs?: number;\n /**\n * The run's declared sandbox environment — the SAME `Record<string,string>` the\n * worker turn threads into the agent's TARGET manifest (and into the group box at\n * create). Threaded into a selfhosted swap target's session so its\n * `state.manifest.environment` EQUALS the turn's, making the SDK's per-turn\n * provided-session manifest-env delta empty (validateNoEnvironmentDelta).\n * WITHOUT this a pin-to-vm turn throws \"Live sandbox sessions cannot change\n * manifest environment variables\". Omitted → `{}` (the test/negotiation path).\n */\n environment?: Record<string, string>;\n /**\n * A pre-established selfhosted session to PIN for the STEADY-STATE machine\n * pointer (the worker turn's machine-primary path, Stage D). When the pointer\n * targets THIS sandbox at THIS epoch, the resolver returns this SAME instance\n * instead of building a fresh `SelfhostedSession`. This is the instance-identity\n * pin: the SDK reads/writes `state.manifest` at turn START via the proxy's `state`\n * getter (which reads the default/last-resolved backend's state) and then reads it\n * per op via this resolver — those MUST land on ONE SelfhostedSession/manifest, or\n * a turn-start manifest write is invisible to the per-op reads (two-instance\n * divergence). A swap AWAY (a different sandbox id, or the same id at a moved epoch)\n * falls through to a fresh build under the new epoch. Omitted for the API/live-swap\n * path (which always builds fresh — it has no pre-established turn session).\n */\n pinnedSelfhosted?: { sandboxId: string; epoch: number; session: RoutableBackendSession };\n}\n\n/** Thrown when a swap target cannot be resolved (unknown sandbox, or a modal\n * target with no establisher in this context). The caller maps it to a 409. */\nexport class ActiveBackendUnresolvableError extends Error {\n readonly name = \"ActiveBackendUnresolvableError\";\n constructor(message: string) {\n super(message);\n }\n}\n\n/**\n * Build the `resolveActiveBackend(pointer)` closure for a `RoutingSandboxSession`.\n * The returned closure is re-invoked by the proxy whenever the active_epoch moves\n * (the per-epoch cache miss), so it must be cheap-and-correct for the steady-state\n * (default pointer → the already-established group box) and build a fresh backend\n * for a swap target.\n *\n * - `activeSandboxId === null` → the default group backend (no re-establish).\n * - a selfhosted target → a `SelfhostedSession` bound to the enrollment agentId,\n * fenced under `pointer.activeEpoch` (echoed on every ControlRequest so the\n * agent can reject a stale op with ERROR_CODE_FENCED — the swap-race fence).\n * - a modal target → `establishModalTarget` (the resume-by-id closure), else\n * unresolvable.\n */\nexport function makeActiveBackendResolver(\n deps: ActiveBackendResolverDeps,\n): (pointer: ActivePointer) => Promise<ResolvedActiveBackend> {\n return async (pointer: ActivePointer): Promise<ResolvedActiveBackend> => {\n // The DEFAULT target: the session's own group sandbox (backward-compat). The\n // proxy routes to the already-established box; the lease owns its lifecycle.\n if (pointer.activeSandboxId === null) {\n return { session: deps.defaultBackend, sandboxId: null, kind: deps.defaultKind };\n }\n\n // INSTANCE PIN (Stage D machine-primary): the steady-state machine pointer\n // returns the pre-established turn session BY REFERENCE — never a fresh build —\n // so the turn-start manifest write + the per-op reads land on ONE\n // SelfhostedSession/manifest. Matched on BOTH the sandbox id AND the epoch: a\n // swap away (different id) or a swap-back (same id, higher epoch) falls through\n // to a fresh build fenced under the CURRENT epoch (the stale pinned instance is\n // fenced at the old epoch and must not be reused).\n if (\n deps.pinnedSelfhosted\n && pointer.activeSandboxId === deps.pinnedSelfhosted.sandboxId\n && pointer.activeEpoch === deps.pinnedSelfhosted.epoch\n ) {\n return { session: deps.pinnedSelfhosted.session, sandboxId: pointer.activeSandboxId, kind: \"selfhosted\" };\n }\n\n const sandbox = await deps.getSandbox(pointer.activeSandboxId);\n if (!sandbox) {\n throw new ActiveBackendUnresolvableError(\n `active sandbox ${pointer.activeSandboxId} not found in workspace ${deps.workspaceId}`,\n );\n }\n\n if (sandbox.kind === \"selfhosted\") {\n if (!sandbox.enrollmentId) {\n throw new ActiveBackendUnresolvableError(\n `selfhosted sandbox ${sandbox.id} has no enrollment (agent id) to address`,\n );\n }\n // Build a request-scoped selfhosted client bound to the target's workspace +\n // enrollment agentId, fenced under the swap's active_epoch. The agent echoes\n // the epoch and rejects a stale op with ERROR_CODE_FENCED → the proxy\n // re-resolves + retries against the new active sandbox. The SAME factory the\n // worker turn's machine-primary establish branch uses (one build shape).\n const { session } = await buildSelfhostedBackendSession({\n workspaceId: deps.workspaceId,\n relay: deps.relay,\n controlRpcFactory: deps.controlRpcFactory,\n agentId: sandbox.enrollmentId,\n epoch: pointer.activeEpoch,\n ...(deps.selfhostedTimeoutMs !== undefined ? { timeoutMs: deps.selfhostedTimeoutMs } : {}),\n // The turn's declared environment → the session's manifest.environment, so\n // the SDK's per-turn manifest-env delta is empty (no \"cannot change manifest\n // environment variables\" throw on a pin-to-vm turn).\n ...(deps.environment !== undefined ? { environment: deps.environment } : {}),\n // The session's working directory (per-session pointer) → the path/cwd base\n // for this selfhosted backend. Absent/empty ⇒ the default workspace_root.\n ...(pointer.workingDir ? { workingDir: pointer.workingDir } : {}),\n });\n return { session: session as RoutableBackendSession, sandboxId: sandbox.id, kind: \"selfhosted\" };\n }\n\n if (sandbox.kind === \"modal\") {\n if (!deps.establishModalTarget) {\n throw new ActiveBackendUnresolvableError(\n `modal swap target ${sandbox.id} cannot be established in this context (no establisher wired)`,\n );\n }\n const session = await deps.establishModalTarget(sandbox);\n return { session, sandboxId: sandbox.id, kind: \"modal\" };\n }\n\n throw new ActiveBackendUnresolvableError(\n `unsupported swap target kind \"${sandbox.kind}\" for sandbox ${sandbox.id}`,\n );\n };\n}\n"],"mappings":"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","names":["DESKTOP_STREAM_PORT","TERMINAL_STREAM_PORT","SandboxBackend","DESKTOP_STREAM_PORT","DESKTOP_STREAM_PORT","SandboxBackend","collectSandboxEnvironment","parseExposedPorts","CAPABILITY_DESCRIPTORS","DESKTOP_STREAM_PORT","StreamTokenPayload","DESKTOP_STREAM_PORT","DESKTOP_STREAM_PORT","execResultOutput","execResultExitCode","inferExitFromOutput","DESKTOP_STREAM_PORT","DESKTOP_STREAM_PORT","DESKTOP_STREAM_PORT","stripExecBanner","shellQuote","exitCode","ok","ErrorCode","FsEntryKind","encoder","decoder","DESKTOP_STREAM_PORT","TERMINAL_STREAM_PORT","restoredState"]}
|