@workerdeck/server 0.12.0 → 0.15.0

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/README.md CHANGED
@@ -186,6 +186,32 @@ services and any unattended use. Without it the server logs a one-time notice in
186
186
  OAuth, never reads or forwards tokens — see the repo README's
187
187
  ["Auth & Anthropic's terms"](https://github.com/workerdeck/workerdeck#auth--anthropics-terms).
188
188
 
189
+ ## Rules you cannot infer from the types
190
+
191
+ - **A scope miss answers 404, never 403.** Whether a session exists in another scope is not the
192
+ caller's business, so the out-of-scope answer is byte-identical to the unknown-id one.
193
+ - **Visibility is full control.** There is no read-only attach: a client that can see a session can
194
+ send `user_message`, `permission_decision`, `interrupt` and `close`. `SessionPanel`'s `readOnly`
195
+ removes the affordance, not the authority — enforce at the gateway or not at all.
196
+ - **`authorizeSession` is synchronous on purpose.** It runs per route and per row of every list. An
197
+ expensive lookup belongs in `authenticate`, where it happens once and lands on the principal.
198
+ - **`sandboxedProviderProfile()`'s empty arrays are load-bearing.** `capabilities: []` and
199
+ `mcpServers: []` mean "nothing"; *absent* means "whatever the host wired". Do not normalise one
200
+ into the other.
201
+ - **A Claude profile does not pin `CLAUDE_CONFIG_DIR` when that would be a no-op.** Setting the
202
+ variable at all moves the CLI off the macOS Keychain, so pinning the default directory breaks a
203
+ working `claude login`. "Pin the default dir" and "don't pin" are different logins.
204
+ - **`checkCredentials` is display-only** unless you also set `requireAvailableProfile`. A create
205
+ against an unavailable profile otherwise proceeds and fails with the engine's own error — right
206
+ for an operator (the probe can be stale), wrong in front of an end user.
207
+ - **`createEngineRunner` has four invisible obligations**: forward `restore`, adopt `id`, seed the
208
+ VFS only when *not* restoring, and dispose per-session resources via `onClose`. Every one is a
209
+ runtime-only failure. `createProviderRunner()` does all four; reach for the raw hook only when it
210
+ genuinely doesn't fit.
211
+ - **One origin is not a convenience.** A browser cannot put an `Authorization` header on a
212
+ WebSocket upgrade, so a cookie is the only credential a tab can present on an attach, and a
213
+ cookie is per-origin. That is what `fallback` is for — an app served from the gateway's own port.
214
+
189
215
  ## License
190
216
 
191
217
  MIT © Tobias Strebitzer —
package/build/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { IncomingMessage, Server, ServerResponse } from "node:http";
2
- import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, EngineAdapter, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionResult } from "@workerdeck/core";
2
+ import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, EngineAdapter, EngineSessionOptions, HostToolDefinition, LanguageModel, McpConnection, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionResult, ToolExecutor, ToolSet } from "@workerdeck/core";
3
3
  import { JobQueue, QueueAdapter } from "@workerdeck/queue";
4
- import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, SdkSessionSummary, ServerFrame, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
4
+ import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, ProviderConfig, SdkSessionSummary, ServerFrame, SessionCapability, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
5
5
 
6
6
  //#region src/registry.d.ts
