@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 +28 -28
- package/build/index.d.mts +21 -6
- package/build/index.mjs +138 -33
- package/build/index.mjs.map +1 -1
- package/package.json +9 -9
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
64
|
-
| `PUT /v1/fs/write` | Save a host file
|
|
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
|
|
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
|
|
78
|
-
as that session's `CLAUDE_CONFIG_DIR`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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`
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
213
|
-
resumed from the engine's own store
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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
|