@opengeni/runtime 0.14.1 → 0.14.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{chunk-4YJ5Z52Z.js → chunk-7E2ZSCZK.js} +6 -6
  2. package/dist/{chunk-4YJ5Z52Z.js.map → chunk-7E2ZSCZK.js.map} +1 -1
  3. package/dist/{chunk-HHWIZHQM.js → chunk-GNEGANUF.js} +942 -300
  4. package/dist/chunk-GNEGANUF.js.map +1 -0
  5. package/dist/codex-tool-search.d.ts +100 -0
  6. package/dist/context-compaction.d.ts +236 -0
  7. package/dist/history-sanitizer.d.ts +320 -0
  8. package/dist/index.d.ts +186 -718
  9. package/dist/index.js +612 -126
  10. package/dist/index.js.map +1 -1
  11. package/dist/{localSources-T3HZ6KY4.js → localSources-BXHBKGF4.js} +2 -2
  12. package/dist/mcp-network.d.ts +32 -35
  13. package/dist/metrics.d.ts +33 -0
  14. package/dist/sandbox/capabilities.d.ts +11 -0
  15. package/dist/sandbox/channel-a.d.ts +226 -0
  16. package/dist/sandbox/display-stack.d.ts +99 -0
  17. package/dist/sandbox/errors.d.ts +16 -0
  18. package/dist/sandbox/exec-banner.d.ts +19 -0
  19. package/dist/sandbox/index.d.ts +190 -6
  20. package/dist/sandbox/index.js +20 -4
  21. package/dist/sandbox/op-correlation.d.ts +24 -0
  22. package/dist/sandbox/provider-errors.d.ts +32 -0
  23. package/dist/sandbox/provider-operation-gate.d.ts +12 -0
  24. package/dist/sandbox/providers/blaxel.d.ts +2 -0
  25. package/dist/sandbox/providers/cloudflare.d.ts +2 -0
  26. package/dist/sandbox/providers/daytona.d.ts +2 -0
  27. package/dist/sandbox/providers/docker.d.ts +2 -0
  28. package/dist/sandbox/providers/e2b.d.ts +2 -0
  29. package/dist/sandbox/providers/index.d.ts +11 -0
  30. package/dist/sandbox/providers/local.d.ts +2 -0
  31. package/dist/sandbox/providers/modal.d.ts +132 -0
  32. package/dist/sandbox/providers/none.d.ts +2 -0
  33. package/dist/sandbox/providers/runloop.d.ts +2 -0
  34. package/dist/sandbox/providers/selfhosted.d.ts +2 -0
  35. package/dist/sandbox/providers/types.d.ts +30 -0
  36. package/dist/sandbox/providers/vercel.d.ts +2 -0
  37. package/dist/sandbox/recording.d.ts +80 -0
  38. package/dist/sandbox/routing/backend-resolver.d.ts +173 -0
  39. package/dist/sandbox/routing/routing-session.d.ts +401 -0
  40. package/dist/sandbox/run-credentials.d.ts +65 -0
  41. package/dist/sandbox/select.d.ts +120 -0
  42. package/dist/sandbox/selfhosted/capabilities.d.ts +91 -0
  43. package/dist/sandbox/selfhosted/control-rpc.d.ts +175 -0
  44. package/dist/sandbox/selfhosted/fault-rendering.d.ts +40 -0
  45. package/dist/sandbox/selfhosted/op-observer.d.ts +40 -0
  46. package/dist/sandbox/selfhosted/op-stream.d.ts +104 -0
  47. package/dist/sandbox/selfhosted/op-testing.d.ts +108 -0
  48. package/dist/sandbox/selfhosted/op-transport.d.ts +60 -0
  49. package/dist/sandbox/selfhosted/retry-policy.d.ts +106 -0
  50. package/dist/sandbox/selfhosted/session.d.ts +552 -0
  51. package/dist/sandbox/selfhosted/testing.d.ts +48 -0
  52. package/dist/sandbox/stream-port.d.ts +80 -0
  53. package/dist/sandbox/stream-token.d.ts +44 -0
  54. package/dist/sandbox/terminal-server.d.ts +67 -0
  55. package/dist/sandbox/toolspace-token.d.ts +23 -0
  56. package/dist/sandbox/turn-tool-cancellation.d.ts +72 -0
  57. package/dist/sandbox/workspace-archive.d.ts +39 -0
  58. package/dist/sandbox-computer.d.ts +227 -0
  59. package/dist/screenshot-error-card.d.ts +1 -0
  60. package/dist/skill-library.d.ts +10 -12
  61. package/dist/usage-telemetry.d.ts +45 -0
  62. package/dist/workspace-skills.d.ts +31 -0
  63. package/package.json +7 -7
  64. package/src/context-compaction.ts +600 -6
  65. package/src/history-sanitizer.ts +10 -92
  66. package/src/index.ts +301 -62
  67. package/src/sandbox/channel-a.ts +267 -81
  68. package/src/sandbox/index.ts +56 -94
  69. package/src/sandbox/provider-operation-gate.ts +110 -0
  70. package/src/sandbox/providers/docker.ts +13 -1
  71. package/src/sandbox/providers/modal.ts +433 -3
  72. package/src/sandbox/routing/routing-session.ts +84 -8
  73. package/src/sandbox/selfhosted/session.ts +7 -6
  74. package/src/sandbox/turn-tool-cancellation.ts +47 -7
  75. package/src/sandbox/workspace-archive.ts +176 -73
  76. package/src/sandbox-computer.ts +25 -75
  77. package/dist/chunk-HHWIZHQM.js.map +0 -1
  78. package/dist/index-BraazrAO.d.ts +0 -2931
  79. /package/dist/{localSources-T3HZ6KY4.js.map → localSources-BXHBKGF4.js.map} +0 -0
@@ -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
+ }