chainlesschain 0.45.11 → 0.45.19
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/package.json +1 -1
- package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
- package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
- package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
- package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
- package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
- package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
- package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
- package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
- package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
- package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
- package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
- package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
- package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/agent.js +7 -8
- package/src/commands/chat.js +9 -11
- package/src/commands/serve.js +11 -106
- package/src/commands/session.js +185 -18
- package/src/commands/ui.js +10 -151
- package/src/gateways/repl/agent-repl.js +1 -0
- package/src/gateways/repl/chat-repl.js +1 -0
- package/src/gateways/ui/web-ui-server.js +1 -0
- package/src/gateways/ws/action-protocol.js +83 -0
- package/src/gateways/ws/message-dispatcher.js +73 -0
- package/src/gateways/ws/session-protocol.js +396 -0
- package/src/gateways/ws/task-protocol.js +55 -0
- package/src/gateways/ws/worktree-protocol.js +315 -0
- package/src/gateways/ws/ws-server.js +4 -0
- package/src/gateways/ws/ws-session-gateway.js +1 -0
- package/src/harness/background-task-manager.js +506 -0
- package/src/harness/background-task-worker.js +48 -0
- package/src/harness/compression-telemetry.js +214 -0
- package/src/harness/feature-flags.js +157 -0
- package/src/harness/jsonl-session-store.js +452 -0
- package/src/harness/prompt-compressor.js +416 -0
- package/src/harness/worktree-isolator.js +845 -0
- package/src/lib/agent-core.js +246 -45
- package/src/lib/background-task-manager.js +1 -305
- package/src/lib/background-task-worker.js +1 -50
- package/src/lib/compression-telemetry.js +5 -0
- package/src/lib/feature-flags.js +7 -182
- package/src/lib/interaction-adapter.js +32 -6
- package/src/lib/jsonl-session-store.js +21 -237
- package/src/lib/prompt-compressor.js +10 -351
- package/src/lib/sub-agent-context.js +91 -0
- package/src/lib/worktree-isolator.js +13 -231
- package/src/lib/ws-agent-handler.js +1 -0
- package/src/lib/ws-server.js +155 -359
- package/src/lib/ws-session-manager.js +82 -1
- package/src/repl/agent-repl.js +114 -32
- package/src/runtime/agent-runtime.js +417 -0
- package/src/runtime/contracts/agent-turn.js +11 -0
- package/src/runtime/contracts/session-record.js +31 -0
- package/src/runtime/contracts/task-record.js +18 -0
- package/src/runtime/contracts/telemetry-record.js +23 -0
- package/src/runtime/contracts/worktree-record.js +14 -0
- package/src/runtime/index.js +13 -0
- package/src/runtime/policies/agent-policy.js +45 -0
- package/src/runtime/runtime-context.js +14 -0
- package/src/runtime/runtime-events.js +37 -0
- package/src/runtime/runtime-factory.js +50 -0
- package/src/tools/index.js +22 -0
- package/src/tools/legacy-agent-tools.js +171 -0
- package/src/tools/registry.js +141 -0
- package/src/tools/tool-context.js +28 -0
- package/src/tools/tool-permissions.js +28 -0
- package/src/tools/tool-telemetry.js +39 -0
- package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
- package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
- package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
- package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
package/src/lib/ws-server.js
CHANGED
|
@@ -11,6 +11,38 @@ import { spawn } from "node:child_process";
|
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { dirname, join } from "node:path";
|
|
13
13
|
import { WebSocketServer } from "ws";
|
|
14
|
+
import { createTaskRecord } from "../runtime/contracts/task-record.js";
|
|
15
|
+
import {
|
|
16
|
+
RUNTIME_EVENTS,
|
|
17
|
+
createRuntimeEvent,
|
|
18
|
+
} from "../runtime/runtime-events.js";
|
|
19
|
+
import { createWsMessageDispatcher } from "../gateways/ws/message-dispatcher.js";
|
|
20
|
+
import {
|
|
21
|
+
handleTaskDetail,
|
|
22
|
+
handleTaskHistory,
|
|
23
|
+
} from "../gateways/ws/task-protocol.js";
|
|
24
|
+
import {
|
|
25
|
+
handleSessionCreate,
|
|
26
|
+
handleSessionResume,
|
|
27
|
+
handleSessionMessage,
|
|
28
|
+
handleSessionPolicyUpdate,
|
|
29
|
+
handleSessionList,
|
|
30
|
+
handleSessionClose,
|
|
31
|
+
handleSessionAnswer,
|
|
32
|
+
handleHostToolResult,
|
|
33
|
+
} from "../gateways/ws/session-protocol.js";
|
|
34
|
+
import {
|
|
35
|
+
handleSlashCommand,
|
|
36
|
+
handleOrchestrate,
|
|
37
|
+
} from "../gateways/ws/action-protocol.js";
|
|
38
|
+
import {
|
|
39
|
+
handleWorktreeDiff,
|
|
40
|
+
handleWorktreeMerge,
|
|
41
|
+
handleWorktreeMergePreview,
|
|
42
|
+
handleWorktreeAutomationApply,
|
|
43
|
+
handleWorktreeList,
|
|
44
|
+
handleCompressionStats,
|
|
45
|
+
} from "../gateways/ws/worktree-protocol.js";
|
|
14
46
|
|
|
15
47
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
48
|
const __dirname = dirname(__filename);
|
|
@@ -99,6 +131,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
99
131
|
|
|
100
132
|
/** Session handlers: sessionId → WSAgentHandler | WSChatHandler */
|
|
101
133
|
this.sessionHandlers = new Map();
|
|
134
|
+
this._dispatcher = createWsMessageDispatcher(this);
|
|
102
135
|
|
|
103
136
|
this._heartbeatTimer = null;
|
|
104
137
|
this._clientCounter = 0;
|
|
@@ -227,77 +260,7 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
227
260
|
|
|
228
261
|
/** @private */
|
|
229
262
|
async _handleMessage(clientId, ws, message) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (!id) {
|
|
233
|
-
this._send(ws, {
|
|
234
|
-
type: "error",
|
|
235
|
-
code: "MISSING_ID",
|
|
236
|
-
message: 'Message must include an "id" field',
|
|
237
|
-
});
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Check authentication
|
|
242
|
-
const client = this.clients.get(clientId);
|
|
243
|
-
if (this.token && !client.authenticated && type !== "auth") {
|
|
244
|
-
this._send(ws, {
|
|
245
|
-
id,
|
|
246
|
-
type: "error",
|
|
247
|
-
code: "AUTH_REQUIRED",
|
|
248
|
-
message: "Authentication required. Send an auth message first.",
|
|
249
|
-
});
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
switch (type) {
|
|
254
|
-
case "auth":
|
|
255
|
-
this._handleAuth(clientId, ws, message);
|
|
256
|
-
break;
|
|
257
|
-
case "ping":
|
|
258
|
-
this._send(ws, { id, type: "pong", serverTime: Date.now() });
|
|
259
|
-
break;
|
|
260
|
-
case "execute":
|
|
261
|
-
this._executeCommand(id, ws, message.command, false);
|
|
262
|
-
break;
|
|
263
|
-
case "stream":
|
|
264
|
-
this._executeCommand(id, ws, message.command, true);
|
|
265
|
-
break;
|
|
266
|
-
case "cancel":
|
|
267
|
-
this._cancelRequest(id, ws);
|
|
268
|
-
break;
|
|
269
|
-
case "session-create":
|
|
270
|
-
await this._handleSessionCreate(id, ws, message);
|
|
271
|
-
break;
|
|
272
|
-
case "session-resume":
|
|
273
|
-
await this._handleSessionResume(id, ws, message);
|
|
274
|
-
break;
|
|
275
|
-
case "session-message":
|
|
276
|
-
this._handleSessionMessage(id, ws, message);
|
|
277
|
-
break;
|
|
278
|
-
case "session-list":
|
|
279
|
-
this._handleSessionList(id, ws);
|
|
280
|
-
break;
|
|
281
|
-
case "session-close":
|
|
282
|
-
this._handleSessionClose(id, ws, message);
|
|
283
|
-
break;
|
|
284
|
-
case "slash-command":
|
|
285
|
-
this._handleSlashCommand(id, ws, message);
|
|
286
|
-
break;
|
|
287
|
-
case "session-answer":
|
|
288
|
-
this._handleSessionAnswer(id, ws, message);
|
|
289
|
-
break;
|
|
290
|
-
case "orchestrate":
|
|
291
|
-
this._handleOrchestrate(id, ws, message);
|
|
292
|
-
break;
|
|
293
|
-
default:
|
|
294
|
-
this._send(ws, {
|
|
295
|
-
id,
|
|
296
|
-
type: "error",
|
|
297
|
-
code: "UNKNOWN_TYPE",
|
|
298
|
-
message: `Unknown message type: ${type}`,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
263
|
+
return this._dispatcher.dispatch(clientId, ws, message);
|
|
301
264
|
}
|
|
302
265
|
|
|
303
266
|
/**
|
|
@@ -314,76 +277,110 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
314
277
|
* { type: "error", code: "ORCHESTRATE_FAILED", ... }
|
|
315
278
|
*/
|
|
316
279
|
async _handleOrchestrate(id, ws, message) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
cwd,
|
|
320
|
-
agents = 3,
|
|
321
|
-
ci = "npm test",
|
|
322
|
-
noCi = false,
|
|
323
|
-
strategy,
|
|
324
|
-
} = message;
|
|
325
|
-
|
|
326
|
-
if (!task || typeof task !== "string") {
|
|
327
|
-
this._send(ws, {
|
|
328
|
-
id,
|
|
329
|
-
type: "error",
|
|
330
|
-
code: "INVALID_TASK",
|
|
331
|
-
message: "task field required",
|
|
332
|
-
});
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
280
|
+
return handleOrchestrate(this, id, ws, message);
|
|
281
|
+
}
|
|
335
282
|
|
|
283
|
+
/** @private – list background tasks */
|
|
284
|
+
async _handleTasksList(id, ws) {
|
|
336
285
|
try {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
verbose: false,
|
|
345
|
-
});
|
|
286
|
+
await this._ensureTaskManager();
|
|
287
|
+
const tasks = this._taskManager.list();
|
|
288
|
+
this._send(ws, { id, type: "tasks-list", tasks });
|
|
289
|
+
} catch (err) {
|
|
290
|
+
this._send(ws, { id, type: "tasks-list", tasks: [] });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
346
293
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
294
|
+
/** @private — subscribe to task completion events and broadcast to all clients */
|
|
295
|
+
_subscribeTaskNotifications() {
|
|
296
|
+
if (!this._taskManager || this._taskNotificationsSubscribed) return;
|
|
297
|
+
this._taskNotificationsSubscribed = true;
|
|
298
|
+
|
|
299
|
+
this._taskManager.on("task:complete", (task) => {
|
|
300
|
+
const record = createTaskRecord(task, {
|
|
301
|
+
source: "background-task-manager",
|
|
302
|
+
});
|
|
303
|
+
this.emit(
|
|
304
|
+
RUNTIME_EVENTS.TASK_NOTIFICATION,
|
|
305
|
+
createRuntimeEvent(
|
|
306
|
+
RUNTIME_EVENTS.TASK_NOTIFICATION,
|
|
307
|
+
{ task: record },
|
|
308
|
+
{ kind: "server" },
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
this._broadcast({
|
|
312
|
+
type: "task:notification",
|
|
313
|
+
task: record,
|
|
351
314
|
});
|
|
315
|
+
});
|
|
316
|
+
}
|
|
352
317
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
orch.on("task:added", (t) => wsNotifier.sendStatus(t));
|
|
358
|
-
orch.on("task:decomposing", (t) => wsNotifier.sendStatus(t));
|
|
359
|
-
orch.on("ci:checking", ({ task: t }) => wsNotifier.sendStatus(t));
|
|
360
|
-
orch.on("task:retrying", ({ task: t }) => wsNotifier.sendStatus(t));
|
|
361
|
-
|
|
362
|
-
const result = await orch.addTask(task, {
|
|
363
|
-
source: TASK_SOURCE.CLI,
|
|
364
|
-
cwd: cwd || this.projectRoot || process.cwd(),
|
|
365
|
-
runCI: !noCi,
|
|
366
|
-
notify: true,
|
|
367
|
-
});
|
|
318
|
+
/** @private – stop a background task */
|
|
319
|
+
async _handleTasksStop(id, ws, message) {
|
|
320
|
+
try {
|
|
321
|
+
await this._ensureTaskManager();
|
|
368
322
|
|
|
369
|
-
this.
|
|
370
|
-
|
|
371
|
-
type: "
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
323
|
+
if (this._taskManager && message.taskId) {
|
|
324
|
+
this._taskManager.stop(message.taskId);
|
|
325
|
+
this._send(ws, { id, type: "tasks-stopped", taskId: message.taskId });
|
|
326
|
+
} else {
|
|
327
|
+
this._send(ws, {
|
|
328
|
+
id,
|
|
329
|
+
type: "error",
|
|
330
|
+
code: "NO_TASK",
|
|
331
|
+
message: "taskId required or no task manager",
|
|
332
|
+
});
|
|
333
|
+
}
|
|
377
334
|
} catch (err) {
|
|
378
335
|
this._send(ws, {
|
|
379
336
|
id,
|
|
380
337
|
type: "error",
|
|
381
|
-
code: "
|
|
338
|
+
code: "TASKS_STOP_FAILED",
|
|
382
339
|
message: err.message,
|
|
383
340
|
});
|
|
384
341
|
}
|
|
385
342
|
}
|
|
386
343
|
|
|
344
|
+
/** @private */
|
|
345
|
+
async _handleTaskDetail(id, ws, message) {
|
|
346
|
+
return handleTaskDetail(this, id, ws, message);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** @private */
|
|
350
|
+
async _handleTaskHistory(id, ws, message) {
|
|
351
|
+
return handleTaskHistory(this, id, ws, message);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** @private — diff preview for agent worktree branch */
|
|
355
|
+
async _handleWorktreeDiff(id, ws, message) {
|
|
356
|
+
return handleWorktreeDiff(this, id, ws, message);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** @private — one-click merge of agent worktree branch */
|
|
360
|
+
async _handleWorktreeMerge(id, ws, message) {
|
|
361
|
+
return handleWorktreeMerge(this, id, ws, message);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** @private - dry-run merge preview for an agent worktree branch */
|
|
365
|
+
async _handleWorktreeMergePreview(id, ws, message) {
|
|
366
|
+
return handleWorktreeMergePreview(this, id, ws, message);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** @private - apply a safe automation candidate inside an agent worktree */
|
|
370
|
+
async _handleWorktreeAutomationApply(id, ws, message) {
|
|
371
|
+
return handleWorktreeAutomationApply(this, id, ws, message);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** @private - list agent worktrees */
|
|
375
|
+
async _handleWorktreeList(id, ws) {
|
|
376
|
+
return handleWorktreeList(this, id, ws);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** @private */
|
|
380
|
+
async _handleCompressionStats(id, ws, message) {
|
|
381
|
+
return handleCompressionStats(this, id, ws, message);
|
|
382
|
+
}
|
|
383
|
+
|
|
387
384
|
/** @private */
|
|
388
385
|
_handleAuth(clientId, ws, message) {
|
|
389
386
|
const { id, token } = message;
|
|
@@ -574,268 +571,58 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
574
571
|
|
|
575
572
|
/** @private */
|
|
576
573
|
async _handleSessionCreate(id, ws, message) {
|
|
577
|
-
|
|
578
|
-
this._send(ws, {
|
|
579
|
-
id,
|
|
580
|
-
type: "error",
|
|
581
|
-
code: "NO_SESSION_SUPPORT",
|
|
582
|
-
message: "Session support not configured on this server",
|
|
583
|
-
});
|
|
584
|
-
return;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
const { sessionType, provider, model, apiKey, baseUrl, projectRoot } =
|
|
588
|
-
message;
|
|
589
|
-
|
|
590
|
-
try {
|
|
591
|
-
const { sessionId } = this.sessionManager.createSession({
|
|
592
|
-
type: sessionType || "agent",
|
|
593
|
-
provider,
|
|
594
|
-
model,
|
|
595
|
-
apiKey,
|
|
596
|
-
baseUrl,
|
|
597
|
-
projectRoot,
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
const session = this.sessionManager.getSession(sessionId);
|
|
601
|
-
|
|
602
|
-
// Lazy-load handler modules to avoid circular deps
|
|
603
|
-
try {
|
|
604
|
-
const { WebSocketInteractionAdapter } =
|
|
605
|
-
await import("./interaction-adapter.js");
|
|
606
|
-
session.interaction = new WebSocketInteractionAdapter(ws, sessionId);
|
|
607
|
-
|
|
608
|
-
let handler;
|
|
609
|
-
if ((sessionType || "agent") === "chat") {
|
|
610
|
-
const { WSChatHandler } = await import("./ws-chat-handler.js");
|
|
611
|
-
handler = new WSChatHandler({
|
|
612
|
-
session,
|
|
613
|
-
interaction: session.interaction,
|
|
614
|
-
});
|
|
615
|
-
} else {
|
|
616
|
-
const { WSAgentHandler } = await import("./ws-agent-handler.js");
|
|
617
|
-
handler = new WSAgentHandler({
|
|
618
|
-
session,
|
|
619
|
-
interaction: session.interaction,
|
|
620
|
-
db: this.sessionManager.db,
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
this.sessionHandlers.set(sessionId, handler);
|
|
624
|
-
} catch (_err) {
|
|
625
|
-
// Handler creation failed — session still created, handler can be set later
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
this.emit("session:create", { sessionId, type: sessionType || "agent" });
|
|
629
|
-
|
|
630
|
-
this._send(ws, {
|
|
631
|
-
id,
|
|
632
|
-
type: "session-created",
|
|
633
|
-
sessionId,
|
|
634
|
-
sessionType: sessionType || "agent",
|
|
635
|
-
});
|
|
636
|
-
} catch (err) {
|
|
637
|
-
this._send(ws, {
|
|
638
|
-
id,
|
|
639
|
-
type: "error",
|
|
640
|
-
code: "SESSION_CREATE_FAILED",
|
|
641
|
-
message: err.message,
|
|
642
|
-
});
|
|
643
|
-
}
|
|
574
|
+
return handleSessionCreate(this, id, ws, message);
|
|
644
575
|
}
|
|
645
576
|
|
|
646
577
|
/** @private */
|
|
647
578
|
async _handleSessionResume(id, ws, message) {
|
|
648
|
-
|
|
649
|
-
this._send(ws, {
|
|
650
|
-
id,
|
|
651
|
-
type: "error",
|
|
652
|
-
code: "NO_SESSION_SUPPORT",
|
|
653
|
-
message: "Session support not configured",
|
|
654
|
-
});
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const { sessionId } = message;
|
|
659
|
-
const session = this.sessionManager.resumeSession(sessionId);
|
|
660
|
-
|
|
661
|
-
if (!session) {
|
|
662
|
-
this._send(ws, {
|
|
663
|
-
id,
|
|
664
|
-
type: "error",
|
|
665
|
-
code: "SESSION_NOT_FOUND",
|
|
666
|
-
message: `Session not found: ${sessionId}`,
|
|
667
|
-
});
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Rebuild interaction adapter and handler for the resumed session
|
|
672
|
-
if (!this.sessionHandlers.has(sessionId)) {
|
|
673
|
-
try {
|
|
674
|
-
const { WebSocketInteractionAdapter } =
|
|
675
|
-
await import("./interaction-adapter.js");
|
|
676
|
-
session.interaction = new WebSocketInteractionAdapter(ws, sessionId);
|
|
677
|
-
|
|
678
|
-
let handler;
|
|
679
|
-
if (session.type === "chat") {
|
|
680
|
-
const { WSChatHandler } = await import("./ws-chat-handler.js");
|
|
681
|
-
handler = new WSChatHandler({
|
|
682
|
-
session,
|
|
683
|
-
interaction: session.interaction,
|
|
684
|
-
});
|
|
685
|
-
} else {
|
|
686
|
-
const { WSAgentHandler } = await import("./ws-agent-handler.js");
|
|
687
|
-
handler = new WSAgentHandler({
|
|
688
|
-
session,
|
|
689
|
-
interaction: session.interaction,
|
|
690
|
-
db: this.sessionManager.db,
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
this.sessionHandlers.set(sessionId, handler);
|
|
694
|
-
} catch (_err) {
|
|
695
|
-
// Handler creation failed — session resumed without handler
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Filter out system messages for history
|
|
700
|
-
const history = (session.messages || []).filter((m) => m.role !== "system");
|
|
701
|
-
|
|
702
|
-
this._send(ws, {
|
|
703
|
-
id,
|
|
704
|
-
type: "session-resumed",
|
|
705
|
-
sessionId: session.id,
|
|
706
|
-
history,
|
|
707
|
-
});
|
|
579
|
+
return handleSessionResume(this, id, ws, message);
|
|
708
580
|
}
|
|
709
581
|
|
|
710
582
|
/** @private */
|
|
711
583
|
_handleSessionMessage(id, ws, message) {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (!handler) {
|
|
716
|
-
this._send(ws, {
|
|
717
|
-
id,
|
|
718
|
-
type: "error",
|
|
719
|
-
code: "SESSION_NOT_FOUND",
|
|
720
|
-
message: `No active session handler for: ${sessionId}`,
|
|
721
|
-
});
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
584
|
+
return handleSessionMessage(this, id, ws, message);
|
|
585
|
+
}
|
|
724
586
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
.then(() => {
|
|
729
|
-
// Persist messages after each turn
|
|
730
|
-
if (this.sessionManager) {
|
|
731
|
-
try {
|
|
732
|
-
this.sessionManager.persistMessages(sessionId);
|
|
733
|
-
} catch (_err) {
|
|
734
|
-
// Non-critical
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
})
|
|
738
|
-
.catch((err) => {
|
|
739
|
-
this._send(ws, {
|
|
740
|
-
id,
|
|
741
|
-
type: "error",
|
|
742
|
-
code: "MESSAGE_FAILED",
|
|
743
|
-
message: err.message,
|
|
744
|
-
});
|
|
745
|
-
});
|
|
587
|
+
/** @private */
|
|
588
|
+
_handleSessionPolicyUpdate(id, ws, message) {
|
|
589
|
+
return handleSessionPolicyUpdate(this, id, ws, message);
|
|
746
590
|
}
|
|
747
591
|
|
|
748
592
|
/** @private */
|
|
749
593
|
_handleSessionList(id, ws) {
|
|
750
|
-
|
|
751
|
-
this._send(ws, {
|
|
752
|
-
id,
|
|
753
|
-
type: "error",
|
|
754
|
-
code: "NO_SESSION_SUPPORT",
|
|
755
|
-
message: "Session support not configured",
|
|
756
|
-
});
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
const sessions = this.sessionManager.listSessions();
|
|
761
|
-
this._send(ws, {
|
|
762
|
-
id,
|
|
763
|
-
type: "session-list-result",
|
|
764
|
-
sessions,
|
|
765
|
-
});
|
|
594
|
+
return handleSessionList(this, id, ws);
|
|
766
595
|
}
|
|
767
596
|
|
|
768
597
|
/** @private */
|
|
769
598
|
_handleSessionClose(id, ws, message) {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
// Remove handler
|
|
773
|
-
const handler = this.sessionHandlers.get(sessionId);
|
|
774
|
-
if (handler && handler.destroy) {
|
|
775
|
-
handler.destroy();
|
|
776
|
-
}
|
|
777
|
-
this.sessionHandlers.delete(sessionId);
|
|
778
|
-
|
|
779
|
-
// Close session in manager
|
|
780
|
-
if (this.sessionManager) {
|
|
781
|
-
try {
|
|
782
|
-
this.sessionManager.closeSession(sessionId);
|
|
783
|
-
} catch (_err) {
|
|
784
|
-
// Non-critical
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
this.emit("session:close", { sessionId });
|
|
789
|
-
|
|
790
|
-
this._send(ws, {
|
|
791
|
-
id,
|
|
792
|
-
type: "result",
|
|
793
|
-
success: true,
|
|
794
|
-
sessionId,
|
|
795
|
-
});
|
|
599
|
+
return handleSessionClose(this, id, ws, message);
|
|
796
600
|
}
|
|
797
601
|
|
|
798
602
|
/** @private */
|
|
799
603
|
_handleSlashCommand(id, ws, message) {
|
|
800
|
-
|
|
801
|
-
const handler = this.sessionHandlers.get(sessionId);
|
|
802
|
-
|
|
803
|
-
if (!handler) {
|
|
804
|
-
this._send(ws, {
|
|
805
|
-
id,
|
|
806
|
-
type: "error",
|
|
807
|
-
code: "SESSION_NOT_FOUND",
|
|
808
|
-
message: `No active session handler for: ${sessionId}`,
|
|
809
|
-
});
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
handler.handleSlashCommand(command, id);
|
|
604
|
+
return handleSlashCommand(this, id, ws, message);
|
|
814
605
|
}
|
|
815
606
|
|
|
816
607
|
/** @private */
|
|
817
608
|
_handleSessionAnswer(id, ws, message) {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (!this.sessionManager) {
|
|
821
|
-
this._send(ws, {
|
|
822
|
-
id,
|
|
823
|
-
type: "error",
|
|
824
|
-
code: "NO_SESSION_SUPPORT",
|
|
825
|
-
message: "Session support not configured",
|
|
826
|
-
});
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
const session = this.sessionManager.getSession(sessionId);
|
|
831
|
-
if (session && session.interaction && session.interaction.resolveAnswer) {
|
|
832
|
-
session.interaction.resolveAnswer(requestId, answer);
|
|
833
|
-
}
|
|
609
|
+
return handleSessionAnswer(this, id, ws, message);
|
|
610
|
+
}
|
|
834
611
|
|
|
835
|
-
|
|
612
|
+
_handleHostToolResult(id, ws, message) {
|
|
613
|
+
return handleHostToolResult(this, id, ws, message);
|
|
836
614
|
}
|
|
837
615
|
|
|
838
616
|
/** @private — ping/pong heartbeat to detect dead connections */
|
|
617
|
+
async _ensureTaskManager() {
|
|
618
|
+
if (this._taskManager) return this._taskManager;
|
|
619
|
+
const { BackgroundTaskManager } =
|
|
620
|
+
await import("./background-task-manager.js");
|
|
621
|
+
this._taskManager = new BackgroundTaskManager({ recoverOnStart: true });
|
|
622
|
+
this._subscribeTaskNotifications();
|
|
623
|
+
return this._taskManager;
|
|
624
|
+
}
|
|
625
|
+
|
|
839
626
|
_startHeartbeat() {
|
|
840
627
|
this._heartbeatTimer = setInterval(() => {
|
|
841
628
|
for (const [clientId, client] of this.clients) {
|
|
@@ -865,4 +652,13 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
865
652
|
}
|
|
866
653
|
}
|
|
867
654
|
}
|
|
655
|
+
|
|
656
|
+
/** @private — broadcast a message to all connected, authenticated clients */
|
|
657
|
+
_broadcast(data) {
|
|
658
|
+
for (const [, client] of this.clients) {
|
|
659
|
+
if (client.authenticated || !this.token) {
|
|
660
|
+
this._send(client.ws, data);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
868
664
|
}
|