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.
Files changed (81) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
  3. package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
  4. package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
  5. package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
  6. package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
  7. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
  8. package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
  9. package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
  10. package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
  11. package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
  14. package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
  15. package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
  16. package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
  17. package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
  18. package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
  19. package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
  20. package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
  21. package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
  22. package/src/assets/web-panel/index.html +2 -2
  23. package/src/commands/agent.js +7 -8
  24. package/src/commands/chat.js +9 -11
  25. package/src/commands/serve.js +11 -106
  26. package/src/commands/session.js +185 -18
  27. package/src/commands/ui.js +10 -151
  28. package/src/gateways/repl/agent-repl.js +1 -0
  29. package/src/gateways/repl/chat-repl.js +1 -0
  30. package/src/gateways/ui/web-ui-server.js +1 -0
  31. package/src/gateways/ws/action-protocol.js +83 -0
  32. package/src/gateways/ws/message-dispatcher.js +73 -0
  33. package/src/gateways/ws/session-protocol.js +396 -0
  34. package/src/gateways/ws/task-protocol.js +55 -0
  35. package/src/gateways/ws/worktree-protocol.js +315 -0
  36. package/src/gateways/ws/ws-server.js +4 -0
  37. package/src/gateways/ws/ws-session-gateway.js +1 -0
  38. package/src/harness/background-task-manager.js +506 -0
  39. package/src/harness/background-task-worker.js +48 -0
  40. package/src/harness/compression-telemetry.js +214 -0
  41. package/src/harness/feature-flags.js +157 -0
  42. package/src/harness/jsonl-session-store.js +452 -0
  43. package/src/harness/prompt-compressor.js +416 -0
  44. package/src/harness/worktree-isolator.js +845 -0
  45. package/src/lib/agent-core.js +246 -45
  46. package/src/lib/background-task-manager.js +1 -305
  47. package/src/lib/background-task-worker.js +1 -50
  48. package/src/lib/compression-telemetry.js +5 -0
  49. package/src/lib/feature-flags.js +7 -182
  50. package/src/lib/interaction-adapter.js +32 -6
  51. package/src/lib/jsonl-session-store.js +21 -237
  52. package/src/lib/prompt-compressor.js +10 -351
  53. package/src/lib/sub-agent-context.js +91 -0
  54. package/src/lib/worktree-isolator.js +13 -231
  55. package/src/lib/ws-agent-handler.js +1 -0
  56. package/src/lib/ws-server.js +155 -359
  57. package/src/lib/ws-session-manager.js +82 -1
  58. package/src/repl/agent-repl.js +114 -32
  59. package/src/runtime/agent-runtime.js +417 -0
  60. package/src/runtime/contracts/agent-turn.js +11 -0
  61. package/src/runtime/contracts/session-record.js +31 -0
  62. package/src/runtime/contracts/task-record.js +18 -0
  63. package/src/runtime/contracts/telemetry-record.js +23 -0
  64. package/src/runtime/contracts/worktree-record.js +14 -0
  65. package/src/runtime/index.js +13 -0
  66. package/src/runtime/policies/agent-policy.js +45 -0
  67. package/src/runtime/runtime-context.js +14 -0
  68. package/src/runtime/runtime-events.js +37 -0
  69. package/src/runtime/runtime-factory.js +50 -0
  70. package/src/tools/index.js +22 -0
  71. package/src/tools/legacy-agent-tools.js +171 -0
  72. package/src/tools/registry.js +141 -0
  73. package/src/tools/tool-context.js +28 -0
  74. package/src/tools/tool-permissions.js +28 -0
  75. package/src/tools/tool-telemetry.js +39 -0
  76. package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
  77. package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
  78. package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
  79. package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
  80. package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
  81. package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
