@opengeni/runtime 0.14.1 → 0.14.15

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.
Files changed (79) hide show
  1. package/dist/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
  2. package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
  3. package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
  4. package/dist/chunk-GNEGANUF.js.map +1 -0
  5. package/dist/codex-tool-search.d.ts +100 -0
  6. package/dist/context-compaction.d.ts +236 -0
  7. package/dist/history-sanitizer.d.ts +320 -0
  8. package/dist/index.d.ts +186 -718
  9. package/dist/index.js +612 -126
  10. package/dist/index.js.map +1 -1
  11. package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
  12. package/dist/mcp-network.d.ts +32 -35
  13. package/dist/metrics.d.ts +33 -0
  14. package/dist/sandbox/capabilities.d.ts +11 -0
  15. package/dist/sandbox/channel-a.d.ts +226 -0
  16. package/dist/sandbox/display-stack.d.ts +99 -0
  17. package/dist/sandbox/errors.d.ts +16 -0
  18. package/dist/sandbox/exec-banner.d.ts +19 -0
  19. package/dist/sandbox/index.d.ts +190 -6
  20. package/dist/sandbox/index.js +20 -4
  21. package/dist/sandbox/op-correlation.d.ts +24 -0
  22. package/dist/sandbox/provider-errors.d.ts +32 -0
  23. package/dist/sandbox/provider-operation-gate.d.ts +12 -0
  24. package/dist/sandbox/providers/blaxel.d.ts +2 -0
  25. package/dist/sandbox/providers/cloudflare.d.ts +2 -0
  26. package/dist/sandbox/providers/daytona.d.ts +2 -0
  27. package/dist/sandbox/providers/docker.d.ts +2 -0
  28. package/dist/sandbox/providers/e2b.d.ts +2 -0
  29. package/dist/sandbox/providers/index.d.ts +11 -0
  30. package/dist/sandbox/providers/local.d.ts +2 -0
  31. package/dist/sandbox/providers/modal.d.ts +132 -0
  32. package/dist/sandbox/providers/none.d.ts +2 -0
  33. package/dist/sandbox/providers/runloop.d.ts +2 -0
  34. package/dist/sandbox/providers/selfhosted.d.ts +2 -0
  35. package/dist/sandbox/providers/types.d.ts +30 -0
  36. package/dist/sandbox/providers/vercel.d.ts +2 -0
  37. package/dist/sandbox/recording.d.ts +80 -0
  38. package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
  39. package/dist/sandbox/routing/routing-session.d.ts +401 -0
  40. package/dist/sandbox/run-credentials.d.ts +65 -0
  41. package/dist/sandbox/select.d.ts +120 -0
  42. package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
  43. package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
  44. package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
  45. package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
  46. package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
  47. package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
  48. package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
  49. package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
  50. package/dist/sandbox/selfhosted/session.d.ts +552 -0
  51. package/dist/sandbox/selfhosted/testing.d.ts +48 -0
  52. package/dist/sandbox/stream-port.d.ts +80 -0
  53. package/dist/sandbox/stream-token.d.ts +44 -0
  54. package/dist/sandbox/terminal-server.d.ts +67 -0
  55. package/dist/sandbox/toolspace-token.d.ts +23 -0
  56. package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
  57. package/dist/sandbox/workspace-archive.d.ts +39 -0
  58. package/dist/sandbox-computer.d.ts +227 -0
  59. package/dist/screenshot-error-card.d.ts +1 -0
  60. package/dist/skill-library.d.ts +10 -12
  61. package/dist/usage-telemetry.d.ts +45 -0
  62. package/dist/workspace-skills.d.ts +31 -0
  63. package/package.json +7 -7
  64. package/src/context-compaction.ts +600 -6
  65. package/src/history-sanitizer.ts +10 -92
  66. package/src/index.ts +301 -62
  67. package/src/sandbox/channel-a.ts +267 -81
  68. package/src/sandbox/index.ts +56 -94
  69. package/src/sandbox/provider-operation-gate.ts +110 -0
  70. package/src/sandbox/providers/docker.ts +13 -1
  71. package/src/sandbox/providers/modal.ts +433 -3
  72. package/src/sandbox/routing/routing-session.ts +84 -8
  73. package/src/sandbox/selfhosted/session.ts +7 -6
  74. package/src/sandbox/turn-tool-cancellation.ts +47 -7
  75. package/src/sandbox/workspace-archive.ts +176 -73
  76. package/src/sandbox-computer.ts +25 -75
  77. package/dist/chunk-HHWIZHQM.js.map +0 -1
  78. package/dist/index-BraazrAO.d.ts +0 -2931
  79. /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
