agent-console-server 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/dist/EphemeralAgentEventTypes-Spjnk48Y.js +0 -869
- package/dist/agentConsoleServerCommand-B7491RR7.js +0 -3275
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -5
- package/dist/contracts-DPknvuKq.d.ts +0 -1220
- package/dist/contracts-la_KkjCS.js +0 -1
- package/dist/contracts.d.ts +0 -2
- package/dist/contracts.js +0 -2
- package/dist/index.d.ts +0 -828
- package/dist/index.js +0 -21
|
@@ -1,3275 +0,0 @@
|
|
|
1
|
-
import { H as getSignedText, Ot as permissionModeSchema, U as serverMessageSchema, _n as SHELL_OUTPUT_LENGTH, dn as DEFAULT_HOSTNAME, fn as DEFAULT_PORT, gn as SCHEME_PAIRING_CODE_DURATION_MS, gt as todoStatusSchema, hn as PAIRING_HASH_PARAMETER, it as agentEventSchema, mn as PAIRING_CODE_PARAMETER, pn as HOST_SCHEME, r as handshakeMessageSchema, t as EphemeralAgentEventTypes, u as commandSchema, un as DEFAULT_APP_ORIGIN, y as driverCommandSchema } from "./EphemeralAgentEventTypes-Spjnk48Y.js";
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { homedir, hostname } from "node:os";
|
|
4
|
-
import { ID_SEPARATOR, InvalidOperationError, Operation, RoutePath, SITE_NAME, createUniqueArraySchema, exhaustiveGuard, getOrCreate, getResult, getResultAsync, noop, withFinalizerAsync } from "@esposter/shared";
|
|
5
|
-
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
6
|
-
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
7
|
-
import { parseArgs, promisify } from "node:util";
|
|
8
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { getAssetKeys, getRawAsset, isSea } from "node:sea";
|
|
10
|
-
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import { once } from "node:events";
|
|
13
|
-
import { WebSocket, WebSocketServer } from "ws";
|
|
14
|
-
import { getSessionInfo, getSessionMessages, listSessions, query } from "@anthropic-ai/claude-agent-sdk";
|
|
15
|
-
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
16
|
-
import { createServer } from "node:http";
|
|
17
|
-
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
18
|
-
//#region src/services/cli/getReachableHostname.ts
|
|
19
|
-
const getReachableHostname = (hostname$1) => hostname$1 === "0.0.0.0" ? hostname() : hostname$1;
|
|
20
|
-
//#endregion
|
|
21
|
-
//#region src/services/cli/hostArgs.ts
|
|
22
|
-
const hostArgs = {
|
|
23
|
-
hostname: {
|
|
24
|
-
default: DEFAULT_HOSTNAME,
|
|
25
|
-
description: "The address the host listens on",
|
|
26
|
-
type: "string"
|
|
27
|
-
},
|
|
28
|
-
origin: {
|
|
29
|
-
default: DEFAULT_APP_ORIGIN,
|
|
30
|
-
description: "The app's origin the printed link opens",
|
|
31
|
-
type: "string"
|
|
32
|
-
},
|
|
33
|
-
port: {
|
|
34
|
-
default: String(DEFAULT_PORT),
|
|
35
|
-
description: "The port the host listens on",
|
|
36
|
-
type: "string"
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/services/installer/constants.ts
|
|
41
|
-
const HOST_EXECUTABLE_FILENAME = "agent-console-host.exe";
|
|
42
|
-
const CLAUDE_CODE_EXECUTABLE_FILENAME = "claude.exe";
|
|
43
|
-
const HOST_SCHEME_REGISTRY_KEY = win32.join("HKCU", "Software", "Classes", HOST_SCHEME);
|
|
44
|
-
const HOST_DIRECTORY_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_HOST_DIRECTORY`;
|
|
45
|
-
const HOST_INSTALL_DIRECTORY_SEGMENTS = [SITE_NAME, "Host"];
|
|
46
|
-
const NODE_PTY_DIRECTORY_NAME = "node-pty";
|
|
47
|
-
//#endregion
|
|
48
|
-
//#region src/services/installer/getHostInstallDirectory.ts
|
|
49
|
-
const getHostInstallDirectory = () => join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), ...HOST_INSTALL_DIRECTORY_SEGMENTS);
|
|
50
|
-
//#endregion
|
|
51
|
-
//#region src/services/installer/checkIsHostInstalled.ts
|
|
52
|
-
const checkIsHostInstalled = () => relative(getHostInstallDirectory(), dirname(process.execPath)) === "";
|
|
53
|
-
//#endregion
|
|
54
|
-
//#region ../shared-node/dist/execSync-CtM99iYq.js
|
|
55
|
-
const withHiddenWindow = (parameters) => {
|
|
56
|
-
const optionsIndex = Array.isArray(parameters[0]) || parameters[0] === void 0 && parameters.length > 1 ? 1 : 0;
|
|
57
|
-
const options = parameters[optionsIndex];
|
|
58
|
-
if (typeof options === "object" && options !== null) return parameters.toSpliced(optionsIndex, 1, {
|
|
59
|
-
...options,
|
|
60
|
-
windowsHide: true
|
|
61
|
-
});
|
|
62
|
-
const isPlaceholder = options === void 0 && optionsIndex < parameters.length;
|
|
63
|
-
return parameters.toSpliced(optionsIndex, Number(isPlaceholder), { windowsHide: true });
|
|
64
|
-
};
|
|
65
|
-
//#endregion
|
|
66
|
-
//#region ../shared-node/dist/child-process.js
|
|
67
|
-
const execFile$1 = ((file, ...parameters) => execFile(file, ...withHiddenWindow(parameters)));
|
|
68
|
-
Object.defineProperty(execFile$1, promisify.custom, { value: (file, ...parameters) => promisify(execFile)(file, ...withHiddenWindow(parameters)) });
|
|
69
|
-
const execFileSync$1 = ((file, ...parameters) => execFileSync(file, ...withHiddenWindow(parameters)));
|
|
70
|
-
const spawn$1 = ((file, ...parameters) => spawn(file, ...withHiddenWindow(parameters)));
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region src/services/installer/installHost.ts
|
|
73
|
-
const installHost = () => {
|
|
74
|
-
const installDirectory = getHostInstallDirectory();
|
|
75
|
-
const executablePath = join(installDirectory, HOST_EXECUTABLE_FILENAME);
|
|
76
|
-
mkdirSync(installDirectory, { recursive: true });
|
|
77
|
-
copyFileSync(process.execPath, executablePath);
|
|
78
|
-
writeFileSync(join(installDirectory, CLAUDE_CODE_EXECUTABLE_FILENAME), new Uint8Array(getRawAsset(CLAUDE_CODE_EXECUTABLE_FILENAME)));
|
|
79
|
-
for (const assetKey of getAssetKeys().filter((key) => key.startsWith(`${NODE_PTY_DIRECTORY_NAME}/`))) {
|
|
80
|
-
const assetPath = join(installDirectory, ...assetKey.split("/"));
|
|
81
|
-
mkdirSync(dirname(assetPath), { recursive: true });
|
|
82
|
-
writeFileSync(assetPath, new Uint8Array(getRawAsset(assetKey)));
|
|
83
|
-
}
|
|
84
|
-
for (const registryArguments of [
|
|
85
|
-
[
|
|
86
|
-
HOST_SCHEME_REGISTRY_KEY,
|
|
87
|
-
"/ve",
|
|
88
|
-
"/d",
|
|
89
|
-
`URL:${SITE_NAME} Host`
|
|
90
|
-
],
|
|
91
|
-
[
|
|
92
|
-
HOST_SCHEME_REGISTRY_KEY,
|
|
93
|
-
"/v",
|
|
94
|
-
"URL Protocol",
|
|
95
|
-
"/d",
|
|
96
|
-
""
|
|
97
|
-
],
|
|
98
|
-
[
|
|
99
|
-
win32.join(HOST_SCHEME_REGISTRY_KEY, "shell", "open", "command"),
|
|
100
|
-
"/ve",
|
|
101
|
-
"/d",
|
|
102
|
-
`"${executablePath}" "%1"`
|
|
103
|
-
]
|
|
104
|
-
]) execFileSync$1("reg", [
|
|
105
|
-
"add",
|
|
106
|
-
...registryArguments,
|
|
107
|
-
"/f"
|
|
108
|
-
], { stdio: "ignore" });
|
|
109
|
-
return installDirectory;
|
|
110
|
-
};
|
|
111
|
-
//#endregion
|
|
112
|
-
//#region src/services/cli/installDownloadedHost.ts
|
|
113
|
-
const installDownloadedHost = () => {
|
|
114
|
-
if (!isSea() || checkIsHostInstalled()) return;
|
|
115
|
-
getResult(() => installHost()).match((installDirectory) => {
|
|
116
|
-
process.stdout.write(`Installed the host in ${installDirectory}. The page's Connect starts it from now on.\n\n`);
|
|
117
|
-
}, (error) => {
|
|
118
|
-
process.stderr.write(`Could not install the host: ${error.message}\nClose any other host window, then open this file again.\n`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
//#endregion
|
|
123
|
-
//#region src/services/cli/writeLine.ts
|
|
124
|
-
const writeLine = (line) => {
|
|
125
|
-
process.stdout.write(`${line}\n`);
|
|
126
|
-
};
|
|
127
|
-
//#endregion
|
|
128
|
-
//#region src/services/device/constants.ts
|
|
129
|
-
const STATE_DIRECTORY_NAME = ".agent-console-server";
|
|
130
|
-
const DEVICES_FILENAME = "devices.json";
|
|
131
|
-
const HOST_KEY_FILENAME = "host-key.pem";
|
|
132
|
-
const SECRET_BYTE_LENGTH = 32;
|
|
133
|
-
const PRINTED_PAIRING_CODE_DURATION_MS = Temporal.Duration.from({ minutes: 10 }).total("milliseconds");
|
|
134
|
-
//#endregion
|
|
135
|
-
//#region src/services/device/getStateDirectory.ts
|
|
136
|
-
const getStateDirectory = () => join(homedir(), STATE_DIRECTORY_NAME);
|
|
137
|
-
//#endregion
|
|
138
|
-
//#region src/services/device/writeStateFile.ts
|
|
139
|
-
const writeStateFile = (stateDirectory, filename, content) => {
|
|
140
|
-
const filePath = join(stateDirectory, filename);
|
|
141
|
-
const temporaryFilePath = `${filePath}.${crypto.randomUUID()}.tmp`;
|
|
142
|
-
mkdirSync(stateDirectory, { recursive: true });
|
|
143
|
-
getResult(() => {
|
|
144
|
-
writeFileSync(temporaryFilePath, content, {
|
|
145
|
-
flag: "wx",
|
|
146
|
-
mode: 384
|
|
147
|
-
});
|
|
148
|
-
renameSync(temporaryFilePath, filePath);
|
|
149
|
-
}).match(noop, (error) => {
|
|
150
|
-
getResult(() => {
|
|
151
|
-
rmSync(temporaryFilePath, { force: true });
|
|
152
|
-
}).match(noop, console.error);
|
|
153
|
-
throw error;
|
|
154
|
-
});
|
|
155
|
-
};
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/services/device/readHostKey.ts
|
|
158
|
-
const readHostKey = (stateDirectory) => {
|
|
159
|
-
const hostKeyPath = join(stateDirectory, HOST_KEY_FILENAME);
|
|
160
|
-
if (existsSync(hostKeyPath)) return createPrivateKey(readFileSync(hostKeyPath, "utf8"));
|
|
161
|
-
const { privateKey } = generateKeyPairSync("ed25519");
|
|
162
|
-
writeStateFile(stateDirectory, HOST_KEY_FILENAME, privateKey.export({
|
|
163
|
-
format: "pem",
|
|
164
|
-
type: "pkcs8"
|
|
165
|
-
}));
|
|
166
|
-
return privateKey;
|
|
167
|
-
};
|
|
168
|
-
//#endregion
|
|
169
|
-
//#region src/models/device/Device.ts
|
|
170
|
-
const deviceSchema = z.object({
|
|
171
|
-
createdAt: z.coerce.date(),
|
|
172
|
-
credentialHash: z.string().min(1),
|
|
173
|
-
id: z.string().min(1),
|
|
174
|
-
name: z.string(),
|
|
175
|
-
origin: z.string()
|
|
176
|
-
});
|
|
177
|
-
//#endregion
|
|
178
|
-
//#region src/services/device/readDevices.ts
|
|
179
|
-
const readDevices = (stateDirectory) => {
|
|
180
|
-
const devicesPath = join(stateDirectory, DEVICES_FILENAME);
|
|
181
|
-
if (existsSync(devicesPath)) return z.array(deviceSchema).parse(JSON.parse(readFileSync(devicesPath, "utf8")));
|
|
182
|
-
else return [];
|
|
183
|
-
};
|
|
184
|
-
//#endregion
|
|
185
|
-
//#region src/services/device/writeDevices.ts
|
|
186
|
-
const writeDevices = (stateDirectory, devices) => {
|
|
187
|
-
writeStateFile(stateDirectory, DEVICES_FILENAME, JSON.stringify(devices, void 0, 2));
|
|
188
|
-
};
|
|
189
|
-
//#endregion
|
|
190
|
-
//#region src/services/handshake/checkIsSignatureValid.ts
|
|
191
|
-
const checkIsSignatureValid = (key, purpose, port, nonce, signature) => verify(void 0, Buffer.from(getSignedText(purpose, port, nonce)), key, Buffer.from(signature, "base64url"));
|
|
192
|
-
//#endregion
|
|
193
|
-
//#region src/services/handshake/signNonce.ts
|
|
194
|
-
const signNonce = (privateKey, purpose, port, nonce) => sign(void 0, Buffer.from(getSignedText(purpose, port, nonce)), privateKey).toString("base64url");
|
|
195
|
-
//#endregion
|
|
196
|
-
//#region src/services/server/constants.ts
|
|
197
|
-
const HOST_PROBE_TIMEOUT_MS = 2e3;
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region src/services/shared/readMessageText.ts
|
|
200
|
-
const readMessageText = (data) => Buffer.isBuffer(data) ? data.toString() : Array.isArray(data) ? Buffer.concat(data).toString() : Buffer.from(data).toString();
|
|
201
|
-
//#endregion
|
|
202
|
-
//#region src/services/server/sendToRunningHost.ts
|
|
203
|
-
const sendToRunningHost = async (hostname, port, hostKey, createMessage) => {
|
|
204
|
-
const nonce = randomBytes(32).toString("base64url");
|
|
205
|
-
const webSocket = new WebSocket(`ws://${hostname}:${port}`, { handshakeTimeout: HOST_PROBE_TIMEOUT_MS });
|
|
206
|
-
const isHost = await getResultAsync(async () => {
|
|
207
|
-
const signal = AbortSignal.timeout(HOST_PROBE_TIMEOUT_MS);
|
|
208
|
-
await once(webSocket, "open", { signal });
|
|
209
|
-
const pendingMessage = once(webSocket, "message", { signal });
|
|
210
|
-
webSocket.send(JSON.stringify({
|
|
211
|
-
nonce,
|
|
212
|
-
type: "Challenge"
|
|
213
|
-
}));
|
|
214
|
-
const [data] = await pendingMessage;
|
|
215
|
-
const serverMessage = serverMessageSchema.parse(JSON.parse(readMessageText(data)));
|
|
216
|
-
if (serverMessage.type !== "Proof" || serverMessage.port !== port || !checkIsSignatureValid(createPublicKey(hostKey), "HostProof", port, nonce, serverMessage.signature)) return false;
|
|
217
|
-
if (!createMessage) return true;
|
|
218
|
-
const pendingClose = once(webSocket, "close", { signal });
|
|
219
|
-
webSocket.send(JSON.stringify(createMessage(signNonce(hostKey, "OwnerProof", port, serverMessage.nonce))));
|
|
220
|
-
await pendingClose;
|
|
221
|
-
return true;
|
|
222
|
-
}).match((isHolderHost) => isHolderHost, () => false);
|
|
223
|
-
webSocket.terminate();
|
|
224
|
-
return isHost;
|
|
225
|
-
};
|
|
226
|
-
//#endregion
|
|
227
|
-
//#region src/services/device/runDevicesCommand.ts
|
|
228
|
-
const runDevicesCommand = async ({ hostKey, hostname, port, revokeDeviceId, stateDirectory }, writeLine) => {
|
|
229
|
-
const devices = readDevices(stateDirectory);
|
|
230
|
-
if (!revokeDeviceId) {
|
|
231
|
-
if (devices.length === 0) writeLine("No page is connected to this host.");
|
|
232
|
-
for (const { createdAt, id, name, origin } of devices) writeLine(`${id} ${name} ${origin} since ${createdAt.toISOString()}`);
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
|
-
if (!devices.some(({ id }) => id === revokeDeviceId)) {
|
|
236
|
-
writeLine(`No device has the id ${revokeDeviceId}.`);
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
writeDevices(stateDirectory, devices.filter(({ id }) => id !== revokeDeviceId));
|
|
240
|
-
await sendToRunningHost(hostname, port, hostKey, (signature) => ({
|
|
241
|
-
deviceId: revokeDeviceId,
|
|
242
|
-
signature,
|
|
243
|
-
type: "Revoke"
|
|
244
|
-
}));
|
|
245
|
-
writeLine(`Removed ${revokeDeviceId}. It has to connect again to use this host.`);
|
|
246
|
-
return true;
|
|
247
|
-
};
|
|
248
|
-
//#endregion
|
|
249
|
-
//#region ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/_chunks/libs/scule.mjs
|
|
250
|
-
const NUMBER_CHAR_RE = /\d/;
|
|
251
|
-
const STR_SPLITTERS = [
|
|
252
|
-
"-",
|
|
253
|
-
"_",
|
|
254
|
-
"/",
|
|
255
|
-
"."
|
|
256
|
-
];
|
|
257
|
-
function isUppercase(char = "") {
|
|
258
|
-
if (NUMBER_CHAR_RE.test(char)) return;
|
|
259
|
-
return char !== char.toLowerCase();
|
|
260
|
-
}
|
|
261
|
-
function splitByCase(str, separators) {
|
|
262
|
-
const splitters = separators ?? STR_SPLITTERS;
|
|
263
|
-
const parts = [];
|
|
264
|
-
if (!str || typeof str !== "string") return parts;
|
|
265
|
-
let buff = "";
|
|
266
|
-
let previousUpper;
|
|
267
|
-
let previousSplitter;
|
|
268
|
-
for (const char of str) {
|
|
269
|
-
const isSplitter = splitters.includes(char);
|
|
270
|
-
if (isSplitter === true) {
|
|
271
|
-
parts.push(buff);
|
|
272
|
-
buff = "";
|
|
273
|
-
previousUpper = void 0;
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
const isUpper = isUppercase(char);
|
|
277
|
-
if (previousSplitter === false) {
|
|
278
|
-
if (previousUpper === false && isUpper === true) {
|
|
279
|
-
parts.push(buff);
|
|
280
|
-
buff = char;
|
|
281
|
-
previousUpper = isUpper;
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
if (previousUpper === true && isUpper === false && buff.length > 1) {
|
|
285
|
-
const lastChar = buff.at(-1);
|
|
286
|
-
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
|
|
287
|
-
buff = lastChar + char;
|
|
288
|
-
previousUpper = isUpper;
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
buff += char;
|
|
293
|
-
previousUpper = isUpper;
|
|
294
|
-
previousSplitter = isSplitter;
|
|
295
|
-
}
|
|
296
|
-
parts.push(buff);
|
|
297
|
-
return parts;
|
|
298
|
-
}
|
|
299
|
-
function upperFirst(str) {
|
|
300
|
-
return str ? str[0].toUpperCase() + str.slice(1) : "";
|
|
301
|
-
}
|
|
302
|
-
function lowerFirst(str) {
|
|
303
|
-
return str ? str[0].toLowerCase() + str.slice(1) : "";
|
|
304
|
-
}
|
|
305
|
-
function pascalCase(str, opts) {
|
|
306
|
-
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
|
|
307
|
-
}
|
|
308
|
-
function camelCase(str, opts) {
|
|
309
|
-
return lowerFirst(pascalCase(str || "", opts));
|
|
310
|
-
}
|
|
311
|
-
function kebabCase(str, joiner) {
|
|
312
|
-
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
|
|
313
|
-
}
|
|
314
|
-
function snakeCase(str) {
|
|
315
|
-
return kebabCase(str || "", "_");
|
|
316
|
-
}
|
|
317
|
-
//#endregion
|
|
318
|
-
//#region ../../node_modules/.pnpm/citty@0.2.2/node_modules/citty/dist/index.mjs
|
|
319
|
-
function toArray(val) {
|
|
320
|
-
if (Array.isArray(val)) return val;
|
|
321
|
-
return val === void 0 ? [] : [val];
|
|
322
|
-
}
|
|
323
|
-
function formatLineColumns(lines, linePrefix = "") {
|
|
324
|
-
const maxLength = [];
|
|
325
|
-
for (const line of lines) for (const [i, element] of line.entries()) maxLength[i] = Math.max(maxLength[i] || 0, element.length);
|
|
326
|
-
return lines.map((l) => l.map((c, i) => linePrefix + c[i === 0 ? "padStart" : "padEnd"](maxLength[i])).join(" ")).join("\n");
|
|
327
|
-
}
|
|
328
|
-
function resolveValue(input) {
|
|
329
|
-
return typeof input === "function" ? input() : input;
|
|
330
|
-
}
|
|
331
|
-
var CLIError = class extends Error {
|
|
332
|
-
code;
|
|
333
|
-
constructor(message, code) {
|
|
334
|
-
super(message);
|
|
335
|
-
this.name = "CLIError";
|
|
336
|
-
this.code = code;
|
|
337
|
-
}
|
|
338
|
-
};
|
|
339
|
-
function parseRawArgs(args = [], opts = {}) {
|
|
340
|
-
const booleans = new Set(opts.boolean || []);
|
|
341
|
-
const strings = new Set(opts.string || []);
|
|
342
|
-
const aliasMap = opts.alias || {};
|
|
343
|
-
const defaults = opts.default || {};
|
|
344
|
-
const aliasToMain = /* @__PURE__ */ new Map();
|
|
345
|
-
const mainToAliases = /* @__PURE__ */ new Map();
|
|
346
|
-
for (const [key, value] of Object.entries(aliasMap)) {
|
|
347
|
-
const targets = value;
|
|
348
|
-
for (const target of targets) {
|
|
349
|
-
aliasToMain.set(key, target);
|
|
350
|
-
if (!mainToAliases.has(target)) mainToAliases.set(target, []);
|
|
351
|
-
mainToAliases.get(target).push(key);
|
|
352
|
-
aliasToMain.set(target, key);
|
|
353
|
-
if (!mainToAliases.has(key)) mainToAliases.set(key, []);
|
|
354
|
-
mainToAliases.get(key).push(target);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
const options = {};
|
|
358
|
-
function getType(name) {
|
|
359
|
-
if (booleans.has(name)) return "boolean";
|
|
360
|
-
const aliases = mainToAliases.get(name) || [];
|
|
361
|
-
for (const alias of aliases) if (booleans.has(alias)) return "boolean";
|
|
362
|
-
return "string";
|
|
363
|
-
}
|
|
364
|
-
function isStringType(name) {
|
|
365
|
-
if (strings.has(name)) return true;
|
|
366
|
-
const aliases = mainToAliases.get(name) || [];
|
|
367
|
-
for (const alias of aliases) if (strings.has(alias)) return true;
|
|
368
|
-
return false;
|
|
369
|
-
}
|
|
370
|
-
const allOptions = /* @__PURE__ */ new Set([
|
|
371
|
-
...booleans,
|
|
372
|
-
...strings,
|
|
373
|
-
...Object.keys(aliasMap),
|
|
374
|
-
...Object.values(aliasMap).flat(),
|
|
375
|
-
...Object.keys(defaults)
|
|
376
|
-
]);
|
|
377
|
-
for (const name of allOptions) if (!options[name]) options[name] = {
|
|
378
|
-
type: getType(name),
|
|
379
|
-
default: defaults[name]
|
|
380
|
-
};
|
|
381
|
-
for (const [alias, main] of aliasToMain.entries()) if (alias.length === 1 && options[main] && !options[main].short) options[main].short = alias;
|
|
382
|
-
const processedArgs = [];
|
|
383
|
-
const negatedFlags = {};
|
|
384
|
-
for (let i = 0; i < args.length; i++) {
|
|
385
|
-
const arg = args[i];
|
|
386
|
-
if (arg === "--") {
|
|
387
|
-
processedArgs.push(...args.slice(i));
|
|
388
|
-
break;
|
|
389
|
-
}
|
|
390
|
-
if (arg.startsWith("--no-")) {
|
|
391
|
-
const flagName = arg.slice(5);
|
|
392
|
-
negatedFlags[flagName] = true;
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
processedArgs.push(arg);
|
|
396
|
-
}
|
|
397
|
-
let parsed;
|
|
398
|
-
try {
|
|
399
|
-
parsed = parseArgs({
|
|
400
|
-
args: processedArgs,
|
|
401
|
-
options: Object.keys(options).length > 0 ? options : void 0,
|
|
402
|
-
allowPositionals: true,
|
|
403
|
-
strict: false
|
|
404
|
-
});
|
|
405
|
-
} catch {
|
|
406
|
-
parsed = {
|
|
407
|
-
values: {},
|
|
408
|
-
positionals: processedArgs
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const out = { _: [] };
|
|
412
|
-
out._ = parsed.positionals;
|
|
413
|
-
for (const [key, value] of Object.entries(parsed.values)) {
|
|
414
|
-
let coerced = value;
|
|
415
|
-
if (getType(key) === "boolean" && typeof value === "string") coerced = value !== "false";
|
|
416
|
-
else if (isStringType(key) && typeof value === "boolean") coerced = "";
|
|
417
|
-
out[key] = coerced;
|
|
418
|
-
}
|
|
419
|
-
for (const [name] of Object.entries(negatedFlags)) {
|
|
420
|
-
out[name] = false;
|
|
421
|
-
const mainName = aliasToMain.get(name);
|
|
422
|
-
if (mainName) out[mainName] = false;
|
|
423
|
-
const aliases = mainToAliases.get(name);
|
|
424
|
-
if (aliases) for (const alias of aliases) out[alias] = false;
|
|
425
|
-
}
|
|
426
|
-
for (const [alias, main] of aliasToMain.entries()) {
|
|
427
|
-
if (out[alias] !== void 0 && out[main] === void 0) out[main] = out[alias];
|
|
428
|
-
if (out[main] !== void 0 && out[alias] === void 0) out[alias] = out[main];
|
|
429
|
-
if (out[alias] !== out[main] && defaults[main] === out[main]) out[main] = out[alias];
|
|
430
|
-
}
|
|
431
|
-
return out;
|
|
432
|
-
}
|
|
433
|
-
const noColor = /* @__PURE__ */ (() => {
|
|
434
|
-
const env = globalThis.process?.env ?? {};
|
|
435
|
-
return env.NO_COLOR === "1" || env.TERM === "dumb" || env.TEST || env.CI;
|
|
436
|
-
})();
|
|
437
|
-
const _c = (c, r = 39) => (t) => noColor ? t : `\u001B[${c}m${t}\u001B[${r}m`;
|
|
438
|
-
const bold = /* @__PURE__ */ _c(1, 22);
|
|
439
|
-
const cyan = /* @__PURE__ */ _c(36);
|
|
440
|
-
const gray = /* @__PURE__ */ _c(90);
|
|
441
|
-
const underline = /* @__PURE__ */ _c(4, 24);
|
|
442
|
-
function parseArgs$1(rawArgs, argsDef) {
|
|
443
|
-
const parseOptions = {
|
|
444
|
-
boolean: [],
|
|
445
|
-
string: [],
|
|
446
|
-
alias: {},
|
|
447
|
-
default: {}
|
|
448
|
-
};
|
|
449
|
-
const args = resolveArgs(argsDef);
|
|
450
|
-
for (const arg of args) {
|
|
451
|
-
if (arg.type === "positional") continue;
|
|
452
|
-
if (arg.type === "string" || arg.type === "enum") parseOptions.string.push(arg.name);
|
|
453
|
-
else if (arg.type === "boolean") parseOptions.boolean.push(arg.name);
|
|
454
|
-
if (arg.default !== void 0) parseOptions.default[arg.name] = arg.default;
|
|
455
|
-
if (arg.alias) parseOptions.alias[arg.name] = arg.alias;
|
|
456
|
-
const camelName = camelCase(arg.name);
|
|
457
|
-
const kebabName = kebabCase(arg.name);
|
|
458
|
-
if (camelName !== arg.name || kebabName !== arg.name) {
|
|
459
|
-
const existingAliases = toArray(parseOptions.alias[arg.name] || []);
|
|
460
|
-
if (camelName !== arg.name && !existingAliases.includes(camelName)) existingAliases.push(camelName);
|
|
461
|
-
if (kebabName !== arg.name && !existingAliases.includes(kebabName)) existingAliases.push(kebabName);
|
|
462
|
-
if (existingAliases.length > 0) parseOptions.alias[arg.name] = existingAliases;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
const parsed = parseRawArgs(rawArgs, parseOptions);
|
|
466
|
-
const [ ...positionalArguments] = parsed._;
|
|
467
|
-
const parsedArgsProxy = new Proxy(parsed, { get(target, prop) {
|
|
468
|
-
return target[prop] ?? target[camelCase(prop)] ?? target[kebabCase(prop)];
|
|
469
|
-
} });
|
|
470
|
-
for (const [, arg] of args.entries()) if (arg.type === "positional") {
|
|
471
|
-
const nextPositionalArgument = positionalArguments.shift();
|
|
472
|
-
if (nextPositionalArgument !== void 0) parsedArgsProxy[arg.name] = nextPositionalArgument;
|
|
473
|
-
else if (arg.default === void 0 && arg.required !== false) throw new CLIError(`Missing required positional argument: ${arg.name.toUpperCase()}`, "EARG");
|
|
474
|
-
else parsedArgsProxy[arg.name] = arg.default;
|
|
475
|
-
} else if (arg.type === "enum") {
|
|
476
|
-
const argument = parsedArgsProxy[arg.name];
|
|
477
|
-
const options = arg.options || [];
|
|
478
|
-
if (argument !== void 0 && options.length > 0 && !options.includes(argument)) throw new CLIError(`Invalid value for argument: ${cyan(`--${arg.name}`)} (${cyan(argument)}). Expected one of: ${options.map((o) => cyan(o)).join(", ")}.`, "EARG");
|
|
479
|
-
} else if (arg.required && parsedArgsProxy[arg.name] === void 0) throw new CLIError(`Missing required argument: --${arg.name}`, "EARG");
|
|
480
|
-
return parsedArgsProxy;
|
|
481
|
-
}
|
|
482
|
-
function resolveArgs(argsDef) {
|
|
483
|
-
const args = [];
|
|
484
|
-
for (const [name, argDef] of Object.entries(argsDef || {})) args.push({
|
|
485
|
-
...argDef,
|
|
486
|
-
name,
|
|
487
|
-
alias: toArray(argDef.alias)
|
|
488
|
-
});
|
|
489
|
-
return args;
|
|
490
|
-
}
|
|
491
|
-
async function resolvePlugins(plugins) {
|
|
492
|
-
return Promise.all(plugins.map((p) => resolveValue(p)));
|
|
493
|
-
}
|
|
494
|
-
function defineCommand(def) {
|
|
495
|
-
return def;
|
|
496
|
-
}
|
|
497
|
-
async function runCommand(cmd, opts) {
|
|
498
|
-
const cmdArgs = await resolveValue(cmd.args || {});
|
|
499
|
-
const parsedArgs = parseArgs$1(opts.rawArgs, cmdArgs);
|
|
500
|
-
const context = {
|
|
501
|
-
rawArgs: opts.rawArgs,
|
|
502
|
-
args: parsedArgs,
|
|
503
|
-
data: opts.data,
|
|
504
|
-
cmd
|
|
505
|
-
};
|
|
506
|
-
const plugins = await resolvePlugins(cmd.plugins ?? []);
|
|
507
|
-
let result;
|
|
508
|
-
let runError;
|
|
509
|
-
try {
|
|
510
|
-
for (const plugin of plugins) await plugin.setup?.(context);
|
|
511
|
-
if (typeof cmd.setup === "function") await cmd.setup(context);
|
|
512
|
-
const subCommands = await resolveValue(cmd.subCommands);
|
|
513
|
-
if (subCommands && Object.keys(subCommands).length > 0) {
|
|
514
|
-
const subCommandArgIndex = findSubCommandIndex(opts.rawArgs, cmdArgs);
|
|
515
|
-
const explicitName = opts.rawArgs[subCommandArgIndex];
|
|
516
|
-
if (explicitName) {
|
|
517
|
-
const subCommand = await _findSubCommand(subCommands, explicitName);
|
|
518
|
-
if (!subCommand) throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
|
|
519
|
-
await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
|
|
520
|
-
} else {
|
|
521
|
-
const defaultSubCommand = await resolveValue(cmd.default);
|
|
522
|
-
if (defaultSubCommand) {
|
|
523
|
-
if (cmd.run) throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
|
|
524
|
-
const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
|
|
525
|
-
if (!subCommand) throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
|
|
526
|
-
await runCommand(subCommand, { rawArgs: opts.rawArgs });
|
|
527
|
-
} else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND");
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
if (typeof cmd.run === "function") result = await cmd.run(context);
|
|
531
|
-
} catch (error) {
|
|
532
|
-
runError = error;
|
|
533
|
-
}
|
|
534
|
-
const cleanupErrors = [];
|
|
535
|
-
if (typeof cmd.cleanup === "function") try {
|
|
536
|
-
await cmd.cleanup(context);
|
|
537
|
-
} catch (error) {
|
|
538
|
-
cleanupErrors.push(error);
|
|
539
|
-
}
|
|
540
|
-
for (const plugin of [...plugins].reverse()) try {
|
|
541
|
-
await plugin.cleanup?.(context);
|
|
542
|
-
} catch (error) {
|
|
543
|
-
cleanupErrors.push(error);
|
|
544
|
-
}
|
|
545
|
-
if (runError) throw runError;
|
|
546
|
-
if (cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
547
|
-
if (cleanupErrors.length > 1) throw new Error("Multiple cleanup errors", { cause: cleanupErrors });
|
|
548
|
-
return { result };
|
|
549
|
-
}
|
|
550
|
-
async function resolveSubCommand(cmd, rawArgs, parent) {
|
|
551
|
-
const subCommands = await resolveValue(cmd.subCommands);
|
|
552
|
-
if (subCommands && Object.keys(subCommands).length > 0) {
|
|
553
|
-
const subCommandArgIndex = findSubCommandIndex(rawArgs, await resolveValue(cmd.args || {}));
|
|
554
|
-
const subCommandName = rawArgs[subCommandArgIndex];
|
|
555
|
-
const subCommand = await _findSubCommand(subCommands, subCommandName);
|
|
556
|
-
if (subCommand) return resolveSubCommand(subCommand, rawArgs.slice(subCommandArgIndex + 1), cmd);
|
|
557
|
-
}
|
|
558
|
-
return [cmd, parent];
|
|
559
|
-
}
|
|
560
|
-
async function _findSubCommand(subCommands, name) {
|
|
561
|
-
if (name in subCommands) return resolveValue(subCommands[name]);
|
|
562
|
-
for (const sub of Object.values(subCommands)) {
|
|
563
|
-
const resolved = await resolveValue(sub);
|
|
564
|
-
const meta = await resolveValue(resolved?.meta);
|
|
565
|
-
if (meta?.alias) {
|
|
566
|
-
if (toArray(meta.alias).includes(name)) return resolved;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
function findSubCommandIndex(rawArgs, argsDef) {
|
|
571
|
-
for (let i = 0; i < rawArgs.length; i++) {
|
|
572
|
-
const arg = rawArgs[i];
|
|
573
|
-
if (arg === "--") return -1;
|
|
574
|
-
if (arg.startsWith("-")) {
|
|
575
|
-
if (!arg.includes("=") && _isValueFlag(arg, argsDef)) i++;
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
return i;
|
|
579
|
-
}
|
|
580
|
-
return -1;
|
|
581
|
-
}
|
|
582
|
-
function _isValueFlag(flag, argsDef) {
|
|
583
|
-
const name = flag.replace(/^-{1,2}/, "");
|
|
584
|
-
const normalized = camelCase(name);
|
|
585
|
-
for (const [key, def] of Object.entries(argsDef)) {
|
|
586
|
-
if (def.type !== "string" && def.type !== "enum") continue;
|
|
587
|
-
if (normalized === camelCase(key)) return true;
|
|
588
|
-
if ((Array.isArray(def.alias) ? def.alias : def.alias ? [def.alias] : []).includes(name)) return true;
|
|
589
|
-
}
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
|
-
async function showUsage(cmd, parent) {
|
|
593
|
-
try {
|
|
594
|
-
console.log(await renderUsage(cmd, parent) + "\n");
|
|
595
|
-
} catch (error) {
|
|
596
|
-
console.error(error);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
const negativePrefixRe = /^no[-A-Z]/;
|
|
600
|
-
async function renderUsage(cmd, parent) {
|
|
601
|
-
const cmdMeta = await resolveValue(cmd.meta || {});
|
|
602
|
-
const cmdArgs = resolveArgs(await resolveValue(cmd.args || {}));
|
|
603
|
-
const parentMeta = await resolveValue(parent?.meta || {});
|
|
604
|
-
const commandName = `${parentMeta.name ? `${parentMeta.name} ` : ""}` + (cmdMeta.name || process.argv[1]);
|
|
605
|
-
const argLines = [];
|
|
606
|
-
const posLines = [];
|
|
607
|
-
const commandsLines = [];
|
|
608
|
-
const usageLine = [];
|
|
609
|
-
for (const arg of cmdArgs) if (arg.type === "positional") {
|
|
610
|
-
const name = arg.name.toUpperCase();
|
|
611
|
-
const isRequired = arg.required !== false && arg.default === void 0;
|
|
612
|
-
posLines.push([cyan(name + renderValueHint(arg)), renderDescription(arg, isRequired)]);
|
|
613
|
-
usageLine.push(isRequired ? `<${name}>` : `[${name}]`);
|
|
614
|
-
} else {
|
|
615
|
-
const isRequired = arg.required === true && arg.default === void 0;
|
|
616
|
-
const argStr = [...(arg.alias || []).map((a) => `-${a}`), `--${arg.name}`].join(", ") + renderValueHint(arg);
|
|
617
|
-
argLines.push([cyan(argStr), renderDescription(arg, isRequired)]);
|
|
618
|
-
/**
|
|
619
|
-
* print negative boolean arg variant usage when
|
|
620
|
-
* - enabled by default or has `negativeDescription`
|
|
621
|
-
* - not prefixed with `no-` or `no[A-Z]`
|
|
622
|
-
*/
|
|
623
|
-
if (arg.type === "boolean" && (arg.default === true || arg.negativeDescription) && !negativePrefixRe.test(arg.name)) {
|
|
624
|
-
const negativeArgStr = [...(arg.alias || []).map((a) => `--no-${a}`), `--no-${arg.name}`].join(", ");
|
|
625
|
-
argLines.push([cyan(negativeArgStr), [arg.negativeDescription, isRequired ? gray("(Required)") : ""].filter(Boolean).join(" ")]);
|
|
626
|
-
}
|
|
627
|
-
if (isRequired) usageLine.push(`--${arg.name}` + renderValueHint(arg));
|
|
628
|
-
}
|
|
629
|
-
if (cmd.subCommands) {
|
|
630
|
-
const commandNames = [];
|
|
631
|
-
const subCommands = await resolveValue(cmd.subCommands);
|
|
632
|
-
for (const [name, sub] of Object.entries(subCommands)) {
|
|
633
|
-
const meta = await resolveValue((await resolveValue(sub))?.meta);
|
|
634
|
-
if (meta?.hidden) continue;
|
|
635
|
-
const aliases = toArray(meta?.alias);
|
|
636
|
-
const label = [name, ...aliases].join(", ");
|
|
637
|
-
commandsLines.push([cyan(label), meta?.description || ""]);
|
|
638
|
-
commandNames.push(name, ...aliases);
|
|
639
|
-
}
|
|
640
|
-
usageLine.push(commandNames.join("|"));
|
|
641
|
-
}
|
|
642
|
-
const usageLines = [];
|
|
643
|
-
const version = cmdMeta.version || parentMeta.version;
|
|
644
|
-
usageLines.push(gray(`${cmdMeta.description} (${commandName + (version ? ` v${version}` : "")})`), "");
|
|
645
|
-
const hasOptions = argLines.length > 0 || posLines.length > 0;
|
|
646
|
-
usageLines.push(`${underline(bold("USAGE"))} ${cyan(`${commandName}${hasOptions ? " [OPTIONS]" : ""} ${usageLine.join(" ")}`)}`, "");
|
|
647
|
-
if (posLines.length > 0) {
|
|
648
|
-
usageLines.push(underline(bold("ARGUMENTS")), "");
|
|
649
|
-
usageLines.push(formatLineColumns(posLines, " "));
|
|
650
|
-
usageLines.push("");
|
|
651
|
-
}
|
|
652
|
-
if (argLines.length > 0) {
|
|
653
|
-
usageLines.push(underline(bold("OPTIONS")), "");
|
|
654
|
-
usageLines.push(formatLineColumns(argLines, " "));
|
|
655
|
-
usageLines.push("");
|
|
656
|
-
}
|
|
657
|
-
if (commandsLines.length > 0) {
|
|
658
|
-
usageLines.push(underline(bold("COMMANDS")), "");
|
|
659
|
-
usageLines.push(formatLineColumns(commandsLines, " "));
|
|
660
|
-
usageLines.push("", `Use ${cyan(`${commandName} <command> --help`)} for more information about a command.`);
|
|
661
|
-
}
|
|
662
|
-
return usageLines.filter((l) => typeof l === "string").join("\n");
|
|
663
|
-
}
|
|
664
|
-
function renderValueHint(arg) {
|
|
665
|
-
const valueHint = arg.valueHint ? `=<${arg.valueHint}>` : "";
|
|
666
|
-
const fallbackValueHint = valueHint || `=<${snakeCase(arg.name)}>`;
|
|
667
|
-
if (!arg.type || arg.type === "positional" || arg.type === "boolean") return valueHint;
|
|
668
|
-
if (arg.type === "enum" && arg.options?.length) return `=<${arg.options.join("|")}>`;
|
|
669
|
-
return fallbackValueHint;
|
|
670
|
-
}
|
|
671
|
-
function renderDescription(arg, required) {
|
|
672
|
-
const requiredHint = required ? gray("(Required)") : "";
|
|
673
|
-
const defaultHint = arg.default === void 0 ? "" : gray(`(Default: ${arg.default})`);
|
|
674
|
-
return [
|
|
675
|
-
arg.description,
|
|
676
|
-
requiredHint,
|
|
677
|
-
defaultHint
|
|
678
|
-
].filter(Boolean).join(" ");
|
|
679
|
-
}
|
|
680
|
-
async function runMain(cmd, opts = {}) {
|
|
681
|
-
const rawArgs = opts.rawArgs || process.argv.slice(2);
|
|
682
|
-
const showUsage$1 = opts.showUsage || showUsage;
|
|
683
|
-
try {
|
|
684
|
-
const builtinFlags = await _resolveBuiltinFlags(cmd);
|
|
685
|
-
if (builtinFlags.help.length > 0 && rawArgs.some((arg) => builtinFlags.help.includes(arg))) {
|
|
686
|
-
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
|
|
687
|
-
process.exit(0);
|
|
688
|
-
} else if (rawArgs.length === 1 && builtinFlags.version.includes(rawArgs[0])) {
|
|
689
|
-
const meta = typeof cmd.meta === "function" ? await cmd.meta() : await cmd.meta;
|
|
690
|
-
if (!meta?.version) throw new CLIError("No version specified", "E_NO_VERSION");
|
|
691
|
-
console.log(meta.version);
|
|
692
|
-
} else await runCommand(cmd, { rawArgs });
|
|
693
|
-
} catch (error) {
|
|
694
|
-
if (error instanceof CLIError) {
|
|
695
|
-
await showUsage$1(...await resolveSubCommand(cmd, rawArgs));
|
|
696
|
-
console.error(error.message);
|
|
697
|
-
} else console.error(error, "\n");
|
|
698
|
-
process.exit(1);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
async function _resolveBuiltinFlags(cmd) {
|
|
702
|
-
const argsDef = await resolveValue(cmd.args || {});
|
|
703
|
-
const userNames = /* @__PURE__ */ new Set();
|
|
704
|
-
const userAliases = /* @__PURE__ */ new Set();
|
|
705
|
-
for (const [name, def] of Object.entries(argsDef)) {
|
|
706
|
-
userNames.add(name);
|
|
707
|
-
for (const alias of toArray(def.alias)) userAliases.add(alias);
|
|
708
|
-
}
|
|
709
|
-
return {
|
|
710
|
-
help: _getBuiltinFlags("help", "h", userNames, userAliases),
|
|
711
|
-
version: _getBuiltinFlags("version", "v", userNames, userAliases)
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
715
|
-
if (userNames.has(long) || userAliases.has(long)) return [];
|
|
716
|
-
if (userNames.has(short) || userAliases.has(short)) return [`--${long}`];
|
|
717
|
-
return [`--${long}`, `-${short}`];
|
|
718
|
-
}
|
|
719
|
-
//#endregion
|
|
720
|
-
//#region src/services/cli/commands/devicesCommand.ts
|
|
721
|
-
const devicesCommand = defineCommand({
|
|
722
|
-
args: {
|
|
723
|
-
hostname: hostArgs.hostname,
|
|
724
|
-
port: hostArgs.port,
|
|
725
|
-
revoke: {
|
|
726
|
-
default: "",
|
|
727
|
-
description: "A paired page's id, to remove it",
|
|
728
|
-
type: "string"
|
|
729
|
-
}
|
|
730
|
-
},
|
|
731
|
-
meta: {
|
|
732
|
-
description: "List the pages paired with this host, or remove one",
|
|
733
|
-
name: "devices"
|
|
734
|
-
},
|
|
735
|
-
run: async ({ args }) => {
|
|
736
|
-
installDownloadedHost();
|
|
737
|
-
const stateDirectory = getStateDirectory();
|
|
738
|
-
const isDone = await runDevicesCommand({
|
|
739
|
-
hostKey: readHostKey(stateDirectory),
|
|
740
|
-
hostname: getReachableHostname(args.hostname),
|
|
741
|
-
port: Number(args.port),
|
|
742
|
-
revokeDeviceId: args.revoke,
|
|
743
|
-
stateDirectory
|
|
744
|
-
}, writeLine);
|
|
745
|
-
process.exit(isDone ? 0 : 1);
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
//#endregion
|
|
749
|
-
//#region src/services/drivers/claudeAgentSdk/getEventId.ts
|
|
750
|
-
const getEventId = (sourceId, suffix) => `${sourceId}${ID_SEPARATOR}${suffix}`;
|
|
751
|
-
//#endregion
|
|
752
|
-
//#region src/services/drivers/claudeAgentSdk/toSessionClosedEvents.ts
|
|
753
|
-
const toSessionClosedEvents = (message) => {
|
|
754
|
-
const createdAt = /* @__PURE__ */ new Date();
|
|
755
|
-
const closedId = getEventId(crypto.randomUUID(), "SessionState");
|
|
756
|
-
return [...message ? [{
|
|
757
|
-
createdAt,
|
|
758
|
-
id: getEventId(closedId, "HostError"),
|
|
759
|
-
message,
|
|
760
|
-
type: "HostError"
|
|
761
|
-
}] : [], {
|
|
762
|
-
createdAt,
|
|
763
|
-
id: closedId,
|
|
764
|
-
state: "Closed",
|
|
765
|
-
type: "SessionState"
|
|
766
|
-
}];
|
|
767
|
-
};
|
|
768
|
-
//#endregion
|
|
769
|
-
//#region src/services/drivers/claudeAgentSdk/closeOpenSession.ts
|
|
770
|
-
const closeOpenSession = (sessionId, openSession, message, { emit, onSessionsChange, openSessionMap }) => {
|
|
771
|
-
if (openSessionMap.get(sessionId) !== openSession) return;
|
|
772
|
-
for (const pendingPermission of openSession.pendingPermissionMap.values()) pendingPermission.settle("Deny", "");
|
|
773
|
-
emit(sessionId, toSessionClosedEvents(message));
|
|
774
|
-
openSessionMap.delete(sessionId);
|
|
775
|
-
onSessionsChange();
|
|
776
|
-
};
|
|
777
|
-
//#endregion
|
|
778
|
-
//#region src/services/drivers/claudeAgentSdk/constants.ts
|
|
779
|
-
const IGNORED_SDK_MESSAGE_TYPES = ["command_lifecycle"];
|
|
780
|
-
const DEFAULT_DENY_MESSAGE = "The user denied this tool call.";
|
|
781
|
-
const SETTING_SOURCES = [
|
|
782
|
-
"user",
|
|
783
|
-
"project",
|
|
784
|
-
"local"
|
|
785
|
-
];
|
|
786
|
-
const SESSION_LIST_LIMIT = 50;
|
|
787
|
-
//#endregion
|
|
788
|
-
//#region src/services/drivers/claudeAgentSdk/createInputQueue.ts
|
|
789
|
-
const createInputQueue = () => {
|
|
790
|
-
let items = [];
|
|
791
|
-
let pendingReads = [];
|
|
792
|
-
let isClosed = false;
|
|
793
|
-
const next = () => {
|
|
794
|
-
const [item, ...restItems] = items;
|
|
795
|
-
if (item !== void 0) {
|
|
796
|
-
items = restItems;
|
|
797
|
-
return Promise.resolve({
|
|
798
|
-
done: false,
|
|
799
|
-
value: item
|
|
800
|
-
});
|
|
801
|
-
} else if (isClosed) return Promise.resolve({
|
|
802
|
-
done: true,
|
|
803
|
-
value: void 0
|
|
804
|
-
});
|
|
805
|
-
else return new Promise((resolve) => {
|
|
806
|
-
pendingReads = [...pendingReads, resolve];
|
|
807
|
-
});
|
|
808
|
-
};
|
|
809
|
-
return {
|
|
810
|
-
close: () => {
|
|
811
|
-
isClosed = true;
|
|
812
|
-
const closedReads = pendingReads;
|
|
813
|
-
pendingReads = [];
|
|
814
|
-
for (const closedRead of closedReads) closedRead({
|
|
815
|
-
done: true,
|
|
816
|
-
value: void 0
|
|
817
|
-
});
|
|
818
|
-
},
|
|
819
|
-
iterable: { [Symbol.asyncIterator]: () => ({ next }) },
|
|
820
|
-
push: (item) => {
|
|
821
|
-
const [pendingRead, ...restPendingReads] = pendingReads;
|
|
822
|
-
if (pendingRead) {
|
|
823
|
-
pendingReads = restPendingReads;
|
|
824
|
-
pendingRead({
|
|
825
|
-
done: false,
|
|
826
|
-
value: item
|
|
827
|
-
});
|
|
828
|
-
} else items = [...items, item];
|
|
829
|
-
}
|
|
830
|
-
};
|
|
831
|
-
};
|
|
832
|
-
//#endregion
|
|
833
|
-
//#region src/services/drivers/claudeAgentSdk/toPermissionRequestEvent.ts
|
|
834
|
-
const toPermissionRequestEvent = (toolName, input, options, createdAt) => {
|
|
835
|
-
const requestId = options.requestId ?? crypto.randomUUID();
|
|
836
|
-
return {
|
|
837
|
-
blockedPath: options.blockedPath ?? "",
|
|
838
|
-
createdAt,
|
|
839
|
-
decisionReason: options.decisionReason ?? "",
|
|
840
|
-
hasSuggestions: Boolean(options.suggestions?.length),
|
|
841
|
-
id: requestId,
|
|
842
|
-
input,
|
|
843
|
-
requestId,
|
|
844
|
-
title: options.title ?? options.description ?? "",
|
|
845
|
-
toolName,
|
|
846
|
-
toolUseId: options.toolUseID ?? "",
|
|
847
|
-
type: "PermissionRequest"
|
|
848
|
-
};
|
|
849
|
-
};
|
|
850
|
-
//#endregion
|
|
851
|
-
//#region src/services/drivers/claudeAgentSdk/toPermissionResult.ts
|
|
852
|
-
const toPermissionResult = (input, suggestions, behavior, message) => {
|
|
853
|
-
switch (behavior) {
|
|
854
|
-
case "Allow": return {
|
|
855
|
-
behavior: "allow",
|
|
856
|
-
updatedInput: input
|
|
857
|
-
};
|
|
858
|
-
case "AllowAlways": return {
|
|
859
|
-
behavior: "allow",
|
|
860
|
-
updatedInput: input,
|
|
861
|
-
updatedPermissions: suggestions
|
|
862
|
-
};
|
|
863
|
-
case "Deny": return {
|
|
864
|
-
behavior: "deny",
|
|
865
|
-
message: message || "The user denied this tool call."
|
|
866
|
-
};
|
|
867
|
-
default: return exhaustiveGuard(behavior);
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
//#endregion
|
|
871
|
-
//#region src/services/drivers/claudeAgentSdk/createPermissionBridge.ts
|
|
872
|
-
const createPermissionBridge = (sessionId, pendingPermissionMap, emit) => (toolName, input, options) => new Promise((resolve) => {
|
|
873
|
-
const requestEvent = toPermissionRequestEvent(toolName, input, options, /* @__PURE__ */ new Date());
|
|
874
|
-
const { requestId } = requestEvent;
|
|
875
|
-
const resolutionId = getEventId(requestId, "PermissionResolution");
|
|
876
|
-
pendingPermissionMap.set(requestId, { settle: (behavior, message) => {
|
|
877
|
-
if (!pendingPermissionMap.delete(requestId)) return;
|
|
878
|
-
resolve(toPermissionResult(input, options.suggestions ?? [], behavior, message));
|
|
879
|
-
const createdAt = /* @__PURE__ */ new Date();
|
|
880
|
-
emit(sessionId, [{
|
|
881
|
-
behavior,
|
|
882
|
-
createdAt,
|
|
883
|
-
id: resolutionId,
|
|
884
|
-
requestId,
|
|
885
|
-
type: "PermissionResolution"
|
|
886
|
-
}, {
|
|
887
|
-
createdAt,
|
|
888
|
-
id: getEventId(resolutionId, "SessionState"),
|
|
889
|
-
state: "Running",
|
|
890
|
-
type: "SessionState"
|
|
891
|
-
}]);
|
|
892
|
-
} });
|
|
893
|
-
options.signal.addEventListener("abort", () => {
|
|
894
|
-
pendingPermissionMap.get(requestId)?.settle("Deny", "");
|
|
895
|
-
}, { once: true });
|
|
896
|
-
emit(sessionId, [requestEvent, {
|
|
897
|
-
createdAt: requestEvent.createdAt,
|
|
898
|
-
id: getEventId(requestId, "SessionState"),
|
|
899
|
-
state: "RequiresAction",
|
|
900
|
-
type: "SessionState"
|
|
901
|
-
}]);
|
|
902
|
-
});
|
|
903
|
-
//#endregion
|
|
904
|
-
//#region src/services/drivers/claudeAgentSdk/createFileOriginTracker.ts
|
|
905
|
-
const fileEditResultSchema = z.object({
|
|
906
|
-
filePath: z.string().min(1),
|
|
907
|
-
originalFile: z.string().nullable(),
|
|
908
|
-
type: z.enum(["create", "update"]).optional()
|
|
909
|
-
});
|
|
910
|
-
const createFileOriginTracker = () => {
|
|
911
|
-
const changedFilePaths = /* @__PURE__ */ new Set();
|
|
912
|
-
return { readToolResult: (event, toolUseResult) => {
|
|
913
|
-
const fileEditResult = fileEditResultSchema.safeParse(toolUseResult);
|
|
914
|
-
if (event.isError || !fileEditResult.success) return event;
|
|
915
|
-
const { filePath, originalFile, type } = fileEditResult.data;
|
|
916
|
-
if (changedFilePaths.has(filePath)) return event;
|
|
917
|
-
changedFilePaths.add(filePath);
|
|
918
|
-
const originalText = originalFile ?? (type === "create" ? "" : void 0);
|
|
919
|
-
return originalText === void 0 ? event : {
|
|
920
|
-
...event,
|
|
921
|
-
originalText
|
|
922
|
-
};
|
|
923
|
-
} };
|
|
924
|
-
};
|
|
925
|
-
//#endregion
|
|
926
|
-
//#region src/services/drivers/claudeAgentSdk/createStreamTracker.ts
|
|
927
|
-
const toTurnUsageEvent = (id, createdAt, outputTokens) => ({
|
|
928
|
-
createdAt,
|
|
929
|
-
id,
|
|
930
|
-
outputTokens,
|
|
931
|
-
type: "TurnUsage"
|
|
932
|
-
});
|
|
933
|
-
const createStreamTracker = () => {
|
|
934
|
-
let messageId = "";
|
|
935
|
-
let finishedOutputTokens = 0;
|
|
936
|
-
return {
|
|
937
|
-
readStreamEvent: ({ event, parent_tool_use_id, uuid }, createdAt) => {
|
|
938
|
-
if (parent_tool_use_id) return [];
|
|
939
|
-
switch (event.type) {
|
|
940
|
-
case "content_block_delta":
|
|
941
|
-
if (event.delta.type !== "text_delta" && event.delta.type !== "thinking_delta") return [];
|
|
942
|
-
return [{
|
|
943
|
-
blockId: getEventId(messageId, event.index),
|
|
944
|
-
createdAt,
|
|
945
|
-
id: uuid,
|
|
946
|
-
isThinking: event.delta.type === "thinking_delta",
|
|
947
|
-
text: event.delta.type === "text_delta" ? event.delta.text : event.delta.thinking,
|
|
948
|
-
type: "StreamDelta"
|
|
949
|
-
}];
|
|
950
|
-
case "message_delta":
|
|
951
|
-
finishedOutputTokens += event.usage.output_tokens;
|
|
952
|
-
return [toTurnUsageEvent(uuid, createdAt, finishedOutputTokens)];
|
|
953
|
-
case "message_start":
|
|
954
|
-
({id: messageId} = event.message);
|
|
955
|
-
return [];
|
|
956
|
-
default: return [];
|
|
957
|
-
}
|
|
958
|
-
},
|
|
959
|
-
readThinkingTokens: ({ estimated_tokens, uuid }, createdAt) => [toTurnUsageEvent(uuid, createdAt, finishedOutputTokens + estimated_tokens)],
|
|
960
|
-
reset: () => {
|
|
961
|
-
finishedOutputTokens = 0;
|
|
962
|
-
}
|
|
963
|
-
};
|
|
964
|
-
};
|
|
965
|
-
//#endregion
|
|
966
|
-
//#region src/models/claudeAgentSdk/TodoToolName.ts
|
|
967
|
-
let TodoToolName = /* @__PURE__ */ function(TodoToolName) {
|
|
968
|
-
TodoToolName["TaskCreate"] = "TaskCreate";
|
|
969
|
-
TodoToolName["TaskUpdate"] = "TaskUpdate";
|
|
970
|
-
TodoToolName["TodoWrite"] = "TodoWrite";
|
|
971
|
-
return TodoToolName;
|
|
972
|
-
}({});
|
|
973
|
-
//#endregion
|
|
974
|
-
//#region src/services/drivers/claudeAgentSdk/createTodoTracker.ts
|
|
975
|
-
const todoWriteInputSchema = z.object({ todos: z.object({
|
|
976
|
-
activeForm: z.string(),
|
|
977
|
-
content: z.string(),
|
|
978
|
-
status: todoStatusSchema
|
|
979
|
-
}).array() });
|
|
980
|
-
const taskCreateInputSchema = z.object({
|
|
981
|
-
activeForm: z.string().optional(),
|
|
982
|
-
subject: z.string()
|
|
983
|
-
});
|
|
984
|
-
const taskCreateResultSchema = z.object({ task: z.object({ id: z.string().min(1) }) });
|
|
985
|
-
const taskUpdateInputSchema = z.object({
|
|
986
|
-
activeForm: z.string().optional(),
|
|
987
|
-
status: z.union([todoStatusSchema, z.literal("deleted")]).optional(),
|
|
988
|
-
subject: z.string().optional(),
|
|
989
|
-
taskId: z.string().min(1)
|
|
990
|
-
});
|
|
991
|
-
const createTodoTracker = () => {
|
|
992
|
-
let todos = [];
|
|
993
|
-
const pendingTodoMap = /* @__PURE__ */ new Map();
|
|
994
|
-
const toTodoUpdateEvent = (sourceId, createdAt) => ({
|
|
995
|
-
createdAt,
|
|
996
|
-
id: getEventId(sourceId, "TodoUpdate"),
|
|
997
|
-
todos,
|
|
998
|
-
type: "TodoUpdate"
|
|
999
|
-
});
|
|
1000
|
-
return {
|
|
1001
|
-
readToolResult: (event, toolUseResult) => {
|
|
1002
|
-
const pendingTodo = pendingTodoMap.get(event.toolUseId);
|
|
1003
|
-
if (!pendingTodo) return void 0;
|
|
1004
|
-
pendingTodoMap.delete(event.toolUseId);
|
|
1005
|
-
const taskCreateResult = taskCreateResultSchema.safeParse(toolUseResult);
|
|
1006
|
-
if (!taskCreateResult.success || event.isError) return void 0;
|
|
1007
|
-
todos = [...todos, {
|
|
1008
|
-
...pendingTodo,
|
|
1009
|
-
id: taskCreateResult.data.task.id
|
|
1010
|
-
}];
|
|
1011
|
-
return toTodoUpdateEvent(event.id, event.createdAt);
|
|
1012
|
-
},
|
|
1013
|
-
readToolUse: (event) => {
|
|
1014
|
-
if (event.parentToolUseId) return void 0;
|
|
1015
|
-
switch (event.name) {
|
|
1016
|
-
case "TaskCreate": {
|
|
1017
|
-
const input = taskCreateInputSchema.safeParse(event.input);
|
|
1018
|
-
if (!input.success) return void 0;
|
|
1019
|
-
const { activeForm, subject } = input.data;
|
|
1020
|
-
pendingTodoMap.set(event.toolUseId, {
|
|
1021
|
-
activeForm: activeForm ?? subject,
|
|
1022
|
-
content: subject,
|
|
1023
|
-
status: "pending"
|
|
1024
|
-
});
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
case "TaskUpdate": {
|
|
1028
|
-
const input = taskUpdateInputSchema.safeParse(event.input);
|
|
1029
|
-
if (!input.success) return void 0;
|
|
1030
|
-
const { activeForm, status, subject, taskId } = input.data;
|
|
1031
|
-
todos = status === "deleted" ? todos.filter(({ id }) => id !== taskId) : todos.map((todo) => todo.id === taskId ? {
|
|
1032
|
-
activeForm: activeForm ?? todo.activeForm,
|
|
1033
|
-
content: subject ?? todo.content,
|
|
1034
|
-
id: todo.id,
|
|
1035
|
-
status: status ?? todo.status
|
|
1036
|
-
} : todo);
|
|
1037
|
-
return toTodoUpdateEvent(event.id, event.createdAt);
|
|
1038
|
-
}
|
|
1039
|
-
case "TodoWrite": {
|
|
1040
|
-
const input = todoWriteInputSchema.safeParse(event.input);
|
|
1041
|
-
if (!input.success) return void 0;
|
|
1042
|
-
todos = input.data.todos.map(({ activeForm, content, status }, index) => ({
|
|
1043
|
-
activeForm,
|
|
1044
|
-
content,
|
|
1045
|
-
id: String(index),
|
|
1046
|
-
status
|
|
1047
|
-
}));
|
|
1048
|
-
return toTodoUpdateEvent(event.id, event.createdAt);
|
|
1049
|
-
}
|
|
1050
|
-
default: return;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
};
|
|
1055
|
-
//#endregion
|
|
1056
|
-
//#region src/services/drivers/claudeAgentSdk/toUnknownEvent.ts
|
|
1057
|
-
const toUnknownEvent = (id, sdkType, raw, createdAt) => ({
|
|
1058
|
-
createdAt,
|
|
1059
|
-
id,
|
|
1060
|
-
raw,
|
|
1061
|
-
sdkType,
|
|
1062
|
-
type: "Unknown"
|
|
1063
|
-
});
|
|
1064
|
-
//#endregion
|
|
1065
|
-
//#region src/services/drivers/claudeAgentSdk/mapAssistantContent.ts
|
|
1066
|
-
const mapAssistantContent = (content, context) => content.map((block, index) => {
|
|
1067
|
-
const id = getEventId(context.messageUuid, index);
|
|
1068
|
-
switch (block.type) {
|
|
1069
|
-
case "document":
|
|
1070
|
-
case "image":
|
|
1071
|
-
case "tool_result": return toUnknownEvent(id, block.type, JSON.stringify(block), context.createdAt);
|
|
1072
|
-
case "other": return toUnknownEvent(id, block.blockType, block.raw, context.createdAt);
|
|
1073
|
-
case "text": return {
|
|
1074
|
-
...context,
|
|
1075
|
-
id,
|
|
1076
|
-
text: block.text,
|
|
1077
|
-
type: "AssistantMessage"
|
|
1078
|
-
};
|
|
1079
|
-
case "thinking": return {
|
|
1080
|
-
...context,
|
|
1081
|
-
id,
|
|
1082
|
-
thinking: block.thinking,
|
|
1083
|
-
type: "Thinking"
|
|
1084
|
-
};
|
|
1085
|
-
case "tool_use": return {
|
|
1086
|
-
...context,
|
|
1087
|
-
id,
|
|
1088
|
-
input: block.input,
|
|
1089
|
-
name: block.name,
|
|
1090
|
-
toolUseId: block.id,
|
|
1091
|
-
type: "ToolUse"
|
|
1092
|
-
};
|
|
1093
|
-
default: return exhaustiveGuard(block);
|
|
1094
|
-
}
|
|
1095
|
-
});
|
|
1096
|
-
//#endregion
|
|
1097
|
-
//#region src/services/drivers/claudeAgentSdk/mapHookMessage.ts
|
|
1098
|
-
const HookPhaseMap = {
|
|
1099
|
-
hook_progress: "Progress",
|
|
1100
|
-
hook_response: "Response",
|
|
1101
|
-
hook_started: "Started"
|
|
1102
|
-
};
|
|
1103
|
-
const mapHookMessage = (message, createdAt) => ({
|
|
1104
|
-
createdAt,
|
|
1105
|
-
exitCode: "exit_code" in message ? message.exit_code ?? 0 : 0,
|
|
1106
|
-
hookEvent: message.hook_event,
|
|
1107
|
-
hookId: message.hook_id,
|
|
1108
|
-
hookName: message.hook_name,
|
|
1109
|
-
id: message.uuid,
|
|
1110
|
-
outcome: "outcome" in message ? message.outcome : "",
|
|
1111
|
-
output: "output" in message ? message.output : "",
|
|
1112
|
-
phase: HookPhaseMap[message.subtype],
|
|
1113
|
-
stderr: "stderr" in message ? message.stderr : "",
|
|
1114
|
-
stdout: "stdout" in message ? message.stdout : "",
|
|
1115
|
-
type: "Hook"
|
|
1116
|
-
});
|
|
1117
|
-
//#endregion
|
|
1118
|
-
//#region src/services/drivers/claudeAgentSdk/mapTaskMessage.ts
|
|
1119
|
-
const SubagentStatusMap = {
|
|
1120
|
-
completed: "Completed",
|
|
1121
|
-
failed: "Failed",
|
|
1122
|
-
killed: "Stopped",
|
|
1123
|
-
paused: "Running",
|
|
1124
|
-
pending: "Started",
|
|
1125
|
-
running: "Running",
|
|
1126
|
-
stopped: "Stopped"
|
|
1127
|
-
};
|
|
1128
|
-
const mapTaskMessage = (message, createdAt) => {
|
|
1129
|
-
const base = {
|
|
1130
|
-
createdAt,
|
|
1131
|
-
description: "",
|
|
1132
|
-
durationMs: 0,
|
|
1133
|
-
id: message.uuid,
|
|
1134
|
-
lastToolName: "",
|
|
1135
|
-
subagentType: "",
|
|
1136
|
-
summary: "",
|
|
1137
|
-
taskId: message.task_id,
|
|
1138
|
-
toolUseId: "tool_use_id" in message ? message.tool_use_id ?? "" : "",
|
|
1139
|
-
toolUses: 0,
|
|
1140
|
-
totalTokens: 0,
|
|
1141
|
-
type: "Subagent"
|
|
1142
|
-
};
|
|
1143
|
-
switch (message.subtype) {
|
|
1144
|
-
case "task_notification": return {
|
|
1145
|
-
...base,
|
|
1146
|
-
durationMs: message.usage?.duration_ms ?? 0,
|
|
1147
|
-
status: SubagentStatusMap[message.status],
|
|
1148
|
-
summary: message.summary,
|
|
1149
|
-
toolUses: message.usage?.tool_uses ?? 0,
|
|
1150
|
-
totalTokens: message.usage?.total_tokens ?? 0
|
|
1151
|
-
};
|
|
1152
|
-
case "task_progress": return {
|
|
1153
|
-
...base,
|
|
1154
|
-
description: message.description,
|
|
1155
|
-
durationMs: message.usage.duration_ms,
|
|
1156
|
-
lastToolName: message.last_tool_name ?? "",
|
|
1157
|
-
status: "Running",
|
|
1158
|
-
subagentType: message.subagent_type ?? "",
|
|
1159
|
-
summary: message.summary ?? "",
|
|
1160
|
-
toolUses: message.usage.tool_uses,
|
|
1161
|
-
totalTokens: message.usage.total_tokens
|
|
1162
|
-
};
|
|
1163
|
-
case "task_started": return {
|
|
1164
|
-
...base,
|
|
1165
|
-
description: message.description,
|
|
1166
|
-
status: "Started",
|
|
1167
|
-
subagentType: message.subagent_type ?? ""
|
|
1168
|
-
};
|
|
1169
|
-
case "task_updated": return {
|
|
1170
|
-
...base,
|
|
1171
|
-
description: message.patch.description ?? "",
|
|
1172
|
-
status: message.patch.status ? SubagentStatusMap[message.patch.status] : "Running",
|
|
1173
|
-
summary: message.patch.error ?? ""
|
|
1174
|
-
};
|
|
1175
|
-
default: return exhaustiveGuard(message);
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
//#endregion
|
|
1179
|
-
//#region src/services/drivers/claudeAgentSdk/mapSystemMessage.ts
|
|
1180
|
-
const SessionStateMap = {
|
|
1181
|
-
idle: "Idle",
|
|
1182
|
-
requires_action: "RequiresAction",
|
|
1183
|
-
running: "Running"
|
|
1184
|
-
};
|
|
1185
|
-
const mapSystemMessage = (message, createdAt) => {
|
|
1186
|
-
switch (message.subtype) {
|
|
1187
|
-
case "compact_boundary": return [{
|
|
1188
|
-
createdAt,
|
|
1189
|
-
id: message.uuid,
|
|
1190
|
-
postTokens: message.compact_metadata.post_tokens ?? 0,
|
|
1191
|
-
preTokens: message.compact_metadata.pre_tokens,
|
|
1192
|
-
trigger: message.compact_metadata.trigger === "auto" ? "auto" : "manual",
|
|
1193
|
-
type: "Compaction"
|
|
1194
|
-
}];
|
|
1195
|
-
case "hook_progress":
|
|
1196
|
-
case "hook_response":
|
|
1197
|
-
case "hook_started": return [mapHookMessage(message, createdAt)];
|
|
1198
|
-
case "local_command_output": return [{
|
|
1199
|
-
content: message.content,
|
|
1200
|
-
createdAt,
|
|
1201
|
-
id: message.uuid,
|
|
1202
|
-
type: "CommandOutput"
|
|
1203
|
-
}];
|
|
1204
|
-
case "session_state_changed": return [{
|
|
1205
|
-
createdAt,
|
|
1206
|
-
id: message.uuid,
|
|
1207
|
-
state: SessionStateMap[message.state],
|
|
1208
|
-
type: "SessionState"
|
|
1209
|
-
}];
|
|
1210
|
-
case "task_notification":
|
|
1211
|
-
case "task_progress":
|
|
1212
|
-
case "task_started":
|
|
1213
|
-
case "task_updated": return [mapTaskMessage(message, createdAt)];
|
|
1214
|
-
default: return [toUnknownEvent(message.uuid, `system:${message.subtype}`, JSON.stringify(message), createdAt)];
|
|
1215
|
-
}
|
|
1216
|
-
};
|
|
1217
|
-
//#endregion
|
|
1218
|
-
//#region src/services/drivers/claudeAgentSdk/toToolResultText.ts
|
|
1219
|
-
const toToolResultText = (content) => typeof content === "string" ? content : content.map((part) => part.text === void 0 ? `[${part.type}]` : part.text).join("\n");
|
|
1220
|
-
//#endregion
|
|
1221
|
-
//#region src/services/drivers/claudeAgentSdk/mapUserContent.ts
|
|
1222
|
-
const mapUserContent = (content, context) => {
|
|
1223
|
-
if (typeof content === "string") return [{
|
|
1224
|
-
...context,
|
|
1225
|
-
attachmentCount: 0,
|
|
1226
|
-
id: context.messageUuid,
|
|
1227
|
-
text: content,
|
|
1228
|
-
type: "UserMessage"
|
|
1229
|
-
}];
|
|
1230
|
-
const events = [];
|
|
1231
|
-
const texts = [];
|
|
1232
|
-
let attachmentCount = 0;
|
|
1233
|
-
for (const [index, block] of content.entries()) {
|
|
1234
|
-
const id = getEventId(context.messageUuid, index);
|
|
1235
|
-
switch (block.type) {
|
|
1236
|
-
case "document":
|
|
1237
|
-
case "image":
|
|
1238
|
-
attachmentCount++;
|
|
1239
|
-
break;
|
|
1240
|
-
case "other":
|
|
1241
|
-
events.push(toUnknownEvent(id, block.blockType, block.raw, context.createdAt));
|
|
1242
|
-
break;
|
|
1243
|
-
case "text":
|
|
1244
|
-
texts.push(block.text);
|
|
1245
|
-
break;
|
|
1246
|
-
case "thinking":
|
|
1247
|
-
case "tool_use":
|
|
1248
|
-
events.push(toUnknownEvent(id, block.type, JSON.stringify(block), context.createdAt));
|
|
1249
|
-
break;
|
|
1250
|
-
case "tool_result":
|
|
1251
|
-
events.push({
|
|
1252
|
-
content: toToolResultText(block.content),
|
|
1253
|
-
createdAt: context.createdAt,
|
|
1254
|
-
id,
|
|
1255
|
-
isError: Boolean(block.is_error),
|
|
1256
|
-
parentToolUseId: context.parentToolUseId,
|
|
1257
|
-
toolUseId: block.tool_use_id,
|
|
1258
|
-
type: "ToolResult"
|
|
1259
|
-
});
|
|
1260
|
-
break;
|
|
1261
|
-
default: exhaustiveGuard(block);
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
return texts.length > 0 || attachmentCount > 0 ? [{
|
|
1265
|
-
...context,
|
|
1266
|
-
attachmentCount,
|
|
1267
|
-
id: context.messageUuid,
|
|
1268
|
-
text: texts.join("\n\n"),
|
|
1269
|
-
type: "UserMessage"
|
|
1270
|
-
}, ...events] : events;
|
|
1271
|
-
};
|
|
1272
|
-
//#endregion
|
|
1273
|
-
//#region src/services/drivers/claudeAgentSdk/parseMessageBody.ts
|
|
1274
|
-
const documentBlockSchema = z.object({ type: z.literal("document") });
|
|
1275
|
-
const imageBlockSchema = z.object({ type: z.literal("image") });
|
|
1276
|
-
const textBlockSchema = z.object({
|
|
1277
|
-
text: z.string(),
|
|
1278
|
-
type: z.literal("text")
|
|
1279
|
-
});
|
|
1280
|
-
const thinkingBlockSchema = z.object({
|
|
1281
|
-
thinking: z.string(),
|
|
1282
|
-
type: z.literal("thinking")
|
|
1283
|
-
});
|
|
1284
|
-
const toolResultBlockSchema = z.object({
|
|
1285
|
-
content: z.union([z.string(), z.looseObject({
|
|
1286
|
-
text: z.string().optional(),
|
|
1287
|
-
type: z.string()
|
|
1288
|
-
}).array()]),
|
|
1289
|
-
is_error: z.boolean().optional(),
|
|
1290
|
-
tool_use_id: z.string().min(1),
|
|
1291
|
-
type: z.literal("tool_result")
|
|
1292
|
-
});
|
|
1293
|
-
const toolUseBlockSchema = z.object({
|
|
1294
|
-
id: z.string().min(1),
|
|
1295
|
-
input: z.record(z.string(), z.unknown()),
|
|
1296
|
-
name: z.string().min(1),
|
|
1297
|
-
type: z.literal("tool_use")
|
|
1298
|
-
});
|
|
1299
|
-
const otherBlockSchema = z.looseObject({ type: z.string() }).transform((block) => ({
|
|
1300
|
-
blockType: block.type,
|
|
1301
|
-
raw: JSON.stringify(block),
|
|
1302
|
-
type: "other"
|
|
1303
|
-
}));
|
|
1304
|
-
const contentBlockSchema = z.union([
|
|
1305
|
-
documentBlockSchema,
|
|
1306
|
-
imageBlockSchema,
|
|
1307
|
-
textBlockSchema,
|
|
1308
|
-
thinkingBlockSchema,
|
|
1309
|
-
toolResultBlockSchema,
|
|
1310
|
-
toolUseBlockSchema,
|
|
1311
|
-
otherBlockSchema
|
|
1312
|
-
]);
|
|
1313
|
-
const messageBodySchema = z.looseObject({ content: z.union([contentBlockSchema.array(), z.string()]) });
|
|
1314
|
-
const parseMessageBody = (value) => {
|
|
1315
|
-
const result = messageBodySchema.safeParse(value);
|
|
1316
|
-
return result.success ? result.data : void 0;
|
|
1317
|
-
};
|
|
1318
|
-
//#endregion
|
|
1319
|
-
//#region src/services/drivers/claudeAgentSdk/toSessionInitEvent.ts
|
|
1320
|
-
const toSessionInitEvent = (message, permissionMode, createdAt) => ({
|
|
1321
|
-
agents: [...new Set(message.agents)],
|
|
1322
|
-
claudeCodeVersion: message.claude_code_version,
|
|
1323
|
-
createdAt,
|
|
1324
|
-
cwd: message.cwd,
|
|
1325
|
-
id: message.uuid,
|
|
1326
|
-
mcpServers: [...new Map(message.mcp_servers.map(({ name, status }) => [name, {
|
|
1327
|
-
name,
|
|
1328
|
-
status
|
|
1329
|
-
}])).values()],
|
|
1330
|
-
model: message.model,
|
|
1331
|
-
outputStyle: message.output_style,
|
|
1332
|
-
permissionMode,
|
|
1333
|
-
plugins: [...new Set(message.plugins.map(({ name }) => name))],
|
|
1334
|
-
skills: [...new Set(message.skills)],
|
|
1335
|
-
slashCommands: [...new Set(message.slash_commands)],
|
|
1336
|
-
tools: [...new Set(message.tools)],
|
|
1337
|
-
type: "SessionInit"
|
|
1338
|
-
});
|
|
1339
|
-
//#endregion
|
|
1340
|
-
//#region src/services/drivers/claudeAgentSdk/toTurnResultEvents.ts
|
|
1341
|
-
const toTurnResultEvents = (message, createdAt) => [{
|
|
1342
|
-
createdAt,
|
|
1343
|
-
durationApiMs: message.duration_api_ms,
|
|
1344
|
-
durationMs: message.duration_ms,
|
|
1345
|
-
errors: message.subtype === "success" ? [] : message.errors,
|
|
1346
|
-
id: message.uuid,
|
|
1347
|
-
isError: message.is_error,
|
|
1348
|
-
numTurns: message.num_turns,
|
|
1349
|
-
result: message.subtype === "success" ? message.result : "",
|
|
1350
|
-
subtype: message.subtype,
|
|
1351
|
-
totalCostUsd: message.total_cost_usd,
|
|
1352
|
-
type: "TurnResult",
|
|
1353
|
-
usage: {
|
|
1354
|
-
cacheCreationInputTokens: message.usage.cache_creation_input_tokens,
|
|
1355
|
-
cacheReadInputTokens: message.usage.cache_read_input_tokens,
|
|
1356
|
-
inputTokens: message.usage.input_tokens,
|
|
1357
|
-
outputTokens: message.usage.output_tokens
|
|
1358
|
-
}
|
|
1359
|
-
}, {
|
|
1360
|
-
createdAt,
|
|
1361
|
-
id: getEventId(message.uuid, "SessionState"),
|
|
1362
|
-
state: "Idle",
|
|
1363
|
-
type: "SessionState"
|
|
1364
|
-
}];
|
|
1365
|
-
//#endregion
|
|
1366
|
-
//#region src/services/drivers/claudeAgentSdk/createSdkMessageMapper.ts
|
|
1367
|
-
const createSdkMessageMapper = () => {
|
|
1368
|
-
const fileOriginTracker = createFileOriginTracker();
|
|
1369
|
-
const streamTracker = createStreamTracker();
|
|
1370
|
-
const todoTracker = createTodoTracker();
|
|
1371
|
-
const settings = {
|
|
1372
|
-
model: "",
|
|
1373
|
-
permissionMode: "default"
|
|
1374
|
-
};
|
|
1375
|
-
let isCompacting = false;
|
|
1376
|
-
const updateSettings = (id, createdAt, { model, permissionMode }) => {
|
|
1377
|
-
if (model) settings.model = model;
|
|
1378
|
-
const parsedPermissionMode = permissionModeSchema.safeParse(permissionMode);
|
|
1379
|
-
if (parsedPermissionMode.success) settings.permissionMode = parsedPermissionMode.data;
|
|
1380
|
-
return {
|
|
1381
|
-
createdAt,
|
|
1382
|
-
id,
|
|
1383
|
-
...settings,
|
|
1384
|
-
type: "SessionSettings"
|
|
1385
|
-
};
|
|
1386
|
-
};
|
|
1387
|
-
const mapContent = ({ body, error, messageType, messageUuid, parentToolUseId, toolUseResult }, createdAt) => {
|
|
1388
|
-
const messageBody = parseMessageBody(body);
|
|
1389
|
-
if (!messageBody) return [toUnknownEvent(messageUuid, messageType, JSON.stringify(body), createdAt)];
|
|
1390
|
-
const context = {
|
|
1391
|
-
createdAt,
|
|
1392
|
-
messageUuid,
|
|
1393
|
-
parentToolUseId
|
|
1394
|
-
};
|
|
1395
|
-
const { content } = messageBody;
|
|
1396
|
-
const contentEvents = messageType === "assistant" ? mapAssistantContent(typeof content === "string" ? [{
|
|
1397
|
-
text: content,
|
|
1398
|
-
type: "text"
|
|
1399
|
-
}] : content, context) : mapUserContent(content, context);
|
|
1400
|
-
const events = [];
|
|
1401
|
-
for (const event of contentEvents) if (event.type === "ToolUse") {
|
|
1402
|
-
events.push(event);
|
|
1403
|
-
const todoUpdateEvent = todoTracker.readToolUse(event);
|
|
1404
|
-
if (todoUpdateEvent) events.push(todoUpdateEvent);
|
|
1405
|
-
} else if (event.type === "ToolResult") {
|
|
1406
|
-
events.push(fileOriginTracker.readToolResult(event, toolUseResult));
|
|
1407
|
-
const todoUpdateEvent = todoTracker.readToolResult(event, toolUseResult);
|
|
1408
|
-
if (todoUpdateEvent) events.push(todoUpdateEvent);
|
|
1409
|
-
} else events.push(event);
|
|
1410
|
-
if (error) events.push({
|
|
1411
|
-
createdAt,
|
|
1412
|
-
id: getEventId(messageUuid, "HostError"),
|
|
1413
|
-
message: error,
|
|
1414
|
-
type: "HostError"
|
|
1415
|
-
});
|
|
1416
|
-
return events;
|
|
1417
|
-
};
|
|
1418
|
-
const mapMessage = (message, createdAt) => {
|
|
1419
|
-
const sdkType = message.type;
|
|
1420
|
-
if (IGNORED_SDK_MESSAGE_TYPES.includes(sdkType)) return [];
|
|
1421
|
-
switch (message.type) {
|
|
1422
|
-
case "assistant": return mapContent({
|
|
1423
|
-
body: message.message,
|
|
1424
|
-
error: message.error ?? "",
|
|
1425
|
-
messageType: message.type,
|
|
1426
|
-
messageUuid: message.uuid,
|
|
1427
|
-
parentToolUseId: message.parent_tool_use_id ?? "",
|
|
1428
|
-
toolUseResult: void 0
|
|
1429
|
-
}, createdAt);
|
|
1430
|
-
case "rate_limit_event": {
|
|
1431
|
-
const { rateLimitType, resetsAt, status, utilization } = message.rate_limit_info;
|
|
1432
|
-
return [{
|
|
1433
|
-
createdAt,
|
|
1434
|
-
id: message.uuid,
|
|
1435
|
-
rateLimitType: rateLimitType ?? "",
|
|
1436
|
-
resetsAt: resetsAt === void 0 ? void 0 : new Date(Temporal.Duration.from({ seconds: resetsAt }).total("milliseconds")),
|
|
1437
|
-
status,
|
|
1438
|
-
type: "RateLimit",
|
|
1439
|
-
utilization
|
|
1440
|
-
}];
|
|
1441
|
-
}
|
|
1442
|
-
case "result":
|
|
1443
|
-
streamTracker.reset();
|
|
1444
|
-
return toTurnResultEvents(message, createdAt);
|
|
1445
|
-
case "stream_event": return streamTracker.readStreamEvent(message, createdAt);
|
|
1446
|
-
case "tool_progress": return [{
|
|
1447
|
-
createdAt,
|
|
1448
|
-
elapsedSeconds: message.elapsed_time_seconds,
|
|
1449
|
-
id: message.uuid,
|
|
1450
|
-
parentToolUseId: message.parent_tool_use_id ?? "",
|
|
1451
|
-
toolUseId: message.tool_use_id,
|
|
1452
|
-
type: "ToolProgress"
|
|
1453
|
-
}];
|
|
1454
|
-
case "user": return mapContent({
|
|
1455
|
-
body: message.message,
|
|
1456
|
-
error: "",
|
|
1457
|
-
messageType: message.type,
|
|
1458
|
-
messageUuid: message.uuid ?? crypto.randomUUID(),
|
|
1459
|
-
parentToolUseId: message.parent_tool_use_id ?? "",
|
|
1460
|
-
toolUseResult: message.tool_use_result
|
|
1461
|
-
}, createdAt);
|
|
1462
|
-
case "system": if (message.subtype === "init") {
|
|
1463
|
-
const settingsEvent = updateSettings(getEventId(message.uuid, "SessionSettings"), createdAt, {
|
|
1464
|
-
model: message.model,
|
|
1465
|
-
permissionMode: message.permissionMode
|
|
1466
|
-
});
|
|
1467
|
-
return [toSessionInitEvent(message, settingsEvent.permissionMode, createdAt), settingsEvent];
|
|
1468
|
-
} else if (message.subtype === "status") {
|
|
1469
|
-
const wasCompacting = isCompacting;
|
|
1470
|
-
isCompacting = message.status === "compacting";
|
|
1471
|
-
const events = [];
|
|
1472
|
-
if (isCompacting !== wasCompacting) events.push({
|
|
1473
|
-
createdAt,
|
|
1474
|
-
id: message.uuid,
|
|
1475
|
-
state: isCompacting ? "Compacting" : "Running",
|
|
1476
|
-
type: "SessionState"
|
|
1477
|
-
});
|
|
1478
|
-
if (message.permissionMode) events.push(updateSettings(getEventId(message.uuid, "SessionSettings"), createdAt, { permissionMode: message.permissionMode }));
|
|
1479
|
-
return events;
|
|
1480
|
-
} else if (message.subtype === "thinking_tokens") return streamTracker.readThinkingTokens(message, createdAt);
|
|
1481
|
-
else return mapSystemMessage(message, createdAt);
|
|
1482
|
-
default: return [toUnknownEvent(message.uuid, message.type, JSON.stringify(message), createdAt)];
|
|
1483
|
-
}
|
|
1484
|
-
};
|
|
1485
|
-
return {
|
|
1486
|
-
mapHistory: ({ message, parent_tool_use_id, type, uuid }, createdAt, toolUseResult) => type === "system" ? [toUnknownEvent(uuid, type, JSON.stringify(message), createdAt)] : mapContent({
|
|
1487
|
-
body: message,
|
|
1488
|
-
error: "",
|
|
1489
|
-
messageType: type,
|
|
1490
|
-
messageUuid: uuid,
|
|
1491
|
-
parentToolUseId: parent_tool_use_id ?? "",
|
|
1492
|
-
toolUseResult
|
|
1493
|
-
}, createdAt),
|
|
1494
|
-
mapMessage,
|
|
1495
|
-
updateSettings
|
|
1496
|
-
};
|
|
1497
|
-
};
|
|
1498
|
-
//#endregion
|
|
1499
|
-
//#region src/services/drivers/claudeAgentSdk/readSessionHistory.ts
|
|
1500
|
-
const readSessionHistory = async (sessionId, cwd, resumeAt) => {
|
|
1501
|
-
const sessionMessages = await getSessionMessages(sessionId, { dir: cwd });
|
|
1502
|
-
if (!resumeAt) return sessionMessages;
|
|
1503
|
-
const resumeAtIndex = sessionMessages.findIndex(({ uuid }) => uuid === resumeAt);
|
|
1504
|
-
if (resumeAtIndex === -1) throw new InvalidOperationError(Operation.Read, sessionId, `the transcript holds no message ${resumeAt}`);
|
|
1505
|
-
return sessionMessages.slice(0, resumeAtIndex + 1);
|
|
1506
|
-
};
|
|
1507
|
-
//#endregion
|
|
1508
|
-
//#region src/services/drivers/claudeAgentSdk/readToolUseResultMap.ts
|
|
1509
|
-
const transcriptEntrySchema = z.object({
|
|
1510
|
-
toolUseResult: z.unknown(),
|
|
1511
|
-
uuid: z.string()
|
|
1512
|
-
});
|
|
1513
|
-
const readToolUseResultMap = async (sessionId) => {
|
|
1514
|
-
const toolUseResultMap = /* @__PURE__ */ new Map();
|
|
1515
|
-
const projectsDirectory = join(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude"), "projects");
|
|
1516
|
-
if (!existsSync(projectsDirectory)) return toolUseResultMap;
|
|
1517
|
-
const transcriptPath = (await readdir(projectsDirectory)).map((projectDirectory) => join(projectsDirectory, projectDirectory, `${sessionId}.jsonl`)).find((path) => existsSync(path));
|
|
1518
|
-
if (!transcriptPath) return toolUseResultMap;
|
|
1519
|
-
for (const line of (await readFile(transcriptPath, "utf8")).split("\n")) getResult(() => transcriptEntrySchema.parse(JSON.parse(line))).match(({ toolUseResult, uuid }) => {
|
|
1520
|
-
if (toolUseResult !== void 0) toolUseResultMap.set(uuid, toolUseResult);
|
|
1521
|
-
}, noop);
|
|
1522
|
-
return toolUseResultMap;
|
|
1523
|
-
};
|
|
1524
|
-
//#endregion
|
|
1525
|
-
//#region src/services/drivers/claudeAgentSdk/getSessionTitle.ts
|
|
1526
|
-
const getSessionTitle = ({ customTitle, firstPrompt, summary }) => customTitle || summary || firstPrompt || "";
|
|
1527
|
-
//#endregion
|
|
1528
|
-
//#region src/services/drivers/claudeAgentSdk/toCapabilitiesEvent.ts
|
|
1529
|
-
const toCapabilitiesEvent = (id, slashCommands, models, createdAt) => ({
|
|
1530
|
-
commands: [...new Map(slashCommands.map(({ argumentHint, description, name }) => [name, {
|
|
1531
|
-
argumentHint,
|
|
1532
|
-
description,
|
|
1533
|
-
name
|
|
1534
|
-
}])).values()],
|
|
1535
|
-
createdAt,
|
|
1536
|
-
id,
|
|
1537
|
-
models: [...new Map(models.map(({ description, displayName, value }) => [value, {
|
|
1538
|
-
description,
|
|
1539
|
-
displayName,
|
|
1540
|
-
value
|
|
1541
|
-
}])).values()],
|
|
1542
|
-
type: "Capabilities"
|
|
1543
|
-
});
|
|
1544
|
-
//#endregion
|
|
1545
|
-
//#region src/services/drivers/claudeAgentSdk/toContextUsageEvent.ts
|
|
1546
|
-
const toContextUsageEvent = (id, { autoCompactThreshold, isAutoCompactEnabled, maxTokens, percentage, totalTokens }, createdAt) => ({
|
|
1547
|
-
autoCompactThreshold: autoCompactThreshold ?? 0,
|
|
1548
|
-
createdAt,
|
|
1549
|
-
id,
|
|
1550
|
-
isAutoCompactEnabled,
|
|
1551
|
-
maxTokens,
|
|
1552
|
-
percentage,
|
|
1553
|
-
totalTokens,
|
|
1554
|
-
type: "ContextUsage"
|
|
1555
|
-
});
|
|
1556
|
-
//#endregion
|
|
1557
|
-
//#region src/services/drivers/claudeAgentSdk/watchSession.ts
|
|
1558
|
-
const watchSession = async (sessionId, openSession, { emit, onSessionsChange, openSessionMap }) => {
|
|
1559
|
-
const { cwd, mapper, query } = openSession;
|
|
1560
|
-
const readContextUsage = () => getResultAsync(() => query.getContextUsage()).match((contextUsage) => {
|
|
1561
|
-
emit(sessionId, [toContextUsageEvent(crypto.randomUUID(), contextUsage, /* @__PURE__ */ new Date())]);
|
|
1562
|
-
}, console.error);
|
|
1563
|
-
const readTitle = () => getResultAsync(() => getSessionInfo(sessionId, { dir: cwd })).match((sessionInfo) => {
|
|
1564
|
-
if (!sessionInfo) return;
|
|
1565
|
-
openSession.title = getSessionTitle(sessionInfo) || openSession.title;
|
|
1566
|
-
onSessionsChange();
|
|
1567
|
-
}, console.error);
|
|
1568
|
-
await Promise.all([
|
|
1569
|
-
getResultAsync(() => Promise.all([query.supportedCommands(), query.supportedModels()])).match(([slashCommands, models]) => {
|
|
1570
|
-
emit(sessionId, [toCapabilitiesEvent(crypto.randomUUID(), slashCommands, models, /* @__PURE__ */ new Date())]);
|
|
1571
|
-
}, console.error),
|
|
1572
|
-
readContextUsage(),
|
|
1573
|
-
getResultAsync(async () => {
|
|
1574
|
-
for await (const message of query) {
|
|
1575
|
-
emit(sessionId, mapper.mapMessage(message, /* @__PURE__ */ new Date()));
|
|
1576
|
-
if (message.type === "result") await Promise.all([readContextUsage(), readTitle()]);
|
|
1577
|
-
}
|
|
1578
|
-
}).match(() => {
|
|
1579
|
-
closeOpenSession(sessionId, openSession, "", {
|
|
1580
|
-
emit,
|
|
1581
|
-
onSessionsChange,
|
|
1582
|
-
openSessionMap
|
|
1583
|
-
});
|
|
1584
|
-
}, (error) => {
|
|
1585
|
-
closeOpenSession(sessionId, openSession, error.message, {
|
|
1586
|
-
emit,
|
|
1587
|
-
onSessionsChange,
|
|
1588
|
-
openSessionMap
|
|
1589
|
-
});
|
|
1590
|
-
})
|
|
1591
|
-
]);
|
|
1592
|
-
};
|
|
1593
|
-
//#endregion
|
|
1594
|
-
//#region src/services/installer/getClaudeCodeExecutablePath.ts
|
|
1595
|
-
const getClaudeCodeExecutablePath = () => isSea() ? join(getHostInstallDirectory(), CLAUDE_CODE_EXECUTABLE_FILENAME) : void 0;
|
|
1596
|
-
//#endregion
|
|
1597
|
-
//#region src/services/drivers/claudeAgentSdk/createSessionOpener.ts
|
|
1598
|
-
const createSessionOpener = ({ emit, onSessionOpen, onSessionsChange, openSessionMap, taskRegistry }) => async ({ cwd, isFork, resumeAt, resumeFrom, sessionId }) => {
|
|
1599
|
-
const mapper = createSdkMessageMapper();
|
|
1600
|
-
const history = resumeFrom ? await readSessionHistory(resumeFrom, cwd, resumeAt) : [];
|
|
1601
|
-
const toolUseResultMap = resumeFrom ? await readToolUseResultMap(resumeFrom) : /* @__PURE__ */ new Map();
|
|
1602
|
-
const input = createInputQueue();
|
|
1603
|
-
const pendingPermissionMap = /* @__PURE__ */ new Map();
|
|
1604
|
-
const claudeCodeExecutablePath = getClaudeCodeExecutablePath();
|
|
1605
|
-
const sessionQuery = query({
|
|
1606
|
-
options: {
|
|
1607
|
-
allowDangerouslySkipPermissions: true,
|
|
1608
|
-
canUseTool: createPermissionBridge(sessionId, pendingPermissionMap, emit),
|
|
1609
|
-
cwd,
|
|
1610
|
-
enableFileCheckpointing: true,
|
|
1611
|
-
env: {
|
|
1612
|
-
...process.env,
|
|
1613
|
-
CLAUDE_CODE_ENABLE_TODO_TOOLS: "true"
|
|
1614
|
-
},
|
|
1615
|
-
includeHookEvents: true,
|
|
1616
|
-
includePartialMessages: true,
|
|
1617
|
-
...claudeCodeExecutablePath ? { pathToClaudeCodeExecutable: claudeCodeExecutablePath } : {},
|
|
1618
|
-
settingSources: SETTING_SOURCES,
|
|
1619
|
-
systemPrompt: {
|
|
1620
|
-
preset: "claude_code",
|
|
1621
|
-
type: "preset"
|
|
1622
|
-
},
|
|
1623
|
-
thinking: {
|
|
1624
|
-
display: "summarized",
|
|
1625
|
-
type: "adaptive"
|
|
1626
|
-
},
|
|
1627
|
-
...resumeFrom ? { resume: resumeFrom } : {},
|
|
1628
|
-
...resumeAt ? { resumeSessionAt: resumeAt } : {},
|
|
1629
|
-
...!resumeFrom || isFork ? {
|
|
1630
|
-
forkSession: isFork,
|
|
1631
|
-
sessionId
|
|
1632
|
-
} : {}
|
|
1633
|
-
},
|
|
1634
|
-
prompt: input.iterable
|
|
1635
|
-
});
|
|
1636
|
-
const openSession = {
|
|
1637
|
-
cwd,
|
|
1638
|
-
input,
|
|
1639
|
-
lastActivityAt: /* @__PURE__ */ new Date(),
|
|
1640
|
-
mapper,
|
|
1641
|
-
pendingPermissionMap,
|
|
1642
|
-
query: sessionQuery,
|
|
1643
|
-
state: "Idle",
|
|
1644
|
-
title: ""
|
|
1645
|
-
};
|
|
1646
|
-
onSessionOpen(sessionId);
|
|
1647
|
-
openSessionMap.set(sessionId, openSession);
|
|
1648
|
-
const createdAt = /* @__PURE__ */ new Date();
|
|
1649
|
-
emit(sessionId, [...history.flatMap((sessionMessage) => mapper.mapHistory(sessionMessage, createdAt, toolUseResultMap.get(sessionMessage.uuid))), {
|
|
1650
|
-
createdAt,
|
|
1651
|
-
id: getEventId(crypto.randomUUID(), "SessionState"),
|
|
1652
|
-
state: "Idle",
|
|
1653
|
-
type: "SessionState"
|
|
1654
|
-
}]);
|
|
1655
|
-
onSessionsChange();
|
|
1656
|
-
taskRegistry.run(() => watchSession(sessionId, openSession, {
|
|
1657
|
-
emit,
|
|
1658
|
-
onSessionsChange,
|
|
1659
|
-
openSessionMap
|
|
1660
|
-
}));
|
|
1661
|
-
return sessionId;
|
|
1662
|
-
};
|
|
1663
|
-
//#endregion
|
|
1664
|
-
//#region src/services/drivers/claudeAgentSdk/listSessionSummaries.ts
|
|
1665
|
-
const listSessionSummaries = async (openSessionMap) => {
|
|
1666
|
-
const sessionInfos = await listSessions({ limit: 50 });
|
|
1667
|
-
const savedSessionIds = new Set(sessionInfos.map(({ sessionId }) => sessionId));
|
|
1668
|
-
const unsavedSessions = [...openSessionMap].filter(([id]) => !savedSessionIds.has(id)).map(([id, { cwd, lastActivityAt, state, title }]) => ({
|
|
1669
|
-
cwd,
|
|
1670
|
-
id,
|
|
1671
|
-
lastActivityAt,
|
|
1672
|
-
state,
|
|
1673
|
-
title
|
|
1674
|
-
}));
|
|
1675
|
-
const savedSessions = sessionInfos.map((sessionInfo) => {
|
|
1676
|
-
const openSession = openSessionMap.get(sessionInfo.sessionId);
|
|
1677
|
-
return {
|
|
1678
|
-
cwd: sessionInfo.cwd ?? "",
|
|
1679
|
-
id: sessionInfo.sessionId,
|
|
1680
|
-
lastActivityAt: openSession?.lastActivityAt ?? new Date(sessionInfo.lastModified),
|
|
1681
|
-
state: openSession?.state ?? "Closed",
|
|
1682
|
-
title: openSession?.title || getSessionTitle(sessionInfo)
|
|
1683
|
-
};
|
|
1684
|
-
});
|
|
1685
|
-
return [...unsavedSessions, ...savedSessions];
|
|
1686
|
-
};
|
|
1687
|
-
//#endregion
|
|
1688
|
-
//#region src/services/drivers/claudeAgentSdk/readSessionCwd.ts
|
|
1689
|
-
const readSessionCwd = async (sessionId) => {
|
|
1690
|
-
const sessionInfo = await getSessionInfo(sessionId);
|
|
1691
|
-
if (!sessionInfo?.cwd) throw new InvalidOperationError(Operation.Read, sessionId, "no session with this id on disk");
|
|
1692
|
-
return sessionInfo.cwd;
|
|
1693
|
-
};
|
|
1694
|
-
//#endregion
|
|
1695
|
-
//#region src/services/drivers/claudeAgentSdk/toContentBlockParam.ts
|
|
1696
|
-
const toContentBlockParam = ({ data, mediaType, name }) => {
|
|
1697
|
-
switch (mediaType) {
|
|
1698
|
-
case "image/gif":
|
|
1699
|
-
case "image/jpeg":
|
|
1700
|
-
case "image/png":
|
|
1701
|
-
case "image/webp": return {
|
|
1702
|
-
source: {
|
|
1703
|
-
data,
|
|
1704
|
-
media_type: mediaType,
|
|
1705
|
-
type: "base64"
|
|
1706
|
-
},
|
|
1707
|
-
type: "image"
|
|
1708
|
-
};
|
|
1709
|
-
case "application/pdf": return {
|
|
1710
|
-
source: {
|
|
1711
|
-
data,
|
|
1712
|
-
media_type: mediaType,
|
|
1713
|
-
type: "base64"
|
|
1714
|
-
},
|
|
1715
|
-
title: name,
|
|
1716
|
-
type: "document"
|
|
1717
|
-
};
|
|
1718
|
-
case "text/plain": return {
|
|
1719
|
-
source: {
|
|
1720
|
-
data,
|
|
1721
|
-
media_type: mediaType,
|
|
1722
|
-
type: "text"
|
|
1723
|
-
},
|
|
1724
|
-
title: name,
|
|
1725
|
-
type: "document"
|
|
1726
|
-
};
|
|
1727
|
-
default: return exhaustiveGuard(mediaType);
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
//#endregion
|
|
1731
|
-
//#region src/services/shared/createTaskRegistry.ts
|
|
1732
|
-
const createTaskRegistry = () => {
|
|
1733
|
-
const pendingSet = /* @__PURE__ */ new Set();
|
|
1734
|
-
return {
|
|
1735
|
-
drain: async () => {
|
|
1736
|
-
await Promise.all(pendingSet);
|
|
1737
|
-
},
|
|
1738
|
-
run: (task) => {
|
|
1739
|
-
const pending = task().finally(() => {
|
|
1740
|
-
pendingSet.delete(pending);
|
|
1741
|
-
});
|
|
1742
|
-
pendingSet.add(pending);
|
|
1743
|
-
}
|
|
1744
|
-
};
|
|
1745
|
-
};
|
|
1746
|
-
//#endregion
|
|
1747
|
-
//#region src/services/drivers/claudeAgentSdk/createClaudeAgentSdkDriver.ts
|
|
1748
|
-
const createClaudeAgentSdkDriver = ({ onEvents, onSessionOpen, onSessionsChange }) => {
|
|
1749
|
-
const openSessionMap = /* @__PURE__ */ new Map();
|
|
1750
|
-
const taskRegistry = createTaskRegistry();
|
|
1751
|
-
const emit = (sessionId, events) => {
|
|
1752
|
-
const openSession = openSessionMap.get(sessionId);
|
|
1753
|
-
let isStateChanged = false;
|
|
1754
|
-
if (openSession) {
|
|
1755
|
-
openSession.lastActivityAt = /* @__PURE__ */ new Date();
|
|
1756
|
-
for (const event of events) if (event.type === "SessionState" && event.state !== openSession.state) {
|
|
1757
|
-
openSession.state = event.state;
|
|
1758
|
-
isStateChanged = true;
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
onEvents(sessionId, events);
|
|
1762
|
-
if (isStateChanged) onSessionsChange();
|
|
1763
|
-
};
|
|
1764
|
-
const getOpenSession = (sessionId) => {
|
|
1765
|
-
const openSession = openSessionMap.get(sessionId);
|
|
1766
|
-
if (!openSession) throw new InvalidOperationError(Operation.Read, sessionId, "the session is not open on this host");
|
|
1767
|
-
return openSession;
|
|
1768
|
-
};
|
|
1769
|
-
const openSessionQuery = createSessionOpener({
|
|
1770
|
-
emit,
|
|
1771
|
-
onSessionOpen,
|
|
1772
|
-
onSessionsChange,
|
|
1773
|
-
openSessionMap,
|
|
1774
|
-
taskRegistry
|
|
1775
|
-
});
|
|
1776
|
-
const closeSession = (sessionId) => {
|
|
1777
|
-
const closingSession = getOpenSession(sessionId);
|
|
1778
|
-
closeOpenSession(sessionId, closingSession, "", {
|
|
1779
|
-
emit,
|
|
1780
|
-
onSessionsChange,
|
|
1781
|
-
openSessionMap
|
|
1782
|
-
});
|
|
1783
|
-
closingSession.input.close();
|
|
1784
|
-
closingSession.query.close();
|
|
1785
|
-
};
|
|
1786
|
-
const prompt = (sessionId, text, attachments) => {
|
|
1787
|
-
const openSession = getOpenSession(sessionId);
|
|
1788
|
-
const uuid = crypto.randomUUID();
|
|
1789
|
-
const createdAt = /* @__PURE__ */ new Date();
|
|
1790
|
-
openSession.input.push({
|
|
1791
|
-
message: {
|
|
1792
|
-
content: [...attachments.map((attachment) => toContentBlockParam(attachment)), {
|
|
1793
|
-
text,
|
|
1794
|
-
type: "text"
|
|
1795
|
-
}],
|
|
1796
|
-
role: "user"
|
|
1797
|
-
},
|
|
1798
|
-
parent_tool_use_id: null,
|
|
1799
|
-
type: "user",
|
|
1800
|
-
uuid
|
|
1801
|
-
});
|
|
1802
|
-
openSession.title ||= text;
|
|
1803
|
-
emit(sessionId, [{
|
|
1804
|
-
attachmentCount: attachments.length,
|
|
1805
|
-
createdAt,
|
|
1806
|
-
id: uuid,
|
|
1807
|
-
messageUuid: uuid,
|
|
1808
|
-
parentToolUseId: "",
|
|
1809
|
-
text,
|
|
1810
|
-
type: "UserMessage"
|
|
1811
|
-
}, {
|
|
1812
|
-
createdAt,
|
|
1813
|
-
id: getEventId(uuid, "SessionState"),
|
|
1814
|
-
state: "Running",
|
|
1815
|
-
type: "SessionState"
|
|
1816
|
-
}]);
|
|
1817
|
-
};
|
|
1818
|
-
const resumeSession = async (sessionId) => {
|
|
1819
|
-
if (openSessionMap.has(sessionId)) return sessionId;
|
|
1820
|
-
const cwd = await readSessionCwd(sessionId);
|
|
1821
|
-
return openSessionQuery({
|
|
1822
|
-
cwd,
|
|
1823
|
-
isFork: false,
|
|
1824
|
-
resumeAt: "",
|
|
1825
|
-
resumeFrom: sessionId,
|
|
1826
|
-
sessionId
|
|
1827
|
-
});
|
|
1828
|
-
};
|
|
1829
|
-
return {
|
|
1830
|
-
backgroundTasks: async (sessionId) => {
|
|
1831
|
-
await getOpenSession(sessionId).query.backgroundTasks();
|
|
1832
|
-
},
|
|
1833
|
-
close: async () => {
|
|
1834
|
-
for (const sessionId of openSessionMap.keys()) closeSession(sessionId);
|
|
1835
|
-
await taskRegistry.drain();
|
|
1836
|
-
},
|
|
1837
|
-
closeSession,
|
|
1838
|
-
createSession: (cwd) => openSessionQuery({
|
|
1839
|
-
cwd,
|
|
1840
|
-
isFork: false,
|
|
1841
|
-
resumeAt: "",
|
|
1842
|
-
resumeFrom: "",
|
|
1843
|
-
sessionId: crypto.randomUUID()
|
|
1844
|
-
}),
|
|
1845
|
-
forkSession: async (sessionId, messageUuid) => {
|
|
1846
|
-
const cwd = await readSessionCwd(sessionId);
|
|
1847
|
-
return openSessionQuery({
|
|
1848
|
-
cwd,
|
|
1849
|
-
isFork: true,
|
|
1850
|
-
resumeAt: messageUuid,
|
|
1851
|
-
resumeFrom: sessionId,
|
|
1852
|
-
sessionId: crypto.randomUUID()
|
|
1853
|
-
});
|
|
1854
|
-
},
|
|
1855
|
-
interrupt: async (sessionId) => {
|
|
1856
|
-
await getOpenSession(sessionId).query.interrupt();
|
|
1857
|
-
},
|
|
1858
|
-
listSessions: () => listSessionSummaries(openSessionMap),
|
|
1859
|
-
prompt,
|
|
1860
|
-
resolvePermission: (sessionId, requestId, behavior, message) => {
|
|
1861
|
-
getOpenSession(sessionId).pendingPermissionMap.get(requestId)?.settle(behavior, message);
|
|
1862
|
-
},
|
|
1863
|
-
resumeAt: async (sessionId, messageUuid) => {
|
|
1864
|
-
const cwd = await readSessionCwd(sessionId);
|
|
1865
|
-
if (openSessionMap.has(sessionId)) closeSession(sessionId);
|
|
1866
|
-
return openSessionQuery({
|
|
1867
|
-
cwd,
|
|
1868
|
-
isFork: false,
|
|
1869
|
-
resumeAt: messageUuid,
|
|
1870
|
-
resumeFrom: sessionId,
|
|
1871
|
-
sessionId
|
|
1872
|
-
});
|
|
1873
|
-
},
|
|
1874
|
-
resumeSession,
|
|
1875
|
-
rewindFiles: async (sessionId, messageUuid) => {
|
|
1876
|
-
await resumeSession(sessionId);
|
|
1877
|
-
const { query } = getOpenSession(sessionId);
|
|
1878
|
-
const { canRewind, deletions, error, filesChanged, insertions } = await query.rewindFiles(messageUuid, { dryRun: true });
|
|
1879
|
-
if (!canRewind) throw new InvalidOperationError(Operation.Update, sessionId, error ?? "nothing to rewind");
|
|
1880
|
-
const rewindResult = await query.rewindFiles(messageUuid);
|
|
1881
|
-
if (rewindResult.error) throw new InvalidOperationError(Operation.Update, sessionId, rewindResult.error);
|
|
1882
|
-
emit(sessionId, [{
|
|
1883
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1884
|
-
deletions: deletions ?? 0,
|
|
1885
|
-
filePaths: filesChanged ?? [],
|
|
1886
|
-
id: crypto.randomUUID(),
|
|
1887
|
-
insertions: insertions ?? 0,
|
|
1888
|
-
messageUuid,
|
|
1889
|
-
type: "FileRewind"
|
|
1890
|
-
}]);
|
|
1891
|
-
},
|
|
1892
|
-
runSlashCommand: (sessionId, name, commandArguments) => {
|
|
1893
|
-
prompt(sessionId, commandArguments ? `/${name} ${commandArguments}` : `/${name}`, []);
|
|
1894
|
-
},
|
|
1895
|
-
setModel: async (sessionId, model) => {
|
|
1896
|
-
const { mapper, query } = getOpenSession(sessionId);
|
|
1897
|
-
await query.setModel(model);
|
|
1898
|
-
emit(sessionId, [mapper.updateSettings(crypto.randomUUID(), /* @__PURE__ */ new Date(), { model })]);
|
|
1899
|
-
},
|
|
1900
|
-
setPermissionMode: async (sessionId, permissionMode) => {
|
|
1901
|
-
const { mapper, query } = getOpenSession(sessionId);
|
|
1902
|
-
await query.setPermissionMode(permissionMode);
|
|
1903
|
-
emit(sessionId, [mapper.updateSettings(crypto.randomUUID(), /* @__PURE__ */ new Date(), { permissionMode })]);
|
|
1904
|
-
},
|
|
1905
|
-
stopTask: async (sessionId, taskId) => {
|
|
1906
|
-
await getOpenSession(sessionId).query.stopTask(taskId);
|
|
1907
|
-
}
|
|
1908
|
-
};
|
|
1909
|
-
};
|
|
1910
|
-
//#endregion
|
|
1911
|
-
//#region src/models/window/BaseChildMessage.ts
|
|
1912
|
-
const createBaseChildMessageSchema = (typeSchema) => z.object({ type: typeSchema });
|
|
1913
|
-
//#endregion
|
|
1914
|
-
//#region src/models/window/ChildMessageType.ts
|
|
1915
|
-
let ChildMessageType = /* @__PURE__ */ function(ChildMessageType) {
|
|
1916
|
-
ChildMessageType["CommandError"] = "CommandError";
|
|
1917
|
-
ChildMessageType["CommandResult"] = "CommandResult";
|
|
1918
|
-
ChildMessageType["Events"] = "Events";
|
|
1919
|
-
ChildMessageType["Rejoin"] = "Rejoin";
|
|
1920
|
-
ChildMessageType["SessionOpen"] = "SessionOpen";
|
|
1921
|
-
ChildMessageType["SessionsChange"] = "SessionsChange";
|
|
1922
|
-
return ChildMessageType;
|
|
1923
|
-
}({});
|
|
1924
|
-
const childMessageTypeSchema = z.enum(ChildMessageType);
|
|
1925
|
-
//#endregion
|
|
1926
|
-
//#region src/models/window/ChildCommandErrorMessage.ts
|
|
1927
|
-
const childCommandErrorMessageSchema = z.object({
|
|
1928
|
-
...createBaseChildMessageSchema(z.literal("CommandError")).shape,
|
|
1929
|
-
commandId: z.string(),
|
|
1930
|
-
message: z.string().min(1)
|
|
1931
|
-
});
|
|
1932
|
-
//#endregion
|
|
1933
|
-
//#region src/models/window/ChildCommandResultMessage.ts
|
|
1934
|
-
const childCommandResultMessageSchema = z.object({
|
|
1935
|
-
...createBaseChildMessageSchema(z.literal("CommandResult")).shape,
|
|
1936
|
-
commandId: z.string().min(1),
|
|
1937
|
-
sessionId: z.string()
|
|
1938
|
-
});
|
|
1939
|
-
//#endregion
|
|
1940
|
-
//#region src/models/window/ChildEventsMessage.ts
|
|
1941
|
-
const childEventsMessageSchema = z.object({
|
|
1942
|
-
...createBaseChildMessageSchema(z.literal("Events")).shape,
|
|
1943
|
-
events: createUniqueArraySchema(agentEventSchema, "id"),
|
|
1944
|
-
sessionId: z.string().min(1)
|
|
1945
|
-
});
|
|
1946
|
-
//#endregion
|
|
1947
|
-
//#region src/models/window/RejoiningSession.ts
|
|
1948
|
-
const rejoiningSessionSchema = z.object({
|
|
1949
|
-
events: createUniqueArraySchema(agentEventSchema, "id"),
|
|
1950
|
-
sessionId: z.string().min(1)
|
|
1951
|
-
});
|
|
1952
|
-
//#endregion
|
|
1953
|
-
//#region src/models/window/ChildRejoinMessage.ts
|
|
1954
|
-
const childRejoinMessageSchema = z.object({
|
|
1955
|
-
...createBaseChildMessageSchema(z.literal("Rejoin")).shape,
|
|
1956
|
-
sessions: createUniqueArraySchema(rejoiningSessionSchema, "sessionId")
|
|
1957
|
-
});
|
|
1958
|
-
//#endregion
|
|
1959
|
-
//#region src/models/window/ChildSessionOpenMessage.ts
|
|
1960
|
-
const childSessionOpenMessageSchema = z.object({
|
|
1961
|
-
...createBaseChildMessageSchema(z.literal("SessionOpen")).shape,
|
|
1962
|
-
sessionId: z.string().min(1)
|
|
1963
|
-
});
|
|
1964
|
-
//#endregion
|
|
1965
|
-
//#region src/models/window/ChildSessionsChangeMessage.ts
|
|
1966
|
-
const childSessionsChangeMessageSchema = z.object({ ...createBaseChildMessageSchema(z.literal("SessionsChange")).shape });
|
|
1967
|
-
//#endregion
|
|
1968
|
-
//#region src/models/window/ChildMessage.ts
|
|
1969
|
-
const childMessageSchema = z.discriminatedUnion("type", [
|
|
1970
|
-
childCommandErrorMessageSchema,
|
|
1971
|
-
childCommandResultMessageSchema,
|
|
1972
|
-
childEventsMessageSchema,
|
|
1973
|
-
childRejoinMessageSchema,
|
|
1974
|
-
childSessionOpenMessageSchema,
|
|
1975
|
-
childSessionsChangeMessageSchema
|
|
1976
|
-
]);
|
|
1977
|
-
//#endregion
|
|
1978
|
-
//#region src/models/window/SessionWindowCloseCode.ts
|
|
1979
|
-
let SessionWindowCloseCode = /* @__PURE__ */ function(SessionWindowCloseCode) {
|
|
1980
|
-
SessionWindowCloseCode[SessionWindowCloseCode["SessionEnded"] = 4e3] = "SessionEnded";
|
|
1981
|
-
return SessionWindowCloseCode;
|
|
1982
|
-
}({});
|
|
1983
|
-
//#endregion
|
|
1984
|
-
//#region src/services/device/hashCredential.ts
|
|
1985
|
-
const hashCredential = (credential) => createHash("sha256").update(credential).digest("base64url");
|
|
1986
|
-
//#endregion
|
|
1987
|
-
//#region src/services/drivers/window/constants.ts
|
|
1988
|
-
const SESSION_SUBCOMMAND = "session";
|
|
1989
|
-
const SESSION_SECRET_ENVIRONMENT_VARIABLE = `${SITE_NAME.toUpperCase()}_SESSION_SECRET`;
|
|
1990
|
-
const SESSION_SECRET_BYTE_LENGTH = 32;
|
|
1991
|
-
const SESSION_WINDOW_CONNECT_TIMEOUT_MS = Temporal.Duration.from({ seconds: 30 }).total("milliseconds");
|
|
1992
|
-
const SESSION_WINDOWS_FILENAME = "session-windows.json";
|
|
1993
|
-
const SESSION_WINDOW_REJOIN_DURATION_MS = Temporal.Duration.from({ minutes: 1 }).total("milliseconds");
|
|
1994
|
-
const SESSION_WINDOW_RECONNECT_DELAY_MS = Temporal.Duration.from({ seconds: 1 }).total("milliseconds");
|
|
1995
|
-
//#endregion
|
|
1996
|
-
//#region src/models/window/SessionWindows.ts
|
|
1997
|
-
const sessionWindowsSchema = z.object({
|
|
1998
|
-
port: z.int().min(0),
|
|
1999
|
-
secretHashes: z.array(z.string().min(1))
|
|
2000
|
-
});
|
|
2001
|
-
//#endregion
|
|
2002
|
-
//#region src/services/drivers/window/readSessionWindows.ts
|
|
2003
|
-
const readSessionWindows = (stateDirectory) => getResult(() => sessionWindowsSchema.parse(JSON.parse(readFileSync(join(stateDirectory, SESSION_WINDOWS_FILENAME), "utf8")))).unwrapOr({
|
|
2004
|
-
port: 0,
|
|
2005
|
-
secretHashes: []
|
|
2006
|
-
});
|
|
2007
|
-
//#endregion
|
|
2008
|
-
//#region src/services/drivers/window/sendChildCommand.ts
|
|
2009
|
-
const sendChildCommand = (child, command) => {
|
|
2010
|
-
if (child.webSocket.readyState !== WebSocket.OPEN) throw new InvalidOperationError(Operation.Update, child.cwd, "the session's window closed");
|
|
2011
|
-
const reply = Promise.withResolvers();
|
|
2012
|
-
child.pendingReplyMap.set(command.id, reply);
|
|
2013
|
-
child.webSocket.send(JSON.stringify(command));
|
|
2014
|
-
return reply.promise;
|
|
2015
|
-
};
|
|
2016
|
-
//#endregion
|
|
2017
|
-
//#region src/services/drivers/window/takeChildReply.ts
|
|
2018
|
-
const takeChildReply = ({ pendingReplyMap }, commandId) => {
|
|
2019
|
-
const reply = pendingReplyMap.get(commandId);
|
|
2020
|
-
pendingReplyMap.delete(commandId);
|
|
2021
|
-
return reply;
|
|
2022
|
-
};
|
|
2023
|
-
//#endregion
|
|
2024
|
-
//#region src/services/server/checkIsTokenValid.ts
|
|
2025
|
-
const checkIsTokenValid = (candidate, token) => {
|
|
2026
|
-
if (!token) return false;
|
|
2027
|
-
const candidateBuffer = Buffer.from(candidate);
|
|
2028
|
-
const tokenBuffer = Buffer.from(token);
|
|
2029
|
-
return candidateBuffer.length === tokenBuffer.length && timingSafeEqual(candidateBuffer, tokenBuffer);
|
|
2030
|
-
};
|
|
2031
|
-
//#endregion
|
|
2032
|
-
//#region src/services/drivers/window/createWindowDriver.ts
|
|
2033
|
-
const createWindowDriver = ({ onEvents, onSessionOpen, onSessionsChange }, { launchSessionWindow, stateDirectory, writeLine }) => {
|
|
2034
|
-
const windowSessionMap = /* @__PURE__ */ new Map();
|
|
2035
|
-
const childSet = /* @__PURE__ */ new Set();
|
|
2036
|
-
const pendingLaunchMap = /* @__PURE__ */ new Map();
|
|
2037
|
-
const taskRegistry = createTaskRegistry();
|
|
2038
|
-
const webSocketServer = new WebSocketServer({ noServer: true });
|
|
2039
|
-
const httpServer = createServer((_request, response) => {
|
|
2040
|
-
response.writeHead(405).end();
|
|
2041
|
-
});
|
|
2042
|
-
const rejoinSecretHashSet = new Set(readSessionWindows(stateDirectory).secretHashes);
|
|
2043
|
-
let port = 0;
|
|
2044
|
-
const saveSessionWindows = () => {
|
|
2045
|
-
const secretHashes = [...Array.from(childSet, ({ secretHash }) => secretHash), ...rejoinSecretHashSet];
|
|
2046
|
-
getResult(() => {
|
|
2047
|
-
writeStateFile(stateDirectory, SESSION_WINDOWS_FILENAME, JSON.stringify({
|
|
2048
|
-
port,
|
|
2049
|
-
secretHashes
|
|
2050
|
-
}));
|
|
2051
|
-
}).match(noop, console.error);
|
|
2052
|
-
};
|
|
2053
|
-
const rejoinTimeout = setTimeout(() => {
|
|
2054
|
-
rejoinSecretHashSet.clear();
|
|
2055
|
-
saveSessionWindows();
|
|
2056
|
-
}, SESSION_WINDOW_REJOIN_DURATION_MS);
|
|
2057
|
-
const getWindowSession = (sessionId) => {
|
|
2058
|
-
const windowSession = windowSessionMap.get(sessionId);
|
|
2059
|
-
if (!windowSession) throw new InvalidOperationError(Operation.Read, sessionId, "the session is not open on this host");
|
|
2060
|
-
return windowSession;
|
|
2061
|
-
};
|
|
2062
|
-
const closeWindowSession = (sessionId, windowSession) => {
|
|
2063
|
-
if (windowSessionMap.get(sessionId) !== windowSession) return;
|
|
2064
|
-
windowSessionMap.delete(sessionId);
|
|
2065
|
-
if (windowSession.state !== "Closed") onEvents(sessionId, toSessionClosedEvents(""));
|
|
2066
|
-
writeLine(`A session in ${windowSession.child.cwd} closed, and its window with it.`);
|
|
2067
|
-
onSessionsChange();
|
|
2068
|
-
windowSession.child.webSocket.close(4e3);
|
|
2069
|
-
};
|
|
2070
|
-
const holdSession = (child, sessionId) => {
|
|
2071
|
-
windowSessionMap.set(sessionId, {
|
|
2072
|
-
child,
|
|
2073
|
-
cwd: child.cwd,
|
|
2074
|
-
lastActivityAt: /* @__PURE__ */ new Date(),
|
|
2075
|
-
state: "Idle",
|
|
2076
|
-
title: ""
|
|
2077
|
-
});
|
|
2078
|
-
onSessionOpen(sessionId);
|
|
2079
|
-
};
|
|
2080
|
-
const handleChildMessage = (child, childMessage) => {
|
|
2081
|
-
switch (childMessage.type) {
|
|
2082
|
-
case "CommandError": {
|
|
2083
|
-
const reply = takeChildReply(child, childMessage.commandId);
|
|
2084
|
-
if (reply) reply.reject(new Error(childMessage.message));
|
|
2085
|
-
else console.error(childMessage.message);
|
|
2086
|
-
return;
|
|
2087
|
-
}
|
|
2088
|
-
case "CommandResult":
|
|
2089
|
-
takeChildReply(child, childMessage.commandId)?.resolve(childMessage.sessionId);
|
|
2090
|
-
return;
|
|
2091
|
-
case "Events": {
|
|
2092
|
-
const { events, sessionId } = childMessage;
|
|
2093
|
-
const windowSession = windowSessionMap.get(sessionId);
|
|
2094
|
-
const isHeld = windowSession?.child === child;
|
|
2095
|
-
if (isHeld) {
|
|
2096
|
-
windowSession.lastActivityAt = /* @__PURE__ */ new Date();
|
|
2097
|
-
for (const event of events) if (event.type === "SessionState") windowSession.state = event.state;
|
|
2098
|
-
}
|
|
2099
|
-
onEvents(sessionId, events);
|
|
2100
|
-
if (isHeld && windowSession.state === "Closed") closeWindowSession(sessionId, windowSession);
|
|
2101
|
-
return;
|
|
2102
|
-
}
|
|
2103
|
-
case "Rejoin":
|
|
2104
|
-
for (const { events, sessionId } of childMessage.sessions) {
|
|
2105
|
-
child.cwd = events.findLast((event) => event.type === "SessionInit")?.cwd ?? child.cwd;
|
|
2106
|
-
holdSession(child, sessionId);
|
|
2107
|
-
writeLine(`A session in ${child.cwd} came back from its window.`);
|
|
2108
|
-
handleChildMessage(child, {
|
|
2109
|
-
events,
|
|
2110
|
-
sessionId,
|
|
2111
|
-
type: "Events"
|
|
2112
|
-
});
|
|
2113
|
-
}
|
|
2114
|
-
onSessionsChange();
|
|
2115
|
-
return;
|
|
2116
|
-
case "SessionOpen":
|
|
2117
|
-
holdSession(child, childMessage.sessionId);
|
|
2118
|
-
writeLine(`A session in ${child.cwd} opened in a window of its own.`);
|
|
2119
|
-
return;
|
|
2120
|
-
case "SessionsChange":
|
|
2121
|
-
onSessionsChange();
|
|
2122
|
-
return;
|
|
2123
|
-
default: exhaustiveGuard(childMessage);
|
|
2124
|
-
}
|
|
2125
|
-
};
|
|
2126
|
-
const receive = (child, data) => {
|
|
2127
|
-
getResult(() => childMessageSchema.parse(JSON.parse(readMessageText(data)))).match((childMessage) => {
|
|
2128
|
-
handleChildMessage(child, childMessage);
|
|
2129
|
-
}, console.error);
|
|
2130
|
-
};
|
|
2131
|
-
const removeChild = (child) => {
|
|
2132
|
-
childSet.delete(child);
|
|
2133
|
-
saveSessionWindows();
|
|
2134
|
-
for (const reply of child.pendingReplyMap.values()) reply.reject(new InvalidOperationError(Operation.Update, child.cwd, "the session's window closed"));
|
|
2135
|
-
child.pendingReplyMap.clear();
|
|
2136
|
-
for (const [sessionId, windowSession] of windowSessionMap) if (windowSession.child === child) closeWindowSession(sessionId, windowSession);
|
|
2137
|
-
};
|
|
2138
|
-
const addChild = (webSocket, cwd, secretHash) => {
|
|
2139
|
-
const child = {
|
|
2140
|
-
cwd,
|
|
2141
|
-
pendingReplyMap: /* @__PURE__ */ new Map(),
|
|
2142
|
-
secretHash,
|
|
2143
|
-
webSocket
|
|
2144
|
-
};
|
|
2145
|
-
childSet.add(child);
|
|
2146
|
-
saveSessionWindows();
|
|
2147
|
-
webSocket.on("message", (data) => {
|
|
2148
|
-
receive(child, data);
|
|
2149
|
-
});
|
|
2150
|
-
webSocket.on("close", () => {
|
|
2151
|
-
removeChild(child);
|
|
2152
|
-
});
|
|
2153
|
-
return child;
|
|
2154
|
-
};
|
|
2155
|
-
httpServer.on("upgrade", (request, socket, head) => {
|
|
2156
|
-
const secret = request.headers.authorization?.replace(/^Bearer /u, "") ?? "";
|
|
2157
|
-
const pendingLaunchEntry = [...pendingLaunchMap].find(([launchSecret]) => checkIsTokenValid(secret, launchSecret));
|
|
2158
|
-
if (pendingLaunchEntry) {
|
|
2159
|
-
const [launchSecret, { connection, timeout }] = pendingLaunchEntry;
|
|
2160
|
-
pendingLaunchMap.delete(launchSecret);
|
|
2161
|
-
clearTimeout(timeout);
|
|
2162
|
-
webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
|
|
2163
|
-
connection.resolve(webSocket);
|
|
2164
|
-
});
|
|
2165
|
-
return;
|
|
2166
|
-
}
|
|
2167
|
-
const secretHash = hashCredential(secret);
|
|
2168
|
-
if (!secret || !rejoinSecretHashSet.delete(secretHash)) {
|
|
2169
|
-
socket.end("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
2170
|
-
return;
|
|
2171
|
-
}
|
|
2172
|
-
webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
|
|
2173
|
-
addChild(webSocket, "", secretHash);
|
|
2174
|
-
});
|
|
2175
|
-
});
|
|
2176
|
-
const listen = async () => {
|
|
2177
|
-
httpServer.listen(0, DEFAULT_HOSTNAME);
|
|
2178
|
-
await once(httpServer, "listening");
|
|
2179
|
-
const address = httpServer.address();
|
|
2180
|
-
port = typeof address === "object" && address ? address.port : 0;
|
|
2181
|
-
saveSessionWindows();
|
|
2182
|
-
};
|
|
2183
|
-
const listening = listen();
|
|
2184
|
-
const openWindow = async (cwd) => {
|
|
2185
|
-
await listening;
|
|
2186
|
-
const secret = randomBytes(32).toString("base64url");
|
|
2187
|
-
const connection = Promise.withResolvers();
|
|
2188
|
-
const timeout = setTimeout(() => {
|
|
2189
|
-
pendingLaunchMap.delete(secret);
|
|
2190
|
-
connection.reject(new InvalidOperationError(Operation.Create, cwd, "the session's window did not start"));
|
|
2191
|
-
}, SESSION_WINDOW_CONNECT_TIMEOUT_MS);
|
|
2192
|
-
pendingLaunchMap.set(secret, {
|
|
2193
|
-
connection,
|
|
2194
|
-
timeout
|
|
2195
|
-
});
|
|
2196
|
-
launchSessionWindow({
|
|
2197
|
-
port,
|
|
2198
|
-
secret
|
|
2199
|
-
});
|
|
2200
|
-
const webSocket = await connection.promise;
|
|
2201
|
-
return addChild(webSocket, cwd, hashCredential(secret));
|
|
2202
|
-
};
|
|
2203
|
-
const openInWindow = async (cwd, command) => {
|
|
2204
|
-
const child = await openWindow(cwd);
|
|
2205
|
-
return getResultAsync(() => sendChildCommand(child, command)).match((sessionId) => sessionId, (error) => {
|
|
2206
|
-
child.webSocket.close(4e3);
|
|
2207
|
-
throw error;
|
|
2208
|
-
});
|
|
2209
|
-
};
|
|
2210
|
-
const forward = (sessionId, command) => sendChildCommand(getWindowSession(sessionId).child, command);
|
|
2211
|
-
const forwardInBackground = (sessionId, command) => {
|
|
2212
|
-
const reply = forward(sessionId, command);
|
|
2213
|
-
taskRegistry.run(() => getResultAsync(() => reply).match(noop, console.error));
|
|
2214
|
-
};
|
|
2215
|
-
const resumeSession = async (sessionId) => {
|
|
2216
|
-
if (windowSessionMap.has(sessionId)) return sessionId;
|
|
2217
|
-
return openInWindow(await readSessionCwd(sessionId), {
|
|
2218
|
-
id: crypto.randomUUID(),
|
|
2219
|
-
sessionId,
|
|
2220
|
-
type: "Resume"
|
|
2221
|
-
});
|
|
2222
|
-
};
|
|
2223
|
-
return {
|
|
2224
|
-
backgroundTasks: async (sessionId) => {
|
|
2225
|
-
await forward(sessionId, {
|
|
2226
|
-
id: crypto.randomUUID(),
|
|
2227
|
-
sessionId,
|
|
2228
|
-
type: "BackgroundTasks"
|
|
2229
|
-
});
|
|
2230
|
-
},
|
|
2231
|
-
close: async () => {
|
|
2232
|
-
for (const { connection, timeout } of pendingLaunchMap.values()) {
|
|
2233
|
-
clearTimeout(timeout);
|
|
2234
|
-
connection.reject(new InvalidOperationError(Operation.Create, createWindowDriver.name, "the host is stopping"));
|
|
2235
|
-
}
|
|
2236
|
-
pendingLaunchMap.clear();
|
|
2237
|
-
clearTimeout(rejoinTimeout);
|
|
2238
|
-
rejoinSecretHashSet.clear();
|
|
2239
|
-
const children = [...childSet];
|
|
2240
|
-
for (const { webSocket } of children) webSocket.close(4e3);
|
|
2241
|
-
await Promise.all(children.map(({ webSocket }) => once(webSocket, "close")));
|
|
2242
|
-
await taskRegistry.drain();
|
|
2243
|
-
await listening;
|
|
2244
|
-
saveSessionWindows();
|
|
2245
|
-
httpServer.close();
|
|
2246
|
-
await once(httpServer, "close");
|
|
2247
|
-
},
|
|
2248
|
-
closeSession: (sessionId) => {
|
|
2249
|
-
closeWindowSession(sessionId, getWindowSession(sessionId));
|
|
2250
|
-
},
|
|
2251
|
-
createSession: (cwd) => openInWindow(cwd, {
|
|
2252
|
-
cwd,
|
|
2253
|
-
id: crypto.randomUUID(),
|
|
2254
|
-
type: "CreateSession"
|
|
2255
|
-
}),
|
|
2256
|
-
forkSession: async (sessionId, messageUuid) => openInWindow(await readSessionCwd(sessionId), {
|
|
2257
|
-
id: crypto.randomUUID(),
|
|
2258
|
-
messageUuid,
|
|
2259
|
-
sessionId,
|
|
2260
|
-
type: "Fork"
|
|
2261
|
-
}),
|
|
2262
|
-
interrupt: async (sessionId) => {
|
|
2263
|
-
await forward(sessionId, {
|
|
2264
|
-
id: crypto.randomUUID(),
|
|
2265
|
-
sessionId,
|
|
2266
|
-
type: "Interrupt"
|
|
2267
|
-
});
|
|
2268
|
-
},
|
|
2269
|
-
listSessions: () => listSessionSummaries(windowSessionMap),
|
|
2270
|
-
prompt: (sessionId, text, attachments) => {
|
|
2271
|
-
forwardInBackground(sessionId, {
|
|
2272
|
-
attachments,
|
|
2273
|
-
id: crypto.randomUUID(),
|
|
2274
|
-
sessionId,
|
|
2275
|
-
text,
|
|
2276
|
-
type: "Prompt"
|
|
2277
|
-
});
|
|
2278
|
-
},
|
|
2279
|
-
resolvePermission: (sessionId, requestId, behavior, message) => {
|
|
2280
|
-
forwardInBackground(sessionId, {
|
|
2281
|
-
behavior,
|
|
2282
|
-
id: crypto.randomUUID(),
|
|
2283
|
-
message,
|
|
2284
|
-
requestId,
|
|
2285
|
-
sessionId,
|
|
2286
|
-
type: "PermissionVerdict"
|
|
2287
|
-
});
|
|
2288
|
-
},
|
|
2289
|
-
resumeAt: async (sessionId, messageUuid) => {
|
|
2290
|
-
const cwd = await readSessionCwd(sessionId);
|
|
2291
|
-
const windowSession = windowSessionMap.get(sessionId);
|
|
2292
|
-
if (windowSession) closeWindowSession(sessionId, windowSession);
|
|
2293
|
-
return openInWindow(cwd, {
|
|
2294
|
-
id: crypto.randomUUID(),
|
|
2295
|
-
messageUuid,
|
|
2296
|
-
sessionId,
|
|
2297
|
-
type: "ResumeAt"
|
|
2298
|
-
});
|
|
2299
|
-
},
|
|
2300
|
-
resumeSession,
|
|
2301
|
-
rewindFiles: async (sessionId, messageUuid) => {
|
|
2302
|
-
await resumeSession(sessionId);
|
|
2303
|
-
await forward(sessionId, {
|
|
2304
|
-
id: crypto.randomUUID(),
|
|
2305
|
-
messageUuid,
|
|
2306
|
-
sessionId,
|
|
2307
|
-
type: "RewindFiles"
|
|
2308
|
-
});
|
|
2309
|
-
},
|
|
2310
|
-
runSlashCommand: (sessionId, name, commandArguments) => {
|
|
2311
|
-
forwardInBackground(sessionId, {
|
|
2312
|
-
arguments: commandArguments,
|
|
2313
|
-
id: crypto.randomUUID(),
|
|
2314
|
-
name,
|
|
2315
|
-
sessionId,
|
|
2316
|
-
type: "SlashCommand"
|
|
2317
|
-
});
|
|
2318
|
-
},
|
|
2319
|
-
setModel: async (sessionId, model) => {
|
|
2320
|
-
await forward(sessionId, {
|
|
2321
|
-
id: crypto.randomUUID(),
|
|
2322
|
-
model,
|
|
2323
|
-
sessionId,
|
|
2324
|
-
type: "SetModel"
|
|
2325
|
-
});
|
|
2326
|
-
},
|
|
2327
|
-
setPermissionMode: async (sessionId, permissionMode) => {
|
|
2328
|
-
await forward(sessionId, {
|
|
2329
|
-
id: crypto.randomUUID(),
|
|
2330
|
-
permissionMode,
|
|
2331
|
-
sessionId,
|
|
2332
|
-
type: "SetPermissionMode"
|
|
2333
|
-
});
|
|
2334
|
-
},
|
|
2335
|
-
stopTask: async (sessionId, taskId) => {
|
|
2336
|
-
await forward(sessionId, {
|
|
2337
|
-
id: crypto.randomUUID(),
|
|
2338
|
-
sessionId,
|
|
2339
|
-
taskId,
|
|
2340
|
-
type: "StopTask"
|
|
2341
|
-
});
|
|
2342
|
-
}
|
|
2343
|
-
};
|
|
2344
|
-
};
|
|
2345
|
-
//#endregion
|
|
2346
|
-
//#region src/services/drivers/window/launchSessionWindow.ts
|
|
2347
|
-
const SESSION_COMMAND_ENVIRONMENT_VARIABLE_PREFIX = `${SITE_NAME.toUpperCase()}_SESSION_COMMAND_`;
|
|
2348
|
-
const launchSessionWindow = ({ port, secret }) => {
|
|
2349
|
-
const hostCommand = isSea() ? [process.execPath] : [process.execPath, process.argv[1] ?? ""];
|
|
2350
|
-
const quotedHostCommand = hostCommand.map((_value, index) => `"%${SESSION_COMMAND_ENVIRONMENT_VARIABLE_PREFIX}${index}%"`).join(" ");
|
|
2351
|
-
spawn$1("cmd.exe", [
|
|
2352
|
-
"/d",
|
|
2353
|
-
"/s",
|
|
2354
|
-
"/c",
|
|
2355
|
-
`"start "" ${quotedHostCommand} ${SESSION_SUBCOMMAND} --port ${port}"`
|
|
2356
|
-
], {
|
|
2357
|
-
env: {
|
|
2358
|
-
...process.env,
|
|
2359
|
-
...Object.fromEntries(hostCommand.map((part, index) => [`${SESSION_COMMAND_ENVIRONMENT_VARIABLE_PREFIX}${index}`, part])),
|
|
2360
|
-
[SESSION_SECRET_ENVIRONMENT_VARIABLE]: secret
|
|
2361
|
-
},
|
|
2362
|
-
stdio: "ignore",
|
|
2363
|
-
windowsVerbatimArguments: true
|
|
2364
|
-
}).on("error", console.error);
|
|
2365
|
-
};
|
|
2366
|
-
//#endregion
|
|
2367
|
-
//#region src/services/device/findDevice.ts
|
|
2368
|
-
const findDevice = (devices, credential) => {
|
|
2369
|
-
const credentialHash = hashCredential(credential);
|
|
2370
|
-
return devices.find((device) => checkIsTokenValid(credentialHash, device.credentialHash));
|
|
2371
|
-
};
|
|
2372
|
-
//#endregion
|
|
2373
|
-
//#region src/services/device/getDeviceName.ts
|
|
2374
|
-
const BrowserNames = [
|
|
2375
|
-
[/Edg\//u, "Edge"],
|
|
2376
|
-
[/Firefox\//u, "Firefox"],
|
|
2377
|
-
[/Chrome\//u, "Chrome"],
|
|
2378
|
-
[/Safari\//u, "Safari"]
|
|
2379
|
-
];
|
|
2380
|
-
const SystemNames = [
|
|
2381
|
-
[/Windows/u, "Windows"],
|
|
2382
|
-
[/Android/u, "Android"],
|
|
2383
|
-
[/iPhone|iPad/u, "iOS"],
|
|
2384
|
-
[/Mac OS X/u, "macOS"],
|
|
2385
|
-
[/Linux/u, "Linux"]
|
|
2386
|
-
];
|
|
2387
|
-
const getDeviceName = (userAgent) => {
|
|
2388
|
-
const browserName = BrowserNames.find(([pattern]) => pattern.test(userAgent))?.[1] ?? "A browser";
|
|
2389
|
-
const systemName = SystemNames.find(([pattern]) => pattern.test(userAgent))?.[1];
|
|
2390
|
-
return systemName ? `${browserName} on ${systemName}` : browserName;
|
|
2391
|
-
};
|
|
2392
|
-
//#endregion
|
|
2393
|
-
//#region src/services/handshake/getPublicKeyText.ts
|
|
2394
|
-
const getPublicKeyText = (privateKey) => createPublicKey(privateKey).export({ format: "jwk" }).x ?? "";
|
|
2395
|
-
//#endregion
|
|
2396
|
-
//#region src/services/server/answerHttpRequest.ts
|
|
2397
|
-
const answerHttpRequest = ({ headers, method }, response) => {
|
|
2398
|
-
if (method !== "OPTIONS") {
|
|
2399
|
-
response.writeHead(405).end();
|
|
2400
|
-
return;
|
|
2401
|
-
}
|
|
2402
|
-
response.setHeader("Access-Control-Allow-Origin", headers.origin ?? "*");
|
|
2403
|
-
response.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
2404
|
-
response.setHeader("Access-Control-Allow-Methods", "GET");
|
|
2405
|
-
response.setHeader("Access-Control-Allow-Headers", headers["access-control-request-headers"] ?? "*");
|
|
2406
|
-
response.setHeader("Vary", "Origin");
|
|
2407
|
-
response.writeHead(204).end();
|
|
2408
|
-
};
|
|
2409
|
-
//#endregion
|
|
2410
|
-
//#region src/services/server/createEventLog.ts
|
|
2411
|
-
const createEventLog = () => {
|
|
2412
|
-
const sessionEventMap = /* @__PURE__ */ new Map();
|
|
2413
|
-
return {
|
|
2414
|
-
append: (sessionId, events) => {
|
|
2415
|
-
const sessionEvents = getOrCreate(sessionEventMap, sessionId, () => ({
|
|
2416
|
-
eventIds: /* @__PURE__ */ new Set(),
|
|
2417
|
-
events: []
|
|
2418
|
-
}));
|
|
2419
|
-
const newEvents = [];
|
|
2420
|
-
for (const event of events) {
|
|
2421
|
-
if (EphemeralAgentEventTypes.includes(event.type)) {
|
|
2422
|
-
newEvents.push(event);
|
|
2423
|
-
continue;
|
|
2424
|
-
}
|
|
2425
|
-
if (sessionEvents.eventIds.has(event.id)) continue;
|
|
2426
|
-
sessionEvents.eventIds.add(event.id);
|
|
2427
|
-
sessionEvents.events.push(event);
|
|
2428
|
-
newEvents.push(event);
|
|
2429
|
-
}
|
|
2430
|
-
return newEvents;
|
|
2431
|
-
},
|
|
2432
|
-
entries: () => Array.from(sessionEventMap, ([sessionId, { events }]) => [sessionId, events]),
|
|
2433
|
-
reset: (sessionId) => {
|
|
2434
|
-
sessionEventMap.delete(sessionId);
|
|
2435
|
-
}
|
|
2436
|
-
};
|
|
2437
|
-
};
|
|
2438
|
-
//#endregion
|
|
2439
|
-
//#region src/services/server/createPairingCodes.ts
|
|
2440
|
-
const createPairingCodes = () => {
|
|
2441
|
-
const codeTimeoutMap = /* @__PURE__ */ new Map();
|
|
2442
|
-
return {
|
|
2443
|
-
add: (code, durationMs) => {
|
|
2444
|
-
clearTimeout(codeTimeoutMap.get(code));
|
|
2445
|
-
codeTimeoutMap.set(code, setTimeout(() => {
|
|
2446
|
-
codeTimeoutMap.delete(code);
|
|
2447
|
-
}, durationMs));
|
|
2448
|
-
},
|
|
2449
|
-
clear: () => {
|
|
2450
|
-
for (const timeout of codeTimeoutMap.values()) clearTimeout(timeout);
|
|
2451
|
-
codeTimeoutMap.clear();
|
|
2452
|
-
},
|
|
2453
|
-
take: (code) => {
|
|
2454
|
-
const heldCode = [...codeTimeoutMap.keys()].find((candidate) => checkIsTokenValid(code, candidate));
|
|
2455
|
-
if (heldCode === void 0) return false;
|
|
2456
|
-
clearTimeout(codeTimeoutMap.get(heldCode));
|
|
2457
|
-
codeTimeoutMap.delete(heldCode);
|
|
2458
|
-
return true;
|
|
2459
|
-
}
|
|
2460
|
-
};
|
|
2461
|
-
};
|
|
2462
|
-
//#endregion
|
|
2463
|
-
//#region src/services/server/handleCommand.ts
|
|
2464
|
-
const handleCommand = async (driver, command) => {
|
|
2465
|
-
switch (command.type) {
|
|
2466
|
-
case "BackgroundTasks":
|
|
2467
|
-
await driver.backgroundTasks(command.sessionId);
|
|
2468
|
-
return "";
|
|
2469
|
-
case "CloseSession":
|
|
2470
|
-
driver.closeSession(command.sessionId);
|
|
2471
|
-
return "";
|
|
2472
|
-
case "CreateSession": return driver.createSession(command.cwd);
|
|
2473
|
-
case "Fork": return driver.forkSession(command.sessionId, command.messageUuid);
|
|
2474
|
-
case "Interrupt":
|
|
2475
|
-
await driver.interrupt(command.sessionId);
|
|
2476
|
-
return "";
|
|
2477
|
-
case "ListSessions": return "";
|
|
2478
|
-
case "PermissionVerdict":
|
|
2479
|
-
driver.resolvePermission(command.sessionId, command.requestId, command.behavior, command.message);
|
|
2480
|
-
return "";
|
|
2481
|
-
case "Prompt":
|
|
2482
|
-
driver.prompt(command.sessionId, command.text, command.attachments);
|
|
2483
|
-
return "";
|
|
2484
|
-
case "Resume": return driver.resumeSession(command.sessionId);
|
|
2485
|
-
case "ResumeAt": return driver.resumeAt(command.sessionId, command.messageUuid);
|
|
2486
|
-
case "RewindFiles":
|
|
2487
|
-
await driver.rewindFiles(command.sessionId, command.messageUuid);
|
|
2488
|
-
return "";
|
|
2489
|
-
case "SetModel":
|
|
2490
|
-
await driver.setModel(command.sessionId, command.model);
|
|
2491
|
-
return "";
|
|
2492
|
-
case "SetPermissionMode":
|
|
2493
|
-
await driver.setPermissionMode(command.sessionId, command.permissionMode);
|
|
2494
|
-
return "";
|
|
2495
|
-
case "SlashCommand":
|
|
2496
|
-
driver.runSlashCommand(command.sessionId, command.name, command.arguments);
|
|
2497
|
-
return "";
|
|
2498
|
-
case "StopTask":
|
|
2499
|
-
await driver.stopTask(command.sessionId, command.taskId);
|
|
2500
|
-
return "";
|
|
2501
|
-
default: return exhaustiveGuard(command);
|
|
2502
|
-
}
|
|
2503
|
-
};
|
|
2504
|
-
//#endregion
|
|
2505
|
-
//#region src/services/files/getSessionCwd.ts
|
|
2506
|
-
const getSessionCwd = async (driver, sessionId) => {
|
|
2507
|
-
const session = (await driver.listSessions()).find(({ id }) => id === sessionId);
|
|
2508
|
-
if (!session) throw new InvalidOperationError(Operation.Read, sessionId, "the session is not open on this host");
|
|
2509
|
-
return session.cwd;
|
|
2510
|
-
};
|
|
2511
|
-
//#endregion
|
|
2512
|
-
//#region src/services/files/constants.ts
|
|
2513
|
-
const REPOSITORY_FILE_MAX_BYTE_LENGTH = 524288;
|
|
2514
|
-
const REPOSITORY_WALK_IGNORED_NAMES = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2515
|
-
//#endregion
|
|
2516
|
-
//#region src/services/files/listRepositoryFiles.ts
|
|
2517
|
-
const execFileAsync = promisify(execFile$1);
|
|
2518
|
-
const listGitFiles = async (cwd) => {
|
|
2519
|
-
const { stdout } = await execFileAsync("git", [
|
|
2520
|
-
"ls-files",
|
|
2521
|
-
"-co",
|
|
2522
|
-
"--exclude-standard",
|
|
2523
|
-
"-z"
|
|
2524
|
-
], { cwd });
|
|
2525
|
-
return stdout.split("\0").filter(Boolean);
|
|
2526
|
-
};
|
|
2527
|
-
const walkFiles = async (root, directory) => {
|
|
2528
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
2529
|
-
const files = entries.filter((entry) => entry.isFile()).map((entry) => relative(root, join(directory, entry.name)));
|
|
2530
|
-
const subdirectories = entries.filter((entry) => entry.isDirectory() && !REPOSITORY_WALK_IGNORED_NAMES.has(entry.name));
|
|
2531
|
-
const nested = await Promise.all(subdirectories.map((entry) => walkFiles(root, join(directory, entry.name))));
|
|
2532
|
-
return [...files, ...nested.flat()];
|
|
2533
|
-
};
|
|
2534
|
-
const listRepositoryFiles = async (cwd) => {
|
|
2535
|
-
return (await getResultAsync(() => listGitFiles(cwd)).unwrapOr(void 0) ?? await walkFiles(cwd, cwd)).map((file) => file.split(sep).join("/")).toSorted();
|
|
2536
|
-
};
|
|
2537
|
-
//#endregion
|
|
2538
|
-
//#region src/services/files/resolveRepositoryPath.ts
|
|
2539
|
-
const resolveRepositoryPath = async (cwd, path) => {
|
|
2540
|
-
const root = await realpath(cwd);
|
|
2541
|
-
const target = await realpath(resolve(root, path));
|
|
2542
|
-
const relation = relative(root, target);
|
|
2543
|
-
if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) throw new InvalidOperationError(Operation.Read, path, "the file is outside the session's working directory");
|
|
2544
|
-
return target;
|
|
2545
|
-
};
|
|
2546
|
-
//#endregion
|
|
2547
|
-
//#region src/services/files/readRepositoryFile.ts
|
|
2548
|
-
const readRepositoryFile = async (cwd, path) => {
|
|
2549
|
-
const target = await resolveRepositoryPath(cwd, path);
|
|
2550
|
-
const { size } = await stat(target);
|
|
2551
|
-
if (size > 524288) return {
|
|
2552
|
-
content: "",
|
|
2553
|
-
notice: "The file is too large to show."
|
|
2554
|
-
};
|
|
2555
|
-
const buffer = await readFile(target);
|
|
2556
|
-
if (buffer.includes(0)) return {
|
|
2557
|
-
content: "",
|
|
2558
|
-
notice: "The file is not text, so it cannot be shown."
|
|
2559
|
-
};
|
|
2560
|
-
return {
|
|
2561
|
-
content: buffer.toString("utf8"),
|
|
2562
|
-
notice: ""
|
|
2563
|
-
};
|
|
2564
|
-
};
|
|
2565
|
-
//#endregion
|
|
2566
|
-
//#region src/services/server/handleFileCommand.ts
|
|
2567
|
-
const handleFileCommand = async (driver, command) => {
|
|
2568
|
-
const cwd = await getSessionCwd(driver, command.sessionId);
|
|
2569
|
-
switch (command.type) {
|
|
2570
|
-
case "ListFiles": return {
|
|
2571
|
-
commandId: command.id,
|
|
2572
|
-
files: await listRepositoryFiles(cwd),
|
|
2573
|
-
type: "Files"
|
|
2574
|
-
};
|
|
2575
|
-
case "ReadFile": {
|
|
2576
|
-
const file = await readRepositoryFile(cwd, command.path);
|
|
2577
|
-
return {
|
|
2578
|
-
commandId: command.id,
|
|
2579
|
-
content: file.content,
|
|
2580
|
-
notice: file.notice,
|
|
2581
|
-
path: command.path,
|
|
2582
|
-
type: "FileContent"
|
|
2583
|
-
};
|
|
2584
|
-
}
|
|
2585
|
-
default: return exhaustiveGuard(command);
|
|
2586
|
-
}
|
|
2587
|
-
};
|
|
2588
|
-
//#endregion
|
|
2589
|
-
//#region src/services/server/handleShellCommand.ts
|
|
2590
|
-
const handleShellCommand = async (shellRegistry, command) => {
|
|
2591
|
-
switch (command.type) {
|
|
2592
|
-
case "CloseShell":
|
|
2593
|
-
shellRegistry.close(command.shellId);
|
|
2594
|
-
return "";
|
|
2595
|
-
case "OpenShell": return await shellRegistry.open(command.sessionId, {
|
|
2596
|
-
cols: command.cols,
|
|
2597
|
-
cwd: command.cwd,
|
|
2598
|
-
rows: command.rows
|
|
2599
|
-
});
|
|
2600
|
-
case "ShellInput":
|
|
2601
|
-
shellRegistry.write(command.shellId, command.data);
|
|
2602
|
-
return "";
|
|
2603
|
-
case "ShellResize":
|
|
2604
|
-
shellRegistry.resize(command.shellId, command.cols, command.rows);
|
|
2605
|
-
return "";
|
|
2606
|
-
default: return exhaustiveGuard(command);
|
|
2607
|
-
}
|
|
2608
|
-
};
|
|
2609
|
-
//#endregion
|
|
2610
|
-
//#region src/services/server/sendServerMessage.ts
|
|
2611
|
-
const sendServerMessage = (webSocket, message) => {
|
|
2612
|
-
if (webSocket.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(message));
|
|
2613
|
-
};
|
|
2614
|
-
//#endregion
|
|
2615
|
-
//#region src/services/shell/createShellRegistry.ts
|
|
2616
|
-
const createShellRegistry = ({ onClose, onOutput, spawnShell }) => {
|
|
2617
|
-
const shellMap = /* @__PURE__ */ new Map();
|
|
2618
|
-
const pendingShells = /* @__PURE__ */ new Set();
|
|
2619
|
-
const closedSessionIds = /* @__PURE__ */ new Set();
|
|
2620
|
-
let isStopped = false;
|
|
2621
|
-
const getTerminal = (shellId) => {
|
|
2622
|
-
const shell = shellMap.get(shellId);
|
|
2623
|
-
if (!shell) throw new InvalidOperationError(Operation.Read, shellId, "the shell is not running on this host");
|
|
2624
|
-
return shell.terminal;
|
|
2625
|
-
};
|
|
2626
|
-
return {
|
|
2627
|
-
close: (shellId) => {
|
|
2628
|
-
getTerminal(shellId).kill();
|
|
2629
|
-
},
|
|
2630
|
-
closeAll: () => {
|
|
2631
|
-
isStopped = true;
|
|
2632
|
-
for (const pendingShell of pendingShells) pendingShell.isClosed = true;
|
|
2633
|
-
for (const { terminal } of shellMap.values()) terminal.kill();
|
|
2634
|
-
},
|
|
2635
|
-
closeSession: (sessionId) => {
|
|
2636
|
-
closedSessionIds.add(sessionId);
|
|
2637
|
-
for (const pendingShell of pendingShells) if (pendingShell.sessionId === sessionId) pendingShell.isClosed = true;
|
|
2638
|
-
for (const shell of shellMap.values()) if (shell.sessionId === sessionId) shell.terminal.kill();
|
|
2639
|
-
},
|
|
2640
|
-
entries: () => Array.from(shellMap, ([shellId, { output, sessionId }]) => ({
|
|
2641
|
-
output,
|
|
2642
|
-
sessionId,
|
|
2643
|
-
shellId
|
|
2644
|
-
})),
|
|
2645
|
-
open: async (sessionId, options) => {
|
|
2646
|
-
if (isStopped) throw new InvalidOperationError(Operation.Create, sessionId, "the host is stopping");
|
|
2647
|
-
if (closedSessionIds.has(sessionId)) throw new InvalidOperationError(Operation.Create, sessionId, "the session is closed");
|
|
2648
|
-
const pendingShell = {
|
|
2649
|
-
isClosed: false,
|
|
2650
|
-
sessionId
|
|
2651
|
-
};
|
|
2652
|
-
pendingShells.add(pendingShell);
|
|
2653
|
-
const terminal = await withFinalizerAsync(() => spawnShell(options), () => {
|
|
2654
|
-
pendingShells.delete(pendingShell);
|
|
2655
|
-
});
|
|
2656
|
-
if (pendingShell.isClosed) {
|
|
2657
|
-
terminal.kill();
|
|
2658
|
-
throw new InvalidOperationError(Operation.Create, sessionId, "the shell's session or host closed as it started");
|
|
2659
|
-
}
|
|
2660
|
-
const shellId = crypto.randomUUID();
|
|
2661
|
-
const shell = {
|
|
2662
|
-
output: "",
|
|
2663
|
-
sessionId,
|
|
2664
|
-
terminal
|
|
2665
|
-
};
|
|
2666
|
-
shellMap.set(shellId, shell);
|
|
2667
|
-
terminal.onData((data) => {
|
|
2668
|
-
shell.output = `${shell.output}${data}`.slice(-SHELL_OUTPUT_LENGTH);
|
|
2669
|
-
onOutput(shellId, data);
|
|
2670
|
-
});
|
|
2671
|
-
terminal.onExit(() => {
|
|
2672
|
-
shellMap.delete(shellId);
|
|
2673
|
-
onClose(shellId);
|
|
2674
|
-
});
|
|
2675
|
-
return shellId;
|
|
2676
|
-
},
|
|
2677
|
-
openSession: (sessionId) => {
|
|
2678
|
-
closedSessionIds.delete(sessionId);
|
|
2679
|
-
},
|
|
2680
|
-
resize: (shellId, cols, rows) => {
|
|
2681
|
-
getTerminal(shellId).resize(cols, rows);
|
|
2682
|
-
},
|
|
2683
|
-
write: (shellId, data) => {
|
|
2684
|
-
getTerminal(shellId).write(data);
|
|
2685
|
-
}
|
|
2686
|
-
};
|
|
2687
|
-
};
|
|
2688
|
-
//#endregion
|
|
2689
|
-
//#region src/services/server/createAgentConsoleServer.ts
|
|
2690
|
-
const createAgentConsoleServer = async ({ createDriver, hostKey, hostname, origin, port, spawnShell, stateDirectory, writeLine }) => {
|
|
2691
|
-
const eventLog = createEventLog();
|
|
2692
|
-
const taskRegistry = createTaskRegistry();
|
|
2693
|
-
const pairingCodes = createPairingCodes();
|
|
2694
|
-
const publicKey = createPublicKey(hostKey);
|
|
2695
|
-
const publicKeyText = getPublicKeyText(hostKey);
|
|
2696
|
-
const webSocketServer = new WebSocketServer({ noServer: true });
|
|
2697
|
-
const socketDeviceIdMap = /* @__PURE__ */ new Map();
|
|
2698
|
-
const broadcast = (message) => {
|
|
2699
|
-
for (const webSocket of socketDeviceIdMap.keys()) sendServerMessage(webSocket, message);
|
|
2700
|
-
};
|
|
2701
|
-
let isRefreshing = false;
|
|
2702
|
-
let isRefreshStale = false;
|
|
2703
|
-
const refreshSessions = () => {
|
|
2704
|
-
if (isRefreshing) {
|
|
2705
|
-
isRefreshStale = true;
|
|
2706
|
-
return;
|
|
2707
|
-
}
|
|
2708
|
-
isRefreshing = true;
|
|
2709
|
-
taskRegistry.run(async () => {
|
|
2710
|
-
do {
|
|
2711
|
-
isRefreshStale = false;
|
|
2712
|
-
await getResultAsync(() => driver.listSessions()).match((sessions) => {
|
|
2713
|
-
broadcast({
|
|
2714
|
-
sessions,
|
|
2715
|
-
type: "Sessions"
|
|
2716
|
-
});
|
|
2717
|
-
}, console.error);
|
|
2718
|
-
} while (isRefreshStale);
|
|
2719
|
-
isRefreshing = false;
|
|
2720
|
-
});
|
|
2721
|
-
};
|
|
2722
|
-
const shellRegistry = createShellRegistry({
|
|
2723
|
-
onClose: (shellId) => {
|
|
2724
|
-
broadcast({
|
|
2725
|
-
shellId,
|
|
2726
|
-
type: "ShellClosed"
|
|
2727
|
-
});
|
|
2728
|
-
},
|
|
2729
|
-
onOutput: (shellId, data) => {
|
|
2730
|
-
broadcast({
|
|
2731
|
-
data,
|
|
2732
|
-
shellId,
|
|
2733
|
-
type: "ShellOutput"
|
|
2734
|
-
});
|
|
2735
|
-
},
|
|
2736
|
-
spawnShell
|
|
2737
|
-
});
|
|
2738
|
-
const driver = createDriver({
|
|
2739
|
-
onEvents: (sessionId, events) => {
|
|
2740
|
-
const newEvents = eventLog.append(sessionId, events);
|
|
2741
|
-
if (newEvents.length > 0) broadcast({
|
|
2742
|
-
events: newEvents,
|
|
2743
|
-
sessionId,
|
|
2744
|
-
type: "Events"
|
|
2745
|
-
});
|
|
2746
|
-
if (events.some((event) => event.type === "SessionState" && event.state === "Closed")) shellRegistry.closeSession(sessionId);
|
|
2747
|
-
},
|
|
2748
|
-
onSessionOpen: (sessionId) => {
|
|
2749
|
-
shellRegistry.openSession(sessionId);
|
|
2750
|
-
eventLog.reset(sessionId);
|
|
2751
|
-
broadcast({
|
|
2752
|
-
sessionId,
|
|
2753
|
-
type: "SessionReset"
|
|
2754
|
-
});
|
|
2755
|
-
},
|
|
2756
|
-
onSessionsChange: () => {
|
|
2757
|
-
refreshSessions();
|
|
2758
|
-
}
|
|
2759
|
-
});
|
|
2760
|
-
const receive = async (webSocket, data) => {
|
|
2761
|
-
const command = getResult(() => commandSchema.parse(JSON.parse(readMessageText(data)))).orTee((error) => {
|
|
2762
|
-
sendServerMessage(webSocket, {
|
|
2763
|
-
commandId: "",
|
|
2764
|
-
message: error.message,
|
|
2765
|
-
type: "CommandError"
|
|
2766
|
-
});
|
|
2767
|
-
}).unwrapOr(void 0);
|
|
2768
|
-
if (!command) return;
|
|
2769
|
-
const sendCommandError = (error) => {
|
|
2770
|
-
sendServerMessage(webSocket, {
|
|
2771
|
-
commandId: command.id,
|
|
2772
|
-
message: error.message,
|
|
2773
|
-
type: "CommandError"
|
|
2774
|
-
});
|
|
2775
|
-
};
|
|
2776
|
-
switch (command.type) {
|
|
2777
|
-
case "CloseShell":
|
|
2778
|
-
case "OpenShell":
|
|
2779
|
-
case "ShellInput":
|
|
2780
|
-
case "ShellResize":
|
|
2781
|
-
await getResultAsync(() => handleShellCommand(shellRegistry, command)).match((shellId) => {
|
|
2782
|
-
if (command.type === "OpenShell") sendServerMessage(webSocket, {
|
|
2783
|
-
commandId: command.id,
|
|
2784
|
-
sessionId: command.sessionId,
|
|
2785
|
-
shellId,
|
|
2786
|
-
type: "ShellOpened"
|
|
2787
|
-
});
|
|
2788
|
-
}, sendCommandError);
|
|
2789
|
-
return;
|
|
2790
|
-
case "ListFiles":
|
|
2791
|
-
case "ReadFile":
|
|
2792
|
-
await getResultAsync(() => handleFileCommand(driver, command)).match((message) => {
|
|
2793
|
-
sendServerMessage(webSocket, message);
|
|
2794
|
-
}, sendCommandError);
|
|
2795
|
-
return;
|
|
2796
|
-
default: await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
|
|
2797
|
-
if (sessionId) sendServerMessage(webSocket, {
|
|
2798
|
-
commandId: command.id,
|
|
2799
|
-
sessionId,
|
|
2800
|
-
type: "SessionOpened"
|
|
2801
|
-
});
|
|
2802
|
-
else if (command.type === "ListSessions") refreshSessions();
|
|
2803
|
-
}, sendCommandError);
|
|
2804
|
-
}
|
|
2805
|
-
};
|
|
2806
|
-
const admit = (webSocket, deviceId) => {
|
|
2807
|
-
socketDeviceIdMap.set(webSocket, deviceId);
|
|
2808
|
-
for (const [sessionId, events] of eventLog.entries()) sendServerMessage(webSocket, {
|
|
2809
|
-
events,
|
|
2810
|
-
sessionId,
|
|
2811
|
-
type: "Events"
|
|
2812
|
-
});
|
|
2813
|
-
for (const { output, sessionId, shellId } of shellRegistry.entries()) {
|
|
2814
|
-
sendServerMessage(webSocket, {
|
|
2815
|
-
commandId: "",
|
|
2816
|
-
sessionId,
|
|
2817
|
-
shellId,
|
|
2818
|
-
type: "ShellOpened"
|
|
2819
|
-
});
|
|
2820
|
-
if (output) sendServerMessage(webSocket, {
|
|
2821
|
-
data: output,
|
|
2822
|
-
shellId,
|
|
2823
|
-
type: "ShellOutput"
|
|
2824
|
-
});
|
|
2825
|
-
}
|
|
2826
|
-
refreshSessions();
|
|
2827
|
-
};
|
|
2828
|
-
const pair = (webSocket, request, code) => {
|
|
2829
|
-
if (request.headers.origin !== origin || !pairingCodes.take(code)) {
|
|
2830
|
-
webSocket.close(4403);
|
|
2831
|
-
return;
|
|
2832
|
-
}
|
|
2833
|
-
const credential = randomBytes(32).toString("base64url");
|
|
2834
|
-
const device = {
|
|
2835
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
2836
|
-
credentialHash: hashCredential(credential),
|
|
2837
|
-
id: crypto.randomUUID(),
|
|
2838
|
-
name: getDeviceName(request.headers["user-agent"] ?? ""),
|
|
2839
|
-
origin
|
|
2840
|
-
};
|
|
2841
|
-
writeDevices(stateDirectory, [...readDevices(stateDirectory), device]);
|
|
2842
|
-
writeLine(`${device.name} connected. To remove it: devices --revoke ${device.id}`);
|
|
2843
|
-
sendServerMessage(webSocket, {
|
|
2844
|
-
credential,
|
|
2845
|
-
deviceId: device.id,
|
|
2846
|
-
publicKey: publicKeyText,
|
|
2847
|
-
type: "Paired"
|
|
2848
|
-
});
|
|
2849
|
-
admit(webSocket, device.id);
|
|
2850
|
-
};
|
|
2851
|
-
const receiveHandshake = (webSocket, request, hostNonce, handshakeMessage) => {
|
|
2852
|
-
const localPort = request.socket.localPort ?? 0;
|
|
2853
|
-
switch (handshakeMessage.type) {
|
|
2854
|
-
case "Authenticate": {
|
|
2855
|
-
const device = findDevice(readDevices(stateDirectory), handshakeMessage.credential);
|
|
2856
|
-
if (!device) {
|
|
2857
|
-
webSocket.close(4401);
|
|
2858
|
-
return;
|
|
2859
|
-
}
|
|
2860
|
-
sendServerMessage(webSocket, { type: "Authenticated" });
|
|
2861
|
-
admit(webSocket, device.id);
|
|
2862
|
-
return;
|
|
2863
|
-
}
|
|
2864
|
-
case "Challenge":
|
|
2865
|
-
sendServerMessage(webSocket, {
|
|
2866
|
-
nonce: hostNonce,
|
|
2867
|
-
port: localPort,
|
|
2868
|
-
signature: signNonce(hostKey, "HostProof", localPort, handshakeMessage.nonce),
|
|
2869
|
-
type: "Proof"
|
|
2870
|
-
});
|
|
2871
|
-
return;
|
|
2872
|
-
case "HandOff":
|
|
2873
|
-
case "Revoke":
|
|
2874
|
-
if (!checkIsSignatureValid(publicKey, "OwnerProof", localPort, hostNonce, handshakeMessage.signature)) {
|
|
2875
|
-
webSocket.close(4404);
|
|
2876
|
-
return;
|
|
2877
|
-
}
|
|
2878
|
-
if (handshakeMessage.type === "HandOff") pairingCodes.add(handshakeMessage.code, SCHEME_PAIRING_CODE_DURATION_MS);
|
|
2879
|
-
else {
|
|
2880
|
-
writeDevices(stateDirectory, readDevices(stateDirectory).filter(({ id }) => id !== handshakeMessage.deviceId));
|
|
2881
|
-
for (const [deviceWebSocket, deviceId] of socketDeviceIdMap) if (deviceId === handshakeMessage.deviceId) deviceWebSocket.close(4401);
|
|
2882
|
-
writeLine("A device was removed, and its connection closed.");
|
|
2883
|
-
}
|
|
2884
|
-
webSocket.close();
|
|
2885
|
-
return;
|
|
2886
|
-
case "Pair":
|
|
2887
|
-
pair(webSocket, request, handshakeMessage.code);
|
|
2888
|
-
return;
|
|
2889
|
-
default: exhaustiveGuard(handshakeMessage);
|
|
2890
|
-
}
|
|
2891
|
-
};
|
|
2892
|
-
webSocketServer.on("connection", (webSocket, request) => {
|
|
2893
|
-
const hostNonce = randomBytes(32).toString("base64url");
|
|
2894
|
-
webSocket.on("message", (data) => {
|
|
2895
|
-
if (socketDeviceIdMap.has(webSocket)) {
|
|
2896
|
-
taskRegistry.run(() => receive(webSocket, data));
|
|
2897
|
-
return;
|
|
2898
|
-
}
|
|
2899
|
-
getResult(() => {
|
|
2900
|
-
receiveHandshake(webSocket, request, hostNonce, handshakeMessageSchema.parse(JSON.parse(readMessageText(data))));
|
|
2901
|
-
}).match(noop, (error) => {
|
|
2902
|
-
console.error(error);
|
|
2903
|
-
webSocket.close(4400);
|
|
2904
|
-
});
|
|
2905
|
-
});
|
|
2906
|
-
webSocket.on("close", () => {
|
|
2907
|
-
socketDeviceIdMap.delete(webSocket);
|
|
2908
|
-
});
|
|
2909
|
-
});
|
|
2910
|
-
const httpServer = createServer((request, response) => {
|
|
2911
|
-
answerHttpRequest(request, response);
|
|
2912
|
-
});
|
|
2913
|
-
httpServer.on("upgrade", (request, socket, head) => {
|
|
2914
|
-
if (request.headers.origin !== void 0 && request.headers.origin !== origin) {
|
|
2915
|
-
socket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
2916
|
-
return;
|
|
2917
|
-
}
|
|
2918
|
-
webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
|
|
2919
|
-
webSocketServer.emit("connection", webSocket, request);
|
|
2920
|
-
});
|
|
2921
|
-
});
|
|
2922
|
-
httpServer.listen(port, hostname);
|
|
2923
|
-
await once(httpServer, "listening");
|
|
2924
|
-
const address = httpServer.address();
|
|
2925
|
-
let closing;
|
|
2926
|
-
const close = async () => {
|
|
2927
|
-
broadcast({ type: "HostStopping" });
|
|
2928
|
-
pairingCodes.clear();
|
|
2929
|
-
shellRegistry.closeAll();
|
|
2930
|
-
await driver.close();
|
|
2931
|
-
for (const webSocket of webSocketServer.clients) webSocket.terminate();
|
|
2932
|
-
webSocketServer.close();
|
|
2933
|
-
httpServer.close();
|
|
2934
|
-
await Promise.all([
|
|
2935
|
-
taskRegistry.drain(),
|
|
2936
|
-
once(webSocketServer, "close"),
|
|
2937
|
-
once(httpServer, "close")
|
|
2938
|
-
]);
|
|
2939
|
-
};
|
|
2940
|
-
return {
|
|
2941
|
-
addPairingCode: pairingCodes.add,
|
|
2942
|
-
close: () => closing ??= close(),
|
|
2943
|
-
port: typeof address === "object" && address ? address.port : port
|
|
2944
|
-
};
|
|
2945
|
-
};
|
|
2946
|
-
//#endregion
|
|
2947
|
-
//#region src/services/shell/getShellFile.ts
|
|
2948
|
-
const POWERSHELL_FILE = "pwsh.exe";
|
|
2949
|
-
const WINDOWS_POWERSHELL_FILE = "powershell.exe";
|
|
2950
|
-
const getShellFile = () => {
|
|
2951
|
-
if (process.platform !== "win32") return process.env.SHELL ?? "/bin/sh";
|
|
2952
|
-
return (process.env.PATH ?? "").split(delimiter).filter(Boolean).some((pathDirectory) => existsSync(join(pathDirectory, POWERSHELL_FILE))) ? POWERSHELL_FILE : WINDOWS_POWERSHELL_FILE;
|
|
2953
|
-
};
|
|
2954
|
-
//#endregion
|
|
2955
|
-
//#region src/services/shell/loadNodePty.ts
|
|
2956
|
-
const loadNodePty = () => {
|
|
2957
|
-
if (!isSea()) return import("node-pty");
|
|
2958
|
-
const installDirectory = getHostInstallDirectory();
|
|
2959
|
-
return Promise.resolve(createRequire(join(installDirectory, "package.json"))(join(installDirectory, NODE_PTY_DIRECTORY_NAME)));
|
|
2960
|
-
};
|
|
2961
|
-
//#endregion
|
|
2962
|
-
//#region src/services/shell/spawnPtyShell.ts
|
|
2963
|
-
const spawnPtyShell = async ({ cols, cwd, rows }) => {
|
|
2964
|
-
const { spawn } = await getResultAsync(() => loadNodePty()).match((nodePty) => nodePty, () => {
|
|
2965
|
-
throw new InvalidOperationError(Operation.Create, cwd, "this host has no terminal support, so no shell can start");
|
|
2966
|
-
});
|
|
2967
|
-
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => key.toLowerCase() !== "psmodulepath"));
|
|
2968
|
-
return spawn(getShellFile(), [], {
|
|
2969
|
-
cols,
|
|
2970
|
-
cwd,
|
|
2971
|
-
env,
|
|
2972
|
-
name: "xterm-256color",
|
|
2973
|
-
rows
|
|
2974
|
-
});
|
|
2975
|
-
};
|
|
2976
|
-
//#endregion
|
|
2977
|
-
//#region src/services/cli/serveHost.ts
|
|
2978
|
-
const serveHost = async ({ hostname, origin, port, schemeLaunch }) => {
|
|
2979
|
-
installDownloadedHost();
|
|
2980
|
-
const stateDirectory = getStateDirectory();
|
|
2981
|
-
const hostKey = readHostKey(stateDirectory);
|
|
2982
|
-
const reachableHostname = getReachableHostname(hostname);
|
|
2983
|
-
const server = await getResultAsync(() => createAgentConsoleServer({
|
|
2984
|
-
createDriver: (callbacks) => process.platform === "win32" && hostname === "127.0.0.1" ? createWindowDriver(callbacks, {
|
|
2985
|
-
launchSessionWindow,
|
|
2986
|
-
stateDirectory,
|
|
2987
|
-
writeLine
|
|
2988
|
-
}) : createClaudeAgentSdkDriver(callbacks),
|
|
2989
|
-
hostKey,
|
|
2990
|
-
hostname,
|
|
2991
|
-
origin,
|
|
2992
|
-
port,
|
|
2993
|
-
spawnShell: spawnPtyShell,
|
|
2994
|
-
stateDirectory,
|
|
2995
|
-
writeLine
|
|
2996
|
-
})).match((agentConsoleServer) => agentConsoleServer, async (error) => {
|
|
2997
|
-
if (!schemeLaunch || !("code" in error) || error.code !== "EADDRINUSE") throw error;
|
|
2998
|
-
const { code } = schemeLaunch;
|
|
2999
|
-
if (await sendToRunningHost(reachableHostname, port, hostKey, code ? (signature) => ({
|
|
3000
|
-
code,
|
|
3001
|
-
signature,
|
|
3002
|
-
type: "HandOff"
|
|
3003
|
-
}) : void 0)) {
|
|
3004
|
-
process.stdout.write("The host is already running in another window.\n");
|
|
3005
|
-
process.exit(0);
|
|
3006
|
-
}
|
|
3007
|
-
process.stderr.write(`Another program is using port ${port}, so the host cannot start. Close it and connect again.\n`);
|
|
3008
|
-
process.exit(1);
|
|
3009
|
-
});
|
|
3010
|
-
if (schemeLaunch) {
|
|
3011
|
-
if (schemeLaunch.code) server.addPairingCode(schemeLaunch.code, SCHEME_PAIRING_CODE_DURATION_MS);
|
|
3012
|
-
process.stdout.write("The host is running. Keep this window open.\n\nTo stop, close this window.\n");
|
|
3013
|
-
} else {
|
|
3014
|
-
const code = randomBytes(32).toString("base64url");
|
|
3015
|
-
server.addPairingCode(code, PRINTED_PAIRING_CODE_DURATION_MS);
|
|
3016
|
-
const hostAddress = `ws://${reachableHostname}:${server.port}`;
|
|
3017
|
-
const pairingUrl = `${origin}${RoutePath.Genshin}#${new URLSearchParams({
|
|
3018
|
-
[PAIRING_CODE_PARAMETER]: code,
|
|
3019
|
-
[PAIRING_HASH_PARAMETER]: hostAddress
|
|
3020
|
-
})}`;
|
|
3021
|
-
process.stdout.write(`The host is running. Keep this window open.\n\nTo connect, press Connect on the page, or hold Ctrl and click this link in the next ten minutes:\n ${pairingUrl}\n\nTo add this machine from another computer, enter its address and this code in the page's sessions tab:\n ${code}\n\nTo stop, close this window.\n`);
|
|
3022
|
-
}
|
|
3023
|
-
await Promise.race([once(process, "SIGINT"), once(process, "SIGHUP")]);
|
|
3024
|
-
await server.close();
|
|
3025
|
-
};
|
|
3026
|
-
//#endregion
|
|
3027
|
-
//#region src/services/cli/commands/serveCommand.ts
|
|
3028
|
-
const serveCommand = defineCommand({
|
|
3029
|
-
args: { ...hostArgs },
|
|
3030
|
-
meta: {
|
|
3031
|
-
description: "Start the host and print the link that pairs a page with it",
|
|
3032
|
-
name: "serve"
|
|
3033
|
-
},
|
|
3034
|
-
run: ({ args }) => serveHost({
|
|
3035
|
-
hostname: args.hostname,
|
|
3036
|
-
origin: args.origin,
|
|
3037
|
-
port: Number(args.port)
|
|
3038
|
-
})
|
|
3039
|
-
});
|
|
3040
|
-
//#endregion
|
|
3041
|
-
//#region src/services/drivers/window/formatSessionLogLine.ts
|
|
3042
|
-
const formatSessionLogLine = (event) => {
|
|
3043
|
-
switch (event.type) {
|
|
3044
|
-
case "AssistantMessage": return event.parentToolUseId ? "" : `Claude: ${event.text}`;
|
|
3045
|
-
case "HostError": return `Error: ${event.message}`;
|
|
3046
|
-
case "PermissionRequest": return `Claude asks to use ${event.toolName}. Answer in the page.`;
|
|
3047
|
-
case "ToolUse": return event.parentToolUseId ? "" : `Claude uses ${event.name}`;
|
|
3048
|
-
case "UserMessage": return event.parentToolUseId ? "" : `You: ${event.text}`;
|
|
3049
|
-
default: return "";
|
|
3050
|
-
}
|
|
3051
|
-
};
|
|
3052
|
-
//#endregion
|
|
3053
|
-
//#region src/services/drivers/window/serveSessionChild.ts
|
|
3054
|
-
const serveSessionChild = async (connect, createDriver, writeLine, signal) => {
|
|
3055
|
-
const taskRegistry = createTaskRegistry();
|
|
3056
|
-
const sessionEventMap = /* @__PURE__ */ new Map();
|
|
3057
|
-
let webSocket;
|
|
3058
|
-
const send = (childMessage) => {
|
|
3059
|
-
if (webSocket?.readyState === WebSocket.OPEN) webSocket.send(JSON.stringify(childMessage));
|
|
3060
|
-
};
|
|
3061
|
-
const driver = createDriver({
|
|
3062
|
-
onEvents: (sessionId, events) => {
|
|
3063
|
-
for (const event of events) {
|
|
3064
|
-
const line = formatSessionLogLine(event);
|
|
3065
|
-
if (line) writeLine(line);
|
|
3066
|
-
}
|
|
3067
|
-
getOrCreate(sessionEventMap, sessionId, () => []).push(...events.filter(({ type }) => !EphemeralAgentEventTypes.includes(type)));
|
|
3068
|
-
if (events.some((event) => event.type === "SessionState" && event.state === "Closed")) sessionEventMap.delete(sessionId);
|
|
3069
|
-
send({
|
|
3070
|
-
events,
|
|
3071
|
-
sessionId,
|
|
3072
|
-
type: "Events"
|
|
3073
|
-
});
|
|
3074
|
-
},
|
|
3075
|
-
onSessionOpen: (sessionId) => {
|
|
3076
|
-
sessionEventMap.set(sessionId, []);
|
|
3077
|
-
send({
|
|
3078
|
-
sessionId,
|
|
3079
|
-
type: "SessionOpen"
|
|
3080
|
-
});
|
|
3081
|
-
},
|
|
3082
|
-
onSessionsChange: () => {
|
|
3083
|
-
send({ type: "SessionsChange" });
|
|
3084
|
-
}
|
|
3085
|
-
});
|
|
3086
|
-
const receive = async (text) => {
|
|
3087
|
-
const command = getResult(() => driverCommandSchema.parse(JSON.parse(text))).orTee((error) => {
|
|
3088
|
-
send({
|
|
3089
|
-
commandId: "",
|
|
3090
|
-
message: error.message,
|
|
3091
|
-
type: "CommandError"
|
|
3092
|
-
});
|
|
3093
|
-
}).unwrapOr(void 0);
|
|
3094
|
-
if (!command) return;
|
|
3095
|
-
await getResultAsync(() => handleCommand(driver, command)).match((sessionId) => {
|
|
3096
|
-
send({
|
|
3097
|
-
commandId: command.id,
|
|
3098
|
-
sessionId,
|
|
3099
|
-
type: "CommandResult"
|
|
3100
|
-
});
|
|
3101
|
-
}, (error) => {
|
|
3102
|
-
send({
|
|
3103
|
-
commandId: command.id,
|
|
3104
|
-
message: error.message,
|
|
3105
|
-
type: "CommandError"
|
|
3106
|
-
});
|
|
3107
|
-
});
|
|
3108
|
-
};
|
|
3109
|
-
signal.addEventListener("abort", () => {
|
|
3110
|
-
webSocket?.close();
|
|
3111
|
-
});
|
|
3112
|
-
let isRejoining = false;
|
|
3113
|
-
while (!signal.aborted) {
|
|
3114
|
-
const currentWebSocket = connect();
|
|
3115
|
-
webSocket = currentWebSocket;
|
|
3116
|
-
let isRefused = false;
|
|
3117
|
-
currentWebSocket.on("message", (data) => {
|
|
3118
|
-
taskRegistry.run(() => receive(readMessageText(data)));
|
|
3119
|
-
});
|
|
3120
|
-
currentWebSocket.on("error", noop);
|
|
3121
|
-
currentWebSocket.on("unexpected-response", () => {
|
|
3122
|
-
isRefused = true;
|
|
3123
|
-
currentWebSocket.terminate();
|
|
3124
|
-
});
|
|
3125
|
-
if (isRejoining) currentWebSocket.once("open", () => {
|
|
3126
|
-
writeLine("The host is back, and has this session again.");
|
|
3127
|
-
send({
|
|
3128
|
-
sessions: Array.from(sessionEventMap, ([sessionId, events]) => ({
|
|
3129
|
-
events,
|
|
3130
|
-
sessionId
|
|
3131
|
-
})),
|
|
3132
|
-
type: "Rejoin"
|
|
3133
|
-
});
|
|
3134
|
-
});
|
|
3135
|
-
const [code] = await once(currentWebSocket, "close");
|
|
3136
|
-
if (isRefused && isRejoining) writeLine("The host did not take this session back, so it ends here.");
|
|
3137
|
-
if (signal.aborted || isRefused || code === 4e3 || sessionEventMap.size === 0) break;
|
|
3138
|
-
if (!isRejoining) writeLine("The host went away. This session keeps running, and comes back when the host is started again.");
|
|
3139
|
-
isRejoining = true;
|
|
3140
|
-
await setTimeout$1(SESSION_WINDOW_RECONNECT_DELAY_MS);
|
|
3141
|
-
}
|
|
3142
|
-
await driver.close();
|
|
3143
|
-
await taskRegistry.drain();
|
|
3144
|
-
};
|
|
3145
|
-
//#endregion
|
|
3146
|
-
//#region src/services/drivers/window/runSessionChild.ts
|
|
3147
|
-
const runSessionChild = async (port, writeLine) => {
|
|
3148
|
-
const secret = process.env[SESSION_SECRET_ENVIRONMENT_VARIABLE] ?? "";
|
|
3149
|
-
Reflect.deleteProperty(process.env, SESSION_SECRET_ENVIRONMENT_VARIABLE);
|
|
3150
|
-
process.title = `${SITE_NAME} session`;
|
|
3151
|
-
const stateDirectory = getStateDirectory();
|
|
3152
|
-
let isFirstConnection = true;
|
|
3153
|
-
const connect = () => {
|
|
3154
|
-
const hostPort = isFirstConnection ? port : readSessionWindows(stateDirectory).port;
|
|
3155
|
-
isFirstConnection = false;
|
|
3156
|
-
return new WebSocket(`ws://${DEFAULT_HOSTNAME}:${hostPort}`, { headers: { authorization: `Bearer ${secret}` } });
|
|
3157
|
-
};
|
|
3158
|
-
const abortController = new AbortController();
|
|
3159
|
-
const stop = () => {
|
|
3160
|
-
abortController.abort();
|
|
3161
|
-
};
|
|
3162
|
-
process.once("SIGINT", stop);
|
|
3163
|
-
process.once("SIGHUP", stop);
|
|
3164
|
-
writeLine("This window shows one session as it runs. Type to it in the page.\nTo stop it, close this window.\n");
|
|
3165
|
-
await serveSessionChild(connect, createClaudeAgentSdkDriver, writeLine, abortController.signal);
|
|
3166
|
-
};
|
|
3167
|
-
//#endregion
|
|
3168
|
-
//#region src/services/cli/commands/sessionCommand.ts
|
|
3169
|
-
const sessionCommand = defineCommand({
|
|
3170
|
-
args: { port: {
|
|
3171
|
-
description: "The port of the host that started this window",
|
|
3172
|
-
required: true,
|
|
3173
|
-
type: "string"
|
|
3174
|
-
} },
|
|
3175
|
-
meta: {
|
|
3176
|
-
description: "Serve one session in a window of its own, for the host that started it",
|
|
3177
|
-
name: SESSION_SUBCOMMAND
|
|
3178
|
-
},
|
|
3179
|
-
run: async ({ args }) => {
|
|
3180
|
-
await runSessionChild(Number(args.port), writeLine);
|
|
3181
|
-
process.exit(0);
|
|
3182
|
-
}
|
|
3183
|
-
});
|
|
3184
|
-
//#endregion
|
|
3185
|
-
//#region src/services/installer/uninstallHost.ts
|
|
3186
|
-
const uninstallHost = () => {
|
|
3187
|
-
execFileSync$1("reg", [
|
|
3188
|
-
"delete",
|
|
3189
|
-
HOST_SCHEME_REGISTRY_KEY,
|
|
3190
|
-
"/f"
|
|
3191
|
-
], { stdio: "ignore" });
|
|
3192
|
-
spawn$1("powershell.exe", [
|
|
3193
|
-
"-NoProfile",
|
|
3194
|
-
"-Command",
|
|
3195
|
-
`Start-Sleep -Seconds 2; Remove-Item -LiteralPath $env:${HOST_DIRECTORY_ENVIRONMENT_VARIABLE} -Recurse -Force`
|
|
3196
|
-
], {
|
|
3197
|
-
detached: true,
|
|
3198
|
-
env: {
|
|
3199
|
-
...process.env,
|
|
3200
|
-
[HOST_DIRECTORY_ENVIRONMENT_VARIABLE]: getHostInstallDirectory()
|
|
3201
|
-
},
|
|
3202
|
-
stdio: "ignore"
|
|
3203
|
-
}).unref();
|
|
3204
|
-
};
|
|
3205
|
-
//#endregion
|
|
3206
|
-
//#region src/services/cli/commands/uninstallCommand.ts
|
|
3207
|
-
const uninstallCommand = defineCommand({
|
|
3208
|
-
meta: {
|
|
3209
|
-
description: "Remove the installed host, its link and its folder",
|
|
3210
|
-
name: "uninstall"
|
|
3211
|
-
},
|
|
3212
|
-
run: () => {
|
|
3213
|
-
uninstallHost();
|
|
3214
|
-
process.stdout.write("The host is uninstalled, and its folder is removed once this window closes.\n");
|
|
3215
|
-
process.exit(0);
|
|
3216
|
-
}
|
|
3217
|
-
});
|
|
3218
|
-
//#endregion
|
|
3219
|
-
//#region src/services/installer/checkIsSchemeLaunchTampered.ts
|
|
3220
|
-
const checkIsSchemeLaunchTampered = (commandLineArguments) => commandLineArguments.length > 1 && commandLineArguments.some((commandLineArgument) => commandLineArgument.startsWith(`${HOST_SCHEME}:`));
|
|
3221
|
-
//#endregion
|
|
3222
|
-
//#region src/services/installer/getSchemeLaunch.ts
|
|
3223
|
-
const getSchemeLaunch = (argument) => {
|
|
3224
|
-
if (!URL.canParse(argument)) return void 0;
|
|
3225
|
-
const url = new URL(argument);
|
|
3226
|
-
if (url.protocol === `${HOST_SCHEME}:`) return { code: url.searchParams.get("code") ?? "" };
|
|
3227
|
-
else return void 0;
|
|
3228
|
-
};
|
|
3229
|
-
//#endregion
|
|
3230
|
-
//#region src/services/shell/constants.ts
|
|
3231
|
-
const CONSOLE_LIST_AGENT_NAME = "conpty_console_list_agent";
|
|
3232
|
-
//#endregion
|
|
3233
|
-
//#region src/services/shell/runConsoleListAgent.ts
|
|
3234
|
-
const runConsoleListAgent = (shellPid) => {
|
|
3235
|
-
const installDirectory = getHostInstallDirectory();
|
|
3236
|
-
const { getConsoleProcessList } = createRequire(join(installDirectory, "package.json"))(join(installDirectory, NODE_PTY_DIRECTORY_NAME, "prebuilds", "win32-x64", "conpty_console_list.node"));
|
|
3237
|
-
process.send?.({ consoleProcessList: getConsoleProcessList(shellPid) });
|
|
3238
|
-
};
|
|
3239
|
-
//#endregion
|
|
3240
|
-
//#region src/services/cli/commands/agentConsoleServerCommand.ts
|
|
3241
|
-
const agentConsoleServerCommand = defineCommand({
|
|
3242
|
-
default: "serve",
|
|
3243
|
-
meta: {
|
|
3244
|
-
description: "The agent console's host: Claude Code sessions for a paired page",
|
|
3245
|
-
name: "agent-console-server"
|
|
3246
|
-
},
|
|
3247
|
-
setup: async ({ rawArgs }) => {
|
|
3248
|
-
if (checkIsSchemeLaunchTampered(rawArgs)) {
|
|
3249
|
-
process.stderr.write("This link tried to start the host with settings of its own, so it was not started.\n");
|
|
3250
|
-
process.exit(1);
|
|
3251
|
-
}
|
|
3252
|
-
const [firstArgument = "", secondArgument = ""] = rawArgs;
|
|
3253
|
-
if (basename(firstArgument) === "conpty_console_list_agent") {
|
|
3254
|
-
runConsoleListAgent(Number(secondArgument));
|
|
3255
|
-
process.exit(0);
|
|
3256
|
-
}
|
|
3257
|
-
const schemeLaunch = getSchemeLaunch(firstArgument);
|
|
3258
|
-
if (!schemeLaunch) return;
|
|
3259
|
-
await serveHost({
|
|
3260
|
-
hostname: DEFAULT_HOSTNAME,
|
|
3261
|
-
origin: DEFAULT_APP_ORIGIN,
|
|
3262
|
-
port: DEFAULT_PORT,
|
|
3263
|
-
schemeLaunch
|
|
3264
|
-
});
|
|
3265
|
-
process.exit(0);
|
|
3266
|
-
},
|
|
3267
|
-
subCommands: {
|
|
3268
|
-
devices: devicesCommand,
|
|
3269
|
-
serve: serveCommand,
|
|
3270
|
-
[SESSION_SUBCOMMAND]: sessionCommand,
|
|
3271
|
-
uninstall: uninstallCommand
|
|
3272
|
-
}
|
|
3273
|
-
});
|
|
3274
|
-
//#endregion
|
|
3275
|
-
export { childSessionOpenMessageSchema as $, signNonce as $t, createEventLog as A, mapAssistantContent as At, readSessionWindows as B, DEFAULT_DENY_MESSAGE as Bt, resolveRepositoryPath as C, hostArgs as Cn, toSessionInitEvent as Ct, getSessionCwd as D, mapSystemMessage as Dt, REPOSITORY_WALK_IGNORED_NAMES as E, toToolResultText as Et, launchSessionWindow as F, createFileOriginTracker as Ft, SESSION_WINDOWS_FILENAME as G, toSessionClosedEvents as Gt, SESSION_SECRET_BYTE_LENGTH as H, SESSION_LIST_LIMIT as Ht, createWindowDriver as I, createPermissionBridge as It, SESSION_WINDOW_REJOIN_DURATION_MS as J, runMain as Jt, SESSION_WINDOW_CONNECT_TIMEOUT_MS as K, getEventId as Kt, checkIsTokenValid as L, toPermissionResult as Lt, getPublicKeyText as M, createTodoTracker as Mt, getDeviceName as N, TodoToolName as Nt, handleCommand as O, mapTaskMessage as Ot, findDevice as P, createStreamTracker as Pt, childSessionsChangeMessageSchema as Q, HOST_PROBE_TIMEOUT_MS as Qt, takeChildReply as R, toPermissionRequestEvent as Rt, readRepositoryFile as S, NODE_PTY_DIRECTORY_NAME as Sn, toTurnResultEvents as St, REPOSITORY_FILE_MAX_BYTE_LENGTH as T, mapUserContent as Tt, SESSION_SECRET_ENVIRONMENT_VARIABLE as U, SETTING_SOURCES as Ut, sessionWindowsSchema as V, IGNORED_SDK_MESSAGE_TYPES as Vt, SESSION_SUBCOMMAND as W, closeOpenSession as Wt, SessionWindowCloseCode as X, sendToRunningHost as Xt, hashCredential as Y, runDevicesCommand as Yt, childMessageSchema as Z, readMessageText as Zt, createAgentConsoleServer as _, CLAUDE_CODE_EXECUTABLE_FILENAME as _n, toCapabilitiesEvent as _t, checkIsSchemeLaunchTampered as a, writeStateFile as an, ChildMessageType as at, handleShellCommand as b, HOST_INSTALL_DIRECTORY_SEGMENTS as bn, readSessionHistory as bt, sessionCommand as c, HOST_KEY_FILENAME as cn, createClaudeAgentSdkDriver as ct, formatSessionLogLine as d, STATE_DIRECTORY_NAME as dn, readSessionCwd as dt, checkIsSignatureValid as en, childRejoinMessageSchema as et, serveCommand as f, writeLine as fn, listSessionSummaries as ft, getShellFile as g, getHostInstallDirectory as gn, toContextUsageEvent as gt, loadNodePty as h, checkIsHostInstalled as hn, watchSession as ht, getSchemeLaunch as i, readHostKey as in, childCommandErrorMessageSchema as it, answerHttpRequest as j, toUnknownEvent as jt, createPairingCodes as k, mapHookMessage as kt, runSessionChild as l, PRINTED_PAIRING_CODE_DURATION_MS as ln, createTaskRegistry as lt, spawnPtyShell as m, installHost as mn, getClaudeCodeExecutablePath as mt, runConsoleListAgent as n, readDevices as nn, childEventsMessageSchema as nt, uninstallCommand as o, getStateDirectory as on, childMessageTypeSchema as ot, serveHost as p, installDownloadedHost as pn, createSessionOpener as pt, SESSION_WINDOW_RECONNECT_DELAY_MS as q, devicesCommand as qt, CONSOLE_LIST_AGENT_NAME as r, deviceSchema as rn, childCommandResultMessageSchema as rt, uninstallHost as s, DEVICES_FILENAME as sn, createBaseChildMessageSchema as st, agentConsoleServerCommand as t, writeDevices as tn, rejoiningSessionSchema as tt, serveSessionChild as u, SECRET_BYTE_LENGTH as un, toContentBlockParam as ut, createShellRegistry as v, HOST_DIRECTORY_ENVIRONMENT_VARIABLE as vn, getSessionTitle as vt, listRepositoryFiles as w, getReachableHostname as wn, parseMessageBody as wt, handleFileCommand as x, HOST_SCHEME_REGISTRY_KEY as xn, createSdkMessageMapper as xt, sendServerMessage as y, HOST_EXECUTABLE_FILENAME as yn, readToolUseResultMap as yt, sendChildCommand as z, createInputQueue as zt };
|