@workerdeck/server 2.7.2 → 2.9.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
@@ -3,7 +3,7 @@
3
3
  The WorkerDeck gateway: HTTP + WebSocket session server over
4
4
  [`@workerdeck/core`](https://www.npmjs.com/package/@workerdeck/core). Session registry
5
5
  (create/list/attach/interrupt/kill), pluggable auth hook, replay-from-seq attach, profiles,
6
- parked-session storage, optional job-queue routes. Runs anywhere Node runs — needs a real
6
+ parked-session storage, optional job-queue routes. Runs anywhere Node runs - needs a real
7
7
  filesystem (no serverless).
8
8
 
9
9
  Want the whole thing running rather than embedded? [`workerdeck`](https://www.npmjs.com/package/workerdeck)
@@ -24,22 +24,22 @@ npm install @workerdeck/server
24
24
  ```
25
25
 
26
26
  Node ≥ 22. The Agent SDK spawns the Claude Code CLI as a long-running subprocess with filesystem
27
- state — edge/serverless functions cannot host this; realistic targets are a VM or a container.
27
+ state - edge/serverless functions cannot host this; realistic targets are a VM or a container.
28
28
  The server implements no Anthropic auth: the SDK/CLI resolves credentials from the operator's
29
29
  environment (`ANTHROPIC_API_KEY`, Bedrock/Vertex, or a personal `claude login`).
30
30
 
31
31
  ## Usage
32
32
 
33
- The host app supplies the authenticator — return a truthy principal to accept, null/undefined to
33
+ The host app supplies the authenticator - return a truthy principal to accept, null/undefined to
34
34
  reject with 401. `createWorkerServer` refuses to start without `authenticate` unless you
35
- explicitly pass `allowUnauthenticated: true` (loopback dev only — never expose that):
35
+ explicitly pass `allowUnauthenticated: true` (loopback dev only - never expose that):
36
36
 
37
37
  ```ts
38
38
  import { createWorkerServer } from '@workerdeck/server'
39
39
 
40
40
  const worker = createWorkerServer({
41
41
  authenticate: async (req) => verifyMyAppToken(req.headers.authorization),
42
- allowedCwdRoots: ['/srv/checkouts'], // where sessions may run — and what /fs serves
42
+ allowedCwdRoots: ['/srv/checkouts'], // where sessions may run - and what /fs serves
43
43
  hostFiles: { write: true }, // /fs reads follow the roots above; writing opts in
44
44
  buildRunnerConfig: (req) => ({ ...req, env: { ...process.env } }),
45
45
  requireApiKey: true, // fail closed on subscription credentials
@@ -60,22 +60,22 @@ Routes (default `basePath: '/v1'`):
60
60
  | `GET /v1/sdk-sessions?dir=…` | List the Agent SDK's on-disk sessions to offer resume |
61
61
  | `GET /v1/sessions/:id/files`, `…/files/<path>` | List and download a session's scratch-filesystem deliverables |
62
62
  | `GET /v1/fs/roots`, `/fs/list?path=`, `/fs/read?path=` | Browse and read the **host's** real tree (the `allowedCwdRoots` trees, unless `hostFiles.roots` narrows them; 404 when neither is set) |
63
- | `GET /v1/fs/find?path=&q=` | Recursive fuzzy file search under one directory — what backs `@file` completion |
64
- | `PUT /v1/fs/write` | Save a host file — needs `hostFiles.write`, and always carries the hash it replaces |
63
+ | `GET /v1/fs/find?path=&q=` | Recursive fuzzy file search under one directory - what backs `@file` completion |
64
+ | `PUT /v1/fs/write` | Save a host file - needs `hostFiles.write`, and always carries the hash it replaces |
65
65
  | `POST /v1/executions/:executionId/result` | Deliver a deferred execution's result, waking a parked session |
66
66
  | `GET /v1/profiles`, `GET /v1/profiles/:name` | What sessions may run as (+ a view-only config snapshot) |
67
67
  | `GET/POST /v1/jobs`, `GET/DELETE /v1/jobs/:id` | Job queue (when `queue` is configured) |
68
68
  | `GET /v1/queue`, `WS /v1/queue/ws` | Queue stats / one-way live stream of job events + stats |
69
69
 
70
- Requests outside `basePath` fall through to the optional `fallback` hook — which is how the
70
+ Requests outside `basePath` fall through to the optional `fallback` hook - which is how the
71
71
  turnkey instance serves a dashboard from the same origin, so a browser's WebSocket attach can
72
72
  present a cookie.
73
73
 
74
74
  ## Profiles and the second engine
75
75
 
76
76
  A **profile** is what a session runs as, and which engine runs it. Declared at startup (or managed
77
- over the API with a `profileStore`), each one names either a Claude Code config directory — applied
78
- as that session's `CLAUDE_CONFIG_DIR` — or a model provider for the model-agnostic engine:
77
+ over the API with a `profileStore`), each one names either a Claude Code config directory - applied
78
+ as that session's `CLAUDE_CONFIG_DIR` - or a model provider for the model-agnostic engine:
79
79
 
80
80
  ```ts
81
81
  createWorkerServer({
@@ -88,19 +88,19 @@ createWorkerServer({
88
88
  {
89
89
  name: 'kimi',
90
90
  engine: 'provider',
91
- // A variable NAME — no credential is stored here or served by GET /profiles.
91
+ // A variable NAME - no credential is stored here or served by GET /profiles.
92
92
  provider: { id: 'moonshotai', model: 'kimi-k3', apiKeyEnv: 'MOONSHOT_API_KEY' },
93
93
  session: { capabilities: ['web_fetch', 'deliver_file'] },
94
94
  },
95
95
  ],
96
- // The one place a model SDK and its credentials are resolved — this package imports neither.
96
+ // The one place a model SDK and its credentials are resolved - this package imports neither.
97
97
  createEngineRunner: ({ config, profile, bridge }) => buildProviderRunner({ /* … */ }),
98
98
  })
99
99
  ```
100
100
 
101
101
  With more than one profile declared, every create must name its `profile`; with exactly one it is
102
102
  implicit, and with the option unset a `default` is auto-detected from `~/.claude`.
103
- `allowedProfiles` on the principal scopes who may run as what — the line between one worker serving
103
+ `allowedProfiles` on the principal scopes who may run as what - the line between one worker serving
104
104
  several people and account pooling. `SessionInfo.engine` and `supportsPermissionMode` let a UI hide
105
105
  affordances the provider engine doesn't have; asking for one anyway is a 400 rather than a silent
106
106
  coercion.
@@ -108,7 +108,7 @@ coercion.
108
108
  ## Parked sessions
109
109
 
110
110
  A session whose tool call can't answer in the next few seconds **parks**: the runner is torn down,
111
- its snapshot goes to a `SessionStore`, and `POST /executions/:id/result` rebuilds it — same id,
111
+ its snapshot goes to a `SessionStore`, and `POST /executions/:id/result` rebuilds it - same id,
112
112
  same event log, same seq numbering, mid-turn. Results are idempotent by `executionId`. The default
113
113
  store is in-memory (a park survives a client disconnect, not a restart); the bundled file store
114
114
  survives both, on one host:
@@ -123,12 +123,12 @@ createWorkerServer({
123
123
  })
124
124
  ```
125
125
 
126
- That directory holds each parked session's whole transcript in plaintext — protect it like the
126
+ That directory holds each parked session's whole transcript in plaintext - protect it like the
127
127
  SDK's own `~/.claude/projects`. Credentials never reach it. One process per directory.
128
128
 
129
129
  ## Job queue
130
130
 
131
- Pass `queue` options to mount the job routes — one-shot unattended runs with bounded concurrency,
131
+ Pass `queue` options to mount the job routes - one-shot unattended runs with bounded concurrency,
132
132
  token budgets, retries, and webhook delivery:
133
133
 
134
134
  ```ts
@@ -145,7 +145,7 @@ const worker = createWorkerServer({
145
145
  })
146
146
  ```
147
147
 
148
- Job sessions are ordinary registry sessions — attachable over the sessions WS — and go through
148
+ Job sessions are ordinary registry sessions - attachable over the sessions WS - and go through
149
149
  the same `buildRunnerConfig` hook and auth-provenance watcher as client sessions. The in-memory
150
150
  adapter is single-process and non-persistent; implement `QueueAdapter` against a shared store for
151
151
  anything beyond one trusted host.
@@ -153,7 +153,7 @@ anything beyond one trusted host.
153
153
  ## Session notifications
154
154
 
155
155
  The live WebSocket only reaches someone who has one open. `notifications` is the outbound
156
- channel for everyone else — the four moments a person acts on, POSTed to a URL you control:
156
+ channel for everyone else - the four moments a person acts on, POSTed to a URL you control:
157
157
 
158
158
  ```ts
159
159
  const worker = createWorkerServer({
@@ -166,13 +166,13 @@ const worker = createWorkerServer({
166
166
  })
167
167
  ```
168
168
 
169
- `permission_requested`, `turn_completed`, `session_error`, `session_closed` — delivered as a
169
+ `permission_requested`, `turn_completed`, `session_error`, `session_closed` - delivered as a
170
170
  `SessionNotification`, ordered per session, retried with exponential backoff. Server-wide, unlike
171
171
  the queue's per-job `webhook`: the point is hearing about sessions you neither created nor are
172
172
  attached to, and every registry session qualifies (job runs and rebuilt parked sessions included).
173
173
 
174
174
  `permission_requested` carries the whole `PermissionRequest`, so a consumer can answer it with
175
- `POST /sessions/:id/permissions/:requestId` — the mechanism behind an Approve button in a chat
175
+ `POST /sessions/:id/permissions/:requestId` - the mechanism behind an Approve button in a chat
176
176
  message or on a phone's lock screen. The server holds no push credentials and knows nothing about
177
177
  APNs, FCM or Slack; forwarding to one of those is a separate process's job.
178
178
 
@@ -180,10 +180,10 @@ APNs, FCM or Slack; forwarding to one of those is a separate process's job.
180
180
 
181
181
  Each session's credential provenance surfaces as `apiKeySource` on `SessionInfo` and the
182
182
  `system_init` event; `'oauth'` means claude.ai subscription credentials. With
183
- `requireApiKey: true` such sessions are terminated with a `session_error` — recommended for
183
+ `requireApiKey: true` such sessions are terminated with a `session_error` - recommended for
184
184
  services and any unattended use. Without it the server logs a one-time notice instead
185
185
  (appropriate only for personal single-user deployments). WorkerDeck never implements claude.ai
186
- OAuth, never reads or forwards tokens — see the repo README's
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
189
  ## Rules you cannot infer from the types
@@ -192,7 +192,7 @@ OAuth, never reads or forwards tokens — see the repo README's
192
192
  caller's business, so the out-of-scope answer is byte-identical to the unknown-id one.
193
193
  - **Visibility is full control.** There is no read-only attach: a client that can see a session can
194
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.
195
+ removes the affordance, not the authority - enforce at the gateway or not at all.
196
196
  - **`authorizeSession` is synchronous on purpose.** It runs per route and per row of every list. An
197
197
  expensive lookup belongs in `authenticate`, where it happens once and lands on the principal.
198
198
  - **`sandboxedProviderProfile()`'s empty arrays are load-bearing.** `capabilities: []` and
@@ -202,15 +202,15 @@ OAuth, never reads or forwards tokens — see the repo README's
202
202
  variable at all moves the CLI off the macOS Keychain, so pinning the default directory breaks a
203
203
  working `claude login`. "Pin the default dir" and "don't pin" are different logins.
204
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
205
+ against an unavailable profile otherwise proceeds and fails with the engine's own error - right
206
206
  for an operator (the probe can be stale), wrong in front of an end user.
207
207
  - **`createEngineRunner` has four invisible obligations**: forward `restore`, adopt `id`, seed the
208
208
  VFS only when *not* restoring, and dispose per-session resources via `onClose`. Every one is a
209
209
  runtime-only failure. `createProviderRunner()` does all four; reach for the raw hook only when it
210
210
  genuinely doesn't fit.
211
211
  - **`parking.persistLive` is how a *provider* session survives a restart, and it needs a durable
212
- store to mean anything.** Claude and codex go dormant — remembered by engine session id and
213
- resumed from the engine's own store — which a provider session cannot do, so its record carries
212
+ store to mean anything.** Claude and codex go dormant - remembered by engine session id and
213
+ resumed from the engine's own store - which a provider session cannot do, so its record carries
214
214
  the state itself, written through after every turn. With the default in-memory store the option
215
215
  does nothing and says nothing. It is off by default: a library must not start writing sessions'
216
216
  transcripts to disk because someone upgraded, and the record holds the whole transcript in
@@ -220,9 +220,9 @@ OAuth, never reads or forwards tokens — see the repo README's
220
220
  implement a `SessionStore`, do not "tidy up" a record on read.
221
221
  - **One origin is not a convenience.** A browser cannot put an `Authorization` header on a
222
222
  WebSocket upgrade, so a cookie is the only credential a tab can present on an attach, and a
223
- cookie is per-origin. That is what `fallback` is for — an app served from the gateway's own port.
223
+ cookie is per-origin. That is what `fallback` is for - an app served from the gateway's own port.
224
224
 
225
225
  ## License
226
226
 
227
- MIT © Tobias Strebitzer —
227
+ MIT © Tobias Strebitzer -
228
228
  [LICENSE](https://github.com/workerdeck/workerdeck/blob/master/LICENSE)
package/build/index.d.mts CHANGED
@@ -29,21 +29,22 @@ declare class SessionNotifier {
29
29
  #private;
30
30
  constructor(options: SessionNotificationOptions);
31
31
  get idle(): boolean;
32
- watch(runner: Runner, afterSeq?: number): void;
32
+ watch(runner: Runner, afterSeq?: number): () => void;
33
33
  }
34
34
  //#endregion
35
35
  //#region src/services/registry.d.ts
36
36
  type SessionRegistryOptions = {
37
- onRegister?: (runner: Runner) => void;
37
+ onRegister?: (runner: Runner) => unknown;
38
38
  };
39
39
  declare class SessionRegistry {
40
40
  #private;
41
41
  constructor(options?: SessionRegistryOptions);
42
- observe(listener: (runner: Runner) => void): () => void;
42
+ observe(listener: (runner: Runner) => unknown): () => void;
43
43
  create(config: SessionRunnerConfig): Runner;
44
44
  prepare(config: SessionRunnerConfig): Runner;
45
45
  adopt(runner: Runner): Runner;
46
46
  register(runner: Runner): Runner;
47
+ retain(id: string, detach: () => void): void;
47
48
  get(id: string): Runner | undefined;
48
49
  list(): SessionInfo[];
49
50
  remove(id: string): boolean;
@@ -72,6 +73,7 @@ type DormantSessionRecord = {
72
73
  savedAt: number;
73
74
  };
74
75
  type StoredSessionRecord = ParkedSessionRecord | DormantSessionRecord;
76
+ declare function isDormant(record: StoredSessionRecord): record is DormantSessionRecord;
75
77
  interface SessionStore {
76
78
  save(record: StoredSessionRecord): Promise<void>;
77
79
  get(id: string): Promise<StoredSessionRecord | null>;
@@ -119,6 +121,9 @@ declare class SessionParkManager {
119
121
  touch(runner: Runner): void;
120
122
  hydrate(): Promise<void>;
121
123
  watch(runner: Runner, afterSeq?: number): () => void;
124
+ release(sessionId: string): SessionRunnerConfig | undefined;
125
+ adopt(runner: Runner, config?: SessionRunnerConfig): void;
126
+ flush(sessionId?: string): Promise<void>;
122
127
  onDetach(sessionId: string): void;
123
128
  sessionFor(executionId: string): string | undefined;
124
129
  get(id: string): Promise<StoredSessionRecord | null>;
@@ -223,6 +228,10 @@ type QueueServerOptions = {
223
228
  webhookRetryDelayMs?: number;
224
229
  onEvent?: (event: JobEvent) => void;
225
230
  };
231
+ type CarriedSession = {
232
+ runner: Runner;
233
+ config?: SessionRunnerConfig;
234
+ };
226
235
  type DrainReport = {
227
236
  working: string[];
228
237
  awaitingHuman: string[];
@@ -243,6 +252,8 @@ type WorkerServer = {
243
252
  port: number;
244
253
  }>;
245
254
  drain: (options?: DrainOptions) => Promise<DrainReport>;
255
+ releaseSession: (id: string) => CarriedSession | undefined;
256
+ adoptSession: (carried: CarriedSession) => boolean;
246
257
  close: () => Promise<void>;
247
258
  };
248
259
  //#endregion
@@ -281,6 +292,10 @@ type ProviderRunnerOptions = {
281
292
  };
282
293
  declare function createProviderRunner(ctx: EngineRunnerContext, options: ProviderRunnerOptions): Promise<Runner>;
283
294
  //#endregion
295
+ //#region src/lib/reload-plan.d.ts
296
+ type ReloadPlan = 'carry' | 'persist' | 'drop';
297
+ declare function reloadPlan(runner: Runner): ReloadPlan;
298
+ //#endregion
284
299
  //#region src/services/attachments.d.ts
285
300
  type AttachmentStoreOptions = {
286
301
  maxFileBytes?: number;
@@ -332,7 +347,7 @@ type ProducedFile = {
332
347
  };
333
348
  declare class ProducedFileStore {
334
349
  #private;
335
- watch(runner: Runner): void;
350
+ watch(runner: Runner): () => void;
336
351
  get(sessionId: string, fileId: string): ProducedFile | undefined;
337
352
  list(sessionId: string): ProducedFile[];
338
353
  drop(sessionId: string): void;
@@ -341,9 +356,9 @@ declare class ProducedFileStore {
341
356
  //#region src/services/profile-usage.d.ts
342
357
  declare class ProfileUsageTracker {
343
358
  #private;
344
- watch(runner: Runner): void;
359
+ watch(runner: Runner): () => void;
345
360
  usage(profile: string, now?: number): ProfileUsage | undefined;
346
361
  }
347
362
  //#endregion
348
- export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, 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 };
363
+ export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type CarriedSession, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProducedFile, ProducedFileStore, type ProfileStore, ProfileUsageTracker, type ProviderRunnerOptions, type QueueServerOptions, type ReloadPlan, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type StoredSessionRecord, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createProviderRunner, createWorkerServer, isDormant, reloadPlan, sandboxedProviderProfile, toDurableRecord };
349
364
  //# sourceMappingURL=index.d.mts.map