agent-console-server 3.4.0 → 3.5.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.
@@ -1,25 +1,147 @@
1
- import { M as todoStatusSchema, P as permissionModeSchema, i as commandSchema, t as EphemeralAgentEventTypes } from "./EphemeralAgentEventTypes-Cm5oNQXu.js";
1
+ import { B as getSignedText, V as serverMessageSchema, _ as driverCommandSchema, et as agentEventSchema, ft as todoStatusSchema, in as DEFAULT_HOSTNAME, ln as SCHEME_PAIRING_CODE_DURATION, on as HOST_SCHEME, r as handshakeMessageSchema, t as EphemeralAgentEventTypes, u as commandSchema, un as SHELL_OUTPUT_LENGTH, wt as permissionModeSchema } from "./EphemeralAgentEventTypes-DGkHIIcW.js";
2
+ import { createRequire } from "node:module";
2
3
  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";
4
+ import { ID_SEPARATOR, InvalidOperationError, Operation, SITE_NAME, createUniqueArraySchema, exhaustiveGuard, getOrCreate, getResult, getResultAsync, noop, withFinalizerAsync } from "@esposter/shared";
7
5
  import { homedir } from "node:os";
8
- import { join } from "node:path";
9
- import { randomBytes, timingSafeEqual } from "node:crypto";
10
- import { WebSocket, WebSocketServer } from "ws";
6
+ import { dirname, join, relative, win32 } from "node:path";
7
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
8
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";
11
9
  import { once } from "node:events";
10
+ import { WebSocket, WebSocketServer } from "ws";
11
+ import { getSessionInfo, getSessionMessages, listSessions, query } from "@anthropic-ai/claude-agent-sdk";
12
+ import { readFile, readdir } from "node:fs/promises";
13
+ import { getAssetKeys, getRawAsset, isSea } from "node:sea";
12
14
  import { createServer } from "node:http";
15
+ import { execFileSync, spawn } from "node:child_process";
16
+ //#region src/services/device/constants.ts
17
+ const STATE_DIRECTORY_NAME = ".agent-console-server";
18
+ const DEVICES_FILENAME = "devices.json";
19
+ const HOST_KEY_FILENAME = "host-key.pem";
20
+ const SECRET_BYTE_LENGTH = 32;
21
+ const PRINTED_PAIRING_CODE_DURATION = Temporal.Duration.from({ minutes: 10 }).total("milliseconds");
22
+ //#endregion
23
+ //#region src/services/device/getStateDirectory.ts
24
+ const getStateDirectory = () => join(homedir(), STATE_DIRECTORY_NAME);
25
+ //#endregion
26
+ //#region src/services/device/writeStateFile.ts
27
+ const writeStateFile = (stateDirectory, filename, content) => {
28
+ const filePath = join(stateDirectory, filename);
29
+ const temporaryFilePath = `${filePath}.${crypto.randomUUID()}.tmp`;
30
+ mkdirSync(stateDirectory, { recursive: true });
31
+ getResult(() => {
32
+ writeFileSync(temporaryFilePath, content, {
33
+ flag: "wx",
34
+ mode: 384
35
+ });
36
+ renameSync(temporaryFilePath, filePath);
37
+ }).match(noop, (error) => {
38
+ getResult(() => {
39
+ rmSync(temporaryFilePath, { force: true });
40
+ }).match(noop, console.error);
41
+ throw error;
42
+ });
43
+ };
44
+ //#endregion
45
+ //#region src/services/device/readHostKey.ts
46
+ const readHostKey = (stateDirectory) => {
47
+ const hostKeyPath = join(stateDirectory, HOST_KEY_FILENAME);
48
+ if (existsSync(hostKeyPath)) return createPrivateKey(readFileSync(hostKeyPath, "utf8"));
49
+ const { privateKey } = generateKeyPairSync("ed25519");
50
+ writeStateFile(stateDirectory, HOST_KEY_FILENAME, privateKey.export({
51
+ format: "pem",
52
+ type: "pkcs8"
53
+ }));
54
+ return privateKey;
55
+ };
56
+ //#endregion
57
+ //#region src/models/device/Device.ts
58
+ const deviceSchema = z.object({
59
+ createdAt: z.coerce.date(),
60
+ credentialHash: z.string().min(1),
61
+ id: z.string().min(1),
62
+ name: z.string(),
63
+ origin: z.string()
64
+ });
65
+ //#endregion
66
+ //#region src/services/device/readDevices.ts
67
+ const readDevices = (stateDirectory) => {
68
+ const devicesPath = join(stateDirectory, DEVICES_FILENAME);
69
+ if (!existsSync(devicesPath)) return [];
70
+ return z.array(deviceSchema).parse(JSON.parse(readFileSync(devicesPath, "utf8")));
71
+ };
72
+ //#endregion
73
+ //#region src/services/device/writeDevices.ts
74
+ const writeDevices = (stateDirectory, devices) => {
75
+ writeStateFile(stateDirectory, DEVICES_FILENAME, JSON.stringify(devices, void 0, 2));
76
+ };
77
+ //#endregion
78
+ //#region src/services/handshake/checkIsSignatureValid.ts
79
+ const checkIsSignatureValid = (key, purpose, port, nonce, signature) => verify(void 0, Buffer.from(getSignedText(purpose, port, nonce)), key, Buffer.from(signature, "base64url"));
80
+ //#endregion
81
+ //#region src/services/handshake/signNonce.ts
82
+ const signNonce = (privateKey, purpose, port, nonce) => sign(void 0, Buffer.from(getSignedText(purpose, port, nonce)), privateKey).toString("base64url");
83
+ //#endregion
84
+ //#region src/services/server/constants.ts
85
+ const HOST_PROBE_TIMEOUT_MS = 2e3;
86
+ //#endregion
87
+ //#region src/services/shared/readMessageText.ts
88
+ const readMessageText = (data) => Buffer.isBuffer(data) ? data.toString() : Array.isArray(data) ? Buffer.concat(data).toString() : Buffer.from(data).toString();
89
+ //#endregion
90
+ //#region src/services/server/sendToRunningHost.ts
91
+ const sendToRunningHost = async (hostname, port, hostKey, createMessage) => {
92
+ const nonce = randomBytes(32).toString("base64url");
93
+ const webSocket = new WebSocket(`ws://${hostname}:${port}`, { handshakeTimeout: HOST_PROBE_TIMEOUT_MS });
94
+ const isHost = await getResultAsync(async () => {
95
+ const signal = AbortSignal.timeout(HOST_PROBE_TIMEOUT_MS);
96
+ await once(webSocket, "open", { signal });
97
+ const pendingMessage = once(webSocket, "message", { signal });
98
+ webSocket.send(JSON.stringify({
99
+ nonce,
100
+ type: "Challenge"
101
+ }));
102
+ const [data] = await pendingMessage;
103
+ const serverMessage = serverMessageSchema.parse(JSON.parse(readMessageText(data)));
104
+ if (serverMessage.type !== "Proof" || serverMessage.port !== port || !checkIsSignatureValid(createPublicKey(hostKey), "HostProof", port, nonce, serverMessage.signature)) return false;
105
+ if (!createMessage) return true;
106
+ const pendingClose = once(webSocket, "close", { signal });
107
+ webSocket.send(JSON.stringify(createMessage(signNonce(hostKey, "OwnerProof", port, serverMessage.nonce))));
108
+ await pendingClose;
109
+ return true;
110
+ }).match((isHolderHost) => isHolderHost, () => false);
111
+ webSocket.terminate();
112
+ return isHost;
113
+ };
114
+ //#endregion
115
+ //#region src/services/device/runDevicesCommand.ts
116
+ const runDevicesCommand = async ({ hostKey, hostname, port, revokeDeviceId, stateDirectory }, writeLine) => {
117
+ const devices = readDevices(stateDirectory);
118
+ if (!revokeDeviceId) {
119
+ if (devices.length === 0) writeLine("No page is connected to this host.");
120
+ for (const { createdAt, id, name, origin } of devices) writeLine(`${id} ${name} ${origin} since ${createdAt.toISOString()}`);
121
+ return true;
122
+ }
123
+ if (!devices.some(({ id }) => id === revokeDeviceId)) {
124
+ writeLine(`No device has the id ${revokeDeviceId}.`);
125
+ return false;
126
+ }
127
+ writeDevices(stateDirectory, devices.filter(({ id }) => id !== revokeDeviceId));
128
+ await sendToRunningHost(hostname, port, hostKey, (signature) => ({
129
+ deviceId: revokeDeviceId,
130
+ signature,
131
+ type: "Revoke"
132
+ }));
133
+ writeLine(`Removed ${revokeDeviceId}. It has to connect again to use this host.`);
134
+ return true;
135
+ };
136
+ //#endregion
13
137
  //#region src/services/drivers/claudeAgentSdk/getEventId.ts
14
138
  const getEventId = (sourceId, suffix) => `${sourceId}${ID_SEPARATOR}${suffix}`;
15
139
  //#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", "");