7
7
  type SessionRegistryOptions = {
@@ -118,6 +118,7 @@ declare class BridgeHub {
118
118
  * snapshot, and what it is waiting for.
119
119
  */
120
120
  type ParkedSessionRecord = {
121
+ /** Absent on records written before dormant sessions existed — those are all parked. */kind?: 'parked';
121
122
  id: string; /** Session info as of the park, with `status: 'parked'`. */
122
123
  info: SessionInfo;
123
124
  profile?: string; /** The config the session was created with (profile defaults already applied). */
@@ -126,6 +127,46 @@ type ParkedSessionRecord = {
126
127
  executions: ParkedExecution[];
127
128
  parkedAt: number;
128
129
  };
130
+ /**
131
+ * A live session, remembered so it can be brought back after a gateway restart.
132
+ *
133
+ * The counterpart to a park, and deliberately not the same mechanism. A park
134
+ * preserves *mid-task* state, which means a `RunnerSnapshot` — and only the
135
+ * provider engine can produce one, because the claude and codex engines run
136
+ * behind a binary that owns its own process state. What those two have instead
137
+ * is a session store of their own: the transcript is already on disk under an
138
+ * engine session id, and `CreateSessionRequest.resume` is how you get it back.
139
+ *
140
+ * So this record holds no transcript at all. It holds the id (every client keys
141
+ * its watermarks and routes on it), the engine session id to resume from, and
142
+ * the config to rebuild with — and rehydration is an ordinary create with
143
+ * `resume` set, done **lazily on first attach**, because eagerly respawning
144
+ * every session at boot is a fork bomb wearing a feature's clothes.
145
+ *
146
+ * Written only for engines whose capability record says `resume`, and only once
147
+ * an `sdkSessionId` exists: a record that would come back with an empty
148
+ * transcript is worse than no record.
149
+ */
150
+ type DormantSessionRecord = {
151
+ kind: 'dormant';
152
+ id: string;
153
+ /** Session info as of the last save, with `status: 'idle'` — whatever it was
154
+ * doing, it is not doing it now. */
155
+ info: SessionInfo;
156
+ profile?: string;
157
+ /**
158
+ * The config the session was built from, minus the ephemeral keys. Fed back
159
+ * through the server's `buildRunnerConfig` on wake rather than used as-is, so
160
+ * the profile's env pin and the host hook's injections are **re-derived**
161
+ * instead of persisted (see {@link EPHEMERAL_CONFIG_KEYS}).
162
+ */
163
+ config: SessionRunnerConfig; /** Where the transcript actually lives. Without one there is nothing to resume. */
164
+ sdkSessionId: string;
165
+ savedAt: number;
166
+ };
167
+ /** What a {@link SessionStore} holds: a session waiting on deferred work, or one
168
+ * waiting to be asked for again. */
169
+ type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord;
129
170
  /**
130
171
  * Where parked sessions live. Two implementations ship: {@link MemorySessionStore}
131
172
  * (a park survives a disconnect, not a restart) and {@link createFileSessionStore}
@@ -138,22 +179,22 @@ type ParkedSessionRecord = {
138
179
  * {@link toDurableRecord} is the filter the bundled file store applies — reuse it.
139
180
  */
140
181
  interface SessionStore {
141
- save(record: ParkedSessionRecord): Promise<void>;
142
- get(id: string): Promise<ParkedSessionRecord | null>;
143
- list(): Promise<ParkedSessionRecord[]>;
182
+ save(record: StoredSessionRecord): Promise<void>;
183
+ get(id: string): Promise<StoredSessionRecord | null>;
184
+ list(): Promise<StoredSessionRecord[]>;
144
185
  delete(id: string): Promise<boolean>;
145
186
  }
146
187
  /** Single-process, no persistence: parks survive a client disconnect, not a restart. */
147
188
  declare class MemorySessionStore implements SessionStore {
148
189
  #private;
149
- save(record: ParkedSessionRecord): Promise<void>;
150
- get(id: string): Promise<ParkedSessionRecord | null>;
151
- list(): Promise<ParkedSessionRecord[]>;
190
+ save(record: StoredSessionRecord): Promise<void>;
191
+ get(id: string): Promise<StoredSessionRecord | null>;
192
+ list(): Promise<StoredSessionRecord[]>;
152
193
  delete(id: string): Promise<boolean>;
153
194
  }
154
195
  /** The record as it may be persisted: same session, config narrowed to what is
155
196
  * safe and meaningful to keep (see {@link EPHEMERAL_CONFIG_KEYS}). */
156
- declare function toDurableRecord(record: ParkedSessionRecord): ParkedSessionRecord;
197
+ declare function toDurableRecord<T extends StoredSessionRecord>(record: T): T;
157
198
  type FileSessionStoreOptions = {
158
199
  /** Directory holding one JSON file per parked session.
159
200
  * Default `<cwd>/.workerdeck/parked`. */
@@ -191,9 +232,14 @@ declare function createFileSessionStore(options?: FileSessionStoreOptions): Sess
191
232
  type SessionParkOptions = {
192
233
  registry: SessionRegistry;
193
234
  store: SessionStore;
194
- /** Rebuild a parked session's runner. The snapshot rides in on
195
- * `config.restore`, so the engine adopts the id, event log, and history. */
196
- rebuild: (record: ParkedSessionRecord) => Promise<Runner>;
235
+ /**
236
+ * Rebuild a stored session's runner. For a park the snapshot rides in on
237
+ * `config.restore`, so the engine adopts the id, event log, and history; for a
238
+ * dormant record there is no snapshot and the engine is asked to resume its
239
+ * own session under the stored id. Either way the runner it returns must carry
240
+ * `record.id` — {@link SessionParkManager} refuses one that does not.
241
+ */
242
+ rebuild: (record: StoredSessionRecord) => Promise<Runner>;
197
243
  /** How many clients are attached to this session. A watched session stays live:
198
244
  * parking would pull the runner out from under the socket. */
199
245
  attachedCount: (sessionId: string) => number;
@@ -212,17 +258,25 @@ type SessionParkOptions = {
212
258
  /** The session is live again under a NEW runner object — anything holding the
213
259
  * old reference must rebind. */
214
260
  onResumed?: (sessionId: string, runner: Runner) => void;
215
- /** Park/resume failures. These are not session errors — the session is intact,
216
- * the host's storage or engine assembly isn't. */
261
+ /** Park/remember/resume failures. These are not session errors — the session
262
+ * is intact, the host's storage or engine assembly isn't. */
217
263
  onError?: (error: unknown, context: {
218
264
  sessionId: string;
219
- phase: 'park' | 'resume';
265
+ phase: 'park' | 'remember' | 'resume';
220
266
  }) => void;
221
267
  };
222
268
  /**
223
- * Deferred execution's other half: parking a session that is waiting on work no
269
+ * Two ways a session outlives its runner, behind one door.
270
+ *
271
+ * **Parking** is deferred execution's other half: a session waiting on work no
224
272
  * process in this server is doing.
225
273
  *
274
+ * **Dormancy** is the restart story for the engines that cannot park. Every live
275
+ * claude or codex session leaves a small record naming its engine session id, so
276
+ * a gateway that comes back up lists them and resumes one the first time someone
277
+ * attaches. Both kinds live in the same store and come back through the same
278
+ * `ensureLive`, which is why there is one class here and not two.
279
+ *
226
280
  * The runner announces the moment with `status_changed: 'parked'` — emitted only
227
281
  * once every dispatch of the batch has been handed over, so the snapshot can never
228
282
  * miss a call that was still being dispatched. From there this class snapshots,
@@ -236,9 +290,15 @@ declare class SessionParkManager {
236
290
  /** Record the config a session was created with. Only sessions the host
237
291
  * remembers can be parked — there is no way to rebuild the others. */
238
292
  remember(sessionId: string, config: SessionRunnerConfig): void;
239
- /** Adopt the store's contents (a durable store after a restart): re-index the
293
+ /**
294
+ * Adopt the store's contents (a durable store after a restart): re-index the
240
295
  * executions and re-arm their watchdogs, no deadline sooner than the grace
241
- * window — nothing could have been delivered while the process was down. */
296
+ * window — nothing could have been delivered while the process was down.
297
+ *
298
+ * Dormant records need nothing here, which is the point of them. They list
299
+ * from the store (`listInfo`) and come back on first attach (`ensureLive`), so
300
+ * a boot with fifty remembered sessions spawns nothing at all.
301
+ */
242
302
  hydrate(): Promise<void>;
243
303
  /**
244
304
  * Follow a session's lifecycle: index its deferred executions, park it when the
@@ -251,9 +311,9 @@ declare class SessionParkManager {
251
311
  onDetach(sessionId: string): void;
252
312
  /** Which session this execution belongs to — still waiting, or already settled. */
253
313
  sessionFor(executionId: string): string | undefined;
254
- /** The parked session's record, for the read paths (GET, list, attach). */
255
- get(id: string): Promise<ParkedSessionRecord | null>;
256
- /** Every parked session's info, to merge into `GET {basePath}/sessions`. */
314
+ /** The stored session's record, for the read paths (GET, list, attach). */
315
+ get(id: string): Promise<StoredSessionRecord | null>;
316
+ /** Every stored session's info, to merge into `GET {basePath}/sessions`. */
257
317
  listInfo(): Promise<SessionInfo[]>;
258
318
  /** The live runner for a session, rehydrating a parked one on demand. Undefined
259
319
  * when the session is neither live nor parked. */
@@ -321,11 +381,64 @@ type SdkSessionLister = (options: {
321
381
  * without it the caller may use every declared profile. It may also carry
322
382
  * `canManageProfiles: true` to allow creating/editing/deleting managed profiles
323
383
  * (requires the `profileStore` option); anything else means no.
384
+ *
385
+ * It may also carry `scope: Record<string, string>` — the opaque tags deciding
386
+ * which *sessions* this caller may see at all (see
387
+ * {@link WorkerServerOptions.authorizeSession}). A principal carrying a scope is
388
+ * an embedded end user rather than the operator, and is refused the
389
+ * operator-privileged surfaces outright: `/fs/*`, `/sdk-sessions`, `/queue` and
390
+ * `/queue/ws`.
391
+ *
392
+ * **This is the place to be expensive.** It is already async and already runs
393
+ * once per request, so a lookup (which spaces is this user in?) belongs here,
394
+ * landing its answer on the principal. The visibility check itself is
395
+ * synchronous by design: it runs on every route and every row of every list.
324
396
  */
325
397
  type Authenticator = (req: IncomingMessage) => unknown | Promise<unknown>;
326
398
  type WorkerServerOptions = {
327
399
  /** Required unless `allowUnauthenticated: true` — the worker must never be exposed bare. */authenticate?: Authenticator; /** Explicit opt-in to run without auth (local dev only). */
328
- allowUnauthenticated?: boolean; /** If set, session cwd must resolve inside one of these roots. Strongly recommended. */
400
+ allowUnauthenticated?: boolean;
401
+ /**
402
+ * Whether a principal may see one session — the policy half of
403
+ * {@link CreateSessionRequest.scope}. WorkerDeck stores the opaque tags and
404
+ * enforces the answer at every door; what the tags *mean* is the host's, and
405
+ * has to be, because "space" and "user" are one app's vocabulary and the next
406
+ * embedder has tenants or projects or nothing.
407
+ *
408
+ * **Synchronous, deliberately.** It runs per route and per row of every list,
409
+ * so resolving it against a database per request is the failure mode this
410
+ * signature designs out: do the lookup in {@link Authenticator} and put the
411
+ * answer on the principal.
412
+ *
413
+ * Unset, the default rule applies: every key the principal's `scope` pins must
414
+ * equal the session's, and a principal with no scope (`undefined` or `{}`) is
415
+ * unrestricted — the same "unset means all" rule `allowedProfiles` uses, so an
416
+ * operator's dashboard is unaffected. A consequence worth stating: a session
417
+ * carrying *no* scope is invisible to a scoped principal, which is the right
418
+ * fail direction — sessions predating this feature never leak into an
419
+ * end user's list.
420
+ *
421
+ * **False means the session does not exist**: every refusal answers 404, never
422
+ * 403, matching `host-files.ts`' uniform-disclosure discipline. A predicate
423
+ * that *throws* has not said yes — it is caught and read as false, so one
424
+ * surprising row cannot turn a hundred-row list into a page-wide error.
425
+ *
426
+ * **Declaring this withdraws the unscoped-means-operator default.** The
427
+ * gateway-wide surfaces (`/fs/*`, `/sdk-sessions`, `/queue`, `/queue/ws`) key
428
+ * on {@link Authenticator}'s principal carrying no `scope` — but a host may
429
+ * well write this predicate over its own principal shape and never set one,
430
+ * and reading that as "everyone is the operator" would serve the host
431
+ * filesystem to every end user whose sessions this correctly walls off. So
432
+ * with a policy declared, operator principals must say `operator: true`.
433
+ *
434
+ * **True means full control, not read access.** An attach can send
435
+ * `user_message`, `permission_decision`, `interrupt` and `close`, and a
436
+ * bridged client can settle a tool call — so this is one boolean over "may
437
+ * drive this session", not a visibility level. A read-only-for-my-team policy
438
+ * is not expressible here yet; do not approximate it with `readOnly`, which is
439
+ * affordance removal in a client and not an authorization boundary.
440
+ */
441
+ authorizeSession?: (principal: unknown, session: SessionInfo) => boolean; /** If set, session cwd must resolve inside one of these roots. Strongly recommended. */
329
442
  allowedCwdRoots?: string[];
330
443
  /**
331
444
  * The host filesystem routes (`{basePath}/fs/*`) — browse and read the
@@ -433,7 +546,27 @@ type WorkerServerOptions = {
433
546
  *
434
547
  * Upgrades are not routed here: anything outside `basePath` is still refused.
435
548
  */
436
- fallback?: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>; /** Max JSON body size in bytes. Default 1 MiB. */
549
+ fallback?: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
550
+ /**
551
+ * Browser origins allowed to call this API cross-origin — for a dashboard
552
+ * served somewhere other than this gateway.
553
+ *
554
+ * Off unless configured, and even then it is *sharing policy, not a
555
+ * credential*: preflights are answered before auth (browsers strip
556
+ * credentials from them, so they would otherwise 401), but every real request
557
+ * still goes through `authenticate`, and an allowlisted page that does not
558
+ * hold the key gets nothing.
559
+ *
560
+ * Two rules the implementation must keep: **exact origins only**, no
561
+ * wildcards or suffix matching; and `Access-Control-Allow-Credentials` is
562
+ * **never** sent, which is what keeps an ambient cookie from becoming
563
+ * cross-origin authority. WebSocket upgrades are exempt from CORS entirely
564
+ * and are unaffected by this — their credential is whatever the host's
565
+ * `authenticate` accepts on the handshake.
566
+ */
567
+ cors?: {
568
+ origins: string[];
569
+ }; /** Max JSON body size in bytes. Default 1 MiB. */
437
570
  maxBodyBytes?: number;
438
571
  /**
439
572
  * Server-wide bypass policy: refuse `permissionMode: 'bypassPermissions'` on
@@ -469,6 +602,24 @@ type WorkerServerOptions = {
469
602
  probe?: ClaudeAuthProbe;
470
603
  timeoutMs?: number;
471
604
  };
605
+ /**
606
+ * Refuse to create a session or submit a job on a profile the credential
607
+ * probe has reported **unavailable** — 503 with the probe's own reason —
608
+ * rather than letting the run start and die mid-turn on a raw provider error.
609
+ *
610
+ * Off by default, and that default is right for an operator's own gateway:
611
+ * the verdict can be stale in both directions, the operator may be three
612
+ * seconds from finishing a login, and turning a probe bug into an outage is
613
+ * worse than one confusing failure. It is wrong in front of an **end user**,
614
+ * who cannot read a provider stack trace and did not choose the deployment's
615
+ * credentials — which is why every embedder otherwise grows its own
616
+ * `available` flag in front of the create button.
617
+ *
618
+ * Requires `checkCredentials`; without probes nothing is ever unavailable.
619
+ * A profile whose verdict is 'unknown' (never probed, probe couldn't run) is
620
+ * always allowed through — "couldn't check" is not "not available".
621
+ */
622
+ requireAvailableProfile?: boolean;
472
623
  /** Injectable lister for GET /sdk-sessions (tests) — honored for the CLAUDE
473
624
  * engine only, like the injectable claude auth probe (it predates the adapter
474
625
  * layer). Defaults to the claude adapter's lister (the SDK's on-disk session
@@ -512,10 +663,13 @@ type WorkerServerOptions = {
512
663
  parkDelayMs?: number;
513
664
  /** Grace given on boot to an execution whose deadline passed while the server
514
665
  * was down (durable stores only — nothing else survives a restart). Default 60000. */
515
- expiredGraceMs?: number; /** Park/resume failures — storage or engine-assembly problems, not session errors. */
666
+ expiredGraceMs?: number;
667
+ /** Park/remember/resume failures — storage or engine-assembly problems, not
668
+ * session errors. 'remember' is the write that lets a live session survive a
669
+ * restart; losing one costs that session its way back and nothing else. */
516
670
  onError?: (error: unknown, context: {
517
671
  sessionId: string;
518
- phase: 'park' | 'resume';
672
+ phase: 'park' | 'remember' | 'resume';
519
673
  }) => void;
520
674
  };
521
675
  /**
@@ -556,6 +710,13 @@ type EngineRunnerContext = {
556
710
  * numbering, history, and scratch filesystem instead of starting fresh.
557
711
  */
558
712
  restore?: RunnerSnapshot;
713
+ /**
714
+ * Set when rehydrating a session across a gateway restart: build the runner
715
+ * under exactly this id rather than a fresh one. Never set together with
716
+ * `restore` (a snapshot carries its own id). Ignoring it strands every
717
+ * client's watermarks and routes, and the rebuild is refused.
718
+ */
719
+ id?: string;
559
720
  };
560
721
  type QueueServerOptions = {
561
722
  /** Concurrent job sessions. Default 1. */maxConcurrency?: number; /** Token cap per job session (input+output+cache tokens); exceeding it kills the run. */
@@ -594,6 +755,133 @@ type WorkerServer = {
594
755
  };
595
756
  declare function createWorkerServer(options?: WorkerServerOptions): WorkerServer;
596
757
  //#endregion
758
+ //#region src/sandboxed-profile.d.ts
759
+ /**
760
+ * A `provider` profile that grants a session nothing but the sandbox: the
761
+ * QuickJS guest, the in-memory VFS, and the model.
762
+ *
763
+ * This adds no mechanism. `capabilities: []` and `mcpServers: []` already mean
764
+ * what they mean, and `createToolContext` already withholds a tool whose backend
765
+ * the host did not inject. What the helper buys is that the locked-down profile
766
+ * is one call rather than three fields an operator has to get right together —
767
+ * the failure mode being a profile that *looks* sandboxed and still grants
768
+ * `deliver_file` because nobody wrote the empty array.
769
+ *
770
+ * What a session under it can do:
771
+ * - run untrusted JavaScript in the WASM guest, under the interpreter's own
772
+ * timeout and memory limits (`eval_script`),
773
+ * - read and write the session's in-memory VFS, which is a map and not a
774
+ * filesystem — no host path is reachable from it.
775
+ *
776
+ * What it cannot do: read or write a host path, spawn a process, reach the
777
+ * network (`web_fetch`/`download`/`web_search` are capabilities, and none is
778
+ * granted), deliver a file, or use an MCP server.
779
+ *
780
+ * Two things this helper does **not** do, because they are not a profile's to
781
+ * decide. It does not authorize anyone — visibility is
782
+ * `CreateSessionRequest.scope` plus the gateway's `authorizeSession`. And it
783
+ * does not make the model's *input* trustworthy: content the loop reads is
784
+ * attacker-influenced by default, and a sandbox bounds what a tool can reach,
785
+ * not what a prompt can talk the model into asking for.
786
+ *
787
+ * @param name Profile name clients name in `CreateSessionRequest.profile`.
788
+ * @param provider Which model to run (credentials stay in the operator's
789
+ * environment and are resolved by the host's `createEngineRunner` — never
790
+ * here, and never on the wire).
791
+ */
792
+ declare function sandboxedProviderProfile(name: string, provider: ProviderConfig, options?: {
793
+ description?: string; /** Prepended to the session's system prompt. */
794
+ instructions?: string;
795
+ /** Profile-level run defaults (model, permission mode) — see
796
+ * {@link ProfileInfo.defaults}. */
797
+ defaults?: ProfileInfo['defaults'];
798
+ /**
799
+ * Capabilities to grant on top of the floor. Default `[]` — the floor is
800
+ * nothing, and every entry here is a deliberate widening you are writing
801
+ * down: `web_fetch` gives the loop egress (SSRF-guarded, but egress),
802
+ * `download` and `web_search` reach whatever backends you injected, and
803
+ * `deliver_file` lets it hand a file to the client.
804
+ */
805
+ capabilities?: SessionCapability[];
806
+ /**
807
+ * MCP servers, **by name**, whose tools sessions may use. Default `[]`.
808
+ * MCP tools are authoritative — they run with the host's credentials and
809
+ * are never bridged — so naming one here is a larger grant than any
810
+ * capability above it.
811
+ */
812
+ mcpServers?: string[];
813
+ }): ProfileInfo;
814
+ //#endregion
815
+ //#region src/provider-runner.d.ts
816
+ type ProviderRunnerOptions = {
817
+ /**
818
+ * The model to run. A function is called per turn with the session's
819
+ * requested model id (undefined = the profile's default), which is what makes
820
+ * the in-session model switcher work; a bare instance pins one model.
821
+ */
822
+ model: LanguageModel | ((modelId: string | undefined) => LanguageModel);
823
+ /**
824
+ * Where sandboxed tools (`eval_script` and any `sandboxed` entry in `tools`)
825
+ * execute. This is a real architectural choice, not a default worth guessing
826
+ * at, so it is required:
827
+ *
828
+ * - a {@link ToolExecutor} — an in-process guest (`new QuickJsExecutor(...)`
829
+ * from `@workerdeck/core`), which is right when the data the loop reasons
830
+ * over lives in this process. It is also the only option that works when no
831
+ * client is attached, which is every unattended job.
832
+ * - `'browser'` — the attached tab, resolved per call from the bridge. Right
833
+ * when the data is *there* (a document the user is editing) and it should
834
+ * not travel to the gateway at all. Note the trade: it hands an executor to
835
+ * the party being sandboxed against, so its results are untrusted input.
836
+ */
837
+ executor: ToolExecutor | 'browser';
838
+ /** Capability backends — the same shape {@link createEngineSession} takes.
839
+ * Wiring one only offers it; the profile and request decide the grant. */
840
+ capabilities?: EngineSessionOptions['capabilities']; /** Host tools at explicit trust levels (`@workerdeck/core`'s `withHostTools`). */
841
+ tools?: Record<string, HostToolDefinition>;
842
+ /** A live MCP connection from `connectMcpTools`. Prefer this over `mcpTools`:
843
+ * it is what lets a profile's unhonoured `mcpServers` refuse the build, and
844
+ * what makes `GET /sessions/:id/mcp` answer for this session. */
845
+ mcp?: McpConnection; /** A bare MCP tool set, for a host assembling one itself. */
846
+ mcpTools?: ToolSet; /** System-prompt addition, unless the profile declares its own. */
847
+ instructions?: string; /** Sandbox limits per execution. */
848
+ executionLimits?: {
849
+ timeoutMs?: number;
850
+ memoryLimitBytes?: number;
851
+ };
852
+ /** Scratch-filesystem seed for a new session. Ignored on a rehydration, so a
853
+ * parked turn's files are never overwritten. */
854
+ seedVfs?: Record<string, string>;
855
+ /** Release per-session resources: the MCP connection, an issued token, a
856
+ * watcher. Runs on close **and on park** — parking releases the same things. */
857
+ onClose?: () => void | Promise<void>;
858
+ };
859
+ /**
860
+ * Build a provider-engine runner from the server's `createEngineRunner` context.
861
+ *
862
+ * `createEngineRunner` is a blank sheet: it hands you a context and wants a
863
+ * `Runner`, and four of the five things a correct one must do are invisible in
864
+ * the types — forward `restore`, adopt `id`, seed the VFS only when *not*
865
+ * restoring, and dispose per-session resources. Each is a runtime-only failure
866
+ * (a woken session that starts empty, a refused rebuild, an overwritten
867
+ * filesystem, a connection leaked per session), and each is handled here.
868
+ *
869
+ * ```ts
870
+ * createEngineRunner: (ctx) =>
871
+ * createProviderRunner(ctx, {
872
+ * model: (id) => openai(id ?? 'gpt-5.6-luna'),
873
+ * executor: quickjs,
874
+ * capabilities: { webFetch: {} },
875
+ * mcp,
876
+ * onClose: () => mcp.close(),
877
+ * }),
878
+ * ```
879
+ *
880
+ * The hook itself stays open for anything this does not cover — this is the
881
+ * 80% case, not a replacement for it.
882
+ */
883
+ declare function createProviderRunner(ctx: EngineRunnerContext, options: ProviderRunnerOptions): Promise<Runner>;
884
+ //#endregion
597
885
  //#region src/attachments.d.ts
598
886
  type AttachmentStoreOptions = {
599
887
  /** Largest single upload. Default 10 MiB. */maxFileBytes?: number; /** Ceiling on everything one session is holding. Default 64 MiB. */
@@ -708,5 +996,5 @@ declare class ProducedFileStore {
708
996
  drop(sessionId: string): void;
709
997
  }
710
998
  //#endregion
711
- export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, type QueueServerOptions, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createWorkerServer, toDurableRecord };
999
+ export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, type ProviderRunnerOptions, type QueueServerOptions, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, sandboxedProviderProfile, toDurableRecord };
712
1000
  //# sourceMappingURL=index.d.mts.map