@@ -1,2931 +0,0 @@
1
- import { Settings } from '@opengeni/config';
2
- import { SandboxBackend, CapabilityDescriptor, SandboxOs, SessionCapabilities, StreamTokenPayload, RunCredentialFile, RunCredentialAuthNeeded, RunCredentialRedaction, RunCredentialsResolution, SessionEventType, SessionStructuredCapabilities, FsListRequest, FsListResponse, FsReadRequest, FsReadResponse, FsWriteRequest, FsWriteResponse, FsDeleteRequest, FsDeleteResponse, FsMoveRequest, FsMoveResponse, FsMkdirRequest, FsMkdirResponse, GitStatusRequest, GitStatusResponse, GitDiffRequest, GitDiffResponse, GitLogRequest, GitLogResponse, GitShowRequest, GitShowResponse, TerminalExecRequest, TerminalExecResponse, PtyOpenRequest, PtyOpenResponse, PtyWriteRequest, PtyResizeRequest, PtyCloseRequest, GitChangedPayload, GitDiffHunk, GitFileStatusCode, CapabilityUnavailableReason } from '@opengeni/contracts';
3
- import { SandboxSessionLike, ExecCommandArgs, SandboxClient, Manifest, SandboxSessionState } from '@openai/agents/sandbox';
4
- import * as modal from 'modal';
5
- import { ControlRequest, ControlResponse, ErrorCode, AgentError, ExecResponse, DesktopInputRequest, ExecRequest } from '@opengeni/agent-proto';
6
-
7
- declare const WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION: 1;
8
- type WorkspaceTreeFingerprint = {
9
- algorithm: "sha256";
10
- sha256: string;
11
- entryCount: number;
12
- fileCount: number;
13
- totalFileBytes: number;
14
- };
15
- type WorkspaceArchiveDescriptor = {
16
- version: typeof WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION;
17
- revision: string;
18
- archiveSha256: string;
19
- archiveBytes: number;
20
- capturedAt: string;
21
- workspace: WorkspaceTreeFingerprint;
22
- };
23
- type VerifiedWorkspaceArchive = {
24
- bytes: Uint8Array;
25
- base64: string;
26
- descriptor: WorkspaceArchiveDescriptor;
27
- };
28
- type WorkspaceArchiveIntegrityCode = "archive_metadata_missing" | "archive_metadata_invalid" | "archive_base64_invalid" | "archive_hash_mismatch" | "archive_hydration_failed" | "workspace_fingerprint_unavailable" | "workspace_changed_during_capture" | "workspace_fingerprint_mismatch";
29
- declare class WorkspaceArchiveIntegrityError extends Error {
30
- readonly code: WorkspaceArchiveIntegrityCode;
31
- readonly name = "WorkspaceArchiveIntegrityError";
32
- readonly retryable: boolean;
33
- constructor(code: WorkspaceArchiveIntegrityCode, message: string, options?: {
34
- retryable?: boolean;
35
- });
36
- }
37
- declare function parseWorkspaceArchiveDescriptor(value: unknown): WorkspaceArchiveDescriptor | null;
38
- declare function readVerifiedWorkspaceArchive(base64: unknown, metadata: unknown): VerifiedWorkspaceArchive | null;
39
- /** Hashes names, kinds, modes, symlink targets and all file bytes through one
40
- * deterministic GNU tar stream. Only the aggregate is returned; paths and file
41
- * contents never enter logs or durable metadata. */
42
- declare function fingerprintSandboxWorkspace(session: unknown): Promise<WorkspaceTreeFingerprint>;
43
- /** Capture is accepted only when the complete tree is byte-identical immediately
44
- * before and after the provider snapshot. A concurrent mutation makes the
45
- * candidate non-durable and it is never folded onto the lease. */
46
- declare function captureVerifiedWorkspaceArchive(session: unknown, capturedAtMs?: number): Promise<VerifiedWorkspaceArchive>;
47
- declare function verifyRestoredWorkspace(session: unknown, descriptor: WorkspaceArchiveDescriptor): Promise<WorkspaceTreeFingerprint>;
48
-
49
- type RuntimeMetricsHooks = {
50
- onModelCall?: (input: {
51
- provider: string;
52
- outcome: "completed" | "failed";
53
- durationSeconds: number;
54
- }) => void;
55
- onSandboxCreate?: (input: {
56
- backend: string;
57
- outcome: "completed" | "failed";
58
- durationSeconds: number;
59
- }) => void;
60
- onSandboxWarmingTimeout?: (input: {
61
- backend: string;
62
- }) => void;
63
- /**
64
- * One completed Connected Machine (selfhosted) control op — the out-of-band
65
- * telemetry twin of the in-band fault rendering. `code` is the typed wire-code
66
- * NAME on a failure (bounded label cardinality); `healed` marks a success that
67
- * only landed after ≥1 retry (the leading indicator of the next unhealed fault);
68
- * `replyBytes` is set only on a payload-wall fault. Wired from the runtime's
69
- * `SelfhostedOpObserver` seam.
70
- */
71
- onSandboxOp?: (input: {
72
- backend: string;
73
- op: string;
74
- outcome: "ok" | "failed";
75
- code?: string;
76
- healed: boolean;
77
- retries: number;
78
- durationSeconds: number;
79
- replyBytes?: number;
80
- }) => void;
81
- };
82
-
83
- /**
84
- * Descriptor-table invariants, asserted once at registry build (and from a unit
85
- * test). This is the guardrail that keeps the static matrix internally coherent.
86
- * It validates the descriptor data only; the descriptor.backendId === SDK
87
- * client.backendId assertion (the deferred-from-P0.1 check) lives in
88
- * providers/index.ts because it must construct the real SDK clients.
89
- */
90
- declare function assertDescriptorRegistryInvariants(): void;
91
-
92
- declare class SandboxConfigError extends Error {
93
- readonly backend: SandboxBackend | string;
94
- constructor(backend: SandboxBackend | string, message: string);
95
- }
96
- /** A resume-only caller was handed a warm lease without a provider identity it
97
- * can resume. The lease-aware caller must retire that exact epoch and re-enter
98
- * admission; silently creating here would bypass the single-spawner guard. */
99
- declare class SandboxResumeStateUnavailableError extends Error {
100
- readonly backend: SandboxBackend | string;
101
- constructor(backend: SandboxBackend | string);
102
- }
103
- declare class SandboxProviderUnavailableError extends Error {
104
- readonly backend: SandboxBackend | string;
105
- constructor(backend: SandboxBackend | string);
106
- }
107
-
108
- interface ProviderConstructionContext {
109
- settings: Settings;
110
- /** The env map for the box (collectSandboxEnvironment / per-run environment). */
111
- environment: Record<string, string>;
112
- /**
113
- * Parsed exposed ports (config string -> number[]); already includes the
114
- * desktop stream port (6080) when this is a desktop tier with desktop enabled
115
- * and the provider cannot expose ports on demand (the merge happens in
116
- * createSandboxClient before build()).
117
- */
118
- exposedPorts: number[];
119
- }
120
- interface ProviderRegistration {
121
- backend: SandboxBackend;
122
- descriptor: CapabilityDescriptor;
123
- /**
124
- * Validate that the settings carry the credentials/config this provider
125
- * REQUIRES. Throw SandboxConfigError on any missing/contradictory field.
126
- * Pure — no network. Called by both the factory and a deploy-time preflight.
127
- * The factory calls this before build(), so build() may assume valid settings.
128
- */
129
- validateCredentials(settings: Settings): void;
130
- /**
131
- * Build the raw SDK SandboxClient. Returns undefined ONLY for "none".
132
- * The factory calls validateCredentials() first, so build() can assume valid.
133
- */
134
- build(ctx: ProviderConstructionContext): unknown;
135
- }
136
-
137
- declare const PROVIDER_REGISTRY: Record<SandboxBackend, ProviderRegistration>;
138
- /**
139
- * Assert the descriptor table AND that each registered provider's SDK client
140
- * reports the backendId its descriptor claims. The latter is the
141
- * deferred-from-P0.1 invariant — it can only run here because it constructs the
142
- * real clients. Called once at registry build (and from a unit test).
143
- */
144
- declare function assertProviderRegistryInvariants(): void;
145
-
146
- /**
147
- * Provider sandbox failure classification.
148
- *
149
- * This is deliberately structural and fail-closed. DNS failures often contain
150
- * the word "not found" (`ENOTFOUND`) while gRPC status values are also used by
151
- * unrelated SDKs and command routers. A bare numeric status (including gRPC's
152
- * numeric 5) is therefore not enough to retire a provider instance. Only
153
- * authoritative, non-transient evidence may retire a provider instance and
154
- * license lease recovery.
155
- */
156
- type ProviderSandboxFailureKind = "not_found" | "transient_transport" | "other";
157
- type ProviderSandboxFailure = {
158
- kind: ProviderSandboxFailureKind;
159
- /** Bounded, non-secret diagnostic suitable for logs and lifecycle events. */
160
- diagnostic: string;
161
- };
162
- /** Typed transport evidence always dominates nested/string NotFound text. */
163
- declare function classifyProviderSandboxFailure(backendId: string, error: unknown): ProviderSandboxFailure;
164
- declare function isProviderSandboxNotFoundError(backendId: string, error: unknown): boolean;
165
- /**
166
- * Whether a failure from an operation routed through an already-established
167
- * sandbox proves that exact provider sandbox disappeared.
168
- *
169
- * Routed operations can legitimately return HTTP 404 / `NOT_FOUND` for paths,
170
- * files, ports, processes, and other subresources. Those generic signals are
171
- * sufficient for provider create/resume APIs, but they must never retire the
172
- * live sandbox identity after an arbitrary operation. Only an explicit
173
- * sandbox-scoped provider code or Modal's exact terminal sandbox grammar is
174
- * authoritative on this path. Transient transport evidence still dominates.
175
- */
176
- declare function isProviderSandboxGoneDuringRoutedOperation(backendId: string, error: unknown): boolean;
177
- declare function isProviderSandboxTransientError(backendId: string, error: unknown): boolean;
178
-
179
- type ModalSandboxAttribution = {
180
- leaseId: string;
181
- workspaceId: string;
182
- sandboxGroupId: string;
183
- };
184
- type LiveModalSandboxLeaseAttribution = ModalSandboxAttribution & {
185
- instanceId: string | null;
186
- liveness?: string;
187
- };
188
- type ModalOrphanSweepTermination = {
189
- sandboxId: string;
190
- reason: "stale_attribution" | "unattributed";
191
- tags: Record<string, string>;
192
- };
193
- type ModalOrphanSweepResult = {
194
- examined: number;
195
- terminated: ModalOrphanSweepTermination[];
196
- skipped: number;
197
- };
198
- type RevalidateModalOrphanTermination = (candidate: ModalOrphanSweepTermination) => Promise<boolean>;
199
- declare function modalSandboxAttributionEnvironment(input: ModalSandboxAttribution): Record<string, string>;
200
- declare function modalSandboxAttributionTags(input: ModalSandboxAttribution): Record<string, string>;
201
- type ModalModule = typeof modal;
202
- type ModalClientLike = InstanceType<ModalModule["ModalClient"]>;
203
- /** Loader seam so unit tests can inject a fake modal module. */
204
- type ModalModuleLoader = () => Promise<Pick<ModalModule, "ModalClient">>;
205
- /**
206
- * Resolve + cache the private-registry Modal image. No-op unless BOTH
207
- * `modalImageRef` and `modalImageRegistrySecret` are set. Memoized per
208
- * (imageRef, secret, environment) so it runs once per worker process. Awaited at
209
- * worker boot for the deployment-global image and at turn time for pack-scoped
210
- * images BEFORE the first sandbox using that ref is created; `build` then reads the
211
- * resolved image and otherwise falls back to the public `fromTag` path.
212
- */
213
- declare function ensureModalRegistryImage(settings: Settings, loadModal?: ModalModuleLoader): Promise<void>;
214
- declare function tagModalSandbox(settings: Settings, sandboxId: string, attribution: ModalSandboxAttribution): Promise<boolean>;
215
- declare function terminateModalSandboxById(settings: Settings, sandboxId: string): Promise<boolean>;
216
- declare function sweepModalOrphanSandboxes(settings: Settings, liveLeases: LiveModalSandboxLeaseAttribution[], options?: {
217
- now?: Date;
218
- maxTerminations?: number;
219
- unattributedGraceMs?: number;
220
- client?: ModalClientLike;
221
- /** Re-read durable ownership immediately before provider termination. A
222
- * false result or callback failure skips the destructive action. */
223
- revalidateTermination?: RevalidateModalOrphanTermination;
224
- }): Promise<ModalOrphanSweepResult>;
225
-
226
- interface NegotiationContext {
227
- sessionId: string;
228
- backend: SandboxBackend;
229
- os: SandboxOs;
230
- /** Current lease liveness; cold means nothing is provisioned yet. */
231
- liveness: "cold" | "warming" | "warm" | "draining";
232
- /** The lease epoch echoed on viewer heartbeats (the split-brain fence). */
233
- leaseEpoch: number;
234
- /** Numeric/boolean archive completeness truth. Non-persistable targets use
235
- * null/null/false; archive locations and provider details are never exposed. */
236
- workspaceGeneration?: number | null;
237
- archiveGeneration?: number | null;
238
- archiveComplete?: boolean;
239
- /** The deployment desktop toggle (settings.sandboxDesktopEnabled). */
240
- desktopEnabled: boolean;
241
- /**
242
- * The HUMAN take-control toggle (settings.sandboxDesktopInteractive). When true
243
- * (default) and the desktop cell is available, the negotiated DesktopStream.mode
244
- * is "interactive" — the noVNC viewer can drive mouse+keyboard into :0 (the box's
245
- * x11vnc runs without -viewonly). When false the cell reports mode "read-only"
246
- * and the client disables the "Take control" affordance (a genuinely read-only
247
- * deployment). Independent of `computerUseReadOnly`, which gates the AGENT
248
- * driver, not the human viewer plane. Defaults to true so a caller that never
249
- * threads it (e.g. headless tests) still gets the interactive plane when the
250
- * desktop is available.
251
- */
252
- desktopInteractive?: boolean;
253
- /** The deployment computer-use toggle (settings.computerUseEnabled). The agent
254
- * drives :0 via xdotool/scrot; availability tracks desktop. Defaults to true. */
255
- computerUseEnabled?: boolean;
256
- /** Whether the agent computer-use driver is gated to no-op input
257
- * (settings.computerUseReadOnly). v1 default false (the agent clicks/types). */
258
- computerUseReadOnly?: boolean;
259
- /**
260
- * Whether a scoped-stream-token secret is resolvable (stream-token availability contract). When desktop
261
- * is enabled but this is false (no streamTokenSecret AND no delegationSecret),
262
- * the desktop plane GRACEFULLY DEGRADES to transport:null — the deployment
263
- * boots, but the pixel plane cannot mint scoped tokens. Defaults to true so a
264
- * caller that never threads it (e.g. headless tests) is unaffected.
265
- */
266
- streamTokenSecretAvailable?: boolean;
267
- /** Whether the calling principal has acknowledged the un-redacted desktop
268
- * pixels (and, for a shared box, the shared-exposure disclosure). When the
269
- * box is shared this must be the SHARED acknowledgment; a bare un-redacted ack
270
- * does not satisfy a shared box. */
271
- desktopAcknowledged?: boolean;
272
- /** True when the box's group has >1 session: watching this desktop also shows
273
- * the sibling sessions' agents on the one :0 framebuffer (addendum E.1). */
274
- shared?: boolean;
275
- /** The OTHER sessions whose agents may appear on the shared desktop — IDS
276
- * ONLY, never their conversation/metadata (stress g). Empty for a solo box. */
277
- sharedSessionIds?: string[];
278
- /**
279
- * The minted pixel-plane endpoint (P4.2): the direct-to-provider WS URL + the
280
- * scoped stream token + its expiry + the framebuffer geometry. Threaded by the
281
- * API-direct handshake AFTER it has resumed the box, ensured the display stack,
282
- * and resolved the provider tunnel. When ABSENT (the negotiation-only read, a
283
- * cold lease, or a degraded desktop) the DesktopStream cell reports url/token/
284
- * expiresAt as null — the capability is advertised, the live address is not yet
285
- * minted (the caller POSTs to /viewers to mint it). Presence does NOT override
286
- * the gates: a degraded/cold/unacked desktop still reports transport:null and
287
- * the minted endpoint is dropped.
288
- */
289
- desktopStream?: {
290
- url: string;
291
- token: string;
292
- expiresAt: string;
293
- resolution: [number, number];
294
- };
295
- /** The deployment terminal toggle (settings.sandboxTerminalEnabled). The REAL
296
- * PTY (ttyd pty-ws) is gated on this + a real-PTY backend; when off the
297
- * Terminal cell still advertises the read-only sse-events firehose. Defaults to
298
- * true so a caller that never threads it is unaffected. */
299
- terminalEnabled?: boolean;
300
- /**
301
- * The minted terminal-plane endpoint (P5.t): the direct-to-provider ttyd
302
- * PTY-over-websocket URL + the scoped stream token + its expiry. Threaded by the
303
- * API-direct handshake AFTER it has resumed the box, ensured the terminal
304
- * server, and resolved the provider tunnel (mintTerminalStream) — SYMMETRIC with
305
- * `desktopStream`. When ABSENT (the negotiation-only read, a cold lease, or a
306
- * degraded terminal) the Terminal cell reports url/token/expiresAt as null and
307
- * falls back to transport "sse-events" (the read-only firehose) — the caller
308
- * POSTs to /viewers to mint the live pty-ws address.
309
- */
310
- terminalStream?: {
311
- url: string;
312
- token: string;
313
- expiresAt: string;
314
- };
315
- /** Override the negotiation clock (tests). */
316
- now?: Date;
317
- }
318
- /**
319
- * Resolve the descriptor for a backend. Throws on an unknown backend rather than
320
- * returning a half-formed default (the registry is the single source of truth).
321
- */
322
- declare function selectBackend(backend: SandboxBackend): CapabilityDescriptor;
323
- /** True iff the descriptor lists the requested OS as supported. */
324
- declare function backendSupportsOs(descriptor: CapabilityDescriptor, os: SandboxOs): boolean;
325
- /**
326
- * True iff the backend can serve the Channel-B desktop pixel plane at all — i.e.
327
- * its static descriptor advertises DesktopStream as available. The gate the
328
- * worker / API use before launching the display stack (so a headless-only
329
- * backend like cloudflare/vercel/none never tries). This is the STATIC
330
- * feasibility only; the runtime `sandboxDesktopEnabled` policy toggle and the
331
- * stream-token-secret gate are layered on by the caller / negotiateCapabilities.
332
- *
333
- * Accepts EITHER the SandboxBackend enum value (e.g. "local") OR the SDK
334
- * client backendId (e.g. "unix_local") — they diverge for the local backend —
335
- * so a caller holding only `established.backendId` resolves correctly.
336
- */
337
- declare function desktopCapableBackend(backend: SandboxBackend | string): boolean;
338
- /**
339
- * Negotiate a coherent SessionCapabilities document for (backend, os). Every
340
- * capability is reported with availability + a reason-when-unavailable; nothing
341
- * is ever absent. The reason precedence is: os_unsupported (the OS axis can't be
342
- * served at all) > the per-capability static feasibility > policy/liveness gates.
343
- */
344
- declare function negotiateCapabilities(ctx: NegotiationContext): SessionCapabilities;
345
-
346
- declare const STREAM_TOKEN_DEFAULT_TTL_SECONDS = 120;
347
- type MintStreamTokenInput = {
348
- workspaceId: string;
349
- sessionId: string;
350
- /** The sandbox_lease_holders viewer row id. */
351
- viewerId: string;
352
- /** The epoch the token is fenced to. For a Modal box this is the live LEASE
353
- * epoch (re-minted on box rollover). For a SELFHOSTED relay stream (M8b) this is
354
- * the session's swap `active_epoch`: the relay tracks the highest epoch any
355
- * viewer presented per channel and REJECTS a token with a lower epoch, so a
356
- * viewer whose token predates a swap-away cannot reach the machine the session
357
- * swapped off of. One field, two fences — the relay/in-box edge reads it as the
358
- * stale-viewer floor either way. */
359
- leaseEpoch: number;
360
- /** v1 is always "view"; "control" is the never-granted raw-input plane. */
361
- mode?: "view" | "control";
362
- /** The exposed stream port (noVNC); defaults to 6080. */
363
- port?: number;
364
- /** TTL in seconds; defaults to STREAM_TOKEN_DEFAULT_TTL_SECONDS. */
365
- ttlSeconds?: number;
366
- /** Override the issue clock (tests). Seconds since the epoch. */
367
- nowSeconds?: number;
368
- };
369
- /**
370
- * Mint a scoped stream token for one viewer holder. Builds the hard-narrow
371
- * StreamTokenPayload (the claim set the in-box edge / control plane validates)
372
- * and signs it with the resolved stream-token secret via the contracts HMAC
373
- * envelope (`ogs_` prefix). The token is RECORDED against the holder row by the
374
- * caller and is NEVER appended to the data-plane URL as a query param.
375
- */
376
- declare function mintStreamToken(secret: string, input: MintStreamTokenInput): Promise<string>;
377
- /**
378
- * Verify a scoped stream token. Returns the parsed claims on success, or null on
379
- * a bad prefix / malformed envelope / bad HMAC signature / schema-invalid claims
380
- * / expiry. Re-exports the contracts verify; the leaf is the agent-loop-free
381
- * import surface the API uses.
382
- *
383
- * The epoch fence (claim.leaseEpoch vs the LIVE lease epoch) and the
384
- * workspace+session scope are enforced at USE by the caller against the live
385
- * lease + route params — verify proves authenticity + freshness only.
386
- */
387
- declare function verifyStreamToken(secret: string, token: string, nowSeconds?: number): Promise<StreamTokenPayload | null>;
388
-
389
- declare const STREAM_PORT = 6080;
390
- declare const DISPLAY_STACK_TIMEOUT_MS = 90000;
391
- /** Desktop geometry for the framebuffer. v1 has no live RANDR: a resolution
392
- * change is a full down -> up restart (a separate op). */
393
- type DesktopGeometry = {
394
- width: number;
395
- height: number;
396
- dpi: number;
397
- };
398
- declare const DEFAULT_DESKTOP_GEOMETRY: DesktopGeometry;
399
- /** Thrown when a stage of the launch script failed. exitCode 11/12/13 map to
400
- * Xvfb / x11vnc / websockify respectively (the stage that died); 14 is the
401
- * PAINTABLE-FRAME gate (ports listening but scrot still yields an empty frame —
402
- * the display is up but not actually painting). Degradation is surfaced as a
403
- * value to viewers by the caller; this error is for diagnostics. */
404
- declare class DisplayStackError extends Error {
405
- readonly exitCode: number;
406
- readonly stage: "xvfb" | "x11vnc" | "websockify" | "paint" | "lock" | "timeout" | "unknown";
407
- constructor(exitCode: number, output: string);
408
- }
409
- /** Thrown when the provider session cannot run commands (a headless-only
410
- * backend with neither `exec` nor `execCommand`). The desktop tier degrades to
411
- * Channel-A-only — the caller maps this to `DesktopStream.transport: null`. */
412
- declare class DisplayStackUnsupportedError extends Error {
413
- constructor(message: string);
414
- }
415
- type DisplayStackCallerKind = "viewer" | "computer" | "unknown";
416
- type DisplayStackClassification = "cold" | "already_ready" | "contention" | "unknown";
417
- type DisplayStackTelemetryStatus = "started" | "waiting" | "completed" | "failed";
418
- /** Secret-free, bounded-cardinality stage observation. Provider/sandbox IDs are
419
- * log correlation fields, never metric labels. */
420
- type DisplayStackTelemetryEvent = {
421
- type: "display_stack.stage";
422
- requestId: string;
423
- stage: string;
424
- status: DisplayStackTelemetryStatus;
425
- elapsedMs: number;
426
- source: "host" | "sandbox";
427
- classification: DisplayStackClassification;
428
- callerKind: DisplayStackCallerKind;
429
- sandboxId?: string;
430
- leaseEpoch?: number;
431
- providerSessionId?: number;
432
- };
433
- type DisplayStackTelemetryContext = {
434
- callerKind: DisplayStackCallerKind;
435
- sandboxId?: string;
436
- leaseEpoch?: number;
437
- };
438
- type EnsureDisplayStackOptions = {
439
- geometry?: DesktopGeometry;
440
- /** The exposed stream port; defaults to 6080. */
441
- port?: number;
442
- /** Whole-operation wall-clock timeout; defaults to DISPLAY_STACK_TIMEOUT_MS. */
443
- timeoutMs?: number;
444
- /** Caller correlation. Supplying it enables the default structured log sink. */
445
- telemetryContext?: DisplayStackTelemetryContext;
446
- /** Optional non-blocking telemetry sink (tests/embedders may replace logging). */
447
- onTelemetry?: (event: DisplayStackTelemetryEvent) => void | Promise<void>;
448
- };
449
- type EnsureDisplayStackResult = {
450
- /** The exposed port the stack listens on (websockify/noVNC). */
451
- port: number;
452
- geometry: DesktopGeometry;
453
- /** The raw `OPENGENI_DESKTOP_UP …` marker line, for diagnostics. Never
454
- * surfaced to viewers. */
455
- marker: string;
456
- };
457
- /**
458
- * Build the shell command that runs the idempotent up-script under an in-box
459
- * `flock`. The script is shipped in the image at /usr/local/bin/opengeni-desktop-up
460
- * (the canonical desktop image); we set the geometry/port env and wrap the call
461
- * in `flock` so two concurrent ensureDisplayStack callers (the API viewer op +
462
- * the agent turn, both racing after a rollover) serialize without a double
463
- * launch. The up-script's own per-stage PID guards make the second call a no-op.
464
- *
465
- * Exported (pure, side-effect-free) so the ensureDisplayStack unit test can
466
- * assert the exact command sequence without a live box.
467
- */
468
- declare function buildDisplayStackScript(options?: EnsureDisplayStackOptions): string;
469
- /**
470
- * Idempotently bring up the desktop display stack on the live box. Safe to call
471
- * N times (the in-box flock + the up-script's PID guards make a second call a
472
- * no-op). Resolves with the exposed port + geometry on success; throws
473
- * `DisplayStackError` on a stage failure and `DisplayStackUnsupportedError` when
474
- * the session cannot run commands.
475
- *
476
- * `session` is the externally-owned provider session (the `established.session`
477
- * from establishSandboxSessionFromEnvelope, or any SandboxSessionLike). We
478
- * prefer `session.exec` (structured `{exitCode}`) and fall back to
479
- * `session.execCommand` (bare string), inferring success from the up-script's
480
- * marker line in the fallback case.
481
- */
482
- declare function ensureDisplayStack(session: unknown, options?: EnsureDisplayStackOptions): Promise<EnsureDisplayStackResult>;
483
- /** Tear the stack down (down-script). Best-effort; never throws on a missing
484
- * process. Used by the geometry-change restart and cold/drain. */
485
- declare function tearDownDisplayStack(session: unknown): Promise<void>;
486
-
487
- declare const TERMINAL_SERVER_TIMEOUT_MS = 60000;
488
- /** Thrown when the ttyd launch failed inside the box. exitCode 14 maps to the
489
- * up-script's "ttyd failed to come up" stage; any other non-zero is unknown.
490
- * Degradation is surfaced as a value to clients by the caller (Terminal
491
- * transport falls back to sse-events / null); this error is for diagnostics. */
492
- declare class TerminalServerError extends Error {
493
- readonly exitCode: number;
494
- readonly stage: "ttyd" | "unknown";
495
- constructor(exitCode: number, output: string);
496
- }
497
- /** Thrown when the provider session cannot run commands (a headless-only backend
498
- * with neither `exec` nor `execCommand`). The terminal tier degrades to the
499
- * Channel-A sse-events firehose — the caller maps this to a `transport:null`
500
- * pty-ws (the read-only firehose still works). */
501
- declare class TerminalServerUnsupportedError extends Error {
502
- constructor(message: string);
503
- }
504
- type EnsureTerminalServerOptions = {
505
- /** The exposed terminal port; defaults to 7681 (ttyd default). */
506
- port?: number;
507
- /** Per-exec timeout; defaults to TERMINAL_SERVER_TIMEOUT_MS. */
508
- timeoutMs?: number;
509
- };
510
- type EnsureTerminalServerResult = {
511
- /** The exposed port ttyd listens on (PTY-over-websocket). */
512
- port: number;
513
- /** The raw `OPENGENI_TERMINAL_UP …` marker line, for diagnostics. Never
514
- * surfaced to clients. */
515
- marker: string;
516
- };
517
- /**
518
- * Build the shell command that runs the idempotent up-script under an in-box
519
- * `flock`. The script is shipped in the image at /usr/local/bin/opengeni-terminal-up
520
- * (the canonical desktop image, alongside opengeni-desktop-up); we set the port
521
- * env and wrap the call in `flock` so two concurrent ensureTerminalServer callers
522
- * (the API viewer op + the agent turn, both racing after a rollover) serialize
523
- * without a double launch. The up-script's own curl readiness probe makes the
524
- * second call a no-op.
525
- *
526
- * Exported (pure, side-effect-free) so the ensureTerminalServer unit test can
527
- * assert the exact command sequence without a live box. Mirrors
528
- * buildDisplayStackScript. A ttyd process is sandbox-group-global, not
529
- * OpenGeni-session-specific, so it must never inherit the box-global legacy
530
- * Toolspace pointer. Session-scoped interactive exec/PTY uses Channel A; the
531
- * shared ttyd shell fails closed for Toolspace until that stream plane itself is
532
- * session-isolated.
533
- */
534
- declare function buildTerminalServerScript(options?: EnsureTerminalServerOptions): string;
535
- /**
536
- * Idempotently bring up the ttyd PTY-over-websocket server on the live box. Safe
537
- * to call N times (the in-box flock + the up-script's curl readiness probe make a
538
- * second call a no-op). Resolves with the exposed port on success; throws
539
- * `TerminalServerError` on a launch failure and `TerminalServerUnsupportedError`
540
- * when the session cannot run commands.
541
- *
542
- * `session` is the externally-owned provider session (the `established.session`
543
- * from establishSandboxSessionFromEnvelope, or any SandboxSessionLike). We prefer
544
- * `session.exec` (structured `{exitCode}`) and fall back to `session.execCommand`
545
- * (bare string), inferring success from the up-script's marker line in the
546
- * fallback case. Mirrors ensureDisplayStack exactly.
547
- */
548
- declare function ensureTerminalServer(session: unknown, options?: EnsureTerminalServerOptions): Promise<EnsureTerminalServerResult>;
549
- /** Tear the terminal server down (down-script). Best-effort; never throws on a
550
- * missing process. Mirrors tearDownDisplayStack. */
551
- declare function tearDownTerminalServer(session: unknown): Promise<void>;
552
-
553
- type RunCredentialExpectedScope = {
554
- accountId: string;
555
- workspaceId: string;
556
- sessionId: string;
557
- };
558
- type NormalizedRunCredentialMaterial = {
559
- environment: Record<string, string>;
560
- files: RunCredentialFile[];
561
- fileEnvironment: Record<string, string>;
562
- expiresAt: Date | null;
563
- authNeeded: RunCredentialAuthNeeded[];
564
- redactions: RunCredentialRedaction[];
565
- };
566
- type RunCredentialCommandSession = Pick<SandboxSessionLike, "exec" | "execCommand">;
567
- type RunCredentialCommandRunner = (session: RunCredentialCommandSession, args: ExecCommandArgs) => Promise<unknown>;
568
- type RunCredentialSessionReady = (session: RunCredentialCommandSession) => Promise<void> | void;
569
- declare class RunCredentialValidationError extends Error {
570
- constructor(message: string);
571
- }
572
- /**
573
- * Validate the untrusted host-port response before any secret is written to a
574
- * box. The host owns credential selection; OpenGeni owns scope checks, bounds,
575
- * path safety, and the transport lifecycle.
576
- */
577
- declare function normalizeRunCredentialsResolution(resolution: Extract<RunCredentialsResolution, {
578
- status: "not_applicable";
579
- }>, expected: RunCredentialExpectedScope, now?: Date): null;
580
- declare function normalizeRunCredentialsResolution(resolution: Exclude<RunCredentialsResolution, {
581
- status: "not_applicable";
582
- }>, expected: RunCredentialExpectedScope, now?: Date): NormalizedRunCredentialMaterial;
583
- declare function normalizeRunCredentialsResolution(resolution: RunCredentialsResolution, expected: RunCredentialExpectedScope, now?: Date): NormalizedRunCredentialMaterial | null;
584
- declare function runCredentialRoot(sessionId: string): string;
585
- declare function runCredentialPointerFile(sessionId: string): string;
586
- type MaterializeRunCredentialsOptions = {
587
- sessionId: string;
588
- attemptId: string;
589
- executionGeneration: number;
590
- /** Initial provision after recovery may prune generations from older attempts. */
591
- pruneOtherAttempts?: boolean;
592
- /** An all-auth-needed replacement removes every prior readable generation. */
593
- prunePreviousGenerations?: boolean;
594
- /** Renewal keeps the active and immediately prior same-attempt generations. */
595
- pruneSupersededGenerations?: boolean;
596
- commandRunner?: RunCredentialCommandRunner;
597
- };
598
- /** Materialize one complete immutable generation, then atomically flip current. */
599
- declare function materializeRunCredentials(session: RunCredentialCommandSession, material: NormalizedRunCredentialMaterial, options: MaterializeRunCredentialsOptions): Promise<void>;
600
- declare function clearRunCredentials(session: RunCredentialCommandSession, sessionId: string, commandRunner?: RunCredentialCommandRunner): Promise<void>;
601
- /**
602
- * Remove only generations owned by one attempt. A successor may already have
603
- * atomically activated its own pointer after queue admission; this cleanup must
604
- * never clear or overwrite that newer generation.
605
- */
606
- declare function clearRunCredentialsForAttempt(session: RunCredentialCommandSession, options: Pick<MaterializeRunCredentialsOptions, "sessionId" | "attemptId" | "executionGeneration">, commandRunner?: RunCredentialCommandRunner): Promise<void>;
607
- /** Prefix one sandbox command so every new process observes the active generation. */
608
- declare function withRunCredentialEnvironment(cmd: string, sessionId: string): string;
609
- /**
610
- * Preserve the provider session identity/capabilities while decorating only
611
- * command creation. Non-command methods stay bound to the original instance.
612
- */
613
- declare function withRunCredentialsSession<T extends object>(session: T, sessionId: string): T;
614
- /** Decorate every client-created/resumed session after the host seeds it. */
615
- declare function withRunCredentialsClient(client: SandboxClient, sessionId: string, onSessionReady?: RunCredentialSessionReady): SandboxClient;
616
-
617
- declare class ToolspaceTokenPathError extends Error {
618
- constructor(message: string);
619
- }
620
- /**
621
- * Derive the per-session token file beside the legacy manifest pointer.
622
- *
623
- * The manifest pointer stays box-global so an already-warm shared sandbox does
624
- * not receive an illegal environment delta. Every OpenGeni command overrides
625
- * that pointer with this deterministic path. The hash is path hygiene and
626
- * avoids disclosing host/session ids in filenames; filesystem paths are not an
627
- * authorization boundary. The delegated bearer's session claim remains the
628
- * authority boundary for Toolspace calls.
629
- */
630
- declare function toolspaceTokenFileForSession(manifestTokenFile: string, sessionId: string): string;
631
- declare function toolspaceTokenFileFromEnvironment(environment: Readonly<Record<string, string>>, sessionId: string): string;
632
- /** Prefix one sandbox command with its session-specific Toolspace pointer. */
633
- declare function withToolspaceTokenEnvironment(cmd: string, tokenFile: string): string;
634
- /** Preserve provider identity/capabilities while decorating command creation. */
635
- declare function withToolspaceTokenSession<T extends object>(session: T, tokenFile: string): T;
636
- /** Decorate every client-created/resumed session with one session pointer. */
637
- declare function withToolspaceTokenClient(client: SandboxClient, tokenFile: string): SandboxClient;
638
-
639
- /** The provider-resolved endpoint for an exposed port. Mirrors the SDK's
640
- * `ExposedPortEndpoint` (host/port/tls/query/...) WITHOUT importing the
641
- * agent-loop barrel — the leaf stays agent-loop-free. */
642
- type ExposedPortEndpoint = {
643
- host: string;
644
- port: number;
645
- tls?: boolean;
646
- query?: string;
647
- protocol?: string;
648
- url?: string;
649
- /** The URL path the socket connects on. Modal/Daytona/Blaxel serve the edge at
650
- * the root (`/`, the default); the selfhosted relay serves it at `/stream`
651
- * (M8b). When set, buildStreamUrl uses it instead of the root. */
652
- path?: string;
653
- [key: string]: unknown;
654
- };
655
- /** Thrown when the provider cannot expose the stream port (no resolveExposedPort,
656
- * or the provider tunnel lookup failed). The caller degrades the desktop cell to
657
- * `transport:null` (a value, never a crash) — a headless-only provider or a
658
- * transient tunnel failure must not fail the whole handshake. */
659
- declare class StreamPortUnavailableError extends Error {
660
- readonly cause?: unknown | undefined;
661
- constructor(message: string, cause?: unknown | undefined);
662
- }
663
- type ExposeStreamPortInput = {
664
- workspaceId: string;
665
- sessionId: string;
666
- /** The sandbox_lease_holders viewer row id the token is scoped to. */
667
- viewerId: string;
668
- /** The live lease epoch — the fence the token is pinned to. */
669
- leaseEpoch: number;
670
- /** The HMAC secret for the scoped stream token (resolveStreamTokenSecret). */
671
- streamTokenSecret: string;
672
- /** The exposed stream port; defaults to 6080. */
673
- port?: number;
674
- /** Token TTL in seconds; defaults to STREAM_TOKEN_DEFAULT_TTL_SECONDS. */
675
- ttlSeconds?: number;
676
- /** The framebuffer geometry to echo back to the client. */
677
- resolution?: [number, number];
678
- /** Override the issue clock (tests). Seconds since the epoch. */
679
- nowSeconds?: number;
680
- };
681
- type ExposeStreamPortResult = {
682
- /** The direct-to-provider WS URL the viewer connects to (provider-scoped; the
683
- * OpenGeni token is NOT appended). */
684
- url: string;
685
- /** The scoped OpenGeni stream token — recorded against the holder, NEVER a URL
686
- * query param. */
687
- token: string;
688
- /** ISO absolute expiry of the token (the rotation hot-swap window backstop). */
689
- expiresAt: string;
690
- /** The pixel transport the client speaks. */
691
- transport: "vnc-ws";
692
- /** The reference noVNC client the SDK helper mounts. */
693
- client: "novnc";
694
- resolution: [number, number];
695
- leaseEpoch: number;
696
- };
697
- /**
698
- * Assemble the direct-to-provider WS URL from a resolved endpoint. The SDK's
699
- * `urlForExposedPort(endpoint,'ws')` is the canonical tls-aware, IPv6-bracketing,
700
- * provider-query-preserving assembler — we reimplement its exact logic here so
701
- * the leaf stays agent-loop-free (the helper lives behind the bare
702
- * `@openai/agents-core` root, which the import-discipline test forbids). The
703
- * provider's own `endpoint.query` (Blaxel `bl_preview_token`, Daytona signed
704
- * token) is preserved; the OpenGeni token is NOT appended (it is recorded against
705
- * the holder + validated at the in-box websockify edge).
706
- */
707
- declare function buildStreamUrl(endpoint: ExposedPortEndpoint): string;
708
- /**
709
- * Resolve the provider's scoped tunnel for the stream port and mint the scoped
710
- * OpenGeni stream token. Returns a coherent `{url, token, expiresAt, transport,
711
- * client, resolution}` cell the caller records on the lease (data_plane_url) and
712
- * returns in the DesktopStream handshake.
713
- *
714
- * Throws `StreamPortUnavailableError` when the provider session cannot resolve
715
- * the port (no `resolveExposedPort`, or the tunnel lookup failed) — the caller
716
- * maps this to a `transport:null` degradation (a value, never a crash).
717
- */
718
- declare function exposeStreamPort(session: unknown, input: ExposeStreamPortInput): Promise<ExposeStreamPortResult>;
719
-
720
- type RecordingCodec = "h264-mp4" | "vp9-webm";
721
- type RecordingContentType = "video/mp4" | "video/webm";
722
- declare function contentTypeForCodec(codec: RecordingCodec): RecordingContentType;
723
- declare function extForCodec(codec: RecordingCodec): string;
724
- /** No exec/execCommand on the session — the box cannot run ffmpeg. */
725
- declare class RecordingUnavailableError extends Error {
726
- constructor(message: string);
727
- }
728
- /** ffmpeg failed, the file is missing, or the artifact is over its size limit. */
729
- declare class RecordingError extends Error {
730
- readonly reason: "ffmpeg-error" | "box-death" | "max-bytes-exceeded" | "display-unavailable";
731
- constructor(message: string, reason: "ffmpeg-error" | "box-death" | "max-bytes-exceeded" | "display-unavailable");
732
- }
733
- type StartRecordingInput = {
734
- recordingId: string;
735
- codec?: RecordingCodec;
736
- framerate?: number;
737
- maxSeconds?: number;
738
- dimensions?: [number, number];
739
- display?: string;
740
- runAs?: string;
741
- tmpDir?: string;
742
- };
743
- type RecordingProcess = {
744
- recordingId: string;
745
- codec: RecordingCodec;
746
- boxPath: string;
747
- pidFile: string;
748
- dimensions: [number, number];
749
- framerate: number;
750
- /** epoch-ms when ffmpeg was launched (for duration computation, F14). */
751
- startedAt: number;
752
- display: string;
753
- runAs?: string;
754
- };
755
- /**
756
- * Launch ffmpeg x11grab on :0 → an mp4/webm file on the box. Backgrounded with
757
- * `nohup … & echo $!` so the launch returns immediately (F12 — the exec does not
758
- * block on the recording). A hard `-t <maxSeconds>` ceiling bounds a runaway file
759
- * across a multi-day turn. Returns the handle the caller carries to stop+finalize.
760
- */
761
- declare function startRecording(session: unknown, input: StartRecordingInput): Promise<RecordingProcess>;
762
- /**
763
- * SIGINT ffmpeg (so it writes a clean moov atom / webm trailer) and wait for the
764
- * pid to exit. Bounded well under the yield window (F3). Idempotent: a missing
765
- * pid file is a no-op.
766
- */
767
- declare function stopRecording(session: unknown, proc: RecordingProcess): Promise<void>;
768
- type RecordingArtifactMetadata = {
769
- contentType: RecordingContentType;
770
- sizeBytes: number;
771
- durationSeconds: number;
772
- };
773
- /**
774
- * Inspect and size-gate the finalized recording while it is still on the box.
775
- * No recording bytes cross into the worker process.
776
- */
777
- declare function inspectRecordingArtifact(session: unknown, proc: RecordingProcess, maxBytes?: number): Promise<RecordingArtifactMetadata>;
778
- /**
779
- * Upload the finalized artifact directly from the sandbox to a scoped object-
780
- * storage URL. The worker carries only the short URL/headers and metadata; it
781
- * never materializes a base64 string or Uint8Array for a recording that may be
782
- * hundreds of megabytes. curl's success marker makes an exec transport that
783
- * returns non-zero command output instead of throwing still fail closed.
784
- */
785
- declare function uploadRecordingArtifact(session: unknown, proc: RecordingProcess, input: {
786
- url: string;
787
- requiredHeaders: Record<string, string>;
788
- maxBytes?: number;
789
- maxSeconds?: number;
790
- }): Promise<RecordingArtifactMetadata>;
791
- /**
792
- * Delete the box artifacts. F9: call this ONLY after the storage PUT confirmed
793
- * and the `available` row committed — never before. Best-effort; never throws.
794
- */
795
- declare function deleteRecordingArtifacts(session: unknown, proc: RecordingProcess): Promise<void>;
796
- /** The storage object key for a recording artifact (parallels the file-asset layout). */
797
- declare function recordingStorageKey(workspaceId: string, sessionId: string, recordingId: string, codec: RecordingCodec): string;
798
-
799
- declare function parseExecBannerSessionId(raw: string): number | null;
800
- declare function parseExecBannerExitCode(raw: string): number | null;
801
- declare function isExecSessionLostBanner(out: string, execSessionId: number): boolean;
802
-
803
- type ChannelAExecResult = {
804
- output?: string;
805
- stdout?: string;
806
- stderr?: string;
807
- exitCode?: number | null;
808
- sessionId?: number;
809
- wallTimeSeconds?: number;
810
- };
811
- type ChannelAExecArgs = {
812
- cmd: string;
813
- workdir?: string | undefined;
814
- shell?: string | undefined;
815
- login?: boolean | undefined;
816
- tty?: boolean | undefined;
817
- yieldTimeMs?: number | undefined;
818
- maxOutputTokens?: number | undefined;
819
- runAs?: string | undefined;
820
- };
821
- type ChannelAEditor = {
822
- createFile?(op: unknown): Promise<unknown>;
823
- updateFile?(op: unknown): Promise<unknown>;
824
- deleteFile?(op: unknown): Promise<unknown>;
825
- };
826
- type ChannelASession = {
827
- exec?(args: ChannelAExecArgs): Promise<ChannelAExecResult>;
828
- execCommand?(args: ChannelAExecArgs): Promise<string>;
829
- readFile?(args: {
830
- path: string;
831
- runAs?: string;
832
- maxBytes?: number;
833
- }): Promise<string | Uint8Array>;
834
- writeStdin?(args: {
835
- sessionId: number;
836
- chars?: string;
837
- yieldTimeMs?: number;
838
- maxOutputTokens?: number;
839
- }): Promise<string>;
840
- writeStdinForProcessMutation?(args: {
841
- sessionId: number;
842
- chars?: string;
843
- yieldTimeMs?: number;
844
- maxOutputTokens?: number;
845
- }): Promise<string>;
846
- writeStdinForProcessControl?(args: {
847
- sessionId: number;
848
- chars?: string;
849
- yieldTimeMs?: number;
850
- maxOutputTokens?: number;
851
- }): Promise<string>;
852
- cancelExecCommand?(opId: string): Promise<boolean>;
853
- hasRetainedProcess?(providerSessionId: number): boolean;
854
- execCommandForProcessControl?(providerSessionId: number, args: ChannelAExecArgs): Promise<string>;
855
- createEditor?(runAs?: string): ChannelAEditor;
856
- supportsPty?(): boolean;
857
- };
858
- declare class ChannelAValidationError extends Error {
859
- constructor(message: string);
860
- }
861
- /** A structurally valid Channel-A request could not be completed because the
862
- * sandbox/provider control plane was temporarily unavailable. Callers may retry
863
- * this class; it must never be presented as a bad user path. */
864
- declare class ChannelAUnavailableError extends Error {
865
- constructor(message: string);
866
- }
867
- declare class ChannelAConflictError extends Error {
868
- constructor(message: string);
869
- }
870
- declare class ChannelANotFoundError extends Error {
871
- constructor(message: string);
872
- }
873
- declare class ChannelAUnsupportedError extends Error {
874
- constructor(message: string);
875
- }
876
- type ChannelAEmitter = (events: {
877
- type: SessionEventType;
878
- payload: unknown;
879
- }[]) => Promise<void>;
880
- type SandboxChannelAServiceOptions = {
881
- session: ChannelASession;
882
- workspaceRoot?: string;
883
- leaseEpoch?: number;
884
- revision?: number;
885
- emit?: ChannelAEmitter;
886
- runAs?: string;
887
- };
888
- declare const REPOSITORY_DISCOVERY_LIMIT = 256;
889
- type RepositoryDiscoveryDegradedReason = "command_failed" | "command_timed_out" | "result_limit_exceeded";
890
- type RepositoryDiscoveryResult = {
891
- repos: string[];
892
- complete: boolean;
893
- degradedReason: RepositoryDiscoveryDegradedReason | null;
894
- };
895
- declare class SandboxChannelAService {
896
- private readonly session;
897
- private readonly workspaceRoot;
898
- private readonly leaseEpoch;
899
- private revision;
900
- private readonly emit?;
901
- private readonly runAs?;
902
- constructor(opts: SandboxChannelAServiceOptions);
903
- /** Capability probe — the compact Channel-A projection. */
904
- capabilities(repos?: string[]): SessionStructuredCapabilities;
905
- private run;
906
- fsList(req: FsListRequest): Promise<FsListResponse>;
907
- /**
908
- * Worker-only fast path for a bounded whole-workspace index. Matching
909
- * directory nodes are emitted but pruned by `find` before descent, so residue
910
- * such as node_modules cannot consume the result budget. The public fs/list
911
- * wire remains unchanged; interactive callers still expand every directory
912
- * they explicitly request.
913
- */
914
- fsListPruned(req: FsListRequest, pruneDirectoryNames: readonly string[]): Promise<FsListResponse>;
915
- private fsListInternal;
916
- fsRead(req: FsReadRequest): Promise<FsReadResponse>;
917
- private assertConfinedExistingDirectory;
918
- private assertConfinedMutationParent;
919
- private assertConfinementResult;
920
- /** Binary-safe fallback for sessions without native reads. The file is opened
921
- * before checking `/proc/.../fd/3`, so a symlink swap cannot redirect the
922
- * subsequent read after confinement has been established. */
923
- private fsReadViaExec;
924
- private shapeRead;
925
- fsWrite(req: FsWriteRequest): Promise<FsWriteResponse>;
926
- private tryEditorWrite;
927
- fsDelete(req: FsDeleteRequest): Promise<FsDeleteResponse>;
928
- fsMove(req: FsMoveRequest): Promise<FsMoveResponse>;
929
- fsMkdir(req: FsMkdirRequest): Promise<FsMkdirResponse>;
930
- /** Measure one Git command without returning its potentially large or
931
- * NUL-bearing output through the provider's text transport. */
932
- private measureConfinedCommandBytes;
933
- /** Read an exact prefix of a command's output in chunks that remain below
934
- * every supported execCommand retained-output ceiling. The producer is
935
- * intentionally rerun per chunk; turn-end capture calls this only after the
936
- * durable mutation authority has quiesced the workspace. */
937
- private readConfinedCommandPrefix;
938
- private readConfinedCommandBytes;
939
- /** Read a regular untracked file without routing its body through one large
940
- * execCommand response. Native provider reads are binary-safe; providers
941
- * without that surface use the same bounded chunk protocol as Git patches. */
942
- private readUntrackedRegularFile;
943
- gitStatus(req: GitStatusRequest): Promise<GitStatusResponse>;
944
- gitDiff(req: GitDiffRequest): Promise<GitDiffResponse>;
945
- gitLog(req: GitLogRequest): Promise<GitLogResponse>;
946
- gitShow(req: GitShowRequest): Promise<GitShowResponse>;
947
- /**
948
- * Detect repo roots within every supported workspace layout.
949
- *
950
- * The platform normally seeds repositories at
951
- * `repos/<encoded-host>/<owner>/<repo>`, while explicit mount paths and
952
- * connected-machine layouts can be deeper. Do not reintroduce a fixed
953
- * maxdepth here. Traversal is
954
- * bounded instead by pruning known machine/build residue, a wall-clock
955
- * timeout, and a result limit. `.git` may be either a directory (ordinary
956
- * clone/submodule) or a file (linked worktree).
957
- *
958
- * Callers that persist an authoritative snapshot must use this detailed
959
- * result and surface an incomplete discovery. The compact `detectRepos()`
960
- * wrapper remains for capability negotiation, where the best available list
961
- * is preferable to failing the whole capabilities document.
962
- */
963
- detectReposDetailed(): Promise<RepositoryDiscoveryResult>;
964
- /** Detect repo roots within the workspace (for the Git.repos capability). */
965
- detectRepos(): Promise<string[]>;
966
- /** Run a bounded command to physical completion and return buffered output.
967
- * A deadline cancels the exact provider operation, proves its process group
968
- * absent, and settles any retained-process admission before returning 124. */
969
- terminalExec(req: TerminalExecRequest): Promise<TerminalExecResponse>;
970
- /** Open an interactive PTY: exec the shell with tty:true, yielding the numeric
971
- * exec-session id the caller persists (ptyId<->execSessionId) so subsequent
972
- * writeStdin can drive it. Returns the supportsInput gate (false when the
973
- * backend has no writeStdin). The caller emits terminal.pty.started after it
974
- * persists the row. */
975
- ptyOpen(req: PtyOpenRequest, ptyId: string): Promise<{
976
- response: PtyOpenResponse;
977
- execSessionId: number | null;
978
- shell: string;
979
- initialOutput: string;
980
- }>;
981
- /** Drive an open PTY's stdin. Returns the drained output (the caller publishes
982
- * it as terminal.pty.output.delta). Throws ChannelAUnsupportedError when the
983
- * backend has no writeStdin. */
984
- ptyWrite(_req: PtyWriteRequest, execSessionId: number, data: string): Promise<string>;
985
- /** Resize an open PTY (SIGWINCH via stty against the exec-session). The SDK has
986
- * no resize method; stty in the same tty session updates the geometry. */
987
- ptyResize(req: PtyResizeRequest, execSessionId: number): Promise<void>;
988
- /** Ask an open PTY to exit and return the exact provider banner. The routing
989
- * session settles durable process authority only when that banner proves this
990
- * locator exited or was lost; callers must keep metadata open otherwise. */
991
- ptyClose(_req: PtyCloseRequest, execSessionId: number | null): Promise<string>;
992
- /** The current FS revision (for the caller to persist/seed). */
993
- currentRevision(): number;
994
- private joinRoot;
995
- /** Validate and enter an FS/Git directory in the same remote command that
996
- * performs the operation. This preserves confinement without adding a full
997
- * provider round-trip to every tree expansion or Git query. */
998
- private runInConfinedDirectory;
999
- private repoWorkdir;
1000
- private terminalWorkdir;
1001
- private emitEvents;
1002
- private emitFsChanged;
1003
- /** Re-probe git after a mutation and emit git.changed (best-effort, used by the
1004
- * worker agent-turn side after FS-mutating tools). */
1005
- emitGitChanged(repoPath: string, reason: GitChangedPayload["reason"]): Promise<void>;
1006
- }
1007
- declare function stripExecBanner(raw: string): string;
1008
- declare function assertSafeRelPath(p: string): string;
1009
- declare function parsePorcelainV2(z: string): Omit<GitStatusResponse, "revision">;
1010
- type NumstatEntry = {
1011
- additions: number;
1012
- deletions: number;
1013
- binary: boolean;
1014
- oldPath: string | null;
1015
- newPath: string;
1016
- };
1017
- declare function parseNumstatZ(z: string): NumstatEntry[];
1018
- declare function parseUnifiedPatch(patch: string): {
1019
- hunks: GitDiffHunk[];
1020
- status: GitFileStatusCode;
1021
- };
1022
-
1023
- type SelfhostedUnavailableReason = Extract<CapabilityUnavailableReason, "agent_offline" | "agent_reconnecting" | "consent_required" | "display_unavailable">;
1024
- /**
1025
- * The selfhosted control-plane transport seam. ONE method: `request` — send a
1026
- * `ControlRequest` to the agent addressed by subject and await its
1027
- * `ControlResponse`. The subject is `subjectFor(workspaceId, agentId)`.
1028
- *
1029
- * The CONTRACT every implementor MUST honour (the M3 ruling): a
1030
- * no-responder / request-timeout is NOT an exception that means "not found" — it
1031
- * is surfaced as a `ControlResponse` carrying an `AgentError` with code
1032
- * `AGENT_OFFLINE` (no responder at all) or, when the caller can distinguish a
1033
- * transient blip, `TIMEOUT` (→ `agent_reconnecting`). The session maps these to
1034
- * the runtime error taxonomy; it NEVER lets agent-offline look like a provider
1035
- * NotFound (which would cold-create a rival box for a user's real machine).
1036
- */
1037
- interface ControlRpc {
1038
- request(subject: string, req: ControlRequest, opts: {
1039
- timeoutMs: number;
1040
- }): Promise<ControlResponse>;
1041
- }
1042
- /** The control-plane RPC subject for an enrolled agent — its subscription IS the
1043
- * registry (the binding two-plane decision). */
1044
- declare function subjectFor(workspaceId: string, agentId: string): string;
1045
- /**
1046
- * The runtime-level error a `SelfhostedSession` op throws when the agent returns
1047
- * an `AgentError` (or no responder / timeout maps to one). It carries:
1048
- * - `code` — the wire `ErrorCode` (single-source-of-truth);
1049
- * - `reason` — the negotiated `CapabilityUnavailableReason` the capability /
1050
- * liveness surface uses (`agent_offline` / `agent_reconnecting`
1051
- * / `consent_required`), or null for op-level errors
1052
- * (OS/NOT_FOUND/UNSUPPORTED/STREAM/PROTOCOL) that are not a
1053
- * machine-liveness condition;
1054
- * - `retryable`— whether the caller should re-resolve + retry (DRAINING /
1055
- * FENCED / a reconnecting blip);
1056
- * - `notFound` — ALWAYS the provider-NotFound discriminator value: for
1057
- * selfhosted this is true ONLY for an OS-level NOT_FOUND of a
1058
- * path/ref (a real "the file does not exist"), and is FALSE for
1059
- * AGENT_OFFLINE (the machine isn't recreatable — never let the
1060
- * lease cold-create a rival). `isProviderSandboxNotFoundError`
1061
- * reads this.
1062
- */
1063
- declare class SelfhostedControlError extends Error {
1064
- readonly name = "SelfhostedControlError";
1065
- readonly code: ErrorCode;
1066
- readonly reason: SelfhostedUnavailableReason | null;
1067
- readonly retryable: boolean;
1068
- readonly fenced: boolean;
1069
- readonly draining: boolean;
1070
- readonly agentOffline: boolean;
1071
- readonly osNotFound: boolean;
1072
- /** The reply exceeded the transport's max payload (a distinguishing flag so the
1073
- * fault renderer can name the size wall + recovery instead of a generic OS error). */
1074
- readonly payloadTooLarge: boolean;
1075
- /** The transport synthesized this fault PRE-SEND — it KNOWS the request never
1076
- * reached the machine (no connection, or no responder on the subject), so the op
1077
- * provably did not execute. Only such a fault is safe to re-issue for ANY op kind
1078
- * (a post-send timeout is ambiguous — at-least-once — and must not be re-sent
1079
- * unless the op is read-only). Never set for an agent-returned error. */
1080
- readonly neverSent: boolean;
1081
- readonly detail: Record<string, string>;
1082
- constructor(input: {
1083
- message: string;
1084
- code: ErrorCode;
1085
- reason: SelfhostedUnavailableReason | null;
1086
- retryable: boolean;
1087
- fenced?: boolean;
1088
- draining?: boolean;
1089
- agentOffline?: boolean;
1090
- osNotFound?: boolean;
1091
- payloadTooLarge?: boolean;
1092
- neverSent?: boolean;
1093
- detail?: Record<string, string>;
1094
- });
1095
- }
1096
- /**
1097
- * Map an `AgentError` (from a `ControlResponse`) to the runtime
1098
- * `SelfhostedControlError`. THE load-bearing mapping (the M3 ruling):
1099
- * - AGENT_OFFLINE → reason `agent_offline`, agentOffline=true,
1100
- * osNotFound=FALSE (NEVER a provider NotFound).
1101
- * - TIMEOUT (a transient missed-window / no-responder blip the caller marked
1102
- * retryable) → reason `agent_reconnecting`.
1103
- * - CONSENT_REQUIRED → reason `consent_required`.
1104
- * - DRAINING → no capability reason; retryable (turn pauses + retries).
1105
- * - FENCED → no capability reason; retryable (the existing
1106
- * epoch-fence retry; the caller re-resolves + retries).
1107
- * - NOT_FOUND → an OS-level path/ref NotFound — osNotFound=true (a
1108
- * real "file does not exist"), no machine-liveness
1109
- * reason. (This is the ONLY NotFound; it is NOT the
1110
- * box-gone NotFound that licenses a cold restore.)
1111
- * - PAYLOAD_TOO_LARGE → the reply exceeded the transport's max payload;
1112
- * non-retryable, with actionable copy (the sizes + a
1113
- * redirect-to-file workaround).
1114
- * - OS / UNSUPPORTED / STREAM / PROTOCOL / UNSPECIFIED → op-level error, no
1115
- * reason, non-retryable.
1116
- */
1117
- declare function agentErrorToControlError(err: AgentError): SelfhostedControlError;
1118
- /**
1119
- * PAYLOAD_TOO_LARGE copy: state the actual sizes (from the agent's `detail` map —
1120
- * `encoded_bytes` / `max_payload`) and tell the caller how to get the data anyway:
1121
- * redirect the output to a file and read it back in ranges/chunks.
1122
- */
1123
- declare function payloadTooLargeMessage(detail: Record<string, string>): string;
1124
- /**
1125
- * DRAINING copy: the machine is at its concurrent-work capacity. `retries` is the
1126
- * number of times `SelfhostedSession.call` already re-tried before giving up (0 at
1127
- * the mapping layer, the final count when it surfaces after exhausting retries).
1128
- */
1129
- declare function drainingMessage(retries: number): string;
1130
- /** Rebuild a DRAINING error with the final retry count folded into its message —
1131
- * used by `SelfhostedSession.call` when the bounded DRAINING retries are exhausted. */
1132
- declare function drainingExhaustedError(base: SelfhostedControlError, retries: number): SelfhostedControlError;
1133
- /**
1134
- * exec-deadline copy: the command was terminated at the exec time limit. Advise
1135
- * running long jobs in the background and polling their output rather than blocking
1136
- * the turn on one long command. Surfaced on a timed-out exec result's stderr.
1137
- */
1138
- declare function execDeadlineHint(seconds: number): string;
1139
- /** The reserved `detail` key the transport sets to "1" when it KNOWS a synthesized
1140
- * AGENT_OFFLINE fault occurred pre-send (no connection / no responder) — so the op
1141
- * provably never reached the machine. `agentErrorToControlError` reads it into
1142
- * `SelfhostedControlError.neverSent`. Reserved (never sent by the agent). */
1143
- declare const NEVER_SENT_DETAIL_KEY = "_never_sent";
1144
- /** Build a synthesized AGENT_OFFLINE `AgentError` — the control plane uses this
1145
- * when no agent responds on the subject at all. `neverSent` marks that the request
1146
- * provably never reached the machine (no connection / no responder), which makes it
1147
- * safe to re-issue for ANY op kind; leave it false for an AMBIGUOUS transport error
1148
- * (a connection torn mid-request may have delivered the op). */
1149
- declare function offlineAgentError(message?: string, neverSent?: boolean): AgentError;
1150
- /** Build a synthesized TIMEOUT `AgentError` — the control plane uses this when a
1151
- * responder existed but the request timed out (a transient blip → reconnecting). */
1152
- declare function timeoutAgentError(message?: string): AgentError;
1153
- /**
1154
- * The minimal NATS request/reply surface `NatsControlRpc` needs. It mirrors the
1155
- * `nats` `NatsConnection.request` signature WITHOUT importing `nats` into the
1156
- * agent-loop-free runtime leaf: the API/worker injects the live connection (the
1157
- * SAME `@opengeni/events` bus connection). A factory may return `null` when NATS
1158
- * is not configured (boot must not require a live NATS) — `NatsControlRpc` then
1159
- * surfaces `agent_offline` for every request rather than throwing.
1160
- */
1161
- interface NatsRequestConnection {
1162
- request(subject: string, payload: Uint8Array, opts: {
1163
- timeout: number;
1164
- }): Promise<{
1165
- data: Uint8Array;
1166
- }>;
1167
- }
1168
- /**
1169
- * A thin `ControlRpc` over a NATS request/reply connection. Constructed with a
1170
- * LAZY factory: the connection is resolved on first `request` (so boot never
1171
- * requires a live NATS). A null factory result, a no-responder error, or a
1172
- * request timeout each yield a `ControlResponse` carrying a synthesized
1173
- * `AgentError` (AGENT_OFFLINE / TIMEOUT) — NEVER a thrown transport error and
1174
- * NEVER a NotFound.
1175
- *
1176
- * The factory is async and memoized; it may itself dial the bus. M4 replaces the
1177
- * factory's body with the Accounts-scoped, hardened connection — this class's
1178
- * shape does not change.
1179
- */
1180
- declare class NatsControlRpc implements ControlRpc {
1181
- private readonly connect;
1182
- private connection;
1183
- private connecting;
1184
- constructor(connect: () => Promise<NatsRequestConnection | null>);
1185
- private resolveConnection;
1186
- request(subject: string, req: ControlRequest, opts: {
1187
- timeoutMs: number;
1188
- }): Promise<ControlResponse>;
1189
- }
1190
- /** A `ControlResponse` carrying a synthesized AGENT_OFFLINE error. `neverSent` marks
1191
- * a pre-send synthesis (no connection / no responder) — the op provably never
1192
- * reached the machine, so it is safe to re-issue for any op kind. */
1193
- declare function offlineControlResponse(requestId: string, neverSent?: boolean): ControlResponse;
1194
- /** A `ControlResponse` carrying a synthesized TIMEOUT error (→ reconnecting). */
1195
- declare function timeoutControlResponse(requestId: string): ControlResponse;
1196
-
1197
- /**
1198
- * One completed control-op observation. `outcome` is the terminal result AFTER any
1199
- * in-call retries: `ok` (the op succeeded) or `failed` (a terminal fault surfaced).
1200
- * `healed` marks a success that only landed after ≥1 retry — the doctrine's
1201
- * "healed and invisible in-band, always out-of-band" breadcrumb (drives
1202
- * `machine.op.recovered`). Every field is derivable at the `SelfhostedSession.call`
1203
- * exit points with no extra round-trip.
1204
- */
1205
- interface SelfhostedOpObservation {
1206
- /** The control-op kind (`ControlRequest["op"].$case`): exec / fsRead / ping / … */
1207
- op: string;
1208
- outcome: "ok" | "failed";
1209
- /** True when `outcome==="ok"` only after ≥1 in-call retry (draining/timeout/never-sent). */
1210
- healed: boolean;
1211
- /** Total in-call retries before this outcome (summed across the retry classes). */
1212
- retries: number;
1213
- /** Wall time from the first attempt to the terminal outcome, in ms. */
1214
- durationMs: number;
1215
- /** The typed wire code — present on `failed`. */
1216
- code?: ErrorCode;
1217
- /** The negotiated capability reason (agent_offline / agent_reconnecting / …), if any. */
1218
- reason?: SelfhostedUnavailableReason | null;
1219
- /** Whether a failed fault provably never reached the machine (a pre-send synthesis). */
1220
- neverSent?: boolean;
1221
- /** The enrolled machine (agent) id the op addressed — for machine-scoped fan-out. */
1222
- machineId?: string;
1223
- /** A stable fault-class string (`selfhostedFaultClass`): the fault class on a
1224
- * `failed` op, or the class of retries that a `healed` op recovered from. Absent
1225
- * on a clean success. Drives the `machine.op.*` event mapping + its infra filter. */
1226
- faultClass?: string;
1227
- /** The reply size in bytes when known — set on a PAYLOAD_TOO_LARGE fault (from the
1228
- * agent's `encoded_bytes` detail). The op-stream client fills the success-reply
1229
- * size once it owns the framed transport; today's request/reply does not surface it. */
1230
- replyBytes?: number;
1231
- }
1232
- /** A fire-and-forget observer of completed control ops. It MUST NOT throw (the
1233
- * session guards the call) and MUST NOT block the op — it is a telemetry tap. */
1234
- type SelfhostedOpObserver = (observation: SelfhostedOpObservation) => void;
1235
-
1236
- /**
1237
- * A stable, low-cardinality fault-class string for a `SelfhostedControlError` — the
1238
- * out-of-band twin of the in-band renderer's class taxonomy (single-sourced here).
1239
- * Used for the `machine.op.*` event class + the metrics label + the infra filter.
1240
- */
1241
- declare function selfhostedFaultClass(error: SelfhostedControlError): string;
1242
- /**
1243
- * The fault classes that count as an INFRASTRUCTURE fault (a `machine.op.failed`
1244
- * event). A semantic miss the model asked about — a missing path (`not_found`), a
1245
- * consent gate (`consent`), a nonzero exit (never a fault at all) — is an OUTCOME,
1246
- * not an infra fault, and is DELIBERATELY excluded; `fenced` is a routing/session
1247
- * condition, not a machine fault. (Doctrine two-planes / the team ruling.)
1248
- */
1249
- declare const SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES: ReadonlySet<string>;
1250
- /** The four mandatory field labels (exported so tests can assert their presence). */
1251
- declare const FAULT_FIELD_WHAT_HAPPENED = "What happened:";
1252
- declare const FAULT_FIELD_WHICH_LAYER = "Which layer:";
1253
- declare const FAULT_FIELD_WHAT_PRESERVED = "What was preserved:";
1254
- declare const FAULT_FIELD_WHAT_TO_TRY = "What to try:";
1255
- /**
1256
- * Render a `SelfhostedControlError` into the doctrine's four in-band fields with a
1257
- * correct retry verdict. The fault CLASS is read from the typed flags (never the
1258
- * message prose), so each class gets the right "which layer" and "what to try":
1259
- *
1260
- * - PAYLOAD_TOO_LARGE → the command ran but its reply was too big to return; the
1261
- * fix is to bound the output, not to retry.
1262
- * - DRAINING → pre-admission backpressure; nothing ran; safe to retry shortly.
1263
- * - CONSENT_REQUIRED → a machine-side consent gate; retrying cannot help.
1264
- * - AGENT_OFFLINE → the machine link is down. If the transport KNOWS the request
1265
- * never left (`neverSent`), nothing ran; otherwise the effect is ambiguous and
1266
- * the model must check before re-running. Either way "try again" is wrong until
1267
- * the machine reconnects.
1268
- * - agent_reconnecting (TIMEOUT) → a transient link blip AFTER send: ambiguous, so
1269
- * a state-changing command must be checked before re-running (at-least-once).
1270
- * - NOT_FOUND → a command/filesystem miss, not a link fault.
1271
- * - FENCED → the active machine session changed (a swap); re-run.
1272
- * - default (OS/UNSUPPORTED/STREAM/PROTOCOL) → the command/op failed on the machine.
1273
- */
1274
- declare function renderSelfhostedFault(error: SelfhostedControlError): string;
1275
-
1276
- /**
1277
- * The read-only, idempotent-safe control ops that MAY be retried after a TIMEOUT
1278
- * / `agent_reconnecting` blip.
1279
- *
1280
- * A timed-out request is AT-LEAST-ONCE on the wire: the agent may have RECEIVED
1281
- * and fully EXECUTED the op before its reply was lost, so re-issuing it is only
1282
- * safe when a second execution is observably identical to one — i.e. a pure read.
1283
- * Every MUTATING op (`exec`, `git`, `fsWrite`/`fsMove`/`fsMkdir`/`fsRemove`, and
1284
- * the pty/desktop input ops) is DELIBERATELY EXCLUDED: re-running a mutation that
1285
- * already ran on the machine is a real correctness hazard (a duplicated write, a
1286
- * second `git push`, a re-applied command). Only DRAINING (a pre-admission
1287
- * rejection — see `decideSelfhostedRetry`) is safe to retry for those.
1288
- */
1289
- declare const SELFHOSTED_IDEMPOTENT_READONLY_OPS: ReadonlySet<string>;
1290
- /**
1291
- * DRAINING is a pre-admission backpressure rejection (the op never started), so it
1292
- * is safe to retry for ANY op kind. The budget is split by op class:
1293
- *
1294
- * - NON-EXEC control ops (fs/git/desktop/pty) keep a SHORT budget — 3 retries,
1295
- * worst-case backoff 500+1000+2000 = 3.5s (≤5s). Their start latency matters
1296
- * (fs/git ops gate the model's next step), so a saturated machine should surface
1297
- * quickly rather than stall.
1298
- * - EXEC gets a LONG, patient budget — 10 retries, worst-case ≈55.5s (<60s). exec
1299
- * start is not latency-critical, and the agent-side admission pool is (until a
1300
- * later agent release) a FLAT 8 permits with no per-class split: a burst of slow
1301
- * execs can hold every permit and blanket-DRAIN all fs/git ops. A patient exec
1302
- * retry converts those DRAINING storms into mild queueing instead of a wall of
1303
- * failed tool calls, and pairs with the deliberately modest exec deadline (which,
1304
- * for the same 8-permit reason, defaults to 2min, not 5).
1305
- */
1306
- declare const SELFHOSTED_DRAINING_MAX_RETRIES = 3;
1307
- declare const SELFHOSTED_EXEC_DRAINING_MAX_RETRIES = 10;
1308
- /** A read-only op that timed out is retried at most once (see the at-least-once
1309
- * note above — even a read gets a single re-issue, not an unbounded loop). */
1310
- declare const SELFHOSTED_TIMEOUT_MAX_RETRIES = 1;
1311
- /** A fault the transport synthesized PRE-SEND (no connection / no responder) — the
1312
- * op provably never reached the machine, so it is safe to re-issue for ANY op kind.
1313
- * Short bounded budget (≈3.5s summed): enough to ride out a NATS reconnect blip
1314
- * quietly, not so long it stalls the model on a machine that is genuinely down. */
1315
- declare const SELFHOSTED_NEVER_SENT_MAX_RETRIES = 3;
1316
- /** Full-jitter backoff base (ms). Delay N is sampled in `[0, base * factor^N)`. */
1317
- declare const SELFHOSTED_RETRY_BACKOFF_BASE_MS = 500;
1318
- declare const SELFHOSTED_RETRY_BACKOFF_FACTOR = 2;
1319
- /** Per-delay ceiling: 500 → 1s → 2s → 4s → 8s, then flat at 8s. Bounds the summed
1320
- * backoff — non-exec (3 retries) ≈3.5s ≤5s; exec (10 retries) ≈55.5s <60s. */
1321
- declare const SELFHOSTED_RETRY_BACKOFF_CAP_MS = 8000;
1322
- type SelfhostedRetryDecision = {
1323
- readonly action: "fail";
1324
- } | {
1325
- readonly action: "retry";
1326
- readonly delayMs: number;
1327
- };
1328
- /**
1329
- * Full-jitter exponential backoff. Returns a delay in
1330
- * `[0, min(cap, base * factor^attempt))`. `jitter` is an injected sample in
1331
- * `[0, 1)` so the delay is fully deterministic under test.
1332
- */
1333
- declare function selfhostedRetryBackoffMs(attempt: number, jitter: number): number;
1334
- /**
1335
- * PURE retry policy for one selfhosted control-op reply.
1336
- *
1337
- * - `error.draining` (DRAINING — pre-admission host-work backpressure; the op was
1338
- * rejected at the pool gate and NEVER started) → retry for ANY op kind, since
1339
- * nothing executed. exec gets the long `SELFHOSTED_EXEC_DRAINING_MAX_RETRIES`
1340
- * budget (≈60s of patient queueing under the flat 8-permit pool); every other op
1341
- * keeps the short `SELFHOSTED_DRAINING_MAX_RETRIES` budget (≤5s).
1342
- * - `error.neverSent` (a pre-send AGENT_OFFLINE synthesis — no connection / no
1343
- * responder; the op provably never reached the machine) → retry for ANY op kind
1344
- * up to `SELFHOSTED_NEVER_SENT_MAX_RETRIES`. At-least-once does NOT apply (nothing
1345
- * executed), so even a mutation is safe to re-issue; this heals a NATS reconnect
1346
- * blip quietly. A truly-offline machine surfaces honestly after the short budget.
1347
- * - `error.reason === "agent_reconnecting"` (a TIMEOUT / transient blip AFTER send)
1348
- * → retry at most `SELFHOSTED_TIMEOUT_MAX_RETRIES`, and ONLY for a read-only
1349
- * idempotent-safe op (`SELFHOSTED_IDEMPOTENT_READONLY_OPS`). A timed-out
1350
- * MUTATION is never retried here — it may already have run (at-least-once).
1351
- * - Everything else → no retry at this layer:
1352
- * · FENCED is retried by the routing proxy against a RE-RESOLVED backend
1353
- * (retrying here under the same stale epoch would just re-fence);
1354
- * · AGENT_OFFLINE / OS / PROTOCOL / PAYLOAD_TOO_LARGE / CONSENT_REQUIRED are
1355
- * not transient and must surface to the caller.
1356
- *
1357
- * The draining and timeout budgets are SEPARATE counters so a read that first hit
1358
- * backpressure still keeps its single timeout re-issue (and vice versa); both are
1359
- * bounded, so the total number of attempts — and the summed backoff — is bounded.
1360
- */
1361
- declare function decideSelfhostedRetry(input: {
1362
- opKind: string;
1363
- error: SelfhostedControlError;
1364
- drainingRetries: number;
1365
- timeoutRetries: number;
1366
- /** Prior never-sent retries. Optional (default 0) — only the pre-send AGENT_OFFLINE
1367
- * branch reads it, so callers that cannot hit that class may omit it. */
1368
- neverSentRetries?: number;
1369
- jitter: number;
1370
- }): SelfhostedRetryDecision;
1371
- /**
1372
- * The injected clock the retry loop drives — real in production, a deterministic
1373
- * fake in tests. `jitter()` returns a full-jitter sample in `[0, 1)`.
1374
- */
1375
- interface SelfhostedRetryClock {
1376
- sleep(ms: number): Promise<void>;
1377
- jitter(): number;
1378
- }
1379
- /** The production clock: a real timer + `Math.random()` jitter. */
1380
- declare const defaultSelfhostedRetryClock: SelfhostedRetryClock;
1381
-
1382
- /** The per-op frame subject (runner→server, fire-and-forget). Mirrors the
1383
- * runner's wire constant: `agent.<ws>.<id>.op.<op_id>`. */
1384
- declare function opFrameSubject(workspaceId: string, agentId: string, opId: string): string;
1385
- /** The per-agent ack subject (server→runner, fire-and-forget). Mirrors the
1386
- * runner's wire constant: `agent.<ws>.<id>.ack`. */
1387
- declare function opAckSubject(workspaceId: string, agentId: string): string;
1388
- /** A live subscription handle. `unsubscribe` is idempotent and never throws. */
1389
- interface OpStreamSubscription {
1390
- unsubscribe(): void;
1391
- }
1392
- /**
1393
- * The op-stream transport seam. `subscribe` MUST be established before the
1394
- * OpStart that makes frames flow (subscription-before-start is a protocol
1395
- * invariant: no frame is published before the consumer exists on a healthy
1396
- * path; a missed frame is healed via OpAttach replay regardless). `onMessage`
1397
- * is invoked in arrival order; a decode failure inside the handler must be
1398
- * contained by the caller (a torn frame must never kill the subscription).
1399
- */
1400
- interface OpStreamTransport {
1401
- subscribe(subject: string, onMessage: (payload: Uint8Array) => void): Promise<OpStreamSubscription>;
1402
- publish(subject: string, payload: Uint8Array): Promise<void>;
1403
- }
1404
- /** Thrown when the transport has no live connection: the op-stream path is
1405
- * UNAVAILABLE (never silently degraded). The session catches this pre-start
1406
- * and falls back to the legacy exec — the op provably never started. */
1407
- declare class OpStreamUnavailableError extends Error {
1408
- readonly name = "OpStreamUnavailableError";
1409
- }
1410
- /**
1411
- * The minimal NATS surface the transport needs — mirrors the `nats`
1412
- * `NatsConnection` subscribe/publish WITHOUT importing `nats` into the
1413
- * agent-loop-free runtime leaf (the worker injects the live `@opengeni/events`
1414
- * bus connection, exactly like `NatsRequestConnection` in control-rpc.ts).
1415
- * `subscribe` returns the nats.js Subscription shape: an async iterable of
1416
- * messages plus `unsubscribe()`.
1417
- */
1418
- interface NatsOpStreamConnection {
1419
- subscribe(subject: string): AsyncIterable<{
1420
- data: Uint8Array;
1421
- }> & {
1422
- unsubscribe(): void;
1423
- };
1424
- publish(subject: string, payload: Uint8Array): void;
1425
- }
1426
- /**
1427
- * The NATS-backed transport. Lazy memoized factory (identical discipline to
1428
- * `NatsControlRpc`): the connection resolves on first use; a null factory
1429
- * result or a dial failure surfaces `OpStreamUnavailableError` — the caller
1430
- * (the session) falls back to the LEGACY exec path rather than failing the op,
1431
- * so a NATS-less boot or a torn bus never strands a turn on a dead transport.
1432
- */
1433
- declare class NatsOpStreamTransport implements OpStreamTransport {
1434
- private readonly connect;
1435
- private connection;
1436
- private connecting;
1437
- constructor(connect: () => Promise<NatsOpStreamConnection | null>);
1438
- private resolveConnection;
1439
- subscribe(subject: string, onMessage: (payload: Uint8Array) => void): Promise<OpStreamSubscription>;
1440
- publish(subject: string, payload: Uint8Array): Promise<void>;
1441
- }
1442
-
1443
- /** Initial send-credit window (bytes) when the caller does not size it. Matches
1444
- * the wire default OpStart advertises (~4 MiB). */
1445
- declare const OP_STREAM_DEFAULT_WINDOW_BYTES: number;
1446
- /** Cumulative-ack repetition cadence while an op is live (ruling M1: acks are
1447
- * best-effort and healed by repetition). */
1448
- declare const OP_STREAM_ACK_INTERVAL_MS = 5000;
1449
- /** Frame-silence threshold before the client starts probing with OpQuery. The
1450
- * runner emits Progress every 5s while an op is alive and quiet, so 30s of
1451
- * TOTAL silence means frames are not reaching us (or the op is gone). */
1452
- declare const OP_STREAM_SILENCE_TIMEOUT_MS = 30000;
1453
- /** How long a silent op is held open across a machine reconnect window before
1454
- * the client fails it typed (PROTOCOL liveness rule: hold ≤120s, then only a
1455
- * definitive answer fails the op). */
1456
- declare const OP_STREAM_RECONNECT_HOLD_MS = 120000;
1457
- /**
1458
- * The durable-resume journal the worker adapts onto Temporal (design delta Δ1:
1459
- * the attach generation is `Info.currentAttemptScheduledTimestampMs` — strictly
1460
- * monotonic across worker-death redispatches, which `Info.attempt` is NOT under
1461
- * `maximumAttempts: 1`). `persistSettled` folds the op's settled frontier into
1462
- * the activity heartbeat details and MUST complete before the wire final ack
1463
- * (the durable-before-wire-ack gate); the default no-journal client (tests,
1464
- * non-activity callers) uses generation "1" and skips persistence.
1465
- */
1466
- interface OpStreamJournal {
1467
- /** The consumer's attach generation (u64 decimal string). */
1468
- attachGeneration(): string;
1469
- /** Durably record `opId` as settled at `exitSeq` BEFORE its final ack. */
1470
- persistSettled(opId: string, exitSeq: string): void | Promise<void>;
1471
- }
1472
- /** A completed op-stream exec: the legacy-shaped response plus the healing
1473
- * telemetry the session folds into its op observation. */
1474
- interface OpStreamExecOutcome {
1475
- response: ExecResponse;
1476
- /** Attach/query heals that occurred after streaming began (gap replays,
1477
- * silence probes that re-attached). >0 marks the op `healed`. */
1478
- heals: number;
1479
- /** OpStart-level retries (draining/blip) before the op was accepted. */
1480
- startRetries: number;
1481
- /** Total reassembled Data payload bytes (the observer's replyBytes). */
1482
- replyBytes: number;
1483
- }
1484
-
1485
- /** The V4A-diff applier the SDK's apply_patch editor uses. The leaf cannot import
1486
- * `@openai/agents`'s `applyDiff` (the agent-loop root the leaf forbids), so the
1487
- * runtime barrel (`packages/runtime/src/index.ts`, which DOES import that root)
1488
- * injects it via `setSelfhostedApplyDiff` at module load. Until injected,
1489
- * `createEditor()` surfaces a clear error rather than a silent wrong-edit. */
1490
- type SelfhostedApplyDiff = (input: string, diff: string, mode?: "default" | "create") => string;
1491
- /** Register the SDK's `applyDiff` so `SelfhostedSession.createEditor()` can apply
1492
- * V4A diffs over the NATS fs ops. Called once by the runtime barrel. */
1493
- declare function setSelfhostedApplyDiff(fn: SelfhostedApplyDiff): void;
1494
- /** The structural Editor surface the SDK's filesystem capability consumes (the
1495
- * three apply_patch operations). Mirrors `@openai/agents-core`'s `Editor`. */
1496
- interface SelfhostedEditor {
1497
- createFile(operation: {
1498
- path: string;
1499
- diff: string;
1500
- }, context?: unknown): Promise<{
1501
- output?: string;
1502
- } | void>;
1503
- updateFile(operation: {
1504
- path: string;
1505
- diff: string;
1506
- moveTo?: string;
1507
- }, context?: unknown): Promise<{
1508
- output?: string;
1509
- } | void>;
1510
- deleteFile(operation: {
1511
- path: string;
1512
- }, context?: unknown): Promise<{
1513
- output?: string;
1514
- } | void>;
1515
- }
1516
- /** The image tool-output shape the SDK's view_image tool expects (mirror of
1517
- * `ToolOutputImage` — not re-exported by `@openai/agents/sandbox`, so structural). */
1518
- interface SelfhostedImageOutput {
1519
- type: "image";
1520
- image: {
1521
- data: Uint8Array;
1522
- mediaType: string;
1523
- };
1524
- }
1525
- /** Default control-op timeout. A transient miss surfaces as `agent_reconnecting`
1526
- * (the turn pauses + retries); it is NOT a hard failure. */
1527
- declare const SELFHOSTED_DEFAULT_TIMEOUT_MS = 30000;
1528
- /** The relay-URL shape config the session needs to build a stream endpoint. M8b
1529
- * wires the real relay deployment behind THIS seam so `buildStreamUrl` works
1530
- * unchanged behind `resolveExposedPort`. */
1531
- interface SelfhostedRelayConfig {
1532
- /** The relay edge host (no scheme), e.g. "relay.opengeni.ai". */
1533
- host: string;
1534
- /** The relay port. Defaults to 443 (the relay terminates TLS). */
1535
- port?: number;
1536
- /** Whether the relay endpoint is TLS (wss/https). Defaults true. */
1537
- tls?: boolean;
1538
- /** The relay's stream-dial path (the `opengeni-relay` wss route). Defaults to
1539
- * "/stream" — the route the relay listens on (M8b). */
1540
- path?: string;
1541
- }
1542
- /** The relay's default wss dial path (the `opengeni-relay` server route). */
1543
- declare const SELFHOSTED_RELAY_STREAM_PATH = "/stream";
1544
- /**
1545
- * The op-stream injection (op-stream protocol v1.1 — see op-stream.ts). PRESENT
1546
- * = the streaming exec transport is enabled for this session (the worker
1547
- * injects it only when the runner advertised `Capabilities.op_stream` AND the
1548
- * server flag is on — the leaf carries no flag logic). ABSENT = the legacy
1549
- * monolithic exec, byte-identical to before. The timing knobs exist for tests.
1550
- */
1551
- interface SelfhostedOpStreamDeps {
1552
- transport: OpStreamTransport;
1553
- /** The durable-resume journal (attach generation + settled-frontier persist).
1554
- * Absent (tests, non-activity callers) ⇒ generation "1", no persistence. */
1555
- journal?: OpStreamJournal;
1556
- windowBytes?: number;
1557
- ackIntervalMs?: number;
1558
- silenceTimeoutMs?: number;
1559
- reconnectHoldMs?: number;
1560
- }
1561
- interface SelfhostedSessionDeps {
1562
- workspaceId: string;
1563
- agentId: string;
1564
- controlRpc: ControlRpc;
1565
- relay: SelfhostedRelayConfig;
1566
- /** Op-stream exec transport (present = enabled; see SelfhostedOpStreamDeps). */
1567
- opStream?: SelfhostedOpStreamDeps;
1568
- /** The lease/active epoch this session is fenced under (echoed on every
1569
- * ControlRequest so the agent can reject a stale op with ERROR_CODE_FENCED).
1570
- * Defaults to 0 (no fence) for the negotiation-only / test path. */
1571
- epoch?: number;
1572
- /** The CONTROL-op timeout (ping/fs/desktop/pty and the exec wire fallback). The
1573
- * agent's own control liveness must stay responsive, so this is short (the 30s
1574
- * default). Override for tests or per-deployment tuning. */
1575
- timeoutMs?: number;
1576
- /**
1577
- * The EXEC process deadline — distinct from `timeoutMs`. A real command
1578
- * (compile, test run, install) routinely outlives the short control timeout, so
1579
- * exec gets its own, much larger budget: the agent kills the child at this wall,
1580
- * and the wire waits `execTimeoutMs + SELFHOSTED_EXEC_REPLY_GRACE_MS`. Omitted ⇒
1581
- * falls back to `timeoutMs` (byte-identical to before this split), so any caller
1582
- * that does not opt in keeps today's behavior.
1583
- */
1584
- execTimeoutMs?: number;
1585
- /** The clock the bounded control-op retry loop drives (sleep + jitter). Injected
1586
- * so tests are deterministic; defaults to a real timer + `Math.random()`. */
1587
- retryClock?: SelfhostedRetryClock;
1588
- /** A fire-and-forget observer invoked once per completed control op (out-of-band
1589
- * telemetry — metrics + `machine.*` events). The worker/api wire it to the sinks;
1590
- * the leaf only invokes it (guarded so it can never break an op). Omitted ⇒ no-op. */
1591
- onOp?: SelfhostedOpObserver;
1592
- /**
1593
- * The run's declared sandbox environment — the SAME `Record<string,string>` the
1594
- * worker turn passes to `runtime.buildAgent`'s `sandboxEnvironment` (and that the
1595
- * agent's TARGET manifest, `buildManifest`, carries). The SDK injects this
1596
- * selfhosted session NON-OWNED and applies the agent's manifest as a provided-
1597
- * session delta; `validateNoEnvironmentDelta` throws "Live sandbox sessions cannot
1598
- * change manifest environment variables" on ANY env mismatch. So `state.manifest`'s
1599
- * `environment` MUST EQUAL the turn's environment for the delta to be empty. The
1600
- * selfhosted exec routes over NATS and does NOT consume this env wholesale (the
1601
- * machine owns its own shell), EXCEPT the allowlisted toolspace pointers it
1602
- * exports per exec so `ogtool` works — see SELFHOSTED_EXEC_ENV_ALLOWLIST. The
1603
- * manifest carries the full env for parity. Omitted → `{}` (the negotiation-only
1604
- * / test path, which never applies a turn manifest, so there is no delta).
1605
- */
1606
- environment?: Record<string, string>;
1607
- /**
1608
- * The session's working directory — the BASE every path/cwd is rooted under (see
1609
- * `toMachinePath` / SELFHOSTED_VIRTUAL_ROOT). A launch-workspace_root-relative
1610
- * subdir (resolved under workspace_root by the agent's `resolve_cwd`) or an
1611
- * absolute machine path. Omitted/empty (the default) ⇒ "" ⇒ today's behavior
1612
- * exactly (an empty cwd lets the agent substitute its workspace_root).
1613
- */
1614
- workingDir?: string;
1615
- }
1616
- /** The Channel-A `exec` result shape (a structural superset of the SDK's). */
1617
- interface SelfhostedExecResult {
1618
- output: string;
1619
- stdout: string;
1620
- stderr: string;
1621
- exitCode: number | null;
1622
- /** True when the agent killed the child at the exec deadline. Additive to the
1623
- * Channel-A superset (consumers that don't read it are unaffected); `execCommand`
1624
- * reads it to surface the deadline hint on the stdout-only SDK path. */
1625
- timedOut?: boolean;
1626
- }
1627
- /** The `exec` args the structural surface accepts (mirrors ChannelAExecArgs). */
1628
- interface SelfhostedExecArgs {
1629
- cmd: string;
1630
- workdir?: string | undefined;
1631
- shell?: string | undefined;
1632
- login?: boolean | undefined;
1633
- tty?: boolean | undefined;
1634
- runAs?: string | undefined;
1635
- }
1636
- /**
1637
- * The persistable session state. For selfhosted this is `{agentId}` ONLY — there
1638
- * is NO provider box id, no snapshot, no manifest. Resume re-addresses the live
1639
- * subject; the machine itself is the persistence (`persistable:false`).
1640
- */
1641
- interface SelfhostedSessionState {
1642
- agentId: string;
1643
- }
1644
- /**
1645
- * A live selfhosted session — the structural `SandboxSessionLike` surface over a
1646
- * `ControlRpc`. Mirrors Modal's session shape so Channel-A/viewer/computer-use
1647
- * consume it unchanged.
1648
- */
1649
- declare class SelfhostedSession {
1650
- readonly backendId: "selfhosted";
1651
- readonly workspaceId: string;
1652
- readonly agentId: string;
1653
- private readonly controlRpc;
1654
- private readonly relay;
1655
- private readonly epoch;
1656
- private readonly timeoutMs;
1657
- /** The exec process deadline (falls back to `timeoutMs` when unset). */
1658
- private readonly execTimeoutMs;
1659
- private readonly retryClock;
1660
- private readonly onOp;
1661
- private readonly subject;
1662
- /** The op-stream exec client — constructed iff `deps.opStream` was injected
1663
- * (= the streaming transport is enabled for this session). */
1664
- private readonly opStreamClient;
1665
- /** The session working directory — the path/cwd base every op is rooted under
1666
- * (see `toMachinePath`). "" by default ⇒ today's workspace_root behavior. */
1667
- private readonly workingDir;
1668
- /**
1669
- * The structural `state` slice consumers read. `agentId`/`instanceId` serve the
1670
- * channel-a `readInstanceId` + docker-network decoration (the agentId IS the
1671
- * identity). `manifest` is the slice the @openai/agents SDK reads AND writes per
1672
- * turn (serializeManifestEnvironment / validateProvidedSessionManifestUpdate read
1673
- * `manifest.root` + iterate `manifest.environment`; providedSessionManifest WRITES
1674
- * `state.manifest = next`). It must be a real, MUTABLE Manifest field — when the
1675
- * RoutingSandboxSession proxy resolves THIS as the active backend it returns
1676
- * `session.state` BY REFERENCE, so the SDK's read and write must both land on a
1677
- * well-formed Manifest here (defined `root`, object `environment`). Without it the
1678
- * SDK crashes with `undefined is not an object (evaluating 'current.root')`.
1679
- *
1680
- * `manifest` is intentionally a plain mutable field (not `readonly`) so the SDK's
1681
- * `state.manifest = next` write succeeds. It is NOT part of the persistable state
1682
- * (`serializeSessionState` round-trips `{agentId}` only).
1683
- *
1684
- * `environment` is the SDK `SandboxSessionState.environment` (a `Record<string,
1685
- * string>`). It MUST be present because the GROUP box's client serializes THIS
1686
- * (the active backend's) state at end-of-turn — the non-owned injected session is
1687
- * serialized via the CONFIGURED client (modal in prod), NOT the selfhosted client.
1688
- * Modal's `serializeRemoteSandboxSessionState` does `Object.entries(state.environment)`;
1689
- * an absent field crashes the post-turn RunState serialize with "Object.entries
1690
- * requires that input parameter not be null or undefined". It carries the run's
1691
- * threaded environment (or `{}`). The resulting modal-tagged envelope is inert for
1692
- * selfhosted (resume re-addresses the machine by agentId via the lease pointer,
1693
- * never from this SDK envelope), so its only job is to not crash the serialize.
1694
- */
1695
- readonly state: {
1696
- agentId: string;
1697
- instanceId: string;
1698
- manifest: Manifest;
1699
- environment: Record<string, string>;
1700
- };
1701
- constructor(deps: SelfhostedSessionDeps);
1702
- /** Invoke the injected per-op observer, guarded: a telemetry tap must NEVER break
1703
- * an op (a throwing sink is swallowed). No-op when no observer is wired. */
1704
- private emitOp;
1705
- /**
1706
- * Issue a control op, decoding the agent's reply or throwing the mapped
1707
- * `SelfhostedControlError` on an AgentError (incl. a synthesized offline /
1708
- * timeout error from the transport).
1709
- *
1710
- * Two failure modes are retried in-place (the decision is the pure
1711
- * `decideSelfhostedRetry` policy):
1712
- * - DRAINING — a pre-admission host-work backpressure rejection; the op never
1713
- * started, so it is safe to re-issue for ANY op kind (bounded).
1714
- * - a TIMEOUT / `agent_reconnecting` blip — re-issued ONCE, and ONLY for a
1715
- * read-only idempotent-safe op. A timed-out MUTATION is NEVER re-issued: it
1716
- * may already have executed on the machine (at-least-once), which would
1717
- * duplicate the write/command. FENCED is intentionally NOT retried here — the
1718
- * routing proxy retries it against a re-resolved backend under a fresh epoch.
1719
- * Each attempt carries a FRESH `requestId` (a retry is a distinct request).
1720
- */
1721
- private call;
1722
- /** The clamped exec process deadline (ms): the exec-specific budget when threaded,
1723
- * else the control timeout. Shared by `exec()` (the wire deadline) and
1724
- * `execCommand()` (the timed-out hint's "N-second limit"), so the two never drift. */
1725
- private execDeadlineMs;
1726
- /** Channel-A `exec`: run a command on the machine and return its output. */
1727
- exec(args: SelfhostedExecArgs): Promise<SelfhostedExecResult>;
1728
- /** The legacy monolithic exec request/reply — the permanent fallback wire
1729
- * form (and the only form for runners without `op_stream`). */
1730
- private execLegacy;
1731
- /**
1732
- * The op-stream exec path: the SAME `ExecRequest`, streamed (see
1733
- * op-stream.ts). The durable op id comes from the tool-call correlation
1734
- * context (`{callId}:{ordinal}` — B1) when this exec runs inside an SDK tool
1735
- * invocation; a non-tool caller falls back to a random unique id (never
1736
- * collides, merely not stable across a turn re-dispatch). Emits the SAME
1737
- * per-op observation the legacy path does, with `replyBytes` filled from the
1738
- * reassembled stream (the field the framed transport was designed to own).
1739
- */
1740
- private execViaOpStream;
1741
- /**
1742
- * The turn-end op-stream durability hook: persists each settled op's frontier
1743
- * to the journal, then final-acks it on the wire (licensing the runner to GC
1744
- * its retained frames). The WORKER calls this after the turn's results are
1745
- * durably recorded — never mid-turn (the durable-before-wire-ack ordering).
1746
- * A no-op for sessions without the op-stream transport.
1747
- */
1748
- finalizeOpStreamOps(): Promise<void>;
1749
- /** SDK shell capability `execCommand`: run a command and return its stdout (the
1750
- * `exec_command` tool). Selfhosted exec is non-interactive (no PTY) — `tty` is
1751
- * ignored; `supportsPty()` is false so the SDK never offers a stdin session.
1752
- *
1753
- * On a DEADLINE timeout the SDK path is stdout-only (the model sees `result.output`
1754
- * and nothing else), so the deadline hint that `exec()` puts on stderr would be
1755
- * invisible — a >deadline command would return `{"output":""}` and the model would
1756
- * conclude "no output" with no idea it was killed. So when the result timed out we
1757
- * surface the hint HERE, on stdout: the hint alone when stdout is empty, or appended
1758
- * after a newline when there is partial output (a timed-out result is already not
1759
- * cleanly parseable — silence is worse than an explanatory suffix). The structured
1760
- * `exec()` result is left untouched for the Channel-A parsers. */
1761
- execCommand(args: {
1762
- cmd: string;
1763
- workdir?: string;
1764
- runAs?: string;
1765
- }): Promise<string>;
1766
- /**
1767
- * Physical turn-cancellation seam used by the runtime tool fence. Only the
1768
- * op-stream transport has a process-tree cancellation protocol; a legacy
1769
- * runner returns false and the caller remains fail-closed until its exec
1770
- * deadline rather than claiming the machine is quiescent.
1771
- */
1772
- cancelExecCommand(opId: string): Promise<boolean>;
1773
- /** SDK shell capability never calls this (gated on `supportsPty()` which is
1774
- * false), but the surface advertises it. Selfhosted exec has no interactive PTY
1775
- * session over the structured RPC, so a stdin write is unsupported. */
1776
- supportsPty(): boolean;
1777
- /** SDK filesystem capability `view_image`: read the image bytes off the machine
1778
- * and wrap them in the tool-output image shape (magic-byte sniff + path fallback,
1779
- * mirroring the SDK's `imageOutputFromBytes`). */
1780
- viewImage(args: {
1781
- path: string;
1782
- runAs?: string;
1783
- }): Promise<SelfhostedImageOutput>;
1784
- /** SDK skills/filesystem `pathExists`: whether a path exists on the machine. */
1785
- pathExists(path: string, _runAs?: string): Promise<boolean>;
1786
- /** SDK skills `listDir`: list a directory as `{name, path, type}[]`. */
1787
- listDir(args: {
1788
- path: string;
1789
- runAs?: string;
1790
- }): Promise<Array<{
1791
- name: string;
1792
- path: string;
1793
- type: "file" | "dir" | "other";
1794
- }>>;
1795
- /** SDK manifest-delta `materializeEntry`: a NO-OP for selfhosted. Source
1796
- * materialization (cloning repos / staging files into the box) is how cloud
1797
- * providers prepare a fresh box; a bring-your-own machine already owns its
1798
- * filesystem and is prepared by the agent itself, so there is nothing to stage.
1799
- * Present (not absent) so the SDK's provided-session manifest apply path — which
1800
- * requires `applyManifest()` OR `materializeEntry()` when the agent declares
1801
- * entries — is satisfied without error. The selfhosted manifest declares no
1802
- * entries, so in practice this is never invoked with a real entry. */
1803
- materializeEntry(_args: {
1804
- path: string;
1805
- entry: unknown;
1806
- runAs?: string;
1807
- }): Promise<void>;
1808
- /** SDK filesystem capability `createEditor`: the apply_patch host. Applies V4A
1809
- * diffs over the NATS fs ops (read → applyDiff → write). `applyDiff` is the SDK's
1810
- * own parser, injected by the runtime barrel (the leaf cannot import it). */
1811
- createEditor(runAs?: string): SelfhostedEditor;
1812
- /** Channel-A `readFile`: read a file off the machine (binary-safe). */
1813
- readFile(args: {
1814
- path: string;
1815
- runAs?: string;
1816
- maxBytes?: number;
1817
- }): Promise<Uint8Array>;
1818
- /** Write a file onto the machine (the fs surface the descriptor advertises). */
1819
- writeFile(args: {
1820
- path: string;
1821
- content: string | Uint8Array;
1822
- createParents?: boolean;
1823
- append?: boolean;
1824
- }): Promise<number>;
1825
- /** List a directory on the machine. */
1826
- listFiles(args: {
1827
- path: string;
1828
- recursive?: boolean;
1829
- }): Promise<NonNullable<ControlResponse["result"]> & {
1830
- $case: "fsList";
1831
- }>;
1832
- /** Stat a path on the machine. */
1833
- statFile(args: {
1834
- path: string;
1835
- }): Promise<{
1836
- exists: boolean;
1837
- }>;
1838
- /** Computer-use WRITE op: inject one synthetic desktop input event (pointer/key/
1839
- * scroll) on the machine's OWN display. The agent injects via CGEvent (macOS) /
1840
- * XTEST (Linux) and CONSENT-GATES it — an unconsented call never touches the OS
1841
- * and surfaces the mapped control error (ERROR_CODE_CONSENT_REQUIRED) via `call()`. */
1842
- desktopInput(event: DesktopInputRequest["event"]): Promise<void>;
1843
- /** Computer-use VIEW op: capture a single PNG screenshot of the machine's desktop
1844
- * plus its geometry (via ScreenCaptureKit / x11). NOT consent-gated (a view op —
1845
- * the view/control decoupling), so it works with a display but no screen-control
1846
- * consent. Returns the raw encoded bytes + the ENCODED width/height, plus the
1847
- * NATIVE (pre-downscale) geometry: when the agent had to downscale the PNG to fit
1848
- * the transport's max payload, `nativeWidth`/`nativeHeight` carry the original
1849
- * capture size so the computer-use layer can scale model clicks (in encoded-pixel
1850
- * space) back to native pixels. An older agent leaves them 0 → read as "same as
1851
- * width/height" (no downscale). */
1852
- screenshot(): Promise<{
1853
- png: Uint8Array;
1854
- width: number;
1855
- height: number;
1856
- nativeWidth: number;
1857
- nativeHeight: number;
1858
- }>;
1859
- /** A cheap liveness probe — request a Ping on the subject; returns true iff a
1860
- * responder answered (no AgentError). Used by `negotiateSelfhostedCapabilities`.
1861
- * The wire `nonce` is a uint64 (a numeric string), so the default is a random
1862
- * numeric value — NOT a UUID (which would fail proto uint64 encoding). */
1863
- ping(nonce?: string): Promise<boolean>;
1864
- /**
1865
- * Resolve an exposed port to a relay stream endpoint (the viewer/pty plane).
1866
- * Returns the relay URL SHAPE — `{host:relay, port, tls, query:channel-key}` —
1867
- * after asking the agent to ensure a stream channel for the port. M8b wires the
1868
- * real relay tier (the byte pump) behind THIS seam.
1869
- *
1870
- * THE CHANNEL-KEY QUERY (the M8b relay-dial contract): the relay
1871
- * routes by `{workspaceId, agentId, port}` — the EXACT `ChannelKey::query` the
1872
- * agent's relay client (`opengeni-agent-stream`) appends when it registers the
1873
- * producer side: `ws=<workspaceId>&agent=<agentId>&port=<port>`. We append the
1874
- * agent-registered `channel=<channelId>` as a correlation hint. So the viewer
1875
- * dials `wss://<relay>/stream?ws=&agent=&port=&channel=` and presents the minted
1876
- * `ogs_` token in-band (NEVER as a URL param) — the relay pairs it with the
1877
- * producer by the routing key.
1878
- */
1879
- resolveExposedPort(port: number): Promise<ExposedPortEndpoint>;
1880
- /** Round-trip the persistable state — `{agentId}` ONLY (resume = re-address). */
1881
- serializeSessionState(): Promise<SelfhostedSessionState>;
1882
- }
1883
- /**
1884
- * The selfhosted SDK-client surface the registry builds. `backendId:"selfhosted"`
1885
- * (the resume-fence field asserted against the descriptor). `create()`/`resume()`
1886
- * return a `SelfhostedSession` bound to `{workspaceId, agentId, controlRpc}`.
1887
- *
1888
- * `create()` and `resume()` are IDENTICAL for selfhosted — there is no box to
1889
- * provision (the machine already exists); both just bind a session to the live
1890
- * subject. `serializeSessionState`/`deserializeSessionState` round-trip
1891
- * `{agentId}` only.
1892
- *
1893
- * The `controlRpc` is constructed LAZILY via an injected factory (defaulting to
1894
- * `NatsControlRpc`); a session built before NATS is configured surfaces
1895
- * `agent_offline` on its first op rather than failing at construction.
1896
- */
1897
- declare class SelfhostedSandboxClient {
1898
- readonly backendId: "selfhosted";
1899
- readonly supportsDefaultOptions = false;
1900
- private readonly workspaceId;
1901
- private readonly relay;
1902
- private readonly controlRpcFactory;
1903
- private readonly defaultAgentId;
1904
- private readonly epoch;
1905
- private readonly timeoutMs;
1906
- private readonly execTimeoutMs;
1907
- private readonly environment;
1908
- private readonly workingDir;
1909
- private readonly onOp;
1910
- private readonly opStream;
1911
- private controlRpcMemo;
1912
- constructor(opts: {
1913
- workspaceId: string;
1914
- relay: SelfhostedRelayConfig;
1915
- /** Lazily build the ControlRpc (defaults to NatsControlRpc in the provider). */
1916
- controlRpcFactory: () => ControlRpc;
1917
- /** The agentId a bare create()/resume() (no state) binds to. Optional: the
1918
- * resume path supplies it via deserializeSessionState. */
1919
- agentId?: string;
1920
- epoch?: number;
1921
- /** The control-op timeout threaded into every bound session. */
1922
- timeoutMs?: number;
1923
- /** The exec process deadline threaded into every bound session (distinct from
1924
- * `timeoutMs`; falls back to it when unset). See SelfhostedSessionDeps.execTimeoutMs. */
1925
- execTimeoutMs?: number;
1926
- /** The run's declared sandbox environment, threaded into every bound session's
1927
- * `state.manifest.environment` so the SDK's per-turn manifest-env delta is
1928
- * empty (validateNoEnvironmentDelta). See SelfhostedSessionDeps.environment.
1929
- * Omitted → `{}` (the negotiation-only path; no turn manifest is applied). */
1930
- environment?: Record<string, string>;
1931
- /** The session working directory threaded into every bound session (the path/
1932
- * cwd base; see SelfhostedSessionDeps.workingDir). Omitted/empty ⇒ the default
1933
- * workspace_root behavior. */
1934
- workingDir?: string;
1935
- /** The per-op observer threaded into every bound session (out-of-band telemetry). */
1936
- onOp?: SelfhostedOpObserver;
1937
- /** The op-stream exec transport threaded into every bound session (present
1938
- * = enabled; see SelfhostedOpStreamDeps). */
1939
- opStream?: SelfhostedOpStreamDeps;
1940
- });
1941
- private controlRpc;
1942
- private bind;
1943
- /** Bind a session to the live agent subject. There is no box to provision. */
1944
- create(_manifest?: unknown, _options?: unknown): Promise<SelfhostedSession>;
1945
- /** Resume = re-address the subject. Identical to create — no provider state. */
1946
- resume(state: SelfhostedSessionState | Record<string, unknown>, _options?: unknown): Promise<SelfhostedSession>;
1947
- /** Serialize a live session's state → `{agentId}` ONLY. */
1948
- serializeSessionState(state: SelfhostedSessionState | {
1949
- agentId?: string;
1950
- } | unknown): Promise<SelfhostedSessionState>;
1951
- /** Deserialize `{agentId}` from the persisted envelope. */
1952
- deserializeSessionState(state: Record<string, unknown>): Promise<SelfhostedSessionState>;
1953
- /** selfhosted is NOT persistable — there is no owned session state to preserve
1954
- * (the machine is the persistence). The lease never snapshots it. */
1955
- canPersistOwnedSessionState(): Promise<boolean>;
1956
- private requireAgentId;
1957
- }
1958
- /**
1959
- * The dependency shape `buildSelfhostedBackendSession` needs to bind a live
1960
- * selfhosted session to a target machine. A structural superset of the fields the
1961
- * routing resolver (backend-resolver.ts) reads off its deps + pointer, and the
1962
- * fields the WORKER turn's machine-primary establish branch threads in — so a
1963
- * SINGLE build shape is shared by both (never two divergent constructions of the
1964
- * same SelfhostedSandboxClient/resume pair).
1965
- */
1966
- interface SelfhostedSessionBuild {
1967
- /** The workspace the machine's control-plane subject is scoped to. */
1968
- workspaceId: string;
1969
- /** The enrollment id == the agent id `agent.<ws>.<id>.rpc` addresses. */
1970
- agentId: string;
1971
- /** The relay-URL shape for stream endpoints. */
1972
- relay: SelfhostedRelayConfig;
1973
- /** Lazily build the live ControlRpc (the request-scoped NATS connection). */
1974
- controlRpcFactory: () => ControlRpc;
1975
- /** The lease/active epoch the session is fenced under (echoed on every op). */
1976
- epoch: number;
1977
- /** The run's declared sandbox environment → the session manifest.environment
1978
- * (env-parity; see SelfhostedSessionDeps.environment). */
1979
- environment?: Record<string, string>;
1980
- /** The session working directory (the path/cwd base). Null/absent ⇒ workspace_root. */
1981
- workingDir?: string | null;
1982
- /** The control-op timeout (ping/fs/desktop/pty). Absent ⇒ the 30s default. */
1983
- timeoutMs?: number;
1984
- /** The exec process deadline, distinct from `timeoutMs`. Absent ⇒ falls back to
1985
- * `timeoutMs` (a real turn threads the long exec budget here). */
1986
- execTimeoutMs?: number;
1987
- /** The per-op observer (out-of-band telemetry). Absent ⇒ no-op. */
1988
- onOp?: SelfhostedOpObserver;
1989
- /** The op-stream exec transport (present = enabled for the session; the
1990
- * worker injects it only when the runner advertised the capability AND the
1991
- * server flag is on). Absent ⇒ the legacy exec, byte-identical to before. */
1992
- opStream?: SelfhostedOpStreamDeps;
1993
- }
1994
- /**
1995
- * Build a live selfhosted session bound to a target machine: construct a request-
1996
- * scoped `SelfhostedSandboxClient` (fenced under `epoch`, carrying the run's env +
1997
- * working dir) and `resume()` it (= re-address the live subject — no provider box
1998
- * is created). Returns BOTH the client (the OWNED-sandbox client the turn injects,
1999
- * whose `serializeSessionState` round-trips `{agentId}`) and the live session.
2000
- *
2001
- * Shared by:
2002
- * - the routing resolver (backend-resolver.ts) — a swap target, where only the
2003
- * session is needed; and
2004
- * - the worker turn's machine-primary establish branch — where the client is the
2005
- * owned-sandbox client AND the session is the pinned routing default.
2006
- * Factoring it here keeps the two builds identical (no divergence in the fence
2007
- * epoch, env threading, or working-dir base).
2008
- */
2009
- declare function buildSelfhostedBackendSession(deps: SelfhostedSessionBuild): Promise<{
2010
- client: SelfhostedSandboxClient;
2011
- session: SelfhostedSession;
2012
- }>;
2013
- /**
2014
- * The selfhosted NotFound discriminator — THE load-bearing safety property
2015
- *: for selfhosted, `agent-offline` (no responder) is NEVER a
2016
- * provider NotFound. A user's real machine is not recreatable; if the lease saw
2017
- * agent-offline as NotFound it would cold-create a RIVAL box (a Modal box) for
2018
- * the user's machine. So this ALWAYS returns FALSE for selfhosted — there is no
2019
- * "box gone, recreate it" condition. An OS-level file NotFound is an op-level
2020
- * error the fs layer 404s; it is likewise NOT a session-recreate condition.
2021
- *
2022
- * `establishSandboxSessionFromEnvelope` cold-restores ONLY when the per-backend
2023
- * NotFound discriminator returns true; returning false here guarantees the
2024
- * selfhosted path never cold-creates a rival — the op surfaces agent_offline and
2025
- * the caller backs off / retries.
2026
- */
2027
- declare function isSelfhostedProviderNotFoundError(_error: unknown): false;
2028
-
2029
- /**
2030
- * Sanitize a tool call id into a legal NATS subject TOKEN fragment. The op id
2031
- * is interpolated into the per-op frame subject, whose tokens must not contain
2032
- * whitespace/control characters or the subject-structure characters (`.`,
2033
- * `*`, `>`); the runner refuses illegal ids loudly. The mapping is INJECTIVE
2034
- * (each disallowed char becomes `_<hex>_`), so two distinct call ids can never
2035
- * collide into one op id — a collision would merge two different execs through
2036
- * the idempotent-OpStart dedup and return the wrong result.
2037
- */
2038
- declare function sanitizeOpIdToken(raw: string): string;
2039
- /**
2040
- * Bind a tool-call correlation context around `fn` (the tool's execute). Every
2041
- * durable op id minted inside — however deep in the transport — is
2042
- * `{callId}:{ordinal}` with ordinals starting at 0 per invocation.
2043
- */
2044
- declare function runWithToolCallCorrelation<T>(callId: string, fn: () => T): T;
2045
- /**
2046
- * Mint the next durable op id for the current tool invocation, or null when no
2047
- * correlation context is bound (a non-tool caller, e.g. Channel-A structural
2048
- * exec). Callers fall back to a random unique id — safe (never collides, never
2049
- * wrongly dedups), merely not stable across a turn re-dispatch, which degrades
2050
- * that one op to today's at-least-once semantics.
2051
- */
2052
- declare function nextDurableOpId(): string | null;
2053
-
2054
- /**
2055
- * The structural slice of the M2 `@opengeni/db` `EnrollmentRecord` the selfhosted
2056
- * negotiation reads. Defined STRUCTURALLY (not imported from `@opengeni/db`) so
2057
- * the agent-loop-free sandbox leaf does not couple to the DB package's graph —
2058
- * the API/worker pass an `EnrollmentRecord`, which satisfies this shape. The
2059
- * fields: `status` (active gates reachability), `exposure` +
2060
- * `allowScreenControl` (whole-machine + screen-control consent),`hasDisplay`
2061
- * (the display plane), `lastSeenAt` (the reconnecting-window disambiguator).
2062
- */
2063
- interface SelfhostedEnrollment {
2064
- status: string;
2065
- exposure: string;
2066
- allowScreenControl: boolean;
2067
- hasDisplay: boolean;
2068
- lastSeenAt: string | null;
2069
- /** An un-cleared clean going-offline marker (the machine announced a typed
2070
- * GoingOffline). When set, the derivation reads the machine OFFLINE immediately,
2071
- * regardless of a still-fresh `lastSeenAt`. NULL ⇒ no pending goodbye. */
2072
- wentOfflineAt: string | null;
2073
- /** The typed reason string of the pending goodbye (rides alongside
2074
- * `wentOfflineAt`; NULL when there is no un-cleared marker). */
2075
- wentOfflineReason: string | null;
2076
- }
2077
- /** The derived liveness state of a selfhosted machine (the online/offline/
2078
- * reconnecting/consent/display matrix). */
2079
- interface SelfhostedLivenessState {
2080
- /** The dominant machine state. */
2081
- state: "online" | "reconnecting" | "offline";
2082
- /** Whole-machine + screen-control consent acknowledged (gates desktop input). */
2083
- consented: boolean;
2084
- /** A display (real or Xvfb) is present (gates the desktop pixel plane). */
2085
- hasDisplay: boolean;
2086
- }
2087
- /**
2088
- * The window after `lastSeenAt` within which a missed liveness probe is read as a
2089
- * transient BLIP (`reconnecting`) rather than a hard `offline`. Mirrors the
2090
- * resiliency model (§10.6: reconnecting after 1 missed window, offline after
2091
- * ~30s). A probe miss with a lastSeenAt inside this window → reconnecting.
2092
- */
2093
- declare const SELFHOSTED_RECONNECT_WINDOW_MS = 30000;
2094
- /**
2095
- * Derive the selfhosted liveness state from the enrollment row + a liveness probe
2096
- * outcome. The probe is the authoritative "is the agent answering NOW" signal;
2097
- * `lastSeenAt` disambiguates a probe-miss into reconnecting (recent) vs offline
2098
- * (stale / never seen).
2099
- *
2100
- * - no enrollment / revoked → offline (the machine isn't enrolled).
2101
- * - un-cleared clean goodbye → offline (an announced GoingOffline beats
2102
- * last_seen aging AND a lingering probe).
2103
- * - probe responded → online.
2104
- * - probe missed, lastSeenAt recent → reconnecting (a transient blip).
2105
- * - probe missed, lastSeenAt stale → offline.
2106
- */
2107
- declare function selfhostedLiveness(input: {
2108
- enrollment: SelfhostedEnrollment | null;
2109
- /** The ControlRpc Ping outcome: true iff a responder answered. */
2110
- probeResponded: boolean;
2111
- /** Override the clock (tests). */
2112
- now?: Date;
2113
- }): SelfhostedLivenessState;
2114
- interface SelfhostedNegotiationInput {
2115
- sessionId: string;
2116
- os?: SandboxOs;
2117
- leaseEpoch: number;
2118
- /** The M2 enrollment row for the machine (null → never enrolled → offline). */
2119
- enrollment: SelfhostedEnrollment | null;
2120
- /** A live liveness probe — typically `session.ping()`. When a session is
2121
- * provided this is called; otherwise pass `probeResponded` explicitly. */
2122
- session?: Pick<SelfhostedSession, "ping">;
2123
- /** Explicit probe outcome (when no session is given, e.g. a pure read). */
2124
- probeResponded?: boolean;
2125
- /** The deployment desktop/terminal/computer-use policy toggles (threaded
2126
- * through to the base negotiation). */
2127
- desktopEnabled?: boolean;
2128
- terminalEnabled?: boolean;
2129
- computerUseEnabled?: boolean;
2130
- /** Whether the calling principal acknowledged the un-redacted desktop. */
2131
- desktopAcknowledged?: boolean;
2132
- shared?: boolean;
2133
- sharedSessionIds?: string[];
2134
- /** Override the clock (tests). */
2135
- now?: Date;
2136
- }
2137
- /**
2138
- * Negotiate the full `SessionCapabilities` document for a selfhosted machine,
2139
- * with the online/offline/reconnecting/consent_required/display_unavailable cells
2140
- * correctly decided. Async because it issues the liveness probe.
2141
- */
2142
- declare function negotiateSelfhostedCapabilities(input: SelfhostedNegotiationInput): Promise<SessionCapabilities>;
2143
-
2144
- /** A pluggable exec handler — given an ExecRequest, return an ExecResponse (or
2145
- * throw to surface a synthesized error). Defaults to a trivial echo. */
2146
- type MockExecHandler = (req: ExecRequest) => ExecResponse | Promise<ExecResponse>;
2147
- interface MockAgentResponderOptions {
2148
- /** Whether a responder exists at all. When false EVERY request yields an
2149
- * AGENT_OFFLINE error (the "machine is offline" condition) — used to drive the
2150
- * agent_offline capability + the isProviderSandboxNotFoundError test. */
2151
- online?: boolean;
2152
- /** Whether the agent has acknowledged whole-machine / screen-control consent.
2153
- * When false, an op gated on consent yields CONSENT_REQUIRED. Defaults true. */
2154
- consented?: boolean;
2155
- /** Force the agent into a draining posture (every op → DRAINING). */
2156
- draining?: boolean;
2157
- /** Seed files (path → string|Uint8Array) into the virtual filesystem. */
2158
- files?: Record<string, string | Uint8Array>;
2159
- /** A custom exec handler; defaults to an echo of argv. */
2160
- exec?: MockExecHandler;
2161
- /** The hostname the mock reports (so PTY/exec `$HOSTNAME`-style asserts work). */
2162
- hostname?: string;
2163
- }
2164
- /**
2165
- * An in-process `ControlRpc` answering the agent op table against an in-memory
2166
- * virtual filesystem. Drive a `SelfhostedSession` with this to test exec /
2167
- * readFile / writeFile / list / stat round-trips without any NATS.
2168
- */
2169
- declare class MockAgentResponder implements ControlRpc {
2170
- private online;
2171
- private readonly consented;
2172
- private readonly draining;
2173
- private readonly files;
2174
- private readonly execHandler;
2175
- readonly hostname: string;
2176
- /** Every request seen, for assertion (subject + decoded ControlRequest). */
2177
- readonly requests: Array<{
2178
- subject: string;
2179
- req: ControlRequest;
2180
- }>;
2181
- constructor(opts?: MockAgentResponderOptions);
2182
- /** Flip the responder offline mid-test (a deliberate stop / blip). */
2183
- setOnline(online: boolean): void;
2184
- /** Read a file the session wrote (test assertion helper). */
2185
- fileText(path: string): string | undefined;
2186
- request(subject: string, req: ControlRequest, _opts: {
2187
- timeoutMs: number;
2188
- }): Promise<ControlResponse>;
2189
- }
2190
-
2191
- /** The per-session active-sandbox pointer the proxy re-reads on every op. Mirror
2192
- * of `@opengeni/db`'s `ActiveSandboxPointer` (structural, so the leaf does not
2193
- * import the DB package). `activeSandboxId === null` == "use the session's own
2194
- * group sandbox" (the default/backward-compat target). */
2195
- interface ActivePointer {
2196
- activeSandboxId: string | null;
2197
- activeEpoch: number;
2198
- /** The session's working directory — the path/cwd base for a selfhosted backend
2199
- * (threaded into the SelfhostedSession via the resolver). `null`/absent ⇒ the
2200
- * default workspace_root behavior. Optional so the default-pointer fallback
2201
- * (`{ activeSandboxId: null, activeEpoch: 0 }`) the readPointer wiring synthesizes
2202
- * when no row exists needs no extra field. Only the selfhosted branch reads it;
2203
- * the modal/default branches ignore it. */
2204
- workingDir?: string | null;
2205
- }
2206
- /**
2207
- * The structural slice of a backend session the routing proxy forwards to. It is
2208
- * a superset-by-optionality of every backend's surface (Modal's `SandboxSession`
2209
- * AND the `SelfhostedSession`): each method is optional because a heterogeneous
2210
- * target may or may not implement it, and the proxy reflects that at call-time.
2211
- */
2212
- interface RoutableBackendSession {
2213
- state?: unknown;
2214
- exec?(args: unknown): Promise<unknown>;
2215
- execCommand?(args: unknown): Promise<string>;
2216
- writeStdin?(args: unknown): Promise<string>;
2217
- cancelExecCommand?(opId: string): Promise<boolean>;
2218
- readFile?(args: unknown): Promise<string | Uint8Array>;
2219
- writeFile?(args: unknown): Promise<unknown>;
2220
- createEditor?(runAs?: string): unknown;
2221
- listDir?(args: unknown): Promise<unknown>;
2222
- pathExists?(path: string, runAs?: string): Promise<boolean>;
2223
- viewImage?(args: unknown): Promise<unknown>;
2224
- materializeEntry?(args: unknown): Promise<void>;
2225
- supportsPty?(): boolean;
2226
- resolveExposedPort?(port: number): Promise<ExposedPortEndpoint>;
2227
- serializeSessionState?(): Promise<unknown>;
2228
- desktopInput?(event: unknown): Promise<void>;
2229
- screenshot?(): Promise<{
2230
- png: Uint8Array;
2231
- width: number;
2232
- height: number;
2233
- nativeWidth: number;
2234
- nativeHeight: number;
2235
- }>;
2236
- }
2237
- /** The resolved active backend for an epoch: the live session + the sandbox id it
2238
- * belongs to (`null` == the group sandbox) so a fence-retry can detect a move. */
2239
- interface ResolvedActiveBackend {
2240
- session: RoutableBackendSession;
2241
- /** The sandbox id this backend serves (`null` == the session's group sandbox). */
2242
- sandboxId: string | null;
2243
- /** A label for diagnostics ("modal" | "selfhosted" | the sandbox name). */
2244
- kind: string;
2245
- /** Exact durable home-lease epoch for a persistable provider. Absent for
2246
- * connected-machine and other non-persistable route targets. */
2247
- leaseEpoch?: number;
2248
- /** Exact durable provider identity paired with `leaseEpoch`. This is internal
2249
- * routing metadata only; it is never projected to an agent or public API. */
2250
- providerInstanceId?: string;
2251
- /** Active-pointer epoch observed when this route was resolved. The proxy fills
2252
- * this internally even when a resolver omits it. */
2253
- activeEpoch?: number;
2254
- }
2255
- /** Durable identity assigned to a provider exec that yielded instead of
2256
- * exiting. The UUID is OpenGeni authority; the numeric provider session id is
2257
- * only a locator within the exact copied backend route. */
2258
- type RoutingRetainedProcess = {
2259
- id: string;
2260
- providerSessionId: number;
2261
- };
2262
- /** A yielded process was durably promoted, but the mutable authority checked
2263
- * after that commit was stale. The provider output must still be rejected;
2264
- * this result carries only the safe process identity needed to drain the exact
2265
- * original backend without replaying the mutation. */
2266
- type RoutingMutationSettlementResult = {
2267
- status: "retained_process_durable_output_rejected";
2268
- retainedProcess: RoutingRetainedProcess;
2269
- };
2270
- /** Exact copied route identity required to reconstruct an already-durable
2271
- * retained process after the request/runtime object that opened it is gone.
2272
- * The live session is deliberately not caller-supplied: adoption may bind only
2273
- * to this proxy's construction-time default backend, so it cannot resolve or
2274
- * follow the current active pointer. */
2275
- type RoutingRetainedProcessAdoption = {
2276
- process: RoutingRetainedProcess;
2277
- backend: {
2278
- sandboxId: string | null;
2279
- leaseEpoch?: number;
2280
- providerInstanceId?: string;
2281
- activeEpoch: number;
2282
- };
2283
- };
2284
- type RoutingRetainedProcessTerminalProof = {
2285
- outcome: "exited";
2286
- exitCode: number;
2287
- reason: "provider_exit_banner";
2288
- } | {
2289
- outcome: "lost";
2290
- exitCode: null;
2291
- reason: "provider_session_lost_banner";
2292
- };
2293
- interface RoutingSandboxSessionDeps {
2294
- /**
2295
- * The DEFAULT backend resolved at construction time (the same shape `resolve()`
2296
- * caches as `lastResolved`). This seeds `session.state` BEFORE the first op so a
2297
- * consumer that reads `session.state.manifest` at turn START — the @openai/agents
2298
- * SDK does, before any tool runs — sees the real default backend's state object
2299
- * (and writes to `session.state.manifest = …` land on it by reference), instead
2300
- * of an empty `{}` that crashes serializeManifestEnvironment /
2301
- * validateProvidedSessionManifestUpdate. The default-pointer case
2302
- * (`activeSandboxId === null`) resolves synchronously to this same backend, so
2303
- * seeding it here is byte-identical to what the first `resolve()` would produce.
2304
- */
2305
- defaultResolved?: ResolvedActiveBackend;
2306
- /** Re-read the per-session active pointer. Called on EVERY op (the per-call
2307
- * re-resolve that makes a mid-turn swap visible to the next tool call). */
2308
- readPointer(): Promise<ActivePointer>;
2309
- /**
2310
- * Resolve the active backend session for a pointer. The proxy memoizes the
2311
- * result by `activeEpoch`, so this is called at most once per epoch (per op the
2312
- * pointer is re-read, but the heavy resolve only re-runs when the epoch moved).
2313
- * For `pointer.activeSandboxId === null` this returns the default/group backend
2314
- * (typically the already-established turn box); for a non-null target it builds
2315
- * the target backend (a sibling Modal box or a selfhosted machine session).
2316
- */
2317
- resolveActiveBackend(pointer: ActivePointer): Promise<ResolvedActiveBackend>;
2318
- /** Max fence/stale retries within a single op before surfacing the error.
2319
- * Defaults to 3 — enough to absorb a couple of concurrent swaps, bounded so a
2320
- * swap-storm cannot loop forever. */
2321
- maxFenceRetries?: number;
2322
- /** Optional structured-log sink for swap/fence transitions (diagnostics). */
2323
- onTransition?: (event: RoutingTransitionEvent) => void;
2324
- /** Admit a filesystem-writing operation after resolving its exact route but
2325
- * before invoking the provider. A rejection fails closed and is deliberately
2326
- * outside provider fence-retry/error handling, so it can never replay the op
2327
- * against a rival backend. */
2328
- beforeMutation?: (input: {
2329
- op: string;
2330
- backend: ResolvedActiveBackend;
2331
- }) => Promise<unknown>;
2332
- /** Mark a mutation physically settled after its provider promise resolves OR
2333
- * rejects. A resolved result is also revalidated against the same durable
2334
- * route and attempt fence before its output is accepted. Settlement rejection
2335
- * leaves a fail-closed blocker and the mutation must never be replayed. */
2336
- afterMutation?: (input: {
2337
- op: string;
2338
- backend: ResolvedActiveBackend;
2339
- admission: unknown;
2340
- outcome: "resolved" | "rejected";
2341
- /** Provider result for a resolved call. Never present for rejection. */
2342
- result?: unknown;
2343
- /** Stable candidate generated before durable promotion is attempted. It is
2344
- * supplied only when the provider returned a positive yielded-session id. */
2345
- retainedProcess?: RoutingRetainedProcess;
2346
- }) => Promise<void | RoutingMutationSettlementResult>;
2347
- /** Admit one model/user-visible stdin mutation under the already-durable
2348
- * retained process authority. Control polling and helper execs never call it. */
2349
- beforeProcessMutation?: (input: {
2350
- op: string;
2351
- backend: ResolvedActiveBackend;
2352
- process: RoutingRetainedProcess;
2353
- }) => Promise<unknown>;
2354
- /** Physically settle one process-scoped stdin admission. */
2355
- afterProcessMutation?: (input: {
2356
- op: string;
2357
- backend: ResolvedActiveBackend;
2358
- process: RoutingRetainedProcess;
2359
- admission: unknown;
2360
- outcome: "resolved" | "rejected";
2361
- result?: unknown;
2362
- }) => Promise<void>;
2363
- /** Close the durable parent admission/process holder only after exact exit or
2364
- * matching provider-loss proof. Tracking is removed only after this resolves. */
2365
- settleProcess?: (input: {
2366
- backend: ResolvedActiveBackend;
2367
- process: RoutingRetainedProcess;
2368
- proof: RoutingRetainedProcessTerminalProof;
2369
- }) => Promise<void>;
2370
- /** Called only when an operation against the default/home backend throws a
2371
- * non-fence error. Wiring may classify definitive provider disappearance and
2372
- * atomically retire the exact lease epoch. Returning a result makes dispatch
2373
- * throw a typed recovery-required error WITHOUT replaying the operation (the
2374
- * original mutation may have reached the provider). */
2375
- onDefaultBackendError?: (input: {
2376
- op: string;
2377
- error: unknown;
2378
- kind: string;
2379
- backend: ResolvedActiveBackend;
2380
- }) => Promise<DefaultBackendLossResult | null>;
2381
- }
2382
- type DefaultBackendLossResult = {
2383
- leaseEpoch: number;
2384
- recovery: "pending" | "degraded" | "unrecoverable" | "superseded";
2385
- };
2386
- interface RoutingTransitionEvent {
2387
- type: "resolved" | "fenced-retry" | "epoch-changed";
2388
- fromEpoch: number;
2389
- toEpoch: number;
2390
- sandboxId: string | null;
2391
- kind: string;
2392
- }
2393
- /** Thrown when the active backend does not implement the requested op (a
2394
- * heterogeneous target whose surface lacks the method the caller reached for). */
2395
- declare class RoutingUnsupportedError extends Error {
2396
- readonly name = "RoutingUnsupportedError";
2397
- constructor(op: string, kind: string);
2398
- }
2399
- declare class RoutingBackendRecoveryRequiredError extends Error {
2400
- readonly op: string;
2401
- readonly leaseEpoch: number;
2402
- readonly recovery: DefaultBackendLossResult["recovery"];
2403
- readonly name = "RoutingBackendRecoveryRequiredError";
2404
- readonly retryable: boolean;
2405
- constructor(op: string, leaseEpoch: number, recovery: DefaultBackendLossResult["recovery"]);
2406
- }
2407
- /** A mutating provider call was admitted but could not be settled against the
2408
- * exact route that admitted it. The provider may have applied the effect, so the
2409
- * proxy rejects the output and explicitly forbids an automatic replay. */
2410
- declare class RoutingMutationOutcomeUnknownError extends Error {
2411
- readonly op: string;
2412
- readonly name = "RoutingMutationOutcomeUnknownError";
2413
- readonly retryable = false;
2414
- readonly retainedProcess: RoutingRetainedProcess | null;
2415
- constructor(op: string, message: string, options?: {
2416
- cause?: unknown;
2417
- retainedProcess?: RoutingRetainedProcess;
2418
- });
2419
- }
2420
- /** An explicit process-aware operation named a numeric provider locator that
2421
- * is not retained by this routing session. Callers must never fall back to the
2422
- * current active pointer for such an operation. */
2423
- declare class RoutingRetainedProcessNotFoundError extends Error {
2424
- readonly providerSessionId: number;
2425
- readonly name = "RoutingRetainedProcessNotFoundError";
2426
- readonly retryable = false;
2427
- constructor(providerSessionId: number);
2428
- }
2429
- /**
2430
- * ONE stable session-shaped object the SDK binds to. Every method re-reads the
2431
- * pointer, resolves the active backend (cached by epoch), and dispatches. A
2432
- * stale-epoch fence (the pointer moved mid-op) re-resolves and retries.
2433
- *
2434
- * The proxy implements ALL of the consumed surface so the SDK (which binds method
2435
- * presence ONCE) always sees `exec`/`readFile`/`resolveExposedPort`/… present. If
2436
- * the CURRENTLY-active backend lacks a method, the proxy applies the natural
2437
- * fallback (`exec`→`execCommand`) or throws `RoutingUnsupportedError` — degrade is
2438
- * a value, not a crash.
2439
- *
2440
- * `state` is a STABLE getter so a consumer reading `session.state` (channel-a's
2441
- * `readInstanceId`, the docker-network decoration) gets a coherent snapshot of the
2442
- * currently-active backend without a method call.
2443
- */
2444
- declare class RoutingSandboxSession implements RoutableBackendSession {
2445
- private readonly deps;
2446
- private readonly maxFenceRetries;
2447
- private cachedEpoch;
2448
- private cachedSandboxId;
2449
- private cached;
2450
- private lastResolved;
2451
- /** Provider session ids are scoped to one backend instance. Each entry copies
2452
- * that exact resolved route so pointer movement can never redirect stdin,
2453
- * polling, or process-group helpers to another box. */
2454
- private readonly retainedProcesses;
2455
- desktopInput?: (event: unknown) => Promise<void>;
2456
- screenshot?: () => Promise<{
2457
- png: Uint8Array;
2458
- width: number;
2459
- height: number;
2460
- nativeWidth: number;
2461
- nativeHeight: number;
2462
- }>;
2463
- constructor(deps: RoutingSandboxSessionDeps);
2464
- /**
2465
- * A method-free read of the active backend's `state` (best-effort: the last
2466
- * resolved backend, falling back to the default backend resolved at construction
2467
- * so this is non-empty BEFORE the first op). Consumers that read `session.state`
2468
- * (instanceId/decoration) get the active backend's state.
2469
- *
2470
- * CRITICAL: this returns the underlying backend's `state` OBJECT BY REFERENCE
2471
- * (never a fresh `{}` when a backend exists). The @openai/agents SDK both READS
2472
- * `session.state.manifest` and WRITES `session.state.manifest = nextManifest`
2473
- * (providedSessionManifest); returning the live object by reference means those
2474
- * property writes land on the real backend state and persist. Only when NO
2475
- * backend has been resolved yet (no default seeded, no op dispatched) do we
2476
- * return an empty object — and that path no longer occurs in the turn wiring,
2477
- * which always seeds `defaultResolved`.
2478
- */
2479
- get state(): unknown;
2480
- /**
2481
- * Re-read the pointer and resolve the active backend, using the per-epoch cache.
2482
- * The cache is keyed by `activeEpoch`: if the epoch is unchanged we return the
2483
- * cached backend; if it moved (a swap) we re-resolve and update the cache. This
2484
- * is THE per-call re-read that makes a mid-turn swap land on the next op.
2485
- */
2486
- private resolve;
2487
- private invalidate;
2488
- private registerRetainedProcess;
2489
- private retainedProcess;
2490
- /** Reconstruct one process that was already promoted durably by an earlier
2491
- * request. Adoption never reads the active pointer and never repeats parent
2492
- * promotion. It can bind only to the exact construction-time default backend;
2493
- * callers must reject non-default routes until they can independently rebuild
2494
- * that provider session from its copied durable identity. */
2495
- adoptRetainedProcess(input: RoutingRetainedProcessAdoption): void;
2496
- private ensureParentPromotion;
2497
- private confirmDurableRejectedPromotion;
2498
- private settleRetainedProcess;
2499
- private flushPendingProcessMutation;
2500
- private dispatchProcessMutation;
2501
- private dispatchProcessControl;
2502
- /**
2503
- * Dispatch an op to the currently-active backend, retrying on a stale-epoch
2504
- * fence. The sequence per attempt:
2505
- * 1. re-read the pointer + resolve the active backend (cached by epoch),
2506
- * 2. run `fn(activeSession)`,
2507
- * 3. on a FENCE error (the pointer moved under us / the backend rejected a
2508
- * stale epoch), INVALIDATE the cache and retry against the re-resolved
2509
- * active sandbox — up to `maxFenceRetries`.
2510
- * A non-fence error propagates immediately (it is a real op failure, not a swap
2511
- * race).
2512
- */
2513
- private dispatch;
2514
- /**
2515
- * The failure-visibility boundary for the `exec_command` SDK capability tool — the
2516
- * dominant fault surface, and the one whose thrown `SelfhostedControlError` reaches
2517
- * the model wrapped by the SDK's generic tool-error function as
2518
- * "…Please try again. Error: …" — actively wrong for a machine that is offline, a
2519
- * consent that is not granted, or an oversized reply. Since the SDK closure-captures
2520
- * its `errorFunction` (there is no seam to attach one to its internally-built tools),
2521
- * we render the fault into the doctrine's four fields HERE and return it as the
2522
- * tool's string result — legible, in-band, and free of the misleading wrapper.
2523
- * (`apply_patch` already surfaces `error.message` via the SDK's own catch; the
2524
- * skills / `view_image` tools consume their session methods internally, so they are
2525
- * rendered elsewhere or left to their own SDK renderers — not this string boundary.)
2526
- *
2527
- * A FENCE error is re-thrown, NEVER rendered: `dispatch` already retries it against
2528
- * a re-resolved backend, and a fence that escapes retries is a routing condition the
2529
- * turn handles, not a model-facing fault. Any non-selfhosted error (a Modal fault, a
2530
- * `RoutingUnsupportedError`) is re-thrown unchanged.
2531
- */
2532
- private renderSelfhostedFaultOrThrow;
2533
- exec(args: unknown): Promise<unknown>;
2534
- execCommand(args: unknown): Promise<string>;
2535
- writeStdin(args: unknown): Promise<string>;
2536
- /** Whether a positive provider session locator is still pinned to the exact
2537
- * backend that yielded it. This synchronous probe is used only to select the
2538
- * process-aware routing surface; it is not itself durable authority. */
2539
- hasRetainedProcess(providerSessionId: number): boolean;
2540
- /** Return only OpenGeni's durable UUID + provider locator. Backend/session
2541
- * objects remain private so a caller cannot forge route authority from this
2542
- * diagnostic handoff. */
2543
- retainedProcessIdentity(providerSessionId: number): RoutingRetainedProcess | null;
2544
- /** Model/user-visible stdin is a distinct workspace mutation admission under
2545
- * the durable retained-process holder. It never re-reads the active pointer. */
2546
- writeStdinForProcessMutation(args: unknown): Promise<string>;
2547
- /** Cancellation and drain polling are control operations. They stay pinned to
2548
- * the process backend and may prove terminal state, but never advance the
2549
- * workspace generation. */
2550
- writeStdinForProcessControl(args: unknown): Promise<string>;
2551
- /** Run a PID/PGID marker or signal helper on the retained process's exact
2552
- * backend. The helper is control-plane work and therefore has no workspace
2553
- * mutation admission of its own. */
2554
- execCommandForProcessControl(providerSessionId: number, args: unknown): Promise<string>;
2555
- cancelExecCommand(opId: string): Promise<boolean>;
2556
- readFile(args: unknown): Promise<string | Uint8Array>;
2557
- writeFile(args: unknown): Promise<unknown>;
2558
- listDir(args: unknown): Promise<unknown>;
2559
- pathExists(path: string, runAs?: string): Promise<boolean>;
2560
- viewImage(args: unknown): Promise<unknown>;
2561
- materializeEntry(args: unknown): Promise<void>;
2562
- /** PTY support reflects the LAST-resolved backend (a synchronous probe; the SDK
2563
- * reads it to decide if the terminal is interactive). It cannot re-read the
2564
- * pointer (synchronous), so it answers from the last resolve — coherent with
2565
- * the resolve the surrounding op already performed. Defaults false before the
2566
- * first resolve. */
2567
- supportsPty(): boolean;
2568
- /** createEditor is a synchronous factory in the SDK surface. The SDK's filesystem
2569
- * capability calls it ONCE at tool-BIND time — `FilesystemCapability.tools()`,
2570
- * every turn, before any tool runs — and throws "Filesystem sandbox sessions must
2571
- * provide createEditor()" if it returns falsy. When a backend is already resolved
2572
- * (eager/selfhosted routing) we bind to its editor directly, byte-for-byte as
2573
- * before. But under LAZY provisioning the backend is not established yet
2574
- * (defaultResolved is the synthetic unprovisioned session with no editor), so a
2575
- * direct delegate returns undefined and every lazy turn would die at bind. Return a
2576
- * LAZY EDITOR PROXY instead: a non-null editor whose async ops resolve the active
2577
- * backend (establishing the box on first use, via `dispatch`) and delegate to its
2578
- * real editor — mirroring how this proxy defers exec/readFile. Even when an
2579
- * eager editor already exists, returning it directly would bypass per-edit
2580
- * route resolution and mutation-generation admission, so every editor is
2581
- * represented by this dispatching proxy. */
2582
- createEditor(runAs?: string): unknown;
2583
- resolveExposedPort(port: number): Promise<ExposedPortEndpoint>;
2584
- /** Serialize the active backend's session state. Used by the resume-by-id seam
2585
- * to fold the live box onto the lease. Dispatches to the active backend. */
2586
- serializeSessionState(): Promise<unknown>;
2587
- /** Force a resolve (priming the proxy before the first op so `state`/`supportsPty`
2588
- * read a real backend). Optional — every op resolves lazily anyway. */
2589
- prime(): Promise<ResolvedActiveBackend>;
2590
- }
2591
-
2592
- /** The structural slice of a first-class sandbox the resolver reads (mirror of
2593
- * `@opengeni/db`'s `SandboxRecord`; structural so the leaf does not import DB). */
2594
- interface RoutableSandbox {
2595
- id: string;
2596
- kind: "modal" | "selfhosted" | string;
2597
- name: string;
2598
- /** For a selfhosted sandbox this is its enrollment id (== the agent id the
2599
- * control-plane subject `agent.<ws>.<id>.rpc` addresses). Null for modal. */
2600
- enrollmentId: string | null;
2601
- }
2602
- interface ActiveBackendResolverDeps {
2603
- /** The workspace the session belongs to (the control-plane subject scope). */
2604
- workspaceId: string;
2605
- /** The session's own group sandbox session — the DEFAULT target
2606
- * (`activeSandboxId === null`). Already established (lease-owned); the proxy
2607
- * never re-establishes it. */
2608
- defaultBackend: RoutableBackendSession;
2609
- /** A label for the default backend (its backend id: "modal"/"selfhosted"/…). */
2610
- defaultKind: string;
2611
- /**
2612
- * Re-resolve the session's HOME backend after the route cache is fenced.
2613
- *
2614
- * The normal path returns `defaultBackend` (the turn's already-established
2615
- * handle), but an API-side same-target repair can rematerialize that lease
2616
- * while a worker turn is still alive. Such a repair advances the route epoch
2617
- * without changing the null pointer; callers that can resume the current
2618
- * lease-owned identity may provide this callback so the next epoch does not
2619
- * keep dispatching to the dead pre-repair object. This callback must never
2620
- * create a provider or replay an operation.
2621
- */
2622
- resolveDefaultBackend?: (pointer: ActivePointer) => Promise<ResolvedActiveBackend>;
2623
- /** Look up a first-class sandbox by id (the swap target). Returns null when the
2624
- * id is unknown or not in this workspace (the caller 409s the swap). */
2625
- getSandbox(sandboxId: string): Promise<RoutableSandbox | null>;
2626
- /** Build a live `ControlRpc` for the selfhosted control plane (the request-
2627
- * scoped NATS connection). Returns a ControlRpc whose offline/timeout maps to
2628
- * agent_offline/agent_reconnecting (never a NotFound). */
2629
- controlRpcFactory(): ControlRpc;
2630
- /** The relay-URL shape config for selfhosted stream endpoints. */
2631
- relay: SelfhostedRelayConfig;
2632
- /** Establish (resume-by-id) a NON-DEFAULT modal target's box session for a swap.
2633
- * Supplied by the API/worker (a closure over the sibling sandbox's lease). When
2634
- * absent, a modal swap target surfaces as unsupported (the caller validated
2635
- * liveness, so this is the "modal swap not wired in this context" guard). */
2636
- establishModalTarget?: (sandbox: RoutableSandbox) => Promise<RoutableBackendSession>;
2637
- /** The selfhosted CONTROL-op timeout (ping/fs/desktop/pty) for a swap/pin target.
2638
- * Absent ⇒ the session's 30s default. */
2639
- selfhostedTimeoutMs?: number;
2640
- /** The selfhosted EXEC process deadline for a swap/pin target, distinct from the
2641
- * control timeout (a swapped-to machine runs real commands too). Absent ⇒ falls
2642
- * back to the control timeout, as in the session leaf. */
2643
- selfhostedExecTimeoutMs?: number;
2644
- /** The per-op observer threaded into a selfhosted swap/pin target (out-of-band
2645
- * telemetry — metrics + machine.* events). Absent ⇒ no-op. */
2646
- selfhostedOnOp?: SelfhostedOpObserver;
2647
- /**
2648
- * The run's declared sandbox environment — the SAME `Record<string,string>` the
2649
- * worker turn threads into the agent's TARGET manifest (and into the group box at
2650
- * create). Threaded into a selfhosted swap target's session so its
2651
- * `state.manifest.environment` EQUALS the turn's, making the SDK's per-turn
2652
- * provided-session manifest-env delta empty (validateNoEnvironmentDelta).
2653
- * WITHOUT this a pin-to-vm turn throws "Live sandbox sessions cannot change
2654
- * manifest environment variables". Omitted → `{}` (the test/negotiation path).
2655
- */
2656
- environment?: Record<string, string>;
2657
- /**
2658
- * A pre-established selfhosted session to PIN for the STEADY-STATE machine
2659
- * pointer (the worker turn's machine-primary path, Stage D). When the pointer
2660
- * targets THIS sandbox at THIS epoch, the resolver returns this SAME instance
2661
- * instead of building a fresh `SelfhostedSession`. This is the instance-identity
2662
- * pin: the SDK reads/writes `state.manifest` at turn START via the proxy's `state`
2663
- * getter (which reads the default/last-resolved backend's state) and then reads it
2664
- * per op via this resolver — those MUST land on ONE SelfhostedSession/manifest, or
2665
- * a turn-start manifest write is invisible to the per-op reads (two-instance
2666
- * divergence). A swap AWAY (a different sandbox id, or the same id at a moved epoch)
2667
- * falls through to a fresh build under the new epoch. Omitted for the API/live-swap
2668
- * path (which always builds fresh — it has no pre-established turn session).
2669
- */
2670
- pinnedSelfhosted?: {
2671
- sandboxId: string;
2672
- epoch: number;
2673
- session: RoutableBackendSession;
2674
- };
2675
- /**
2676
- * Whether `defaultBackend` IS the session's home box — i.e. whether the null pointer
2677
- * (== home) may resolve to it. Defaults to TRUE (omitted): a genuine machine-HOME turn
2678
- * (home IS the machine) and a Modal-home turn established on its group box both pass/omit
2679
- * true, so null resolves to `defaultBackend` as before. Set explicitly FALSE only for a
2680
- * machine-primary turn of a Modal-HOME session (pinned to a machine, group box never
2681
- * established this turn): the null pointer then has nothing to resolve to THIS turn, so
2682
- * rather than silently keep serving the pinned machine as if the detach never happened,
2683
- * the null branch throws a typed `home_unavailable_this_turn` error — the detach's pointer
2684
- * commit stands and takes effect on the NEXT turn, which starts null and establishes the
2685
- * home box normally. Lazily establishing the home box mid-turn on such a clear is a
2686
- * deferred follow-up (issue #341); until then this makes the gap fail typed-and-specific,
2687
- * never silent.
2688
- */
2689
- defaultIsHome?: boolean;
2690
- }
2691
- /** Why a persisted pointer / swap target cannot be turned into a live backend. A
2692
- * typed discriminant so callers (turn-start reconcile, the swap tool, future
2693
- * reports) can distinguish a stale pointer from an unaddressable enrollment from a
2694
- * backend the turn context simply cannot establish — instead of string-matching
2695
- * one opaque message (issue #341: "typed diagnostics that distinguish stale
2696
- * pointer, offline enrollment, unsupported backend context, transient failure").
2697
- * - `stale_pointer` — the pointed-at sandbox row is gone (deleted/orphaned).
2698
- * - `offline_enrollment` — a selfhosted target carries no enrollment/agent id to address.
2699
- * - `unsupported_backend_context` — a target no turn routing context can establish
2700
- * (a non-group Modal sibling; an unknown kind).
2701
- * - `transient_establishment` — a momentary establish failure worth a retry (reserved;
2702
- * control-plane timeouts surface as their own typed error).
2703
- * - `home_unavailable_this_turn` — the pointer was cleared to the session default (home)
2704
- * mid-turn, but this turn started pinned to a machine and
2705
- * never established the home box, so null has nothing to
2706
- * resolve to THIS turn (the detach takes effect next turn). */
2707
- type BackendUnresolvableCode = "stale_pointer" | "offline_enrollment" | "unsupported_backend_context" | "transient_establishment" | "home_unavailable_this_turn";
2708
- /** Thrown when a swap target cannot be resolved (unknown sandbox, or a modal
2709
- * target with no establisher in this context). The caller maps it to a 409.
2710
- * Carries a typed {@link BackendUnresolvableCode} in addition to the message. */
2711
- declare class ActiveBackendUnresolvableError extends Error {
2712
- readonly name = "ActiveBackendUnresolvableError";
2713
- readonly code: BackendUnresolvableCode;
2714
- constructor(code: BackendUnresolvableCode, message: string);
2715
- }
2716
- /** The outcome of {@link swapTargetEstablishability}. */
2717
- type SwapTargetEstablishability = {
2718
- ok: true;
2719
- } | {
2720
- ok: false;
2721
- code: BackendUnresolvableCode;
2722
- reason: string;
2723
- };
2724
- /**
2725
- * THE SINGLE SOURCE OF TRUTH for "can a turn's routing context ESTABLISH this swap
2726
- * target?" — shared by swap/seed ADMISSION (`fleet.resolveTarget`, before the CAS
2727
- * pointer commit) and turn-time ESTABLISHMENT (this resolver + `wrapTurnBoxWithRouting`).
2728
- * Admission MUST reject any target this predicate calls unestablishable, so the
2729
- * pointer is never committed to a backend no turn can resume (issue #341 Shape 1:
2730
- * a Modal-sibling swap was admitted + epoch-bumped, then every following op
2731
- * stranded because no context wires an establisher for it).
2732
- *
2733
- * - the session's OWN group box (`isSessionGroup`) is the null/home target and is
2734
- * always establishable (it IS the default backend);
2735
- * - a `selfhosted` machine target is establishable (`buildSelfhostedBackendSession`);
2736
- * admission's separate liveness probe — not this predicate — gates online-ness;
2737
- * - a NON-group `modal` target is NOT establishable: no turn context wires
2738
- * `establishModalTarget` (cross-group Modal resume-by-id is a future, billing-
2739
- * touching feature — sandbox-routing.ts). Wiring that establisher is the ONE
2740
- * toggle that flips this branch; keep admission and the resolver in lockstep.
2741
- */
2742
- declare function swapTargetEstablishability(target: {
2743
- kind: string;
2744
- isSessionGroup: boolean;
2745
- }): SwapTargetEstablishability;
2746
- /**
2747
- * Build the `resolveActiveBackend(pointer)` closure for a `RoutingSandboxSession`.
2748
- * The returned closure is re-invoked by the proxy whenever the active_epoch moves
2749
- * (the per-epoch cache miss), so it must be cheap-and-correct for the steady-state
2750
- * (default pointer → the already-established group box) and build a fresh backend
2751
- * for a swap target.
2752
- *
2753
- * - `activeSandboxId === null` → the default group backend (no re-establish).
2754
- * - a selfhosted target → a `SelfhostedSession` bound to the enrollment agentId,
2755
- * fenced under `pointer.activeEpoch` (echoed on every ControlRequest so the
2756
- * agent can reject a stale op with ERROR_CODE_FENCED — the swap-race fence).
2757
- * - a modal target → `establishModalTarget` (the resume-by-id closure), else
2758
- * unresolvable.
2759
- */
2760
- declare function makeActiveBackendResolver(deps: ActiveBackendResolverDeps): (pointer: ActivePointer) => Promise<ResolvedActiveBackend>;
2761
-
2762
- /**
2763
- * Construct the raw provider SandboxClient for the configured backend. Registry-
2764
- * driven (the old flat if/else is gone): the backend's ProviderRegistration owns
2765
- * validateCredentials + build, with per-provider units/field-names. Returns
2766
- * undefined for "none".
2767
- *
2768
- * The desktop stream port (6080) is merged into exposedPorts for every desktop-
2769
- * capable (backend, os) when desktop is enabled AND the provider cannot expose
2770
- * ports on demand (modal/runloop/e2b pre-declare; blaxel resolves on demand).
2771
- * Existing modal/docker/local construction is behavior-preserved.
2772
- */
2773
- declare function createSandboxClient(settings: Settings, environment?: Record<string, string>): unknown;
2774
- /**
2775
- * Construct the raw provider SandboxClient for an EXPLICIT backend, independent
2776
- * of settings.sandboxBackend. This is the resume-by-id builder the per-turn
2777
- * resume path (and the API-direct control plane) call: a lease's box was created
2778
- * on a specific backend (the envelope's backendId / the lease's
2779
- * resume_backend_id), and the client that reattaches to it must be built for
2780
- * THAT backend, not the process's currently-configured default. When the backend
2781
- * equals settings.sandboxBackend this is identical to createSandboxClient
2782
- * (behavior-preserved). Returns undefined for "none".
2783
- */
2784
- declare function createSandboxClientForBackend(backend: SandboxBackend, settings: Settings, environment?: Record<string, string>): unknown;
2785
- /**
2786
- * Extract the sandbox recovery entry from a run state as a plain JSON record,
2787
- * for storage decoupled from the RunState blob (issue #35). Encapsulates the
2788
- * underscore-internal `_sandbox` read in exactly one place.
2789
- */
2790
- declare function sandboxStateEntryFromRunState(state: unknown): Record<string, unknown> | null;
2791
- /**
2792
- * Items-mode counterpart of restoredSandboxSessionState: rebuild the live
2793
- * sandbox session state from a stored entry (as produced by
2794
- * sandboxStateEntryFromRunState) instead of from a RunState blob.
2795
- */
2796
- declare function restoredSandboxSessionStateFromEntry(entry: Record<string, unknown>, client: unknown): Promise<SandboxSessionState | undefined>;
2797
- /**
2798
- * Read the persisted /workspace snapshot archive off a lease envelope's
2799
- * `sessionState` (sandbox-file-persistence). The reaper (persistDrainSnapshot)
2800
- * folds the base64 archive — a Modal native snapshot-ref or a tar archive, the
2801
- * exact bytes `session.persistWorkspace()` returned — at
2802
- * `sessionState.workspaceArchive`. Cold-restore decodes it and replays it via
2803
- * `session.hydrateWorkspace(archive)` on the freshly-created box so /workspace is
2804
- * restored. Returns undefined when the envelope carries no archive (a box that
2805
- * was never drain-persisted, or a non-persistence config that stored none).
2806
- *
2807
- * It is deliberately read SEPARATELY from deserializeSandboxSessionStateEnvelope:
2808
- * the archive does NOT ride serializeSessionState (it originates at reaper time),
2809
- * and the SDK's deserializeSessionState must NOT receive it (it is an opaque
2810
- * runtime-level field, not provider state).
2811
- */
2812
- declare function readWorkspaceArchiveFromEnvelopeSessionState(sessionState: unknown): Uint8Array | undefined;
2813
- /** Decode the Modal snapshot id out of a persisted base64 archive ref, or
2814
- * undefined when the archive is a tar payload (no provider snapshot to GC) or
2815
- * is unparseable. Used only for keep-latest-per-lease snapshot GC. */
2816
- declare function decodeModalSnapshotId(archive: Uint8Array): string | undefined;
2817
- /**
2818
- * Best-effort GC of a SUPERSEDED Modal filesystem/directory snapshot
2819
- * (sandbox-file-persistence). restoreSnapshotFilesystem terminates the previous
2820
- * SANDBOX but never deletes the prior SNAPSHOT image, so snapshots accumulate
2821
- * unbounded across warm/cold cycles. The reaper keeps only the latest per lease:
2822
- * when it writes a NEW archive it passes the PRIOR archive here to delete its
2823
- * image via the live session's Modal client (`session.modal.images.delete(id)` —
2824
- * the same API the SDK uses for directory images). Never throws (GC is a
2825
- * best-effort backstop; a leaked snapshot is a cost issue, not a correctness one).
2826
- * A tar archive (no snapshot id) is a no-op. Returns the deleted snapshot id (or
2827
- * undefined when nothing was deleted) for observability.
2828
- */
2829
- declare function deletePriorPersistedSnapshot(session: unknown, priorArchiveBase64: string | null | undefined): Promise<string | undefined>;
2830
- declare function deserializeSandboxSessionStateEnvelope(client: SandboxClient, envelope: unknown): Promise<SandboxSessionState | undefined>;
2831
- /** A live, externally-owned sandbox session re-established from the group lease
2832
- * envelope. The caller injects `{client, session, sessionState}` NON-OWNED into
2833
- * the run (or drives session.exec/readFile/resolveExposedPort directly) and
2834
- * drops the handle when done — the lease, not this handle, owns the box. */
2835
- type EstablishedSandboxSession = {
2836
- client: unknown;
2837
- session: unknown;
2838
- sessionState: unknown;
2839
- instanceId: string;
2840
- backendId: string;
2841
- /** How this establish reached a live box: warm reattach by id ("resumed"),
2842
- * fresh create ("created"), or fresh create + archive hydration
2843
- * ("restored"). Callers use it to emit the durable sandbox.box.* lifecycle
2844
- * events — box transitions were previously unrecorded, which made both
2845
- * 2026-07-06 incidents near-unattributable. Optional so external/legacy
2846
- * constructors of this shape stay valid. */
2847
- origin?: "resumed" | "created" | "restored";
2848
- /** Set when a create-authorized reattach found the envelope's box GONE
2849
- * (provider NotFound) and fell through to cold-restore. */
2850
- lostInstanceId?: string;
2851
- /** Exact durable archive revision verified byte-for-byte after hydration. */
2852
- restoredArchive?: WorkspaceArchiveDescriptor;
2853
- };
2854
- declare class SandboxExecReadinessError extends Error {
2855
- readonly backend: string;
2856
- readonly code: "exec_probe_unavailable" | "exec_probe_timeout" | "exec_probe_failed";
2857
- readonly timeoutMs: number;
2858
- readonly exitCode: number | null;
2859
- readonly name = "SandboxExecReadinessError";
2860
- constructor(backend: string, code: "exec_probe_unavailable" | "exec_probe_timeout" | "exec_probe_failed", timeoutMs: number, exitCode?: number | null);
2861
- }
2862
- /** A provider handle is not workspace readiness. Modal can return a handle
2863
- * before its command router accepts exec, so every create path must pass this
2864
- * bounded probe before atomically publishing the lease warm/ready. */
2865
- declare function verifySandboxExecReadiness(established: EstablishedSandboxSession, timeoutMs?: number): Promise<void>;
2866
- type SandboxCreatedCallback = (established: EstablishedSandboxSession) => Promise<void>;
2867
- /**
2868
- * Establish from one recovery envelope under an explicit creation policy. The
2869
- * envelope is the lease's box-identity descriptor (the same per-turn `_sandbox`
2870
- * envelope upserted by the turn activity).
2871
- *
2872
- * - `opts.backendOverride ?? envelope.backendId ?? settings.sandboxBackend`
2873
- * selects the backend; the client is built for THAT backend (resume-by-id is
2874
- * fenced to the original provider).
2875
- * - warm reattach: deserialize the envelope sessionState → client.resume(state)
2876
- * (no lock; R4-safe). `resume-only` propagates provider NotFound.
2877
- * - `create-or-restore`: the elected owner may replace a missing warm instance,
2878
- * or create directly from a cold/null envelope.
2879
- */
2880
- declare function establishSandboxSessionFromEnvelope(settings: Settings, envelope: Record<string, unknown> | null, opts: {
2881
- sessionId: string;
2882
- recovery: "create-or-restore" | "resume-only";
2883
- backendOverride?: SandboxBackend;
2884
- environment?: Record<string, string>;
2885
- onSandboxCreated?: SandboxCreatedCallback;
2886
- /** Called after archive hydration but immediately before the exact workspace
2887
- * fingerprint probe. Lease-aware callers persist `verifying` here so a box
2888
- * is never observable as ready while verification is in flight. */
2889
- onWorkspaceRestoreVerifying?: (descriptor: WorkspaceArchiveDescriptor) => Promise<void>;
2890
- metrics?: RuntimeMetricsHooks;
2891
- }): Promise<EstablishedSandboxSession>;
2892
- /**
2893
- * Fold a freshly-established (or resumed) sandbox session into the persistable
2894
- * `resume_state` envelope the lease stores — the SAME `{ backendId, sessionState }`
2895
- * shape `establishSandboxSessionFromEnvelope` consumes to RESUME BY ID. The
2896
- * API-direct control plane (viewer attach / Channel-A) MUST persist this onto the
2897
- * lease at warm-commit time, or a later op (which reads the lease's resume_state)
2898
- * has nothing to resume from and COLD-CREATES A RIVAL BOX — the box-churn the
2899
- * prove-it surfaced (fs.write then fs.read 404'd on a different box; N Channel-A
2900
- * ops leaked N boxes). Returns null when the client cannot serialize (the caller
2901
- * stores null and the box rides the provider idle-timeout — no rival spawn, just
2902
- * no warm-reattach).
2903
- */
2904
- declare function serializeEstablishedSandboxEnvelope(established: EstablishedSandboxSession): Promise<Record<string, unknown> | null>;
2905
- /**
2906
- * Whether a replacement envelope contains a provider-resumable identity. An
2907
- * archive-only envelope is intentionally valid recovery metadata, but it is
2908
- * never proof that the newly-created provider box can be resumed or attached.
2909
- */
2910
- declare function hasPersistableSandboxProviderIdentity(envelope: Record<string, unknown> | null | undefined): envelope is Record<string, unknown>;
2911
- /** A replacement provider may not be published warm without a resumable
2912
- * identity. Archive-only state is retained only when the caller rolls back to
2913
- * cold and needs the verified workspace bytes for the next attempt. */
2914
- declare class SandboxReplacementProviderStateError extends Error {
2915
- readonly backend: string;
2916
- readonly code: "replacement_provider_state_unpersistable";
2917
- constructor(backend: string);
2918
- }
2919
- declare function requirePersistableReplacementSandboxEnvelope(envelope: Record<string, unknown> | null, backend: string): Record<string, unknown>;
2920
- /**
2921
- * Build the only resume envelope that may be published for a newly-created
2922
- * replacement sandbox. Historical state contributes durable archive pointers
2923
- * only; it can never substitute for serialization of the replacement's provider
2924
- * identity. If replacement serialization is unavailable or fails, publication
2925
- * keeps an archive-only envelope (when one exists) or null. The new instance id
2926
- * remains separately fenced on the lease, while later attach/resume fails closed
2927
- * rather than targeting the dead provider that initiated rematerialization.
2928
- */
2929
- declare function serializeReplacementSandboxEnvelope(established: EstablishedSandboxSession, archiveSource: Record<string, unknown> | null): Promise<Record<string, unknown> | null>;
2930
-
2931
- export { type NatsRequestConnection as $, type ActiveBackendResolverDeps as A, type BackendUnresolvableCode as B, ChannelAConflictError as C, DEFAULT_DESKTOP_GEOMETRY as D, type EnsureDisplayStackOptions as E, type ExposeStreamPortInput as F, type ExposeStreamPortResult as G, type ExposedPortEndpoint as H, FAULT_FIELD_WHAT_HAPPENED as I, FAULT_FIELD_WHAT_PRESERVED as J, FAULT_FIELD_WHAT_TO_TRY as K, FAULT_FIELD_WHICH_LAYER as L, type LiveModalSandboxLeaseAttribution as M, type MaterializeRunCredentialsOptions as N, type MintStreamTokenInput as O, MockAgentResponder as P, type MockAgentResponderOptions as Q, type RunCredentialSessionReady as R, type MockExecHandler as S, type ModalModuleLoader as T, type ModalOrphanSweepResult as U, type ModalOrphanSweepTermination as V, type ModalSandboxAttribution as W, NEVER_SENT_DETAIL_KEY as X, NatsControlRpc as Y, type NatsOpStreamConnection as Z, NatsOpStreamTransport as _, type RuntimeMetricsHooks as a, SandboxExecReadinessError as a$, type NegotiationContext as a0, type NormalizedRunCredentialMaterial as a1, type NumstatEntry as a2, OP_STREAM_ACK_INTERVAL_MS as a3, OP_STREAM_DEFAULT_WINDOW_BYTES as a4, OP_STREAM_RECONNECT_HOLD_MS as a5, OP_STREAM_SILENCE_TIMEOUT_MS as a6, type OpStreamExecOutcome as a7, type OpStreamJournal as a8, type OpStreamSubscription as a9, type RoutingRetainedProcessTerminalProof as aA, RoutingSandboxSession as aB, type RoutingSandboxSessionDeps as aC, type RoutingTransitionEvent as aD, RoutingUnsupportedError as aE, type RunCredentialCommandRunner as aF, type RunCredentialCommandSession as aG, type RunCredentialExpectedScope as aH, RunCredentialValidationError as aI, SELFHOSTED_DEFAULT_TIMEOUT_MS as aJ, SELFHOSTED_DRAINING_MAX_RETRIES as aK, SELFHOSTED_EXEC_DRAINING_MAX_RETRIES as aL, SELFHOSTED_IDEMPOTENT_READONLY_OPS as aM, SELFHOSTED_INFRASTRUCTURE_FAULT_CLASSES as aN, SELFHOSTED_NEVER_SENT_MAX_RETRIES as aO, SELFHOSTED_RECONNECT_WINDOW_MS as aP, SELFHOSTED_RELAY_STREAM_PATH as aQ, SELFHOSTED_RETRY_BACKOFF_BASE_MS as aR, SELFHOSTED_RETRY_BACKOFF_CAP_MS as aS, SELFHOSTED_RETRY_BACKOFF_FACTOR as aT, SELFHOSTED_TIMEOUT_MAX_RETRIES as aU, STREAM_PORT as aV, STREAM_TOKEN_DEFAULT_TTL_SECONDS as aW, SandboxChannelAService as aX, type SandboxChannelAServiceOptions as aY, SandboxConfigError as aZ, type SandboxCreatedCallback as a_, type OpStreamTransport as aa, OpStreamUnavailableError as ab, PROVIDER_REGISTRY as ac, type ProviderConstructionContext as ad, type ProviderRegistration as ae, type ProviderSandboxFailure as af, type ProviderSandboxFailureKind as ag, REPOSITORY_DISCOVERY_LIMIT as ah, type RecordingArtifactMetadata as ai, type RecordingCodec as aj, type RecordingContentType as ak, RecordingError as al, type RecordingProcess as am, RecordingUnavailableError as an, type RepositoryDiscoveryDegradedReason as ao, type RepositoryDiscoveryResult as ap, type ResolvedActiveBackend as aq, type RevalidateModalOrphanTermination as ar, type RoutableBackendSession as as, type RoutableSandbox as at, RoutingBackendRecoveryRequiredError as au, RoutingMutationOutcomeUnknownError as av, type RoutingMutationSettlementResult as aw, type RoutingRetainedProcess as ax, type RoutingRetainedProcessAdoption as ay, RoutingRetainedProcessNotFoundError as az, ActiveBackendUnresolvableError as b, ensureModalRegistryImage as b$, SandboxProviderUnavailableError as b0, SandboxReplacementProviderStateError as b1, SandboxResumeStateUnavailableError as b2, type SelfhostedApplyDiff as b3, SelfhostedControlError as b4, type SelfhostedEditor as b5, type SelfhostedEnrollment as b6, type SelfhostedExecArgs as b7, type SelfhostedExecResult as b8, type SelfhostedImageOutput as b9, type WorkspaceTreeFingerprint as bA, agentErrorToControlError as bB, assertDescriptorRegistryInvariants as bC, assertProviderRegistryInvariants as bD, assertSafeRelPath as bE, backendSupportsOs as bF, buildDisplayStackScript as bG, buildSelfhostedBackendSession as bH, buildStreamUrl as bI, buildTerminalServerScript as bJ, captureVerifiedWorkspaceArchive as bK, classifyProviderSandboxFailure as bL, clearRunCredentials as bM, clearRunCredentialsForAttempt as bN, contentTypeForCodec as bO, createSandboxClient as bP, createSandboxClientForBackend as bQ, decideSelfhostedRetry as bR, decodeModalSnapshotId as bS, defaultSelfhostedRetryClock as bT, deletePriorPersistedSnapshot as bU, deleteRecordingArtifacts as bV, deserializeSandboxSessionStateEnvelope as bW, desktopCapableBackend as bX, drainingExhaustedError as bY, drainingMessage as bZ, ensureDisplayStack as b_, type SelfhostedLivenessState as ba, type SelfhostedNegotiationInput as bb, type SelfhostedOpObservation as bc, type SelfhostedOpObserver as bd, type SelfhostedOpStreamDeps as be, type SelfhostedRelayConfig as bf, type SelfhostedRetryClock as bg, type SelfhostedRetryDecision as bh, SelfhostedSandboxClient as bi, SelfhostedSession as bj, type SelfhostedSessionBuild as bk, type SelfhostedSessionDeps as bl, type SelfhostedSessionState as bm, type SelfhostedUnavailableReason as bn, type StartRecordingInput as bo, StreamPortUnavailableError as bp, type SwapTargetEstablishability as bq, TERMINAL_SERVER_TIMEOUT_MS as br, TerminalServerError as bs, TerminalServerUnsupportedError as bt, ToolspaceTokenPathError as bu, type VerifiedWorkspaceArchive as bv, WORKSPACE_ARCHIVE_DESCRIPTOR_VERSION as bw, type WorkspaceArchiveDescriptor as bx, type WorkspaceArchiveIntegrityCode as by, WorkspaceArchiveIntegrityError as bz, type ActivePointer as c, toolspaceTokenFileForSession as c$, ensureTerminalServer as c0, establishSandboxSessionFromEnvelope as c1, execDeadlineHint as c2, exposeStreamPort as c3, extForCodec as c4, fingerprintSandboxWorkspace as c5, hasPersistableSandboxProviderIdentity as c6, inspectRecordingArtifact as c7, isExecSessionLostBanner as c8, isProviderSandboxGoneDuringRoutedOperation as c9, renderSelfhostedFault as cA, requirePersistableReplacementSandboxEnvelope as cB, restoredSandboxSessionStateFromEntry as cC, runCredentialPointerFile as cD, runCredentialRoot as cE, runWithToolCallCorrelation as cF, sandboxStateEntryFromRunState as cG, sanitizeOpIdToken as cH, selectBackend as cI, selfhostedFaultClass as cJ, selfhostedLiveness as cK, selfhostedRetryBackoffMs as cL, serializeEstablishedSandboxEnvelope as cM, serializeReplacementSandboxEnvelope as cN, setSelfhostedApplyDiff as cO, startRecording as cP, stopRecording as cQ, stripExecBanner as cR, subjectFor as cS, swapTargetEstablishability as cT, sweepModalOrphanSandboxes as cU, tagModalSandbox as cV, tearDownDisplayStack as cW, tearDownTerminalServer as cX, terminateModalSandboxById as cY, timeoutAgentError as cZ, timeoutControlResponse as c_, isProviderSandboxNotFoundError as ca, isProviderSandboxTransientError as cb, isSelfhostedProviderNotFoundError as cc, makeActiveBackendResolver as cd, materializeRunCredentials as ce, mintStreamToken as cf, modalSandboxAttributionEnvironment as cg, modalSandboxAttributionTags as ch, negotiateCapabilities as ci, negotiateSelfhostedCapabilities as cj, nextDurableOpId as ck, normalizeRunCredentialsResolution as cl, offlineAgentError as cm, offlineControlResponse as cn, opAckSubject as co, opFrameSubject as cp, parseExecBannerExitCode as cq, parseExecBannerSessionId as cr, parseNumstatZ as cs, parsePorcelainV2 as ct, parseUnifiedPatch as cu, parseWorkspaceArchiveDescriptor as cv, payloadTooLargeMessage as cw, readVerifiedWorkspaceArchive as cx, readWorkspaceArchiveFromEnvelopeSessionState as cy, recordingStorageKey as cz, type ChannelAEmitter as d, toolspaceTokenFileFromEnvironment as d0, uploadRecordingArtifact as d1, verifyRestoredWorkspace as d2, verifySandboxExecReadiness as d3, verifyStreamToken as d4, withRunCredentialEnvironment as d5, withRunCredentialsClient as d6, withRunCredentialsSession as d7, withToolspaceTokenClient as d8, withToolspaceTokenEnvironment as d9, withToolspaceTokenSession as da, type ChannelAExecArgs as e, type ChannelAExecResult as f, ChannelANotFoundError as g, type ChannelASession as h, ChannelAUnavailableError as i, ChannelAUnsupportedError as j, ChannelAValidationError as k, type ControlRpc as l, DISPLAY_STACK_TIMEOUT_MS as m, type DefaultBackendLossResult as n, type DesktopGeometry as o, type DisplayStackCallerKind as p, type DisplayStackClassification as q, DisplayStackError as r, type DisplayStackTelemetryContext as s, type DisplayStackTelemetryEvent as t, type DisplayStackTelemetryStatus as u, DisplayStackUnsupportedError as v, type EnsureDisplayStackResult as w, type EnsureTerminalServerOptions as x, type EnsureTerminalServerResult as y, type EstablishedSandboxSession as z };