@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,552 @@
|
|
|
1
|
+
import { ControlResponse, type DesktopInputRequest } from "@opengeni/agent-proto";
|
|
2
|
+
import { Manifest } from "@openai/agents/sandbox";
|
|
3
|
+
import type { ExposedPortEndpoint } from "../stream-port";
|
|
4
|
+
import { type ControlRpc } from "./control-rpc";
|
|
5
|
+
import { type SelfhostedRetryClock } from "./retry-policy";
|
|
6
|
+
import type { SelfhostedOpObserver } from "./op-observer";
|
|
7
|
+
import { type OpStreamJournal } from "./op-stream";
|
|
8
|
+
import { type OpStreamTransport } from "./op-transport";
|
|
9
|
+
/** The V4A-diff applier the SDK's apply_patch editor uses. The leaf cannot import
|
|
10
|
+
* `@openai/agents`'s `applyDiff` (the agent-loop root the leaf forbids), so the
|
|
11
|
+
* runtime barrel (`packages/runtime/src/index.ts`, which DOES import that root)
|
|
12
|
+
* injects it via `setSelfhostedApplyDiff` at module load. Until injected,
|
|
13
|
+
* `createEditor()` surfaces a clear error rather than a silent wrong-edit. */
|
|
14
|
+
export type SelfhostedApplyDiff = (input: string, diff: string, mode?: "default" | "create") => string;
|
|
15
|
+
/** Register the SDK's `applyDiff` so `SelfhostedSession.createEditor()` can apply
|
|
16
|
+
* V4A diffs over the NATS fs ops. Called once by the runtime barrel. */
|
|
17
|
+
export declare function setSelfhostedApplyDiff(fn: SelfhostedApplyDiff): void;
|
|
18
|
+
/** The structural Editor surface the SDK's filesystem capability consumes (the
|
|
19
|
+
* three apply_patch operations). Mirrors `@openai/agents-core`'s `Editor`. */
|
|
20
|
+
export interface SelfhostedEditor {
|
|
21
|
+
createFile(operation: {
|
|
22
|
+
path: string;
|
|
23
|
+
diff: string;
|
|
24
|
+
}, context?: unknown): Promise<{
|
|
25
|
+
output?: string;
|
|
26
|
+
} | void>;
|
|
27
|
+
updateFile(operation: {
|
|
28
|
+
path: string;
|
|
29
|
+
diff: string;
|
|
30
|
+
moveTo?: string;
|
|
31
|
+
}, context?: unknown): Promise<{
|
|
32
|
+
output?: string;
|
|
33
|
+
} | void>;
|
|
34
|
+
deleteFile(operation: {
|
|
35
|
+
path: string;
|
|
36
|
+
}, context?: unknown): Promise<{
|
|
37
|
+
output?: string;
|
|
38
|
+
} | void>;
|
|
39
|
+
}
|
|
40
|
+
/** The image tool-output shape the SDK's view_image tool expects (mirror of
|
|
41
|
+
* `ToolOutputImage` — not re-exported by `@openai/agents/sandbox`, so structural). */
|
|
42
|
+
export interface SelfhostedImageOutput {
|
|
43
|
+
type: "image";
|
|
44
|
+
image: {
|
|
45
|
+
data: Uint8Array;
|
|
46
|
+
mediaType: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Default control-op timeout. A transient miss surfaces as `agent_reconnecting`
|
|
50
|
+
* (the turn pauses + retries); it is NOT a hard failure. */
|
|
51
|
+
export declare const SELFHOSTED_DEFAULT_TIMEOUT_MS = 30000;
|
|
52
|
+
/** The relay-URL shape config the session needs to build a stream endpoint. M8b
|
|
53
|
+
* wires the real relay deployment behind THIS seam so `buildStreamUrl` works
|
|
54
|
+
* unchanged behind `resolveExposedPort`. */
|
|
55
|
+
export interface SelfhostedRelayConfig {
|
|
56
|
+
/** The relay edge host (no scheme), e.g. "relay.opengeni.ai". */
|
|
57
|
+
host: string;
|
|
58
|
+
/** The relay port. Defaults to 443 (the relay terminates TLS). */
|
|
59
|
+
port?: number;
|
|
60
|
+
/** Whether the relay endpoint is TLS (wss/https). Defaults true. */
|
|
61
|
+
tls?: boolean;
|
|
62
|
+
/** The relay's stream-dial path (the `opengeni-relay` wss route). Defaults to
|
|
63
|
+
* "/stream" — the route the relay listens on (M8b). */
|
|
64
|
+
path?: string;
|
|
65
|
+
}
|
|
66
|
+
/** The relay's default wss dial path (the `opengeni-relay` server route). */
|
|
67
|
+
export declare const SELFHOSTED_RELAY_STREAM_PATH = "/stream";
|
|
68
|
+
/**
|
|
69
|
+
* The op-stream injection (op-stream protocol v1.1 — see op-stream.ts). PRESENT
|
|
70
|
+
* = the streaming exec transport is enabled for this session (the worker
|
|
71
|
+
* injects it only when the runner advertised `Capabilities.op_stream` AND the
|
|
72
|
+
* server flag is on — the leaf carries no flag logic). ABSENT = the legacy
|
|
73
|
+
* monolithic exec, byte-identical to before. The timing knobs exist for tests.
|
|
74
|
+
*/
|
|
75
|
+
export interface SelfhostedOpStreamDeps {
|
|
76
|
+
transport: OpStreamTransport;
|
|
77
|
+
/** The durable-resume journal (attach generation + settled-frontier persist).
|
|
78
|
+
* Absent (tests, non-activity callers) ⇒ generation "1", no persistence. */
|
|
79
|
+
journal?: OpStreamJournal;
|
|
80
|
+
windowBytes?: number;
|
|
81
|
+
ackIntervalMs?: number;
|
|
82
|
+
silenceTimeoutMs?: number;
|
|
83
|
+
reconnectHoldMs?: number;
|
|
84
|
+
}
|
|
85
|
+
export interface SelfhostedSessionDeps {
|
|
86
|
+
workspaceId: string;
|
|
87
|
+
agentId: string;
|
|
88
|
+
controlRpc: ControlRpc;
|
|
89
|
+
relay: SelfhostedRelayConfig;
|
|
90
|
+
/** Op-stream exec transport (present = enabled; see SelfhostedOpStreamDeps). */
|
|
91
|
+
opStream?: SelfhostedOpStreamDeps;
|
|
92
|
+
/** The lease/active epoch this session is fenced under (echoed on every
|
|
93
|
+
* ControlRequest so the agent can reject a stale op with ERROR_CODE_FENCED).
|
|
94
|
+
* Defaults to 0 (no fence) for the negotiation-only / test path. */
|
|
95
|
+
epoch?: number;
|
|
96
|
+
/** The CONTROL-op timeout (ping/fs/desktop/pty and the exec wire fallback). The
|
|
97
|
+
* agent's own control liveness must stay responsive, so this is short (the 30s
|
|
98
|
+
* default). Override for tests or per-deployment tuning. */
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
/**
|
|
101
|
+
* The EXEC process deadline — distinct from `timeoutMs`. A real command
|
|
102
|
+
* (compile, test run, install) routinely outlives the short control timeout, so
|
|
103
|
+
* exec gets its own, much larger budget: the agent kills the child at this wall,
|
|
104
|
+
* and the wire waits `execTimeoutMs + SELFHOSTED_EXEC_REPLY_GRACE_MS`. Omitted ⇒
|
|
105
|
+
* falls back to `timeoutMs` (byte-identical to before this split), so any caller
|
|
106
|
+
* that does not opt in keeps today's behavior.
|
|
107
|
+
*/
|
|
108
|
+
execTimeoutMs?: number;
|
|
109
|
+
/** The clock the bounded control-op retry loop drives (sleep + jitter). Injected
|
|
110
|
+
* so tests are deterministic; defaults to a real timer + `Math.random()`. */
|
|
111
|
+
retryClock?: SelfhostedRetryClock;
|
|
112
|
+
/** A fire-and-forget observer invoked once per completed control op (out-of-band
|
|
113
|
+
* telemetry — metrics + `machine.*` events). The worker/api wire it to the sinks;
|
|
114
|
+
* the leaf only invokes it (guarded so it can never break an op). Omitted ⇒ no-op. */
|
|
115
|
+
onOp?: SelfhostedOpObserver;
|
|
116
|
+
/**
|
|
117
|
+
* The run's declared sandbox environment — the SAME `Record<string,string>` the
|
|
118
|
+
* worker turn passes to `runtime.buildAgent`'s `sandboxEnvironment` (and that the
|
|
119
|
+
* agent's TARGET manifest, `buildManifest`, carries). The SDK injects this
|
|
120
|
+
* selfhosted session NON-OWNED and applies the agent's manifest as a provided-
|
|
121
|
+
* session delta; `validateNoEnvironmentDelta` throws "Live sandbox sessions cannot
|
|
122
|
+
* change manifest environment variables" on ANY env mismatch. So `state.manifest`'s
|
|
123
|
+
* `environment` MUST EQUAL the turn's environment for the delta to be empty. The
|
|
124
|
+
* selfhosted exec routes over NATS and does NOT consume this env wholesale (the
|
|
125
|
+
* machine owns its own shell), EXCEPT the allowlisted toolspace pointers it
|
|
126
|
+
* exports per exec so `ogtool` works — see SELFHOSTED_EXEC_ENV_ALLOWLIST. The
|
|
127
|
+
* manifest carries the full env for parity. Omitted → `{}` (the negotiation-only
|
|
128
|
+
* / test path, which never applies a turn manifest, so there is no delta).
|
|
129
|
+
*/
|
|
130
|
+
environment?: Record<string, string>;
|
|
131
|
+
/**
|
|
132
|
+
* The session's working directory — the BASE every path/cwd is rooted under (see
|
|
133
|
+
* `toMachinePath` / SELFHOSTED_VIRTUAL_ROOT). A launch-workspace_root-relative
|
|
134
|
+
* subdir (resolved under workspace_root by the agent's `resolve_cwd`) or an
|
|
135
|
+
* absolute machine path. Omitted/empty (the default) ⇒ "" ⇒ today's behavior
|
|
136
|
+
* exactly (an empty cwd lets the agent substitute its workspace_root).
|
|
137
|
+
*/
|
|
138
|
+
workingDir?: string;
|
|
139
|
+
}
|
|
140
|
+
/** The Channel-A `exec` result shape (a structural superset of the SDK's). */
|
|
141
|
+
export interface SelfhostedExecResult {
|
|
142
|
+
output: string;
|
|
143
|
+
stdout: string;
|
|
144
|
+
stderr: string;
|
|
145
|
+
exitCode: number | null;
|
|
146
|
+
/** True when the agent killed the child at the exec deadline. Additive to the
|
|
147
|
+
* Channel-A superset (consumers that don't read it are unaffected); `execCommand`
|
|
148
|
+
* reads it to surface the deadline hint on the stdout-only SDK path. */
|
|
149
|
+
timedOut?: boolean;
|
|
150
|
+
}
|
|
151
|
+
/** The `exec` args the structural surface accepts (mirrors ChannelAExecArgs). */
|
|
152
|
+
export interface SelfhostedExecArgs {
|
|
153
|
+
cmd: string;
|
|
154
|
+
workdir?: string | undefined;
|
|
155
|
+
shell?: string | undefined;
|
|
156
|
+
login?: boolean | undefined;
|
|
157
|
+
tty?: boolean | undefined;
|
|
158
|
+
runAs?: string | undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The persistable session state. For selfhosted this is `{agentId}` ONLY — there
|
|
162
|
+
* is NO provider box id, no snapshot, no manifest. Resume re-addresses the live
|
|
163
|
+
* subject; the machine itself is the persistence (`persistable:false`).
|
|
164
|
+
*/
|
|
165
|
+
export interface SelfhostedSessionState {
|
|
166
|
+
agentId: string;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A live selfhosted session — the structural `SandboxSessionLike` surface over a
|
|
170
|
+
* `ControlRpc`. Mirrors Modal's session shape so Channel-A/viewer/computer-use
|
|
171
|
+
* consume it unchanged.
|
|
172
|
+
*/
|
|
173
|
+
export declare class SelfhostedSession {
|
|
174
|
+
readonly backendId: "selfhosted";
|
|
175
|
+
readonly workspaceId: string;
|
|
176
|
+
readonly agentId: string;
|
|
177
|
+
private readonly controlRpc;
|
|
178
|
+
private readonly relay;
|
|
179
|
+
private readonly epoch;
|
|
180
|
+
private readonly timeoutMs;
|
|
181
|
+
/** The exec process deadline (falls back to `timeoutMs` when unset). */
|
|
182
|
+
private readonly execTimeoutMs;
|
|
183
|
+
private readonly retryClock;
|
|
184
|
+
private readonly onOp;
|
|
185
|
+
private readonly subject;
|
|
186
|
+
/** The op-stream exec client — constructed iff `deps.opStream` was injected
|
|
187
|
+
* (= the streaming transport is enabled for this session). */
|
|
188
|
+
private readonly opStreamClient;
|
|
189
|
+
/** The session working directory — the path/cwd base every op is rooted under
|
|
190
|
+
* (see `toMachinePath`). "" by default ⇒ today's workspace_root behavior. */
|
|
191
|
+
private readonly workingDir;
|
|
192
|
+
/**
|
|
193
|
+
* The structural `state` slice consumers read. `agentId`/`instanceId` serve the
|
|
194
|
+
* channel-a `readInstanceId` + docker-network decoration (the agentId IS the
|
|
195
|
+
* identity). `manifest` is the slice the @openai/agents SDK reads AND writes per
|
|
196
|
+
* turn (serializeManifestEnvironment / validateProvidedSessionManifestUpdate read
|
|
197
|
+
* `manifest.root` + iterate `manifest.environment`; providedSessionManifest WRITES
|
|
198
|
+
* `state.manifest = next`). It must be a real, MUTABLE Manifest field — when the
|
|
199
|
+
* RoutingSandboxSession proxy resolves THIS as the active backend it returns
|
|
200
|
+
* `session.state` BY REFERENCE, so the SDK's read and write must both land on a
|
|
201
|
+
* well-formed Manifest here (defined `root`, object `environment`). Without it the
|
|
202
|
+
* SDK crashes with `undefined is not an object (evaluating 'current.root')`.
|
|
203
|
+
*
|
|
204
|
+
* `manifest` is intentionally a plain mutable field (not `readonly`) so the SDK's
|
|
205
|
+
* `state.manifest = next` write succeeds. It is NOT part of the persistable state
|
|
206
|
+
* (`serializeSessionState` round-trips `{agentId}` only).
|
|
207
|
+
*
|
|
208
|
+
* `environment` is the SDK `SandboxSessionState.environment` (a `Record<string,
|
|
209
|
+
* string>`). It MUST be present because the GROUP box's client serializes THIS
|
|
210
|
+
* (the active backend's) state at end-of-turn — the non-owned injected session is
|
|
211
|
+
* serialized via the CONFIGURED client (modal in prod), NOT the selfhosted client.
|
|
212
|
+
* Modal's `serializeRemoteSandboxSessionState` does `Object.entries(state.environment)`;
|
|
213
|
+
* an absent field crashes the post-turn RunState serialize with "Object.entries
|
|
214
|
+
* requires that input parameter not be null or undefined". It carries the run's
|
|
215
|
+
* threaded environment (or `{}`). The resulting modal-tagged envelope is inert for
|
|
216
|
+
* selfhosted (resume re-addresses the machine by agentId via the lease pointer,
|
|
217
|
+
* never from this SDK envelope), so its only job is to not crash the serialize.
|
|
218
|
+
*/
|
|
219
|
+
readonly state: {
|
|
220
|
+
agentId: string;
|
|
221
|
+
instanceId: string;
|
|
222
|
+
manifest: Manifest;
|
|
223
|
+
environment: Record<string, string>;
|
|
224
|
+
};
|
|
225
|
+
constructor(deps: SelfhostedSessionDeps);
|
|
226
|
+
/** Invoke the injected per-op observer, guarded: a telemetry tap must NEVER break
|
|
227
|
+
* an op (a throwing sink is swallowed). No-op when no observer is wired. */
|
|
228
|
+
private emitOp;
|
|
229
|
+
/**
|
|
230
|
+
* Issue a control op, decoding the agent's reply or throwing the mapped
|
|
231
|
+
* `SelfhostedControlError` on an AgentError (incl. a synthesized offline /
|
|
232
|
+
* timeout error from the transport).
|
|
233
|
+
*
|
|
234
|
+
* Two failure modes are retried in-place (the decision is the pure
|
|
235
|
+
* `decideSelfhostedRetry` policy):
|
|
236
|
+
* - DRAINING — a pre-admission host-work backpressure rejection; the op never
|
|
237
|
+
* started, so it is safe to re-issue for ANY op kind (bounded).
|
|
238
|
+
* - a TIMEOUT / `agent_reconnecting` blip — re-issued ONCE, and ONLY for a
|
|
239
|
+
* read-only idempotent-safe op. A timed-out MUTATION is NEVER re-issued: it
|
|
240
|
+
* may already have executed on the machine (at-least-once), which would
|
|
241
|
+
* duplicate the write/command. FENCED is intentionally NOT retried here — the
|
|
242
|
+
* routing proxy retries it against a re-resolved backend under a fresh epoch.
|
|
243
|
+
* Each attempt carries a FRESH `requestId` (a retry is a distinct request).
|
|
244
|
+
*/
|
|
245
|
+
private call;
|
|
246
|
+
/** The effective clamped exec process deadline (ms): the exec-specific budget
|
|
247
|
+
* when threaded, else the control timeout. Public so adapters that project a
|
|
248
|
+
* structured command receipt can report the exact deadline the agent enforces
|
|
249
|
+
* instead of repeating (and potentially drifting from) this normalization. */
|
|
250
|
+
get effectiveExecDeadlineMs(): number;
|
|
251
|
+
/** Channel-A `exec`: run a command on the machine and return its output. */
|
|
252
|
+
exec(args: SelfhostedExecArgs): Promise<SelfhostedExecResult>;
|
|
253
|
+
/** The legacy monolithic exec request/reply — the permanent fallback wire
|
|
254
|
+
* form (and the only form for runners without `op_stream`). */
|
|
255
|
+
private execLegacy;
|
|
256
|
+
/**
|
|
257
|
+
* The op-stream exec path: the SAME `ExecRequest`, streamed (see
|
|
258
|
+
* op-stream.ts). The durable op id comes from the tool-call correlation
|
|
259
|
+
* context (`{callId}:{ordinal}` — B1) when this exec runs inside an SDK tool
|
|
260
|
+
* invocation; a non-tool caller falls back to a random unique id (never
|
|
261
|
+
* collides, merely not stable across a turn re-dispatch). Emits the SAME
|
|
262
|
+
* per-op observation the legacy path does, with `replyBytes` filled from the
|
|
263
|
+
* reassembled stream (the field the framed transport was designed to own).
|
|
264
|
+
*/
|
|
265
|
+
private execViaOpStream;
|
|
266
|
+
/**
|
|
267
|
+
* The turn-end op-stream durability hook: persists each settled op's frontier
|
|
268
|
+
* to the journal, then final-acks it on the wire (licensing the runner to GC
|
|
269
|
+
* its retained frames). The WORKER calls this after the turn's results are
|
|
270
|
+
* durably recorded — never mid-turn (the durable-before-wire-ack ordering).
|
|
271
|
+
* A no-op for sessions without the op-stream transport.
|
|
272
|
+
*/
|
|
273
|
+
finalizeOpStreamOps(): Promise<void>;
|
|
274
|
+
/** SDK shell capability `execCommand`: run a command and return its stdout (the
|
|
275
|
+
* `exec_command` tool). Selfhosted exec is non-interactive (no PTY) — `tty` is
|
|
276
|
+
* ignored; `supportsPty()` is false so the SDK never offers a stdin session.
|
|
277
|
+
*
|
|
278
|
+
* On a DEADLINE timeout the SDK path is stdout-only (the model sees `result.output`
|
|
279
|
+
* and nothing else), so the deadline hint that `exec()` puts on stderr would be
|
|
280
|
+
* invisible — a >deadline command would return `{"output":""}` and the model would
|
|
281
|
+
* conclude "no output" with no idea it was killed. So when the result timed out we
|
|
282
|
+
* surface the hint HERE, on stdout: the hint alone when stdout is empty, or appended
|
|
283
|
+
* after a newline when there is partial output (a timed-out result is already not
|
|
284
|
+
* cleanly parseable — silence is worse than an explanatory suffix). The structured
|
|
285
|
+
* `exec()` result is left untouched for the Channel-A parsers. */
|
|
286
|
+
execCommand(args: {
|
|
287
|
+
cmd: string;
|
|
288
|
+
workdir?: string;
|
|
289
|
+
runAs?: string;
|
|
290
|
+
}): Promise<string>;
|
|
291
|
+
/**
|
|
292
|
+
* Physical turn-cancellation seam used by the runtime tool fence. Only the
|
|
293
|
+
* op-stream transport has a process-tree cancellation protocol; a legacy
|
|
294
|
+
* runner returns false and the caller remains fail-closed until its exec
|
|
295
|
+
* deadline rather than claiming the machine is quiescent.
|
|
296
|
+
*/
|
|
297
|
+
cancelExecCommand(opId: string): Promise<boolean>;
|
|
298
|
+
/** SDK shell capability never calls this (gated on `supportsPty()` which is
|
|
299
|
+
* false), but the surface advertises it. Selfhosted exec has no interactive PTY
|
|
300
|
+
* session over the structured RPC, so a stdin write is unsupported. */
|
|
301
|
+
supportsPty(): boolean;
|
|
302
|
+
/** SDK filesystem capability `view_image`: read the image bytes off the machine
|
|
303
|
+
* and wrap them in the tool-output image shape (magic-byte sniff + path fallback,
|
|
304
|
+
* mirroring the SDK's `imageOutputFromBytes`). */
|
|
305
|
+
viewImage(args: {
|
|
306
|
+
path: string;
|
|
307
|
+
runAs?: string;
|
|
308
|
+
}): Promise<SelfhostedImageOutput>;
|
|
309
|
+
/** SDK skills/filesystem `pathExists`: whether a path exists on the machine. */
|
|
310
|
+
pathExists(path: string, _runAs?: string): Promise<boolean>;
|
|
311
|
+
/** SDK skills `listDir`: list a directory as `{name, path, type}[]`. */
|
|
312
|
+
listDir(args: {
|
|
313
|
+
path: string;
|
|
314
|
+
runAs?: string;
|
|
315
|
+
}): Promise<Array<{
|
|
316
|
+
name: string;
|
|
317
|
+
path: string;
|
|
318
|
+
type: "file" | "dir" | "other";
|
|
319
|
+
}>>;
|
|
320
|
+
/** SDK manifest-delta `materializeEntry`: a NO-OP for selfhosted. Source
|
|
321
|
+
* materialization (cloning repos / staging files into the box) is how cloud
|
|
322
|
+
* providers prepare a fresh box; a bring-your-own machine already owns its
|
|
323
|
+
* filesystem and is prepared by the agent itself, so there is nothing to stage.
|
|
324
|
+
* Present (not absent) so the SDK's provided-session manifest apply path — which
|
|
325
|
+
* requires `applyManifest()` OR `materializeEntry()` when the agent declares
|
|
326
|
+
* entries — is satisfied without error. The selfhosted manifest declares no
|
|
327
|
+
* entries, so in practice this is never invoked with a real entry. */
|
|
328
|
+
materializeEntry(_args: {
|
|
329
|
+
path: string;
|
|
330
|
+
entry: unknown;
|
|
331
|
+
runAs?: string;
|
|
332
|
+
}): Promise<void>;
|
|
333
|
+
/** SDK filesystem capability `createEditor`: the apply_patch host. Applies V4A
|
|
334
|
+
* diffs over the NATS fs ops (read → applyDiff → write). `applyDiff` is the SDK's
|
|
335
|
+
* own parser, injected by the runtime barrel (the leaf cannot import it). */
|
|
336
|
+
createEditor(runAs?: string): SelfhostedEditor;
|
|
337
|
+
/** Channel-A `readFile`: read a file off the machine (binary-safe). */
|
|
338
|
+
readFile(args: {
|
|
339
|
+
path: string;
|
|
340
|
+
runAs?: string;
|
|
341
|
+
maxBytes?: number;
|
|
342
|
+
}): Promise<Uint8Array>;
|
|
343
|
+
/** Write a file onto the machine (the fs surface the descriptor advertises). */
|
|
344
|
+
writeFile(args: {
|
|
345
|
+
path: string;
|
|
346
|
+
content: string | Uint8Array;
|
|
347
|
+
createParents?: boolean;
|
|
348
|
+
append?: boolean;
|
|
349
|
+
}): Promise<number>;
|
|
350
|
+
/** List a directory on the machine. */
|
|
351
|
+
listFiles(args: {
|
|
352
|
+
path: string;
|
|
353
|
+
recursive?: boolean;
|
|
354
|
+
}): Promise<NonNullable<ControlResponse["result"]> & {
|
|
355
|
+
$case: "fsList";
|
|
356
|
+
}>;
|
|
357
|
+
/** Stat a path on the machine. */
|
|
358
|
+
statFile(args: {
|
|
359
|
+
path: string;
|
|
360
|
+
}): Promise<{
|
|
361
|
+
exists: boolean;
|
|
362
|
+
}>;
|
|
363
|
+
/** Computer-use WRITE op: inject one synthetic desktop input event (pointer/key/
|
|
364
|
+
* scroll) on the machine's OWN display. The agent injects via CGEvent (macOS) /
|
|
365
|
+
* XTEST (Linux) and CONSENT-GATES it — an unconsented call never touches the OS
|
|
366
|
+
* and surfaces the mapped control error (ERROR_CODE_CONSENT_REQUIRED) via `call()`. */
|
|
367
|
+
desktopInput(event: DesktopInputRequest["event"]): Promise<void>;
|
|
368
|
+
/** Computer-use VIEW op: capture a single PNG screenshot of the machine's desktop
|
|
369
|
+
* plus its geometry (via ScreenCaptureKit / x11). NOT consent-gated (a view op —
|
|
370
|
+
* the view/control decoupling), so it works with a display but no screen-control
|
|
371
|
+
* consent. Returns the raw encoded bytes + the ENCODED width/height, plus the
|
|
372
|
+
* NATIVE (pre-downscale) geometry: when the agent had to downscale the PNG to fit
|
|
373
|
+
* the transport's max payload, `nativeWidth`/`nativeHeight` carry the original
|
|
374
|
+
* capture size so the computer-use layer can scale model clicks (in encoded-pixel
|
|
375
|
+
* space) back to native pixels. An older agent leaves them 0 → read as "same as
|
|
376
|
+
* width/height" (no downscale). */
|
|
377
|
+
screenshot(): Promise<{
|
|
378
|
+
png: Uint8Array;
|
|
379
|
+
width: number;
|
|
380
|
+
height: number;
|
|
381
|
+
nativeWidth: number;
|
|
382
|
+
nativeHeight: number;
|
|
383
|
+
}>;
|
|
384
|
+
/** A cheap liveness probe — request a Ping on the subject; returns true iff a
|
|
385
|
+
* responder answered (no AgentError). Used by `negotiateSelfhostedCapabilities`.
|
|
386
|
+
* The wire `nonce` is a uint64 (a numeric string), so the default is a random
|
|
387
|
+
* numeric value — NOT a UUID (which would fail proto uint64 encoding). */
|
|
388
|
+
ping(nonce?: string): Promise<boolean>;
|
|
389
|
+
/**
|
|
390
|
+
* Resolve an exposed port to a relay stream endpoint (the viewer/pty plane).
|
|
391
|
+
* Returns the relay URL SHAPE — `{host:relay, port, tls, query:channel-key}` —
|
|
392
|
+
* after asking the agent to ensure a stream channel for the port. M8b wires the
|
|
393
|
+
* real relay tier (the byte pump) behind THIS seam.
|
|
394
|
+
*
|
|
395
|
+
* THE CHANNEL-KEY QUERY (the M8b relay-dial contract): the relay
|
|
396
|
+
* routes by `{workspaceId, agentId, port}` — the EXACT `ChannelKey::query` the
|
|
397
|
+
* agent's relay client (`opengeni-agent-stream`) appends when it registers the
|
|
398
|
+
* producer side: `ws=<workspaceId>&agent=<agentId>&port=<port>`. We append the
|
|
399
|
+
* agent-registered `channel=<channelId>` as a correlation hint. So the viewer
|
|
400
|
+
* dials `wss://<relay>/stream?ws=&agent=&port=&channel=` and presents the minted
|
|
401
|
+
* `ogs_` token in-band (NEVER as a URL param) — the relay pairs it with the
|
|
402
|
+
* producer by the routing key.
|
|
403
|
+
*/
|
|
404
|
+
resolveExposedPort(port: number): Promise<ExposedPortEndpoint>;
|
|
405
|
+
/** Round-trip the persistable state — `{agentId}` ONLY (resume = re-address). */
|
|
406
|
+
serializeSessionState(): Promise<SelfhostedSessionState>;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* The selfhosted SDK-client surface the registry builds. `backendId:"selfhosted"`
|
|
410
|
+
* (the resume-fence field asserted against the descriptor). `create()`/`resume()`
|
|
411
|
+
* return a `SelfhostedSession` bound to `{workspaceId, agentId, controlRpc}`.
|
|
412
|
+
*
|
|
413
|
+
* `create()` and `resume()` are IDENTICAL for selfhosted — there is no box to
|
|
414
|
+
* provision (the machine already exists); both just bind a session to the live
|
|
415
|
+
* subject. `serializeSessionState`/`deserializeSessionState` round-trip
|
|
416
|
+
* `{agentId}` only.
|
|
417
|
+
*
|
|
418
|
+
* The `controlRpc` is constructed LAZILY via an injected factory (defaulting to
|
|
419
|
+
* `NatsControlRpc`); a session built before NATS is configured surfaces
|
|
420
|
+
* `agent_offline` on its first op rather than failing at construction.
|
|
421
|
+
*/
|
|
422
|
+
export declare class SelfhostedSandboxClient {
|
|
423
|
+
readonly backendId: "selfhosted";
|
|
424
|
+
readonly supportsDefaultOptions = false;
|
|
425
|
+
private readonly workspaceId;
|
|
426
|
+
private readonly relay;
|
|
427
|
+
private readonly controlRpcFactory;
|
|
428
|
+
private readonly defaultAgentId;
|
|
429
|
+
private readonly epoch;
|
|
430
|
+
private readonly timeoutMs;
|
|
431
|
+
private readonly execTimeoutMs;
|
|
432
|
+
private readonly environment;
|
|
433
|
+
private readonly workingDir;
|
|
434
|
+
private readonly onOp;
|
|
435
|
+
private readonly opStream;
|
|
436
|
+
private controlRpcMemo;
|
|
437
|
+
constructor(opts: {
|
|
438
|
+
workspaceId: string;
|
|
439
|
+
relay: SelfhostedRelayConfig;
|
|
440
|
+
/** Lazily build the ControlRpc (defaults to NatsControlRpc in the provider). */
|
|
441
|
+
controlRpcFactory: () => ControlRpc;
|
|
442
|
+
/** The agentId a bare create()/resume() (no state) binds to. Optional: the
|
|
443
|
+
* resume path supplies it via deserializeSessionState. */
|
|
444
|
+
agentId?: string;
|
|
445
|
+
epoch?: number;
|
|
446
|
+
/** The control-op timeout threaded into every bound session. */
|
|
447
|
+
timeoutMs?: number;
|
|
448
|
+
/** The exec process deadline threaded into every bound session (distinct from
|
|
449
|
+
* `timeoutMs`; falls back to it when unset). See SelfhostedSessionDeps.execTimeoutMs. */
|
|
450
|
+
execTimeoutMs?: number;
|
|
451
|
+
/** The run's declared sandbox environment, threaded into every bound session's
|
|
452
|
+
* `state.manifest.environment` so the SDK's per-turn manifest-env delta is
|
|
453
|
+
* empty (validateNoEnvironmentDelta). See SelfhostedSessionDeps.environment.
|
|
454
|
+
* Omitted → `{}` (the negotiation-only path; no turn manifest is applied). */
|
|
455
|
+
environment?: Record<string, string>;
|
|
456
|
+
/** The session working directory threaded into every bound session (the path/
|
|
457
|
+
* cwd base; see SelfhostedSessionDeps.workingDir). Omitted/empty ⇒ the default
|
|
458
|
+
* workspace_root behavior. */
|
|
459
|
+
workingDir?: string;
|
|
460
|
+
/** The per-op observer threaded into every bound session (out-of-band telemetry). */
|
|
461
|
+
onOp?: SelfhostedOpObserver;
|
|
462
|
+
/** The op-stream exec transport threaded into every bound session (present
|
|
463
|
+
* = enabled; see SelfhostedOpStreamDeps). */
|
|
464
|
+
opStream?: SelfhostedOpStreamDeps;
|
|
465
|
+
});
|
|
466
|
+
private controlRpc;
|
|
467
|
+
private bind;
|
|
468
|
+
/** Bind a session to the live agent subject. There is no box to provision. */
|
|
469
|
+
create(_manifest?: unknown, _options?: unknown): Promise<SelfhostedSession>;
|
|
470
|
+
/** Resume = re-address the subject. Identical to create — no provider state. */
|
|
471
|
+
resume(state: SelfhostedSessionState | Record<string, unknown>, _options?: unknown): Promise<SelfhostedSession>;
|
|
472
|
+
/** Serialize a live session's state → `{agentId}` ONLY. */
|
|
473
|
+
serializeSessionState(state: SelfhostedSessionState | {
|
|
474
|
+
agentId?: string;
|
|
475
|
+
} | unknown): Promise<SelfhostedSessionState>;
|
|
476
|
+
/** Deserialize `{agentId}` from the persisted envelope. */
|
|
477
|
+
deserializeSessionState(state: Record<string, unknown>): Promise<SelfhostedSessionState>;
|
|
478
|
+
/** selfhosted is NOT persistable — there is no owned session state to preserve
|
|
479
|
+
* (the machine is the persistence). The lease never snapshots it. */
|
|
480
|
+
canPersistOwnedSessionState(): Promise<boolean>;
|
|
481
|
+
private requireAgentId;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* The dependency shape `buildSelfhostedBackendSession` needs to bind a live
|
|
485
|
+
* selfhosted session to a target machine. A structural superset of the fields the
|
|
486
|
+
* routing resolver (backend-resolver.ts) reads off its deps + pointer, and the
|
|
487
|
+
* fields the WORKER turn's machine-primary establish branch threads in — so a
|
|
488
|
+
* SINGLE build shape is shared by both (never two divergent constructions of the
|
|
489
|
+
* same SelfhostedSandboxClient/resume pair).
|
|
490
|
+
*/
|
|
491
|
+
export interface SelfhostedSessionBuild {
|
|
492
|
+
/** The workspace the machine's control-plane subject is scoped to. */
|
|
493
|
+
workspaceId: string;
|
|
494
|
+
/** The enrollment id == the agent id `agent.<ws>.<id>.rpc` addresses. */
|
|
495
|
+
agentId: string;
|
|
496
|
+
/** The relay-URL shape for stream endpoints. */
|
|
497
|
+
relay: SelfhostedRelayConfig;
|
|
498
|
+
/** Lazily build the live ControlRpc (the request-scoped NATS connection). */
|
|
499
|
+
controlRpcFactory: () => ControlRpc;
|
|
500
|
+
/** The lease/active epoch the session is fenced under (echoed on every op). */
|
|
501
|
+
epoch: number;
|
|
502
|
+
/** The run's declared sandbox environment → the session manifest.environment
|
|
503
|
+
* (env-parity; see SelfhostedSessionDeps.environment). */
|
|
504
|
+
environment?: Record<string, string>;
|
|
505
|
+
/** The session working directory (the path/cwd base). Null/absent ⇒ workspace_root. */
|
|
506
|
+
workingDir?: string | null;
|
|
507
|
+
/** The control-op timeout (ping/fs/desktop/pty). Absent ⇒ the 30s default. */
|
|
508
|
+
timeoutMs?: number;
|
|
509
|
+
/** The exec process deadline, distinct from `timeoutMs`. Absent ⇒ falls back to
|
|
510
|
+
* `timeoutMs` (a real turn threads the long exec budget here). */
|
|
511
|
+
execTimeoutMs?: number;
|
|
512
|
+
/** The per-op observer (out-of-band telemetry). Absent ⇒ no-op. */
|
|
513
|
+
onOp?: SelfhostedOpObserver;
|
|
514
|
+
/** The op-stream exec transport (present = enabled for the session; the
|
|
515
|
+
* worker injects it only when the runner advertised the capability AND the
|
|
516
|
+
* server flag is on). Absent ⇒ the legacy exec, byte-identical to before. */
|
|
517
|
+
opStream?: SelfhostedOpStreamDeps;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Build a live selfhosted session bound to a target machine: construct a request-
|
|
521
|
+
* scoped `SelfhostedSandboxClient` (fenced under `epoch`, carrying the run's env +
|
|
522
|
+
* working dir) and `resume()` it (= re-address the live subject — no provider box
|
|
523
|
+
* is created). Returns BOTH the client (the OWNED-sandbox client the turn injects,
|
|
524
|
+
* whose `serializeSessionState` round-trips `{agentId}`) and the live session.
|
|
525
|
+
*
|
|
526
|
+
* Shared by:
|
|
527
|
+
* - the routing resolver (backend-resolver.ts) — a swap target, where only the
|
|
528
|
+
* session is needed; and
|
|
529
|
+
* - the worker turn's machine-primary establish branch — where the client is the
|
|
530
|
+
* owned-sandbox client AND the session is the pinned routing default.
|
|
531
|
+
* Factoring it here keeps the two builds identical (no divergence in the fence
|
|
532
|
+
* epoch, env threading, or working-dir base).
|
|
533
|
+
*/
|
|
534
|
+
export declare function buildSelfhostedBackendSession(deps: SelfhostedSessionBuild): Promise<{
|
|
535
|
+
client: SelfhostedSandboxClient;
|
|
536
|
+
session: SelfhostedSession;
|
|
537
|
+
}>;
|
|
538
|
+
/**
|
|
539
|
+
* The selfhosted NotFound discriminator — THE load-bearing safety property
|
|
540
|
+
*: for selfhosted, `agent-offline` (no responder) is NEVER a
|
|
541
|
+
* provider NotFound. A user's real machine is not recreatable; if the lease saw
|
|
542
|
+
* agent-offline as NotFound it would cold-create a RIVAL box (a Modal box) for
|
|
543
|
+
* the user's machine. So this ALWAYS returns FALSE for selfhosted — there is no
|
|
544
|
+
* "box gone, recreate it" condition. An OS-level file NotFound is an op-level
|
|
545
|
+
* error the fs layer 404s; it is likewise NOT a session-recreate condition.
|
|
546
|
+
*
|
|
547
|
+
* `establishSandboxSessionFromEnvelope` cold-restores ONLY when the per-backend
|
|
548
|
+
* NotFound discriminator returns true; returning false here guarantees the
|
|
549
|
+
* selfhosted path never cold-creates a rival — the op surfaces agent_offline and
|
|
550
|
+
* the caller backs off / retries.
|
|
551
|
+
*/
|
|
552
|
+
export declare function isSelfhostedProviderNotFoundError(_error: unknown): false;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ControlRequest, ControlResponse, type ExecRequest, type ExecResponse } from "@opengeni/agent-proto";
|
|
2
|
+
import type { ControlRpc } from "./control-rpc";
|
|
3
|
+
/** A pluggable exec handler — given an ExecRequest, return an ExecResponse (or
|
|
4
|
+
* throw to surface a synthesized error). Defaults to a trivial echo. */
|
|
5
|
+
export type MockExecHandler = (req: ExecRequest) => ExecResponse | Promise<ExecResponse>;
|
|
6
|
+
export interface MockAgentResponderOptions {
|
|
7
|
+
/** Whether a responder exists at all. When false EVERY request yields an
|
|
8
|
+
* AGENT_OFFLINE error (the "machine is offline" condition) — used to drive the
|
|
9
|
+
* agent_offline capability + the isProviderSandboxNotFoundError test. */
|
|
10
|
+
online?: boolean;
|
|
11
|
+
/** Whether the agent has acknowledged whole-machine / screen-control consent.
|
|
12
|
+
* When false, an op gated on consent yields CONSENT_REQUIRED. Defaults true. */
|
|
13
|
+
consented?: boolean;
|
|
14
|
+
/** Force the agent into a draining posture (every op → DRAINING). */
|
|
15
|
+
draining?: boolean;
|
|
16
|
+
/** Seed files (path → string|Uint8Array) into the virtual filesystem. */
|
|
17
|
+
files?: Record<string, string | Uint8Array>;
|
|
18
|
+
/** A custom exec handler; defaults to an echo of argv. */
|
|
19
|
+
exec?: MockExecHandler;
|
|
20
|
+
/** The hostname the mock reports (so PTY/exec `$HOSTNAME`-style asserts work). */
|
|
21
|
+
hostname?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* An in-process `ControlRpc` answering the agent op table against an in-memory
|
|
25
|
+
* virtual filesystem. Drive a `SelfhostedSession` with this to test exec /
|
|
26
|
+
* readFile / writeFile / list / stat round-trips without any NATS.
|
|
27
|
+
*/
|
|
28
|
+
export declare class MockAgentResponder implements ControlRpc {
|
|
29
|
+
private online;
|
|
30
|
+
private readonly consented;
|
|
31
|
+
private readonly draining;
|
|
32
|
+
private readonly files;
|
|
33
|
+
private readonly execHandler;
|
|
34
|
+
readonly hostname: string;
|
|
35
|
+
/** Every request seen, for assertion (subject + decoded ControlRequest). */
|
|
36
|
+
readonly requests: Array<{
|
|
37
|
+
subject: string;
|
|
38
|
+
req: ControlRequest;
|
|
39
|
+
}>;
|
|
40
|
+
constructor(opts?: MockAgentResponderOptions);
|
|
41
|
+
/** Flip the responder offline mid-test (a deliberate stop / blip). */
|
|
42
|
+
setOnline(online: boolean): void;
|
|
43
|
+
/** Read a file the session wrote (test assertion helper). */
|
|
44
|
+
fileText(path: string): string | undefined;
|
|
45
|
+
request(subject: string, req: ControlRequest, _opts: {
|
|
46
|
+
timeoutMs: number;
|
|
47
|
+
}): Promise<ControlResponse>;
|
|
48
|
+
}
|