140
+ //#region src/services/drivers/claudeAgentSdk/toSessionClosedEvents.ts
141
+ const toSessionClosedEvents = (message) => {
20
142
  const createdAt = /* @__PURE__ */ new Date();
21
143
  const closedId = getEventId(crypto.randomUUID(), "SessionState");
22
- emit(sessionId, [...message ? [{
144
+ return [...message ? [{
23
145
  createdAt,
24
146
  id: getEventId(closedId, "HostError"),
25
147
  message,
@@ -29,7 +151,14 @@ const closeOpenSession = (sessionId, openSession, message, { emit, onSessionsCha
29
151
  id: closedId,
30
152
  state: "Closed",
31
153
  type: "SessionState"
32
- }]);
154
+ }];
155
+ };
156
+ //#endregion
157
+ //#region src/services/drivers/claudeAgentSdk/closeOpenSession.ts
158
+ const closeOpenSession = (sessionId, openSession, message, { emit, onSessionsChange, openSessionMap }) => {
159
+ if (openSessionMap.get(sessionId) !== openSession) return;
160
+ for (const pendingPermission of openSession.pendingPermissionMap.values()) pendingPermission.settle("Deny", "");
161
+ emit(sessionId, toSessionClosedEvents(message));
33
162
  openSessionMap.delete(sessionId);
34
163
  onSessionsChange();
35
164
  };
@@ -845,6 +974,20 @@ const watchSession = async (sessionId, openSession, { emit, onSessionsChange, op
845
974
  ]);
846
975
  };
847
976
  //#endregion
977
+ //#region src/services/installer/constants.ts
978
+ const HOST_EXECUTABLE_FILENAME = "agent-console-host.exe";
979
+ const CLAUDE_CODE_EXECUTABLE_FILENAME = "claude.exe";
980
+ const HOST_SCHEME_REGISTRY_KEY = win32.join("HKCU", "Software", "Classes", HOST_SCHEME);
981
+ const HOST_DIRECTORY_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_HOST_DIRECTORY`;
982
+ const HOST_INSTALL_DIRECTORY_SEGMENTS = [SITE_NAME, "Host"];
983
+ const NODE_PTY_DIRECTORY_NAME = "node-pty";
984
+ //#endregion
985
+ //#region src/services/installer/getHostInstallDirectory.ts
986
+ const getHostInstallDirectory = () => join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), ...HOST_INSTALL_DIRECTORY_SEGMENTS);
987
+ //#endregion
988
+ //#region src/services/installer/getClaudeCodeExecutablePath.ts
989
+ const getClaudeCodeExecutablePath = () => isSea() ? join(getHostInstallDirectory(), CLAUDE_CODE_EXECUTABLE_FILENAME) : void 0;
990
+ //#endregion
848
991
  //#region src/services/drivers/claudeAgentSdk/createSessionOpener.ts
849
992
  const createSessionOpener = ({ emit, onSessionOpen, onSessionsChange, openSessionMap, taskRegistry }) => async ({ cwd, isFork, resumeAt, resumeFrom, sessionId }) => {
850
993
  const mapper = createSdkMessageMapper();
@@ -852,6 +995,7 @@ const createSessionOpener = ({ emit, onSessionOpen, onSessionsChange, openSessio
852
995
  const toolUseResultMap = resumeFrom ? await readToolUseResultMap(resumeFrom) : /* @__PURE__ */ new Map();
853
996
  const input = createInputQueue();
854
997
  const pendingPermissionMap = /* @__PURE__ */ new Map();
998
+ const claudeCodeExecutablePath = getClaudeCodeExecutablePath();
855
999
  const sessionQuery = query({
856
1000
  options: {
857
1001
  canUseTool: createPermissionBridge(sessionId, pendingPermissionMap, emit),
@@ -863,6 +1007,7 @@ const createSessionOpener = ({ emit, onSessionOpen, onSessionsChange, openSessio
863
1007
  },
864
1008
  includeHookEvents: true,
865
1009
  includePartialMessages: true,
1010
+ ...claudeCodeExecutablePath ? { pathToClaudeCodeExecutable: claudeCodeExecutablePath } : {},
866
1011
  settingSources: SETTING_SOURCES,
867
1012
  systemPrompt: {
868
1013
  preset: "claude_code",
@@ -909,6 +1054,30 @@ const createSessionOpener = ({ emit, onSessionOpen, onSessionsChange, openSessio
909
1054
  return sessionId;
910
1055
  };
911
1056
  //#endregion
1057
+ //#region src/services/drivers/claudeAgentSdk/listSessionSummaries.ts
1058
+ const listSessionSummaries = async (openSessionMap) => {
1059
+ const sessionInfos = await listSessions({ limit: 50 });
1060
+ const savedSessionIds = new Set(sessionInfos.map(({ sessionId }) => sessionId));
1061
+ const unsavedSessions = [...openSessionMap].filter(([id]) => !savedSessionIds.has(id)).map(([id, { cwd, lastActivityAt, state, title }]) => ({
1062
+ cwd,
1063
+ id,
1064
+ lastActivityAt,
1065
+ state,
1066
+ title
1067
+ }));
1068
+ const savedSessions = sessionInfos.map((sessionInfo) => {
1069
+ const openSession = openSessionMap.get(sessionInfo.sessionId);
1070
+ return {
1071
+ cwd: sessionInfo.cwd ?? "",
1072
+ id: sessionInfo.sessionId,
1073
+ lastActivityAt: openSession?.lastActivityAt ?? new Date(sessionInfo.lastModified),
1074
+ state: openSession?.state ?? "Closed",
1075
+ title: openSession?.title || getSessionTitle(sessionInfo)
1076
+ };
1077
+ });
1078
+ return [...unsavedSessions, ...savedSessions];
1079
+ };
1080
+ //#endregion
912
1081
  //#region src/services/drivers/claudeAgentSdk/readSessionCwd.ts
913
1082
  const readSessionCwd = async (sessionId) => {
914
1083
  const sessionInfo = await getSessionInfo(sessionId);
@@ -1051,6 +1220,9 @@ const createClaudeAgentSdkDriver = ({ onEvents, onSessionOpen, onSessionsChange
1051
1220
  });
1052
1221
  };
1053
1222
  return {
1223
+ backgroundTasks: async (sessionId) => {
1224
+ await getOpenSession(sessionId).query.backgroundTasks();
1225
+ },
1054
1226
  close: async () => {
1055
1227
  for (const sessionId of openSessionMap.keys()) closeSession(sessionId);
1056
1228
  await taskRegistry.drain();
@@ -1076,28 +1248,7 @@ const createClaudeAgentSdkDriver = ({ onEvents, onSessionOpen, onSessionsChange
1076
1248
  interrupt: async (sessionId) => {
1077
1249
  await getOpenSession(sessionId).query.interrupt();
1078
1250
  },
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
- },
1251
+ listSessions: () => listSessionSummaries(openSessionMap),
1101
1252
  prompt,
1102
1253
  resolvePermission: (sessionId, requestId, behavior, message) => {
1103
1254
  getOpenSession(sessionId).pendingPermissionMap.get(requestId)?.settle(behavior, message);
@@ -1143,22 +1294,86 @@ const createClaudeAgentSdkDriver = ({ onEvents, onSessionOpen, onSessionsChange
1143
1294
  const { mapper, query } = getOpenSession(sessionId);
1144
1295
  await query.setPermissionMode(permissionMode);
1145
1296
  emit(sessionId, [mapper.updateSettings(crypto.randomUUID(), /* @__PURE__ */ new Date(), { permissionMode })]);
1297
+ },
1298
+ stopTask: async (sessionId, taskId) => {
1299
+ await getOpenSession(sessionId).query.stopTask(taskId);
1146
1300
  }
1147
1301
  };
1148
1302
  };
1149
1303
  //#endregion
1150
- //#region src/services/server/answerHttpRequest.ts
1151
- const answerHttpRequest = ({ headers, method }, response) => {
1152
- if (method !== "OPTIONS") {
1153
- response.writeHead(405).end();
1154
- return;
1155
- }
1156
- response.setHeader("Access-Control-Allow-Origin", headers.origin ?? "*");
1157
- response.setHeader("Access-Control-Allow-Private-Network", "true");
1158
- response.setHeader("Access-Control-Allow-Methods", "GET");
1159
- response.setHeader("Access-Control-Allow-Headers", headers["access-control-request-headers"] ?? "*");
1160
- response.setHeader("Vary", "Origin");
1161
- response.writeHead(204).end();
1304
+ //#region src/services/drivers/window/constants.ts
1305
+ const SESSION_SUBCOMMAND = "session";
1306
+ const SESSION_SECRET_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_SESSION_SECRET`;
1307
+ const SESSION_SECRET_BYTE_LENGTH = 32;
1308
+ const SESSION_WINDOW_CONNECT_TIMEOUT = Temporal.Duration.from({ seconds: 30 }).total("milliseconds");
1309
+ //#endregion
1310
+ //#region src/models/window/BaseChildMessage.ts
1311
+ const createBaseChildMessageSchema = (typeSchema) => z.object({ type: typeSchema });
1312
+ //#endregion
1313
+ //#region src/models/window/ChildMessageType.ts
1314
+ let ChildMessageType = /* @__PURE__ */ function(ChildMessageType) {
1315
+ ChildMessageType["CommandError"] = "CommandError";
1316
+ ChildMessageType["CommandResult"] = "CommandResult";
1317
+ ChildMessageType["Events"] = "Events";
1318
+ ChildMessageType["SessionOpen"] = "SessionOpen";
1319
+ ChildMessageType["SessionsChange"] = "SessionsChange";
1320
+ return ChildMessageType;
1321
+ }({});
1322
+ const childMessageTypeSchema = z.enum(ChildMessageType);
1323
+ //#endregion
1324
+ //#region src/models/window/ChildCommandErrorMessage.ts
1325
+ const childCommandErrorMessageSchema = z.object({
1326
+ ...createBaseChildMessageSchema(z.literal("CommandError")).shape,
1327
+ commandId: z.string(),
1328
+ message: z.string().min(1)
1329
+ });
1330
+ //#endregion
1331
+ //#region src/models/window/ChildCommandResultMessage.ts
1332
+ const childCommandResultMessageSchema = z.object({
1333
+ ...createBaseChildMessageSchema(z.literal("CommandResult")).shape,
1334
+ commandId: z.string().min(1),
1335
+ sessionId: z.string()
1336
+ });
1337
+ //#endregion
1338
+ //#region src/models/window/ChildEventsMessage.ts
1339
+ const childEventsMessageSchema = z.object({
1340
+ ...createBaseChildMessageSchema(z.literal("Events")).shape,
1341
+ events: createUniqueArraySchema(agentEventSchema, "id"),
1342
+ sessionId: z.string().min(1)
1343
+ });
1344
+ //#endregion
1345
+ //#region src/models/window/ChildSessionOpenMessage.ts
1346
+ const childSessionOpenMessageSchema = z.object({
1347
+ ...createBaseChildMessageSchema(z.literal("SessionOpen")).shape,
1348
+ sessionId: z.string().min(1)
1349
+ });
1350
+ //#endregion
1351
+ //#region src/models/window/ChildSessionsChangeMessage.ts
1352
+ const childSessionsChangeMessageSchema = z.object({ ...createBaseChildMessageSchema(z.literal("SessionsChange")).shape });
1353
+ //#endregion
1354
+ //#region src/models/window/ChildMessage.ts
1355
+ const childMessageSchema = z.discriminatedUnion("type", [
1356
+ childCommandErrorMessageSchema,
1357
+ childCommandResultMessageSchema,
1358
+ childEventsMessageSchema,
1359
+ childSessionOpenMessageSchema,
1360
+ childSessionsChangeMessageSchema
1361
+ ]);
1362
+ //#endregion
1363
+ //#region src/services/drivers/window/sendChildCommand.ts
1364
+ const sendChildCommand = (child, command) => {
1365
+ if (child.webSocket.readyState !== WebSocket.OPEN) throw new InvalidOperationError(Operation.Update, child.cwd, "the session's window closed");
1366
+ const reply = Promise.withResolvers();
1367
+ child.pendingReplyMap.set(command.id, reply);
1368
+ child.webSocket.send(JSON.stringify(command));
1369
+ return reply.promise;
1370
+ };
1371
+ //#endregion
1372
+ //#region src/services/drivers/window/takeChildReply.ts
1373
+ const takeChildReply = ({ pendingReplyMap }, commandId) => {
1374
+ const reply = pendingReplyMap.get(commandId);
1375
+ pendingReplyMap.delete(commandId);
1376
+ return reply;
1162
1377
  };
1163
1378
  //#endregion
1164
1379
  //#region src/services/server/checkIsTokenValid.ts
@@ -1169,38 +1384,312 @@ const checkIsTokenValid = (candidate, token) => {
1169
1384
  return candidateBuffer.length === tokenBuffer.length && timingSafeEqual(candidateBuffer, tokenBuffer);
1170
1385
  };
1171
1386
  //#endregion
1172
- //#region src/services/server/createEventLog.ts
1173
- const createEventLog = () => {
1174
- const sessionEventMap = /* @__PURE__ */ new Map();
1175
- return {
1176
- append: (sessionId, events) => {
1177
- const sessionEvents = getOrCreate(sessionEventMap, sessionId, () => ({
1178
- eventIds: /* @__PURE__ */ new Set(),
1179
- events: []
1180
- }));
1181
- const newEvents = [];
1182
- for (const event of events) {
1183
- if (EphemeralAgentEventTypes.includes(event.type)) {
1184
- newEvents.push(event);
1185
- continue;
1387
+ //#region src/services/drivers/window/createWindowDriver.ts
1388
+ const createWindowDriver = ({ onEvents, onSessionOpen, onSessionsChange }, { launchSessionWindow, writeLine }) => {
1389
+ const windowSessionMap = /* @__PURE__ */ new Map();
1390
+ const childSet = /* @__PURE__ */ new Set();
1391
+ const pendingLaunchMap = /* @__PURE__ */ new Map();
1392
+ const taskRegistry = createTaskRegistry();
1393
+ const webSocketServer = new WebSocketServer({ noServer: true });
1394
+ const httpServer = createServer((_request, response) => {
1395
+ response.writeHead(405).end();
1396
+ });
1397
+ httpServer.on("upgrade", (request, socket, head) => {
1398
+ const secret = request.headers.authorization?.replace(/^Bearer /u, "") ?? "";
1399
+ const pendingLaunchEntry = [...pendingLaunchMap].find(([launchSecret]) => checkIsTokenValid(secret, launchSecret));
1400
+ if (!pendingLaunchEntry) {
1401
+ socket.end("HTTP/1.1 401 Unauthorized\r\n\r\n");
1402
+ return;
1403
+ }
1404
+ const [launchSecret, { connection, timeout }] = pendingLaunchEntry;
1405
+ pendingLaunchMap.delete(launchSecret);
1406
+ clearTimeout(timeout);
1407
+ webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
1408
+ connection.resolve(webSocket);
1409
+ });
1410
+ });
1411
+ let listening;
1412
+ const listen = async () => {
1413
+ httpServer.listen(0, DEFAULT_HOSTNAME);
1414
+ await once(httpServer, "listening");
1415
+ const address = httpServer.address();
1416
+ return typeof address === "object" && address ? address.port : 0;
1417
+ };
1418
+ const getWindowSession = (sessionId) => {
1419
+ const windowSession = windowSessionMap.get(sessionId);
1420
+ if (!windowSession) throw new InvalidOperationError(Operation.Read, sessionId, "the session is not open on this host");
1421
+ return windowSession;
1422
+ };
1423
+ const closeWindowSession = (sessionId, windowSession) => {
1424
+ if (windowSessionMap.get(sessionId) !== windowSession) return;
1425
+ windowSessionMap.delete(sessionId);
1426
+ if (windowSession.state !== "Closed") onEvents(sessionId, toSessionClosedEvents(""));
1427
+ writeLine(`A session in ${windowSession.child.cwd} closed, and its window with it.`);
1428
+ onSessionsChange();
1429
+ windowSession.child.webSocket.close();
1430
+ };
1431
+ const handleChildMessage = (child, childMessage) => {
1432
+ switch (childMessage.type) {
1433
+ case "CommandError": {
1434
+ const reply = takeChildReply(child, childMessage.commandId);
1435
+ if (reply) reply.reject(new Error(childMessage.message));
1436
+ else console.error(childMessage.message);
1437
+ return;
1438
+ }
1439
+ case "CommandResult":
1440
+ takeChildReply(child, childMessage.commandId)?.resolve(childMessage.sessionId);
1441
+ return;
1442
+ case "Events": {
1443
+ const { events, sessionId } = childMessage;
1444
+ const windowSession = windowSessionMap.get(sessionId);
1445
+ const isHeld = windowSession?.child === child;
1446
+ if (isHeld) {
1447
+ windowSession.lastActivityAt = /* @__PURE__ */ new Date();
1448
+ for (const event of events) if (event.type === "SessionState") windowSession.state = event.state;
1186
1449
  }
1187
- if (sessionEvents.eventIds.has(event.id)) continue;
1188
- sessionEvents.eventIds.add(event.id);
1189
- sessionEvents.events.push(event);
1190
- newEvents.push(event);
1450
+ onEvents(sessionId, events);
1451
+ if (isHeld && windowSession.state === "Closed") closeWindowSession(sessionId, windowSession);
1452
+ return;
1191
1453
  }
1192
- return newEvents;
1454
+ case "SessionOpen":
1455
+ windowSessionMap.set(childMessage.sessionId, {
1456
+ child,
1457
+ cwd: child.cwd,
1458
+ lastActivityAt: /* @__PURE__ */ new Date(),
1459
+ state: "Idle",
1460
+ title: ""
1461
+ });
1462
+ writeLine(`A session in ${child.cwd} opened in a window of its own.`);
1463
+ onSessionOpen(childMessage.sessionId);
1464
+ return;
1465
+ case "SessionsChange":
1466
+ onSessionsChange();
1467
+ return;
1468
+ default: exhaustiveGuard(childMessage);
1469
+ }
1470
+ };
1471
+ const receive = (child, data) => {
1472
+ getResult(() => childMessageSchema.parse(JSON.parse(readMessageText(data)))).match((childMessage) => {
1473
+ handleChildMessage(child, childMessage);
1474
+ }, console.error);
1475
+ };
1476
+ const removeChild = (child) => {
1477
+ childSet.delete(child);
1478
+ for (const reply of child.pendingReplyMap.values()) reply.reject(new InvalidOperationError(Operation.Update, child.cwd, "the session's window closed"));
1479
+ child.pendingReplyMap.clear();
1480
+ for (const [sessionId, windowSession] of windowSessionMap) if (windowSession.child === child) closeWindowSession(sessionId, windowSession);
1481
+ };
1482
+ const openWindow = async (cwd) => {
1483
+ const port = await (listening ??= listen());
1484
+ const secret = randomBytes(32).toString("base64url");
1485
+ const connection = Promise.withResolvers();
1486
+ const timeout = setTimeout(() => {
1487
+ pendingLaunchMap.delete(secret);
1488
+ connection.reject(new InvalidOperationError(Operation.Create, cwd, "the session's window did not start"));
1489
+ }, SESSION_WINDOW_CONNECT_TIMEOUT);
1490
+ pendingLaunchMap.set(secret, {
1491
+ connection,
1492
+ timeout
1493
+ });
1494
+ launchSessionWindow({
1495
+ port,
1496
+ secret
1497
+ });
1498
+ const webSocket = await connection.promise;
1499
+ const child = {
1500
+ cwd,
1501
+ pendingReplyMap: /* @__PURE__ */ new Map(),
1502
+ webSocket
1503
+ };
1504
+ childSet.add(child);
1505
+ webSocket.on("message", (data) => {
1506
+ receive(child, data);
1507
+ });
1508
+ webSocket.on("close", () => {
1509
+ removeChild(child);
1510
+ });
1511
+ return child;
1512
+ };
1513
+ const openInWindow = async (cwd, command) => {
1514
+ const child = await openWindow(cwd);
1515
+ return getResultAsync(() => sendChildCommand(child, command)).match((sessionId) => sessionId, (error) => {
1516
+ child.webSocket.close();
1517
+ throw error;
1518
+ });
1519
+ };
1520
+ const forward = (sessionId, command) => sendChildCommand(getWindowSession(sessionId).child, command);
1521
+ const forwardInBackground = (sessionId, command) => {
1522
+ const reply = forward(sessionId, command);
1523
+ taskRegistry.run(() => getResultAsync(() => reply).match(noop, console.error));
1524
+ };
1525
+ const resumeSession = async (sessionId) => {
1526
+ if (windowSessionMap.has(sessionId)) return sessionId;
1527
+ return openInWindow(await readSessionCwd(sessionId), {
1528
+ id: crypto.randomUUID(),
1529
+ sessionId,
1530
+ type: "Resume"
1531
+ });
1532
+ };
1533
+ return {
1534
+ backgroundTasks: async (sessionId) => {
1535
+ await forward(sessionId, {
1536
+ id: crypto.randomUUID(),
1537
+ sessionId,
1538
+ type: "BackgroundTasks"
1539
+ });
1193
1540
  },
1194
- entries: () => Array.from(sessionEventMap, ([sessionId, { events }]) => [sessionId, events]),
1195
- reset: (sessionId) => {
1196
- sessionEventMap.delete(sessionId);
1541
+ close: async () => {
1542
+ for (const { connection, timeout } of pendingLaunchMap.values()) {
1543
+ clearTimeout(timeout);
1544
+ connection.reject(new InvalidOperationError(Operation.Create, createWindowDriver.name, "the host is stopping"));
1545
+ }
1546
+ pendingLaunchMap.clear();
1547
+ const children = [...childSet];
1548
+ for (const { webSocket } of children) webSocket.terminate();
1549
+ await Promise.all(children.map(({ webSocket }) => once(webSocket, "close")));
1550
+ await taskRegistry.drain();
1551
+ if (!listening) return;
1552
+ await listening;
1553
+ httpServer.close();
1554
+ await once(httpServer, "close");
1555
+ },
1556
+ closeSession: (sessionId) => {
1557
+ closeWindowSession(sessionId, getWindowSession(sessionId));
1558
+ },
1559
+ createSession: (cwd) => openInWindow(cwd, {
1560
+ cwd,
1561
+ id: crypto.randomUUID(),
1562
+ type: "CreateSession"
1563
+ }),
1564
+ forkSession: async (sessionId, messageUuid) => openInWindow(await readSessionCwd(sessionId), {
1565
+ id: crypto.randomUUID(),
1566
+ messageUuid,
1567
+ sessionId,
1568
+ type: "Fork"
1569
+ }),
1570
+ interrupt: async (sessionId) => {
1571
+ await forward(sessionId, {
1572
+ id: crypto.randomUUID(),
1573
+ sessionId,
1574
+ type: "Interrupt"
1575
+ });
1576
+ },
1577
+ listSessions: () => listSessionSummaries(windowSessionMap),
1578
+ prompt: (sessionId, text, attachments) => {
1579
+ forwardInBackground(sessionId, {
1580
+ attachments,
1581
+ id: crypto.randomUUID(),
1582
+ sessionId,
1583
+ text,
1584
+ type: "Prompt"
1585
+ });
1586
+ },
1587
+ resolvePermission: (sessionId, requestId, behavior, message) => {
1588
+ forwardInBackground(sessionId, {
1589
+ behavior,
1590
+ id: crypto.randomUUID(),
1591
+ message,
1592
+ requestId,
1593
+ sessionId,
1594
+ type: "PermissionVerdict"
1595
+ });
1596
+ },
1597
+ resumeAt: async (sessionId, messageUuid) => {
1598
+ const cwd = await readSessionCwd(sessionId);
1599
+ const windowSession = windowSessionMap.get(sessionId);
1600
+ if (windowSession) closeWindowSession(sessionId, windowSession);
1601
+ return openInWindow(cwd, {
1602
+ id: crypto.randomUUID(),
1603
+ messageUuid,
1604
+ sessionId,
1605
+ type: "ResumeAt"
1606
+ });
1607
+ },
1608
+ resumeSession,
1609
+ rewindFiles: async (sessionId, messageUuid) => {
1610
+ await resumeSession(sessionId);
1611
+ await forward(sessionId, {
1612
+ id: crypto.randomUUID(),
1613
+ messageUuid,
1614
+ sessionId,
1615
+ type: "RewindFiles"
1616
+ });
1617
+ },
1618
+ runSlashCommand: (sessionId, name, commandArguments) => {
1619
+ forwardInBackground(sessionId, {
1620
+ arguments: commandArguments,
1621
+ id: crypto.randomUUID(),
1622
+ name,
1623
+ sessionId,
1624
+ type: "SlashCommand"
1625
+ });
1626
+ },
1627
+ setModel: async (sessionId, model) => {
1628
+ await forward(sessionId, {
1629
+ id: crypto.randomUUID(),
1630
+ model,
1631
+ sessionId,
1632
+ type: "SetModel"
1633
+ });
1634
+ },
1635
+ setPermissionMode: async (sessionId, permissionMode) => {
1636
+ await forward(sessionId, {
1637
+ id: crypto.randomUUID(),
1638
+ permissionMode,
1639
+ sessionId,
1640
+ type: "SetPermissionMode"
1641
+ });
1642
+ },
1643
+ stopTask: async (sessionId, taskId) => {
1644
+ await forward(sessionId, {
1645
+ id: crypto.randomUUID(),
1646
+ sessionId,
1647
+ taskId,
1648
+ type: "StopTask"
1649
+ });
1197
1650
  }
1198
1651
  };
1199
1652
  };
1200
1653
  //#endregion
1654
+ //#region src/services/drivers/window/launchSessionWindow.ts
1655
+ const SESSION_COMMAND_ENVIRONMENT_VARIABLE_PREFIX = `${SITE_NAME.toUpperCase()}_SESSION_COMMAND_`;
1656
+ const launchSessionWindow = ({ port, secret }) => {
1657
+ const hostCommand = isSea() ? [process.execPath] : [process.execPath, process.argv[1] ?? ""];
1658
+ const quotedHostCommand = hostCommand.map((_, index) => `"%${SESSION_COMMAND_ENVIRONMENT_VARIABLE_PREFIX}${index}%"`).join(" ");
1659
+ spawn("cmd.exe", [
1660
+ "/d",
1661
+ "/s",
1662
+ "/c",
1663
+ `"start "" ${quotedHostCommand} ${SESSION_SUBCOMMAND} --port ${port}"`
1664
+ ], {
1665
+ env: {
1666
+ ...process.env,
1667
+ ...Object.fromEntries(hostCommand.map((part, index) => [`${SESSION_COMMAND_ENVIRONMENT_VARIABLE_PREFIX}${index}`, part])),
1668
+ [SESSION_SECRET_ENVIRONMENT_VARIABLE]: secret
1669
+ },
1670
+ stdio: "ignore",
1671
+ windowsVerbatimArguments: true
1672
+ }).on("error", console.error);
1673
+ };
1674
+ //#endregion
1675
+ //#region src/services/drivers/window/formatSessionLogLine.ts
1676
+ const formatSessionLogLine = (event) => {
1677
+ switch (event.type) {
1678
+ case "AssistantMessage": return event.parentToolUseId ? "" : `Claude: ${event.text}`;
1679
+ case "HostError": return `Error: ${event.message}`;
1680
+ case "PermissionRequest": return `Claude asks to use ${event.toolName}. Answer in the page.`;
1681
+ case "ToolUse": return event.parentToolUseId ? "" : `Claude uses ${event.name}`;
1682
+ case "UserMessage": return event.parentToolUseId ? "" : `You: ${event.text}`;
1683
+ default: return "";
1684
+ }
1685
+ };
1686
+ //#endregion
1201
1687
  //#region src/services/server/handleCommand.ts
1202
1688
  const handleCommand = async (driver, command) => {
1203
1689
  switch (command.type) {
1690
+ case "BackgroundTasks":
1691
+ await driver.backgroundTasks(command.sessionId);
1692
+ return "";
1204
1693
  case "CloseSession":
1205
1694
  driver.closeSession(command.sessionId);
1206
1695
  return "";
@@ -1230,6 +1719,281 @@ const handleCommand = async (driver, command) => {
1230
1719
  case "SlashCommand":
1231
1720
  driver.runSlashCommand(command.sessionId, command.name, command.arguments);
1232
1721
  return "";
1722
+ case "StopTask":
1723
+ await driver.stopTask(command.sessionId, command.taskId);
1724
+ return "";
1725
+ default: return exhaustiveGuard(command);
1726
+ }
1727
+ };
1728
+ //#endregion
1729
+ //#region src/services/drivers/window/serveSessionChild.ts
1730
+ const serveSessionChild = async (webSocket, createDriver, writeLine) => {
1731
+ const taskRegistry = createTaskRegistry();
1732
+ const send = (childMessage) => {
1733
+ if (webSocket.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(childMessage));
1734
+ };
1735
+ const driver = createDriver({
1736
+ onEvents: (sessionId, events) => {
1737
+ for (const event of events) {
1738
+ const line = formatSessionLogLine(event);
1739
+ if (line) writeLine(line);
1740
+ }
1741
+ send({
1742
+ events,
1743
+ sessionId,
1744
+ type: "Events"
1745
+ });
1746
+ },
1747
+ onSessionOpen: (sessionId) => {
1748
+ send({
1749
+ sessionId,
1750
+ type: "SessionOpen"
1751
+ });
1752
+ },
1753
+ onSessionsChange: () => {
1754
+ send({ type: "SessionsChange" });
1755
+ }
1756
+ });
1757
+ const receive = async (text) => {
1758
+ const command = getResult(() => driverCommandSchema.parse(JSON.parse(text))).orTee((error) => {
1759
+ send({
1760
+ commandId: "",
1761
+ message: error.message,
1762
+ type: "CommandError"
1763
+ });
1764
+ }).unwrapOr(void 0);
1765
+ if (!command) return;
1766
+ await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
1767
+ send({
1768
+ commandId: command.id,
1769
+ sessionId,
1770
+ type: "CommandResult"
1771
+ });
1772
+ }, (error) => {
1773
+ send({
1774
+ commandId: command.id,
1775
+ message: error.message,
1776
+ type: "CommandError"
1777
+ });
1778
+ });
1779
+ };
1780
+ webSocket.on("message", (data) => {
1781
+ taskRegistry.run(() => receive(readMessageText(data)));
1782
+ });
1783
+ await once(webSocket, "close");
1784
+ await driver.close();
1785
+ await taskRegistry.drain();
1786
+ };
1787
+ //#endregion
1788
+ //#region src/services/drivers/window/runSessionChild.ts
1789
+ const runSessionChild = async (port, writeLine) => {
1790
+ const secret = process.env[SESSION_SECRET_ENVIRONMENT_VARIABLE] ?? "";
1791
+ Reflect.deleteProperty(process.env, SESSION_SECRET_ENVIRONMENT_VARIABLE);
1792
+ process.title = `${SITE_NAME} session`;
1793
+ const webSocket = new WebSocket(`ws://${DEFAULT_HOSTNAME}:${port}`, { headers: { authorization: `Bearer ${secret}` } });
1794
+ const serving = serveSessionChild(webSocket, createClaudeAgentSdkDriver, writeLine);
1795
+ await once(webSocket, "open");
1796
+ writeLine("This window shows one session as it runs. Type to it in the page.\nTo stop it, close this window.\n");
1797
+ const stop = () => {
1798
+ webSocket.close();
1799
+ };
1800
+ process.once("SIGINT", stop);
1801
+ process.once("SIGHUP", stop);
1802
+ await serving;
1803
+ };
1804
+ //#endregion
1805
+ //#region src/services/installer/checkIsHostInstalled.ts
1806
+ const checkIsHostInstalled = () => relative(getHostInstallDirectory(), dirname(process.execPath)) === "";
1807
+ //#endregion
1808
+ //#region src/services/installer/checkIsSchemeLaunchTampered.ts
1809
+ const checkIsSchemeLaunchTampered = (commandLineArguments) => commandLineArguments.length > 1 && commandLineArguments.some((commandLineArgument) => commandLineArgument.startsWith(`${HOST_SCHEME}:`));
1810
+ //#endregion
1811
+ //#region src/services/installer/getSchemeLaunch.ts
1812
+ const getSchemeLaunch = (argument) => {
1813
+ if (!URL.canParse(argument)) return void 0;
1814
+ const url = new URL(argument);
1815
+ if (url.protocol !== `${HOST_SCHEME}:`) return void 0;
1816
+ return { code: url.searchParams.get("code") ?? "" };
1817
+ };
1818
+ //#endregion
1819
+ //#region src/services/installer/installHost.ts
1820
+ const installHost = () => {
1821
+ const installDirectory = getHostInstallDirectory();
1822
+ const executablePath = join(installDirectory, HOST_EXECUTABLE_FILENAME);
1823
+ mkdirSync(installDirectory, { recursive: true });
1824
+ copyFileSync(process.execPath, executablePath);
1825
+ writeFileSync(join(installDirectory, CLAUDE_CODE_EXECUTABLE_FILENAME), new Uint8Array(getRawAsset(CLAUDE_CODE_EXECUTABLE_FILENAME)));
1826
+ for (const assetKey of getAssetKeys().filter((key) => key.startsWith(`${NODE_PTY_DIRECTORY_NAME}/`))) {
1827
+ const assetPath = join(installDirectory, ...assetKey.split("/"));
1828
+ mkdirSync(dirname(assetPath), { recursive: true });
1829
+ writeFileSync(assetPath, new Uint8Array(getRawAsset(assetKey)));
1830
+ }
1831
+ for (const registryArguments of [
1832
+ [
1833
+ HOST_SCHEME_REGISTRY_KEY,
1834
+ "/ve",
1835
+ "/d",
1836
+ `URL:${SITE_NAME} Host`
1837
+ ],
1838
+ [
1839
+ HOST_SCHEME_REGISTRY_KEY,
1840
+ "/v",
1841
+ "URL Protocol",
1842
+ "/d",
1843
+ ""
1844
+ ],
1845
+ [
1846
+ win32.join(HOST_SCHEME_REGISTRY_KEY, "shell", "open", "command"),
1847
+ "/ve",
1848
+ "/d",
1849
+ `"${executablePath}" "%1"`
1850
+ ]
1851
+ ]) execFileSync("reg", [
1852
+ "add",
1853
+ ...registryArguments,
1854
+ "/f"
1855
+ ], { stdio: "ignore" });
1856
+ return installDirectory;
1857
+ };
1858
+ //#endregion
1859
+ //#region src/services/installer/uninstallHost.ts
1860
+ const uninstallHost = () => {
1861
+ execFileSync("reg", [
1862
+ "delete",
1863
+ HOST_SCHEME_REGISTRY_KEY,
1864
+ "/f"
1865
+ ], { stdio: "ignore" });
1866
+ spawn("powershell.exe", [
1867
+ "-NoProfile",
1868
+ "-Command",
1869
+ `Start-Sleep -Seconds 2; Remove-Item -LiteralPath $env:${HOST_DIRECTORY_ENVIRONMENT_VARIABLE} -Recurse -Force`
1870
+ ], {
1871
+ detached: true,
1872
+ env: {
1873
+ ...process.env,
1874
+ [HOST_DIRECTORY_ENVIRONMENT_VARIABLE]: getHostInstallDirectory()
1875
+ },
1876
+ stdio: "ignore",
1877
+ windowsHide: true
1878
+ }).unref();
1879
+ };
1880
+ //#endregion
1881
+ //#region src/services/device/hashCredential.ts
1882
+ const hashCredential = (credential) => createHash("sha256").update(credential).digest("base64url");
1883
+ //#endregion
1884
+ //#region src/services/device/findDevice.ts
1885
+ const findDevice = (devices, credential) => {
1886
+ const credentialHash = hashCredential(credential);
1887
+ return devices.find((device) => checkIsTokenValid(credentialHash, device.credentialHash));
1888
+ };
1889
+ //#endregion
1890
+ //#region src/services/device/getDeviceName.ts
1891
+ const BrowserNames = [
1892
+ [/Edg\//u, "Edge"],
1893
+ [/Firefox\//u, "Firefox"],
1894
+ [/Chrome\//u, "Chrome"],
1895
+ [/Safari\//u, "Safari"]
1896
+ ];
1897
+ const SystemNames = [
1898
+ [/Windows/u, "Windows"],
1899
+ [/Android/u, "Android"],
1900
+ [/iPhone|iPad/u, "iOS"],
1901
+ [/Mac OS X/u, "macOS"],
1902
+ [/Linux/u, "Linux"]
1903
+ ];
1904
+ const getDeviceName = (userAgent) => {
1905
+ const browserName = BrowserNames.find(([pattern]) => pattern.test(userAgent))?.[1] ?? "A browser";
1906
+ const systemName = SystemNames.find(([pattern]) => pattern.test(userAgent))?.[1];
1907
+ return systemName ? `${browserName} on ${systemName}` : browserName;
1908
+ };
1909
+ //#endregion
1910
+ //#region src/services/handshake/getPublicKeyText.ts
1911
+ const getPublicKeyText = (privateKey) => createPublicKey(privateKey).export({ format: "jwk" }).x ?? "";
1912
+ //#endregion
1913
+ //#region src/services/server/answerHttpRequest.ts
1914
+ const answerHttpRequest = ({ headers, method }, response) => {
1915
+ if (method !== "OPTIONS") {
1916
+ response.writeHead(405).end();
1917
+ return;
1918
+ }
1919
+ response.setHeader("Access-Control-Allow-Origin", headers.origin ?? "*");
1920
+ response.setHeader("Access-Control-Allow-Private-Network", "true");
1921
+ response.setHeader("Access-Control-Allow-Methods", "GET");
1922
+ response.setHeader("Access-Control-Allow-Headers", headers["access-control-request-headers"] ?? "*");
1923
+ response.setHeader("Vary", "Origin");
1924
+ response.writeHead(204).end();
1925
+ };
1926
+ //#endregion
1927
+ //#region src/services/server/createEventLog.ts
1928
+ const createEventLog = () => {
1929
+ const sessionEventMap = /* @__PURE__ */ new Map();
1930
+ return {
1931
+ append: (sessionId, events) => {
1932
+ const sessionEvents = getOrCreate(sessionEventMap, sessionId, () => ({
1933
+ eventIds: /* @__PURE__ */ new Set(),
1934
+ events: []
1935
+ }));
1936
+ const newEvents = [];
1937
+ for (const event of events) {
1938
+ if (EphemeralAgentEventTypes.includes(event.type)) {
1939
+ newEvents.push(event);
1940
+ continue;
1941
+ }
1942
+ if (sessionEvents.eventIds.has(event.id)) continue;
1943
+ sessionEvents.eventIds.add(event.id);
1944
+ sessionEvents.events.push(event);
1945
+ newEvents.push(event);
1946
+ }
1947
+ return newEvents;
1948
+ },
1949
+ entries: () => Array.from(sessionEventMap, ([sessionId, { events }]) => [sessionId, events]),
1950
+ reset: (sessionId) => {
1951
+ sessionEventMap.delete(sessionId);
1952
+ }
1953
+ };
1954
+ };
1955
+ //#endregion
1956
+ //#region src/services/server/createPairingCodes.ts
1957
+ const createPairingCodes = () => {
1958
+ const codeTimeoutMap = /* @__PURE__ */ new Map();
1959
+ return {
1960
+ add: (code, duration) => {
1961
+ clearTimeout(codeTimeoutMap.get(code));
1962
+ codeTimeoutMap.set(code, setTimeout(() => {
1963
+ codeTimeoutMap.delete(code);
1964
+ }, duration));
1965
+ },
1966
+ clear: () => {
1967
+ for (const timeout of codeTimeoutMap.values()) clearTimeout(timeout);
1968
+ codeTimeoutMap.clear();
1969
+ },
1970
+ take: (code) => {
1971
+ const heldCode = [...codeTimeoutMap.keys()].find((candidate) => checkIsTokenValid(code, candidate));
1972
+ if (heldCode === void 0) return false;
1973
+ clearTimeout(codeTimeoutMap.get(heldCode));
1974
+ codeTimeoutMap.delete(heldCode);
1975
+ return true;
1976
+ }
1977
+ };
1978
+ };
1979
+ //#endregion
1980
+ //#region src/services/server/handleShellCommand.ts
1981
+ const handleShellCommand = async (shellRegistry, command) => {
1982
+ switch (command.type) {
1983
+ case "CloseShell":
1984
+ shellRegistry.close(command.shellId);
1985
+ return "";
1986
+ case "OpenShell": return await shellRegistry.open(command.sessionId, {
1987
+ cols: command.cols,
1988
+ cwd: command.cwd,
1989
+ rows: command.rows
1990
+ });
1991
+ case "ShellInput":
1992
+ shellRegistry.write(command.shellId, command.data);
1993
+ return "";
1994
+ case "ShellResize":
1995
+ shellRegistry.resize(command.shellId, command.cols, command.rows);
1996
+ return "";
1233
1997
  default: return exhaustiveGuard(command);
1234
1998
  }
1235
1999
  };
@@ -1239,13 +2003,91 @@ const sendServerMessage = (webSocket, message) => {
1239
2003
  if (webSocket.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(message));
1240
2004
  };
1241
2005
  //#endregion
2006
+ //#region src/services/shell/createShellRegistry.ts
2007
+ const createShellRegistry = ({ onClose, onOutput, spawnShell }) => {
2008
+ const shellMap = /* @__PURE__ */ new Map();
2009
+ const pendingShells = /* @__PURE__ */ new Set();
2010
+ const closedSessionIds = /* @__PURE__ */ new Set();
2011
+ let isStopped = false;
2012
+ const getTerminal = (shellId) => {
2013
+ const shell = shellMap.get(shellId);
2014
+ if (!shell) throw new InvalidOperationError(Operation.Read, shellId, "the shell is not running on this host");
2015
+ return shell.terminal;
2016
+ };
2017
+ return {
2018
+ close: (shellId) => {
2019
+ getTerminal(shellId).kill();
2020
+ },
2021
+ closeAll: () => {
2022
+ isStopped = true;
2023
+ for (const pendingShell of pendingShells) pendingShell.isClosed = true;
2024
+ for (const { terminal } of shellMap.values()) terminal.kill();
2025
+ },
2026
+ closeSession: (sessionId) => {
2027
+ closedSessionIds.add(sessionId);
2028
+ for (const pendingShell of pendingShells) if (pendingShell.sessionId === sessionId) pendingShell.isClosed = true;
2029
+ for (const shell of shellMap.values()) if (shell.sessionId === sessionId) shell.terminal.kill();
2030
+ },
2031
+ entries: () => Array.from(shellMap, ([shellId, { output, sessionId }]) => ({
2032
+ output,
2033
+ sessionId,
2034
+ shellId
2035
+ })),
2036
+ open: async (sessionId, options) => {
2037
+ if (isStopped) throw new InvalidOperationError(Operation.Create, sessionId, "the host is stopping");
2038
+ if (closedSessionIds.has(sessionId)) throw new InvalidOperationError(Operation.Create, sessionId, "the session is closed");
2039
+ const pendingShell = {
2040
+ isClosed: false,
2041
+ sessionId
2042
+ };
2043
+ pendingShells.add(pendingShell);
2044
+ const terminal = await withFinalizerAsync(() => spawnShell(options), () => {
2045
+ pendingShells.delete(pendingShell);
2046
+ });
2047
+ if (pendingShell.isClosed) {
2048
+ terminal.kill();
2049
+ throw new InvalidOperationError(Operation.Create, sessionId, "the shell's session or host closed as it started");
2050
+ }
2051
+ const shellId = crypto.randomUUID();
2052
+ const shell = {
2053
+ output: "",
2054
+ sessionId,
2055
+ terminal
2056
+ };
2057
+ shellMap.set(shellId, shell);
2058
+ terminal.onData((data) => {
2059
+ shell.output = `${shell.output}${data}`.slice(-SHELL_OUTPUT_LENGTH);
2060
+ onOutput(shellId, data);
2061
+ });
2062
+ terminal.onExit(() => {
2063
+ shellMap.delete(shellId);
2064
+ onClose(shellId);
2065
+ });
2066
+ return shellId;
2067
+ },
2068
+ openSession: (sessionId) => {
2069
+ closedSessionIds.delete(sessionId);
2070
+ },
2071
+ resize: (shellId, cols, rows) => {
2072
+ getTerminal(shellId).resize(cols, rows);
2073
+ },
2074
+ write: (shellId, data) => {
2075
+ getTerminal(shellId).write(data);
2076
+ }
2077
+ };
2078
+ };
2079
+ //#endregion
1242
2080
  //#region src/services/server/createAgentConsoleServer.ts
1243
- const createAgentConsoleServer = async ({ createDriver, hostname, port, token }) => {
2081
+ const createAgentConsoleServer = async ({ createDriver, hostKey, hostname, origin, port, spawnShell, stateDirectory, writeLine }) => {
1244
2082
  const eventLog = createEventLog();
1245
2083
  const taskRegistry = createTaskRegistry();
2084
+ const pairingCodes = createPairingCodes();
2085
+ const publicKey = createPublicKey(hostKey);
2086
+ const publicKeyText = getPublicKeyText(hostKey);
1246
2087
  const webSocketServer = new WebSocketServer({ noServer: true });
2088
+ const socketDeviceIdMap = /* @__PURE__ */ new Map();
1247
2089
  const broadcast = (message) => {
1248
- for (const webSocket of webSocketServer.clients) sendServerMessage(webSocket, message);
2090
+ for (const webSocket of socketDeviceIdMap.keys()) sendServerMessage(webSocket, message);
1249
2091
  };
1250
2092
  let isRefreshing = false;
1251
2093
  let isRefreshStale = false;
@@ -1268,6 +2110,22 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1268
2110
  isRefreshing = false;
1269
2111
  });
1270
2112
  };
2113
+ const shellRegistry = createShellRegistry({
2114
+ onClose: (shellId) => {
2115
+ broadcast({
2116
+ shellId,
2117
+ type: "ShellClosed"
2118
+ });
2119
+ },
2120
+ onOutput: (shellId, data) => {
2121
+ broadcast({
2122
+ data,
2123
+ shellId,
2124
+ type: "ShellOutput"
2125
+ });
2126
+ },
2127
+ spawnShell
2128
+ });
1271
2129
  const driver = createDriver({
1272
2130
  onEvents: (sessionId, events) => {
1273
2131
  const newEvents = eventLog.append(sessionId, events);
@@ -1276,8 +2134,10 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1276
2134
  sessionId,
1277
2135
  type: "Events"
1278
2136
  });
2137
+ if (events.some((event) => event.type === "SessionState" && event.state === "Closed")) shellRegistry.closeSession(sessionId);
1279
2138
  },
1280
2139
  onSessionOpen: (sessionId) => {
2140
+ shellRegistry.openSession(sessionId);
1281
2141
  eventLog.reset(sessionId);
1282
2142
  broadcast({
1283
2143
  sessionId,
@@ -1289,8 +2149,7 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1289
2149
  }
1290
2150
  });
1291
2151
  const receive = async (webSocket, data) => {
1292
- const text = Buffer.isBuffer(data) ? data.toString() : Array.isArray(data) ? Buffer.concat(data).toString() : Buffer.from(data).toString();
1293
- const command = getResult(() => commandSchema.parse(JSON.parse(text))).orTee((error) => {
2152
+ const command = getResult(() => commandSchema.parse(JSON.parse(readMessageText(data)))).orTee((error) => {
1294
2153
  sendServerMessage(webSocket, {
1295
2154
  commandId: "",
1296
2155
  message: error.message,
@@ -1298,39 +2157,147 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1298
2157
  });
1299
2158
  }).unwrapOr(void 0);
1300
2159
  if (!command) return;
1301
- await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
1302
- if (sessionId) sendServerMessage(webSocket, {
1303
- commandId: command.id,
1304
- sessionId,
1305
- type: "SessionOpened"
1306
- });
1307
- else if (command.type === "ListSessions") refreshSessions();
1308
- }, (error) => {
2160
+ const sendCommandError = (error) => {
1309
2161
  sendServerMessage(webSocket, {
1310
2162
  commandId: command.id,
1311
2163
  message: error.message,
1312
2164
  type: "CommandError"
1313
2165
  });
1314
- });
2166
+ };
2167
+ switch (command.type) {
2168
+ case "CloseShell":
2169
+ case "OpenShell":
2170
+ case "ShellInput":
2171
+ case "ShellResize":
2172
+ await getResultAsync(() => handleShellCommand(shellRegistry, command)).match((shellId) => {
2173
+ if (command.type === "OpenShell") sendServerMessage(webSocket, {
2174
+ commandId: command.id,
2175
+ sessionId: command.sessionId,
2176
+ shellId,
2177
+ type: "ShellOpened"
2178
+ });
2179
+ }, sendCommandError);
2180
+ return;
2181
+ default: await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
2182
+ if (sessionId) sendServerMessage(webSocket, {
2183
+ commandId: command.id,
2184
+ sessionId,
2185
+ type: "SessionOpened"
2186
+ });
2187
+ else if (command.type === "ListSessions") refreshSessions();
2188
+ }, sendCommandError);
2189
+ }
1315
2190
  };
1316
- webSocketServer.on("connection", (webSocket) => {
2191
+ const admit = (webSocket, deviceId) => {
2192
+ socketDeviceIdMap.set(webSocket, deviceId);
1317
2193
  for (const [sessionId, events] of eventLog.entries()) sendServerMessage(webSocket, {
1318
2194
  events,
1319
2195
  sessionId,
1320
2196
  type: "Events"
1321
2197
  });
2198
+ for (const { output, sessionId, shellId } of shellRegistry.entries()) {
2199
+ sendServerMessage(webSocket, {
2200
+ commandId: "",
2201
+ sessionId,
2202
+ shellId,
2203
+ type: "ShellOpened"
2204
+ });
2205
+ if (output) sendServerMessage(webSocket, {
2206
+ data: output,
2207
+ shellId,
2208
+ type: "ShellOutput"
2209
+ });
2210
+ }
1322
2211
  refreshSessions();
2212
+ };
2213
+ const pair = (webSocket, request, code) => {
2214
+ if (request.headers.origin !== origin || !pairingCodes.take(code)) {
2215
+ webSocket.close(4403);
2216
+ return;
2217
+ }
2218
+ const credential = randomBytes(32).toString("base64url");
2219
+ const device = {
2220
+ createdAt: /* @__PURE__ */ new Date(),
2221
+ credentialHash: hashCredential(credential),
2222
+ id: crypto.randomUUID(),
2223
+ name: getDeviceName(request.headers["user-agent"] ?? ""),
2224
+ origin
2225
+ };
2226
+ writeDevices(stateDirectory, [...readDevices(stateDirectory), device]);
2227
+ writeLine(`${device.name} connected. To remove it: devices --revoke ${device.id}`);
2228
+ sendServerMessage(webSocket, {
2229
+ credential,
2230
+ deviceId: device.id,
2231
+ publicKey: publicKeyText,
2232
+ type: "Paired"
2233
+ });
2234
+ admit(webSocket, device.id);
2235
+ };
2236
+ const receiveHandshake = (webSocket, request, hostNonce, handshakeMessage) => {
2237
+ const localPort = request.socket.localPort ?? 0;
2238
+ switch (handshakeMessage.type) {
2239
+ case "Authenticate": {
2240
+ const device = findDevice(readDevices(stateDirectory), handshakeMessage.credential);
2241
+ if (!device) {
2242
+ webSocket.close(4401);
2243
+ return;
2244
+ }
2245
+ sendServerMessage(webSocket, { type: "Authenticated" });
2246
+ admit(webSocket, device.id);
2247
+ return;
2248
+ }
2249
+ case "Challenge":
2250
+ sendServerMessage(webSocket, {
2251
+ nonce: hostNonce,
2252
+ port: localPort,
2253
+ signature: signNonce(hostKey, "HostProof", localPort, handshakeMessage.nonce),
2254
+ type: "Proof"
2255
+ });
2256
+ return;
2257
+ case "HandOff":
2258
+ case "Revoke":
2259
+ if (!checkIsSignatureValid(publicKey, "OwnerProof", localPort, hostNonce, handshakeMessage.signature)) {
2260
+ webSocket.close(4404);
2261
+ return;
2262
+ }
2263
+ if (handshakeMessage.type === "HandOff") pairingCodes.add(handshakeMessage.code, SCHEME_PAIRING_CODE_DURATION);
2264
+ else {
2265
+ writeDevices(stateDirectory, readDevices(stateDirectory).filter(({ id }) => id !== handshakeMessage.deviceId));
2266
+ for (const [deviceWebSocket, deviceId] of socketDeviceIdMap) if (deviceId === handshakeMessage.deviceId) deviceWebSocket.close(4401);
2267
+ writeLine("A device was removed, and its connection closed.");
2268
+ }
2269
+ webSocket.close();
2270
+ return;
2271
+ case "Pair":
2272
+ pair(webSocket, request, handshakeMessage.code);
2273
+ return;
2274
+ default: exhaustiveGuard(handshakeMessage);
2275
+ }
2276
+ };
2277
+ webSocketServer.on("connection", (webSocket, request) => {
2278
+ const hostNonce = randomBytes(32).toString("base64url");
1323
2279
  webSocket.on("message", (data) => {
1324
- taskRegistry.run(() => receive(webSocket, data));
2280
+ if (socketDeviceIdMap.has(webSocket)) {
2281
+ taskRegistry.run(() => receive(webSocket, data));
2282
+ return;
2283
+ }
2284
+ getResult(() => {
2285
+ receiveHandshake(webSocket, request, hostNonce, handshakeMessageSchema.parse(JSON.parse(readMessageText(data))));
2286
+ }).match(noop, (error) => {
2287
+ console.error(error);
2288
+ webSocket.close(4400);
2289
+ });
2290
+ });
2291
+ webSocket.on("close", () => {
2292
+ socketDeviceIdMap.delete(webSocket);
1325
2293
  });
1326
2294
  });
1327
2295
  const httpServer = createServer((request, response) => {
1328
2296
  answerHttpRequest(request, response);
1329
2297
  });
1330
2298
  httpServer.on("upgrade", (request, socket, head) => {
1331
- const url = new URL(request.url ?? "/", "http://localhost");
1332
- if (!checkIsTokenValid(url.searchParams.get("token") ?? "", token)) {
1333
- socket.end("HTTP/1.1 401 Unauthorized\r\n\r\n");
2299
+ if (request.headers.origin !== void 0 && request.headers.origin !== origin) {
2300
+ socket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
1334
2301
  return;
1335
2302
  }
1336
2303
  webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
@@ -1340,38 +2307,60 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1340
2307
  httpServer.listen(port, hostname);
1341
2308
  await once(httpServer, "listening");
1342
2309
  const address = httpServer.address();
2310
+ let closing;
2311
+ const close = async () => {
2312
+ broadcast({ type: "HostStopping" });
2313
+ pairingCodes.clear();
2314
+ shellRegistry.closeAll();
2315
+ await driver.close();
2316
+ for (const webSocket of webSocketServer.clients) webSocket.terminate();
2317
+ webSocketServer.close();
2318
+ httpServer.close();
2319
+ await Promise.all([
2320
+ taskRegistry.drain(),
2321
+ once(webSocketServer, "close"),
2322
+ once(httpServer, "close")
2323
+ ]);
2324
+ };
1343
2325
  return {
1344
- close: async () => {
1345
- await driver.close();
1346
- for (const webSocket of webSocketServer.clients) webSocket.terminate();
1347
- webSocketServer.close();
1348
- httpServer.close();
1349
- await Promise.all([
1350
- taskRegistry.drain(),
1351
- once(webSocketServer, "close"),
1352
- once(httpServer, "close")
1353
- ]);
1354
- },
2326
+ addPairingCode: pairingCodes.add,
2327
+ close: () => closing ??= close(),
1355
2328
  port: typeof address === "object" && address ? address.port : port
1356
2329
  };
1357
2330
  };
1358
2331
  //#endregion
1359
- //#region src/services/server/constants.ts
1360
- const TOKEN_DIRECTORY_NAME = ".agent-console-server";
1361
- const TOKEN_FILENAME = "token";
1362
- const TOKEN_BYTE_LENGTH = 32;
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;
2332
+ //#region src/services/shell/constants.ts
2333
+ const CONSOLE_LIST_AGENT_NAME = "conpty_console_list_agent";
2334
+ //#endregion
2335
+ //#region src/services/shell/runConsoleListAgent.ts
2336
+ const runConsoleListAgent = (shellPid) => {
2337
+ const installDirectory = getHostInstallDirectory();
2338
+ const { getConsoleProcessList } = createRequire(join(installDirectory, "package.json"))(join(installDirectory, NODE_PTY_DIRECTORY_NAME, "prebuilds", "win32-x64", "conpty_console_list.node"));
2339
+ process.send?.({ consoleProcessList: getConsoleProcessList(shellPid) });
2340
+ };
2341
+ //#endregion
2342
+ //#region src/services/shell/getShellFile.ts
2343
+ const getShellFile = () => process.platform === "win32" ? "powershell.exe" : process.env.SHELL ?? "/bin/sh";
2344
+ //#endregion
2345
+ //#region src/services/shell/loadNodePty.ts
2346
+ const loadNodePty = () => {
2347
+ if (!isSea()) return import("node-pty");
2348
+ const installDirectory = getHostInstallDirectory();
2349
+ return Promise.resolve(createRequire(join(installDirectory, "package.json"))(join(installDirectory, NODE_PTY_DIRECTORY_NAME)));
2350
+ };
2351
+ //#endregion
2352
+ //#region src/services/shell/spawnPtyShell.ts
2353
+ const spawnPtyShell = async ({ cols, cwd, rows }) => {
2354
+ const { spawn } = await getResultAsync(() => loadNodePty()).match((nodePty) => nodePty, () => {
2355
+ throw new InvalidOperationError(Operation.Create, cwd, "this host has no terminal support, so no shell can start");
2356
+ });
2357
+ return spawn(getShellFile(), [], {
2358
+ cols,
2359
+ cwd,
2360
+ env: process.env,
2361
+ name: "xterm-256color",
2362
+ rows
2363
+ });
1375
2364
  };
1376
2365
  //#endregion
1377
- export { mapHookMessage as A, createInputQueue as B, toTurnResultEvents as C, toToolResultText as D, mapUserContent as E, createStreamTracker as F, closeOpenSession as G, IGNORED_SDK_MESSAGE_TYPES as H, createFileOriginTracker as I, getEventId as K, createPermissionBridge as L, toUnknownEvent as M, createTodoTracker as N, mapSystemMessage as O, TodoToolName as P, toPermissionResult as R, createSdkMessageMapper as S, parseMessageBody as T, SESSION_LIST_LIMIT as U, DEFAULT_DENY_MESSAGE as V, SETTING_SOURCES as W, toContextUsageEvent as _, createAgentConsoleServer as a, readToolUseResultMap as b, createEventLog as c, createClaudeAgentSdkDriver as d, createTaskRegistry as f, watchSession as g, createSessionOpener as h, TOKEN_FILENAME as i, mapAssistantContent as j, mapTaskMessage as k, checkIsTokenValid as l, readSessionCwd as m, TOKEN_BYTE_LENGTH as n, sendServerMessage as o, toContentBlockParam as p, TOKEN_DIRECTORY_NAME as r, handleCommand as s, readToken as t, answerHttpRequest as u, toCapabilitiesEvent as v, toSessionInitEvent as w, readSessionHistory as x, getSessionTitle as y, toPermissionRequestEvent as z };
2366
+ export { HOST_DIRECTORY_ENVIRONMENT_VARIABLE as $, sendChildCommand as A, IGNORED_SDK_MESSAGE_TYPES as At, SESSION_SECRET_BYTE_LENGTH as B, signNonce as Bt, serveSessionChild as C, createStreamTracker as Ct, createWindowDriver as D, toPermissionRequestEvent as Dt, launchSessionWindow as E, toPermissionResult as Et, childCommandResultMessageSchema as F, getEventId as Ft, createTaskRegistry as G, readHostKey as Gt, SESSION_SUBCOMMAND as H, writeDevices as Ht, childCommandErrorMessageSchema as I, runDevicesCommand as It, listSessionSummaries as J, DEVICES_FILENAME as Jt, toContentBlockParam as K, writeStateFile as Kt, ChildMessageType as L, sendToRunningHost as Lt, childSessionsChangeMessageSchema as M, SETTING_SOURCES as Mt, childSessionOpenMessageSchema as N, closeOpenSession as Nt, checkIsTokenValid as O, createInputQueue as Ot, childEventsMessageSchema as P, toSessionClosedEvents as Pt, CLAUDE_CODE_EXECUTABLE_FILENAME as Q, STATE_DIRECTORY_NAME as Qt, childMessageTypeSchema as R, readMessageText as Rt, runSessionChild as S, TodoToolName as St, formatSessionLogLine as T, createPermissionBridge as Tt, SESSION_WINDOW_CONNECT_TIMEOUT as U, readDevices as Ut, SESSION_SECRET_ENVIRONMENT_VARIABLE as V, checkIsSignatureValid as Vt, createClaudeAgentSdkDriver as W, deviceSchema as Wt, getClaudeCodeExecutablePath as X, PRINTED_PAIRING_CODE_DURATION as Xt, createSessionOpener as Y, HOST_KEY_FILENAME as Yt, getHostInstallDirectory as Z, SECRET_BYTE_LENGTH as Zt, uninstallHost as _, mapTaskMessage as _t, CONSOLE_LIST_AGENT_NAME as a, toContextUsageEvent as at, checkIsSchemeLaunchTampered as b, toUnknownEvent as bt, sendServerMessage as c, readToolUseResultMap as ct, createEventLog as d, toTurnResultEvents as dt, HOST_EXECUTABLE_FILENAME as et, answerHttpRequest as f, toSessionInitEvent as ft, hashCredential as g, mapSystemMessage as gt, findDevice as h, toToolResultText as ht, runConsoleListAgent as i, watchSession as it, childMessageSchema as j, SESSION_LIST_LIMIT as jt, takeChildReply as k, DEFAULT_DENY_MESSAGE as kt, handleShellCommand as l, readSessionHistory as lt, getDeviceName as m, mapUserContent as mt, loadNodePty as n, HOST_SCHEME_REGISTRY_KEY as nt, createAgentConsoleServer as o, toCapabilitiesEvent as ot, getPublicKeyText as p, parseMessageBody as pt, readSessionCwd as q, getStateDirectory as qt, getShellFile as r, NODE_PTY_DIRECTORY_NAME as rt, createShellRegistry as s, getSessionTitle as st, spawnPtyShell as t, HOST_INSTALL_DIRECTORY_SEGMENTS as tt, createPairingCodes as u, createSdkMessageMapper as ut, installHost as v, mapHookMessage as vt, handleCommand as w, createFileOriginTracker as wt, checkIsHostInstalled as x, createTodoTracker as xt, getSchemeLaunch as y, mapAssistantContent as yt, createBaseChildMessageSchema as z, HOST_PROBE_TIMEOUT_MS as zt };