agent-console-server 3.3.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 +201 -0
- package/README.md +56 -0
- package/bin/cli.js +2 -0
- package/dist/EphemeralAgentEventTypes-Cm5oNQXu.js +274 -0
- package/dist/ServerMessage-Deh-bExC.js +363 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +36 -0
- package/dist/constants-eNCfNezp.d.ts +902 -0
- package/dist/contracts.d.ts +2 -0
- package/dist/contracts.js +3 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.js +4 -0
- package/dist/readToken-CAukYBGj.js +1377 -0
- package/package.json +75 -0
|
@@ -0,0 +1,1377 @@
|
|
|
1
|
+
import { M as todoStatusSchema, P as permissionModeSchema, i as commandSchema, t as EphemeralAgentEventTypes } from "./EphemeralAgentEventTypes-Cm5oNQXu.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { ID_SEPARATOR, InvalidOperationError, Operation, exhaustiveGuard, getOrCreate, getResult, getResultAsync, noop } from "@esposter/shared";
|
|
4
|
+
import { getSessionInfo, getSessionMessages, listSessions, query } from "@anthropic-ai/claude-agent-sdk";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
|
+
import { WebSocket, WebSocketServer } from "ws";
|
|
11
|
+
import { once } from "node:events";
|
|
12
|
+
import { createServer } from "node:http";
|
|
13
|
+
//#region src/services/drivers/claudeAgentSdk/getEventId.ts
|
|
14
|
+
const getEventId = (sourceId, suffix) => `${sourceId}${ID_SEPARATOR}${suffix}`;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/services/drivers/claudeAgentSdk/closeOpenSession.ts
|
|
17
|
+
const closeOpenSession = (sessionId, openSession, message, { emit, onSessionsChange, openSessionMap }) => {
|
|
18
|
+
if (openSessionMap.get(sessionId) !== openSession) return;
|
|
19
|
+
for (const pendingPermission of openSession.pendingPermissionMap.values()) pendingPermission.settle("Deny", "");
|
|
20
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
21
|
+
const closedId = getEventId(crypto.randomUUID(), "SessionState");
|
|
22
|
+
emit(sessionId, [...message ? [{
|
|
23
|
+
createdAt,
|
|
24
|
+
id: getEventId(closedId, "HostError"),
|
|
25
|
+
message,
|
|
26
|
+
type: "HostError"
|
|
27
|
+
}] : [], {
|
|
28
|
+
createdAt,
|
|
29
|
+
id: closedId,
|
|
30
|
+
state: "Closed",
|
|
31
|
+
type: "SessionState"
|
|
32
|
+
}]);
|
|
33
|
+
openSessionMap.delete(sessionId);
|
|
34
|
+
onSessionsChange();
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/services/drivers/claudeAgentSdk/constants.ts
|
|
38
|
+
const IGNORED_SDK_MESSAGE_TYPES = ["command_lifecycle"];
|
|
39
|
+
const DEFAULT_DENY_MESSAGE = "The user denied this tool call.";
|
|
40
|
+
const SETTING_SOURCES = [
|
|
41
|
+
"user",
|
|
42
|
+
"project",
|
|
43
|
+
"local"
|
|
44
|
+
];
|
|
45
|
+
const SESSION_LIST_LIMIT = 50;
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/services/drivers/claudeAgentSdk/createInputQueue.ts
|
|
48
|
+
const createInputQueue = () => {
|
|
49
|
+
let items = [];
|
|
50
|
+
let pendingReads = [];
|
|
51
|
+
let isClosed = false;
|
|
52
|
+
const next = () => {
|
|
53
|
+
const [item, ...restItems] = items;
|
|
54
|
+
if (item !== void 0) {
|
|
55
|
+
items = restItems;
|
|
56
|
+
return Promise.resolve({
|
|
57
|
+
done: false,
|
|
58
|
+
value: item
|
|
59
|
+
});
|
|
60
|
+
} else if (isClosed) return Promise.resolve({
|
|
61
|
+
done: true,
|
|
62
|
+
value: void 0
|
|
63
|
+
});
|
|
64
|
+
else return new Promise((resolve) => {
|
|
65
|
+
pendingReads = [...pendingReads, resolve];
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
close: () => {
|
|
70
|
+
isClosed = true;
|
|
71
|
+
const closedReads = pendingReads;
|
|
72
|
+
pendingReads = [];
|
|
73
|
+
for (const closedRead of closedReads) closedRead({
|
|
74
|
+
done: true,
|
|
75
|
+
value: void 0
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
iterable: { [Symbol.asyncIterator]: () => ({ next }) },
|
|
79
|
+
push: (item) => {
|
|
80
|
+
const [pendingRead, ...restPendingReads] = pendingReads;
|
|
81
|
+
if (pendingRead) {
|
|
82
|
+
pendingReads = restPendingReads;
|
|
83
|
+
pendingRead({
|
|
84
|
+
done: false,
|
|
85
|
+
value: item
|
|
86
|
+
});
|
|
87
|
+
} else items = [...items, item];
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/services/drivers/claudeAgentSdk/toPermissionRequestEvent.ts
|
|
93
|
+
const toPermissionRequestEvent = (toolName, input, options, createdAt) => {
|
|
94
|
+
const requestId = options.requestId ?? crypto.randomUUID();
|
|
95
|
+
return {
|
|
96
|
+
blockedPath: options.blockedPath ?? "",
|
|
97
|
+
createdAt,
|
|
98
|
+
decisionReason: options.decisionReason ?? "",
|
|
99
|
+
hasSuggestions: Boolean(options.suggestions?.length),
|
|
100
|
+
id: requestId,
|
|
101
|
+
input,
|
|
102
|
+
requestId,
|
|
103
|
+
title: options.title ?? options.description ?? "",
|
|
104
|
+
toolName,
|
|
105
|
+
toolUseId: options.toolUseID ?? "",
|
|
106
|
+
type: "PermissionRequest"
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/services/drivers/claudeAgentSdk/toPermissionResult.ts
|
|
111
|
+
const toPermissionResult = (input, suggestions, behavior, message) => {
|
|
112
|
+
switch (behavior) {
|
|
113
|
+
case "Allow": return {
|
|
114
|
+
behavior: "allow",
|
|
115
|
+
updatedInput: input
|
|
116
|
+
};
|
|
117
|
+
case "AllowAlways": return {
|
|
118
|
+
behavior: "allow",
|
|
119
|
+
updatedInput: input,
|
|
120
|
+
updatedPermissions: suggestions
|
|
121
|
+
};
|
|
122
|
+
case "Deny": return {
|
|
123
|
+
behavior: "deny",
|
|
124
|
+
message: message || "The user denied this tool call."
|
|
125
|
+
};
|
|
126
|
+
default: return exhaustiveGuard(behavior);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/services/drivers/claudeAgentSdk/createPermissionBridge.ts
|
|
131
|
+
const createPermissionBridge = (sessionId, pendingPermissionMap, emit) => (toolName, input, options) => new Promise((resolve) => {
|
|
132
|
+
const requestEvent = toPermissionRequestEvent(toolName, input, options, /* @__PURE__ */ new Date());
|
|
133
|
+
const { requestId } = requestEvent;
|
|
134
|
+
const resolutionId = getEventId(requestId, "PermissionResolution");
|
|
135
|
+
pendingPermissionMap.set(requestId, { settle: (behavior, message) => {
|
|
136
|
+
if (!pendingPermissionMap.delete(requestId)) return;
|
|
137
|
+
resolve(toPermissionResult(input, options.suggestions ?? [], behavior, message));
|
|
138
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
139
|
+
emit(sessionId, [{
|
|
140
|
+
behavior,
|
|
141
|
+
createdAt,
|
|
142
|
+
id: resolutionId,
|
|
143
|
+
requestId,
|
|
144
|
+
type: "PermissionResolution"
|
|
145
|
+
}, {
|
|
146
|
+
createdAt,
|
|
147
|
+
id: getEventId(resolutionId, "SessionState"),
|
|
148
|
+
state: "Running",
|
|
149
|
+
type: "SessionState"
|
|
150
|
+
}]);
|
|
151
|
+
} });
|
|
152
|
+
options.signal.addEventListener("abort", () => {
|
|
153
|
+
pendingPermissionMap.get(requestId)?.settle("Deny", "");
|
|
154
|
+
}, { once: true });
|
|
155
|
+
emit(sessionId, [requestEvent, {
|
|
156
|
+
createdAt: requestEvent.createdAt,
|
|
157
|
+
id: getEventId(requestId, "SessionState"),
|
|
158
|
+
state: "RequiresAction",
|
|
159
|
+
type: "SessionState"
|
|
160
|
+
}]);
|
|
161
|
+
});
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/services/drivers/claudeAgentSdk/createFileOriginTracker.ts
|
|
164
|
+
const fileEditResultSchema = z.object({
|
|
165
|
+
filePath: z.string().min(1),
|
|
166
|
+
originalFile: z.string().nullable(),
|
|
167
|
+
type: z.enum(["create", "update"]).optional()
|
|
168
|
+
});
|
|
169
|
+
const createFileOriginTracker = () => {
|
|
170
|
+
const changedFilePaths = /* @__PURE__ */ new Set();
|
|
171
|
+
return { readToolResult: (event, toolUseResult) => {
|
|
172
|
+
const fileEditResult = fileEditResultSchema.safeParse(toolUseResult);
|
|
173
|
+
if (event.isError || !fileEditResult.success) return event;
|
|
174
|
+
const { filePath, originalFile, type } = fileEditResult.data;
|
|
175
|
+
if (changedFilePaths.has(filePath)) return event;
|
|
176
|
+
changedFilePaths.add(filePath);
|
|
177
|
+
const originalText = originalFile ?? (type === "create" ? "" : void 0);
|
|
178
|
+
return originalText === void 0 ? event : {
|
|
179
|
+
...event,
|
|
180
|
+
originalText
|
|
181
|
+
};
|
|
182
|
+
} };
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/services/drivers/claudeAgentSdk/createStreamTracker.ts
|
|
186
|
+
const toTurnUsageEvent = (id, createdAt, outputTokens) => ({
|
|
187
|
+
createdAt,
|
|
188
|
+
id,
|
|
189
|
+
outputTokens,
|
|
190
|
+
type: "TurnUsage"
|
|
191
|
+
});
|
|
192
|
+
const createStreamTracker = () => {
|
|
193
|
+
let messageId = "";
|
|
194
|
+
let finishedOutputTokens = 0;
|
|
195
|
+
return {
|
|
196
|
+
readStreamEvent: ({ event, parent_tool_use_id, uuid }, createdAt) => {
|
|
197
|
+
if (parent_tool_use_id) return [];
|
|
198
|
+
switch (event.type) {
|
|
199
|
+
case "content_block_delta":
|
|
200
|
+
if (event.delta.type !== "text_delta" && event.delta.type !== "thinking_delta") return [];
|
|
201
|
+
return [{
|
|
202
|
+
blockId: getEventId(messageId, event.index),
|
|
203
|
+
createdAt,
|
|
204
|
+
id: uuid,
|
|
205
|
+
isThinking: event.delta.type === "thinking_delta",
|
|
206
|
+
text: event.delta.type === "text_delta" ? event.delta.text : event.delta.thinking,
|
|
207
|
+
type: "StreamDelta"
|
|
208
|
+
}];
|
|
209
|
+
case "message_delta":
|
|
210
|
+
finishedOutputTokens += event.usage.output_tokens;
|
|
211
|
+
return [toTurnUsageEvent(uuid, createdAt, finishedOutputTokens)];
|
|
212
|
+
case "message_start":
|
|
213
|
+
({id: messageId} = event.message);
|
|
214
|
+
return [];
|
|
215
|
+
default: return [];
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
readThinkingTokens: ({ estimated_tokens, uuid }, createdAt) => [toTurnUsageEvent(uuid, createdAt, finishedOutputTokens + estimated_tokens)],
|
|
219
|
+
reset: () => {
|
|
220
|
+
finishedOutputTokens = 0;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/models/claudeAgentSdk/TodoToolName.ts
|
|
226
|
+
let TodoToolName = /* @__PURE__ */ function(TodoToolName) {
|
|
227
|
+
TodoToolName["TaskCreate"] = "TaskCreate";
|
|
228
|
+
TodoToolName["TaskUpdate"] = "TaskUpdate";
|
|
229
|
+
TodoToolName["TodoWrite"] = "TodoWrite";
|
|
230
|
+
return TodoToolName;
|
|
231
|
+
}({});
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/services/drivers/claudeAgentSdk/createTodoTracker.ts
|
|
234
|
+
const todoWriteInputSchema = z.object({ todos: z.object({
|
|
235
|
+
activeForm: z.string(),
|
|
236
|
+
content: z.string(),
|
|
237
|
+
status: todoStatusSchema
|
|
238
|
+
}).array() });
|
|
239
|
+
const taskCreateInputSchema = z.object({
|
|
240
|
+
activeForm: z.string().optional(),
|
|
241
|
+
subject: z.string()
|
|
242
|
+
});
|
|
243
|
+
const taskCreateResultSchema = z.object({ task: z.object({ id: z.string().min(1) }) });
|
|
244
|
+
const taskUpdateInputSchema = z.object({
|
|
245
|
+
activeForm: z.string().optional(),
|
|
246
|
+
status: z.union([todoStatusSchema, z.literal("deleted")]).optional(),
|
|
247
|
+
subject: z.string().optional(),
|
|
248
|
+
taskId: z.string().min(1)
|
|
249
|
+
});
|
|
250
|
+
const createTodoTracker = () => {
|
|
251
|
+
let todos = [];
|
|
252
|
+
const pendingTodoMap = /* @__PURE__ */ new Map();
|
|
253
|
+
const toTodoUpdateEvent = (sourceId, createdAt) => ({
|
|
254
|
+
createdAt,
|
|
255
|
+
id: getEventId(sourceId, "TodoUpdate"),
|
|
256
|
+
todos,
|
|
257
|
+
type: "TodoUpdate"
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
readToolResult: (event, toolUseResult) => {
|
|
261
|
+
const pendingTodo = pendingTodoMap.get(event.toolUseId);
|
|
262
|
+
if (!pendingTodo) return void 0;
|
|
263
|
+
pendingTodoMap.delete(event.toolUseId);
|
|
264
|
+
const taskCreateResult = taskCreateResultSchema.safeParse(toolUseResult);
|
|
265
|
+
if (!taskCreateResult.success || event.isError) return void 0;
|
|
266
|
+
todos = [...todos, {
|
|
267
|
+
...pendingTodo,
|
|
268
|
+
id: taskCreateResult.data.task.id
|
|
269
|
+
}];
|
|
270
|
+
return toTodoUpdateEvent(event.id, event.createdAt);
|
|
271
|
+
},
|
|
272
|
+
readToolUse: (event) => {
|
|
273
|
+
if (event.parentToolUseId) return void 0;
|
|
274
|
+
switch (event.name) {
|
|
275
|
+
case "TaskCreate": {
|
|
276
|
+
const input = taskCreateInputSchema.safeParse(event.input);
|
|
277
|
+
if (!input.success) return void 0;
|
|
278
|
+
const { activeForm, subject } = input.data;
|
|
279
|
+
pendingTodoMap.set(event.toolUseId, {
|
|
280
|
+
activeForm: activeForm ?? subject,
|
|
281
|
+
content: subject,
|
|
282
|
+
status: "pending"
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
case "TaskUpdate": {
|
|
287
|
+
const input = taskUpdateInputSchema.safeParse(event.input);
|
|
288
|
+
if (!input.success) return void 0;
|
|
289
|
+
const { activeForm, status, subject, taskId } = input.data;
|
|
290
|
+
todos = status === "deleted" ? todos.filter(({ id }) => id !== taskId) : todos.map((todo) => todo.id === taskId ? {
|
|
291
|
+
activeForm: activeForm ?? todo.activeForm,
|
|
292
|
+
content: subject ?? todo.content,
|
|
293
|
+
id: todo.id,
|
|
294
|
+
status: status ?? todo.status
|
|
295
|
+
} : todo);
|
|
296
|
+
return toTodoUpdateEvent(event.id, event.createdAt);
|
|
297
|
+
}
|
|
298
|
+
case "TodoWrite": {
|
|
299
|
+
const input = todoWriteInputSchema.safeParse(event.input);
|
|
300
|
+
if (!input.success) return void 0;
|
|
301
|
+
todos = input.data.todos.map(({ activeForm, content, status }, index) => ({
|
|
302
|
+
activeForm,
|
|
303
|
+
content,
|
|
304
|
+
id: String(index),
|
|
305
|
+
status
|
|
306
|
+
}));
|
|
307
|
+
return toTodoUpdateEvent(event.id, event.createdAt);
|
|
308
|
+
}
|
|
309
|
+
default: return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/services/drivers/claudeAgentSdk/toUnknownEvent.ts
|
|
316
|
+
const toUnknownEvent = (id, sdkType, raw, createdAt) => ({
|
|
317
|
+
createdAt,
|
|
318
|
+
id,
|
|
319
|
+
raw,
|
|
320
|
+
sdkType,
|
|
321
|
+
type: "Unknown"
|
|
322
|
+
});
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/services/drivers/claudeAgentSdk/mapAssistantContent.ts
|
|
325
|
+
const mapAssistantContent = (content, context) => content.map((block, index) => {
|
|
326
|
+
const id = getEventId(context.messageUuid, index);
|
|
327
|
+
switch (block.type) {
|
|
328
|
+
case "document":
|
|
329
|
+
case "image":
|
|
330
|
+
case "tool_result": return toUnknownEvent(id, block.type, JSON.stringify(block), context.createdAt);
|
|
331
|
+
case "other": return toUnknownEvent(id, block.blockType, block.raw, context.createdAt);
|
|
332
|
+
case "text": return {
|
|
333
|
+
...context,
|
|
334
|
+
id,
|
|
335
|
+
text: block.text,
|
|
336
|
+
type: "AssistantMessage"
|
|
337
|
+
};
|
|
338
|
+
case "thinking": return {
|
|
339
|
+
...context,
|
|
340
|
+
id,
|
|
341
|
+
thinking: block.thinking,
|
|
342
|
+
type: "Thinking"
|
|
343
|
+
};
|
|
344
|
+
case "tool_use": return {
|
|
345
|
+
...context,
|
|
346
|
+
id,
|
|
347
|
+
input: block.input,
|
|
348
|
+
name: block.name,
|
|
349
|
+
toolUseId: block.id,
|
|
350
|
+
type: "ToolUse"
|
|
351
|
+
};
|
|
352
|
+
default: return exhaustiveGuard(block);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/services/drivers/claudeAgentSdk/mapHookMessage.ts
|
|
357
|
+
const HookPhaseMap = {
|
|
358
|
+
hook_progress: "Progress",
|
|
359
|
+
hook_response: "Response",
|
|
360
|
+
hook_started: "Started"
|
|
361
|
+
};
|
|
362
|
+
const mapHookMessage = (message, createdAt) => ({
|
|
363
|
+
createdAt,
|
|
364
|
+
exitCode: "exit_code" in message ? message.exit_code ?? 0 : 0,
|
|
365
|
+
hookEvent: message.hook_event,
|
|
366
|
+
hookId: message.hook_id,
|
|
367
|
+
hookName: message.hook_name,
|
|
368
|
+
id: message.uuid,
|
|
369
|
+
outcome: "outcome" in message ? message.outcome : "",
|
|
370
|
+
output: "output" in message ? message.output : "",
|
|
371
|
+
phase: HookPhaseMap[message.subtype],
|
|
372
|
+
stderr: "stderr" in message ? message.stderr : "",
|
|
373
|
+
stdout: "stdout" in message ? message.stdout : "",
|
|
374
|
+
type: "Hook"
|
|
375
|
+
});
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/services/drivers/claudeAgentSdk/mapTaskMessage.ts
|
|
378
|
+
const SubagentStatusMap = {
|
|
379
|
+
completed: "Completed",
|
|
380
|
+
failed: "Failed",
|
|
381
|
+
killed: "Stopped",
|
|
382
|
+
paused: "Running",
|
|
383
|
+
pending: "Started",
|
|
384
|
+
running: "Running",
|
|
385
|
+
stopped: "Stopped"
|
|
386
|
+
};
|
|
387
|
+
const mapTaskMessage = (message, createdAt) => {
|
|
388
|
+
const base = {
|
|
389
|
+
createdAt,
|
|
390
|
+
description: "",
|
|
391
|
+
durationMs: 0,
|
|
392
|
+
id: message.uuid,
|
|
393
|
+
lastToolName: "",
|
|
394
|
+
subagentType: "",
|
|
395
|
+
summary: "",
|
|
396
|
+
taskId: message.task_id,
|
|
397
|
+
toolUseId: "tool_use_id" in message ? message.tool_use_id ?? "" : "",
|
|
398
|
+
toolUses: 0,
|
|
399
|
+
totalTokens: 0,
|
|
400
|
+
type: "Subagent"
|
|
401
|
+
};
|
|
402
|
+
switch (message.subtype) {
|
|
403
|
+
case "task_notification": return {
|
|
404
|
+
...base,
|
|
405
|
+
durationMs: message.usage?.duration_ms ?? 0,
|
|
406
|
+
status: SubagentStatusMap[message.status],
|
|
407
|
+
summary: message.summary,
|
|
408
|
+
toolUses: message.usage?.tool_uses ?? 0,
|
|
409
|
+
totalTokens: message.usage?.total_tokens ?? 0
|
|
410
|
+
};
|
|
411
|
+
case "task_progress": return {
|
|
412
|
+
...base,
|
|
413
|
+
description: message.description,
|
|
414
|
+
durationMs: message.usage.duration_ms,
|
|
415
|
+
lastToolName: message.last_tool_name ?? "",
|
|
416
|
+
status: "Running",
|
|
417
|
+
subagentType: message.subagent_type ?? "",
|
|
418
|
+
summary: message.summary ?? "",
|
|
419
|
+
toolUses: message.usage.tool_uses,
|
|
420
|
+
totalTokens: message.usage.total_tokens
|
|
421
|
+
};
|
|
422
|
+
case "task_started": return {
|
|
423
|
+
...base,
|
|
424
|
+
description: message.description,
|
|
425
|
+
status: "Started",
|
|
426
|
+
subagentType: message.subagent_type ?? ""
|
|
427
|
+
};
|
|
428
|
+
case "task_updated": return {
|
|
429
|
+
...base,
|
|
430
|
+
description: message.patch.description ?? "",
|
|
431
|
+
status: message.patch.status ? SubagentStatusMap[message.patch.status] : "Running",
|
|
432
|
+
summary: message.patch.error ?? ""
|
|
433
|
+
};
|
|
434
|
+
default: return exhaustiveGuard(message);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/services/drivers/claudeAgentSdk/mapSystemMessage.ts
|
|
439
|
+
const SessionStateMap = {
|
|
440
|
+
idle: "Idle",
|
|
441
|
+
requires_action: "RequiresAction",
|
|
442
|
+
running: "Running"
|
|
443
|
+
};
|
|
444
|
+
const mapSystemMessage = (message, createdAt) => {
|
|
445
|
+
switch (message.subtype) {
|
|
446
|
+
case "compact_boundary": return [{
|
|
447
|
+
createdAt,
|
|
448
|
+
id: message.uuid,
|
|
449
|
+
postTokens: message.compact_metadata.post_tokens ?? 0,
|
|
450
|
+
preTokens: message.compact_metadata.pre_tokens,
|
|
451
|
+
trigger: message.compact_metadata.trigger === "auto" ? "auto" : "manual",
|
|
452
|
+
type: "Compaction"
|
|
453
|
+
}];
|
|
454
|
+
case "hook_progress":
|
|
455
|
+
case "hook_response":
|
|
456
|
+
case "hook_started": return [mapHookMessage(message, createdAt)];
|
|
457
|
+
case "local_command_output": return [{
|
|
458
|
+
content: message.content,
|
|
459
|
+
createdAt,
|
|
460
|
+
id: message.uuid,
|
|
461
|
+
type: "CommandOutput"
|
|
462
|
+
}];
|
|
463
|
+
case "session_state_changed": return [{
|
|
464
|
+
createdAt,
|
|
465
|
+
id: message.uuid,
|
|
466
|
+
state: SessionStateMap[message.state],
|
|
467
|
+
type: "SessionState"
|
|
468
|
+
}];
|
|
469
|
+
case "task_notification":
|
|
470
|
+
case "task_progress":
|
|
471
|
+
case "task_started":
|
|
472
|
+
case "task_updated": return [mapTaskMessage(message, createdAt)];
|
|
473
|
+
default: return [toUnknownEvent(message.uuid, `system:${message.subtype}`, JSON.stringify(message), createdAt)];
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region src/services/drivers/claudeAgentSdk/toToolResultText.ts
|
|
478
|
+
const toToolResultText = (content) => typeof content === "string" ? content : content.map((part) => part.text === void 0 ? `[${part.type}]` : part.text).join("\n");
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/services/drivers/claudeAgentSdk/mapUserContent.ts
|
|
481
|
+
const mapUserContent = (content, context) => {
|
|
482
|
+
if (typeof content === "string") return [{
|
|
483
|
+
...context,
|
|
484
|
+
attachmentCount: 0,
|
|
485
|
+
id: context.messageUuid,
|
|
486
|
+
text: content,
|
|
487
|
+
type: "UserMessage"
|
|
488
|
+
}];
|
|
489
|
+
const events = [];
|
|
490
|
+
const texts = [];
|
|
491
|
+
let attachmentCount = 0;
|
|
492
|
+
for (const [index, block] of content.entries()) {
|
|
493
|
+
const id = getEventId(context.messageUuid, index);
|
|
494
|
+
switch (block.type) {
|
|
495
|
+
case "document":
|
|
496
|
+
case "image":
|
|
497
|
+
attachmentCount++;
|
|
498
|
+
break;
|
|
499
|
+
case "other":
|
|
500
|
+
events.push(toUnknownEvent(id, block.blockType, block.raw, context.createdAt));
|
|
501
|
+
break;
|
|
502
|
+
case "text":
|
|
503
|
+
texts.push(block.text);
|
|
504
|
+
break;
|
|
505
|
+
case "thinking":
|
|
506
|
+
case "tool_use":
|
|
507
|
+
events.push(toUnknownEvent(id, block.type, JSON.stringify(block), context.createdAt));
|
|
508
|
+
break;
|
|
509
|
+
case "tool_result":
|
|
510
|
+
events.push({
|
|
511
|
+
content: toToolResultText(block.content),
|
|
512
|
+
createdAt: context.createdAt,
|
|
513
|
+
id,
|
|
514
|
+
isError: Boolean(block.is_error),
|
|
515
|
+
parentToolUseId: context.parentToolUseId,
|
|
516
|
+
toolUseId: block.tool_use_id,
|
|
517
|
+
type: "ToolResult"
|
|
518
|
+
});
|
|
519
|
+
break;
|
|
520
|
+
default: exhaustiveGuard(block);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return texts.length > 0 || attachmentCount > 0 ? [{
|
|
524
|
+
...context,
|
|
525
|
+
attachmentCount,
|
|
526
|
+
id: context.messageUuid,
|
|
527
|
+
text: texts.join("\n\n"),
|
|
528
|
+
type: "UserMessage"
|
|
529
|
+
}, ...events] : events;
|
|
530
|
+
};
|
|
531
|
+
//#endregion
|
|
532
|
+
//#region src/services/drivers/claudeAgentSdk/parseMessageBody.ts
|
|
533
|
+
const documentBlockSchema = z.object({ type: z.literal("document") });
|
|
534
|
+
const imageBlockSchema = z.object({ type: z.literal("image") });
|
|
535
|
+
const textBlockSchema = z.object({
|
|
536
|
+
text: z.string(),
|
|
537
|
+
type: z.literal("text")
|
|
538
|
+
});
|
|
539
|
+
const thinkingBlockSchema = z.object({
|
|
540
|
+
thinking: z.string(),
|
|
541
|
+
type: z.literal("thinking")
|
|
542
|
+
});
|
|
543
|
+
const toolResultBlockSchema = z.object({
|
|
544
|
+
content: z.union([z.string(), z.looseObject({
|
|
545
|
+
text: z.string().optional(),
|
|
546
|
+
type: z.string()
|
|
547
|
+
}).array()]),
|
|
548
|
+
is_error: z.boolean().optional(),
|
|
549
|
+
tool_use_id: z.string().min(1),
|
|
550
|
+
type: z.literal("tool_result")
|
|
551
|
+
});
|
|
552
|
+
const toolUseBlockSchema = z.object({
|
|
553
|
+
id: z.string().min(1),
|
|
554
|
+
input: z.record(z.string(), z.unknown()),
|
|
555
|
+
name: z.string().min(1),
|
|
556
|
+
type: z.literal("tool_use")
|
|
557
|
+
});
|
|
558
|
+
const otherBlockSchema = z.looseObject({ type: z.string() }).transform((block) => ({
|
|
559
|
+
blockType: block.type,
|
|
560
|
+
raw: JSON.stringify(block),
|
|
561
|
+
type: "other"
|
|
562
|
+
}));
|
|
563
|
+
const contentBlockSchema = z.union([
|
|
564
|
+
documentBlockSchema,
|
|
565
|
+
imageBlockSchema,
|
|
566
|
+
textBlockSchema,
|
|
567
|
+
thinkingBlockSchema,
|
|
568
|
+
toolResultBlockSchema,
|
|
569
|
+
toolUseBlockSchema,
|
|
570
|
+
otherBlockSchema
|
|
571
|
+
]);
|
|
572
|
+
const messageBodySchema = z.looseObject({ content: z.union([contentBlockSchema.array(), z.string()]) });
|
|
573
|
+
const parseMessageBody = (value) => {
|
|
574
|
+
const result = messageBodySchema.safeParse(value);
|
|
575
|
+
return result.success ? result.data : void 0;
|
|
576
|
+
};
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/services/drivers/claudeAgentSdk/toSessionInitEvent.ts
|
|
579
|
+
const toSessionInitEvent = (message, permissionMode, createdAt) => ({
|
|
580
|
+
agents: [...new Set(message.agents)],
|
|
581
|
+
claudeCodeVersion: message.claude_code_version,
|
|
582
|
+
createdAt,
|
|
583
|
+
cwd: message.cwd,
|
|
584
|
+
id: message.uuid,
|
|
585
|
+
mcpServers: [...new Map(message.mcp_servers.map(({ name, status }) => [name, {
|
|
586
|
+
name,
|
|
587
|
+
status
|
|
588
|
+
}])).values()],
|
|
589
|
+
model: message.model,
|
|
590
|
+
outputStyle: message.output_style,
|
|
591
|
+
permissionMode,
|
|
592
|
+
plugins: [...new Set(message.plugins.map(({ name }) => name))],
|
|
593
|
+
skills: [...new Set(message.skills)],
|
|
594
|
+
slashCommands: [...new Set(message.slash_commands)],
|
|
595
|
+
tools: [...new Set(message.tools)],
|
|
596
|
+
type: "SessionInit"
|
|
597
|
+
});
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/services/drivers/claudeAgentSdk/toTurnResultEvents.ts
|
|
600
|
+
const toTurnResultEvents = (message, createdAt) => [{
|
|
601
|
+
createdAt,
|
|
602
|
+
durationApiMs: message.duration_api_ms,
|
|
603
|
+
durationMs: message.duration_ms,
|
|
604
|
+
errors: message.subtype === "success" ? [] : message.errors,
|
|
605
|
+
id: message.uuid,
|
|
606
|
+
isError: message.is_error,
|
|
607
|
+
numTurns: message.num_turns,
|
|
608
|
+
result: message.subtype === "success" ? message.result : "",
|
|
609
|
+
subtype: message.subtype,
|
|
610
|
+
totalCostUsd: message.total_cost_usd,
|
|
611
|
+
type: "TurnResult",
|
|
612
|
+
usage: {
|
|
613
|
+
cacheCreationInputTokens: message.usage.cache_creation_input_tokens,
|
|
614
|
+
cacheReadInputTokens: message.usage.cache_read_input_tokens,
|
|
615
|
+
inputTokens: message.usage.input_tokens,
|
|
616
|
+
outputTokens: message.usage.output_tokens
|
|
617
|
+
}
|
|
618
|
+
}, {
|
|
619
|
+
createdAt,
|
|
620
|
+
id: getEventId(message.uuid, "SessionState"),
|
|
621
|
+
state: "Idle",
|
|
622
|
+
type: "SessionState"
|
|
623
|
+
}];
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region src/services/drivers/claudeAgentSdk/createSdkMessageMapper.ts
|
|
626
|
+
const createSdkMessageMapper = () => {
|
|
627
|
+
const fileOriginTracker = createFileOriginTracker();
|
|
628
|
+
const streamTracker = createStreamTracker();
|
|
629
|
+
const todoTracker = createTodoTracker();
|
|
630
|
+
const settings = {
|
|
631
|
+
model: "",
|
|
632
|
+
permissionMode: "default"
|
|
633
|
+
};
|
|
634
|
+
const updateSettings = (id, createdAt, { model, permissionMode }) => {
|
|
635
|
+
if (model) settings.model = model;
|
|
636
|
+
const parsedPermissionMode = permissionModeSchema.safeParse(permissionMode);
|
|
637
|
+
if (parsedPermissionMode.success) settings.permissionMode = parsedPermissionMode.data;
|
|
638
|
+
return {
|
|
639
|
+
createdAt,
|
|
640
|
+
id,
|
|
641
|
+
...settings,
|
|
642
|
+
type: "SessionSettings"
|
|
643
|
+
};
|
|
644
|
+
};
|
|
645
|
+
const mapContent = ({ body, error, messageType, messageUuid, parentToolUseId, toolUseResult }, createdAt) => {
|
|
646
|
+
const messageBody = parseMessageBody(body);
|
|
647
|
+
if (!messageBody) return [toUnknownEvent(messageUuid, messageType, JSON.stringify(body), createdAt)];
|
|
648
|
+
const context = {
|
|
649
|
+
createdAt,
|
|
650
|
+
messageUuid,
|
|
651
|
+
parentToolUseId
|
|
652
|
+
};
|
|
653
|
+
const { content } = messageBody;
|
|
654
|
+
const contentEvents = messageType === "assistant" ? mapAssistantContent(typeof content === "string" ? [{
|
|
655
|
+
text: content,
|
|
656
|
+
type: "text"
|
|
657
|
+
}] : content, context) : mapUserContent(content, context);
|
|
658
|
+
const events = [];
|
|
659
|
+
for (const event of contentEvents) if (event.type === "ToolUse") {
|
|
660
|
+
events.push(event);
|
|
661
|
+
const todoUpdateEvent = todoTracker.readToolUse(event);
|
|
662
|
+
if (todoUpdateEvent) events.push(todoUpdateEvent);
|
|
663
|
+
} else if (event.type === "ToolResult") {
|
|
664
|
+
events.push(fileOriginTracker.readToolResult(event, toolUseResult));
|
|
665
|
+
const todoUpdateEvent = todoTracker.readToolResult(event, toolUseResult);
|
|
666
|
+
if (todoUpdateEvent) events.push(todoUpdateEvent);
|
|
667
|
+
} else events.push(event);
|
|
668
|
+
if (error) events.push({
|
|
669
|
+
createdAt,
|
|
670
|
+
id: getEventId(messageUuid, "HostError"),
|
|
671
|
+
message: error,
|
|
672
|
+
type: "HostError"
|
|
673
|
+
});
|
|
674
|
+
return events;
|
|
675
|
+
};
|
|
676
|
+
const mapMessage = (message, createdAt) => {
|
|
677
|
+
const sdkType = message.type;
|
|
678
|
+
if (IGNORED_SDK_MESSAGE_TYPES.includes(sdkType)) return [];
|
|
679
|
+
switch (message.type) {
|
|
680
|
+
case "assistant": return mapContent({
|
|
681
|
+
body: message.message,
|
|
682
|
+
error: message.error ?? "",
|
|
683
|
+
messageType: message.type,
|
|
684
|
+
messageUuid: message.uuid,
|
|
685
|
+
parentToolUseId: message.parent_tool_use_id ?? "",
|
|
686
|
+
toolUseResult: void 0
|
|
687
|
+
}, createdAt);
|
|
688
|
+
case "rate_limit_event": {
|
|
689
|
+
const { rateLimitType, resetsAt, status, utilization } = message.rate_limit_info;
|
|
690
|
+
return [{
|
|
691
|
+
createdAt,
|
|
692
|
+
id: message.uuid,
|
|
693
|
+
rateLimitType: rateLimitType ?? "",
|
|
694
|
+
resetsAt: resetsAt === void 0 ? void 0 : new Date(Temporal.Duration.from({ seconds: resetsAt }).total("milliseconds")),
|
|
695
|
+
status,
|
|
696
|
+
type: "RateLimit",
|
|
697
|
+
utilization
|
|
698
|
+
}];
|
|
699
|
+
}
|
|
700
|
+
case "result":
|
|
701
|
+
streamTracker.reset();
|
|
702
|
+
return toTurnResultEvents(message, createdAt);
|
|
703
|
+
case "stream_event": return streamTracker.readStreamEvent(message, createdAt);
|
|
704
|
+
case "tool_progress": return [{
|
|
705
|
+
createdAt,
|
|
706
|
+
elapsedSeconds: message.elapsed_time_seconds,
|
|
707
|
+
id: message.uuid,
|
|
708
|
+
parentToolUseId: message.parent_tool_use_id ?? "",
|
|
709
|
+
toolUseId: message.tool_use_id,
|
|
710
|
+
type: "ToolProgress"
|
|
711
|
+
}];
|
|
712
|
+
case "user": return mapContent({
|
|
713
|
+
body: message.message,
|
|
714
|
+
error: "",
|
|
715
|
+
messageType: message.type,
|
|
716
|
+
messageUuid: message.uuid ?? crypto.randomUUID(),
|
|
717
|
+
parentToolUseId: message.parent_tool_use_id ?? "",
|
|
718
|
+
toolUseResult: message.tool_use_result
|
|
719
|
+
}, createdAt);
|
|
720
|
+
case "system": if (message.subtype === "init") {
|
|
721
|
+
const settingsEvent = updateSettings(getEventId(message.uuid, "SessionSettings"), createdAt, {
|
|
722
|
+
model: message.model,
|
|
723
|
+
permissionMode: message.permissionMode
|
|
724
|
+
});
|
|
725
|
+
return [toSessionInitEvent(message, settingsEvent.permissionMode, createdAt), settingsEvent];
|
|
726
|
+
} else if (message.subtype === "status") {
|
|
727
|
+
const stateEvent = {
|
|
728
|
+
createdAt,
|
|
729
|
+
id: message.uuid,
|
|
730
|
+
state: message.status === "compacting" ? "Compacting" : "Running",
|
|
731
|
+
type: "SessionState"
|
|
732
|
+
};
|
|
733
|
+
return message.permissionMode ? [stateEvent, updateSettings(getEventId(message.uuid, "SessionSettings"), createdAt, { permissionMode: message.permissionMode })] : [stateEvent];
|
|
734
|
+
} else if (message.subtype === "thinking_tokens") return streamTracker.readThinkingTokens(message, createdAt);
|
|
735
|
+
else return mapSystemMessage(message, createdAt);
|
|
736
|
+
default: return [toUnknownEvent(message.uuid, message.type, JSON.stringify(message), createdAt)];
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
return {
|
|
740
|
+
mapHistory: ({ message, parent_tool_use_id, type, uuid }, createdAt, toolUseResult) => type === "system" ? [toUnknownEvent(uuid, type, JSON.stringify(message), createdAt)] : mapContent({
|
|
741
|
+
body: message,
|
|
742
|
+
error: "",
|
|
743
|
+
messageType: type,
|
|
744
|
+
messageUuid: uuid,
|
|
745
|
+
parentToolUseId: parent_tool_use_id ?? "",
|
|
746
|
+
toolUseResult
|
|
747
|
+
}, createdAt),
|
|
748
|
+
mapMessage,
|
|
749
|
+
updateSettings
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
//#endregion
|
|
753
|
+
//#region src/services/drivers/claudeAgentSdk/readSessionHistory.ts
|
|
754
|
+
const readSessionHistory = async (sessionId, cwd, resumeAt) => {
|
|
755
|
+
const sessionMessages = await getSessionMessages(sessionId, { dir: cwd });
|
|
756
|
+
if (!resumeAt) return sessionMessages;
|
|
757
|
+
const resumeAtIndex = sessionMessages.findIndex(({ uuid }) => uuid === resumeAt);
|
|
758
|
+
if (resumeAtIndex === -1) throw new InvalidOperationError(Operation.Read, sessionId, `the transcript holds no message ${resumeAt}`);
|
|
759
|
+
return sessionMessages.slice(0, resumeAtIndex + 1);
|
|
760
|
+
};
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/services/drivers/claudeAgentSdk/readToolUseResultMap.ts
|
|
763
|
+
const transcriptEntrySchema = z.object({
|
|
764
|
+
toolUseResult: z.unknown(),
|
|
765
|
+
uuid: z.string()
|
|
766
|
+
});
|
|
767
|
+
const readToolUseResultMap = async (sessionId) => {
|
|
768
|
+
const toolUseResultMap = /* @__PURE__ */ new Map();
|
|
769
|
+
const projectsDirectory = join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "projects");
|
|
770
|
+
if (!existsSync(projectsDirectory)) return toolUseResultMap;
|
|
771
|
+
const transcriptPath = (await readdir(projectsDirectory)).map((projectDirectory) => join(projectsDirectory, projectDirectory, `${sessionId}.jsonl`)).find((path) => existsSync(path));
|
|
772
|
+
if (!transcriptPath) return toolUseResultMap;
|
|
773
|
+
for (const line of (await readFile(transcriptPath, "utf8")).split("\n")) getResult(() => transcriptEntrySchema.parse(JSON.parse(line))).match(({ toolUseResult, uuid }) => {
|
|
774
|
+
if (toolUseResult !== void 0) toolUseResultMap.set(uuid, toolUseResult);
|
|
775
|
+
}, noop);
|
|
776
|
+
return toolUseResultMap;
|
|
777
|
+
};
|
|
778
|
+
//#endregion
|
|
779
|
+
//#region src/services/drivers/claudeAgentSdk/getSessionTitle.ts
|
|
780
|
+
const getSessionTitle = ({ customTitle, firstPrompt, summary }) => customTitle || summary || firstPrompt || "";
|
|
781
|
+
//#endregion
|
|
782
|
+
//#region src/services/drivers/claudeAgentSdk/toCapabilitiesEvent.ts
|
|
783
|
+
const toCapabilitiesEvent = (id, slashCommands, models, createdAt) => ({
|
|
784
|
+
commands: [...new Map(slashCommands.map(({ argumentHint, description, name }) => [name, {
|
|
785
|
+
argumentHint,
|
|
786
|
+
description,
|
|
787
|
+
name
|
|
788
|
+
}])).values()],
|
|
789
|
+
createdAt,
|
|
790
|
+
id,
|
|
791
|
+
models: [...new Map(models.map(({ description, displayName, value }) => [value, {
|
|
792
|
+
description,
|
|
793
|
+
displayName,
|
|
794
|
+
value
|
|
795
|
+
}])).values()],
|
|
796
|
+
type: "Capabilities"
|
|
797
|
+
});
|
|
798
|
+
//#endregion
|
|
799
|
+
//#region src/services/drivers/claudeAgentSdk/toContextUsageEvent.ts
|
|
800
|
+
const toContextUsageEvent = (id, { autoCompactThreshold, isAutoCompactEnabled, maxTokens, percentage, totalTokens }, createdAt) => ({
|
|
801
|
+
autoCompactThreshold: autoCompactThreshold ?? 0,
|
|
802
|
+
createdAt,
|
|
803
|
+
id,
|
|
804
|
+
isAutoCompactEnabled,
|
|
805
|
+
maxTokens,
|
|
806
|
+
percentage,
|
|
807
|
+
totalTokens,
|
|
808
|
+
type: "ContextUsage"
|
|
809
|
+
});
|
|
810
|
+
//#endregion
|
|
811
|
+
//#region src/services/drivers/claudeAgentSdk/watchSession.ts
|
|
812
|
+
const watchSession = async (sessionId, openSession, { emit, onSessionsChange, openSessionMap }) => {
|
|
813
|
+
const { cwd, mapper, query } = openSession;
|
|
814
|
+
const readContextUsage = () => getResultAsync(() => query.getContextUsage()).match((contextUsage) => {
|
|
815
|
+
emit(sessionId, [toContextUsageEvent(crypto.randomUUID(), contextUsage, /* @__PURE__ */ new Date())]);
|
|
816
|
+
}, console.error);
|
|
817
|
+
const readTitle = () => getResultAsync(() => getSessionInfo(sessionId, { dir: cwd })).match((sessionInfo) => {
|
|
818
|
+
if (!sessionInfo) return;
|
|
819
|
+
openSession.title = getSessionTitle(sessionInfo) || openSession.title;
|
|
820
|
+
onSessionsChange();
|
|
821
|
+
}, console.error);
|
|
822
|
+
await Promise.all([
|
|
823
|
+
getResultAsync(() => Promise.all([query.supportedCommands(), query.supportedModels()])).match(([slashCommands, models]) => {
|
|
824
|
+
emit(sessionId, [toCapabilitiesEvent(crypto.randomUUID(), slashCommands, models, /* @__PURE__ */ new Date())]);
|
|
825
|
+
}, console.error),
|
|
826
|
+
readContextUsage(),
|
|
827
|
+
getResultAsync(async () => {
|
|
828
|
+
for await (const message of query) {
|
|
829
|
+
emit(sessionId, mapper.mapMessage(message, /* @__PURE__ */ new Date()));
|
|
830
|
+
if (message.type === "result") await Promise.all([readContextUsage(), readTitle()]);
|
|
831
|
+
}
|
|
832
|
+
}).match(() => {
|
|
833
|
+
closeOpenSession(sessionId, openSession, "", {
|
|
834
|
+
emit,
|
|
835
|
+
onSessionsChange,
|
|
836
|
+
openSessionMap
|
|
837
|
+
});
|
|
838
|
+
}, (error) => {
|
|
839
|
+
closeOpenSession(sessionId, openSession, error.message, {
|
|
840
|
+
emit,
|
|
841
|
+
onSessionsChange,
|
|
842
|
+
openSessionMap
|
|
843
|
+
});
|
|
844
|
+
})
|
|
845
|
+
]);
|
|
846
|
+
};
|
|
847
|
+
//#endregion
|
|
848
|
+
//#region src/services/drivers/claudeAgentSdk/createSessionOpener.ts
|
|
849
|
+
const createSessionOpener = ({ emit, onSessionOpen, onSessionsChange, openSessionMap, taskRegistry }) => async ({ cwd, isFork, resumeAt, resumeFrom, sessionId }) => {
|
|
850
|
+
const mapper = createSdkMessageMapper();
|
|
851
|
+
const history = resumeFrom ? await readSessionHistory(resumeFrom, cwd, resumeAt) : [];
|
|
852
|
+
const toolUseResultMap = resumeFrom ? await readToolUseResultMap(resumeFrom) : /* @__PURE__ */ new Map();
|
|
853
|
+
const input = createInputQueue();
|
|
854
|
+
const pendingPermissionMap = /* @__PURE__ */ new Map();
|
|
855
|
+
const sessionQuery = query({
|
|
856
|
+
options: {
|
|
857
|
+
canUseTool: createPermissionBridge(sessionId, pendingPermissionMap, emit),
|
|
858
|
+
cwd,
|
|
859
|
+
enableFileCheckpointing: true,
|
|
860
|
+
env: {
|
|
861
|
+
...process.env,
|
|
862
|
+
CLAUDE_CODE_ENABLE_TODO_TOOLS: "true"
|
|
863
|
+
},
|
|
864
|
+
includeHookEvents: true,
|
|
865
|
+
includePartialMessages: true,
|
|
866
|
+
settingSources: SETTING_SOURCES,
|
|
867
|
+
systemPrompt: {
|
|
868
|
+
preset: "claude_code",
|
|
869
|
+
type: "preset"
|
|
870
|
+
},
|
|
871
|
+
thinking: {
|
|
872
|
+
display: "summarized",
|
|
873
|
+
type: "adaptive"
|
|
874
|
+
},
|
|
875
|
+
...resumeFrom ? { resume: resumeFrom } : {},
|
|
876
|
+
...resumeAt ? { resumeSessionAt: resumeAt } : {},
|
|
877
|
+
...!resumeFrom || isFork ? {
|
|
878
|
+
forkSession: isFork,
|
|
879
|
+
sessionId
|
|
880
|
+
} : {}
|
|
881
|
+
},
|
|
882
|
+
prompt: input.iterable
|
|
883
|
+
});
|
|
884
|
+
const openSession = {
|
|
885
|
+
cwd,
|
|
886
|
+
input,
|
|
887
|
+
lastActivityAt: /* @__PURE__ */ new Date(),
|
|
888
|
+
mapper,
|
|
889
|
+
pendingPermissionMap,
|
|
890
|
+
query: sessionQuery,
|
|
891
|
+
state: "Idle",
|
|
892
|
+
title: ""
|
|
893
|
+
};
|
|
894
|
+
onSessionOpen(sessionId);
|
|
895
|
+
openSessionMap.set(sessionId, openSession);
|
|
896
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
897
|
+
emit(sessionId, [...history.flatMap((sessionMessage) => mapper.mapHistory(sessionMessage, createdAt, toolUseResultMap.get(sessionMessage.uuid))), {
|
|
898
|
+
createdAt,
|
|
899
|
+
id: getEventId(crypto.randomUUID(), "SessionState"),
|
|
900
|
+
state: "Idle",
|
|
901
|
+
type: "SessionState"
|
|
902
|
+
}]);
|
|
903
|
+
onSessionsChange();
|
|
904
|
+
taskRegistry.run(() => watchSession(sessionId, openSession, {
|
|
905
|
+
emit,
|
|
906
|
+
onSessionsChange,
|
|
907
|
+
openSessionMap
|
|
908
|
+
}));
|
|
909
|
+
return sessionId;
|
|
910
|
+
};
|
|
911
|
+
//#endregion
|
|
912
|
+
//#region src/services/drivers/claudeAgentSdk/readSessionCwd.ts
|
|
913
|
+
const readSessionCwd = async (sessionId) => {
|
|
914
|
+
const sessionInfo = await getSessionInfo(sessionId);
|
|
915
|
+
if (!sessionInfo?.cwd) throw new InvalidOperationError(Operation.Read, sessionId, "no session with this id on disk");
|
|
916
|
+
return sessionInfo.cwd;
|
|
917
|
+
};
|
|
918
|
+
//#endregion
|
|
919
|
+
//#region src/services/drivers/claudeAgentSdk/toContentBlockParam.ts
|
|
920
|
+
const toContentBlockParam = ({ data, mediaType, name }) => {
|
|
921
|
+
switch (mediaType) {
|
|
922
|
+
case "image/gif":
|
|
923
|
+
case "image/jpeg":
|
|
924
|
+
case "image/png":
|
|
925
|
+
case "image/webp": return {
|
|
926
|
+
source: {
|
|
927
|
+
data,
|
|
928
|
+
media_type: mediaType,
|
|
929
|
+
type: "base64"
|
|
930
|
+
},
|
|
931
|
+
type: "image"
|
|
932
|
+
};
|
|
933
|
+
case "application/pdf": return {
|
|
934
|
+
source: {
|
|
935
|
+
data,
|
|
936
|
+
media_type: mediaType,
|
|
937
|
+
type: "base64"
|
|
938
|
+
},
|
|
939
|
+
title: name,
|
|
940
|
+
type: "document"
|
|
941
|
+
};
|
|
942
|
+
case "text/plain": return {
|
|
943
|
+
source: {
|
|
944
|
+
data,
|
|
945
|
+
media_type: mediaType,
|
|
946
|
+
type: "text"
|
|
947
|
+
},
|
|
948
|
+
title: name,
|
|
949
|
+
type: "document"
|
|
950
|
+
};
|
|
951
|
+
default: return exhaustiveGuard(mediaType);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
//#endregion
|
|
955
|
+
//#region src/services/shared/createTaskRegistry.ts
|
|
956
|
+
const createTaskRegistry = () => {
|
|
957
|
+
const pendingSet = /* @__PURE__ */ new Set();
|
|
958
|
+
return {
|
|
959
|
+
drain: async () => {
|
|
960
|
+
await Promise.all(pendingSet);
|
|
961
|
+
},
|
|
962
|
+
run: (task) => {
|
|
963
|
+
const pending = task().finally(() => {
|
|
964
|
+
pendingSet.delete(pending);
|
|
965
|
+
});
|
|
966
|
+
pendingSet.add(pending);
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
};
|
|
970
|
+
//#endregion
|
|
971
|
+
//#region src/services/drivers/claudeAgentSdk/createClaudeAgentSdkDriver.ts
|
|
972
|
+
const createClaudeAgentSdkDriver = ({ onEvents, onSessionOpen, onSessionsChange }) => {
|
|
973
|
+
const openSessionMap = /* @__PURE__ */ new Map();
|
|
974
|
+
const taskRegistry = createTaskRegistry();
|
|
975
|
+
const emit = (sessionId, events) => {
|
|
976
|
+
const openSession = openSessionMap.get(sessionId);
|
|
977
|
+
let isStateChanged = false;
|
|
978
|
+
if (openSession) {
|
|
979
|
+
openSession.lastActivityAt = /* @__PURE__ */ new Date();
|
|
980
|
+
for (const event of events) if (event.type === "SessionState" && event.state !== openSession.state) {
|
|
981
|
+
openSession.state = event.state;
|
|
982
|
+
isStateChanged = true;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
onEvents(sessionId, events);
|
|
986
|
+
if (isStateChanged) onSessionsChange();
|
|
987
|
+
};
|
|
988
|
+
const getOpenSession = (sessionId) => {
|
|
989
|
+
const openSession = openSessionMap.get(sessionId);
|
|
990
|
+
if (!openSession) throw new InvalidOperationError(Operation.Read, sessionId, "the session is not open on this host");
|
|
991
|
+
return openSession;
|
|
992
|
+
};
|
|
993
|
+
const openSessionQuery = createSessionOpener({
|
|
994
|
+
emit,
|
|
995
|
+
onSessionOpen,
|
|
996
|
+
onSessionsChange,
|
|
997
|
+
openSessionMap,
|
|
998
|
+
taskRegistry
|
|
999
|
+
});
|
|
1000
|
+
const closeSession = (sessionId) => {
|
|
1001
|
+
const closingSession = getOpenSession(sessionId);
|
|
1002
|
+
closeOpenSession(sessionId, closingSession, "", {
|
|
1003
|
+
emit,
|
|
1004
|
+
onSessionsChange,
|
|
1005
|
+
openSessionMap
|
|
1006
|
+
});
|
|
1007
|
+
closingSession.input.close();
|
|
1008
|
+
closingSession.query.close();
|
|
1009
|
+
};
|
|
1010
|
+
const prompt = (sessionId, text, attachments) => {
|
|
1011
|
+
const openSession = getOpenSession(sessionId);
|
|
1012
|
+
const uuid = crypto.randomUUID();
|
|
1013
|
+
const createdAt = /* @__PURE__ */ new Date();
|
|
1014
|
+
openSession.input.push({
|
|
1015
|
+
message: {
|
|
1016
|
+
content: [...attachments.map((attachment) => toContentBlockParam(attachment)), {
|
|
1017
|
+
text,
|
|
1018
|
+
type: "text"
|
|
1019
|
+
}],
|
|
1020
|
+
role: "user"
|
|
1021
|
+
},
|
|
1022
|
+
parent_tool_use_id: null,
|
|
1023
|
+
type: "user",
|
|
1024
|
+
uuid
|
|
1025
|
+
});
|
|
1026
|
+
openSession.title ||= text;
|
|
1027
|
+
emit(sessionId, [{
|
|
1028
|
+
attachmentCount: attachments.length,
|
|
1029
|
+
createdAt,
|
|
1030
|
+
id: uuid,
|
|
1031
|
+
messageUuid: uuid,
|
|
1032
|
+
parentToolUseId: "",
|
|
1033
|
+
text,
|
|
1034
|
+
type: "UserMessage"
|
|
1035
|
+
}, {
|
|
1036
|
+
createdAt,
|
|
1037
|
+
id: getEventId(uuid, "SessionState"),
|
|
1038
|
+
state: "Running",
|
|
1039
|
+
type: "SessionState"
|
|
1040
|
+
}]);
|
|
1041
|
+
};
|
|
1042
|
+
const resumeSession = async (sessionId) => {
|
|
1043
|
+
if (openSessionMap.has(sessionId)) return sessionId;
|
|
1044
|
+
const cwd = await readSessionCwd(sessionId);
|
|
1045
|
+
return openSessionQuery({
|
|
1046
|
+
cwd,
|
|
1047
|
+
isFork: false,
|
|
1048
|
+
resumeAt: "",
|
|
1049
|
+
resumeFrom: sessionId,
|
|
1050
|
+
sessionId
|
|
1051
|
+
});
|
|
1052
|
+
};
|
|
1053
|
+
return {
|
|
1054
|
+
close: async () => {
|
|
1055
|
+
for (const sessionId of openSessionMap.keys()) closeSession(sessionId);
|
|
1056
|
+
await taskRegistry.drain();
|
|
1057
|
+
},
|
|
1058
|
+
closeSession,
|
|
1059
|
+
createSession: (cwd) => openSessionQuery({
|
|
1060
|
+
cwd,
|
|
1061
|
+
isFork: false,
|
|
1062
|
+
resumeAt: "",
|
|
1063
|
+
resumeFrom: "",
|
|
1064
|
+
sessionId: crypto.randomUUID()
|
|
1065
|
+
}),
|
|
1066
|
+
forkSession: async (sessionId, messageUuid) => {
|
|
1067
|
+
const cwd = await readSessionCwd(sessionId);
|
|
1068
|
+
return openSessionQuery({
|
|
1069
|
+
cwd,
|
|
1070
|
+
isFork: true,
|
|
1071
|
+
resumeAt: messageUuid,
|
|
1072
|
+
resumeFrom: sessionId,
|
|
1073
|
+
sessionId: crypto.randomUUID()
|
|
1074
|
+
});
|
|
1075
|
+
},
|
|
1076
|
+
interrupt: async (sessionId) => {
|
|
1077
|
+
await getOpenSession(sessionId).query.interrupt();
|
|
1078
|
+
},
|
|
1079
|
+
listSessions: async () => {
|
|
1080
|
+
const sessionInfos = await listSessions({ limit: 50 });
|
|
1081
|
+
const savedSessionIds = new Set(sessionInfos.map(({ sessionId }) => sessionId));
|
|
1082
|
+
const unsavedSessions = [...openSessionMap].filter(([id]) => !savedSessionIds.has(id)).map(([id, { cwd, lastActivityAt, state, title }]) => ({
|
|
1083
|
+
cwd,
|
|
1084
|
+
id,
|
|
1085
|
+
lastActivityAt,
|
|
1086
|
+
state,
|
|
1087
|
+
title
|
|
1088
|
+
}));
|
|
1089
|
+
const savedSessions = sessionInfos.map((sessionInfo) => {
|
|
1090
|
+
const openSession = openSessionMap.get(sessionInfo.sessionId);
|
|
1091
|
+
return {
|
|
1092
|
+
cwd: sessionInfo.cwd ?? "",
|
|
1093
|
+
id: sessionInfo.sessionId,
|
|
1094
|
+
lastActivityAt: openSession?.lastActivityAt ?? new Date(sessionInfo.lastModified),
|
|
1095
|
+
state: openSession?.state ?? "Closed",
|
|
1096
|
+
title: openSession?.title || getSessionTitle(sessionInfo)
|
|
1097
|
+
};
|
|
1098
|
+
});
|
|
1099
|
+
return [...unsavedSessions, ...savedSessions];
|
|
1100
|
+
},
|
|
1101
|
+
prompt,
|
|
1102
|
+
resolvePermission: (sessionId, requestId, behavior, message) => {
|
|
1103
|
+
getOpenSession(sessionId).pendingPermissionMap.get(requestId)?.settle(behavior, message);
|
|
1104
|
+
},
|
|
1105
|
+
resumeAt: async (sessionId, messageUuid) => {
|
|
1106
|
+
const cwd = await readSessionCwd(sessionId);
|
|
1107
|
+
if (openSessionMap.has(sessionId)) closeSession(sessionId);
|
|
1108
|
+
return openSessionQuery({
|
|
1109
|
+
cwd,
|
|
1110
|
+
isFork: false,
|
|
1111
|
+
resumeAt: messageUuid,
|
|
1112
|
+
resumeFrom: sessionId,
|
|
1113
|
+
sessionId
|
|
1114
|
+
});
|
|
1115
|
+
},
|
|
1116
|
+
resumeSession,
|
|
1117
|
+
rewindFiles: async (sessionId, messageUuid) => {
|
|
1118
|
+
await resumeSession(sessionId);
|
|
1119
|
+
const { query } = getOpenSession(sessionId);
|
|
1120
|
+
const { canRewind, deletions, error, filesChanged, insertions } = await query.rewindFiles(messageUuid, { dryRun: true });
|
|
1121
|
+
if (!canRewind) throw new InvalidOperationError(Operation.Update, sessionId, error ?? "nothing to rewind");
|
|
1122
|
+
const rewindResult = await query.rewindFiles(messageUuid);
|
|
1123
|
+
if (rewindResult.error) throw new InvalidOperationError(Operation.Update, sessionId, rewindResult.error);
|
|
1124
|
+
emit(sessionId, [{
|
|
1125
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1126
|
+
deletions: deletions ?? 0,
|
|
1127
|
+
filePaths: filesChanged ?? [],
|
|
1128
|
+
id: crypto.randomUUID(),
|
|
1129
|
+
insertions: insertions ?? 0,
|
|
1130
|
+
messageUuid,
|
|
1131
|
+
type: "FileRewind"
|
|
1132
|
+
}]);
|
|
1133
|
+
},
|
|
1134
|
+
runSlashCommand: (sessionId, name, commandArguments) => {
|
|
1135
|
+
prompt(sessionId, commandArguments ? `/${name} ${commandArguments}` : `/${name}`, []);
|
|
1136
|
+
},
|
|
1137
|
+
setModel: async (sessionId, model) => {
|
|
1138
|
+
const { mapper, query } = getOpenSession(sessionId);
|
|
1139
|
+
await query.setModel(model);
|
|
1140
|
+
emit(sessionId, [mapper.updateSettings(crypto.randomUUID(), /* @__PURE__ */ new Date(), { model })]);
|
|
1141
|
+
},
|
|
1142
|
+
setPermissionMode: async (sessionId, permissionMode) => {
|
|
1143
|
+
const { mapper, query } = getOpenSession(sessionId);
|
|
1144
|
+
await query.setPermissionMode(permissionMode);
|
|
1145
|
+
emit(sessionId, [mapper.updateSettings(crypto.randomUUID(), /* @__PURE__ */ new Date(), { permissionMode })]);
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
};
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/services/server/constants.ts
|
|
1151
|
+
const HOST_NAME = "agent-console-server";
|
|
1152
|
+
const TOKEN_DIRECTORY_NAME = ".agent-console-server";
|
|
1153
|
+
const TOKEN_FILENAME = "token";
|
|
1154
|
+
const TOKEN_BYTE_LENGTH = 32;
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/services/server/answerHttpRequest.ts
|
|
1157
|
+
const answerHttpRequest = ({ headers, method }, response) => {
|
|
1158
|
+
response.setHeader("Access-Control-Allow-Origin", headers.origin ?? "*");
|
|
1159
|
+
response.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
1160
|
+
response.setHeader("Vary", "Origin");
|
|
1161
|
+
if (method === "OPTIONS") {
|
|
1162
|
+
response.setHeader("Access-Control-Allow-Methods", "GET");
|
|
1163
|
+
response.setHeader("Access-Control-Allow-Headers", headers["access-control-request-headers"] ?? "*");
|
|
1164
|
+
response.writeHead(204).end();
|
|
1165
|
+
} else if (method === "GET") response.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ name: HOST_NAME }));
|
|
1166
|
+
else response.writeHead(405).end();
|
|
1167
|
+
};
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region src/services/server/checkIsTokenValid.ts
|
|
1170
|
+
const checkIsTokenValid = (candidate, token) => {
|
|
1171
|
+
if (!token) return false;
|
|
1172
|
+
const candidateBuffer = Buffer.from(candidate);
|
|
1173
|
+
const tokenBuffer = Buffer.from(token);
|
|
1174
|
+
return candidateBuffer.length === tokenBuffer.length && timingSafeEqual(candidateBuffer, tokenBuffer);
|
|
1175
|
+
};
|
|
1176
|
+
//#endregion
|
|
1177
|
+
//#region src/services/server/createEventLog.ts
|
|
1178
|
+
const createEventLog = () => {
|
|
1179
|
+
const sessionEventMap = /* @__PURE__ */ new Map();
|
|
1180
|
+
return {
|
|
1181
|
+
append: (sessionId, events) => {
|
|
1182
|
+
const sessionEvents = getOrCreate(sessionEventMap, sessionId, () => ({
|
|
1183
|
+
eventIds: /* @__PURE__ */ new Set(),
|
|
1184
|
+
events: []
|
|
1185
|
+
}));
|
|
1186
|
+
const newEvents = [];
|
|
1187
|
+
for (const event of events) {
|
|
1188
|
+
if (EphemeralAgentEventTypes.includes(event.type)) {
|
|
1189
|
+
newEvents.push(event);
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (sessionEvents.eventIds.has(event.id)) continue;
|
|
1193
|
+
sessionEvents.eventIds.add(event.id);
|
|
1194
|
+
sessionEvents.events.push(event);
|
|
1195
|
+
newEvents.push(event);
|
|
1196
|
+
}
|
|
1197
|
+
return newEvents;
|
|
1198
|
+
},
|
|
1199
|
+
entries: () => Array.from(sessionEventMap, ([sessionId, { events }]) => [sessionId, events]),
|
|
1200
|
+
reset: (sessionId) => {
|
|
1201
|
+
sessionEventMap.delete(sessionId);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
};
|
|
1205
|
+
//#endregion
|
|
1206
|
+
//#region src/services/server/handleCommand.ts
|
|
1207
|
+
const handleCommand = async (driver, command) => {
|
|
1208
|
+
switch (command.type) {
|
|
1209
|
+
case "CloseSession":
|
|
1210
|
+
driver.closeSession(command.sessionId);
|
|
1211
|
+
return "";
|
|
1212
|
+
case "CreateSession": return driver.createSession(command.cwd);
|
|
1213
|
+
case "Fork": return driver.forkSession(command.sessionId, command.messageUuid);
|
|
1214
|
+
case "Interrupt":
|
|
1215
|
+
await driver.interrupt(command.sessionId);
|
|
1216
|
+
return "";
|
|
1217
|
+
case "ListSessions": return "";
|
|
1218
|
+
case "PermissionVerdict":
|
|
1219
|
+
driver.resolvePermission(command.sessionId, command.requestId, command.behavior, command.message);
|
|
1220
|
+
return "";
|
|
1221
|
+
case "Prompt":
|
|
1222
|
+
driver.prompt(command.sessionId, command.text, command.attachments);
|
|
1223
|
+
return "";
|
|
1224
|
+
case "Resume": return driver.resumeSession(command.sessionId);
|
|
1225
|
+
case "ResumeAt": return driver.resumeAt(command.sessionId, command.messageUuid);
|
|
1226
|
+
case "RewindFiles":
|
|
1227
|
+
await driver.rewindFiles(command.sessionId, command.messageUuid);
|
|
1228
|
+
return "";
|
|
1229
|
+
case "SetModel":
|
|
1230
|
+
await driver.setModel(command.sessionId, command.model);
|
|
1231
|
+
return "";
|
|
1232
|
+
case "SetPermissionMode":
|
|
1233
|
+
await driver.setPermissionMode(command.sessionId, command.permissionMode);
|
|
1234
|
+
return "";
|
|
1235
|
+
case "SlashCommand":
|
|
1236
|
+
driver.runSlashCommand(command.sessionId, command.name, command.arguments);
|
|
1237
|
+
return "";
|
|
1238
|
+
default: return exhaustiveGuard(command);
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/services/server/sendServerMessage.ts
|
|
1243
|
+
const sendServerMessage = (webSocket, message) => {
|
|
1244
|
+
if (webSocket.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(message));
|
|
1245
|
+
};
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region src/services/server/createAgentConsoleServer.ts
|
|
1248
|
+
const createAgentConsoleServer = async ({ createDriver, hostname, port, token }) => {
|
|
1249
|
+
const eventLog = createEventLog();
|
|
1250
|
+
const taskRegistry = createTaskRegistry();
|
|
1251
|
+
const webSocketServer = new WebSocketServer({ noServer: true });
|
|
1252
|
+
const broadcast = (message) => {
|
|
1253
|
+
for (const webSocket of webSocketServer.clients) sendServerMessage(webSocket, message);
|
|
1254
|
+
};
|
|
1255
|
+
let isRefreshing = false;
|
|
1256
|
+
let isRefreshStale = false;
|
|
1257
|
+
const refreshSessions = () => {
|
|
1258
|
+
if (isRefreshing) {
|
|
1259
|
+
isRefreshStale = true;
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
isRefreshing = true;
|
|
1263
|
+
taskRegistry.run(async () => {
|
|
1264
|
+
do {
|
|
1265
|
+
isRefreshStale = false;
|
|
1266
|
+
await getResultAsync(() => driver.listSessions()).match((sessions) => {
|
|
1267
|
+
broadcast({
|
|
1268
|
+
sessions,
|
|
1269
|
+
type: "Sessions"
|
|
1270
|
+
});
|
|
1271
|
+
}, console.error);
|
|
1272
|
+
} while (isRefreshStale);
|
|
1273
|
+
isRefreshing = false;
|
|
1274
|
+
});
|
|
1275
|
+
};
|
|
1276
|
+
const driver = createDriver({
|
|
1277
|
+
onEvents: (sessionId, events) => {
|
|
1278
|
+
const newEvents = eventLog.append(sessionId, events);
|
|
1279
|
+
if (newEvents.length > 0) broadcast({
|
|
1280
|
+
events: newEvents,
|
|
1281
|
+
sessionId,
|
|
1282
|
+
type: "Events"
|
|
1283
|
+
});
|
|
1284
|
+
},
|
|
1285
|
+
onSessionOpen: (sessionId) => {
|
|
1286
|
+
eventLog.reset(sessionId);
|
|
1287
|
+
broadcast({
|
|
1288
|
+
sessionId,
|
|
1289
|
+
type: "SessionReset"
|
|
1290
|
+
});
|
|
1291
|
+
},
|
|
1292
|
+
onSessionsChange: () => {
|
|
1293
|
+
refreshSessions();
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
const receive = async (webSocket, data) => {
|
|
1297
|
+
const text = Buffer.isBuffer(data) ? data.toString() : Array.isArray(data) ? Buffer.concat(data).toString() : Buffer.from(data).toString();
|
|
1298
|
+
const command = getResult(() => commandSchema.parse(JSON.parse(text))).orTee((error) => {
|
|
1299
|
+
sendServerMessage(webSocket, {
|
|
1300
|
+
commandId: "",
|
|
1301
|
+
message: error.message,
|
|
1302
|
+
type: "CommandError"
|
|
1303
|
+
});
|
|
1304
|
+
}).unwrapOr(void 0);
|
|
1305
|
+
if (!command) return;
|
|
1306
|
+
await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
|
|
1307
|
+
if (sessionId) sendServerMessage(webSocket, {
|
|
1308
|
+
commandId: command.id,
|
|
1309
|
+
sessionId,
|
|
1310
|
+
type: "SessionOpened"
|
|
1311
|
+
});
|
|
1312
|
+
else if (command.type === "ListSessions") refreshSessions();
|
|
1313
|
+
}, (error) => {
|
|
1314
|
+
sendServerMessage(webSocket, {
|
|
1315
|
+
commandId: command.id,
|
|
1316
|
+
message: error.message,
|
|
1317
|
+
type: "CommandError"
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
};
|
|
1321
|
+
webSocketServer.on("connection", (webSocket) => {
|
|
1322
|
+
for (const [sessionId, events] of eventLog.entries()) sendServerMessage(webSocket, {
|
|
1323
|
+
events,
|
|
1324
|
+
sessionId,
|
|
1325
|
+
type: "Events"
|
|
1326
|
+
});
|
|
1327
|
+
refreshSessions();
|
|
1328
|
+
webSocket.on("message", (data) => {
|
|
1329
|
+
taskRegistry.run(() => receive(webSocket, data));
|
|
1330
|
+
});
|
|
1331
|
+
});
|
|
1332
|
+
const httpServer = createServer((request, response) => {
|
|
1333
|
+
answerHttpRequest(request, response);
|
|
1334
|
+
});
|
|
1335
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
1336
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
1337
|
+
if (!checkIsTokenValid(url.searchParams.get("token") ?? "", token)) {
|
|
1338
|
+
socket.end("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
|
|
1342
|
+
webSocketServer.emit("connection", webSocket, request);
|
|
1343
|
+
});
|
|
1344
|
+
});
|
|
1345
|
+
httpServer.listen(port, hostname);
|
|
1346
|
+
await once(httpServer, "listening");
|
|
1347
|
+
const address = httpServer.address();
|
|
1348
|
+
return {
|
|
1349
|
+
close: async () => {
|
|
1350
|
+
await driver.close();
|
|
1351
|
+
for (const webSocket of webSocketServer.clients) webSocket.terminate();
|
|
1352
|
+
webSocketServer.close();
|
|
1353
|
+
httpServer.close();
|
|
1354
|
+
await Promise.all([
|
|
1355
|
+
taskRegistry.drain(),
|
|
1356
|
+
once(webSocketServer, "close"),
|
|
1357
|
+
once(httpServer, "close")
|
|
1358
|
+
]);
|
|
1359
|
+
},
|
|
1360
|
+
port: typeof address === "object" && address ? address.port : port
|
|
1361
|
+
};
|
|
1362
|
+
};
|
|
1363
|
+
//#endregion
|
|
1364
|
+
//#region src/services/server/readToken.ts
|
|
1365
|
+
const readToken = () => {
|
|
1366
|
+
const tokenDirectory = join(homedir(), TOKEN_DIRECTORY_NAME);
|
|
1367
|
+
const tokenPath = join(tokenDirectory, TOKEN_FILENAME);
|
|
1368
|
+
const storedToken = existsSync(tokenPath) ? readFileSync(tokenPath, "utf8").trim() : "";
|
|
1369
|
+
if (storedToken) return storedToken;
|
|
1370
|
+
const token = randomBytes(32).toString("base64url");
|
|
1371
|
+
mkdirSync(tokenDirectory, { recursive: true });
|
|
1372
|
+
rmSync(tokenPath, { force: true });
|
|
1373
|
+
writeFileSync(tokenPath, token, { mode: 384 });
|
|
1374
|
+
return token;
|
|
1375
|
+
};
|
|
1376
|
+
//#endregion
|
|
1377
|
+
export { mapTaskMessage as A, toPermissionRequestEvent as B, createSdkMessageMapper as C, mapUserContent as D, parseMessageBody as E, TodoToolName as F, SETTING_SOURCES as G, DEFAULT_DENY_MESSAGE as H, createStreamTracker as I, closeOpenSession as K, createFileOriginTracker as L, mapAssistantContent as M, toUnknownEvent as N, toToolResultText as O, createTodoTracker as P, createPermissionBridge as R, readSessionHistory as S, toSessionInitEvent as T, IGNORED_SDK_MESSAGE_TYPES as U, createInputQueue as V, SESSION_LIST_LIMIT as W, watchSession as _, createEventLog as a, getSessionTitle as b, HOST_NAME as c, TOKEN_FILENAME as d, createClaudeAgentSdkDriver as f, createSessionOpener as g, readSessionCwd as h, handleCommand as i, mapHookMessage as j, mapSystemMessage as k, TOKEN_BYTE_LENGTH as l, toContentBlockParam as m, createAgentConsoleServer as n, checkIsTokenValid as o, createTaskRegistry as p, getEventId as q, sendServerMessage as r, answerHttpRequest as s, readToken as t, TOKEN_DIRECTORY_NAME as u, toContextUsageEvent as v, toTurnResultEvents as w, readToolUseResultMap as x, toCapabilitiesEvent as y, toPermissionResult as z };
|