@@ -0,0 +1 @@
1
+ export { startChatRepl } from "../../repl/chat-repl.js";
@@ -0,0 +1 @@
1
+ export { createWebUIServer } from "../../lib/web-ui-server.js";
@@ -0,0 +1,83 @@
1
+ export function handleSlashCommand(server, id, ws, message) {
2
+ const { sessionId, command } = message;
3
+ const handler = server.sessionHandlers.get(sessionId);
4
+
5
+ if (!handler) {
6
+ server._send(ws, {
7
+ id,
8
+ type: "error",
9
+ code: "SESSION_NOT_FOUND",
10
+ message: `No active session handler for: ${sessionId}`,
11
+ });
12
+ return;
13
+ }
14
+
15
+ handler.handleSlashCommand(command, id);
16
+ }
17
+
18
+ export async function handleOrchestrate(server, id, ws, message) {
19
+ const {
20
+ task,
21
+ cwd,
22
+ agents = 3,
23
+ ci = "npm test",
24
+ noCi = false,
25
+ strategy,
26
+ } = message;
27
+
28
+ if (!task || typeof task !== "string") {
29
+ server._send(ws, {
30
+ id,
31
+ type: "error",
32
+ code: "INVALID_TASK",
33
+ message: "task field required",
34
+ });
35
+ return;
36
+ }
37
+
38
+ try {
39
+ const { Orchestrator, TASK_SOURCE } = await import("../../lib/orchestrator.js");
40
+
41
+ const orch = new Orchestrator({
42
+ cwd: cwd || server.projectRoot || process.cwd(),
43
+ maxParallel: Math.min(parseInt(agents, 10) || 3, 10),
44
+ ciCommand: ci,
45
+ agents: strategy ? { strategy } : undefined,
46
+ verbose: false,
47
+ });
48
+
49
+ const wsNotifier = orch.notifier.addWebSocketChannel({
50
+ send: (data) => server._send(ws, data),
51
+ requestId: id,
52
+ });
53
+
54
+ orch.on("agent:output", (ev) => wsNotifier.sendAgentOutput(ev));
55
+ orch.on("task:added", (t) => wsNotifier.sendStatus(t));
56
+ orch.on("task:decomposing", (t) => wsNotifier.sendStatus(t));
57
+ orch.on("ci:checking", ({ task: t }) => wsNotifier.sendStatus(t));
58
+ orch.on("task:retrying", ({ task: t }) => wsNotifier.sendStatus(t));
59
+
60
+ const result = await orch.addTask(task, {
61
+ source: TASK_SOURCE.CLI,
62
+ cwd: cwd || server.projectRoot || process.cwd(),
63
+ runCI: !noCi,
64
+ notify: true,
65
+ });
66
+
67
+ server._send(ws, {
68
+ id,
69
+ type: "orchestrate:done",
70
+ taskId: result.id,
71
+ status: result.status,
72
+ retries: result.retries,
73
+ subtasks: result.subtasks?.length || 0,
74
+ });
75
+ } catch (err) {
76
+ server._send(ws, {
77
+ id,
78
+ type: "error",
79
+ code: "ORCHESTRATE_FAILED",
80
+ message: err.message,
81
+ });
82
+ }
83
+ }
@@ -0,0 +1,73 @@
1
+ export function createWsMessageDispatcher(server) {
2
+ return {
3
+ async dispatch(clientId, ws, message) {
4
+ const { id, type } = message;
5
+
6
+ if (!id) {
7
+ server._send(ws, {
8
+ type: "error",
9
+ code: "MISSING_ID",
10
+ message: 'Message must include an "id" field',
11
+ });
12
+ return;
13
+ }
14
+
15
+ const client = server.clients.get(clientId);
16
+ if (server.token && !client.authenticated && type !== "auth") {
17
+ server._send(ws, {
18
+ id,
19
+ type: "error",
20
+ code: "AUTH_REQUIRED",
21
+ message: "Authentication required. Send an auth message first.",
22
+ });
23
+ return;
24
+ }
25
+
26
+ const routes = {
27
+ auth: () => server._handleAuth(clientId, ws, message),
28
+ ping: () =>
29
+ server._send(ws, { id, type: "pong", serverTime: Date.now() }),
30
+ execute: () => server._executeCommand(id, ws, message.command, false),
31
+ stream: () => server._executeCommand(id, ws, message.command, true),
32
+ cancel: () => server._cancelRequest(id, ws),
33
+ "session-create": () => server._handleSessionCreate(id, ws, message),
34
+ "session-resume": () => server._handleSessionResume(id, ws, message),
35
+ "session-message": () => server._handleSessionMessage(id, ws, message),
36
+ "session-policy-update": () =>
37
+ server._handleSessionPolicyUpdate(id, ws, message),
38
+ "session-list": () => server._handleSessionList(id, ws),
39
+ "session-close": () => server._handleSessionClose(id, ws, message),
40
+ "slash-command": () => server._handleSlashCommand(id, ws, message),
41
+ "session-answer": () => server._handleSessionAnswer(id, ws, message),
42
+ "host-tool-result": () => server._handleHostToolResult(id, ws, message),
43
+ orchestrate: () => server._handleOrchestrate(id, ws, message),
44
+ "tasks-list": () => server._handleTasksList(id, ws),
45
+ "tasks-stop": () => server._handleTasksStop(id, ws, message),
46
+ "tasks-detail": () => server._handleTaskDetail(id, ws, message),
47
+ "tasks-history": () => server._handleTaskHistory(id, ws, message),
48
+ "worktree-diff": () => server._handleWorktreeDiff(id, ws, message),
49
+ "worktree-merge": () => server._handleWorktreeMerge(id, ws, message),
50
+ "worktree-merge-preview": () =>
51
+ server._handleWorktreeMergePreview(id, ws, message),
52
+ "worktree-automation-apply": () =>
53
+ server._handleWorktreeAutomationApply(id, ws, message),
54
+ "worktree-list": () => server._handleWorktreeList(id, ws),
55
+ "compression-stats": () =>
56
+ server._handleCompressionStats(id, ws, message),
57
+ };
58
+
59
+ const handler = routes[type];
60
+ if (!handler) {
61
+ server._send(ws, {
62
+ id,
63
+ type: "error",
64
+ code: "UNKNOWN_TYPE",
65
+ message: `Unknown message type: ${type}`,
66
+ });
67
+ return;
68
+ }
69
+
70
+ return handler();
71
+ },
72
+ };
73
+ }
@@ -0,0 +1,396 @@
1
+ import {
2
+ RUNTIME_EVENTS,
3
+ createRuntimeEvent,
4
+ } from "../../runtime/runtime-events.js";
5
+ import { createSessionRecord } from "../../runtime/contracts/session-record.js";
6
+
7
+ async function ensureSessionHandler(server, ws, session) {
8
+ if (server.sessionHandlers.has(session.id)) {
9
+ return server.sessionHandlers.get(session.id);
10
+ }
11
+
12
+ const { WebSocketInteractionAdapter } =
13
+ await import("../../lib/interaction-adapter.js");
14
+ session.interaction = new WebSocketInteractionAdapter(ws, session.id);
15
+
16
+ let handler;
17
+ if (session.type === "chat") {
18
+ const { WSChatHandler } = await import("../../lib/ws-chat-handler.js");
19
+ handler = new WSChatHandler({
20
+ session,
21
+ interaction: session.interaction,
22
+ });
23
+ } else {
24
+ const { WSAgentHandler } = await import("../../lib/ws-agent-handler.js");
25
+ handler = new WSAgentHandler({
26
+ session,
27
+ interaction: session.interaction,
28
+ db: server.sessionManager.db,
29
+ });
30
+ }
31
+
32
+ server.sessionHandlers.set(session.id, handler);
33
+ return handler;
34
+ }
35
+
36
+ export async function handleSessionCreate(server, id, ws, message) {
37
+ if (!server.sessionManager) {
38
+ server._send(ws, {
39
+ id,
40
+ type: "error",
41
+ code: "NO_SESSION_SUPPORT",
42
+ message: "Session support not configured on this server",
43
+ });
44
+ return;
45
+ }
46
+
47
+ const {
48
+ sessionType,
49
+ provider,
50
+ model,
51
+ apiKey,
52
+ baseUrl,
53
+ projectRoot,
54
+ hostManagedToolPolicy,
55
+ worktreeIsolation,
56
+ } = message;
57
+
58
+ try {
59
+ const { sessionId } = server.sessionManager.createSession({
60
+ type: sessionType || "agent",
61
+ provider,
62
+ model,
63
+ apiKey,
64
+ baseUrl,
65
+ projectRoot,
66
+ hostManagedToolPolicy,
67
+ worktreeIsolation,
68
+ });
69
+
70
+ const session = server.sessionManager.getSession(sessionId);
71
+ const record = createSessionRecord(session, {
72
+ sessionId,
73
+ sessionType: sessionType || "agent",
74
+ provider,
75
+ model,
76
+ projectRoot: projectRoot || null,
77
+ baseProjectRoot: session?.baseProjectRoot || projectRoot || null,
78
+ worktreeIsolation: worktreeIsolation === true,
79
+ worktree: session?.worktree || null,
80
+ status: "created",
81
+ });
82
+
83
+ try {
84
+ await ensureSessionHandler(server, ws, session);
85
+ } catch (_err) {
86
+ // Session exists even if handler bootstrapping fails.
87
+ }
88
+
89
+ server.emit("session:create", { sessionId, type: sessionType || "agent" });
90
+ server.emit(
91
+ RUNTIME_EVENTS.SESSION_START,
92
+ createRuntimeEvent(
93
+ RUNTIME_EVENTS.SESSION_START,
94
+ {
95
+ sessionId,
96
+ sessionType: sessionType || "agent",
97
+ provider,
98
+ model,
99
+ projectRoot: projectRoot || null,
100
+ record,
101
+ },
102
+ { kind: "server", sessionId },
103
+ ),
104
+ );
105
+
106
+ server._send(ws, {
107
+ id,
108
+ type: "session-created",
109
+ sessionId,
110
+ sessionType: sessionType || "agent",
111
+ record,
112
+ });
113
+ } catch (err) {
114
+ server._send(ws, {
115
+ id,
116
+ type: "error",
117
+ code: "SESSION_CREATE_FAILED",
118
+ message: err.message,
119
+ });
120
+ }
121
+ }
122
+
123
+ export async function handleSessionResume(server, id, ws, message) {
124
+ if (!server.sessionManager) {
125
+ server._send(ws, {
126
+ id,
127
+ type: "error",
128
+ code: "NO_SESSION_SUPPORT",
129
+ message: "Session support not configured",
130
+ });
131
+ return;
132
+ }
133
+
134
+ const { sessionId } = message;
135
+ const session = server.sessionManager.resumeSession(sessionId);
136
+
137
+ if (!session) {
138
+ server._send(ws, {
139
+ id,
140
+ type: "error",
141
+ code: "SESSION_NOT_FOUND",
142
+ message: `Session not found: ${sessionId}`,
143
+ });
144
+ return;
145
+ }
146
+
147
+ if (!server.sessionHandlers.has(sessionId)) {
148
+ try {
149
+ await ensureSessionHandler(server, ws, session);
150
+ } catch (_err) {
151
+ // Session resumed without live handler.
152
+ }
153
+ }
154
+
155
+ const history = (session.messages || []).filter((m) => m.role !== "system");
156
+ const record = createSessionRecord(session, {
157
+ history,
158
+ messageCount: history.length,
159
+ status: "resumed",
160
+ });
161
+
162
+ server.emit(
163
+ RUNTIME_EVENTS.SESSION_RESUME,
164
+ createRuntimeEvent(
165
+ RUNTIME_EVENTS.SESSION_RESUME,
166
+ {
167
+ sessionId: session.id,
168
+ historyCount: history.length,
169
+ sessionType: session.type || null,
170
+ record,
171
+ },
172
+ { kind: "server", sessionId: session.id },
173
+ ),
174
+ );
175
+
176
+ server._send(ws, {
177
+ id,
178
+ type: "session-resumed",
179
+ sessionId: session.id,
180
+ history,
181
+ record,
182
+ });
183
+ }
184
+
185
+ export function handleSessionMessage(server, id, ws, message) {
186
+ const { sessionId, content } = message;
187
+ const handler = server.sessionHandlers.get(sessionId);
188
+
189
+ if (!handler) {
190
+ server._send(ws, {
191
+ id,
192
+ type: "error",
193
+ code: "SESSION_NOT_FOUND",
194
+ message: `No active session handler for: ${sessionId}`,
195
+ });
196
+ return;
197
+ }
198
+
199
+ server.emit(
200
+ RUNTIME_EVENTS.SESSION_MESSAGE,
201
+ createRuntimeEvent(
202
+ RUNTIME_EVENTS.SESSION_MESSAGE,
203
+ {
204
+ sessionId,
205
+ messageId: id,
206
+ content,
207
+ },
208
+ { kind: "server", sessionId },
209
+ ),
210
+ );
211
+
212
+ handler
213
+ .handleMessage(content, id)
214
+ .then(() => {
215
+ if (server.sessionManager) {
216
+ try {
217
+ server.sessionManager.persistMessages(sessionId);
218
+ } catch (_err) {
219
+ // Non-critical.
220
+ }
221
+ }
222
+ })
223
+ .catch((err) => {
224
+ server._send(ws, {
225
+ id,
226
+ type: "error",
227
+ code: "MESSAGE_FAILED",
228
+ message: err.message,
229
+ });
230
+ });
231
+ }
232
+
233
+ export function handleSessionPolicyUpdate(server, id, ws, message) {
234
+ const { sessionId, hostManagedToolPolicy } = message;
235
+
236
+ if (!server.sessionManager) {
237
+ server._send(ws, {
238
+ id,
239
+ type: "error",
240
+ code: "NO_SESSION_SUPPORT",
241
+ message: "Session support not configured",
242
+ });
243
+ return;
244
+ }
245
+
246
+ const session = server.sessionManager.updateSessionPolicy
247
+ ? server.sessionManager.updateSessionPolicy(
248
+ sessionId,
249
+ hostManagedToolPolicy,
250
+ )
251
+ : null;
252
+
253
+ if (!session) {
254
+ server._send(ws, {
255
+ id,
256
+ type: "error",
257
+ code: "SESSION_NOT_FOUND",
258
+ message: `Session not found: ${sessionId}`,
259
+ });
260
+ return;
261
+ }
262
+
263
+ server._send(ws, {
264
+ id,
265
+ type: "session-policy-updated",
266
+ success: true,
267
+ sessionId,
268
+ });
269
+ }
270
+
271
+ export function handleSessionList(server, id, ws) {
272
+ if (!server.sessionManager) {
273
+ server._send(ws, {
274
+ id,
275
+ type: "error",
276
+ code: "NO_SESSION_SUPPORT",
277
+ message: "Session support not configured",
278
+ });
279
+ return;
280
+ }
281
+
282
+ const sessions = server.sessionManager.listSessions().map((session) => ({
283
+ ...session,
284
+ record: createSessionRecord(session, {
285
+ sessionId: session.id,
286
+ sessionType: session.type || null,
287
+ status: session.status || "listed",
288
+ history: Array.isArray(session.messages)
289
+ ? session.messages.filter((item) => item.role !== "system")
290
+ : [],
291
+ }),
292
+ }));
293
+ server._send(ws, {
294
+ id,
295
+ type: "session-list-result",
296
+ sessions,
297
+ });
298
+ }
299
+
300
+ export function handleSessionClose(server, id, ws, message) {
301
+ const { sessionId } = message;
302
+
303
+ const handler = server.sessionHandlers.get(sessionId);
304
+ if (handler && handler.destroy) {
305
+ handler.destroy();
306
+ }
307
+ server.sessionHandlers.delete(sessionId);
308
+
309
+ if (server.sessionManager) {
310
+ try {
311
+ server.sessionManager.closeSession(sessionId);
312
+ } catch (_err) {
313
+ // Non-critical.
314
+ }
315
+ }
316
+
317
+ server.emit("session:close", { sessionId });
318
+ server.emit(
319
+ RUNTIME_EVENTS.SESSION_END,
320
+ createRuntimeEvent(
321
+ RUNTIME_EVENTS.SESSION_END,
322
+ {
323
+ sessionId,
324
+ record: createSessionRecord(
325
+ { id: sessionId, type: null },
326
+ { sessionId, status: "closed", history: [], messageCount: 0 },
327
+ ),
328
+ },
329
+ { kind: "server", sessionId },
330
+ ),
331
+ );
332
+
333
+ server._send(ws, {
334
+ id,
335
+ type: "result",
336
+ success: true,
337
+ sessionId,
338
+ });
339
+ }
340
+
341
+ export function handleSessionAnswer(server, id, ws, message) {
342
+ const { sessionId, requestId, answer } = message;
343
+
344
+ if (!server.sessionManager) {
345
+ server._send(ws, {
346
+ id,
347
+ type: "error",
348
+ code: "NO_SESSION_SUPPORT",
349
+ message: "Session support not configured",
350
+ });
351
+ return;
352
+ }
353
+
354
+ const session = server.sessionManager.getSession(sessionId);
355
+ if (session && session.interaction && session.interaction.resolveAnswer) {
356
+ session.interaction.resolveAnswer(requestId, answer);
357
+ }
358
+
359
+ server._send(ws, { id, type: "result", success: true });
360
+ }
361
+
362
+ export function handleHostToolResult(server, id, ws, message) {
363
+ const { sessionId, requestId, success, result, error, toolName } = message;
364
+
365
+ if (!server.sessionManager) {
366
+ server._send(ws, {
367
+ id,
368
+ type: "error",
369
+ code: "NO_SESSION_SUPPORT",
370
+ message: "Session support not configured",
371
+ });
372
+ return;
373
+ }
374
+
375
+ const session = server.sessionManager.getSession(sessionId);
376
+ if (!session || !session.interaction) {
377
+ server._send(ws, {
378
+ id,
379
+ type: "error",
380
+ code: "SESSION_NOT_FOUND",
381
+ message: `Session not found: ${sessionId}`,
382
+ });
383
+ return;
384
+ }
385
+
386
+ if (typeof session.interaction.resolveHostTool === "function") {
387
+ session.interaction.resolveHostTool(requestId, {
388
+ success: success !== false,
389
+ result,
390
+ error: error || null,
391
+ toolName: toolName || null,
392
+ });
393
+ }
394
+
395
+ server._send(ws, { id, type: "result", success: true });
396
+ }
@@ -0,0 +1,55 @@
1
+ export async function handleTaskDetail(server, id, ws, message) {
2
+ try {
3
+ await server._ensureTaskManager();
4
+ if (!message.taskId) {
5
+ server._send(ws, {
6
+ id,
7
+ type: "error",
8
+ code: "NO_TASK",
9
+ message: "taskId required",
10
+ });
11
+ return;
12
+ }
13
+ const task = server._taskManager.getDetails(message.taskId);
14
+ server._send(ws, { id, type: "tasks-detail", task });
15
+ } catch (err) {
16
+ server._send(ws, {
17
+ id,
18
+ type: "error",
19
+ code: "TASK_DETAIL_FAILED",
20
+ message: err.message,
21
+ });
22
+ }
23
+ }
24
+
25
+ export async function handleTaskHistory(server, id, ws, message) {
26
+ try {
27
+ await server._ensureTaskManager();
28
+ if (!message.taskId) {
29
+ server._send(ws, {
30
+ id,
31
+ type: "error",
32
+ code: "NO_TASK",
33
+ message: "taskId required",
34
+ });
35
+ return;
36
+ }
37
+ const history = server._taskManager.getHistory(message.taskId, {
38
+ limit: message.limit,
39
+ offset: message.offset,
40
+ });
41
+ server._send(ws, {
42
+ id,
43
+ type: "tasks-history",
44
+ taskId: message.taskId,
45
+ history,
46
+ });
47
+ } catch (err) {
48
+ server._send(ws, {
49
+ id,
50
+ type: "error",
51
+ code: "TASK_HISTORY_FAILED",
52
+ message: err.message,
53
+ });
54
+ }
55
+ }