@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.
- package/dist/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
- package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
- package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
- package/dist/chunk-GNEGANUF.js.map +1 -0
- package/dist/codex-tool-search.d.ts +100 -0
- package/dist/context-compaction.d.ts +236 -0
- package/dist/history-sanitizer.d.ts +320 -0
- package/dist/index.d.ts +186 -718
- package/dist/index.js +612 -126
- package/dist/index.js.map +1 -1
- package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
- package/dist/mcp-network.d.ts +32 -35
- package/dist/metrics.d.ts +33 -0
- package/dist/sandbox/capabilities.d.ts +11 -0
- package/dist/sandbox/channel-a.d.ts +226 -0
- package/dist/sandbox/display-stack.d.ts +99 -0
- package/dist/sandbox/errors.d.ts +16 -0
- package/dist/sandbox/exec-banner.d.ts +19 -0
- package/dist/sandbox/index.d.ts +190 -6
- package/dist/sandbox/index.js +20 -4
- package/dist/sandbox/op-correlation.d.ts +24 -0
- package/dist/sandbox/provider-errors.d.ts +32 -0
- package/dist/sandbox/provider-operation-gate.d.ts +12 -0
- package/dist/sandbox/providers/blaxel.d.ts +2 -0
- package/dist/sandbox/providers/cloudflare.d.ts +2 -0
- package/dist/sandbox/providers/daytona.d.ts +2 -0
- package/dist/sandbox/providers/docker.d.ts +2 -0
- package/dist/sandbox/providers/e2b.d.ts +2 -0
- package/dist/sandbox/providers/index.d.ts +11 -0
- package/dist/sandbox/providers/local.d.ts +2 -0
- package/dist/sandbox/providers/modal.d.ts +132 -0
- package/dist/sandbox/providers/none.d.ts +2 -0
- package/dist/sandbox/providers/runloop.d.ts +2 -0
- package/dist/sandbox/providers/selfhosted.d.ts +2 -0
- package/dist/sandbox/providers/types.d.ts +30 -0
- package/dist/sandbox/providers/vercel.d.ts +2 -0
- package/dist/sandbox/recording.d.ts +80 -0
- package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
- package/dist/sandbox/routing/routing-session.d.ts +401 -0
- package/dist/sandbox/run-credentials.d.ts +65 -0
- package/dist/sandbox/select.d.ts +120 -0
- package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
- package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
- package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
- package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
- package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
- package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
- package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
- package/dist/sandbox/selfhosted/session.d.ts +552 -0
- package/dist/sandbox/selfhosted/testing.d.ts +48 -0
- package/dist/sandbox/stream-port.d.ts +80 -0
- package/dist/sandbox/stream-token.d.ts +44 -0
- package/dist/sandbox/terminal-server.d.ts +67 -0
- package/dist/sandbox/toolspace-token.d.ts +23 -0
- package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
- package/dist/sandbox/workspace-archive.d.ts +39 -0
- package/dist/sandbox-computer.d.ts +227 -0
- package/dist/screenshot-error-card.d.ts +1 -0
- package/dist/skill-library.d.ts +10 -12
- package/dist/usage-telemetry.d.ts +45 -0
- package/dist/workspace-skills.d.ts +31 -0
- package/package.json +7 -7
- package/src/context-compaction.ts +600 -6
- package/src/history-sanitizer.ts +10 -92
- package/src/index.ts +301 -62
- package/src/sandbox/channel-a.ts +267 -81
- package/src/sandbox/index.ts +56 -94
- package/src/sandbox/provider-operation-gate.ts +110 -0
- package/src/sandbox/providers/docker.ts +13 -1
- package/src/sandbox/providers/modal.ts +433 -3
- package/src/sandbox/routing/routing-session.ts +84 -8
- package/src/sandbox/selfhosted/session.ts +7 -6
- package/src/sandbox/turn-tool-cancellation.ts +47 -7
- package/src/sandbox/workspace-archive.ts +176 -73
- package/src/sandbox-computer.ts +25 -75
- package/dist/chunk-HHWIZHQM.js.map +0 -1
- package/dist/index-BraazrAO.d.ts +0 -2931
- /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
package/dist/index-BraazrAO.d.ts
DELETED
|
@@ -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 };
|