@workerdeck/client 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 +90 -0
- package/build/index.d.mts +162 -0
- package/build/index.mjs +372 -0
- package/build/index.mjs.map +1 -0
- package/package.json +58 -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,90 @@
|
|
|
1
|
+
# @workerdeck/client
|
|
2
|
+
|
|
3
|
+
Typed WorkerDeck protocol client for browsers and Node: REST session management plus a
|
|
4
|
+
WebSocket attach with auto-reconnect and replay-from-last-seq. Uses the platform's `fetch` and
|
|
5
|
+
`WebSocket`; zero runtime dependencies beyond the wire types.
|
|
6
|
+
|
|
7
|
+
Part of [WorkerDeck](https://github.com/workerdeck/workerdeck). It speaks the
|
|
8
|
+
[`@workerdeck/protocol`](https://www.npmjs.com/package/@workerdeck/protocol) wire format to a
|
|
9
|
+
running [`@workerdeck/server`](https://www.npmjs.com/package/@workerdeck/server) gateway.
|
|
10
|
+
Layers above build on it:
|
|
11
|
+
[`@workerdeck/react`](https://www.npmjs.com/package/@workerdeck/react) (headless hook +
|
|
12
|
+
transcript reducer) and [`@workerdeck/ui`](https://www.npmjs.com/package/@workerdeck/ui)
|
|
13
|
+
(styled session panel).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @workerdeck/client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Pairs with a running `@workerdeck/server` — the client is just the typed caller.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { WorkerDeckClient } from '@workerdeck/client'
|
|
27
|
+
|
|
28
|
+
const client = new WorkerDeckClient({
|
|
29
|
+
baseUrl: 'http://127.0.0.1:8787/v1', // ws:// URL is derived from it
|
|
30
|
+
headers: { authorization: 'Bearer …' }, // REST auth; use buildWsUrl/cookies for WS auth
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const session = await client.createSession({
|
|
34
|
+
cwd: '/srv/checkouts/my-repo',
|
|
35
|
+
prompt: '/verify-content 42',
|
|
36
|
+
settingSources: ['user', 'project'],
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const handle = client.attach(session.id) // auto-reconnects, replays from last seen seq
|
|
40
|
+
handle.on('attached', (frame) => console.log('snapshot', frame.session.status))
|
|
41
|
+
handle.on('event', (event) => console.log(event.seq, event.type))
|
|
42
|
+
handle.on('connectionChange', (up) => console.log(up ? 'connected' : 'reconnecting'))
|
|
43
|
+
|
|
44
|
+
handle.send('also run the tests')
|
|
45
|
+
handle.approve(requestId) // permission decisions
|
|
46
|
+
handle.deny(requestId, 'not this file')
|
|
47
|
+
handle.interrupt()
|
|
48
|
+
handle.setPermissionMode('acceptEdits')
|
|
49
|
+
handle.detach() // disconnect without touching the session
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`attach()` accepts `{ afterSeq, reconnect }`. On reconnect the handle asks the server for events
|
|
53
|
+
after the last seq it saw, so the stream is gapless and duplicates are dropped; commands sent
|
|
54
|
+
while disconnected are buffered and flushed on reopen. REST surface:
|
|
55
|
+
`createSession` / `listSessions` / `getSession` / `deleteSession`, `resolvePermission` (answer a
|
|
56
|
+
pending approval or `AskUserQuestion` over REST), and `listSdkSessions` (on-disk sessions to feed
|
|
57
|
+
`createSession({ resume })`).
|
|
58
|
+
|
|
59
|
+
### Job queue
|
|
60
|
+
|
|
61
|
+
Against a server configured with `queue`:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const job = await client.createJob({
|
|
65
|
+
session: { cwd: '/srv/checkout', prompt: '/verify-content 42' },
|
|
66
|
+
webhook: { url: 'https://my-app.test/hooks/claude' },
|
|
67
|
+
attempts: 3,
|
|
68
|
+
})
|
|
69
|
+
await client.getJob(job.id) // plus listJobs(), cancelJob(id), queueStats()
|
|
70
|
+
|
|
71
|
+
const queue = client.attachQueue() // read-only live stream over /queue/ws
|
|
72
|
+
queue.on('event', (e) => console.log(e.type, e.job.id))
|
|
73
|
+
queue.on('stats', (s) => console.log(s.running, 'running of', s.maxConcurrency))
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The queue stream has no replay: on (re)connect, re-list jobs and treat the stream as updates.
|
|
77
|
+
|
|
78
|
+
## Runtime
|
|
79
|
+
|
|
80
|
+
- **Browsers and Node** — built on platform `fetch` and `WebSocket` (global in Node ≥22). Both are
|
|
81
|
+
injectable (`fetchImpl`, `WebSocketImpl`) for older runtimes, polyfills, and tests.
|
|
82
|
+
- **Zero runtime dependencies** — the only dependency is `@workerdeck/protocol`, which is
|
|
83
|
+
itself dependency-free wire types.
|
|
84
|
+
- Browsers cannot set WS headers: authenticate the socket with a ticket query param via
|
|
85
|
+
`buildWsUrl(sessionId, afterSeq)` (and `buildQueueWsUrl`) or with cookies.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT © Tobias Strebitzer — see
|
|
90
|
+
[LICENSE](https://github.com/workerdeck/workerdeck/blob/master/LICENSE).
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { AttachedFrame, CreateJobRequest, CreateProfileRequest, CreateSessionRequest, GetProfileResponse, JobEvent, JobInfo, ListProfilesResponse, PermissionMode, ProfileInfo, QueueStats, ResolvePermissionRequest, SdkSessionSummary, SessionEvent, SessionFileInfo, SessionInfo, SubmitExecutionResultRequest, SubmitExecutionResultResponse, ToolCallRequestFrame, ToolExecutionOutput, UpdateProfileRequest } from "@workerdeck/protocol";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
type ClientOptions = {
|
|
5
|
+
/** REST base, e.g. "http://127.0.0.1:8787/v1". The ws:// URL is derived from it. */baseUrl: string;
|
|
6
|
+
/** Extra headers for REST calls (auth). Browsers can't set WS headers — use
|
|
7
|
+
* `buildWsUrl` (ticket query param) or cookies for WS auth. */
|
|
8
|
+
headers?: Record<string, string>; /** Override WS URL construction (auth tickets, proxies). */
|
|
9
|
+
buildWsUrl?: (sessionId: string, afterSeq: number) => string; /** Override the queue WS URL (`{baseUrl}/queue/ws` by default). */
|
|
10
|
+
buildQueueWsUrl?: () => string; /** Injectable for non-browser environments/tests. Defaults to globalThis.WebSocket. */
|
|
11
|
+
WebSocketImpl?: typeof WebSocket;
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
};
|
|
14
|
+
type AttachOptions = {
|
|
15
|
+
/** Replay events with seq greater than this. Default 0 (full replay). */afterSeq?: number; /** Auto-reconnect with backoff on unexpected disconnects. Default true. */
|
|
16
|
+
reconnect?: boolean;
|
|
17
|
+
};
|
|
18
|
+
type SessionHandleEvents = {
|
|
19
|
+
/** Fired on every (re)attach with the server's session snapshot. */attached: AttachedFrame; /** Every session event, replayed and live, in seq order. */
|
|
20
|
+
event: SessionEvent;
|
|
21
|
+
protocolError: string; /** WS connectivity: true on open, false on close. */
|
|
22
|
+
connectionChange: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The server is asking this client to execute a tool call in its own sandbox.
|
|
25
|
+
* Answer with {@link SessionHandle.sendToolCallResult} or
|
|
26
|
+
* {@link SessionHandle.sendToolCallError}, echoing the same `executionId`.
|
|
27
|
+
* Ignoring it is safe: the server fails the execution at `expiresAt`.
|
|
28
|
+
*/
|
|
29
|
+
toolCallRequest: ToolCallRequestFrame;
|
|
30
|
+
/** A bridged call no longer needs an answer (turn interrupted, timed out, or
|
|
31
|
+
* the session closed) — abandon any work in progress for this executionId. */
|
|
32
|
+
toolCallCanceled: {
|
|
33
|
+
executionId: string;
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
type Listener<T> = (payload: T) => void;
|
|
38
|
+
declare class SessionHandle {
|
|
39
|
+
#private;
|
|
40
|
+
readonly sessionId: string;
|
|
41
|
+
constructor(client: WorkerDeckClient, sessionId: string, options?: AttachOptions);
|
|
42
|
+
get lastSeq(): number;
|
|
43
|
+
on<K extends keyof SessionHandleEvents>(kind: K, listener: Listener<SessionHandleEvents[K]>): () => void;
|
|
44
|
+
send(text: string): void;
|
|
45
|
+
approve(requestId: string, updatedInput?: Record<string, unknown>): void;
|
|
46
|
+
deny(requestId: string, message?: string, interrupt?: boolean): void;
|
|
47
|
+
interrupt(): void;
|
|
48
|
+
setPermissionMode(mode: PermissionMode): void;
|
|
49
|
+
/** Switch the model for subsequent responses; omit `model` for the default. */
|
|
50
|
+
setModel(model?: string): void;
|
|
51
|
+
/** Answer a bridged tool call (see the `toolCallRequest` event). */
|
|
52
|
+
sendToolCallResult(executionId: string, output: ToolExecutionOutput, logs?: string[]): void;
|
|
53
|
+
/** Report that a bridged tool call could not be executed. The failure is fed
|
|
54
|
+
* to the model as tool output, so the agent can adapt rather than stall. */
|
|
55
|
+
sendToolCallError(executionId: string, reason: string, error: string, logs?: string[]): void;
|
|
56
|
+
/** Ask the server to terminate the session (the handle disconnects too). */
|
|
57
|
+
closeSession(): void;
|
|
58
|
+
/** Disconnect this handle without touching the session. */
|
|
59
|
+
detach(): void;
|
|
60
|
+
}
|
|
61
|
+
type QueueHandleEvents = {
|
|
62
|
+
/** Fired on every (re)attach with the server's current stats. */attached: QueueStats; /** Every job lifecycle/progress event, live. */
|
|
63
|
+
event: JobEvent; /** Refreshed stats pushed after job lifecycle changes. */
|
|
64
|
+
stats: QueueStats; /** WS connectivity: true on open, false on close. */
|
|
65
|
+
connectionChange: boolean;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Live view of the server's job queue over `{basePath}/queue/ws`. The stream is
|
|
69
|
+
* read-only — submit/cancel stay on the REST methods. There is no replay: on
|
|
70
|
+
* (re)connect, re-list jobs and treat the stream as updates from there.
|
|
71
|
+
*/
|
|
72
|
+
declare class QueueHandle {
|
|
73
|
+
#private;
|
|
74
|
+
constructor(client: WorkerDeckClient, options?: {
|
|
75
|
+
reconnect?: boolean;
|
|
76
|
+
});
|
|
77
|
+
on<K extends keyof QueueHandleEvents>(kind: K, listener: Listener<QueueHandleEvents[K]>): () => void;
|
|
78
|
+
detach(): void;
|
|
79
|
+
}
|
|
80
|
+
declare class WorkerDeckClient {
|
|
81
|
+
#private;
|
|
82
|
+
constructor(options: ClientOptions);
|
|
83
|
+
createSession(request: CreateSessionRequest): Promise<SessionInfo>;
|
|
84
|
+
listSessions(): Promise<SessionInfo[]>;
|
|
85
|
+
getSession(id: string): Promise<SessionInfo>;
|
|
86
|
+
deleteSession(id: string): Promise<SessionInfo>;
|
|
87
|
+
/** List the files currently in a session's scratch filesystem (deliverables the
|
|
88
|
+
* agent wrote; see the `file_delivered` event). 404s when the session's engine
|
|
89
|
+
* has no file store (Claude-engine sessions). */
|
|
90
|
+
listSessionFiles(sessionId: string): Promise<SessionFileInfo[]>;
|
|
91
|
+
/** Download one session file as text. */
|
|
92
|
+
fetchSessionFile(sessionId: string, path: string): Promise<string>;
|
|
93
|
+
/** Direct download URL for a session file (e.g. an <a download> href). Carries
|
|
94
|
+
* no headers — on authenticated servers, use fetchSessionFile instead. */
|
|
95
|
+
sessionFileUrl(sessionId: string, path: string): string;
|
|
96
|
+
/** Resolve a pending permission over REST — the remote-controller counterpart of the
|
|
97
|
+
* WS `permission_decision` command (e.g. answering a job's AskUserQuestion from a
|
|
98
|
+
* webhook consumer; the request rides on job_progress deliveries). Throws if the
|
|
99
|
+
* request is unknown, already resolved, or expired. */
|
|
100
|
+
resolvePermission(sessionId: string, requestId: string, decision: ResolvePermissionRequest): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Deliver the result of a deferred tool execution — the callback a remote
|
|
103
|
+
* worker (or a human) makes when the work a session parked on is done. The
|
|
104
|
+
* session is rehydrated if its runner was torn down, and the agent loop
|
|
105
|
+
* continues with this as the tool's output.
|
|
106
|
+
*
|
|
107
|
+
* Applied idempotently by `executionId`: a duplicate, or one racing the
|
|
108
|
+
* execution watchdog, resolves with `applied: false` instead of applying twice.
|
|
109
|
+
* Throws (404) when no session is waiting on that id.
|
|
110
|
+
*/
|
|
111
|
+
submitExecutionResult(executionId: string, result: SubmitExecutionResultRequest): Promise<SubmitExecutionResultResponse>;
|
|
112
|
+
/** List the profiles (named Claude Code config dirs) this server declares, filtered
|
|
113
|
+
* to what the caller may use. Feed a result's `name` to createSession({ profile }).
|
|
114
|
+
* Servers predating profiles 404 here — catch and treat as none declared. */
|
|
115
|
+
/** The profiles this caller may use, plus whether it may create new ones.
|
|
116
|
+
* Each profile carries `managed: true` when it is store-backed and therefore
|
|
117
|
+
* editable; profiles declared in server options are not. */
|
|
118
|
+
listProfiles(): Promise<ListProfilesResponse>;
|
|
119
|
+
/** One profile plus a fresh, view-only snapshot of its config directory (settings,
|
|
120
|
+
* skills, agents, commands — env var names only, never values). */
|
|
121
|
+
getProfile(name: string): Promise<GetProfileResponse>;
|
|
122
|
+
/**
|
|
123
|
+
* Create a managed profile. Requires a server with a profile store and a
|
|
124
|
+
* principal allowed to manage profiles; 409 if the name is already taken by a
|
|
125
|
+
* managed or a startup-declared profile.
|
|
126
|
+
*/
|
|
127
|
+
createProfile(profile: CreateProfileRequest): Promise<ProfileInfo>;
|
|
128
|
+
/** Merge into a managed profile. The name is the route: profiles cannot be
|
|
129
|
+
* renamed, since sessions and jobs are already pinned to the old one. */
|
|
130
|
+
updateProfile(name: string, patch: UpdateProfileRequest): Promise<ProfileInfo>;
|
|
131
|
+
/** Delete a managed profile. Startup-declared profiles are refused (403) —
|
|
132
|
+
* they live in the server's options. */
|
|
133
|
+
deleteProfile(name: string): Promise<void>;
|
|
134
|
+
/** List the Agent SDK's on-disk sessions (for resume across server restarts).
|
|
135
|
+
* Feed a result's `sessionId` to createSession({ resume }). */
|
|
136
|
+
listSdkSessions(params?: {
|
|
137
|
+
dir?: string;
|
|
138
|
+
limit?: number;
|
|
139
|
+
offset?: number;
|
|
140
|
+
}): Promise<SdkSessionSummary[]>;
|
|
141
|
+
/** Schedule a one-shot run. The returned job's `sessionId` (once running) can be
|
|
142
|
+
* fed to `attach()` to watch the run live. */
|
|
143
|
+
createJob(request: CreateJobRequest): Promise<JobInfo>;
|
|
144
|
+
listJobs(): Promise<JobInfo[]>;
|
|
145
|
+
getJob(id: string): Promise<JobInfo>;
|
|
146
|
+
/** Cancel a queued or running job. */
|
|
147
|
+
cancelJob(id: string): Promise<JobInfo>;
|
|
148
|
+
queueStats(): Promise<QueueStats>;
|
|
149
|
+
attach(sessionId: string, options?: AttachOptions): SessionHandle;
|
|
150
|
+
/** Stream the job queue live (requires the server to be configured with `queue`).
|
|
151
|
+
* Servers without a queue refuse the socket — check REST first or expect retries. */
|
|
152
|
+
attachQueue(options?: {
|
|
153
|
+
reconnect?: boolean;
|
|
154
|
+
}): QueueHandle;
|
|
155
|
+
/** @internal used by SessionHandle */
|
|
156
|
+
openSocket(sessionId: string, afterSeq: number): WebSocket;
|
|
157
|
+
/** @internal used by QueueHandle */
|
|
158
|
+
openQueueSocket(): WebSocket;
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { AttachOptions, ClientOptions, QueueHandle, QueueHandleEvents, SessionHandle, SessionHandleEvents, WorkerDeckClient };
|
|
162
|
+
//# sourceMappingURL=index.d.mts.map
|
package/build/index.mjs
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
var SessionHandle = class {
|
|
3
|
+
sessionId;
|
|
4
|
+
#client;
|
|
5
|
+
#options;
|
|
6
|
+
#ws;
|
|
7
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
8
|
+
#lastSeq;
|
|
9
|
+
#closed = false;
|
|
10
|
+
#retries = 0;
|
|
11
|
+
#outbox = [];
|
|
12
|
+
#connectTimer;
|
|
13
|
+
constructor(client, sessionId, options = {}) {
|
|
14
|
+
this.#client = client;
|
|
15
|
+
this.sessionId = sessionId;
|
|
16
|
+
this.#options = {
|
|
17
|
+
reconnect: true,
|
|
18
|
+
...options
|
|
19
|
+
};
|
|
20
|
+
this.#lastSeq = options.afterSeq ?? 0;
|
|
21
|
+
this.#connectTimer = setTimeout(() => this.#connect(), 0);
|
|
22
|
+
}
|
|
23
|
+
get lastSeq() {
|
|
24
|
+
return this.#lastSeq;
|
|
25
|
+
}
|
|
26
|
+
on(kind, listener) {
|
|
27
|
+
let set = this.#listeners.get(kind);
|
|
28
|
+
if (!set) {
|
|
29
|
+
set = /* @__PURE__ */ new Set();
|
|
30
|
+
this.#listeners.set(kind, set);
|
|
31
|
+
}
|
|
32
|
+
set.add(listener);
|
|
33
|
+
return () => set.delete(listener);
|
|
34
|
+
}
|
|
35
|
+
send(text) {
|
|
36
|
+
this.#sendFrame({
|
|
37
|
+
type: "user_message",
|
|
38
|
+
text
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
approve(requestId, updatedInput) {
|
|
42
|
+
this.#sendFrame({
|
|
43
|
+
type: "permission_decision",
|
|
44
|
+
requestId,
|
|
45
|
+
behavior: "allow",
|
|
46
|
+
updatedInput
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
deny(requestId, message, interrupt) {
|
|
50
|
+
this.#sendFrame({
|
|
51
|
+
type: "permission_decision",
|
|
52
|
+
requestId,
|
|
53
|
+
behavior: "deny",
|
|
54
|
+
message,
|
|
55
|
+
interrupt
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
interrupt() {
|
|
59
|
+
this.#sendFrame({ type: "interrupt" });
|
|
60
|
+
}
|
|
61
|
+
setPermissionMode(mode) {
|
|
62
|
+
this.#sendFrame({
|
|
63
|
+
type: "set_permission_mode",
|
|
64
|
+
mode
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Switch the model for subsequent responses; omit `model` for the default. */
|
|
68
|
+
setModel(model) {
|
|
69
|
+
this.#sendFrame({
|
|
70
|
+
type: "set_model",
|
|
71
|
+
model
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Answer a bridged tool call (see the `toolCallRequest` event). */
|
|
75
|
+
sendToolCallResult(executionId, output, logs) {
|
|
76
|
+
this.#sendFrame({
|
|
77
|
+
type: "tool_call_result",
|
|
78
|
+
executionId,
|
|
79
|
+
output,
|
|
80
|
+
logs
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Report that a bridged tool call could not be executed. The failure is fed
|
|
84
|
+
* to the model as tool output, so the agent can adapt rather than stall. */
|
|
85
|
+
sendToolCallError(executionId, reason, error, logs) {
|
|
86
|
+
this.#sendFrame({
|
|
87
|
+
type: "tool_call_error",
|
|
88
|
+
executionId,
|
|
89
|
+
reason,
|
|
90
|
+
error,
|
|
91
|
+
logs
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** Ask the server to terminate the session (the handle disconnects too). */
|
|
95
|
+
closeSession() {
|
|
96
|
+
this.#sendFrame({ type: "close" });
|
|
97
|
+
this.detach();
|
|
98
|
+
}
|
|
99
|
+
/** Disconnect this handle without touching the session. */
|
|
100
|
+
detach() {
|
|
101
|
+
this.#closed = true;
|
|
102
|
+
clearTimeout(this.#connectTimer);
|
|
103
|
+
this.#ws?.close();
|
|
104
|
+
this.#ws = void 0;
|
|
105
|
+
}
|
|
106
|
+
#emit(kind, payload) {
|
|
107
|
+
const set = this.#listeners.get(kind);
|
|
108
|
+
if (!set) return;
|
|
109
|
+
for (const listener of set) try {
|
|
110
|
+
listener(payload);
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
113
|
+
#sendFrame(frame) {
|
|
114
|
+
const payload = JSON.stringify(frame);
|
|
115
|
+
if (this.#ws && this.#ws.readyState === 1) this.#ws.send(payload);
|
|
116
|
+
else this.#outbox.push(payload);
|
|
117
|
+
}
|
|
118
|
+
#connect() {
|
|
119
|
+
if (this.#closed) return;
|
|
120
|
+
const ws = this.#client.openSocket(this.sessionId, this.#lastSeq);
|
|
121
|
+
this.#ws = ws;
|
|
122
|
+
ws.onopen = () => {
|
|
123
|
+
this.#retries = 0;
|
|
124
|
+
this.#emit("connectionChange", true);
|
|
125
|
+
for (const payload of this.#outbox.splice(0)) ws.send(payload);
|
|
126
|
+
};
|
|
127
|
+
ws.onmessage = (msg) => {
|
|
128
|
+
const frame = JSON.parse(String(msg.data));
|
|
129
|
+
if (frame.type === "attached") this.#emit("attached", frame);
|
|
130
|
+
else if (frame.type === "event") {
|
|
131
|
+
if (frame.event.seq <= this.#lastSeq) return;
|
|
132
|
+
this.#lastSeq = frame.event.seq;
|
|
133
|
+
this.#emit("event", frame.event);
|
|
134
|
+
} else if (frame.type === "tool_call_request") this.#emit("toolCallRequest", frame);
|
|
135
|
+
else if (frame.type === "tool_call_canceled") this.#emit("toolCallCanceled", {
|
|
136
|
+
executionId: frame.executionId,
|
|
137
|
+
reason: frame.reason
|
|
138
|
+
});
|
|
139
|
+
else if (frame.type === "protocol_error") this.#emit("protocolError", frame.message);
|
|
140
|
+
};
|
|
141
|
+
ws.onclose = () => {
|
|
142
|
+
this.#emit("connectionChange", false);
|
|
143
|
+
if (this.#closed || !this.#options.reconnect) return;
|
|
144
|
+
const delay = Math.min(500 * 2 ** this.#retries++, 1e4);
|
|
145
|
+
this.#connectTimer = setTimeout(() => this.#connect(), delay);
|
|
146
|
+
};
|
|
147
|
+
ws.onerror = () => {};
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Live view of the server's job queue over `{basePath}/queue/ws`. The stream is
|
|
152
|
+
* read-only — submit/cancel stay on the REST methods. There is no replay: on
|
|
153
|
+
* (re)connect, re-list jobs and treat the stream as updates from there.
|
|
154
|
+
*/
|
|
155
|
+
var QueueHandle = class {
|
|
156
|
+
#client;
|
|
157
|
+
#reconnect;
|
|
158
|
+
#ws;
|
|
159
|
+
#listeners = /* @__PURE__ */ new Map();
|
|
160
|
+
#closed = false;
|
|
161
|
+
#retries = 0;
|
|
162
|
+
#connectTimer;
|
|
163
|
+
constructor(client, options = {}) {
|
|
164
|
+
this.#client = client;
|
|
165
|
+
this.#reconnect = options.reconnect ?? true;
|
|
166
|
+
this.#connectTimer = setTimeout(() => this.#connect(), 0);
|
|
167
|
+
}
|
|
168
|
+
on(kind, listener) {
|
|
169
|
+
let set = this.#listeners.get(kind);
|
|
170
|
+
if (!set) {
|
|
171
|
+
set = /* @__PURE__ */ new Set();
|
|
172
|
+
this.#listeners.set(kind, set);
|
|
173
|
+
}
|
|
174
|
+
set.add(listener);
|
|
175
|
+
return () => set.delete(listener);
|
|
176
|
+
}
|
|
177
|
+
detach() {
|
|
178
|
+
this.#closed = true;
|
|
179
|
+
clearTimeout(this.#connectTimer);
|
|
180
|
+
this.#ws?.close();
|
|
181
|
+
this.#ws = void 0;
|
|
182
|
+
}
|
|
183
|
+
#emit(kind, payload) {
|
|
184
|
+
const set = this.#listeners.get(kind);
|
|
185
|
+
if (!set) return;
|
|
186
|
+
for (const listener of set) try {
|
|
187
|
+
listener(payload);
|
|
188
|
+
} catch {}
|
|
189
|
+
}
|
|
190
|
+
#connect() {
|
|
191
|
+
if (this.#closed) return;
|
|
192
|
+
const ws = this.#client.openQueueSocket();
|
|
193
|
+
this.#ws = ws;
|
|
194
|
+
ws.onopen = () => {
|
|
195
|
+
this.#retries = 0;
|
|
196
|
+
this.#emit("connectionChange", true);
|
|
197
|
+
};
|
|
198
|
+
ws.onmessage = (msg) => {
|
|
199
|
+
const frame = JSON.parse(String(msg.data));
|
|
200
|
+
if (frame.type === "queue_attached") {
|
|
201
|
+
this.#emit("attached", frame.stats);
|
|
202
|
+
this.#emit("stats", frame.stats);
|
|
203
|
+
} else if (frame.type === "job_event") this.#emit("event", frame.event);
|
|
204
|
+
else if (frame.type === "queue_stats") this.#emit("stats", frame.stats);
|
|
205
|
+
};
|
|
206
|
+
ws.onclose = () => {
|
|
207
|
+
this.#emit("connectionChange", false);
|
|
208
|
+
if (this.#closed || !this.#reconnect) return;
|
|
209
|
+
const delay = Math.min(500 * 2 ** this.#retries++, 1e4);
|
|
210
|
+
this.#connectTimer = setTimeout(() => this.#connect(), delay);
|
|
211
|
+
};
|
|
212
|
+
ws.onerror = () => {};
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var WorkerDeckClient = class {
|
|
216
|
+
#options;
|
|
217
|
+
#fetch;
|
|
218
|
+
#WebSocketImpl;
|
|
219
|
+
constructor(options) {
|
|
220
|
+
this.#options = options;
|
|
221
|
+
this.#fetch = options.fetchImpl ?? fetch.bind(globalThis);
|
|
222
|
+
this.#WebSocketImpl = options.WebSocketImpl ?? WebSocket;
|
|
223
|
+
}
|
|
224
|
+
async createSession(request) {
|
|
225
|
+
return (await this.#call("POST", "/sessions", request)).session;
|
|
226
|
+
}
|
|
227
|
+
async listSessions() {
|
|
228
|
+
return (await this.#call("GET", "/sessions")).sessions;
|
|
229
|
+
}
|
|
230
|
+
async getSession(id) {
|
|
231
|
+
return (await this.#call("GET", `/sessions/${encodeURIComponent(id)}`)).session;
|
|
232
|
+
}
|
|
233
|
+
async deleteSession(id) {
|
|
234
|
+
return (await this.#call("DELETE", `/sessions/${encodeURIComponent(id)}`)).session;
|
|
235
|
+
}
|
|
236
|
+
/** List the files currently in a session's scratch filesystem (deliverables the
|
|
237
|
+
* agent wrote; see the `file_delivered` event). 404s when the session's engine
|
|
238
|
+
* has no file store (Claude-engine sessions). */
|
|
239
|
+
async listSessionFiles(sessionId) {
|
|
240
|
+
return (await this.#call("GET", `/sessions/${encodeURIComponent(sessionId)}/files`)).files;
|
|
241
|
+
}
|
|
242
|
+
/** Download one session file as text. */
|
|
243
|
+
async fetchSessionFile(sessionId, path) {
|
|
244
|
+
const res = await this.#fetch(this.sessionFileUrl(sessionId, path), { headers: this.#options.headers });
|
|
245
|
+
if (!res.ok) {
|
|
246
|
+
const payload = await res.json().catch(() => ({}));
|
|
247
|
+
throw new Error(payload.error ?? `GET file failed with ${res.status}`);
|
|
248
|
+
}
|
|
249
|
+
return await res.text();
|
|
250
|
+
}
|
|
251
|
+
/** Direct download URL for a session file (e.g. an <a download> href). Carries
|
|
252
|
+
* no headers — on authenticated servers, use fetchSessionFile instead. */
|
|
253
|
+
sessionFileUrl(sessionId, path) {
|
|
254
|
+
const encoded = path.split("/").filter(Boolean).map(encodeURIComponent).join("/");
|
|
255
|
+
return `${this.#options.baseUrl}/sessions/${encodeURIComponent(sessionId)}/files/${encoded}`;
|
|
256
|
+
}
|
|
257
|
+
/** Resolve a pending permission over REST — the remote-controller counterpart of the
|
|
258
|
+
* WS `permission_decision` command (e.g. answering a job's AskUserQuestion from a
|
|
259
|
+
* webhook consumer; the request rides on job_progress deliveries). Throws if the
|
|
260
|
+
* request is unknown, already resolved, or expired. */
|
|
261
|
+
async resolvePermission(sessionId, requestId, decision) {
|
|
262
|
+
await this.#call("POST", `/sessions/${encodeURIComponent(sessionId)}/permissions/${encodeURIComponent(requestId)}`, decision);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Deliver the result of a deferred tool execution — the callback a remote
|
|
266
|
+
* worker (or a human) makes when the work a session parked on is done. The
|
|
267
|
+
* session is rehydrated if its runner was torn down, and the agent loop
|
|
268
|
+
* continues with this as the tool's output.
|
|
269
|
+
*
|
|
270
|
+
* Applied idempotently by `executionId`: a duplicate, or one racing the
|
|
271
|
+
* execution watchdog, resolves with `applied: false` instead of applying twice.
|
|
272
|
+
* Throws (404) when no session is waiting on that id.
|
|
273
|
+
*/
|
|
274
|
+
async submitExecutionResult(executionId, result) {
|
|
275
|
+
return await this.#call("POST", `/executions/${encodeURIComponent(executionId)}/result`, result);
|
|
276
|
+
}
|
|
277
|
+
/** List the profiles (named Claude Code config dirs) this server declares, filtered
|
|
278
|
+
* to what the caller may use. Feed a result's `name` to createSession({ profile }).
|
|
279
|
+
* Servers predating profiles 404 here — catch and treat as none declared. */
|
|
280
|
+
/** The profiles this caller may use, plus whether it may create new ones.
|
|
281
|
+
* Each profile carries `managed: true` when it is store-backed and therefore
|
|
282
|
+
* editable; profiles declared in server options are not. */
|
|
283
|
+
async listProfiles() {
|
|
284
|
+
return await this.#call("GET", "/profiles");
|
|
285
|
+
}
|
|
286
|
+
/** One profile plus a fresh, view-only snapshot of its config directory (settings,
|
|
287
|
+
* skills, agents, commands — env var names only, never values). */
|
|
288
|
+
async getProfile(name) {
|
|
289
|
+
return await this.#call("GET", `/profiles/${encodeURIComponent(name)}`);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Create a managed profile. Requires a server with a profile store and a
|
|
293
|
+
* principal allowed to manage profiles; 409 if the name is already taken by a
|
|
294
|
+
* managed or a startup-declared profile.
|
|
295
|
+
*/
|
|
296
|
+
async createProfile(profile) {
|
|
297
|
+
return (await this.#call("POST", "/profiles", profile)).profile;
|
|
298
|
+
}
|
|
299
|
+
/** Merge into a managed profile. The name is the route: profiles cannot be
|
|
300
|
+
* renamed, since sessions and jobs are already pinned to the old one. */
|
|
301
|
+
async updateProfile(name, patch) {
|
|
302
|
+
return (await this.#call("PATCH", `/profiles/${encodeURIComponent(name)}`, patch)).profile;
|
|
303
|
+
}
|
|
304
|
+
/** Delete a managed profile. Startup-declared profiles are refused (403) —
|
|
305
|
+
* they live in the server's options. */
|
|
306
|
+
async deleteProfile(name) {
|
|
307
|
+
await this.#call("DELETE", `/profiles/${encodeURIComponent(name)}`);
|
|
308
|
+
}
|
|
309
|
+
/** List the Agent SDK's on-disk sessions (for resume across server restarts).
|
|
310
|
+
* Feed a result's `sessionId` to createSession({ resume }). */
|
|
311
|
+
async listSdkSessions(params) {
|
|
312
|
+
const search = new URLSearchParams();
|
|
313
|
+
if (params?.dir) search.set("dir", params.dir);
|
|
314
|
+
if (params?.limit !== void 0) search.set("limit", String(params.limit));
|
|
315
|
+
if (params?.offset !== void 0) search.set("offset", String(params.offset));
|
|
316
|
+
const qs = search.size > 0 ? `?${search.toString()}` : "";
|
|
317
|
+
return (await this.#call("GET", `/sdk-sessions${qs}`)).sdkSessions;
|
|
318
|
+
}
|
|
319
|
+
/** Schedule a one-shot run. The returned job's `sessionId` (once running) can be
|
|
320
|
+
* fed to `attach()` to watch the run live. */
|
|
321
|
+
async createJob(request) {
|
|
322
|
+
return (await this.#call("POST", "/jobs", request)).job;
|
|
323
|
+
}
|
|
324
|
+
async listJobs() {
|
|
325
|
+
return (await this.#call("GET", "/jobs")).jobs;
|
|
326
|
+
}
|
|
327
|
+
async getJob(id) {
|
|
328
|
+
return (await this.#call("GET", `/jobs/${encodeURIComponent(id)}`)).job;
|
|
329
|
+
}
|
|
330
|
+
/** Cancel a queued or running job. */
|
|
331
|
+
async cancelJob(id) {
|
|
332
|
+
return (await this.#call("DELETE", `/jobs/${encodeURIComponent(id)}`)).job;
|
|
333
|
+
}
|
|
334
|
+
async queueStats() {
|
|
335
|
+
return (await this.#call("GET", "/queue")).stats;
|
|
336
|
+
}
|
|
337
|
+
attach(sessionId, options) {
|
|
338
|
+
return new SessionHandle(this, sessionId, options);
|
|
339
|
+
}
|
|
340
|
+
/** Stream the job queue live (requires the server to be configured with `queue`).
|
|
341
|
+
* Servers without a queue refuse the socket — check REST first or expect retries. */
|
|
342
|
+
attachQueue(options) {
|
|
343
|
+
return new QueueHandle(this, options);
|
|
344
|
+
}
|
|
345
|
+
/** @internal used by SessionHandle */
|
|
346
|
+
openSocket(sessionId, afterSeq) {
|
|
347
|
+
const url = this.#options.buildWsUrl?.(sessionId, afterSeq) ?? `${this.#options.baseUrl.replace(/^http/, "ws")}/sessions/${encodeURIComponent(sessionId)}/ws?afterSeq=${afterSeq}`;
|
|
348
|
+
return new this.#WebSocketImpl(url);
|
|
349
|
+
}
|
|
350
|
+
/** @internal used by QueueHandle */
|
|
351
|
+
openQueueSocket() {
|
|
352
|
+
const url = this.#options.buildQueueWsUrl?.() ?? `${this.#options.baseUrl.replace(/^http/, "ws")}/queue/ws`;
|
|
353
|
+
return new this.#WebSocketImpl(url);
|
|
354
|
+
}
|
|
355
|
+
async #call(method, path, body) {
|
|
356
|
+
const res = await this.#fetch(`${this.#options.baseUrl}${path}`, {
|
|
357
|
+
method,
|
|
358
|
+
headers: {
|
|
359
|
+
...body !== void 0 ? { "content-type": "application/json" } : {},
|
|
360
|
+
...this.#options.headers
|
|
361
|
+
},
|
|
362
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
363
|
+
});
|
|
364
|
+
const payload = await res.json().catch(() => ({}));
|
|
365
|
+
if (!res.ok) throw new Error(payload.error ?? `${method} ${path} failed with ${res.status}`);
|
|
366
|
+
return payload;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
//#endregion
|
|
370
|
+
export { QueueHandle, SessionHandle, WorkerDeckClient };
|
|
371
|
+
|
|
372
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#client","#options","#lastSeq","#connectTimer","#connect","#listeners","#sendFrame","#closed","#ws","#outbox","#retries","#emit","#reconnect","#fetch","#WebSocketImpl","#call"],"sources":["../src/index.ts"],"sourcesContent":["import type {\n AttachedFrame,\n ClientFrame,\n CreateJobRequest,\n CreateProfileRequest,\n CreateSessionRequest,\n JobEvent,\n JobInfo,\n GetProfileResponse,\n ListProfilesResponse,\n ListSessionFilesResponse,\n PermissionMode,\n ProfileInfo,\n QueueServerFrame,\n QueueStats,\n ResolvePermissionRequest,\n SubmitExecutionResultRequest,\n SubmitExecutionResultResponse,\n SaveProfileResponse,\n SdkSessionSummary,\n ServerFrame,\n SessionEvent,\n SessionFileInfo,\n SessionInfo,\n ToolCallRequestFrame,\n ToolExecutionOutput,\n UpdateProfileRequest,\n} from '@workerdeck/protocol'\n\nexport type ClientOptions = {\n /** REST base, e.g. \"http://127.0.0.1:8787/v1\". The ws:// URL is derived from it. */\n baseUrl: string\n /** Extra headers for REST calls (auth). Browsers can't set WS headers — use\n * `buildWsUrl` (ticket query param) or cookies for WS auth. */\n headers?: Record<string, string>\n /** Override WS URL construction (auth tickets, proxies). */\n buildWsUrl?: (sessionId: string, afterSeq: number) => string\n /** Override the queue WS URL (`{baseUrl}/queue/ws` by default). */\n buildQueueWsUrl?: () => string\n /** Injectable for non-browser environments/tests. Defaults to globalThis.WebSocket. */\n WebSocketImpl?: typeof WebSocket\n fetchImpl?: typeof fetch\n}\n\nexport type AttachOptions = {\n /** Replay events with seq greater than this. Default 0 (full replay). */\n afterSeq?: number\n /** Auto-reconnect with backoff on unexpected disconnects. Default true. */\n reconnect?: boolean\n}\n\nexport type SessionHandleEvents = {\n /** Fired on every (re)attach with the server's session snapshot. */\n attached: AttachedFrame\n /** Every session event, replayed and live, in seq order. */\n event: SessionEvent\n protocolError: string\n /** WS connectivity: true on open, false on close. */\n connectionChange: boolean\n /**\n * The server is asking this client to execute a tool call in its own sandbox.\n * Answer with {@link SessionHandle.sendToolCallResult} or\n * {@link SessionHandle.sendToolCallError}, echoing the same `executionId`.\n * Ignoring it is safe: the server fails the execution at `expiresAt`.\n */\n toolCallRequest: ToolCallRequestFrame\n /** A bridged call no longer needs an answer (turn interrupted, timed out, or\n * the session closed) — abandon any work in progress for this executionId. */\n toolCallCanceled: { executionId: string; reason: string }\n}\n\ntype Listener<T> = (payload: T) => void\n\nexport class SessionHandle {\n readonly sessionId: string\n #client: WorkerDeckClient\n #options: Required<Pick<AttachOptions, 'reconnect'>> & AttachOptions\n #ws: WebSocket | undefined\n #listeners = new Map<keyof SessionHandleEvents, Set<Listener<never>>>()\n #lastSeq: number\n #closed = false\n #retries = 0\n #outbox: string[] = []\n #connectTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(client: WorkerDeckClient, sessionId: string, options: AttachOptions = {}) {\n this.#client = client\n this.sessionId = sessionId\n this.#options = { reconnect: true, ...options }\n this.#lastSeq = options.afterSeq ?? 0\n // Deferred a tick so an attach that is detached in the same tick (React\n // StrictMode's throwaway dev mount) never opens a socket — closing a\n // WebSocket mid-upgrade breaks proxies (vite logs EPIPE) for nothing.\n this.#connectTimer = setTimeout(() => this.#connect(), 0)\n }\n\n get lastSeq(): number {\n return this.#lastSeq\n }\n\n on<K extends keyof SessionHandleEvents>(\n kind: K,\n listener: Listener<SessionHandleEvents[K]>,\n ): () => void {\n let set = this.#listeners.get(kind)\n if (!set) {\n set = new Set()\n this.#listeners.set(kind, set)\n }\n set.add(listener as Listener<never>)\n return () => set.delete(listener as Listener<never>)\n }\n\n send(text: string): void {\n this.#sendFrame({ type: 'user_message', text })\n }\n\n approve(requestId: string, updatedInput?: Record<string, unknown>): void {\n this.#sendFrame({ type: 'permission_decision', requestId, behavior: 'allow', updatedInput })\n }\n\n deny(requestId: string, message?: string, interrupt?: boolean): void {\n this.#sendFrame({ type: 'permission_decision', requestId, behavior: 'deny', message, interrupt })\n }\n\n interrupt(): void {\n this.#sendFrame({ type: 'interrupt' })\n }\n\n setPermissionMode(mode: PermissionMode): void {\n this.#sendFrame({ type: 'set_permission_mode', mode })\n }\n\n /** Switch the model for subsequent responses; omit `model` for the default. */\n setModel(model?: string): void {\n this.#sendFrame({ type: 'set_model', model })\n }\n\n /** Answer a bridged tool call (see the `toolCallRequest` event). */\n sendToolCallResult(executionId: string, output: ToolExecutionOutput, logs?: string[]): void {\n this.#sendFrame({ type: 'tool_call_result', executionId, output, logs })\n }\n\n /** Report that a bridged tool call could not be executed. The failure is fed\n * to the model as tool output, so the agent can adapt rather than stall. */\n sendToolCallError(executionId: string, reason: string, error: string, logs?: string[]): void {\n this.#sendFrame({ type: 'tool_call_error', executionId, reason, error, logs })\n }\n\n /** Ask the server to terminate the session (the handle disconnects too). */\n closeSession(): void {\n this.#sendFrame({ type: 'close' })\n this.detach()\n }\n\n /** Disconnect this handle without touching the session. */\n detach(): void {\n this.#closed = true\n clearTimeout(this.#connectTimer)\n this.#ws?.close()\n this.#ws = undefined\n }\n\n #emit<K extends keyof SessionHandleEvents>(kind: K, payload: SessionHandleEvents[K]): void {\n const set = this.#listeners.get(kind)\n if (!set) return\n for (const listener of set) {\n try {\n ;(listener as Listener<SessionHandleEvents[K]>)(payload)\n } catch {\n // listener errors must not break the stream\n }\n }\n }\n\n #sendFrame(frame: ClientFrame): void {\n const payload = JSON.stringify(frame)\n // readyState 1 === OPEN (avoid touching the WebSocket global; impl may be injected)\n if (this.#ws && this.#ws.readyState === 1) this.#ws.send(payload)\n else this.#outbox.push(payload)\n }\n\n #connect(): void {\n if (this.#closed) return\n const ws = this.#client.openSocket(this.sessionId, this.#lastSeq)\n this.#ws = ws\n ws.onopen = () => {\n this.#retries = 0\n this.#emit('connectionChange', true)\n for (const payload of this.#outbox.splice(0)) ws.send(payload)\n }\n ws.onmessage = (msg: MessageEvent) => {\n const frame = JSON.parse(String(msg.data)) as ServerFrame\n if (frame.type === 'attached') {\n this.#emit('attached', frame)\n } else if (frame.type === 'event') {\n if (frame.event.seq <= this.#lastSeq) return\n this.#lastSeq = frame.event.seq\n this.#emit('event', frame.event)\n } else if (frame.type === 'tool_call_request') {\n this.#emit('toolCallRequest', frame)\n } else if (frame.type === 'tool_call_canceled') {\n this.#emit('toolCallCanceled', { executionId: frame.executionId, reason: frame.reason })\n } else if (frame.type === 'protocol_error') {\n this.#emit('protocolError', frame.message)\n }\n }\n ws.onclose = () => {\n this.#emit('connectionChange', false)\n if (this.#closed || !this.#options.reconnect) return\n const delay = Math.min(500 * 2 ** this.#retries++, 10_000)\n this.#connectTimer = setTimeout(() => this.#connect(), delay)\n }\n ws.onerror = () => {\n // onclose follows; reconnect handled there\n }\n }\n}\n\nexport type QueueHandleEvents = {\n /** Fired on every (re)attach with the server's current stats. */\n attached: QueueStats\n /** Every job lifecycle/progress event, live. */\n event: JobEvent\n /** Refreshed stats pushed after job lifecycle changes. */\n stats: QueueStats\n /** WS connectivity: true on open, false on close. */\n connectionChange: boolean\n}\n\n/**\n * Live view of the server's job queue over `{basePath}/queue/ws`. The stream is\n * read-only — submit/cancel stay on the REST methods. There is no replay: on\n * (re)connect, re-list jobs and treat the stream as updates from there.\n */\nexport class QueueHandle {\n #client: WorkerDeckClient\n #reconnect: boolean\n #ws: WebSocket | undefined\n #listeners = new Map<keyof QueueHandleEvents, Set<Listener<never>>>()\n #closed = false\n #retries = 0\n #connectTimer: ReturnType<typeof setTimeout> | undefined\n\n constructor(client: WorkerDeckClient, options: { reconnect?: boolean } = {}) {\n this.#client = client\n this.#reconnect = options.reconnect ?? true\n // Deferred a tick for the same StrictMode reason as SessionHandle.\n this.#connectTimer = setTimeout(() => this.#connect(), 0)\n }\n\n on<K extends keyof QueueHandleEvents>(\n kind: K,\n listener: Listener<QueueHandleEvents[K]>,\n ): () => void {\n let set = this.#listeners.get(kind)\n if (!set) {\n set = new Set()\n this.#listeners.set(kind, set)\n }\n set.add(listener as Listener<never>)\n return () => set.delete(listener as Listener<never>)\n }\n\n detach(): void {\n this.#closed = true\n clearTimeout(this.#connectTimer)\n this.#ws?.close()\n this.#ws = undefined\n }\n\n #emit<K extends keyof QueueHandleEvents>(kind: K, payload: QueueHandleEvents[K]): void {\n const set = this.#listeners.get(kind)\n if (!set) return\n for (const listener of set) {\n try {\n ;(listener as Listener<QueueHandleEvents[K]>)(payload)\n } catch {\n // listener errors must not break the stream\n }\n }\n }\n\n #connect(): void {\n if (this.#closed) return\n const ws = this.#client.openQueueSocket()\n this.#ws = ws\n ws.onopen = () => {\n this.#retries = 0\n this.#emit('connectionChange', true)\n }\n ws.onmessage = (msg: MessageEvent) => {\n const frame = JSON.parse(String(msg.data)) as QueueServerFrame\n if (frame.type === 'queue_attached') {\n this.#emit('attached', frame.stats)\n this.#emit('stats', frame.stats)\n } else if (frame.type === 'job_event') {\n this.#emit('event', frame.event)\n } else if (frame.type === 'queue_stats') {\n this.#emit('stats', frame.stats)\n }\n }\n ws.onclose = () => {\n this.#emit('connectionChange', false)\n if (this.#closed || !this.#reconnect) return\n const delay = Math.min(500 * 2 ** this.#retries++, 10_000)\n this.#connectTimer = setTimeout(() => this.#connect(), delay)\n }\n ws.onerror = () => {\n // onclose follows; reconnect handled there\n }\n }\n}\n\nexport class WorkerDeckClient {\n #options: ClientOptions\n #fetch: typeof fetch\n #WebSocketImpl: typeof WebSocket\n\n constructor(options: ClientOptions) {\n this.#options = options\n this.#fetch = options.fetchImpl ?? fetch.bind(globalThis)\n this.#WebSocketImpl = options.WebSocketImpl ?? WebSocket\n }\n\n async createSession(request: CreateSessionRequest): Promise<SessionInfo> {\n const body = await this.#call('POST', '/sessions', request)\n return (body as { session: SessionInfo }).session\n }\n\n async listSessions(): Promise<SessionInfo[]> {\n const body = await this.#call('GET', '/sessions')\n return (body as { sessions: SessionInfo[] }).sessions\n }\n\n async getSession(id: string): Promise<SessionInfo> {\n const body = await this.#call('GET', `/sessions/${encodeURIComponent(id)}`)\n return (body as { session: SessionInfo }).session\n }\n\n async deleteSession(id: string): Promise<SessionInfo> {\n const body = await this.#call('DELETE', `/sessions/${encodeURIComponent(id)}`)\n return (body as { session: SessionInfo }).session\n }\n\n /** List the files currently in a session's scratch filesystem (deliverables the\n * agent wrote; see the `file_delivered` event). 404s when the session's engine\n * has no file store (Claude-engine sessions). */\n async listSessionFiles(sessionId: string): Promise<SessionFileInfo[]> {\n const body = await this.#call('GET', `/sessions/${encodeURIComponent(sessionId)}/files`)\n return (body as ListSessionFilesResponse).files\n }\n\n /** Download one session file as text. */\n async fetchSessionFile(sessionId: string, path: string): Promise<string> {\n const res = await this.#fetch(this.sessionFileUrl(sessionId, path), {\n headers: this.#options.headers,\n })\n if (!res.ok) {\n const payload = (await res.json().catch(() => ({}))) as { error?: string }\n throw new Error(payload.error ?? `GET file failed with ${res.status}`)\n }\n return await res.text()\n }\n\n /** Direct download URL for a session file (e.g. an <a download> href). Carries\n * no headers — on authenticated servers, use fetchSessionFile instead. */\n sessionFileUrl(sessionId: string, path: string): string {\n const encoded = path\n .split('/')\n .filter(Boolean)\n .map(encodeURIComponent)\n .join('/')\n return `${this.#options.baseUrl}/sessions/${encodeURIComponent(sessionId)}/files/${encoded}`\n }\n\n /** Resolve a pending permission over REST — the remote-controller counterpart of the\n * WS `permission_decision` command (e.g. answering a job's AskUserQuestion from a\n * webhook consumer; the request rides on job_progress deliveries). Throws if the\n * request is unknown, already resolved, or expired. */\n async resolvePermission(\n sessionId: string,\n requestId: string,\n decision: ResolvePermissionRequest,\n ): Promise<void> {\n await this.#call(\n 'POST',\n `/sessions/${encodeURIComponent(sessionId)}/permissions/${encodeURIComponent(requestId)}`,\n decision,\n )\n }\n\n /**\n * Deliver the result of a deferred tool execution — the callback a remote\n * worker (or a human) makes when the work a session parked on is done. The\n * session is rehydrated if its runner was torn down, and the agent loop\n * continues with this as the tool's output.\n *\n * Applied idempotently by `executionId`: a duplicate, or one racing the\n * execution watchdog, resolves with `applied: false` instead of applying twice.\n * Throws (404) when no session is waiting on that id.\n */\n async submitExecutionResult(\n executionId: string,\n result: SubmitExecutionResultRequest,\n ): Promise<SubmitExecutionResultResponse> {\n return (await this.#call(\n 'POST',\n `/executions/${encodeURIComponent(executionId)}/result`,\n result,\n )) as SubmitExecutionResultResponse\n }\n\n /** List the profiles (named Claude Code config dirs) this server declares, filtered\n * to what the caller may use. Feed a result's `name` to createSession({ profile }).\n * Servers predating profiles 404 here — catch and treat as none declared. */\n /** The profiles this caller may use, plus whether it may create new ones.\n * Each profile carries `managed: true` when it is store-backed and therefore\n * editable; profiles declared in server options are not. */\n async listProfiles(): Promise<ListProfilesResponse> {\n return (await this.#call('GET', '/profiles')) as ListProfilesResponse\n }\n\n /** One profile plus a fresh, view-only snapshot of its config directory (settings,\n * skills, agents, commands — env var names only, never values). */\n async getProfile(name: string): Promise<GetProfileResponse> {\n return (await this.#call('GET', `/profiles/${encodeURIComponent(name)}`)) as GetProfileResponse\n }\n\n /**\n * Create a managed profile. Requires a server with a profile store and a\n * principal allowed to manage profiles; 409 if the name is already taken by a\n * managed or a startup-declared profile.\n */\n async createProfile(profile: CreateProfileRequest): Promise<ProfileInfo> {\n const body = await this.#call('POST', '/profiles', profile)\n return (body as SaveProfileResponse).profile\n }\n\n /** Merge into a managed profile. The name is the route: profiles cannot be\n * renamed, since sessions and jobs are already pinned to the old one. */\n async updateProfile(name: string, patch: UpdateProfileRequest): Promise<ProfileInfo> {\n const body = await this.#call('PATCH', `/profiles/${encodeURIComponent(name)}`, patch)\n return (body as SaveProfileResponse).profile\n }\n\n /** Delete a managed profile. Startup-declared profiles are refused (403) —\n * they live in the server's options. */\n async deleteProfile(name: string): Promise<void> {\n await this.#call('DELETE', `/profiles/${encodeURIComponent(name)}`)\n }\n\n /** List the Agent SDK's on-disk sessions (for resume across server restarts).\n * Feed a result's `sessionId` to createSession({ resume }). */\n async listSdkSessions(params?: {\n dir?: string\n limit?: number\n offset?: number\n }): Promise<SdkSessionSummary[]> {\n const search = new URLSearchParams()\n if (params?.dir) search.set('dir', params.dir)\n if (params?.limit !== undefined) search.set('limit', String(params.limit))\n if (params?.offset !== undefined) search.set('offset', String(params.offset))\n const qs = search.size > 0 ? `?${search.toString()}` : ''\n const body = await this.#call('GET', `/sdk-sessions${qs}`)\n return (body as { sdkSessions: SdkSessionSummary[] }).sdkSessions\n }\n\n // -- Job queue (requires the server to be configured with `queue`) ----------\n\n /** Schedule a one-shot run. The returned job's `sessionId` (once running) can be\n * fed to `attach()` to watch the run live. */\n async createJob(request: CreateJobRequest): Promise<JobInfo> {\n const body = await this.#call('POST', '/jobs', request)\n return (body as { job: JobInfo }).job\n }\n\n async listJobs(): Promise<JobInfo[]> {\n const body = await this.#call('GET', '/jobs')\n return (body as { jobs: JobInfo[] }).jobs\n }\n\n async getJob(id: string): Promise<JobInfo> {\n const body = await this.#call('GET', `/jobs/${encodeURIComponent(id)}`)\n return (body as { job: JobInfo }).job\n }\n\n /** Cancel a queued or running job. */\n async cancelJob(id: string): Promise<JobInfo> {\n const body = await this.#call('DELETE', `/jobs/${encodeURIComponent(id)}`)\n return (body as { job: JobInfo }).job\n }\n\n async queueStats(): Promise<QueueStats> {\n const body = await this.#call('GET', '/queue')\n return (body as { stats: QueueStats }).stats\n }\n\n attach(sessionId: string, options?: AttachOptions): SessionHandle {\n return new SessionHandle(this, sessionId, options)\n }\n\n /** Stream the job queue live (requires the server to be configured with `queue`).\n * Servers without a queue refuse the socket — check REST first or expect retries. */\n attachQueue(options?: { reconnect?: boolean }): QueueHandle {\n return new QueueHandle(this, options)\n }\n\n /** @internal used by SessionHandle */\n openSocket(sessionId: string, afterSeq: number): WebSocket {\n const url =\n this.#options.buildWsUrl?.(sessionId, afterSeq) ??\n `${this.#options.baseUrl.replace(/^http/, 'ws')}/sessions/${encodeURIComponent(sessionId)}/ws?afterSeq=${afterSeq}`\n return new this.#WebSocketImpl(url)\n }\n\n /** @internal used by QueueHandle */\n openQueueSocket(): WebSocket {\n const url =\n this.#options.buildQueueWsUrl?.() ??\n `${this.#options.baseUrl.replace(/^http/, 'ws')}/queue/ws`\n return new this.#WebSocketImpl(url)\n }\n\n async #call(method: string, path: string, body?: unknown): Promise<unknown> {\n const res = await this.#fetch(`${this.#options.baseUrl}${path}`, {\n method,\n headers: {\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...this.#options.headers,\n },\n body: body !== undefined ? JSON.stringify(body) : undefined,\n })\n const payload = (await res.json().catch(() => ({}))) as { error?: string }\n if (!res.ok) {\n throw new Error(payload.error ?? `${method} ${path} failed with ${res.status}`)\n }\n return payload\n }\n}\n"],"mappings":";AAyEA,IAAa,gBAAb,MAA2B;CACzB;CACA;CACA;CACA;CACA,6BAAa,IAAI,KAAsD;CACvE;CACA,UAAU;CACV,WAAW;CACX,UAAoB,EAAE;CACtB;CAEA,YAAY,QAA0B,WAAmB,UAAyB,EAAE,EAAE;AACpF,QAAA,SAAe;AACf,OAAK,YAAY;AACjB,QAAA,UAAgB;GAAE,WAAW;GAAM,GAAG;GAAS;AAC/C,QAAA,UAAgB,QAAQ,YAAY;AAIpC,QAAA,eAAqB,iBAAiB,MAAA,SAAe,EAAE,EAAE;;CAG3D,IAAI,UAAkB;AACpB,SAAO,MAAA;;CAGT,GACE,MACA,UACY;EACZ,IAAI,MAAM,MAAA,UAAgB,IAAI,KAAK;AACnC,MAAI,CAAC,KAAK;AACR,yBAAM,IAAI,KAAK;AACf,SAAA,UAAgB,IAAI,MAAM,IAAI;;AAEhC,MAAI,IAAI,SAA4B;AACpC,eAAa,IAAI,OAAO,SAA4B;;CAGtD,KAAK,MAAoB;AACvB,QAAA,UAAgB;GAAE,MAAM;GAAgB;GAAM,CAAC;;CAGjD,QAAQ,WAAmB,cAA8C;AACvE,QAAA,UAAgB;GAAE,MAAM;GAAuB;GAAW,UAAU;GAAS;GAAc,CAAC;;CAG9F,KAAK,WAAmB,SAAkB,WAA2B;AACnE,QAAA,UAAgB;GAAE,MAAM;GAAuB;GAAW,UAAU;GAAQ;GAAS;GAAW,CAAC;;CAGnG,YAAkB;AAChB,QAAA,UAAgB,EAAE,MAAM,aAAa,CAAC;;CAGxC,kBAAkB,MAA4B;AAC5C,QAAA,UAAgB;GAAE,MAAM;GAAuB;GAAM,CAAC;;;CAIxD,SAAS,OAAsB;AAC7B,QAAA,UAAgB;GAAE,MAAM;GAAa;GAAO,CAAC;;;CAI/C,mBAAmB,aAAqB,QAA6B,MAAuB;AAC1F,QAAA,UAAgB;GAAE,MAAM;GAAoB;GAAa;GAAQ;GAAM,CAAC;;;;CAK1E,kBAAkB,aAAqB,QAAgB,OAAe,MAAuB;AAC3F,QAAA,UAAgB;GAAE,MAAM;GAAmB;GAAa;GAAQ;GAAO;GAAM,CAAC;;;CAIhF,eAAqB;AACnB,QAAA,UAAgB,EAAE,MAAM,SAAS,CAAC;AAClC,OAAK,QAAQ;;;CAIf,SAAe;AACb,QAAA,SAAe;AACf,eAAa,MAAA,aAAmB;AAChC,QAAA,IAAU,OAAO;AACjB,QAAA,KAAW,KAAA;;CAGb,MAA2C,MAAS,SAAuC;EACzF,MAAM,MAAM,MAAA,UAAgB,IAAI,KAAK;AACrC,MAAI,CAAC,IAAK;AACV,OAAK,MAAM,YAAY,IACrB,KAAI;AACA,YAA8C,QAAQ;UAClD;;CAMZ,WAAW,OAA0B;EACnC,MAAM,UAAU,KAAK,UAAU,MAAM;AAErC,MAAI,MAAA,MAAY,MAAA,GAAS,eAAe,EAAG,OAAA,GAAS,KAAK,QAAQ;MAC5D,OAAA,OAAa,KAAK,QAAQ;;CAGjC,WAAiB;AACf,MAAI,MAAA,OAAc;EAClB,MAAM,KAAK,MAAA,OAAa,WAAW,KAAK,WAAW,MAAA,QAAc;AACjE,QAAA,KAAW;AACX,KAAG,eAAe;AAChB,SAAA,UAAgB;AAChB,SAAA,KAAW,oBAAoB,KAAK;AACpC,QAAK,MAAM,WAAW,MAAA,OAAa,OAAO,EAAE,CAAE,IAAG,KAAK,QAAQ;;AAEhE,KAAG,aAAa,QAAsB;GACpC,MAAM,QAAQ,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC;AAC1C,OAAI,MAAM,SAAS,WACjB,OAAA,KAAW,YAAY,MAAM;YACpB,MAAM,SAAS,SAAS;AACjC,QAAI,MAAM,MAAM,OAAO,MAAA,QAAe;AACtC,UAAA,UAAgB,MAAM,MAAM;AAC5B,UAAA,KAAW,SAAS,MAAM,MAAM;cACvB,MAAM,SAAS,oBACxB,OAAA,KAAW,mBAAmB,MAAM;YAC3B,MAAM,SAAS,qBACxB,OAAA,KAAW,oBAAoB;IAAE,aAAa,MAAM;IAAa,QAAQ,MAAM;IAAQ,CAAC;YAC/E,MAAM,SAAS,iBACxB,OAAA,KAAW,iBAAiB,MAAM,QAAQ;;AAG9C,KAAG,gBAAgB;AACjB,SAAA,KAAW,oBAAoB,MAAM;AACrC,OAAI,MAAA,UAAgB,CAAC,MAAA,QAAc,UAAW;GAC9C,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,MAAA,WAAiB,IAAO;AAC1D,SAAA,eAAqB,iBAAiB,MAAA,SAAe,EAAE,MAAM;;AAE/D,KAAG,gBAAgB;;;;;;;;AAsBvB,IAAa,cAAb,MAAyB;CACvB;CACA;CACA;CACA,6BAAa,IAAI,KAAoD;CACrE,UAAU;CACV,WAAW;CACX;CAEA,YAAY,QAA0B,UAAmC,EAAE,EAAE;AAC3E,QAAA,SAAe;AACf,QAAA,YAAkB,QAAQ,aAAa;AAEvC,QAAA,eAAqB,iBAAiB,MAAA,SAAe,EAAE,EAAE;;CAG3D,GACE,MACA,UACY;EACZ,IAAI,MAAM,MAAA,UAAgB,IAAI,KAAK;AACnC,MAAI,CAAC,KAAK;AACR,yBAAM,IAAI,KAAK;AACf,SAAA,UAAgB,IAAI,MAAM,IAAI;;AAEhC,MAAI,IAAI,SAA4B;AACpC,eAAa,IAAI,OAAO,SAA4B;;CAGtD,SAAe;AACb,QAAA,SAAe;AACf,eAAa,MAAA,aAAmB;AAChC,QAAA,IAAU,OAAO;AACjB,QAAA,KAAW,KAAA;;CAGb,MAAyC,MAAS,SAAqC;EACrF,MAAM,MAAM,MAAA,UAAgB,IAAI,KAAK;AACrC,MAAI,CAAC,IAAK;AACV,OAAK,MAAM,YAAY,IACrB,KAAI;AACA,YAA4C,QAAQ;UAChD;;CAMZ,WAAiB;AACf,MAAI,MAAA,OAAc;EAClB,MAAM,KAAK,MAAA,OAAa,iBAAiB;AACzC,QAAA,KAAW;AACX,KAAG,eAAe;AAChB,SAAA,UAAgB;AAChB,SAAA,KAAW,oBAAoB,KAAK;;AAEtC,KAAG,aAAa,QAAsB;GACpC,MAAM,QAAQ,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC;AAC1C,OAAI,MAAM,SAAS,kBAAkB;AACnC,UAAA,KAAW,YAAY,MAAM,MAAM;AACnC,UAAA,KAAW,SAAS,MAAM,MAAM;cACvB,MAAM,SAAS,YACxB,OAAA,KAAW,SAAS,MAAM,MAAM;YACvB,MAAM,SAAS,cACxB,OAAA,KAAW,SAAS,MAAM,MAAM;;AAGpC,KAAG,gBAAgB;AACjB,SAAA,KAAW,oBAAoB,MAAM;AACrC,OAAI,MAAA,UAAgB,CAAC,MAAA,UAAiB;GACtC,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,MAAA,WAAiB,IAAO;AAC1D,SAAA,eAAqB,iBAAiB,MAAA,SAAe,EAAE,MAAM;;AAE/D,KAAG,gBAAgB;;;AAMvB,IAAa,mBAAb,MAA8B;CAC5B;CACA;CACA;CAEA,YAAY,SAAwB;AAClC,QAAA,UAAgB;AAChB,QAAA,QAAc,QAAQ,aAAa,MAAM,KAAK,WAAW;AACzD,QAAA,gBAAsB,QAAQ,iBAAiB;;CAGjD,MAAM,cAAc,SAAqD;AAEvE,UAAQ,MADW,MAAA,KAAW,QAAQ,aAAa,QAAQ,EACjB;;CAG5C,MAAM,eAAuC;AAE3C,UAAQ,MADW,MAAA,KAAW,OAAO,YAAY,EACJ;;CAG/C,MAAM,WAAW,IAAkC;AAEjD,UAAQ,MADW,MAAA,KAAW,OAAO,aAAa,mBAAmB,GAAG,GAAG,EACjC;;CAG5C,MAAM,cAAc,IAAkC;AAEpD,UAAQ,MADW,MAAA,KAAW,UAAU,aAAa,mBAAmB,GAAG,GAAG,EACpC;;;;;CAM5C,MAAM,iBAAiB,WAA+C;AAEpE,UAAQ,MADW,MAAA,KAAW,OAAO,aAAa,mBAAmB,UAAU,CAAC,QAAQ,EAC9C;;;CAI5C,MAAM,iBAAiB,WAAmB,MAA+B;EACvE,MAAM,MAAM,MAAM,MAAA,MAAY,KAAK,eAAe,WAAW,KAAK,EAAE,EAClE,SAAS,MAAA,QAAc,SACxB,CAAC;AACF,MAAI,CAAC,IAAI,IAAI;GACX,MAAM,UAAW,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE,EAAE;AACnD,SAAM,IAAI,MAAM,QAAQ,SAAS,wBAAwB,IAAI,SAAS;;AAExE,SAAO,MAAM,IAAI,MAAM;;;;CAKzB,eAAe,WAAmB,MAAsB;EACtD,MAAM,UAAU,KACb,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,IAAI,mBAAmB,CACvB,KAAK,IAAI;AACZ,SAAO,GAAG,MAAA,QAAc,QAAQ,YAAY,mBAAmB,UAAU,CAAC,SAAS;;;;;;CAOrF,MAAM,kBACJ,WACA,WACA,UACe;AACf,QAAM,MAAA,KACJ,QACA,aAAa,mBAAmB,UAAU,CAAC,eAAe,mBAAmB,UAAU,IACvF,SACD;;;;;;;;;;;;CAaH,MAAM,sBACJ,aACA,QACwC;AACxC,SAAQ,MAAM,MAAA,KACZ,QACA,eAAe,mBAAmB,YAAY,CAAC,UAC/C,OACD;;;;;;;;CASH,MAAM,eAA8C;AAClD,SAAQ,MAAM,MAAA,KAAW,OAAO,YAAY;;;;CAK9C,MAAM,WAAW,MAA2C;AAC1D,SAAQ,MAAM,MAAA,KAAW,OAAO,aAAa,mBAAmB,KAAK,GAAG;;;;;;;CAQ1E,MAAM,cAAc,SAAqD;AAEvE,UAAQ,MADW,MAAA,KAAW,QAAQ,aAAa,QAAQ,EACtB;;;;CAKvC,MAAM,cAAc,MAAc,OAAmD;AAEnF,UAAQ,MADW,MAAA,KAAW,SAAS,aAAa,mBAAmB,KAAK,IAAI,MAAM,EACjD;;;;CAKvC,MAAM,cAAc,MAA6B;AAC/C,QAAM,MAAA,KAAW,UAAU,aAAa,mBAAmB,KAAK,GAAG;;;;CAKrE,MAAM,gBAAgB,QAIW;EAC/B,MAAM,SAAS,IAAI,iBAAiB;AACpC,MAAI,QAAQ,IAAK,QAAO,IAAI,OAAO,OAAO,IAAI;AAC9C,MAAI,QAAQ,UAAU,KAAA,EAAW,QAAO,IAAI,SAAS,OAAO,OAAO,MAAM,CAAC;AAC1E,MAAI,QAAQ,WAAW,KAAA,EAAW,QAAO,IAAI,UAAU,OAAO,OAAO,OAAO,CAAC;EAC7E,MAAM,KAAK,OAAO,OAAO,IAAI,IAAI,OAAO,UAAU,KAAK;AAEvD,UAAQ,MADW,MAAA,KAAW,OAAO,gBAAgB,KAAK,EACJ;;;;CAOxD,MAAM,UAAU,SAA6C;AAE3D,UAAQ,MADW,MAAA,KAAW,QAAQ,SAAS,QAAQ,EACrB;;CAGpC,MAAM,WAA+B;AAEnC,UAAQ,MADW,MAAA,KAAW,OAAO,QAAQ,EACR;;CAGvC,MAAM,OAAO,IAA8B;AAEzC,UAAQ,MADW,MAAA,KAAW,OAAO,SAAS,mBAAmB,GAAG,GAAG,EACrC;;;CAIpC,MAAM,UAAU,IAA8B;AAE5C,UAAQ,MADW,MAAA,KAAW,UAAU,SAAS,mBAAmB,GAAG,GAAG,EACxC;;CAGpC,MAAM,aAAkC;AAEtC,UAAQ,MADW,MAAA,KAAW,OAAO,SAAS,EACP;;CAGzC,OAAO,WAAmB,SAAwC;AAChE,SAAO,IAAI,cAAc,MAAM,WAAW,QAAQ;;;;CAKpD,YAAY,SAAgD;AAC1D,SAAO,IAAI,YAAY,MAAM,QAAQ;;;CAIvC,WAAW,WAAmB,UAA6B;EACzD,MAAM,MACJ,MAAA,QAAc,aAAa,WAAW,SAAS,IAC/C,GAAG,MAAA,QAAc,QAAQ,QAAQ,SAAS,KAAK,CAAC,YAAY,mBAAmB,UAAU,CAAC,eAAe;AAC3G,SAAO,IAAI,MAAA,cAAoB,IAAI;;;CAIrC,kBAA6B;EAC3B,MAAM,MACJ,MAAA,QAAc,mBAAmB,IACjC,GAAG,MAAA,QAAc,QAAQ,QAAQ,SAAS,KAAK,CAAC;AAClD,SAAO,IAAI,MAAA,cAAoB,IAAI;;CAGrC,OAAA,KAAY,QAAgB,MAAc,MAAkC;EAC1E,MAAM,MAAM,MAAM,MAAA,MAAY,GAAG,MAAA,QAAc,UAAU,QAAQ;GAC/D;GACA,SAAS;IACP,GAAI,SAAS,KAAA,IAAY,EAAE,gBAAgB,oBAAoB,GAAG,EAAE;IACpE,GAAG,MAAA,QAAc;IAClB;GACD,MAAM,SAAS,KAAA,IAAY,KAAK,UAAU,KAAK,GAAG,KAAA;GACnD,CAAC;EACF,MAAM,UAAW,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE,EAAE;AACnD,MAAI,CAAC,IAAI,GACP,OAAM,IAAI,MAAM,QAAQ,SAAS,GAAG,OAAO,GAAG,KAAK,eAAe,IAAI,SAAS;AAEjF,SAAO"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workerdeck/client",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Typed WorkerDeck protocol client for browsers and Node: REST session management plus a WebSocket attach with auto-reconnect and replay-from-last-seq. Uses the platform's fetch and WebSocket; zero runtime deps.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./build/index.mjs",
|
|
8
|
+
"types": "./build/index.d.mts",
|
|
9
|
+
"files": [
|
|
10
|
+
"build"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"@workerdeck/source": "./src/index.ts",
|
|
15
|
+
"types": "./build/index.d.mts",
|
|
16
|
+
"default": "./build/index.mjs"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@workerdeck/protocol": "0.6.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.10.0",
|
|
24
|
+
"@types/ws": "^8.18.1",
|
|
25
|
+
"rimraf": "^6.1.3",
|
|
26
|
+
"tsdown": "^0.21.10",
|
|
27
|
+
"vitest": "^3.2.7",
|
|
28
|
+
"ws": "^8.21.1",
|
|
29
|
+
"@workerdeck/server": "0.6.0",
|
|
30
|
+
"@workerdeck/core": "0.6.0"
|
|
31
|
+
},
|
|
32
|
+
"author": "Tobias Strebitzer",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/workerdeck/workerdeck.git",
|
|
36
|
+
"directory": "packages/client"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://workerdeck.github.io/workerdeck/",
|
|
39
|
+
"bugs": "https://github.com/workerdeck/workerdeck/issues",
|
|
40
|
+
"keywords": [
|
|
41
|
+
"claude",
|
|
42
|
+
"claude-code",
|
|
43
|
+
"anthropic",
|
|
44
|
+
"agent",
|
|
45
|
+
"client",
|
|
46
|
+
"websocket",
|
|
47
|
+
"browser"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"clean": "rimraf build",
|
|
54
|
+
"build": "tsdown",
|
|
55
|
+
"typecheck": "tsgo -p tsconfig.json && tsgo -p tsconfig.test.json",
|
|
56
|
+
"test": "vitest run"
|
|
57
|
+
}
|
|
58
|
+
}
|