@workerdeck/core 0.6.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/LICENSE +21 -0
- package/README.md +140 -0
- package/build/index.d.mts +848 -0
- package/build/index.mjs +2324 -0
- package/build/index.mjs.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tobias Strebitzer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# @workerdeck/core
|
|
2
|
+
|
|
3
|
+
The WorkerDeck engines, behind one `Runner` interface: `SessionRunner` wraps the Agent SDK's
|
|
4
|
+
`query()` with a push-based input queue, promotes `canUseTool` calls into pending approvals,
|
|
5
|
+
normalizes SDK messages into wire-protocol events, and keeps a seq-numbered event log for
|
|
6
|
+
attach/replay; `AiSdkRunner` does the same for any provider the AI SDK supports. Pure library, no
|
|
7
|
+
transport.
|
|
8
|
+
|
|
9
|
+
Part of [WorkerDeck](https://github.com/workerdeck/workerdeck). A `SessionRunner`
|
|
10
|
+
behaves like Claude Code launched in the session's directory — same skills, same `CLAUDE.md`, same
|
|
11
|
+
permission system — and both runners emit
|
|
12
|
+
[`@workerdeck/protocol`](https://www.npmjs.com/package/@workerdeck/protocol) events.
|
|
13
|
+
[`@workerdeck/server`](https://www.npmjs.com/package/@workerdeck/server) bridges runners to
|
|
14
|
+
HTTP + WebSocket; use core directly when you want sessions in-process with no server.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @workerdeck/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Depends on `@anthropic-ai/claude-agent-sdk`, which spawns the official Claude Code CLI. Needs
|
|
23
|
+
Node ≥ 22 and a real filesystem. WorkerDeck implements no Anthropic auth: the SDK/CLI resolves
|
|
24
|
+
credentials from the operator's environment (`ANTHROPIC_API_KEY`, Bedrock/Vertex, or a personal
|
|
25
|
+
`claude login`).
|
|
26
|
+
|
|
27
|
+
The provider engine additionally wants `ai` (AI SDK v7), your provider package, and — for
|
|
28
|
+
`eval_script` — [`@workerdeck/sandbox`](https://www.npmjs.com/package/@workerdeck/sandbox);
|
|
29
|
+
all optional, and unused if you only run Claude sessions.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
`SessionRunnerConfig` is a protocol `CreateSessionRequest` plus host-side extras (`env`,
|
|
34
|
+
`extraOptions`, `defaultApprovalTimeoutMs`, injectable `queryFn`/`historyFn` for tests):
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { SessionRunner } from '@workerdeck/core'
|
|
38
|
+
|
|
39
|
+
const runner = new SessionRunner({
|
|
40
|
+
cwd: '/srv/checkouts/my-repo',
|
|
41
|
+
prompt: 'Summarize the failing tests', // or a skill invocation like '/verify-content 42'
|
|
42
|
+
settingSources: ['user', 'project'], // pick up the repo's skills + CLAUDE.md
|
|
43
|
+
permissionMode: 'default',
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const unsubscribe = runner.subscribe((event) => {
|
|
47
|
+
switch (event.type) {
|
|
48
|
+
case 'assistant_message':
|
|
49
|
+
console.log(event.message)
|
|
50
|
+
break
|
|
51
|
+
case 'permission_requested':
|
|
52
|
+
// Blocks the tool until resolved; denied on timeout (default 5 minutes).
|
|
53
|
+
runner.resolvePermission(event.request.id, { behavior: 'allow' })
|
|
54
|
+
break
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const done = runner.start() // idempotent; resolves when the query ends
|
|
59
|
+
runner.sendMessage('Now fix the flakiest one') // queues the next turn
|
|
60
|
+
await done
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Other controls: `interrupt()`, `setPermissionMode(mode)`, `setModel(model?)`, `close(reason?)`,
|
|
64
|
+
`fail(message)` for host-enforced policy, and `info()` for a protocol `SessionInfo` snapshot
|
|
65
|
+
(status, cost, pending approval count, title). `runner.id` is the server-side id;
|
|
66
|
+
`runner.sdkSessionId` is the Agent SDK's — the one you pass back as `resume`.
|
|
67
|
+
|
|
68
|
+
## Approvals, event log, resume
|
|
69
|
+
|
|
70
|
+
- **Pending approvals** — the runner's `canUseTool` hook turns each uncovered tool call into a
|
|
71
|
+
`permission_requested` event and a `PendingApproval` that blocks the tool until
|
|
72
|
+
`resolvePermission()` (or the timeout) settles it. Allowing echoes the tool input back as
|
|
73
|
+
`updatedInput` — the SDK requires a record even for an unmodified allow. `AskUserQuestion`
|
|
74
|
+
rides the same path; `questionBehavior: 'auto' | 'deny'` policy-resolves it for unattended runs.
|
|
75
|
+
- **Event log** — every event gets a monotonic `seq`; `subscribe(listener, afterSeq)` replays the
|
|
76
|
+
buffer past `afterSeq` before delivering live events, so late attachers always catch up.
|
|
77
|
+
- **Resume** — pass `resume: sdkSessionId` (optionally `forkSession`). The SDK only re-streams
|
|
78
|
+
user messages, so the runner backfills the full prior transcript from the SDK's on-disk store
|
|
79
|
+
as `replay: true` events before the query starts (`backfillHistory: false` to skip).
|
|
80
|
+
- **Capabilities + usage** — after init (and eagerly for promptless sessions) the runner fetches
|
|
81
|
+
supported models/slash commands and a context-window snapshot, emitting `capabilities` and
|
|
82
|
+
`context_usage` events; context usage is re-polled after every turn.
|
|
83
|
+
|
|
84
|
+
## The second engine
|
|
85
|
+
|
|
86
|
+
`AiSdkRunner` runs the same protocol against any provider the [AI SDK](https://ai-sdk.dev)
|
|
87
|
+
supports — no CLI process, no config directory. `createEngineSession()` assembles one: the model,
|
|
88
|
+
the capability-scoped tool set, and the executor that runs tool calls.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { createEngineSession, QuickJsExecutor } from '@workerdeck/core'
|
|
92
|
+
|
|
93
|
+
const runner = createEngineSession({
|
|
94
|
+
config: { ...createSessionRequest, languageModel: anthropic('claude-sonnet-5') },
|
|
95
|
+
selectExecutor: () => new QuickJsExecutor({ timeoutMs: 15_000 }),
|
|
96
|
+
capabilities: { webFetch: {} }, // backends, not grants
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Two seams matter here:
|
|
101
|
+
|
|
102
|
+
- **Capabilities are grants, wired separately from backends.** `createToolContext` builds the tool
|
|
103
|
+
set from what a profile grants (`fs_*`, `eval_script`, `web_search`, `download`, `web_fetch`,
|
|
104
|
+
`deliver_file`) over what the host actually wired. There is no shell and no host filesystem: the
|
|
105
|
+
files a session sees are an in-memory scratch VFS. Every tool is typed `sandboxed` or
|
|
106
|
+
`authoritative`, and only sandboxed calls may leave the server.
|
|
107
|
+
- **`ToolExecutor` decides where code runs.** `QuickJsExecutor` runs it in-process in the
|
|
108
|
+
[QuickJS guest](https://www.npmjs.com/package/@workerdeck/sandbox); `BrowserBridgeExecutor`
|
|
109
|
+
ships it to the user's own tab, so client-held documents never reach the server; and
|
|
110
|
+
`DeferredExecutor` hands the call off to something that will answer later.
|
|
111
|
+
|
|
112
|
+
## Work that outlives the runner
|
|
113
|
+
|
|
114
|
+
`DeferredExecutor` dispatches a call and doesn't wait. The runner then **parks**: `park()` returns
|
|
115
|
+
a `RunnerSnapshot`, the process can tear the runner down, and passing that snapshot back as
|
|
116
|
+
`restore` rebuilds the session as itself — same id, same event log, same seq numbering, mid-turn,
|
|
117
|
+
scratch filesystem included.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
selectExecutor: () => new DeferredExecutor({
|
|
121
|
+
timeoutMs: 86_400_000, // watchdog; a timeout reaches the agent as tool output
|
|
122
|
+
onDispatch: (call) => enqueueForYourWorkers(call), // call.executionId is the callback address
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
[`@workerdeck/server`](https://www.npmjs.com/package/@workerdeck/server) drives both halves
|
|
127
|
+
for you — a `SessionStore` plus `POST /executions/:id/result` — but the mechanism is here, and works
|
|
128
|
+
with no server at all.
|
|
129
|
+
|
|
130
|
+
## Also exported
|
|
131
|
+
|
|
132
|
+
`InputQueue` (the push-based `AsyncIterable` bridging `sendMessage()` into the SDK's streaming
|
|
133
|
+
prompt), `normalizeSdkMessage`/`toApiMessage` (SDKMessage → protocol event normalization),
|
|
134
|
+
`connectMcpTools` for live MCP over http/sse, and `createWebFetch` with its SSRF guard
|
|
135
|
+
(`isPrivateAddress`). Tests inject a fake `queryFn` — no real CLI spawn needed.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT © Tobias Strebitzer —
|
|
140
|
+
[LICENSE](https://github.com/workerdeck/workerdeck/blob/master/LICENSE)
|