agent-console-server 3.3.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,27 +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/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;
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");
1155
1309
  //#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();
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;
1167
1377
  };
1168
1378
  //#endregion
1169
1379
  //#region src/services/server/checkIsTokenValid.ts
@@ -1174,38 +1384,312 @@ const checkIsTokenValid = (candidate, token) => {
1174
1384
  return candidateBuffer.length === tokenBuffer.length && timingSafeEqual(candidateBuffer, tokenBuffer);
1175
1385
  };
1176
1386
  //#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;
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;
1191
1449
  }
1192
- if (sessionEvents.eventIds.has(event.id)) continue;
1193
- sessionEvents.eventIds.add(event.id);
1194
- sessionEvents.events.push(event);
1195
- newEvents.push(event);
1450
+ onEvents(sessionId, events);
1451
+ if (isHeld && windowSession.state === "Closed") closeWindowSession(sessionId, windowSession);
1452
+ return;
1196
1453
  }
1197
- 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
+ });
1198
1540
  },
1199
- entries: () => Array.from(sessionEventMap, ([sessionId, { events }]) => [sessionId, events]),
1200
- reset: (sessionId) => {
1201
- 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
+ });
1202
1650
  }
1203
1651
  };
1204
1652
  };
1205
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
1206
1687
  //#region src/services/server/handleCommand.ts
1207
1688
  const handleCommand = async (driver, command) => {
1208
1689
  switch (command.type) {
1690
+ case "BackgroundTasks":
1691
+ await driver.backgroundTasks(command.sessionId);
1692
+ return "";
1209
1693
  case "CloseSession":
1210
1694
  driver.closeSession(command.sessionId);
1211
1695
  return "";
@@ -1235,6 +1719,281 @@ const handleCommand = async (driver, command) => {
1235
1719
  case "SlashCommand":
1236
1720
  driver.runSlashCommand(command.sessionId, command.name, command.arguments);
1237
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 "";
1238
1997
  default: return exhaustiveGuard(command);
1239
1998
  }
1240
1999
  };
@@ -1244,13 +2003,91 @@ const sendServerMessage = (webSocket, message) => {
1244
2003
  if (webSocket.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(message));
1245
2004
  };
1246
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
1247
2080
  //#region src/services/server/createAgentConsoleServer.ts
1248
- const createAgentConsoleServer = async ({ createDriver, hostname, port, token }) => {
2081
+ const createAgentConsoleServer = async ({ createDriver, hostKey, hostname, origin, port, spawnShell, stateDirectory, writeLine }) => {
1249
2082
  const eventLog = createEventLog();
1250
2083
  const taskRegistry = createTaskRegistry();
2084
+ const pairingCodes = createPairingCodes();
2085
+ const publicKey = createPublicKey(hostKey);
2086
+ const publicKeyText = getPublicKeyText(hostKey);
1251
2087
  const webSocketServer = new WebSocketServer({ noServer: true });
2088
+ const socketDeviceIdMap = /* @__PURE__ */ new Map();
1252
2089
  const broadcast = (message) => {
1253
- for (const webSocket of webSocketServer.clients) sendServerMessage(webSocket, message);
2090
+ for (const webSocket of socketDeviceIdMap.keys()) sendServerMessage(webSocket, message);
1254
2091
  };
1255
2092
  let isRefreshing = false;
1256
2093
  let isRefreshStale = false;
@@ -1273,6 +2110,22 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1273
2110
  isRefreshing = false;
1274
2111
  });
1275
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
+ });
1276
2129
  const driver = createDriver({
1277
2130
  onEvents: (sessionId, events) => {
1278
2131
  const newEvents = eventLog.append(sessionId, events);
@@ -1281,8 +2134,10 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1281
2134
  sessionId,
1282
2135
  type: "Events"
1283
2136
  });
2137
+ if (events.some((event) => event.type === "SessionState" && event.state === "Closed")) shellRegistry.closeSession(sessionId);
1284
2138
  },
1285
2139
  onSessionOpen: (sessionId) => {
2140
+ shellRegistry.openSession(sessionId);
1286
2141
  eventLog.reset(sessionId);
1287
2142
  broadcast({
1288
2143
  sessionId,
@@ -1294,8 +2149,7 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1294
2149
  }
1295
2150
  });
1296
2151
  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) => {
2152
+ const command = getResult(() => commandSchema.parse(JSON.parse(readMessageText(data)))).orTee((error) => {
1299
2153
  sendServerMessage(webSocket, {
1300
2154
  commandId: "",
1301
2155
  message: error.message,
@@ -1303,39 +2157,147 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1303
2157
  });
1304
2158
  }).unwrapOr(void 0);
1305
2159
  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) => {
2160
+ const sendCommandError = (error) => {
1314
2161
  sendServerMessage(webSocket, {
1315
2162
  commandId: command.id,
1316
2163
  message: error.message,
1317
2164
  type: "CommandError"
1318
2165
  });
1319
- });
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
+ }
1320
2190
  };
1321
- webSocketServer.on("connection", (webSocket) => {
2191
+ const admit = (webSocket, deviceId) => {
2192
+ socketDeviceIdMap.set(webSocket, deviceId);
1322
2193
  for (const [sessionId, events] of eventLog.entries()) sendServerMessage(webSocket, {
1323
2194
  events,
1324
2195
  sessionId,
1325
2196
  type: "Events"
1326
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
+ }
1327
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");
1328
2279
  webSocket.on("message", (data) => {
1329
- 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);
1330
2293
  });
1331
2294
  });
1332
2295
  const httpServer = createServer((request, response) => {
1333
2296
  answerHttpRequest(request, response);
1334
2297
  });
1335
2298
  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");
2299
+ if (request.headers.origin !== void 0 && request.headers.origin !== origin) {
2300
+ socket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
1339
2301
  return;
1340
2302
  }
1341
2303
  webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
@@ -1345,33 +2307,60 @@ const createAgentConsoleServer = async ({ createDriver, hostname, port, token })
1345
2307
  httpServer.listen(port, hostname);
1346
2308
  await once(httpServer, "listening");
1347
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
+ };
1348
2325
  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
- },
2326
+ addPairingCode: pairingCodes.add,
2327
+ close: () => closing ??= close(),
1360
2328
  port: typeof address === "object" && address ? address.port : port
1361
2329
  };
1362
2330
  };
1363
2331
  //#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 { 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 };
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 };