@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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { DESKTOP_STREAM_PORT } from "@opengeni/contracts";
|
|
2
|
+
export { DESKTOP_STREAM_PORT };
|
|
3
|
+
export type RecordingCodec = "h264-mp4" | "vp9-webm";
|
|
4
|
+
export type RecordingContentType = "video/mp4" | "video/webm";
|
|
5
|
+
export declare function contentTypeForCodec(codec: RecordingCodec): RecordingContentType;
|
|
6
|
+
export declare function extForCodec(codec: RecordingCodec): string;
|
|
7
|
+
/** No exec/execCommand on the session — the box cannot run ffmpeg. */
|
|
8
|
+
export declare class RecordingUnavailableError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/** ffmpeg failed, the file is missing, or the artifact is over its size limit. */
|
|
12
|
+
export declare class RecordingError extends Error {
|
|
13
|
+
readonly reason: "ffmpeg-error" | "box-death" | "max-bytes-exceeded" | "display-unavailable";
|
|
14
|
+
constructor(message: string, reason: "ffmpeg-error" | "box-death" | "max-bytes-exceeded" | "display-unavailable");
|
|
15
|
+
}
|
|
16
|
+
export type StartRecordingInput = {
|
|
17
|
+
recordingId: string;
|
|
18
|
+
codec?: RecordingCodec;
|
|
19
|
+
framerate?: number;
|
|
20
|
+
maxSeconds?: number;
|
|
21
|
+
dimensions?: [number, number];
|
|
22
|
+
display?: string;
|
|
23
|
+
runAs?: string;
|
|
24
|
+
tmpDir?: string;
|
|
25
|
+
};
|
|
26
|
+
export type RecordingProcess = {
|
|
27
|
+
recordingId: string;
|
|
28
|
+
codec: RecordingCodec;
|
|
29
|
+
boxPath: string;
|
|
30
|
+
pidFile: string;
|
|
31
|
+
dimensions: [number, number];
|
|
32
|
+
framerate: number;
|
|
33
|
+
/** epoch-ms when ffmpeg was launched (for duration computation, F14). */
|
|
34
|
+
startedAt: number;
|
|
35
|
+
display: string;
|
|
36
|
+
runAs?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Launch ffmpeg x11grab on :0 → an mp4/webm file on the box. Backgrounded with
|
|
40
|
+
* `nohup … & echo $!` so the launch returns immediately (F12 — the exec does not
|
|
41
|
+
* block on the recording). A hard `-t <maxSeconds>` ceiling bounds a runaway file
|
|
42
|
+
* across a multi-day turn. Returns the handle the caller carries to stop+finalize.
|
|
43
|
+
*/
|
|
44
|
+
export declare function startRecording(session: unknown, input: StartRecordingInput): Promise<RecordingProcess>;
|
|
45
|
+
/**
|
|
46
|
+
* SIGINT ffmpeg (so it writes a clean moov atom / webm trailer) and wait for the
|
|
47
|
+
* pid to exit. Bounded well under the yield window (F3). Idempotent: a missing
|
|
48
|
+
* pid file is a no-op.
|
|
49
|
+
*/
|
|
50
|
+
export declare function stopRecording(session: unknown, proc: RecordingProcess): Promise<void>;
|
|
51
|
+
export type RecordingArtifactMetadata = {
|
|
52
|
+
contentType: RecordingContentType;
|
|
53
|
+
sizeBytes: number;
|
|
54
|
+
durationSeconds: number;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Inspect and size-gate the finalized recording while it is still on the box.
|
|
58
|
+
* No recording bytes cross into the worker process.
|
|
59
|
+
*/
|
|
60
|
+
export declare function inspectRecordingArtifact(session: unknown, proc: RecordingProcess, maxBytes?: number): Promise<RecordingArtifactMetadata>;
|
|
61
|
+
/**
|
|
62
|
+
* Upload the finalized artifact directly from the sandbox to a scoped object-
|
|
63
|
+
* storage URL. The worker carries only the short URL/headers and metadata; it
|
|
64
|
+
* never materializes a base64 string or Uint8Array for a recording that may be
|
|
65
|
+
* hundreds of megabytes. curl's success marker makes an exec transport that
|
|
66
|
+
* returns non-zero command output instead of throwing still fail closed.
|
|
67
|
+
*/
|
|
68
|
+
export declare function uploadRecordingArtifact(session: unknown, proc: RecordingProcess, input: {
|
|
69
|
+
url: string;
|
|
70
|
+
requiredHeaders: Record<string, string>;
|
|
71
|
+
maxBytes?: number;
|
|
72
|
+
maxSeconds?: number;
|
|
73
|
+
}): Promise<RecordingArtifactMetadata>;
|
|
74
|
+
/**
|
|
75
|
+
* Delete the box artifacts. F9: call this ONLY after the storage PUT confirmed
|
|
76
|
+
* and the `available` row committed — never before. Best-effort; never throws.
|
|
77
|
+
*/
|
|
78
|
+
export declare function deleteRecordingArtifacts(session: unknown, proc: RecordingProcess): Promise<void>;
|
|
79
|
+
/** The storage object key for a recording artifact (parallels the file-asset layout). */
|
|
80
|
+
export declare function recordingStorageKey(workspaceId: string, sessionId: string, recordingId: string, codec: RecordingCodec): string;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { type SelfhostedRelayConfig } from "../selfhosted/session";
|
|
2
|
+
import type { SelfhostedOpObserver } from "../selfhosted/op-observer";
|
|
3
|
+
import type { ControlRpc } from "../selfhosted/control-rpc";
|
|
4
|
+
import type { ActivePointer, RoutableBackendSession, ResolvedActiveBackend } from "./routing-session";
|
|
5
|
+
/** The structural slice of a first-class sandbox the resolver reads (mirror of
|
|
6
|
+
* `@opengeni/db`'s `SandboxRecord`; structural so the leaf does not import DB). */
|
|
7
|
+
export interface RoutableSandbox {
|
|
8
|
+
id: string;
|
|
9
|
+
kind: "modal" | "selfhosted" | string;
|
|
10
|
+
name: string;
|
|
11
|
+
/** For a selfhosted sandbox this is its enrollment id (== the agent id the
|
|
12
|
+
* control-plane subject `agent.<ws>.<id>.rpc` addresses). Null for modal. */
|
|
13
|
+
enrollmentId: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface ActiveBackendResolverDeps {
|
|
16
|
+
/** The workspace the session belongs to (the control-plane subject scope). */
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
/** The session's own group sandbox session — the DEFAULT target
|
|
19
|
+
* (`activeSandboxId === null`). Already established (lease-owned); the proxy
|
|
20
|
+
* never re-establishes it. */
|
|
21
|
+
defaultBackend: RoutableBackendSession;
|
|
22
|
+
/** A label for the default backend (its backend id: "modal"/"selfhosted"/…). */
|
|
23
|
+
defaultKind: string;
|
|
24
|
+
/**
|
|
25
|
+
* Re-resolve the session's HOME backend after the route cache is fenced.
|
|
26
|
+
*
|
|
27
|
+
* The normal path returns `defaultBackend` (the turn's already-established
|
|
28
|
+
* handle), but an API-side same-target repair can rematerialize that lease
|
|
29
|
+
* while a worker turn is still alive. Such a repair advances the route epoch
|
|
30
|
+
* without changing the null pointer; callers that can resume the current
|
|
31
|
+
* lease-owned identity may provide this callback so the next epoch does not
|
|
32
|
+
* keep dispatching to the dead pre-repair object. This callback must never
|
|
33
|
+
* create a provider or replay an operation.
|
|
34
|
+
*/
|
|
35
|
+
resolveDefaultBackend?: (pointer: ActivePointer) => Promise<ResolvedActiveBackend>;
|
|
36
|
+
/** Look up a first-class sandbox by id (the swap target). Returns null when the
|
|
37
|
+
* id is unknown or not in this workspace (the caller 409s the swap). */
|
|
38
|
+
getSandbox(sandboxId: string): Promise<RoutableSandbox | null>;
|
|
39
|
+
/** Build a live `ControlRpc` for the selfhosted control plane (the request-
|
|
40
|
+
* scoped NATS connection). Returns a ControlRpc whose offline/timeout maps to
|
|
41
|
+
* agent_offline/agent_reconnecting (never a NotFound). */
|
|
42
|
+
controlRpcFactory(): ControlRpc;
|
|
43
|
+
/** The relay-URL shape config for selfhosted stream endpoints. */
|
|
44
|
+
relay: SelfhostedRelayConfig;
|
|
45
|
+
/** Establish (resume-by-id) a NON-DEFAULT modal target's box session for a swap.
|
|
46
|
+
* Supplied by the API/worker (a closure over the sibling sandbox's lease). When
|
|
47
|
+
* absent, a modal swap target surfaces as unsupported (the caller validated
|
|
48
|
+
* liveness, so this is the "modal swap not wired in this context" guard). */
|
|
49
|
+
establishModalTarget?: (sandbox: RoutableSandbox) => Promise<RoutableBackendSession>;
|
|
50
|
+
/** The selfhosted CONTROL-op timeout (ping/fs/desktop/pty) for a swap/pin target.
|
|
51
|
+
* Absent ⇒ the session's 30s default. */
|
|
52
|
+
selfhostedTimeoutMs?: number;
|
|
53
|
+
/** The selfhosted EXEC process deadline for a swap/pin target, distinct from the
|
|
54
|
+
* control timeout (a swapped-to machine runs real commands too). Absent ⇒ falls
|
|
55
|
+
* back to the control timeout, as in the session leaf. */
|
|
56
|
+
selfhostedExecTimeoutMs?: number;
|
|
57
|
+
/** The per-op observer threaded into a selfhosted swap/pin target (out-of-band
|
|
58
|
+
* telemetry — metrics + machine.* events). Absent ⇒ no-op. */
|
|
59
|
+
selfhostedOnOp?: SelfhostedOpObserver;
|
|
60
|
+
/**
|
|
61
|
+
* The run's declared sandbox environment — the SAME `Record<string,string>` the
|
|
62
|
+
* worker turn threads into the agent's TARGET manifest (and into the group box at
|
|
63
|
+
* create). Threaded into a selfhosted swap target's session so its
|
|
64
|
+
* `state.manifest.environment` EQUALS the turn's, making the SDK's per-turn
|
|
65
|
+
* provided-session manifest-env delta empty (validateNoEnvironmentDelta).
|
|
66
|
+
* WITHOUT this a pin-to-vm turn throws "Live sandbox sessions cannot change
|
|
67
|
+
* manifest environment variables". Omitted → `{}` (the test/negotiation path).
|
|
68
|
+
*/
|
|
69
|
+
environment?: Record<string, string>;
|
|
70
|
+
/**
|
|
71
|
+
* A pre-established selfhosted session to PIN for the STEADY-STATE machine
|
|
72
|
+
* pointer (the worker turn's machine-primary path, Stage D). When the pointer
|
|
73
|
+
* targets THIS sandbox at THIS epoch, the resolver returns this SAME instance
|
|
74
|
+
* instead of building a fresh `SelfhostedSession`. This is the instance-identity
|
|
75
|
+
* pin: the SDK reads/writes `state.manifest` at turn START via the proxy's `state`
|
|
76
|
+
* getter (which reads the default/last-resolved backend's state) and then reads it
|
|
77
|
+
* per op via this resolver — those MUST land on ONE SelfhostedSession/manifest, or
|
|
78
|
+
* a turn-start manifest write is invisible to the per-op reads (two-instance
|
|
79
|
+
* divergence). A swap AWAY (a different sandbox id, or the same id at a moved epoch)
|
|
80
|
+
* falls through to a fresh build under the new epoch. Omitted for the API/live-swap
|
|
81
|
+
* path (which always builds fresh — it has no pre-established turn session).
|
|
82
|
+
*/
|
|
83
|
+
pinnedSelfhosted?: {
|
|
84
|
+
sandboxId: string;
|
|
85
|
+
epoch: number;
|
|
86
|
+
session: RoutableBackendSession;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Whether `defaultBackend` IS the session's home box — i.e. whether the null pointer
|
|
90
|
+
* (== home) may resolve to it. Defaults to TRUE (omitted): a genuine machine-HOME turn
|
|
91
|
+
* (home IS the machine) and a Modal-home turn established on its group box both pass/omit
|
|
92
|
+
* true, so null resolves to `defaultBackend` as before. Set explicitly FALSE only for a
|
|
93
|
+
* machine-primary turn of a Modal-HOME session (pinned to a machine, group box never
|
|
94
|
+
* established this turn): the null pointer then has nothing to resolve to THIS turn, so
|
|
95
|
+
* rather than silently keep serving the pinned machine as if the detach never happened,
|
|
96
|
+
* the null branch throws a typed `home_unavailable_this_turn` error — the detach's pointer
|
|
97
|
+
* commit stands and takes effect on the NEXT turn, which starts null and establishes the
|
|
98
|
+
* home box normally. Lazily establishing the home box mid-turn on such a clear is a
|
|
99
|
+
* deferred follow-up (issue #341); until then this makes the gap fail typed-and-specific,
|
|
100
|
+
* never silent.
|
|
101
|
+
*/
|
|
102
|
+
defaultIsHome?: boolean;
|
|
103
|
+
}
|
|
104
|
+
/** Why a persisted pointer / swap target cannot be turned into a live backend. A
|
|
105
|
+
* typed discriminant so callers (turn-start reconcile, the swap tool, future
|
|
106
|
+
* reports) can distinguish a stale pointer from an unaddressable enrollment from a
|
|
107
|
+
* backend the turn context simply cannot establish — instead of string-matching
|
|
108
|
+
* one opaque message (issue #341: "typed diagnostics that distinguish stale
|
|
109
|
+
* pointer, offline enrollment, unsupported backend context, transient failure").
|
|
110
|
+
* - `stale_pointer` — the pointed-at sandbox row is gone (deleted/orphaned).
|
|
111
|
+
* - `offline_enrollment` — a selfhosted target carries no enrollment/agent id to address.
|
|
112
|
+
* - `unsupported_backend_context` — a target no turn routing context can establish
|
|
113
|
+
* (a non-group Modal sibling; an unknown kind).
|
|
114
|
+
* - `transient_establishment` — a momentary establish failure worth a retry (reserved;
|
|
115
|
+
* control-plane timeouts surface as their own typed error).
|
|
116
|
+
* - `home_unavailable_this_turn` — the pointer was cleared to the session default (home)
|
|
117
|
+
* mid-turn, but this turn started pinned to a machine and
|
|
118
|
+
* never established the home box, so null has nothing to
|
|
119
|
+
* resolve to THIS turn (the detach takes effect next turn). */
|
|
120
|
+
export type BackendUnresolvableCode = "stale_pointer" | "offline_enrollment" | "unsupported_backend_context" | "transient_establishment" | "home_unavailable_this_turn";
|
|
121
|
+
/** Thrown when a swap target cannot be resolved (unknown sandbox, or a modal
|
|
122
|
+
* target with no establisher in this context). The caller maps it to a 409.
|
|
123
|
+
* Carries a typed {@link BackendUnresolvableCode} in addition to the message. */
|
|
124
|
+
export declare class ActiveBackendUnresolvableError extends Error {
|
|
125
|
+
readonly name = "ActiveBackendUnresolvableError";
|
|
126
|
+
readonly code: BackendUnresolvableCode;
|
|
127
|
+
constructor(code: BackendUnresolvableCode, message: string);
|
|
128
|
+
}
|
|
129
|
+
/** The outcome of {@link swapTargetEstablishability}. */
|
|
130
|
+
export type SwapTargetEstablishability = {
|
|
131
|
+
ok: true;
|
|
132
|
+
} | {
|
|
133
|
+
ok: false;
|
|
134
|
+
code: BackendUnresolvableCode;
|
|
135
|
+
reason: string;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* THE SINGLE SOURCE OF TRUTH for "can a turn's routing context ESTABLISH this swap
|
|
139
|
+
* target?" — shared by swap/seed ADMISSION (`fleet.resolveTarget`, before the CAS
|
|
140
|
+
* pointer commit) and turn-time ESTABLISHMENT (this resolver + `wrapTurnBoxWithRouting`).
|
|
141
|
+
* Admission MUST reject any target this predicate calls unestablishable, so the
|
|
142
|
+
* pointer is never committed to a backend no turn can resume (issue #341 Shape 1:
|
|
143
|
+
* a Modal-sibling swap was admitted + epoch-bumped, then every following op
|
|
144
|
+
* stranded because no context wires an establisher for it).
|
|
145
|
+
*
|
|
146
|
+
* - the session's OWN group box (`isSessionGroup`) is the null/home target and is
|
|
147
|
+
* always establishable (it IS the default backend);
|
|
148
|
+
* - a `selfhosted` machine target is establishable (`buildSelfhostedBackendSession`);
|
|
149
|
+
* admission's separate liveness probe — not this predicate — gates online-ness;
|
|
150
|
+
* - a NON-group `modal` target is NOT establishable: no turn context wires
|
|
151
|
+
* `establishModalTarget` (cross-group Modal resume-by-id is a future, billing-
|
|
152
|
+
* touching feature — sandbox-routing.ts). Wiring that establisher is the ONE
|
|
153
|
+
* toggle that flips this branch; keep admission and the resolver in lockstep.
|
|
154
|
+
*/
|
|
155
|
+
export declare function swapTargetEstablishability(target: {
|
|
156
|
+
kind: string;
|
|
157
|
+
isSessionGroup: boolean;
|
|
158
|
+
}): SwapTargetEstablishability;
|
|
159
|
+
/**
|
|
160
|
+
* Build the `resolveActiveBackend(pointer)` closure for a `RoutingSandboxSession`.
|
|
161
|
+
* The returned closure is re-invoked by the proxy whenever the active_epoch moves
|
|
162
|
+
* (the per-epoch cache miss), so it must be cheap-and-correct for the steady-state
|
|
163
|
+
* (default pointer → the already-established group box) and build a fresh backend
|
|
164
|
+
* for a swap target.
|
|
165
|
+
*
|
|
166
|
+
* - `activeSandboxId === null` → the default group backend (no re-establish).
|
|
167
|
+
* - a selfhosted target → a `SelfhostedSession` bound to the enrollment agentId,
|
|
168
|
+
* fenced under `pointer.activeEpoch` (echoed on every ControlRequest so the
|
|
169
|
+
* agent can reject a stale op with ERROR_CODE_FENCED — the swap-race fence).
|
|
170
|
+
* - a modal target → `establishModalTarget` (the resume-by-id closure), else
|
|
171
|
+
* unresolvable.
|
|
172
|
+
*/
|
|
173
|
+
export declare function makeActiveBackendResolver(deps: ActiveBackendResolverDeps): (pointer: ActivePointer) => Promise<ResolvedActiveBackend>;
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import type { ExposedPortEndpoint } from "../stream-port";
|
|
2
|
+
/** The per-session active-sandbox pointer the proxy re-reads on every op. Mirror
|
|
3
|
+
* of `@opengeni/db`'s `ActiveSandboxPointer` (structural, so the leaf does not
|
|
4
|
+
* import the DB package). `activeSandboxId === null` == "use the session's own
|
|
5
|
+
* group sandbox" (the default/backward-compat target). */
|
|
6
|
+
export interface ActivePointer {
|
|
7
|
+
activeSandboxId: string | null;
|
|
8
|
+
activeEpoch: number;
|
|
9
|
+
/** The session's working directory — the path/cwd base for a selfhosted backend
|
|
10
|
+
* (threaded into the SelfhostedSession via the resolver). `null`/absent ⇒ the
|
|
11
|
+
* default workspace_root behavior. Optional so the default-pointer fallback
|
|
12
|
+
* (`{ activeSandboxId: null, activeEpoch: 0 }`) the readPointer wiring synthesizes
|
|
13
|
+
* when no row exists needs no extra field. Only the selfhosted branch reads it;
|
|
14
|
+
* the modal/default branches ignore it. */
|
|
15
|
+
workingDir?: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The structural slice of a backend session the routing proxy forwards to. It is
|
|
19
|
+
* a superset-by-optionality of every backend's surface (Modal's `SandboxSession`
|
|
20
|
+
* AND the `SelfhostedSession`): each method is optional because a heterogeneous
|
|
21
|
+
* target may or may not implement it, and the proxy reflects that at call-time.
|
|
22
|
+
*/
|
|
23
|
+
export interface RoutableBackendSession {
|
|
24
|
+
state?: unknown;
|
|
25
|
+
exec?(args: unknown): Promise<unknown>;
|
|
26
|
+
execCommand?(args: unknown): Promise<string>;
|
|
27
|
+
writeStdin?(args: unknown): Promise<string>;
|
|
28
|
+
cancelExecCommand?(opId: string): Promise<boolean>;
|
|
29
|
+
readFile?(args: unknown): Promise<string | Uint8Array>;
|
|
30
|
+
writeFile?(args: unknown): Promise<unknown>;
|
|
31
|
+
createEditor?(runAs?: string): unknown;
|
|
32
|
+
listDir?(args: unknown): Promise<unknown>;
|
|
33
|
+
pathExists?(path: string, runAs?: string): Promise<boolean>;
|
|
34
|
+
viewImage?(args: unknown): Promise<unknown>;
|
|
35
|
+
materializeEntry?(args: unknown): Promise<void>;
|
|
36
|
+
supportsPty?(): boolean;
|
|
37
|
+
resolveExposedPort?(port: number): Promise<ExposedPortEndpoint>;
|
|
38
|
+
serializeSessionState?(): Promise<unknown>;
|
|
39
|
+
desktopInput?(event: unknown): Promise<void>;
|
|
40
|
+
screenshot?(): Promise<{
|
|
41
|
+
png: Uint8Array;
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
nativeWidth: number;
|
|
45
|
+
nativeHeight: number;
|
|
46
|
+
}>;
|
|
47
|
+
}
|
|
48
|
+
/** The resolved active backend for an epoch: the live session + the sandbox id it
|
|
49
|
+
* belongs to (`null` == the group sandbox) so a fence-retry can detect a move. */
|
|
50
|
+
export interface ResolvedActiveBackend {
|
|
51
|
+
session: RoutableBackendSession;
|
|
52
|
+
/** The sandbox id this backend serves (`null` == the session's group sandbox). */
|
|
53
|
+
sandboxId: string | null;
|
|
54
|
+
/** A label for diagnostics ("modal" | "selfhosted" | the sandbox name). */
|
|
55
|
+
kind: string;
|
|
56
|
+
/** Exact durable home-lease epoch for a persistable provider. Absent for
|
|
57
|
+
* connected-machine and other non-persistable route targets. */
|
|
58
|
+
leaseEpoch?: number;
|
|
59
|
+
/** Exact durable provider identity paired with `leaseEpoch`. This is internal
|
|
60
|
+
* routing metadata only; it is never projected to an agent or public API. */
|
|
61
|
+
providerInstanceId?: string;
|
|
62
|
+
/** Active-pointer epoch observed when this route was resolved. The proxy fills
|
|
63
|
+
* this internally even when a resolver omits it. */
|
|
64
|
+
activeEpoch?: number;
|
|
65
|
+
}
|
|
66
|
+
/** Durable identity assigned to a provider exec that yielded instead of
|
|
67
|
+
* exiting. The UUID is OpenGeni authority; the numeric provider session id is
|
|
68
|
+
* only a locator within the exact copied backend route. */
|
|
69
|
+
export type RoutingRetainedProcess = {
|
|
70
|
+
id: string;
|
|
71
|
+
providerSessionId: number;
|
|
72
|
+
};
|
|
73
|
+
/** A yielded process was durably promoted, but the mutable authority checked
|
|
74
|
+
* after that commit was stale. The provider output must still be rejected;
|
|
75
|
+
* this result carries only the safe process identity needed to drain the exact
|
|
76
|
+
* original backend without replaying the mutation. */
|
|
77
|
+
export type RoutingMutationSettlementResult = {
|
|
78
|
+
status: "retained_process_durable_output_rejected";
|
|
79
|
+
retainedProcess: RoutingRetainedProcess;
|
|
80
|
+
};
|
|
81
|
+
/** Exact copied route identity required to reconstruct an already-durable
|
|
82
|
+
* retained process after the request/runtime object that opened it is gone.
|
|
83
|
+
* The live session is deliberately not caller-supplied: adoption may bind only
|
|
84
|
+
* to this proxy's construction-time default backend, so it cannot resolve or
|
|
85
|
+
* follow the current active pointer. */
|
|
86
|
+
export type RoutingRetainedProcessAdoption = {
|
|
87
|
+
process: RoutingRetainedProcess;
|
|
88
|
+
backend: {
|
|
89
|
+
sandboxId: string | null;
|
|
90
|
+
leaseEpoch?: number;
|
|
91
|
+
providerInstanceId?: string;
|
|
92
|
+
activeEpoch: number;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
export type RoutingRetainedProcessTerminalProof = {
|
|
96
|
+
outcome: "exited";
|
|
97
|
+
exitCode: number;
|
|
98
|
+
reason: "provider_exit_banner";
|
|
99
|
+
} | {
|
|
100
|
+
outcome: "lost";
|
|
101
|
+
exitCode: null;
|
|
102
|
+
reason: "provider_session_lost_banner";
|
|
103
|
+
};
|
|
104
|
+
export interface RoutingSandboxSessionDeps {
|
|
105
|
+
/**
|
|
106
|
+
* The DEFAULT backend resolved at construction time (the same shape `resolve()`
|
|
107
|
+
* caches as `lastResolved`). This seeds `session.state` BEFORE the first op so a
|
|
108
|
+
* consumer that reads `session.state.manifest` at turn START — the @openai/agents
|
|
109
|
+
* SDK does, before any tool runs — sees the real default backend's state object
|
|
110
|
+
* (and writes to `session.state.manifest = …` land on it by reference), instead
|
|
111
|
+
* of an empty `{}` that crashes serializeManifestEnvironment /
|
|
112
|
+
* validateProvidedSessionManifestUpdate. The default-pointer case
|
|
113
|
+
* (`activeSandboxId === null`) resolves synchronously to this same backend, so
|
|
114
|
+
* seeding it here is byte-identical to what the first `resolve()` would produce.
|
|
115
|
+
*/
|
|
116
|
+
defaultResolved?: ResolvedActiveBackend;
|
|
117
|
+
/** Re-read the per-session active pointer. Called on EVERY op (the per-call
|
|
118
|
+
* re-resolve that makes a mid-turn swap visible to the next tool call). */
|
|
119
|
+
readPointer(): Promise<ActivePointer>;
|
|
120
|
+
/**
|
|
121
|
+
* Resolve the active backend session for a pointer. The proxy memoizes the
|
|
122
|
+
* result by `activeEpoch`, so this is called at most once per epoch (per op the
|
|
123
|
+
* pointer is re-read, but the heavy resolve only re-runs when the epoch moved).
|
|
124
|
+
* For `pointer.activeSandboxId === null` this returns the default/group backend
|
|
125
|
+
* (typically the already-established turn box); for a non-null target it builds
|
|
126
|
+
* the target backend (a sibling Modal box or a selfhosted machine session).
|
|
127
|
+
*/
|
|
128
|
+
resolveActiveBackend(pointer: ActivePointer): Promise<ResolvedActiveBackend>;
|
|
129
|
+
/** Max fence/stale retries within a single op before surfacing the error.
|
|
130
|
+
* Defaults to 3 — enough to absorb a couple of concurrent swaps, bounded so a
|
|
131
|
+
* swap-storm cannot loop forever. */
|
|
132
|
+
maxFenceRetries?: number;
|
|
133
|
+
/** Optional structured-log sink for swap/fence transitions (diagnostics). */
|
|
134
|
+
onTransition?: (event: RoutingTransitionEvent) => void;
|
|
135
|
+
/** Admit a filesystem-writing operation after resolving its exact route but
|
|
136
|
+
* before invoking the provider. A rejection fails closed and is deliberately
|
|
137
|
+
* outside provider fence-retry/error handling, so it can never replay the op
|
|
138
|
+
* against a rival backend. */
|
|
139
|
+
beforeMutation?: (input: {
|
|
140
|
+
op: string;
|
|
141
|
+
backend: ResolvedActiveBackend;
|
|
142
|
+
}) => Promise<unknown>;
|
|
143
|
+
/** Mark a mutation physically settled after its provider promise resolves OR
|
|
144
|
+
* rejects. A resolved result is also revalidated against the same durable
|
|
145
|
+
* route and attempt fence before its output is accepted. Settlement rejection
|
|
146
|
+
* leaves a fail-closed blocker and the mutation must never be replayed. */
|
|
147
|
+
afterMutation?: (input: {
|
|
148
|
+
op: string;
|
|
149
|
+
backend: ResolvedActiveBackend;
|
|
150
|
+
admission: unknown;
|
|
151
|
+
outcome: "resolved" | "rejected";
|
|
152
|
+
/** Provider result for a resolved call. Never present for rejection. */
|
|
153
|
+
result?: unknown;
|
|
154
|
+
/** Stable candidate generated before durable promotion is attempted. It is
|
|
155
|
+
* supplied only when the provider returned a positive yielded-session id. */
|
|
156
|
+
retainedProcess?: RoutingRetainedProcess;
|
|
157
|
+
}) => Promise<void | RoutingMutationSettlementResult>;
|
|
158
|
+
/** Admit one model/user-visible stdin mutation under the already-durable
|
|
159
|
+
* retained process authority. Control polling and helper execs never call it. */
|
|
160
|
+
beforeProcessMutation?: (input: {
|
|
161
|
+
op: string;
|
|
162
|
+
backend: ResolvedActiveBackend;
|
|
163
|
+
process: RoutingRetainedProcess;
|
|
164
|
+
}) => Promise<unknown>;
|
|
165
|
+
/** Physically settle one process-scoped stdin admission. */
|
|
166
|
+
afterProcessMutation?: (input: {
|
|
167
|
+
op: string;
|
|
168
|
+
backend: ResolvedActiveBackend;
|
|
169
|
+
process: RoutingRetainedProcess;
|
|
170
|
+
admission: unknown;
|
|
171
|
+
outcome: "resolved" | "rejected";
|
|
172
|
+
result?: unknown;
|
|
173
|
+
}) => Promise<void>;
|
|
174
|
+
/** Close the durable parent admission/process holder only after exact exit or
|
|
175
|
+
* matching provider-loss proof. Tracking is removed only after this resolves. */
|
|
176
|
+
settleProcess?: (input: {
|
|
177
|
+
backend: ResolvedActiveBackend;
|
|
178
|
+
process: RoutingRetainedProcess;
|
|
179
|
+
proof: RoutingRetainedProcessTerminalProof;
|
|
180
|
+
}) => Promise<void>;
|
|
181
|
+
/** Called only when an operation against the default/home backend throws a
|
|
182
|
+
* non-fence error. Wiring may classify definitive provider disappearance and
|
|
183
|
+
* atomically retire the exact lease epoch. Returning a result makes dispatch
|
|
184
|
+
* throw a typed recovery-required error WITHOUT replaying the operation (the
|
|
185
|
+
* original mutation may have reached the provider). */
|
|
186
|
+
onDefaultBackendError?: (input: {
|
|
187
|
+
op: string;
|
|
188
|
+
error: unknown;
|
|
189
|
+
kind: string;
|
|
190
|
+
backend: ResolvedActiveBackend;
|
|
191
|
+
}) => Promise<DefaultBackendLossResult | null>;
|
|
192
|
+
}
|
|
193
|
+
export type DefaultBackendLossResult = {
|
|
194
|
+
leaseEpoch: number;
|
|
195
|
+
recovery: "pending" | "degraded" | "unrecoverable" | "superseded";
|
|
196
|
+
};
|
|
197
|
+
export interface RoutingTransitionEvent {
|
|
198
|
+
type: "resolved" | "fenced-retry" | "epoch-changed";
|
|
199
|
+
fromEpoch: number;
|
|
200
|
+
toEpoch: number;
|
|
201
|
+
sandboxId: string | null;
|
|
202
|
+
kind: string;
|
|
203
|
+
}
|
|
204
|
+
/** Thrown when the active backend does not implement the requested op (a
|
|
205
|
+
* heterogeneous target whose surface lacks the method the caller reached for). */
|
|
206
|
+
export declare class RoutingUnsupportedError extends Error {
|
|
207
|
+
readonly name = "RoutingUnsupportedError";
|
|
208
|
+
constructor(op: string, kind: string);
|
|
209
|
+
}
|
|
210
|
+
export declare class RoutingBackendRecoveryRequiredError extends Error {
|
|
211
|
+
readonly op: string;
|
|
212
|
+
readonly leaseEpoch: number;
|
|
213
|
+
readonly recovery: DefaultBackendLossResult["recovery"];
|
|
214
|
+
readonly name = "RoutingBackendRecoveryRequiredError";
|
|
215
|
+
readonly retryable: boolean;
|
|
216
|
+
constructor(op: string, leaseEpoch: number, recovery: DefaultBackendLossResult["recovery"]);
|
|
217
|
+
}
|
|
218
|
+
/** A mutating provider call was admitted but could not be settled against the
|
|
219
|
+
* exact route that admitted it. The provider may have applied the effect, so the
|
|
220
|
+
* proxy rejects the output and explicitly forbids an automatic replay. */
|
|
221
|
+
export declare class RoutingMutationOutcomeUnknownError extends Error {
|
|
222
|
+
readonly op: string;
|
|
223
|
+
readonly name = "RoutingMutationOutcomeUnknownError";
|
|
224
|
+
readonly retryable = false;
|
|
225
|
+
readonly retainedProcess: RoutingRetainedProcess | null;
|
|
226
|
+
constructor(op: string, message: string, options?: {
|
|
227
|
+
cause?: unknown;
|
|
228
|
+
retainedProcess?: RoutingRetainedProcess;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/** An explicit process-aware operation named a numeric provider locator that
|
|
232
|
+
* is not retained by this routing session. Callers must never fall back to the
|
|
233
|
+
* current active pointer for such an operation. */
|
|
234
|
+
export declare class RoutingRetainedProcessNotFoundError extends Error {
|
|
235
|
+
readonly providerSessionId: number;
|
|
236
|
+
readonly name = "RoutingRetainedProcessNotFoundError";
|
|
237
|
+
readonly retryable = false;
|
|
238
|
+
constructor(providerSessionId: number);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* ONE stable session-shaped object the SDK binds to. Every method re-reads the
|
|
242
|
+
* pointer, resolves the active backend (cached by epoch), and dispatches. A
|
|
243
|
+
* stale-epoch fence (the pointer moved mid-op) re-resolves and retries.
|
|
244
|
+
*
|
|
245
|
+
* The proxy implements ALL of the consumed surface so the SDK (which binds method
|
|
246
|
+
* presence ONCE) always sees `exec`/`readFile`/`resolveExposedPort`/… present. If
|
|
247
|
+
* the CURRENTLY-active backend lacks a method, the proxy applies the natural
|
|
248
|
+
* fallback (`exec`→`execCommand`) or throws `RoutingUnsupportedError` — degrade is
|
|
249
|
+
* a value, not a crash.
|
|
250
|
+
*
|
|
251
|
+
* `state` is a STABLE getter so a consumer reading `session.state` (channel-a's
|
|
252
|
+
* `readInstanceId`, the docker-network decoration) gets a coherent snapshot of the
|
|
253
|
+
* currently-active backend without a method call.
|
|
254
|
+
*/
|
|
255
|
+
export declare class RoutingSandboxSession implements RoutableBackendSession {
|
|
256
|
+
private readonly deps;
|
|
257
|
+
private readonly maxFenceRetries;
|
|
258
|
+
private cachedEpoch;
|
|
259
|
+
private cachedSandboxId;
|
|
260
|
+
private cached;
|
|
261
|
+
private lastResolved;
|
|
262
|
+
/** Provider session ids are scoped to one backend instance. Each entry copies
|
|
263
|
+
* that exact resolved route so pointer movement can never redirect stdin,
|
|
264
|
+
* polling, or process-group helpers to another box. */
|
|
265
|
+
private readonly retainedProcesses;
|
|
266
|
+
desktopInput?: (event: unknown) => Promise<void>;
|
|
267
|
+
screenshot?: () => Promise<{
|
|
268
|
+
png: Uint8Array;
|
|
269
|
+
width: number;
|
|
270
|
+
height: number;
|
|
271
|
+
nativeWidth: number;
|
|
272
|
+
nativeHeight: number;
|
|
273
|
+
}>;
|
|
274
|
+
constructor(deps: RoutingSandboxSessionDeps);
|
|
275
|
+
/**
|
|
276
|
+
* A method-free read of the active backend's `state` (best-effort: the last
|
|
277
|
+
* resolved backend, falling back to the default backend resolved at construction
|
|
278
|
+
* so this is non-empty BEFORE the first op). Consumers that read `session.state`
|
|
279
|
+
* (instanceId/decoration) get the active backend's state.
|
|
280
|
+
*
|
|
281
|
+
* CRITICAL: this returns the underlying backend's `state` OBJECT BY REFERENCE
|
|
282
|
+
* (never a fresh `{}` when a backend exists). The @openai/agents SDK both READS
|
|
283
|
+
* `session.state.manifest` and WRITES `session.state.manifest = nextManifest`
|
|
284
|
+
* (providedSessionManifest); returning the live object by reference means those
|
|
285
|
+
* property writes land on the real backend state and persist. Only when NO
|
|
286
|
+
* backend has been resolved yet (no default seeded, no op dispatched) do we
|
|
287
|
+
* return an empty object — and that path no longer occurs in the turn wiring,
|
|
288
|
+
* which always seeds `defaultResolved`.
|
|
289
|
+
*/
|
|
290
|
+
get state(): unknown;
|
|
291
|
+
/**
|
|
292
|
+
* Re-read the pointer and resolve the active backend, using the per-epoch cache.
|
|
293
|
+
* The cache is keyed by `activeEpoch`: if the epoch is unchanged we return the
|
|
294
|
+
* cached backend; if it moved (a swap) we re-resolve and update the cache. This
|
|
295
|
+
* is THE per-call re-read that makes a mid-turn swap land on the next op.
|
|
296
|
+
*/
|
|
297
|
+
private resolve;
|
|
298
|
+
private invalidate;
|
|
299
|
+
private registerRetainedProcess;
|
|
300
|
+
private retainedProcess;
|
|
301
|
+
/** Reconstruct one process that was already promoted durably by an earlier
|
|
302
|
+
* request. Adoption never reads the active pointer and never repeats parent
|
|
303
|
+
* promotion. It can bind only to the exact construction-time default backend;
|
|
304
|
+
* callers must reject non-default routes until they can independently rebuild
|
|
305
|
+
* that provider session from its copied durable identity. */
|
|
306
|
+
adoptRetainedProcess(input: RoutingRetainedProcessAdoption): void;
|
|
307
|
+
private ensureParentPromotion;
|
|
308
|
+
private confirmDurableRejectedPromotion;
|
|
309
|
+
private settleRetainedProcess;
|
|
310
|
+
private flushPendingProcessMutation;
|
|
311
|
+
private dispatchProcessMutation;
|
|
312
|
+
private dispatchProcessControl;
|
|
313
|
+
/**
|
|
314
|
+
* Dispatch an op to the currently-active backend, retrying on a stale-epoch
|
|
315
|
+
* fence. The sequence per attempt:
|
|
316
|
+
* 1. re-read the pointer + resolve the active backend (cached by epoch),
|
|
317
|
+
* 2. run `fn(activeSession)`,
|
|
318
|
+
* 3. on a FENCE error (the pointer moved under us / the backend rejected a
|
|
319
|
+
* stale epoch), INVALIDATE the cache and retry against the re-resolved
|
|
320
|
+
* active sandbox — up to `maxFenceRetries`.
|
|
321
|
+
* A non-fence error propagates immediately (it is a real op failure, not a swap
|
|
322
|
+
* race).
|
|
323
|
+
*/
|
|
324
|
+
private dispatch;
|
|
325
|
+
/**
|
|
326
|
+
* The failure-visibility boundary for the `exec_command` SDK capability tool — the
|
|
327
|
+
* dominant fault surface, and the one whose thrown `SelfhostedControlError` reaches
|
|
328
|
+
* the model wrapped by the SDK's generic tool-error function as
|
|
329
|
+
* "…Please try again. Error: …" — actively wrong for a machine that is offline, a
|
|
330
|
+
* consent that is not granted, or an oversized reply. Since the SDK closure-captures
|
|
331
|
+
* its `errorFunction` (there is no seam to attach one to its internally-built tools),
|
|
332
|
+
* we render the fault into the doctrine's four fields HERE and return it as the
|
|
333
|
+
* tool's string result — legible, in-band, and free of the misleading wrapper.
|
|
334
|
+
* (`apply_patch` already surfaces `error.message` via the SDK's own catch; the
|
|
335
|
+
* skills / `view_image` tools consume their session methods internally, so they are
|
|
336
|
+
* rendered elsewhere or left to their own SDK renderers — not this string boundary.)
|
|
337
|
+
*
|
|
338
|
+
* A FENCE error is re-thrown, NEVER rendered: `dispatch` already retries it against
|
|
339
|
+
* a re-resolved backend, and a fence that escapes retries is a routing condition the
|
|
340
|
+
* turn handles, not a model-facing fault. Any non-selfhosted error (a Modal fault, a
|
|
341
|
+
* `RoutingUnsupportedError`) is re-thrown unchanged.
|
|
342
|
+
*/
|
|
343
|
+
private renderSelfhostedFaultOrThrow;
|
|
344
|
+
exec(args: unknown): Promise<unknown>;
|
|
345
|
+
execCommand(args: unknown): Promise<string>;
|
|
346
|
+
writeStdin(args: unknown): Promise<string>;
|
|
347
|
+
/** Whether a positive provider session locator is still pinned to the exact
|
|
348
|
+
* backend that yielded it. This synchronous probe is used only to select the
|
|
349
|
+
* process-aware routing surface; it is not itself durable authority. */
|
|
350
|
+
hasRetainedProcess(providerSessionId: number): boolean;
|
|
351
|
+
/** Return only OpenGeni's durable UUID + provider locator. Backend/session
|
|
352
|
+
* objects remain private so a caller cannot forge route authority from this
|
|
353
|
+
* diagnostic handoff. */
|
|
354
|
+
retainedProcessIdentity(providerSessionId: number): RoutingRetainedProcess | null;
|
|
355
|
+
/** Model/user-visible stdin is a distinct workspace mutation admission under
|
|
356
|
+
* the durable retained-process holder. It never re-reads the active pointer. */
|
|
357
|
+
writeStdinForProcessMutation(args: unknown): Promise<string>;
|
|
358
|
+
/** Cancellation and drain polling are control operations. They stay pinned to
|
|
359
|
+
* the process backend and may prove terminal state, but never advance the
|
|
360
|
+
* workspace generation. */
|
|
361
|
+
writeStdinForProcessControl(args: unknown): Promise<string>;
|
|
362
|
+
/** Run a PID/PGID marker or signal helper on the retained process's exact
|
|
363
|
+
* backend. The helper is control-plane work and therefore has no workspace
|
|
364
|
+
* mutation admission of its own. */
|
|
365
|
+
execCommandForProcessControl(providerSessionId: number, args: unknown): Promise<string>;
|
|
366
|
+
cancelExecCommand(opId: string): Promise<boolean>;
|
|
367
|
+
readFile(args: unknown): Promise<string | Uint8Array>;
|
|
368
|
+
writeFile(args: unknown): Promise<unknown>;
|
|
369
|
+
listDir(args: unknown): Promise<unknown>;
|
|
370
|
+
pathExists(path: string, runAs?: string): Promise<boolean>;
|
|
371
|
+
viewImage(args: unknown): Promise<unknown>;
|
|
372
|
+
materializeEntry(args: unknown): Promise<void>;
|
|
373
|
+
/** PTY support reflects the LAST-resolved backend (a synchronous probe; the SDK
|
|
374
|
+
* reads it to decide if the terminal is interactive). It cannot re-read the
|
|
375
|
+
* pointer (synchronous), so it answers from the last resolve — coherent with
|
|
376
|
+
* the resolve the surrounding op already performed. Defaults false before the
|
|
377
|
+
* first resolve. */
|
|
378
|
+
supportsPty(): boolean;
|
|
379
|
+
/** createEditor is a synchronous factory in the SDK surface. The SDK's filesystem
|
|
380
|
+
* capability calls it ONCE at tool-BIND time — `FilesystemCapability.tools()`,
|
|
381
|
+
* every turn, before any tool runs — and throws "Filesystem sandbox sessions must
|
|
382
|
+
* provide createEditor()" if it returns falsy. When a backend is already resolved
|
|
383
|
+
* (eager/selfhosted routing) we bind to its editor directly, byte-for-byte as
|
|
384
|
+
* before. But under LAZY provisioning the backend is not established yet
|
|
385
|
+
* (defaultResolved is the synthetic unprovisioned session with no editor), so a
|
|
386
|
+
* direct delegate returns undefined and every lazy turn would die at bind. Return a
|
|
387
|
+
* LAZY EDITOR PROXY instead: a non-null editor whose async ops resolve the active
|
|
388
|
+
* backend (establishing the box on first use, via `dispatch`) and delegate to its
|
|
389
|
+
* real editor — mirroring how this proxy defers exec/readFile. Even when an
|
|
390
|
+
* eager editor already exists, returning it directly would bypass per-edit
|
|
391
|
+
* route resolution and mutation-generation admission, so every editor is
|
|
392
|
+
* represented by this dispatching proxy. */
|
|
393
|
+
createEditor(runAs?: string): unknown;
|
|
394
|
+
resolveExposedPort(port: number): Promise<ExposedPortEndpoint>;
|
|
395
|
+
/** Serialize the active backend's session state. Used by the resume-by-id seam
|
|
396
|
+
* to fold the live box onto the lease. Dispatches to the active backend. */
|
|
397
|
+
serializeSessionState(): Promise<unknown>;
|
|
398
|
+
/** Force a resolve (priming the proxy before the first op so `state`/`supportsPty`
|
|
399
|
+
* read a real backend). Optional — every op resolves lazily anyway. */
|
|
400
|
+
prime(): Promise<ResolvedActiveBackend>;
|
|
401
|
+
}
|