@robota-sdk/agent-transport-ws 3.0.0-beta.63 → 3.0.0-beta.76
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 +5 -88
- package/dist/node/index.cjs +1 -394
- package/dist/node/index.d.ts +138 -156
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2 -365
- package/dist/node/index.js.map +1 -0
- package/package.json +21 -17
- package/src/__tests__/ws-handler.test.ts +409 -0
- package/src/__tests__/ws-transport.test.ts +53 -0
- package/src/index.ts +13 -0
- package/src/ws-background-messages.ts +170 -0
- package/src/ws-handler.ts +280 -0
- package/src/ws-protocol.ts +78 -0
- package/src/ws-transport-configurable.ts +128 -0
- package/src/ws-transport.ts +42 -0
- package/dist/node/index.d.cts +0 -231
package/README.md
CHANGED
|
@@ -1,94 +1,11 @@
|
|
|
1
1
|
# @robota-sdk/agent-transport-ws
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @robota-sdk/agent-transport-ws
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { createWsHandler } from '@robota-sdk/agent-transport-ws';
|
|
15
|
-
import type { InteractiveSession } from '@robota-sdk/agent-sdk';
|
|
16
|
-
import { WebSocketServer } from 'ws';
|
|
17
|
-
|
|
18
|
-
const wss = new WebSocketServer({ port: 8080 });
|
|
19
|
-
|
|
20
|
-
wss.on('connection', (ws) => {
|
|
21
|
-
const { onMessage, cleanup } = createWsHandler({
|
|
22
|
-
session: interactiveSession,
|
|
23
|
-
send: (msg) => ws.send(JSON.stringify(msg)),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
ws.on('message', (data) => onMessage(String(data)));
|
|
27
|
-
ws.on('close', cleanup);
|
|
28
|
-
});
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Message Protocol
|
|
32
|
-
|
|
33
|
-
All messages are JSON-encoded objects with a `type` field.
|
|
34
|
-
|
|
35
|
-
### Client to Server
|
|
36
|
-
|
|
37
|
-
| type | payload | maps to |
|
|
38
|
-
| --------------- | --------------------------------- | ---------------------------- |
|
|
39
|
-
| `submit` | `{ prompt: string }` | `session.submit(prompt)` |
|
|
40
|
-
| `command` | `{ name: string, args?: string }` | `session.executeCommand()` |
|
|
41
|
-
| `abort` | — | `session.abort()` |
|
|
42
|
-
| `cancel-queue` | — | `session.cancelQueue()` |
|
|
43
|
-
| `get-messages` | — | `session.getMessages()` |
|
|
44
|
-
| `get-context` | — | `session.getContextState()` |
|
|
45
|
-
| `get-executing` | — | `session.isExecuting()` |
|
|
46
|
-
| `get-pending` | — | `session.getPendingPrompt()` |
|
|
47
|
-
|
|
48
|
-
### Server to Client (pushed events)
|
|
49
|
-
|
|
50
|
-
| type | payload | source |
|
|
51
|
-
| ----------------------- | ----------------------------------- | ---------------------------------------- |
|
|
52
|
-
| `text_delta` | `{ delta: string }` | InteractiveSession event |
|
|
53
|
-
| `tool_start` | `{ state: IToolState }` | InteractiveSession event |
|
|
54
|
-
| `tool_end` | `{ state: IToolState }` | InteractiveSession event |
|
|
55
|
-
| `background_task_event` | `{ event: TBackgroundTaskEvent }` | InteractiveSession background task event |
|
|
56
|
-
| `thinking` | `{ isThinking: boolean }` | InteractiveSession event |
|
|
57
|
-
| `complete` | `{ result: IExecutionResult }` | InteractiveSession event |
|
|
58
|
-
| `interrupted` | `{ result: IExecutionResult }` | InteractiveSession event |
|
|
59
|
-
| `error` | `{ message: string }` | InteractiveSession event |
|
|
60
|
-
| `command_result` | `{ name, message, success, data? }` | command response |
|
|
61
|
-
| `messages` | `{ messages: TUniversalMessage[] }` | get-messages response |
|
|
62
|
-
| `context` | `{ state: IContextWindowState }` | get-context response |
|
|
63
|
-
| `executing` | `{ executing: boolean }` | get-executing response |
|
|
64
|
-
| `pending` | `{ pending: string\|null }` | get-pending response |
|
|
65
|
-
| `protocol_error` | `{ message: string }` | invalid client message |
|
|
66
|
-
|
|
67
|
-
## ITransportAdapter
|
|
68
|
-
|
|
69
|
-
The WebSocket transport implements the `ITransportAdapter` interface from `@robota-sdk/agent-sdk`:
|
|
3
|
+
WebSocket transport and wire protocol for the Robota SDK. Consumers that only need the WS message
|
|
4
|
+
types (e.g. browser monitors) depend on this package without pulling React/Ink/Hono.
|
|
70
5
|
|
|
71
6
|
```typescript
|
|
72
|
-
import {
|
|
73
|
-
import type {
|
|
74
|
-
|
|
75
|
-
const transport: ITransportAdapter = createWsTransport({
|
|
76
|
-
send: (msg) => ws.send(JSON.stringify(msg)),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
transport.attach(interactiveSession);
|
|
80
|
-
await transport.start();
|
|
81
|
-
|
|
82
|
-
// Wire incoming messages
|
|
83
|
-
ws.on('message', (data) => transport.onMessage(String(data)));
|
|
84
|
-
ws.on('close', () => transport.stop());
|
|
7
|
+
import { WsTransport, createWsHandler } from '@robota-sdk/agent-transport-ws';
|
|
8
|
+
import type { TServerMessage, TClientMessage } from '@robota-sdk/agent-transport-ws';
|
|
85
9
|
```
|
|
86
10
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- `@robota-sdk/agent-sdk` — `InteractiveSession`
|
|
90
|
-
- No WebSocket library dependency (framework-agnostic)
|
|
91
|
-
|
|
92
|
-
## Background Work
|
|
93
|
-
|
|
94
|
-
When the attached `InteractiveSession` emits background task updates, the WebSocket transport forwards them as protocol messages so clients can render subagent jobs, transcript references, and terminal states without polling session JSON.
|
|
11
|
+
See [docs/SPEC.md](./docs/SPEC.md) for the full contract.
|
package/dist/node/index.cjs
CHANGED
|
@@ -1,394 +1 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
WsTransport: () => WsTransport,
|
|
24
|
-
createWsHandler: () => createWsHandler,
|
|
25
|
-
createWsTransport: () => createWsTransport
|
|
26
|
-
});
|
|
27
|
-
module.exports = __toCommonJS(index_exports);
|
|
28
|
-
|
|
29
|
-
// src/ws-background-messages.ts
|
|
30
|
-
function handleBackgroundQueryMessage(session, send, msg) {
|
|
31
|
-
if (msg.type === "get-background-tasks") {
|
|
32
|
-
send({ type: "background_tasks", tasks: session.listBackgroundTasks(msg.filter) });
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (msg.type === "get-background-task") {
|
|
36
|
-
sendBackgroundTaskSnapshot(session, send, msg);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (msg.type === "get-background-job-groups") {
|
|
40
|
-
send({ type: "background_job_groups", groups: session.listBackgroundJobGroups() });
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (msg.type === "get-background-job-group") {
|
|
44
|
-
sendBackgroundJobGroupSnapshot(session, send, msg);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (msg.type === "wait-background-job-group") {
|
|
48
|
-
sendBackgroundJobGroupWaitResult(session, send, msg);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
sendBackgroundTaskLogPage(session, send, msg);
|
|
52
|
-
}
|
|
53
|
-
function handleBackgroundControlMessage(session, send, msg) {
|
|
54
|
-
if (!msg.taskId) {
|
|
55
|
-
send({ type: "protocol_error", message: "taskId is required" });
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (msg.type === "cancel-background-task") {
|
|
59
|
-
sendBackgroundTaskControlResult(
|
|
60
|
-
send,
|
|
61
|
-
"cancel",
|
|
62
|
-
msg.taskId,
|
|
63
|
-
session.cancelBackgroundTask(msg.taskId, msg.reason)
|
|
64
|
-
);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (msg.type === "close-background-task") {
|
|
68
|
-
sendBackgroundTaskControlResult(
|
|
69
|
-
send,
|
|
70
|
-
"close",
|
|
71
|
-
msg.taskId,
|
|
72
|
-
session.closeBackgroundTask(msg.taskId)
|
|
73
|
-
);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
sendBackgroundTaskInput(session, send, msg);
|
|
77
|
-
}
|
|
78
|
-
function sendBackgroundTaskSnapshot(session, send, msg) {
|
|
79
|
-
if (!msg.taskId) {
|
|
80
|
-
send({ type: "protocol_error", message: "taskId is required" });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
send({
|
|
84
|
-
type: "background_task",
|
|
85
|
-
taskId: msg.taskId,
|
|
86
|
-
task: session.getBackgroundTask(msg.taskId) ?? null
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
function sendBackgroundTaskLogPage(session, send, msg) {
|
|
90
|
-
if (!msg.taskId) {
|
|
91
|
-
send({ type: "protocol_error", message: "taskId is required" });
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
session.readBackgroundTaskLog(msg.taskId, msg.cursor).then(
|
|
95
|
-
(page) => send({ type: "background_task_log", taskId: msg.taskId, page }),
|
|
96
|
-
(error) => send({ type: "protocol_error", message: error.message })
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
function sendBackgroundJobGroupSnapshot(session, send, msg) {
|
|
100
|
-
if (!msg.groupId) {
|
|
101
|
-
send({ type: "protocol_error", message: "groupId is required" });
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
send({
|
|
105
|
-
type: "background_job_group",
|
|
106
|
-
groupId: msg.groupId,
|
|
107
|
-
group: session.getBackgroundJobGroup(msg.groupId) ?? null
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
function sendBackgroundJobGroupWaitResult(session, send, msg) {
|
|
111
|
-
if (!msg.groupId) {
|
|
112
|
-
send({ type: "protocol_error", message: "groupId is required" });
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
session.waitBackgroundJobGroup(msg.groupId).then(
|
|
116
|
-
(group) => send({ type: "background_job_group", groupId: msg.groupId, group }),
|
|
117
|
-
(error) => send({ type: "protocol_error", message: error.message })
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
function sendBackgroundTaskInput(session, send, msg) {
|
|
121
|
-
if (!msg.input) {
|
|
122
|
-
send({ type: "protocol_error", message: "input is required" });
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
sendBackgroundTaskControlResult(
|
|
126
|
-
send,
|
|
127
|
-
"send",
|
|
128
|
-
msg.taskId,
|
|
129
|
-
session.sendBackgroundTask(msg.taskId, msg.input)
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
function sendBackgroundTaskControlResult(send, action, taskId, operation) {
|
|
133
|
-
operation.then(
|
|
134
|
-
() => send({ type: "background_task_control_result", action, taskId, success: true }),
|
|
135
|
-
(error) => send({
|
|
136
|
-
type: "background_task_control_result",
|
|
137
|
-
action,
|
|
138
|
-
taskId,
|
|
139
|
-
success: false,
|
|
140
|
-
message: error.message
|
|
141
|
-
})
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// src/ws-handler.ts
|
|
146
|
-
function createWsHandler(options) {
|
|
147
|
-
const cleanup = subscribeSessionEvents(options.session, options.send);
|
|
148
|
-
const onMessage = createWsMessageHandler(options.session, options.send);
|
|
149
|
-
return { onMessage, cleanup };
|
|
150
|
-
}
|
|
151
|
-
function subscribeSessionEvents(session, send) {
|
|
152
|
-
const onUserMessage = (content) => send({ type: "user_message", content });
|
|
153
|
-
const onTextDelta = (delta) => send({ type: "text_delta", delta });
|
|
154
|
-
const onToolStart = (state) => send({ type: "tool_start", state });
|
|
155
|
-
const onToolEnd = (state) => send({ type: "tool_end", state });
|
|
156
|
-
const onThinking = (isThinking) => send({ type: "thinking", isThinking });
|
|
157
|
-
const onComplete = (result) => send({ type: "complete", result });
|
|
158
|
-
const onInterrupted = (result) => send({ type: "interrupted", result });
|
|
159
|
-
const onError = (error) => send({ type: "error", message: error.message });
|
|
160
|
-
const onBackgroundTaskEvent = (event) => send({ type: "background_task_event", event });
|
|
161
|
-
const onBackgroundJobGroupEvent = (event) => send({ type: "background_job_group_event", event });
|
|
162
|
-
const onExecutionWorkspace = (event) => send({ type: "execution_workspace_event", snapshot: event.snapshot });
|
|
163
|
-
session.on("user_message", onUserMessage);
|
|
164
|
-
session.on("text_delta", onTextDelta);
|
|
165
|
-
session.on("tool_start", onToolStart);
|
|
166
|
-
session.on("tool_end", onToolEnd);
|
|
167
|
-
session.on("thinking", onThinking);
|
|
168
|
-
session.on("complete", onComplete);
|
|
169
|
-
session.on("interrupted", onInterrupted);
|
|
170
|
-
session.on("error", onError);
|
|
171
|
-
session.on("background_task_event", onBackgroundTaskEvent);
|
|
172
|
-
session.on("background_job_group_event", onBackgroundJobGroupEvent);
|
|
173
|
-
session.on("execution_workspace_event", onExecutionWorkspace);
|
|
174
|
-
return () => {
|
|
175
|
-
session.off("user_message", onUserMessage);
|
|
176
|
-
session.off("text_delta", onTextDelta);
|
|
177
|
-
session.off("tool_start", onToolStart);
|
|
178
|
-
session.off("tool_end", onToolEnd);
|
|
179
|
-
session.off("thinking", onThinking);
|
|
180
|
-
session.off("complete", onComplete);
|
|
181
|
-
session.off("interrupted", onInterrupted);
|
|
182
|
-
session.off("error", onError);
|
|
183
|
-
session.off("background_task_event", onBackgroundTaskEvent);
|
|
184
|
-
session.off("background_job_group_event", onBackgroundJobGroupEvent);
|
|
185
|
-
session.off("execution_workspace_event", onExecutionWorkspace);
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function createWsMessageHandler(session, send) {
|
|
189
|
-
return (data) => {
|
|
190
|
-
const msg = parseClientMessage(data, send);
|
|
191
|
-
if (!msg) return;
|
|
192
|
-
handleClientMessage(session, send, msg);
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
function parseClientMessage(data, send) {
|
|
196
|
-
try {
|
|
197
|
-
return JSON.parse(data);
|
|
198
|
-
} catch {
|
|
199
|
-
send({ type: "protocol_error", message: "Invalid JSON" });
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function handleClientMessage(session, send, msg) {
|
|
204
|
-
if (isSessionControlMessage(msg)) {
|
|
205
|
-
handleSessionControlMessage(session, send, msg);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (isSessionQueryMessage(msg)) {
|
|
209
|
-
handleSessionQueryMessage(session, send, msg);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (isBackgroundQueryMessage(msg)) {
|
|
213
|
-
handleBackgroundQueryMessage(session, send, msg);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (isBackgroundControlMessage(msg)) {
|
|
217
|
-
handleBackgroundControlMessage(session, send, msg);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
send({ type: "protocol_error", message: `Unknown message type: ${getMessageType(msg)}` });
|
|
221
|
-
}
|
|
222
|
-
function getMessageType(msg) {
|
|
223
|
-
return msg.type;
|
|
224
|
-
}
|
|
225
|
-
function isSessionControlMessage(msg) {
|
|
226
|
-
return msg.type === "submit" || msg.type === "command" || msg.type === "abort" || msg.type === "cancel-queue";
|
|
227
|
-
}
|
|
228
|
-
function isSessionQueryMessage(msg) {
|
|
229
|
-
return msg.type === "get-messages" || msg.type === "get-context" || msg.type === "get-executing" || msg.type === "get-pending" || msg.type === "get-execution-workspace";
|
|
230
|
-
}
|
|
231
|
-
function isBackgroundQueryMessage(msg) {
|
|
232
|
-
return msg.type === "get-background-tasks" || msg.type === "get-background-task" || msg.type === "read-background-task-log" || msg.type === "get-background-job-groups" || msg.type === "get-background-job-group" || msg.type === "wait-background-job-group";
|
|
233
|
-
}
|
|
234
|
-
function isBackgroundControlMessage(msg) {
|
|
235
|
-
return msg.type === "cancel-background-task" || msg.type === "close-background-task" || msg.type === "send-background-task";
|
|
236
|
-
}
|
|
237
|
-
function handleSessionControlMessage(session, send, msg) {
|
|
238
|
-
if (msg.type === "submit") {
|
|
239
|
-
if (!msg.prompt) {
|
|
240
|
-
send({ type: "protocol_error", message: "prompt is required" });
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
session.submit(msg.prompt);
|
|
244
|
-
} else if (msg.type === "command") {
|
|
245
|
-
if (!msg.name) {
|
|
246
|
-
send({ type: "protocol_error", message: "name is required" });
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
session.executeCommand(msg.name, msg.args ?? "").then((result) => {
|
|
250
|
-
send({
|
|
251
|
-
type: "command_result",
|
|
252
|
-
name: msg.name,
|
|
253
|
-
message: result?.message ?? `Unknown command: ${msg.name}`,
|
|
254
|
-
success: result?.success ?? false,
|
|
255
|
-
data: result?.data
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
} else if (msg.type === "abort") {
|
|
259
|
-
session.abort();
|
|
260
|
-
} else {
|
|
261
|
-
session.cancelQueue();
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function handleSessionQueryMessage(session, send, msg) {
|
|
265
|
-
if (msg.type === "get-messages") {
|
|
266
|
-
send({ type: "messages", messages: session.getMessages() });
|
|
267
|
-
} else if (msg.type === "get-context") {
|
|
268
|
-
send({ type: "context", state: session.getContextState() });
|
|
269
|
-
} else if (msg.type === "get-executing") {
|
|
270
|
-
send({ type: "executing", executing: session.isExecuting() });
|
|
271
|
-
} else if (msg.type === "get-execution-workspace") {
|
|
272
|
-
send({
|
|
273
|
-
type: "execution_workspace_event",
|
|
274
|
-
snapshot: session.getExecutionWorkspaceSnapshot()
|
|
275
|
-
});
|
|
276
|
-
} else {
|
|
277
|
-
send({ type: "pending", pending: session.getPendingPrompt() });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/ws-transport.ts
|
|
282
|
-
function createWsTransport(options) {
|
|
283
|
-
let session = null;
|
|
284
|
-
let cleanup = null;
|
|
285
|
-
return {
|
|
286
|
-
name: "ws",
|
|
287
|
-
onMessage: null,
|
|
288
|
-
attach(s) {
|
|
289
|
-
session = s;
|
|
290
|
-
},
|
|
291
|
-
async start() {
|
|
292
|
-
if (!session) throw new Error("No session attached. Call attach() first.");
|
|
293
|
-
const handler = createWsHandler({ session, send: options.send });
|
|
294
|
-
cleanup = handler.cleanup;
|
|
295
|
-
this.onMessage = handler.onMessage;
|
|
296
|
-
},
|
|
297
|
-
async stop() {
|
|
298
|
-
cleanup?.();
|
|
299
|
-
cleanup = null;
|
|
300
|
-
this.onMessage = null;
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// src/ws-transport-configurable.ts
|
|
306
|
-
var import_node_http = require("http");
|
|
307
|
-
var import_ws = require("ws");
|
|
308
|
-
var DEFAULT_PORT = 7070;
|
|
309
|
-
var DEFAULT_MAX_RETRIES = 20;
|
|
310
|
-
var WsTransport = class {
|
|
311
|
-
name = "ws";
|
|
312
|
-
defaultEnabled = true;
|
|
313
|
-
optionsSchema = {
|
|
314
|
-
port: { type: "number", description: "WebSocket server port", default: DEFAULT_PORT },
|
|
315
|
-
maxRetries: {
|
|
316
|
-
type: "number",
|
|
317
|
-
description: "Port retry attempts when port is occupied",
|
|
318
|
-
default: DEFAULT_MAX_RETRIES
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
session = null;
|
|
322
|
-
stopFn = null;
|
|
323
|
-
port;
|
|
324
|
-
maxRetries;
|
|
325
|
-
constructor(config = {}) {
|
|
326
|
-
this.port = config.port ?? DEFAULT_PORT;
|
|
327
|
-
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
328
|
-
}
|
|
329
|
-
attach(session) {
|
|
330
|
-
this.session = session;
|
|
331
|
-
}
|
|
332
|
-
async start() {
|
|
333
|
-
if (!this.session) throw new Error("WsTransport: attach() must be called before start()");
|
|
334
|
-
const handle = await this.bindWithRetry(this.session, this.port, this.maxRetries);
|
|
335
|
-
this.stopFn = handle.stop;
|
|
336
|
-
}
|
|
337
|
-
async stop() {
|
|
338
|
-
await this.stopFn?.();
|
|
339
|
-
this.stopFn = null;
|
|
340
|
-
}
|
|
341
|
-
validateOptions(options) {
|
|
342
|
-
const { port, maxRetries } = options;
|
|
343
|
-
if (port !== void 0 && (typeof port !== "number" || port < 1 || port > 65535)) return false;
|
|
344
|
-
if (maxRetries !== void 0 && (typeof maxRetries !== "number" || maxRetries < 0))
|
|
345
|
-
return false;
|
|
346
|
-
return true;
|
|
347
|
-
}
|
|
348
|
-
bindWithRetry(session, port, retriesLeft) {
|
|
349
|
-
return this.tryBind(session, port).catch((err) => {
|
|
350
|
-
if (err.code === "EADDRINUSE" && retriesLeft > 0)
|
|
351
|
-
return this.bindWithRetry(session, port + 1, retriesLeft - 1);
|
|
352
|
-
throw err;
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
tryBind(session, port) {
|
|
356
|
-
return new Promise((resolve, reject) => {
|
|
357
|
-
const httpServer = (0, import_node_http.createServer)((_, res) => {
|
|
358
|
-
res.writeHead(400).end("WebSocket endpoint");
|
|
359
|
-
});
|
|
360
|
-
httpServer.on("error", (err) => {
|
|
361
|
-
httpServer.close();
|
|
362
|
-
reject(err);
|
|
363
|
-
});
|
|
364
|
-
httpServer.listen(port, "127.0.0.1", () => {
|
|
365
|
-
const wss = new import_ws.WebSocketServer({ server: httpServer });
|
|
366
|
-
wss.on("connection", (ws) => {
|
|
367
|
-
const send = (message) => {
|
|
368
|
-
if (ws.readyState === import_ws.WebSocket.OPEN) ws.send(JSON.stringify(message));
|
|
369
|
-
};
|
|
370
|
-
const { onMessage, cleanup } = createWsHandler({ session, send });
|
|
371
|
-
ws.on("message", (data) => onMessage(String(data)));
|
|
372
|
-
ws.on("close", cleanup);
|
|
373
|
-
ws.on("error", cleanup);
|
|
374
|
-
send({ type: "messages", messages: session.getMessages() });
|
|
375
|
-
send({
|
|
376
|
-
type: "execution_workspace_event",
|
|
377
|
-
snapshot: session.getExecutionWorkspaceSnapshot()
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
resolve({
|
|
381
|
-
stop: () => new Promise((res) => {
|
|
382
|
-
wss.close(() => httpServer.close(() => res()));
|
|
383
|
-
})
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
390
|
-
0 && (module.exports = {
|
|
391
|
-
WsTransport,
|
|
392
|
-
createWsHandler,
|
|
393
|
-
createWsTransport
|
|
394
|
-
});
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:http"),t=require("ws");function n(e,t,n){if(n.type===`get-background-tasks`){t({type:`background_tasks`,tasks:e.listBackgroundTasks(n.filter)});return}if(n.type===`get-background-task`){i(e,t,n);return}if(n.type===`get-background-job-groups`){t({type:`background_job_groups`,groups:e.listBackgroundJobGroups()});return}if(n.type===`get-background-job-group`){o(e,t,n);return}if(n.type===`wait-background-job-group`){s(e,t,n);return}a(e,t,n)}function r(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}if(n.type===`cancel-background-task`){l(t,`cancel`,n.taskId,e.cancelBackgroundTask(n.taskId,n.reason));return}if(n.type===`close-background-task`){l(t,`close`,n.taskId,e.closeBackgroundTask(n.taskId));return}c(e,t,n)}function i(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}t({type:`background_task`,taskId:n.taskId,task:e.getBackgroundTask(n.taskId)??null})}function a(e,t,n){if(!n.taskId){t({type:`protocol_error`,message:`taskId is required`});return}e.readBackgroundTaskLog(n.taskId,n.cursor).then(e=>t({type:`background_task_log`,taskId:n.taskId,page:e}),e=>t({type:`protocol_error`,message:e.message}))}function o(e,t,n){if(!n.groupId){t({type:`protocol_error`,message:`groupId is required`});return}t({type:`background_job_group`,groupId:n.groupId,group:e.getBackgroundJobGroup(n.groupId)??null})}function s(e,t,n){if(!n.groupId){t({type:`protocol_error`,message:`groupId is required`});return}e.waitBackgroundJobGroup(n.groupId).then(e=>t({type:`background_job_group`,groupId:n.groupId,group:e}),e=>t({type:`protocol_error`,message:e.message}))}function c(e,t,n){if(!n.input){t({type:`protocol_error`,message:`input is required`});return}l(t,`send`,n.taskId,e.sendBackgroundTask(n.taskId,n.input))}function l(e,t,n,r){r.then(()=>e({type:`background_task_control_result`,action:t,taskId:n,success:!0}),r=>e({type:`background_task_control_result`,action:t,taskId:n,success:!1,message:r.message}))}function u(e){let t=d(e.session,e.send);return{onMessage:f(e.session,e.send),cleanup:t}}function d(e,t){let n=e=>t({type:`user_message`,content:e}),r=e=>t({type:`text_delta`,delta:e}),i=e=>t({type:`tool_start`,state:e}),a=e=>t({type:`tool_end`,state:e}),o=e=>t({type:`thinking`,isThinking:e}),s=e=>t({type:`complete`,result:e}),c=e=>t({type:`interrupted`,result:e}),l=e=>t({type:`error`,message:e.message}),u=e=>t({type:`background_task_event`,event:e}),d=e=>t({type:`background_job_group_event`,event:e}),f=e=>t({type:`execution_workspace_event`,snapshot:e.snapshot});return e.on(`user_message`,n),e.on(`text_delta`,r),e.on(`tool_start`,i),e.on(`tool_end`,a),e.on(`thinking`,o),e.on(`complete`,s),e.on(`interrupted`,c),e.on(`error`,l),e.on(`background_task_event`,u),e.on(`background_job_group_event`,d),e.on(`execution_workspace_event`,f),()=>{e.off(`user_message`,n),e.off(`text_delta`,r),e.off(`tool_start`,i),e.off(`tool_end`,a),e.off(`thinking`,o),e.off(`complete`,s),e.off(`interrupted`,c),e.off(`error`,l),e.off(`background_task_event`,u),e.off(`background_job_group_event`,d),e.off(`execution_workspace_event`,f)}}function f(e,t){return n=>{let r=p(n,t);r&&m(e,t,r)}}function p(e,t){try{return JSON.parse(e)}catch{return t({type:`protocol_error`,message:`Invalid JSON`}),null}}function m(e,t,i){if(g(i)){b(e,t,i);return}if(_(i)){x(e,t,i);return}if(v(i)){n(e,t,i);return}if(y(i)){r(e,t,i);return}t({type:`protocol_error`,message:`Unknown message type: ${h(i)}`})}function h(e){return e.type}function g(e){return e.type===`submit`||e.type===`command`||e.type===`abort`||e.type===`cancel-queue`}function _(e){return e.type===`get-messages`||e.type===`get-context`||e.type===`get-executing`||e.type===`get-pending`||e.type===`get-execution-workspace`}function v(e){return e.type===`get-background-tasks`||e.type===`get-background-task`||e.type===`read-background-task-log`||e.type===`get-background-job-groups`||e.type===`get-background-job-group`||e.type===`wait-background-job-group`}function y(e){return e.type===`cancel-background-task`||e.type===`close-background-task`||e.type===`send-background-task`}function b(e,t,n){if(n.type===`submit`){if(!n.prompt){t({type:`protocol_error`,message:`prompt is required`});return}e.submit(n.prompt)}else if(n.type===`command`){if(!n.name){t({type:`protocol_error`,message:`name is required`});return}e.executeCommand(n.name,n.args??``).then(e=>{t({type:`command_result`,name:n.name,message:e?.message??`Unknown command: ${n.name}`,success:e?.success??!1,data:e?.data})})}else n.type===`abort`?e.abort():e.cancelQueue()}function x(e,t,n){n.type===`get-messages`?t({type:`messages`,messages:e.getMessages()}):n.type===`get-context`?t({type:`context`,state:e.getContextState()}):n.type===`get-executing`?t({type:`executing`,executing:e.isExecuting()}):n.type===`get-execution-workspace`?t({type:`execution_workspace_event`,snapshot:e.getExecutionWorkspaceSnapshot()}):t({type:`pending`,pending:e.getPendingPrompt()})}function S(e){let t=null,n=null;return{name:`ws`,onMessage:null,attach(e){t=e},async start(){if(!t)throw Error(`No session attached. Call attach() first.`);let r=u({session:t,send:e.send});n=r.cleanup,this.onMessage=r.onMessage},async stop(){n?.(),n=null,this.onMessage=null}}}const C=7070;var w=class{name=`ws`;defaultEnabled=!0;optionsSchema={port:{type:`number`,description:`WebSocket server port`,default:C},maxRetries:{type:`number`,description:`Port retry attempts when port is occupied`,default:20}};session=null;stopFn=null;port;maxRetries;constructor(e={}){this.port=e.port??C,this.maxRetries=e.maxRetries??20}attach(e){this.session=e}async start(){if(!this.session)throw Error(`WsTransport: attach() must be called before start()`);let e=await this.bindWithRetry(this.session,this.port,this.maxRetries);this.stopFn=e.stop}async stop(){await this.stopFn?.(),this.stopFn=null}validateOptions(e){let{port:t,maxRetries:n}=e;return!(t!==void 0&&(typeof t!=`number`||t<1||t>65535)||n!==void 0&&(typeof n!=`number`||n<0))}bindWithRetry(e,t,n){return this.tryBind(e,t).catch(r=>{if(r.code===`EADDRINUSE`&&n>0)return this.bindWithRetry(e,t+1,n-1);throw r})}tryBind(n,r){return new Promise((i,a)=>{let o=(0,e.createServer)((e,t)=>{t.writeHead(400).end(`WebSocket endpoint`)});o.on(`error`,e=>{o.close(),a(e)}),o.listen(r,`127.0.0.1`,()=>{let e=new t.WebSocketServer({server:o});e.on(`connection`,e=>{let r=n=>{e.readyState===t.WebSocket.OPEN&&e.send(JSON.stringify(n))},{onMessage:i,cleanup:a}=u({session:n,send:r});e.on(`message`,e=>i(String(e))),e.on(`close`,a),e.on(`error`,a),r({type:`messages`,messages:n.getMessages()}),r({type:`execution_workspace_event`,snapshot:n.getExecutionWorkspaceSnapshot()})}),i({stop:()=>new Promise(t=>{e.close(()=>o.close(()=>t()))})})})})}};exports.WsTransport=w,exports.createWsHandler=u,exports.createWsTransport=S;
|