@github/copilot-language-server 1.563.0 → 1.564.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.
Files changed (20) hide show
  1. package/dist/assets/cls-builtin-plugins/package.json +12 -0
  2. package/dist/assets/cls-builtin-plugins/plugins/browser-use/.mcp.json +9 -0
  3. package/dist/assets/cls-builtin-plugins/plugins/browser-use/.plugin/plugin.json +11 -0
  4. package/dist/assets/cls-builtin-plugins/plugins/browser-use/README.md +69 -0
  5. package/dist/assets/cls-builtin-plugins/plugins/browser-use/channel.json +12 -0
  6. package/dist/assets/cls-builtin-plugins/plugins/browser-use/dist/browser-mcp.cjs +130 -0
  7. package/dist/assets/cls-builtin-plugins/plugins/browser-use/dist/browser-run-worker.cjs +2656 -0
  8. package/dist/assets/cls-builtin-plugins/plugins/browser-use/dist/native-host.cjs +45 -0
  9. package/dist/assets/cls-builtin-plugins/plugins/browser-use/hooks/hooks.json +16 -0
  10. package/dist/assets/cls-builtin-plugins/plugins/browser-use/scripts/activate.mjs +29 -0
  11. package/dist/assets/cls-builtin-plugins/plugins/browser-use/scripts/deactivate.mjs +7 -0
  12. package/dist/assets/cls-builtin-plugins/plugins/browser-use/scripts/doctor.mjs +1580 -0
  13. package/dist/assets/cls-builtin-plugins/plugins/browser-use/scripts/nativeMessagingRegistration.mjs +8198 -0
  14. package/dist/assets/cls-builtin-plugins/plugins/browser-use/skills/activate/SKILL.md +24 -0
  15. package/dist/assets/cls-builtin-plugins/plugins/browser-use/skills/browser-use/BROWSER-RUN.md +382 -0
  16. package/dist/assets/cls-builtin-plugins/plugins/browser-use/skills/browser-use/SKILL.md +254 -0
  17. package/dist/assets/cls-builtin-plugins/plugins/browser-use/skills/deactivate/SKILL.md +23 -0
  18. package/dist/assets/cls-builtin-plugins/plugins/browser-use/skills/doctor/SKILL.md +42 -0
  19. package/dist/main.js +1176 -1150
  20. package/package.json +9 -8
@@ -0,0 +1,1580 @@
1
+ #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res, err) => function __init() {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
10
+ };
11
+
12
+ // <define:__COPILOT_BROWSER_PLUGIN_CHANNEL__>
13
+ var define_COPILOT_BROWSER_PLUGIN_CHANNEL_default;
14
+ var init_define_COPILOT_BROWSER_PLUGIN_CHANNEL = __esm({
15
+ "<define:__COPILOT_BROWSER_PLUGIN_CHANNEL__>"() {
16
+ define_COPILOT_BROWSER_PLUGIN_CHANNEL_default = { channel: "production", productId: "github-copilot-browser", nativeMessagingHostName: "com.github.copilot.browser", extensionId: "hblomdgalaelcilcdepgohfcogjiijhe", extensionName: "GitHub Copilot", extensionInstallUrls: { chromeWebStore: "https://chromewebstore.google.com/detail/github-copilot/hblomdgalaelcilcdepgohfcogjiijhe" }, advancedPageSurfaces: true, extensionPublicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA08x5MXlp8uHflF+U7A/JPom4nno762WERnUs48S0+BqvlJTgGtyz1MaSVu4na61vaPelmBBAq40MAp2gzH4i4jK5AQBjHIkBm8sTe1ClAvJu9BiA4zxAm/lbyTPZjJTE2IbRIKVSiZs83UP31qwMJpwi3XmkgH4prWXH+IvNRSy985+hcZb25goCLk93WtbQDJDjYnLXzSZR3qyc/+KqZlcQ03lSGPjuKsyCZmG4e5Au7KNMG8PJuQPTAE+eO7BL3of6oJiLqyARj6KEDOWctREeLxqVhq9fp3MbuLBj9RxhvfJuUhYwIl+FpPuHc5wUzPOiJUDTtTVvHDZde2SigwIDAQAB" };
17
+ }
18
+ });
19
+
20
+ // shared/browser-rpc/src/productIdentifiers.ts
21
+ var PRODUCT_ID;
22
+ var init_productIdentifiers = __esm({
23
+ "shared/browser-rpc/src/productIdentifiers.ts"() {
24
+ "use strict";
25
+ init_define_COPILOT_BROWSER_PLUGIN_CHANNEL();
26
+ PRODUCT_ID = true ? "github-copilot-browser" : "github-copilot-browser";
27
+ }
28
+ });
29
+
30
+ // shared/browser-rpc/src/node/localEndpointDiscovery.ts
31
+ import { lstat, opendir, readFile } from "node:fs/promises";
32
+ import { homedir, tmpdir } from "node:os";
33
+ import { isAbsolute, join } from "node:path";
34
+ function localEndpointDirectory() {
35
+ const override = process.env[RUNTIME_DIRECTORY_ENV];
36
+ if (override !== void 0) {
37
+ if (!isAbsolute(override)) {
38
+ throw new Error(`${RUNTIME_DIRECTORY_ENV} must be an absolute path.`);
39
+ }
40
+ return override;
41
+ }
42
+ if (process.platform === "win32") {
43
+ const localAppData = process.env.LOCALAPPDATA;
44
+ const base2 = localAppData && isAbsolute(localAppData) ? localAppData : join(homedir(), "AppData", "Local");
45
+ return join(base2, "GitHub", "Copilot Browser", "runtime");
46
+ }
47
+ const xdgRuntimeDirectory = process.env.XDG_RUNTIME_DIR;
48
+ const useXdgRuntimeDirectory = xdgRuntimeDirectory !== void 0 && isAbsolute(xdgRuntimeDirectory);
49
+ const base = useXdgRuntimeDirectory ? xdgRuntimeDirectory : tmpdir();
50
+ const userSuffix = process.getuid === void 0 ? "" : `-${process.getuid()}`;
51
+ return join(base, `${UNIX_RUNTIME_DIRECTORY_NAME}${useXdgRuntimeDirectory ? "" : userSuffix}`);
52
+ }
53
+ function localEndpointAddress(endpointId) {
54
+ validateLocalEndpointId(endpointId);
55
+ if (process.platform === "win32") {
56
+ return `${WINDOWS_PIPE_ROOT}${PRODUCT_ID}-${endpointId}`;
57
+ }
58
+ return createUnixSocketAddress(localEndpointDirectory(), endpointId, UNIX_SOCKET_PATH_LIMIT);
59
+ }
60
+ function createUnixSocketAddress(runtimeDirectory, endpointId, pathLimit) {
61
+ validateLocalEndpointId(endpointId);
62
+ const token = endpointId.replaceAll("-", "");
63
+ const fixedBytes = Buffer.byteLength(join(runtimeDirectory, ".sock"));
64
+ const availableTokenBytes = pathLimit - 1 - fixedBytes;
65
+ if (availableTokenBytes < MINIMUM_SOCKET_TOKEN_LENGTH) {
66
+ throw new Error(`Local IPC path cannot fit within the ${pathLimit}-byte platform limit.`);
67
+ }
68
+ const address = join(
69
+ runtimeDirectory,
70
+ `${token.slice(0, Math.min(token.length, availableTokenBytes))}.sock`
71
+ );
72
+ if (Buffer.byteLength(address) >= pathLimit) {
73
+ throw new Error(`Local IPC path exceeds the ${pathLimit}-byte platform limit.`);
74
+ }
75
+ return address;
76
+ }
77
+ async function discoverLocalEndpointDescriptorRecords() {
78
+ const directory = localEndpointDirectory();
79
+ let entries;
80
+ try {
81
+ entries = await opendir(directory);
82
+ } catch {
83
+ return [];
84
+ }
85
+ const records = [];
86
+ let scanned = 0;
87
+ for await (const entry of entries) {
88
+ scanned += 1;
89
+ if (scanned > MAX_DESCRIPTOR_FILES_SCANNED) break;
90
+ if (!entry.isFile() || !entry.name.endsWith(DESCRIPTOR_SUFFIX)) continue;
91
+ const descriptor = await readDescriptor(directory, entry.name);
92
+ if (!descriptor) continue;
93
+ records.push({ descriptor, processState: classifyLocalEndpointProcess(descriptor.pid) });
94
+ }
95
+ return records.sort((left, right) => right.descriptor.createdAt - left.descriptor.createdAt);
96
+ }
97
+ function classifyLocalEndpointProcess(pid) {
98
+ try {
99
+ process.kill(pid, 0);
100
+ return "running";
101
+ } catch (error) {
102
+ return isErrorCode(error, "ESRCH") ? "missing" : "unknown";
103
+ }
104
+ }
105
+ function validateLocalEndpointDiagnostics(value) {
106
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
107
+ throw new TypeError("Endpoint diagnostics must be an object.");
108
+ }
109
+ const diagnostics = value;
110
+ const requiredKeys = ["browserRpcVersion", "nativeHostVersion", "installationId"];
111
+ const optionalKeys = ["extensionId", "extensionInstanceId", "extensionVersion"];
112
+ if (Object.keys(diagnostics).some((key) => ![...requiredKeys, ...optionalKeys].includes(key))) {
113
+ throw new TypeError("Endpoint diagnostics contain unsupported fields.");
114
+ }
115
+ if (typeof diagnostics.browserRpcVersion !== "number" || !Number.isSafeInteger(diagnostics.browserRpcVersion) || diagnostics.browserRpcVersion <= 0) {
116
+ throw new TypeError("Endpoint diagnostics contain an invalid Browser RPC version.");
117
+ }
118
+ if (!isBoundedString(diagnostics.nativeHostVersion, 128)) {
119
+ throw new TypeError("Endpoint diagnostics contain an invalid Native Host version.");
120
+ }
121
+ if (typeof diagnostics.installationId !== "string" || !/^[0-9a-f]{64}$/iu.test(diagnostics.installationId)) {
122
+ throw new TypeError("Endpoint diagnostics contain an invalid Installation identity.");
123
+ }
124
+ for (const key of optionalKeys) {
125
+ if (diagnostics[key] !== void 0 && !isBoundedString(diagnostics[key], 256)) {
126
+ throw new TypeError(`Endpoint diagnostics contain an invalid ${key}.`);
127
+ }
128
+ }
129
+ return value;
130
+ }
131
+ function validateLocalEndpointId(endpointId) {
132
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(endpointId)) {
133
+ throw new TypeError("endpointId must be a UUID v4 value.");
134
+ }
135
+ }
136
+ async function readDescriptor(directory, name) {
137
+ const path = join(directory, name);
138
+ try {
139
+ const info = await lstat(path);
140
+ if (!info.isFile() || info.isSymbolicLink() || info.size > LOCAL_ENDPOINT_DESCRIPTOR_MAX_BYTES) {
141
+ return void 0;
142
+ }
143
+ if (process.platform !== "win32" && (info.uid !== process.getuid?.() || (info.mode & 63) !== 0)) {
144
+ return void 0;
145
+ }
146
+ const value = JSON.parse(await readFile(path, "utf8"));
147
+ if (!isEndpointDescriptor(value) || name !== `${value.endpointId}${DESCRIPTOR_SUFFIX}`) {
148
+ return void 0;
149
+ }
150
+ return value.address === localEndpointAddress(value.endpointId) ? value : void 0;
151
+ } catch {
152
+ return void 0;
153
+ }
154
+ }
155
+ function isEndpointDescriptor(value) {
156
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
157
+ const descriptor = value;
158
+ if (descriptor.schemaVersion !== LOCAL_ENDPOINT_DESCRIPTOR_SCHEMA_VERSION || typeof descriptor.endpointId !== "string" || typeof descriptor.address !== "string" || !Number.isSafeInteger(descriptor.pid) || descriptor.pid <= 0 || !Number.isSafeInteger(descriptor.createdAt) || descriptor.createdAt <= 0) {
159
+ return false;
160
+ }
161
+ if (descriptor.diagnostics === void 0) return true;
162
+ try {
163
+ validateLocalEndpointDiagnostics(descriptor.diagnostics);
164
+ return true;
165
+ } catch {
166
+ return false;
167
+ }
168
+ }
169
+ function isBoundedString(value, maxLength) {
170
+ return typeof value === "string" && value.length > 0 && value.length <= maxLength;
171
+ }
172
+ function isErrorCode(error, code) {
173
+ return error !== null && typeof error === "object" && "code" in error && error.code === code;
174
+ }
175
+ var LOCAL_ENDPOINT_DESCRIPTOR_MAX_BYTES, LOCAL_ENDPOINT_DESCRIPTOR_SCHEMA_VERSION, DESCRIPTOR_SUFFIX, MAX_DESCRIPTOR_FILES_SCANNED, WINDOWS_PIPE_ROOT, UNIX_SOCKET_PATH_LIMIT, UNIX_RUNTIME_DIRECTORY_NAME, MINIMUM_SOCKET_TOKEN_LENGTH, RUNTIME_DIRECTORY_ENV;
176
+ var init_localEndpointDiscovery = __esm({
177
+ "shared/browser-rpc/src/node/localEndpointDiscovery.ts"() {
178
+ "use strict";
179
+ init_define_COPILOT_BROWSER_PLUGIN_CHANNEL();
180
+ init_productIdentifiers();
181
+ LOCAL_ENDPOINT_DESCRIPTOR_MAX_BYTES = 4 * 1024;
182
+ LOCAL_ENDPOINT_DESCRIPTOR_SCHEMA_VERSION = 1;
183
+ DESCRIPTOR_SUFFIX = ".json";
184
+ MAX_DESCRIPTOR_FILES_SCANNED = 1024;
185
+ WINDOWS_PIPE_ROOT = "\\\\.\\pipe\\";
186
+ UNIX_SOCKET_PATH_LIMIT = process.platform === "darwin" ? 103 : 107;
187
+ UNIX_RUNTIME_DIRECTORY_NAME = "gcb";
188
+ MINIMUM_SOCKET_TOKEN_LENGTH = 20;
189
+ RUNTIME_DIRECTORY_ENV = "COPILOT_BROWSER_RUNTIME_DIR";
190
+ }
191
+ });
192
+
193
+ // plugin/shared/scripts/doctor.mjs
194
+ init_define_COPILOT_BROWSER_PLUGIN_CHANNEL();
195
+ init_localEndpointDiscovery();
196
+ import { execFile as execFile4 } from "node:child_process";
197
+ import { createHash as createHash2 } from "node:crypto";
198
+ import { once } from "node:events";
199
+ import { readFile as readFile5 } from "node:fs/promises";
200
+ import { connect } from "node:net";
201
+ import { platform as platform3 } from "node:os";
202
+ import { join as join5 } from "node:path";
203
+ import { promisify as promisify4 } from "node:util";
204
+
205
+ // plugin/shared/scripts/browserPrerequisites.mjs
206
+ init_define_COPILOT_BROWSER_PLUGIN_CHANNEL();
207
+ import { execFile as execFile2 } from "node:child_process";
208
+ import { lstat as lstat2, readFile as readFile3, readdir } from "node:fs/promises";
209
+ import { homedir as homedir3, platform as platform2 } from "node:os";
210
+ import { isAbsolute as isAbsolute3, join as join3, resolve as resolve2 } from "node:path";
211
+ import { promisify as promisify2 } from "node:util";
212
+
213
+ // plugin/shared/scripts/pluginPlan.mjs
214
+ init_define_COPILOT_BROWSER_PLUGIN_CHANNEL();
215
+ import { execFile } from "node:child_process";
216
+ import { createHash } from "node:crypto";
217
+ import { readFile as readFile2, stat } from "node:fs/promises";
218
+ import { homedir as homedir2, platform } from "node:os";
219
+ import { dirname, isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
220
+ import { fileURLToPath } from "node:url";
221
+ import { promisify } from "node:util";
222
+
223
+ // plugin/shared/.plugin/plugin.json
224
+ var plugin_default = {
225
+ name: "browser-use",
226
+ description: "GitHub Copilot Browser Use plugin.",
227
+ version: "0.0.4",
228
+ author: {
229
+ name: "GitHub"
230
+ },
231
+ mcpServers: ".mcp.json",
232
+ skills: "skills",
233
+ postInstallMessage: "Load the matching GitHub Copilot extension, then start a new session. If the extension is not installed or enabled, use `/browser-use:doctor` for channel-appropriate installation guidance. Browser registration activates automatically at session start, and Browser guidance loads automatically when needed. Use `/browser-use:activate` to activate this installation immediately or repair setup, `/browser-use:doctor` for diagnostics, and `/browser-use:deactivate` before uninstalling or replacing this installation."
234
+ };
235
+
236
+ // plugin/dev/channel.json
237
+ var channel_default = {
238
+ channel: "dev",
239
+ productId: "github-copilot-browser-dev",
240
+ nativeMessagingHostName: "com.github.copilot.browser.dev",
241
+ extensionId: "dpkmommpdhajlaebcicbbmfdmkohmehi",
242
+ extensionName: "GitHub Copilot",
243
+ advancedPageSurfaces: true,
244
+ extensionPublicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0FaW7/YkDngCegAuXXxhhP72dJ3cERtCgXZ4oRLAzUU7yU2YTH7P/9pBvGcI7fhZKU0FvMtvGcakDnA5+5W7bcUlWx8R3oNSQe1OcTRDqk9yayKUnQb4I83yogkF2tWzsnt3sQNn/LzSFeXVWAYINLvhJ7O0dKJoh1rVH2/TzSkumq7Ey4OHb0bIefWYwJUEFXGj+F5amYDj4NowjekJTL1aiOAIj44xrLn06LvzYF3B58fqKgJzbBpuKjUbm5igCN/oydnlxc6SUaBhx6ilLAt/kFLzGnlkM9/l2h95RyJlvSYLcYhoWM6/R6IdqyE11s/aOGt3y/QIPgU6HuQnnwIDAQAB"
245
+ };
246
+
247
+ // plugin/shared/scripts/pluginPlan.mjs
248
+ var pluginRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
249
+ var execFileAsync = promisify(execFile);
250
+ var channel = typeof define_COPILOT_BROWSER_PLUGIN_CHANNEL_default === "object" ? define_COPILOT_BROWSER_PLUGIN_CHANNEL_default : channel_default;
251
+ var installRootEnvironment = `COPILOT_BROWSER_${channel.channel.replaceAll("-", "_").toUpperCase()}_INSTALL_ROOT`;
252
+ var PluginUserError = class extends Error {
253
+ };
254
+ function parsePluginVersion(value) {
255
+ const match = /^(\d+(?:\.\d+){2,3})(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(
256
+ value
257
+ );
258
+ if (!match) throw new PluginUserError("The plugin version is invalid.");
259
+ const coreParts = match[1].split(".");
260
+ const prerelease = match[2]?.split(".");
261
+ if ([...coreParts, ...prerelease ?? []].some(
262
+ (part) => /^\d+$/.test(part) && (part.length > 1 && part.startsWith("0") || !Number.isSafeInteger(Number(part)))
263
+ )) {
264
+ throw new PluginUserError("The plugin version is invalid.");
265
+ }
266
+ return { core: coreParts.map(Number), prerelease };
267
+ }
268
+ function comparePluginVersions(left, right) {
269
+ const leftVersion = parsePluginVersion(left);
270
+ const rightVersion = parsePluginVersion(right);
271
+ const coreLength = Math.max(leftVersion.core.length, rightVersion.core.length);
272
+ for (let index = 0; index < coreLength; index += 1) {
273
+ const difference = (leftVersion.core[index] ?? 0) - (rightVersion.core[index] ?? 0);
274
+ if (difference !== 0) return Math.sign(difference);
275
+ }
276
+ if (!leftVersion.prerelease && !rightVersion.prerelease) return 0;
277
+ if (!leftVersion.prerelease) return 1;
278
+ if (!rightVersion.prerelease) return -1;
279
+ const length = Math.max(leftVersion.prerelease.length, rightVersion.prerelease.length);
280
+ for (let index = 0; index < length; index += 1) {
281
+ const leftPart = leftVersion.prerelease[index];
282
+ const rightPart = rightVersion.prerelease[index];
283
+ if (leftPart === void 0) return -1;
284
+ if (rightPart === void 0) return 1;
285
+ if (leftPart === rightPart) continue;
286
+ const leftNumber = numericVersionIdentifier(leftPart);
287
+ const rightNumber = numericVersionIdentifier(rightPart);
288
+ if (leftNumber !== void 0 && rightNumber !== void 0) {
289
+ return Math.sign(leftNumber - rightNumber);
290
+ }
291
+ if (leftNumber !== void 0) return -1;
292
+ if (rightNumber !== void 0) return 1;
293
+ return leftPart < rightPart ? -1 : 1;
294
+ }
295
+ return 0;
296
+ }
297
+ function numericVersionIdentifier(value) {
298
+ if (!/^\d+$/.test(value)) return void 0;
299
+ const parsed = Number(value);
300
+ if (!Number.isSafeInteger(parsed)) throw new PluginUserError("The plugin version is invalid.");
301
+ return parsed;
302
+ }
303
+ async function createRegistrationPlan() {
304
+ const distDirectory = join2(pluginRoot, "dist");
305
+ const sourceNativeHostBundle = join2(distDirectory, "native-host.cjs");
306
+ const sourceBundle = await readRequiredFile(sourceNativeHostBundle);
307
+ const bundleSha256 = createHash("sha256").update(sourceBundle).digest("hex");
308
+ const installationSha256 = createHash("sha256").update(sourceBundle).update("\0").update(platform()).update("\0").update(normalizePath(pluginRoot)).digest("hex");
309
+ const installRoot = pluginInstallRoot();
310
+ const version = validateVersion(plugin_default.version);
311
+ const versionDirectory = join2(installRoot, "versions", `${version}-${installationSha256}`);
312
+ const nativeHostBundle = join2(versionDirectory, "native-host.cjs");
313
+ const launcherPath = join2(
314
+ versionDirectory,
315
+ platform() === "win32" ? "native-host.bat" : "native-host"
316
+ );
317
+ const manifest = {
318
+ name: channel.nativeMessagingHostName,
319
+ description: "GitHub Copilot Browser Native Messaging Host",
320
+ path: launcherPath,
321
+ type: "stdio",
322
+ allowed_origins: [`chrome-extension://${channel.extensionId}/`]
323
+ };
324
+ const manifestPaths = nativeMessagingManifestPaths(installRoot);
325
+ const registryKey = platform() === "win32" ? `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${channel.nativeMessagingHostName}` : void 0;
326
+ return {
327
+ channel: channel.channel,
328
+ hostName: channel.nativeMessagingHostName,
329
+ extensionId: channel.extensionId,
330
+ pluginRoot,
331
+ sourceNativeHostBundle,
332
+ bundleSha256,
333
+ installationSha256,
334
+ installRoot,
335
+ version,
336
+ versionDirectory,
337
+ activeInstallationPath: join2(installRoot, "active-installation.json"),
338
+ pendingInstallationPath: join2(installRoot, "pending-installation.json"),
339
+ activationLockPath: join2(installRoot, ".activation-lock"),
340
+ nativeHostBundle,
341
+ launcherPath,
342
+ manifestPaths,
343
+ registryKey,
344
+ manifest
345
+ };
346
+ }
347
+ function launcherContent(nativeHostBundle, installationId, productVersion) {
348
+ if (!/^[0-9a-f]{64}$/u.test(installationId)) {
349
+ throw new PluginUserError("The Browser installation identity is invalid.");
350
+ }
351
+ validateVersion(productVersion);
352
+ if (platform() === "win32") {
353
+ return `@echo off\r
354
+ "${escapeBatchPath(process.execPath)}" "${escapeBatchPath(nativeHostBundle)}" --installation-id "${installationId}" --product-version "${productVersion}" %*\r
355
+ `;
356
+ }
357
+ return `#!/bin/sh
358
+ exec ${shellQuote(process.execPath)} ${shellQuote(nativeHostBundle)} --installation-id ${shellQuote(installationId)} --product-version ${shellQuote(productVersion)} "$@"
359
+ `;
360
+ }
361
+ async function launcherIsExecutable(path) {
362
+ if (platform() === "win32") return true;
363
+ try {
364
+ return ((await stat(path)).mode & 64) !== 0;
365
+ } catch (error) {
366
+ if (isMissingFileError(error)) return false;
367
+ throw error;
368
+ }
369
+ }
370
+ async function readRequiredFile(path) {
371
+ try {
372
+ return await readFile2(path);
373
+ } catch (error) {
374
+ if (isMissingFileError(error)) {
375
+ throw new PluginUserError(
376
+ "Required Browser Use plugin files are missing. Reinstall the plugin."
377
+ );
378
+ }
379
+ throw error;
380
+ }
381
+ }
382
+ function pathsEqual(left, right) {
383
+ const normalizedLeft = resolve(left);
384
+ const normalizedRight = resolve(right);
385
+ return platform() === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
386
+ }
387
+ function chromiumConfigRoot(environment = process.env) {
388
+ const configured = environment.XDG_CONFIG_HOME;
389
+ return configured && isAbsolute2(configured) ? resolve(configured) : join2(homedir2(), ".config");
390
+ }
391
+ function manifestMatchesPlan(actual, plan) {
392
+ return actual !== null && typeof actual === "object" && actual.name === plan.manifest.name && actual.description === plan.manifest.description && typeof actual.path === "string" && pathsEqual(actual.path, plan.launcherPath) && actual.type === plan.manifest.type && Array.isArray(actual.allowed_origins) && actual.allowed_origins.length === 1 && actual.allowed_origins[0] === plan.manifest.allowed_origins[0];
393
+ }
394
+ async function readRegistryDefaultValue(registryKey) {
395
+ try {
396
+ const { stdout } = await execFileAsync("reg.exe", ["query", registryKey, "/ve"], {
397
+ windowsHide: true
398
+ });
399
+ return /^\s+.*?\s+REG_SZ\s+(.+)$/m.exec(stdout)?.[1]?.trim();
400
+ } catch (error) {
401
+ if (!isMissingRegistryKeyError(error)) throw error;
402
+ return void 0;
403
+ }
404
+ }
405
+ function isMissingFileError(error) {
406
+ return isFileSystemError(error, "ENOENT");
407
+ }
408
+ function isFileSystemError(error, code) {
409
+ return error !== null && typeof error === "object" && "code" in error && error.code === code;
410
+ }
411
+ function isMissingRegistryKeyError(error) {
412
+ return error !== null && typeof error === "object" && "code" in error && (error.code === 1 || error.code === "1");
413
+ }
414
+ function pluginInstallRoot() {
415
+ const override = process.env[installRootEnvironment];
416
+ if (override !== void 0) {
417
+ if (!isAbsolute2(override)) {
418
+ throw new PluginUserError(`${installRootEnvironment} must be an absolute path.`);
419
+ }
420
+ return resolve(override);
421
+ }
422
+ if (platform() === "win32") {
423
+ const localAppData = process.env.LOCALAPPDATA;
424
+ const base2 = localAppData && isAbsolute2(localAppData) ? localAppData : join2(homedir2(), "AppData", "Local");
425
+ return join2(base2, "GitHub", "Copilot Browser", channel.channel);
426
+ }
427
+ if (platform() === "darwin") {
428
+ return join2(
429
+ homedir2(),
430
+ "Library",
431
+ "Application Support",
432
+ "GitHub",
433
+ "Copilot Browser",
434
+ channel.channel
435
+ );
436
+ }
437
+ const xdgDataHome = process.env.XDG_DATA_HOME;
438
+ const base = xdgDataHome && isAbsolute2(xdgDataHome) ? xdgDataHome : join2(homedir2(), ".local", "share");
439
+ return join2(base, "github-copilot", "browser", channel.channel);
440
+ }
441
+ function nativeMessagingManifestPaths(installRoot) {
442
+ const filename = `${channel.nativeMessagingHostName}.json`;
443
+ if (platform() === "win32") return [join2(installRoot, filename)];
444
+ if (platform() === "darwin") {
445
+ return [
446
+ join2(
447
+ homedir2(),
448
+ "Library",
449
+ "Application Support",
450
+ "Google",
451
+ "Chrome",
452
+ "NativeMessagingHosts",
453
+ filename
454
+ ),
455
+ join2(
456
+ homedir2(),
457
+ "Library",
458
+ "Application Support",
459
+ "Microsoft Edge",
460
+ "NativeMessagingHosts",
461
+ filename
462
+ )
463
+ ];
464
+ }
465
+ const configRoot = chromiumConfigRoot();
466
+ return [
467
+ join2(configRoot, "google-chrome", "NativeMessagingHosts", filename),
468
+ join2(configRoot, "microsoft-edge", "NativeMessagingHosts", filename)
469
+ ];
470
+ }
471
+ function escapeBatchPath(value) {
472
+ if (/["\r\n]/.test(value)) {
473
+ throw new PluginUserError("The Node.js executable path is unsupported.");
474
+ }
475
+ return value.replaceAll("%", "%%");
476
+ }
477
+ function shellQuote(value) {
478
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
479
+ }
480
+ function validateVersion(version) {
481
+ parsePluginVersion(version);
482
+ return version;
483
+ }
484
+ function normalizePath(path) {
485
+ return platform() === "win32" ? path.toLowerCase() : path;
486
+ }
487
+
488
+ // plugin/shared/scripts/browserPrerequisites.mjs
489
+ var execFileAsync2 = promisify2(execFile2);
490
+ var MINIMUM_BROWSER_MAJOR = 127;
491
+ var MAX_PROFILE_DIRECTORIES = 100;
492
+ var MAX_RPC_METADATA_BYTES = 4 * 1024;
493
+ async function diagnoseInstalledExtensions(extensionId, expectedExtensionVersion, extensionInstallUrls) {
494
+ const installations = (await Promise.all(browserCandidates().map(detectBrowserInstallation))).filter((installation) => installation !== void 0);
495
+ const supportedInstallations = installations.filter(
496
+ ({ majorVersion }) => majorVersion >= MINIMUM_BROWSER_MAJOR
497
+ );
498
+ const browserCheck = classifyBrowserInstallations(installations);
499
+ const extensionStates = (await Promise.all(
500
+ supportedInstallations.map(
501
+ ({ profileRoots }) => readExtensionStates(profileRoots, extensionId)
502
+ )
503
+ )).flat();
504
+ return {
505
+ checks: [
506
+ browserCheck,
507
+ ...classifyInstalledExtensionChecks(
508
+ extensionStates,
509
+ browserCheck.status,
510
+ expectedExtensionVersion,
511
+ extensionInstallUrls
512
+ )
513
+ ],
514
+ states: extensionStates
515
+ };
516
+ }
517
+ function classifyInstalledExtensionChecks(states, browserStatus, expectedExtensionVersion, extensionInstallUrls = {}) {
518
+ return [
519
+ classifyExtensionInstallations(states, browserStatus, extensionInstallUrls),
520
+ classifyExtensionVersions(states, expectedExtensionVersion),
521
+ classifyExtensionRpcVersions(states)
522
+ ];
523
+ }
524
+ function classifyBrowserInstallations(installations) {
525
+ if (installations.some(({ majorVersion }) => majorVersion >= MINIMUM_BROWSER_MAJOR)) {
526
+ return prerequisiteCheck(
527
+ "browser-prerequisite",
528
+ "pass",
529
+ `Chrome or Edge ${MINIMUM_BROWSER_MAJOR} or later is installed.`
530
+ );
531
+ }
532
+ return prerequisiteCheck(
533
+ "browser-prerequisite",
534
+ "fail",
535
+ installations.length === 0 ? `Install Chrome or Edge ${MINIMUM_BROWSER_MAJOR} or later.` : `Update Chrome or Edge to version ${MINIMUM_BROWSER_MAJOR} or later.`
536
+ );
537
+ }
538
+ function classifyExtensionInstallations(states, browserStatus, extensionInstallUrls = {}) {
539
+ if (states.some(({ state }) => state === "enabled")) {
540
+ return prerequisiteCheck(
541
+ "browser-extension",
542
+ "pass",
543
+ "The matching GitHub Copilot extension is enabled in a supported browser profile."
544
+ );
545
+ }
546
+ if (states.some(({ state }) => state === "disabled")) {
547
+ return prerequisiteCheck(
548
+ "browser-extension",
549
+ "warn",
550
+ "Enable the matching GitHub Copilot extension in Chrome or Edge."
551
+ );
552
+ }
553
+ if (states.some(({ state }) => state === "unknown")) {
554
+ return prerequisiteCheck(
555
+ "browser-extension",
556
+ "warn",
557
+ "The matching GitHub Copilot extension installation could not be verified because one or more browser profile files could not be read."
558
+ );
559
+ }
560
+ const chromeWebStoreUrl = extensionInstallUrls.chromeWebStore;
561
+ return prerequisiteCheck(
562
+ "browser-extension",
563
+ browserStatus === "fail" ? "warn" : "fail",
564
+ chromeWebStoreUrl === void 0 ? "Load the matching GitHub Copilot extension in Chrome or Edge." : `Install the matching GitHub Copilot extension from the Chrome Web Store in Chrome or Edge: ${chromeWebStoreUrl}. In Edge, allow extensions from other stores if prompted. If it is already installed, enable it in Chrome or Edge.`
565
+ );
566
+ }
567
+ function classifyExtensionVersions(states, expectedVersion) {
568
+ const enabled = states.filter(({ state }) => state === "enabled");
569
+ if (enabled.length === 0) {
570
+ return prerequisiteCheck(
571
+ "browser-extension-version",
572
+ "warn",
573
+ states.some(({ state }) => state === "unknown") ? "The Browser extension version could not be verified because its installation state could not be read." : states.some(({ state }) => state === "disabled") ? "Enable the matching GitHub Copilot extension before its version can be verified." : "Install and enable the matching GitHub Copilot extension before its version can be verified."
574
+ );
575
+ }
576
+ const versions = [
577
+ ...new Set(
578
+ enabled.filter(({ version }) => version !== void 0).map(({ version }) => version)
579
+ )
580
+ ];
581
+ if (versions.length === 0) {
582
+ return prerequisiteCheck(
583
+ "browser-extension-version",
584
+ "warn",
585
+ "The enabled Browser extension version could not be verified. Update the matching extension if Browser does not connect."
586
+ );
587
+ }
588
+ const relation = versions.map((version) => compareVersions(version, expectedVersion));
589
+ if (relation.every((comparison) => comparison === 0)) {
590
+ return prerequisiteCheck(
591
+ "browser-extension-version",
592
+ "pass",
593
+ `The enabled Browser extension matches plugin version ${expectedVersion}.`
594
+ );
595
+ }
596
+ if (relation.every((comparison) => comparison < 0)) {
597
+ return prerequisiteCheck(
598
+ "browser-extension-version",
599
+ "warn",
600
+ `The enabled Browser extension is older than plugin version ${expectedVersion}. Update the matching extension.`
601
+ );
602
+ }
603
+ if (relation.every((comparison) => comparison > 0)) {
604
+ return prerequisiteCheck(
605
+ "browser-extension-version",
606
+ "warn",
607
+ `The enabled Browser extension is newer than plugin version ${expectedVersion}. Update the Browser Use plugin.`
608
+ );
609
+ }
610
+ return prerequisiteCheck(
611
+ "browser-extension-version",
612
+ "warn",
613
+ `Enabled browser profiles use different Browser extension versions; not all match plugin version ${expectedVersion}. Update the older side.`
614
+ );
615
+ }
616
+ function classifyExtensionRpcVersions(states) {
617
+ const enabled = states.filter(({ state }) => state === "enabled");
618
+ if (enabled.length === 0) {
619
+ return prerequisiteCheck(
620
+ "browser-extension-rpc-version",
621
+ "warn",
622
+ states.some(({ state }) => state === "unknown") ? "The Browser extension RPC version could not be verified because its installation state could not be read." : states.some(({ state }) => state === "disabled") ? "Enable the matching GitHub Copilot extension before its RPC version can be verified." : "Install and enable the matching GitHub Copilot extension before its RPC version can be verified."
623
+ );
624
+ }
625
+ const allVersionsKnown = enabled.every(({ rpcVersion }) => rpcVersion !== void 0);
626
+ const versions = [
627
+ ...new Set(
628
+ enabled.map(({ rpcVersion }) => rpcVersion).filter((version) => version !== void 0)
629
+ )
630
+ ];
631
+ if (!allVersionsKnown) {
632
+ return prerequisiteCheck(
633
+ "browser-extension-rpc-version",
634
+ "warn",
635
+ "The enabled Browser extension RPC version could not be verified."
636
+ );
637
+ }
638
+ if (versions.length === 1) {
639
+ return prerequisiteCheck(
640
+ "browser-extension-rpc-version",
641
+ "pass",
642
+ `The enabled Browser extension reports RPC version ${versions[0]}.`
643
+ );
644
+ }
645
+ return prerequisiteCheck(
646
+ "browser-extension-rpc-version",
647
+ "warn",
648
+ "Enabled browser profiles report different Browser RPC versions."
649
+ );
650
+ }
651
+ function firstIncompatibleExtensionRpcVersion(states, expectedVersion) {
652
+ return states.filter(({ state }) => state === "enabled").map(({ rpcVersion }) => rpcVersion).find((version) => version !== void 0 && version !== expectedVersion);
653
+ }
654
+ function compareVersions(left, right) {
655
+ try {
656
+ return comparePluginVersions(left, right);
657
+ } catch {
658
+ return left === right ? 0 : Number.NaN;
659
+ }
660
+ }
661
+ function parseBrowserMajorVersion(value) {
662
+ const match = /(?:(?:Chrome|Chromium|Edge)\/?\s*)?(\d+)(?:\.\d+){1,3}/iu.exec(value);
663
+ return match ? Number.parseInt(match[1], 10) : void 0;
664
+ }
665
+ async function readExtensionStates(profileRoots, extensionId) {
666
+ const states = [];
667
+ for (const root of profileRoots) {
668
+ let entries;
669
+ try {
670
+ entries = await readdir(root, { withFileTypes: true });
671
+ } catch (error) {
672
+ if (!isMissingFileError2(error)) states.push({ state: "unknown" });
673
+ continue;
674
+ }
675
+ const profileDirectories = entries.filter(
676
+ (entry) => entry.isDirectory() && (entry.name === "Default" || /^Profile \d+$/u.test(entry.name))
677
+ ).slice(0, MAX_PROFILE_DIRECTORIES).map((entry) => join3(root, entry.name));
678
+ for (const profileDirectory of profileDirectories) {
679
+ let foundSettings = false;
680
+ let unreadablePreferences = false;
681
+ for (const filename of ["Secure Preferences", "Preferences"]) {
682
+ try {
683
+ const preferences = JSON.parse(
684
+ await readFile3(join3(profileDirectory, filename), "utf8")
685
+ );
686
+ const settings = preferences?.extensions?.settings?.[extensionId];
687
+ if (settings !== void 0) {
688
+ foundSettings = true;
689
+ const extensionRoot = await resolveInstalledExtensionRoot(
690
+ settings,
691
+ profileDirectory,
692
+ extensionId
693
+ );
694
+ if (extensionRoot !== void 0) {
695
+ const disabled = settings.state === 0 || Array.isArray(settings.disable_reasons) && settings.disable_reasons.length > 0 || typeof settings.disable_reasons === "number" && settings.disable_reasons !== 0;
696
+ states.push({
697
+ state: disabled ? "disabled" : "enabled",
698
+ ...await readExtensionMetadata(settings, extensionRoot)
699
+ });
700
+ }
701
+ break;
702
+ }
703
+ } catch (error) {
704
+ if (!isMissingFileError2(error)) {
705
+ unreadablePreferences = true;
706
+ if (filename === "Secure Preferences") break;
707
+ }
708
+ }
709
+ }
710
+ if (!foundSettings && unreadablePreferences) states.push({ state: "unknown" });
711
+ }
712
+ }
713
+ return states;
714
+ }
715
+ function isMissingFileError2(error) {
716
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
717
+ }
718
+ async function resolveInstalledExtensionRoot(settings, profileDirectory, extensionId) {
719
+ const extensionsDirectory = join3(profileDirectory, "Extensions");
720
+ if (typeof settings?.path === "string" && settings.path.trim() !== "") {
721
+ const configuredRoot = isAbsolute3(settings.path) ? resolve2(settings.path) : resolve2(extensionsDirectory, settings.path);
722
+ return await isDirectory(configuredRoot) ? configuredRoot : void 0;
723
+ }
724
+ const packagedRoot = join3(extensionsDirectory, extensionId);
725
+ let entries;
726
+ try {
727
+ entries = await readdir(packagedRoot, { withFileTypes: true });
728
+ } catch {
729
+ return void 0;
730
+ }
731
+ const versions = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
732
+ const embeddedVersion = typeof settings?.manifest?.version === "string" && settings.manifest.version.trim() !== "" ? settings.manifest.version : void 0;
733
+ const selectedVersion = (embeddedVersion === void 0 ? void 0 : versions.filter(
734
+ (version) => version === embeddedVersion || version.startsWith(`${embeddedVersion}_`)
735
+ ).sort(compareChromeExtensionVersionDirectories).at(-1)) ?? versions.sort(compareChromeExtensionVersionDirectories).at(-1);
736
+ return selectedVersion === void 0 ? void 0 : join3(packagedRoot, selectedVersion);
737
+ }
738
+ function compareChromeExtensionVersionDirectories(left, right) {
739
+ const leftVersion = parseChromeExtensionVersionDirectory(left);
740
+ const rightVersion = parseChromeExtensionVersionDirectory(right);
741
+ if (leftVersion === void 0)
742
+ return rightVersion === void 0 ? left.localeCompare(right) : -1;
743
+ if (rightVersion === void 0) return 1;
744
+ for (let index = 0; index < leftVersion.length; index += 1) {
745
+ const difference = leftVersion[index] - rightVersion[index];
746
+ if (difference !== 0) return difference;
747
+ }
748
+ return 0;
749
+ }
750
+ function parseChromeExtensionVersionDirectory(value) {
751
+ const match = /^(\d+(?:\.\d+){0,3})(?:_(\d+))?$/u.exec(value);
752
+ if (!match) return void 0;
753
+ const version = match[1].split(".").map(Number);
754
+ while (version.length < 4) version.push(0);
755
+ version.push(Number(match[2] ?? 0));
756
+ return version;
757
+ }
758
+ async function isDirectory(path) {
759
+ try {
760
+ return (await lstat2(path)).isDirectory();
761
+ } catch {
762
+ return false;
763
+ }
764
+ }
765
+ async function readExtensionMetadata(settings, extensionRoot) {
766
+ const embeddedVersion = settings?.manifest?.version;
767
+ const version = typeof embeddedVersion === "string" && embeddedVersion.trim() !== "" ? embeddedVersion : void 0;
768
+ try {
769
+ const manifest = JSON.parse(await readFile3(join3(extensionRoot, "manifest.json"), "utf8"));
770
+ const manifestVersion = typeof manifest?.version === "string" && manifest.version.trim() !== "" ? manifest.version : version;
771
+ return {
772
+ version: manifestVersion,
773
+ rpcVersion: await readRpcVersion(extensionRoot)
774
+ };
775
+ } catch {
776
+ return { version };
777
+ }
778
+ }
779
+ async function readRpcVersion(extensionRoot) {
780
+ try {
781
+ const path = join3(extensionRoot, "browser-rpc.json");
782
+ const info = await lstat2(path);
783
+ if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_RPC_METADATA_BYTES) {
784
+ return void 0;
785
+ }
786
+ const content = await readFile3(path, "utf8");
787
+ if (Buffer.byteLength(content) > MAX_RPC_METADATA_BYTES) return void 0;
788
+ const metadata = JSON.parse(content);
789
+ return Object.keys(metadata).length === 1 && Number.isSafeInteger(metadata.version) && metadata.version > 0 ? metadata.version : void 0;
790
+ } catch {
791
+ return void 0;
792
+ }
793
+ }
794
+ async function detectBrowserInstallation(candidate) {
795
+ for (const executable of candidate.executables) {
796
+ try {
797
+ const majorVersion = parseBrowserMajorVersion(await readBrowserVersion(executable));
798
+ if (majorVersion !== void 0) {
799
+ return { majorVersion, profileRoots: candidate.profileRoots };
800
+ }
801
+ } catch {
802
+ }
803
+ }
804
+ return void 0;
805
+ }
806
+ async function readBrowserVersion(executable) {
807
+ if (platform2() === "win32") {
808
+ const { stdout: stdout2 } = await execFileAsync2(
809
+ "powershell.exe",
810
+ [
811
+ "-NoLogo",
812
+ "-NoProfile",
813
+ "-NonInteractive",
814
+ "-Command",
815
+ "(Get-Item -LiteralPath $env:COPILOT_BROWSER_EXECUTABLE).VersionInfo.ProductVersion"
816
+ ],
817
+ {
818
+ env: { ...process.env, COPILOT_BROWSER_EXECUTABLE: executable },
819
+ windowsHide: true,
820
+ timeout: 5e3
821
+ }
822
+ );
823
+ return stdout2;
824
+ }
825
+ const { stdout, stderr } = await execFileAsync2(executable, ["--version"], {
826
+ windowsHide: true,
827
+ timeout: 5e3
828
+ });
829
+ return `${stdout}
830
+ ${stderr}`;
831
+ }
832
+ function browserCandidates() {
833
+ const operatingSystem = platform2();
834
+ const home = environmentHome();
835
+ if (operatingSystem === "win32") {
836
+ const localAppData = process.env.LOCALAPPDATA ?? join3(home, "AppData", "Local");
837
+ const programRoots = [
838
+ process.env.PROGRAMFILES,
839
+ process.env["PROGRAMFILES(X86)"],
840
+ localAppData
841
+ ].filter((value) => value && isAbsolute3(value));
842
+ return [
843
+ {
844
+ executables: programRoots.map(
845
+ (root) => join3(root, "Google", "Chrome", "Application", "chrome.exe")
846
+ ),
847
+ profileRoots: [join3(localAppData, "Google", "Chrome", "User Data")]
848
+ },
849
+ {
850
+ executables: programRoots.map(
851
+ (root) => join3(root, "Microsoft", "Edge", "Application", "msedge.exe")
852
+ ),
853
+ profileRoots: [join3(localAppData, "Microsoft", "Edge", "User Data")]
854
+ }
855
+ ];
856
+ }
857
+ if (operatingSystem === "darwin") {
858
+ return [
859
+ {
860
+ executables: [
861
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
862
+ join3(
863
+ home,
864
+ "Applications",
865
+ "Google Chrome.app",
866
+ "Contents",
867
+ "MacOS",
868
+ "Google Chrome"
869
+ )
870
+ ],
871
+ profileRoots: [join3(home, "Library", "Application Support", "Google", "Chrome")]
872
+ },
873
+ {
874
+ executables: [
875
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
876
+ join3(
877
+ home,
878
+ "Applications",
879
+ "Microsoft Edge.app",
880
+ "Contents",
881
+ "MacOS",
882
+ "Microsoft Edge"
883
+ )
884
+ ],
885
+ profileRoots: [join3(home, "Library", "Application Support", "Microsoft Edge")]
886
+ }
887
+ ];
888
+ }
889
+ const configRoot = chromiumConfigRoot();
890
+ const chromeConfigRoot = process.env.CHROME_CONFIG_HOME && isAbsolute3(process.env.CHROME_CONFIG_HOME) ? resolve2(process.env.CHROME_CONFIG_HOME) : configRoot;
891
+ return [
892
+ {
893
+ executables: ["google-chrome", "google-chrome-stable"],
894
+ profileRoots: [join3(chromeConfigRoot, "google-chrome")]
895
+ },
896
+ {
897
+ executables: ["microsoft-edge", "microsoft-edge-stable"],
898
+ profileRoots: [join3(configRoot, "microsoft-edge")]
899
+ }
900
+ ];
901
+ }
902
+ function environmentHome() {
903
+ const configured = process.env.HOME ?? process.env.USERPROFILE;
904
+ return configured && isAbsolute3(configured) ? configured : homedir3();
905
+ }
906
+ function prerequisiteCheck(id, status, message) {
907
+ return { id, status, message };
908
+ }
909
+
910
+ // plugin/shared/scripts/nativeMessagingRegistration.mjs
911
+ init_define_COPILOT_BROWSER_PLUGIN_CHANNEL();
912
+ import { execFile as execFile3 } from "node:child_process";
913
+ import { mkdir, readFile as readFile4, readdir as readdir2, rename, rm, stat as stat2, writeFile } from "node:fs/promises";
914
+ import { dirname as dirname2, isAbsolute as isAbsolute4, join as join4, resolve as resolve3 } from "node:path";
915
+ import { promisify as promisify3 } from "node:util";
916
+ var execFileAsync3 = promisify3(execFile3);
917
+ async function readActiveInstallation(plan) {
918
+ return readInstallationState(plan.activeInstallationPath, plan, "active");
919
+ }
920
+ async function readPendingInstallation(plan) {
921
+ return readInstallationState(plan.pendingInstallationPath, plan, "pending");
922
+ }
923
+ async function readInstallationState(path, plan, label) {
924
+ const content = await readFile4(path, "utf8").catch((error) => {
925
+ if (isMissingFileError(error)) return void 0;
926
+ throw error;
927
+ });
928
+ if (content === void 0) return void 0;
929
+ try {
930
+ const active = JSON.parse(content);
931
+ if (!validActiveInstallation(active, plan)) throw new Error("invalid active installation");
932
+ return active;
933
+ } catch {
934
+ throw new PluginUserError(
935
+ `Browser ${label} activation state is invalid. Reinstall the plugin or remove the Browser setup.`
936
+ );
937
+ }
938
+ }
939
+ function validActiveInstallation(active, plan) {
940
+ const versionsRoot = resolve3(plan.installRoot, "versions");
941
+ const launcherPath = active && typeof active === "object" && typeof active.manifest?.path === "string" ? resolve3(active.manifest.path) : void 0;
942
+ const expectedVersionDirectory = typeof active?.version === "string" && typeof active?.installationSha256 === "string" ? resolve3(versionsRoot, `${active.version}-${active.installationSha256}`) : void 0;
943
+ const expectedLauncher = expectedVersionDirectory ? resolve3(
944
+ expectedVersionDirectory,
945
+ process.platform === "win32" ? "native-host.bat" : "native-host"
946
+ ) : void 0;
947
+ try {
948
+ if (typeof active?.version === "string") parsePluginVersion(active.version);
949
+ else return false;
950
+ } catch {
951
+ return false;
952
+ }
953
+ return typeof active?.version === "string" && typeof active.installationSha256 === "string" && /^[0-9a-f]{64}$/.test(active.installationSha256) && typeof active.bundleSha256 === "string" && /^[0-9a-f]{64}$/.test(active.bundleSha256) && typeof active.launcherSha256 === "string" && /^[0-9a-f]{64}$/.test(active.launcherSha256) && typeof active.pluginRoot === "string" && isAbsolute4(active.pluginRoot) && launcherPath !== void 0 && expectedLauncher !== void 0 && pathsEqual(launcherPath, expectedLauncher) && Object.keys(active).length === 6 && Object.keys(active.manifest).length === 5 && active.manifest.name === plan.hostName && active.manifest.description === plan.manifest.description && active.manifest.type === "stdio" && Array.isArray(active.manifest.allowed_origins) && active.manifest.allowed_origins.length === 1 && active.manifest.allowed_origins[0] === plan.manifest.allowed_origins[0];
954
+ }
955
+
956
+ // plugin/shared/scripts/doctor.mjs
957
+ var execFileAsync4 = promisify4(execFile4);
958
+ var MINIMUM_NODE_MAJOR = 22;
959
+ var BROWSER_RPC_VERSION = 26;
960
+ var MAX_ENDPOINT_DESCRIPTORS = 64;
961
+ var ENDPOINT_PROBE_TIMEOUT_MS = 250;
962
+ var MAX_ENDPOINT_PROBES = 4;
963
+ await runDoctorCli(process.argv.slice(2));
964
+ async function runDoctorCli(args) {
965
+ let options;
966
+ try {
967
+ options = parseArguments(args);
968
+ } catch {
969
+ console.error("[copilot-browser] Usage: node doctor.mjs [--json]");
970
+ process.exitCode = 1;
971
+ return;
972
+ }
973
+ if (!options) return;
974
+ let report;
975
+ try {
976
+ report = await createDoctorReport();
977
+ } catch {
978
+ report = doctorReport({
979
+ setup: [
980
+ doctorCheck(
981
+ "diagnostics",
982
+ "fail",
983
+ "Diagnostics could not complete. Reinstall the plugin or run activation again."
984
+ )
985
+ ],
986
+ installedExtension: [],
987
+ liveRuntime: [],
988
+ compatibility: []
989
+ });
990
+ }
991
+ if (options.json) console.log(JSON.stringify(report, null, 2));
992
+ else printDoctorReport(report);
993
+ if (report.status === "fail") process.exitCode = 1;
994
+ }
995
+ async function createDoctorReport() {
996
+ const setup = [diagnoseNodeRuntime(), await diagnosePluginFiles()];
997
+ const installed = await diagnoseInstalledExtensions(
998
+ channel.extensionId,
999
+ plugin_default.version,
1000
+ channel.extensionInstallUrls
1001
+ );
1002
+ const liveDescriptors = await readLiveEndpointDescriptors();
1003
+ const liveRuntime = diagnoseLiveRuntime(liveDescriptors);
1004
+ const compatibility = diagnoseCompatibility(installed.states, liveDescriptors);
1005
+ if (hasUsableLiveRuntime(liveDescriptors)) {
1006
+ preferLiveRuntimeEvidence(installed.checks);
1007
+ }
1008
+ if (setup.some(({ status }) => status === "fail")) {
1009
+ return doctorReport({
1010
+ setup,
1011
+ installedExtension: installed.checks,
1012
+ liveRuntime,
1013
+ compatibility
1014
+ });
1015
+ }
1016
+ let plan;
1017
+ try {
1018
+ plan = await createRegistrationPlan();
1019
+ } catch {
1020
+ setup.push(
1021
+ doctorCheck(
1022
+ "plugin-configuration",
1023
+ "fail",
1024
+ "Browser installation configuration is invalid. Remove custom Browser installation overrides or reinstall this plugin."
1025
+ )
1026
+ );
1027
+ return doctorReport({
1028
+ setup,
1029
+ installedExtension: installed.checks,
1030
+ liveRuntime,
1031
+ compatibility
1032
+ });
1033
+ }
1034
+ setup.push(
1035
+ doctorCheck("plugin-configuration", "pass", "Browser installation configuration is valid.")
1036
+ );
1037
+ setup.push(await diagnoseActivationState(plan));
1038
+ const installedHost = await readOptionalFile(plan.nativeHostBundle);
1039
+ if (installedHost === void 0) {
1040
+ setup.push(
1041
+ doctorCheck(
1042
+ "native-host-installation",
1043
+ "warn",
1044
+ "Browser setup is incomplete. Run /browser-use:activate."
1045
+ )
1046
+ );
1047
+ } else {
1048
+ const installedSha256 = createHash2("sha256").update(installedHost).digest("hex");
1049
+ setup.push(
1050
+ installedSha256 === plan.bundleSha256 ? doctorCheck(
1051
+ "native-host-installation",
1052
+ "pass",
1053
+ "Browser setup matches this plugin installation."
1054
+ ) : doctorCheck(
1055
+ "native-host-installation",
1056
+ "fail",
1057
+ "Browser setup does not match this installation. Run /browser-use:activate."
1058
+ )
1059
+ );
1060
+ }
1061
+ const installedLauncher = await readOptionalFile(plan.launcherPath, "utf8");
1062
+ const launcherMatches = installedLauncher === launcherContent(plan.nativeHostBundle, plan.installationSha256, plan.version) && await launcherIsExecutable(plan.launcherPath);
1063
+ setup.push(
1064
+ installedLauncher === void 0 ? doctorCheck(
1065
+ "native-host-launcher",
1066
+ "warn",
1067
+ "Browser startup files are missing. Run /browser-use:activate."
1068
+ ) : launcherMatches ? doctorCheck(
1069
+ "native-host-launcher",
1070
+ "pass",
1071
+ "Browser startup files match this installation."
1072
+ ) : doctorCheck(
1073
+ "native-host-launcher",
1074
+ "fail",
1075
+ "Browser startup files do not match this installation. Run /browser-use:activate."
1076
+ )
1077
+ );
1078
+ setup.push(await diagnoseManifests(plan));
1079
+ setup.push(...await diagnoseProcessUsage(plan));
1080
+ if (plan.registryKey) setup.push(await diagnoseWindowsRegistration(plan));
1081
+ return doctorReport({
1082
+ setup,
1083
+ installedExtension: installed.checks,
1084
+ liveRuntime,
1085
+ compatibility
1086
+ });
1087
+ }
1088
+ function preferLiveRuntimeEvidence(checks) {
1089
+ for (const check of checks) {
1090
+ if (check.id === "browser-prerequisite" && check.status === "fail") {
1091
+ check.status = "warn";
1092
+ check.message = "A Browser runtime is connected, but the offline Chrome or Edge installation could not be verified.";
1093
+ } else if (check.id === "browser-extension" && check.status === "fail") {
1094
+ check.status = "warn";
1095
+ check.message = "A Browser runtime is connected, but the matching offline extension installation could not be verified.";
1096
+ }
1097
+ }
1098
+ }
1099
+ async function readLiveEndpointDescriptors() {
1100
+ const records = (await discoverLocalEndpointDescriptorRecords()).filter(
1101
+ ({ processState }) => processState !== "missing"
1102
+ );
1103
+ const descriptors = [];
1104
+ let next = 0;
1105
+ await Promise.all(
1106
+ Array.from({ length: Math.min(MAX_ENDPOINT_PROBES, records.length) }, async () => {
1107
+ while (next < records.length && descriptors.length < MAX_ENDPOINT_DESCRIPTORS) {
1108
+ const record = records[next++];
1109
+ if (record && await endpointIsReachable(record.descriptor.address)) {
1110
+ descriptors.push(record.descriptor);
1111
+ }
1112
+ }
1113
+ })
1114
+ );
1115
+ return descriptors.sort((left, right) => right.createdAt - left.createdAt).slice(0, MAX_ENDPOINT_DESCRIPTORS);
1116
+ }
1117
+ async function endpointIsReachable(address) {
1118
+ const socket = connect(address);
1119
+ try {
1120
+ await once(socket, "connect", { signal: AbortSignal.timeout(ENDPOINT_PROBE_TIMEOUT_MS) });
1121
+ return true;
1122
+ } catch {
1123
+ return false;
1124
+ } finally {
1125
+ socket.destroy();
1126
+ }
1127
+ }
1128
+ function diagnoseLiveRuntime(descriptors) {
1129
+ const relevant = descriptors.filter(
1130
+ ({ diagnostics }) => diagnostics === void 0 || diagnostics.extensionId === channel.extensionId
1131
+ );
1132
+ if (relevant.length === 0) {
1133
+ return [doctorCheck("live-runtime", "warn", "No Browser runtime is currently connected.")];
1134
+ }
1135
+ const identityCounts = /* @__PURE__ */ new Map();
1136
+ for (const { diagnostics } of relevant) {
1137
+ if (diagnostics?.browserRpcVersion !== BROWSER_RPC_VERSION) continue;
1138
+ const identity = diagnostics?.extensionInstanceId;
1139
+ if (identity) identityCounts.set(identity, (identityCounts.get(identity) ?? 0) + 1);
1140
+ }
1141
+ const hasUsableRuntime = relevant.some(
1142
+ ({ diagnostics }) => diagnostics?.browserRpcVersion === BROWSER_RPC_VERSION && diagnostics.extensionInstanceId && identityCounts.get(diagnostics.extensionInstanceId) === 1
1143
+ );
1144
+ return relevant.map(({ diagnostics }, index) => {
1145
+ if (!diagnostics) {
1146
+ return doctorCheck(
1147
+ `live-runtime-${index + 1}`,
1148
+ "warn",
1149
+ "A connected Browser runtime uses legacy diagnostic metadata."
1150
+ );
1151
+ }
1152
+ const duplicate = diagnostics.extensionInstanceId && identityCounts.get(diagnostics.extensionInstanceId) > 1;
1153
+ const missingIdentity = !diagnostics.extensionInstanceId;
1154
+ const rpcMatches = diagnostics.browserRpcVersion === BROWSER_RPC_VERSION;
1155
+ const identity = diagnostics.installationId.slice(0, 12);
1156
+ return doctorCheck(
1157
+ `live-runtime-${index + 1}`,
1158
+ duplicate || missingIdentity || !rpcMatches ? hasUsableRuntime ? "warn" : "fail" : "pass",
1159
+ `Browser runtime ${identity} uses RPC ${diagnostics.browserRpcVersion}, Extension ${diagnostics.extensionVersion ?? "unknown"}, and Host ${diagnostics.nativeHostVersion}${duplicate ? "; duplicate Extension identity detected" : ""}${missingIdentity ? "; Extension identity is unavailable" : ""}.`
1160
+ );
1161
+ });
1162
+ }
1163
+ function hasUsableLiveRuntime(descriptors) {
1164
+ const relevant = descriptors.filter(
1165
+ ({ diagnostics }) => diagnostics?.extensionId === channel.extensionId && diagnostics.browserRpcVersion === BROWSER_RPC_VERSION
1166
+ );
1167
+ const identityCounts = /* @__PURE__ */ new Map();
1168
+ for (const { diagnostics } of relevant) {
1169
+ const identity = diagnostics.extensionInstanceId;
1170
+ if (identity) identityCounts.set(identity, (identityCounts.get(identity) ?? 0) + 1);
1171
+ }
1172
+ return relevant.some(
1173
+ ({ diagnostics }) => diagnostics.browserRpcVersion === BROWSER_RPC_VERSION && diagnostics.extensionInstanceId && identityCounts.get(diagnostics.extensionInstanceId) === 1
1174
+ );
1175
+ }
1176
+ function diagnoseCompatibility(extensionStates, descriptors) {
1177
+ const live = descriptors.map(({ diagnostics }) => diagnostics).filter((diagnostics) => diagnostics?.extensionId === channel.extensionId);
1178
+ const compatible = live.some(
1179
+ ({ browserRpcVersion }) => browserRpcVersion === BROWSER_RPC_VERSION
1180
+ );
1181
+ const incompatible = live.filter(
1182
+ ({ browserRpcVersion }) => browserRpcVersion !== BROWSER_RPC_VERSION
1183
+ );
1184
+ const incompatibleEnabledRpcVersion = firstIncompatibleExtensionRpcVersion(
1185
+ extensionStates,
1186
+ BROWSER_RPC_VERSION
1187
+ );
1188
+ let rpcCheck;
1189
+ if (!compatible && incompatible.length > 0) {
1190
+ rpcCheck = doctorCheck(
1191
+ "rpc-compatibility",
1192
+ "fail",
1193
+ rpcDirectionMessage(incompatible[0].browserRpcVersion)
1194
+ );
1195
+ } else if (!compatible && incompatibleEnabledRpcVersion !== void 0) {
1196
+ rpcCheck = doctorCheck(
1197
+ "rpc-compatibility",
1198
+ "fail",
1199
+ rpcDirectionMessage(incompatibleEnabledRpcVersion)
1200
+ );
1201
+ } else if (incompatible.length > 0) {
1202
+ rpcCheck = doctorCheck(
1203
+ "rpc-compatibility",
1204
+ "warn",
1205
+ "A compatible Browser runtime is available, but another runtime uses an incompatible RPC version."
1206
+ );
1207
+ } else {
1208
+ rpcCheck = doctorCheck(
1209
+ "rpc-compatibility",
1210
+ compatible ? "pass" : "warn",
1211
+ compatible ? `A connected Browser runtime matches RPC version ${BROWSER_RPC_VERSION}.` : "Browser RPC compatibility could not be verified while no runtime is connected."
1212
+ );
1213
+ }
1214
+ const compatibleRuntimes = live.filter(
1215
+ ({ browserRpcVersion }) => browserRpcVersion === BROWSER_RPC_VERSION
1216
+ );
1217
+ if (compatibleRuntimes.length === 0) return [rpcCheck];
1218
+ const productVersionDifferences = [
1219
+ ...new Set(
1220
+ compatibleRuntimes.flatMap(
1221
+ ({ extensionVersion, nativeHostVersion }) => [
1222
+ describeProductVersionDifference("Extension", extensionVersion),
1223
+ describeProductVersionDifference("Host", nativeHostVersion)
1224
+ ].filter((difference) => difference !== void 0)
1225
+ )
1226
+ )
1227
+ ];
1228
+ return [
1229
+ rpcCheck,
1230
+ doctorCheck(
1231
+ "product-version-compatibility",
1232
+ productVersionDifferences.length === 0 ? "pass" : "warn",
1233
+ productVersionDifferences.length === 0 ? `Connected Browser components match plugin version ${plugin_default.version}.` : `Connected Browser product versions differ from Plugin ${plugin_default.version}: ${productVersionDifferences.join("; ")}. Update the older side; if versions cannot be ordered, update the Plugin and Extension together.`
1234
+ )
1235
+ ];
1236
+ }
1237
+ function describeProductVersionDifference(component, version) {
1238
+ if (typeof version !== "string") return `${component} version is unknown`;
1239
+ try {
1240
+ const comparison = comparePluginVersions(version, plugin_default.version);
1241
+ if (comparison === 0) return void 0;
1242
+ return `${component} ${version} is ${comparison < 0 ? "older" : "newer"}`;
1243
+ } catch {
1244
+ return `${component} ${version} cannot be ordered`;
1245
+ }
1246
+ }
1247
+ function rpcDirectionMessage(otherVersion) {
1248
+ if (!Number.isSafeInteger(otherVersion)) {
1249
+ return "Browser RPC is incompatible; update direction is unknown. Update the Plugin and Extension.";
1250
+ }
1251
+ return otherVersion < BROWSER_RPC_VERSION ? `Browser RPC ${otherVersion} is older than Plugin RPC ${BROWSER_RPC_VERSION}. Update the Extension.` : `Browser RPC ${otherVersion} is newer than Plugin RPC ${BROWSER_RPC_VERSION}. Update the Plugin.`;
1252
+ }
1253
+ async function diagnoseActivationState(plan) {
1254
+ let active;
1255
+ let pending;
1256
+ try {
1257
+ [active, pending] = await Promise.all([
1258
+ readActiveInstallation(plan),
1259
+ readPendingInstallation(plan)
1260
+ ]);
1261
+ } catch {
1262
+ return doctorCheck(
1263
+ "active-installation",
1264
+ "fail",
1265
+ "The Browser activation record is invalid. Run /browser-use:activate."
1266
+ );
1267
+ }
1268
+ if (pending) {
1269
+ return doctorCheck(
1270
+ "active-installation",
1271
+ "fail",
1272
+ `Browser activation for version ${pending.version} was interrupted. Run /browser-use:activate to complete setup for this plugin version ${plan.version}.`
1273
+ );
1274
+ }
1275
+ if (!active) {
1276
+ return doctorCheck(
1277
+ "active-installation",
1278
+ "warn",
1279
+ "Browser has not been activated for this channel. Run /browser-use:activate."
1280
+ );
1281
+ }
1282
+ if (active.installationSha256 === plan.installationSha256 && pathsEqual(active.pluginRoot, plan.pluginRoot) && active.version === plan.version) {
1283
+ return doctorCheck(
1284
+ "active-installation",
1285
+ "pass",
1286
+ `This plugin installation owns the active Browser setup for version ${plan.version}.`
1287
+ );
1288
+ }
1289
+ const comparison = comparePluginVersions(active.version, plan.version);
1290
+ if (comparison > 0) {
1291
+ return doctorCheck(
1292
+ "active-installation",
1293
+ "fail",
1294
+ `The active Browser setup is version ${active.version}, but this plugin session is version ${plan.version}. Upgrade or restart the plugin session, or run /browser-use:activate to replace the active setup intentionally.`
1295
+ );
1296
+ }
1297
+ if (comparison < 0) {
1298
+ return doctorCheck(
1299
+ "active-installation",
1300
+ "fail",
1301
+ `The active Browser setup is version ${active.version}, older than this plugin version ${plan.version}. Run /browser-use:activate.`
1302
+ );
1303
+ }
1304
+ return doctorCheck(
1305
+ "active-installation",
1306
+ "warn",
1307
+ `Another plugin installation owns the active Browser setup for version ${active.version}. Run /browser-use:activate to use this installation.`
1308
+ );
1309
+ }
1310
+ async function diagnosePluginFiles() {
1311
+ const [metadataContent, mcpConfigContent, hooksContent, ...requiredContents] = await Promise.all([
1312
+ readOptionalFile(join5(pluginRoot, ".plugin", "plugin.json"), "utf8"),
1313
+ readOptionalFile(join5(pluginRoot, ".mcp.json"), "utf8"),
1314
+ readOptionalFile(join5(pluginRoot, "hooks", "hooks.json"), "utf8"),
1315
+ ...[
1316
+ ["dist", "browser-mcp.cjs"],
1317
+ ["dist", "browser-run-worker.cjs"],
1318
+ ["dist", "native-host.cjs"],
1319
+ ["scripts", "activate.mjs"],
1320
+ ["scripts", "deactivate.mjs"],
1321
+ ["scripts", "doctor.mjs"],
1322
+ ["scripts", "nativeMessagingRegistration.mjs"],
1323
+ ["skills", "activate", "SKILL.md"],
1324
+ ["skills", "browser-use", "SKILL.md"],
1325
+ ["skills", "deactivate", "SKILL.md"],
1326
+ ["skills", "doctor", "SKILL.md"]
1327
+ ].map((segments) => readOptionalFile(join5(pluginRoot, ...segments)))
1328
+ ]);
1329
+ if (metadataContent === void 0 || mcpConfigContent === void 0 || hooksContent === void 0 || requiredContents.some((content) => content === void 0)) {
1330
+ return doctorCheck(
1331
+ "plugin-files",
1332
+ "fail",
1333
+ "Required plugin files are missing. Reinstall this plugin."
1334
+ );
1335
+ }
1336
+ try {
1337
+ const metadata = JSON.parse(metadataContent);
1338
+ const mcpConfig = JSON.parse(mcpConfigContent);
1339
+ const hooksConfig = JSON.parse(hooksContent);
1340
+ const mcpServer = mcpConfig?.mcpServers?.["browser-use"];
1341
+ const sessionStart = hooksConfig?.hooks?.sessionStart;
1342
+ const activationHook = Array.isArray(sessionStart) ? sessionStart[0] : void 0;
1343
+ if (metadata.name !== plugin_default.name || metadata.version !== plugin_default.version || metadata.mcpServers !== ".mcp.json" || metadata.commands !== void 0 || metadata.skills !== "skills" || hooksConfig?.version !== 1 || Object.keys(hooksConfig?.hooks ?? {}).length !== 1 || sessionStart?.length !== 1 || activationHook?.type !== "command" || activationHook?.bash !== 'node "$COPILOT_BROWSER_PLUGIN_ROOT/scripts/activate.mjs" --session-start' || activationHook?.powershell !== "$ErrorActionPreference = 'Stop'; node (Join-Path $env:COPILOT_BROWSER_PLUGIN_ROOT 'scripts/activate.mjs') --session-start; exit $LASTEXITCODE" || activationHook?.env?.COPILOT_BROWSER_PLUGIN_ROOT !== "${PLUGIN_ROOT}" || Object.keys(activationHook.env).length !== 1 || activationHook.exec !== void 0 || activationHook.args !== void 0 || activationHook?.timeoutSec !== 30 || Object.keys(mcpConfig?.mcpServers ?? {}).length !== 1 || mcpServer?.type !== "stdio" || mcpServer?.command !== "node" || !Array.isArray(mcpServer?.args) || mcpServer.args.length !== 1 || mcpServer.args[0] !== "${PLUGIN_ROOT}/dist/browser-mcp.cjs") {
1344
+ throw new Error("Plugin metadata mismatch.");
1345
+ }
1346
+ } catch {
1347
+ return doctorCheck(
1348
+ "plugin-files",
1349
+ "fail",
1350
+ "Plugin files do not match this plugin version. Reinstall this plugin."
1351
+ );
1352
+ }
1353
+ return doctorCheck(
1354
+ "plugin-files",
1355
+ "pass",
1356
+ `Plugin files match version ${plugin_default.version}.`
1357
+ );
1358
+ }
1359
+ function diagnoseNodeRuntime() {
1360
+ const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
1361
+ return major >= MINIMUM_NODE_MAJOR ? doctorCheck(
1362
+ "node-runtime",
1363
+ "pass",
1364
+ `Node.js ${MINIMUM_NODE_MAJOR} or later is available for the plugin.`
1365
+ ) : doctorCheck(
1366
+ "node-runtime",
1367
+ "fail",
1368
+ `Node.js ${MINIMUM_NODE_MAJOR} or later is required to use this plugin.`
1369
+ );
1370
+ }
1371
+ async function diagnoseManifests(plan) {
1372
+ let missing = 0;
1373
+ for (const manifestPath of plan.manifestPaths) {
1374
+ const content = await readOptionalFile(manifestPath, "utf8");
1375
+ if (content === void 0) {
1376
+ missing += 1;
1377
+ continue;
1378
+ }
1379
+ try {
1380
+ if (!manifestMatchesPlan(JSON.parse(content), plan)) {
1381
+ return doctorCheck(
1382
+ "native-messaging-manifests",
1383
+ "fail",
1384
+ "Browser setup belongs to another installation. Run /browser-use:activate."
1385
+ );
1386
+ }
1387
+ } catch {
1388
+ return doctorCheck(
1389
+ "native-messaging-manifests",
1390
+ "fail",
1391
+ "Browser setup is invalid. Run /browser-use:activate."
1392
+ );
1393
+ }
1394
+ }
1395
+ return missing > 0 ? doctorCheck(
1396
+ "native-messaging-manifests",
1397
+ "warn",
1398
+ "Browser setup is incomplete. Run /browser-use:activate."
1399
+ ) : doctorCheck(
1400
+ "native-messaging-manifests",
1401
+ "pass",
1402
+ "Browser setup is complete for this installation."
1403
+ );
1404
+ }
1405
+ async function diagnoseWindowsRegistration(plan) {
1406
+ const registeredManifest = await readRegistryDefaultValue(plan.registryKey);
1407
+ if (!registeredManifest) {
1408
+ return doctorCheck(
1409
+ "windows-registration",
1410
+ "warn",
1411
+ "Browser setup is incomplete. Run /browser-use:activate."
1412
+ );
1413
+ }
1414
+ if (!pathsEqual(registeredManifest, plan.manifestPaths[0])) {
1415
+ return doctorCheck(
1416
+ "windows-registration",
1417
+ "fail",
1418
+ "Browser setup belongs to another installation. Run /browser-use:activate."
1419
+ );
1420
+ }
1421
+ const content = await readOptionalFile(registeredManifest, "utf8");
1422
+ if (content === void 0) {
1423
+ return doctorCheck(
1424
+ "windows-registration",
1425
+ "warn",
1426
+ "Browser setup is incomplete. Run /browser-use:activate."
1427
+ );
1428
+ }
1429
+ try {
1430
+ if (manifestMatchesPlan(JSON.parse(content), plan)) {
1431
+ return doctorCheck(
1432
+ "windows-registration",
1433
+ "pass",
1434
+ "Browser setup is active for this installation."
1435
+ );
1436
+ }
1437
+ } catch {
1438
+ }
1439
+ return doctorCheck(
1440
+ "windows-registration",
1441
+ "fail",
1442
+ "Browser setup belongs to another installation. Run /browser-use:activate."
1443
+ );
1444
+ }
1445
+ async function diagnoseProcessUsage(plan) {
1446
+ let processes;
1447
+ try {
1448
+ processes = await listProcesses();
1449
+ } catch {
1450
+ return [
1451
+ doctorCheck(
1452
+ "plugin-processes",
1453
+ "warn",
1454
+ "Running plugin processes could not be inspected. Close Copilot sessions before uninstalling or replacing this installation."
1455
+ ),
1456
+ doctorCheck(
1457
+ "browser-runtime-processes",
1458
+ "warn",
1459
+ "Running Browser runtime processes could not be inspected. Restart Chrome or Edge if it remains connected to an older version."
1460
+ )
1461
+ ];
1462
+ }
1463
+ const pluginExecutable = normalizeCommandLine(join5(pluginRoot, "dist", "browser-mcp.cjs"));
1464
+ const processesUsingPlugin = processes.filter(
1465
+ ({ processId, commandLine }) => processId !== process.pid && normalizeCommandLine(commandLine).includes(pluginExecutable)
1466
+ );
1467
+ let active;
1468
+ try {
1469
+ active = await readActiveInstallation(plan);
1470
+ } catch {
1471
+ active = void 0;
1472
+ }
1473
+ const versionsRoot = `${normalizeCommandLine(join5(plan.installRoot, "versions"))}/`;
1474
+ const activeRoot = active ? `${normalizeCommandLine(join5(active.manifest.path, ".."))}/` : void 0;
1475
+ const runtimeProcesses = processes.filter(({ processId, commandLine }) => {
1476
+ if (processId === process.pid) return false;
1477
+ const normalized = normalizeCommandLine(commandLine);
1478
+ return normalized.includes(versionsRoot) && normalized.includes("native-host.cjs");
1479
+ });
1480
+ const inactiveRuntimeRunning = runtimeProcesses.some(({ commandLine }) => {
1481
+ const normalized = normalizeCommandLine(commandLine);
1482
+ return activeRoot === void 0 || !normalized.includes(activeRoot);
1483
+ });
1484
+ return [
1485
+ processesUsingPlugin.length === 0 ? doctorCheck(
1486
+ "plugin-processes",
1487
+ "pass",
1488
+ "No running agent process is using this plugin installation."
1489
+ ) : doctorCheck(
1490
+ "plugin-processes",
1491
+ "warn",
1492
+ "One or more active Copilot sessions are using this installation. Close Copilot CLI, VS Code, IntelliJ, and other agent sessions before uninstalling or replacing it."
1493
+ ),
1494
+ inactiveRuntimeRunning ? doctorCheck(
1495
+ "browser-runtime-processes",
1496
+ "warn",
1497
+ "A Browser runtime process is still using an inactive installed version. Restart Chrome or Edge to use the active version."
1498
+ ) : doctorCheck(
1499
+ "browser-runtime-processes",
1500
+ "pass",
1501
+ runtimeProcesses.length === 0 ? "No Browser runtime process is currently running." : `Running Browser runtime processes use the active version ${active.version}.`
1502
+ )
1503
+ ];
1504
+ }
1505
+ async function listProcesses() {
1506
+ if (platform3() === "win32") {
1507
+ const command = "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress";
1508
+ const { stdout: stdout2 } = await execFileAsync4(
1509
+ "powershell.exe",
1510
+ ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command],
1511
+ { windowsHide: true, maxBuffer: 4 * 1024 * 1024 }
1512
+ );
1513
+ const parsed = JSON.parse(stdout2 || "[]");
1514
+ const values = Array.isArray(parsed) ? parsed : [parsed];
1515
+ return values.filter(
1516
+ (value) => value !== null && typeof value === "object" && Number.isSafeInteger(value.ProcessId) && typeof value.CommandLine === "string"
1517
+ ).map((value) => ({ processId: value.ProcessId, commandLine: value.CommandLine }));
1518
+ }
1519
+ const { stdout } = await execFileAsync4("ps", ["-axo", "pid=,command="], {
1520
+ maxBuffer: 4 * 1024 * 1024
1521
+ });
1522
+ return stdout.split(/\r?\n/u).map((line) => /^\s*(\d+)\s+(.+)$/u.exec(line)).filter((match) => match !== null).map((match) => ({ processId: Number(match[1]), commandLine: match[2] }));
1523
+ }
1524
+ async function readOptionalFile(path, encoding) {
1525
+ try {
1526
+ return await readFile5(path, encoding);
1527
+ } catch (error) {
1528
+ if (isMissingFileError(error)) return void 0;
1529
+ throw error;
1530
+ }
1531
+ }
1532
+ function doctorCheck(id, status, message) {
1533
+ return { id, status, message };
1534
+ }
1535
+ function doctorReport(groups) {
1536
+ const checks = Object.values(groups).flat();
1537
+ const status = checks.some(({ status: status2 }) => status2 === "fail") ? "fail" : checks.some(({ status: status2 }) => status2 === "warn") ? "warn" : "pass";
1538
+ return {
1539
+ status,
1540
+ groups,
1541
+ summary: {
1542
+ passed: checks.filter(({ status: status2 }) => status2 === "pass").length,
1543
+ warnings: checks.filter(({ status: status2 }) => status2 === "warn").length,
1544
+ failures: checks.filter(({ status: status2 }) => status2 === "fail").length
1545
+ }
1546
+ };
1547
+ }
1548
+ function printDoctorReport(report) {
1549
+ console.log("Copilot Browser diagnostics");
1550
+ console.log("");
1551
+ for (const [groupName, checks] of Object.entries(report.groups)) {
1552
+ console.log(groupName.replace(/[A-Z]/gu, (letter) => ` ${letter}`).toUpperCase());
1553
+ for (const check of checks) {
1554
+ console.log(`${check.status.toUpperCase().padEnd(4)} ${check.message}`);
1555
+ }
1556
+ console.log("");
1557
+ }
1558
+ console.log(
1559
+ `Summary: ${report.summary.passed} passed, ${report.summary.warnings} warnings, ${report.summary.failures} failures.`
1560
+ );
1561
+ }
1562
+ function parseArguments(args) {
1563
+ if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
1564
+ console.log("Usage: node doctor.mjs [--json]");
1565
+ return void 0;
1566
+ }
1567
+ const unexpected = args.filter((arg) => arg !== "--json");
1568
+ if (unexpected.length > 0 || args.filter((arg) => arg === "--json").length > 1) {
1569
+ throw new Error("Usage: node doctor.mjs [--json]");
1570
+ }
1571
+ return { json: args.includes("--json") };
1572
+ }
1573
+ function normalizeCommandLine(value) {
1574
+ const normalized = value.replaceAll("\\", "/");
1575
+ return platform3() === "win32" ? normalized.toLowerCase() : normalized;
1576
+ }
1577
+ export {
1578
+ preferLiveRuntimeEvidence,
1579
+ runDoctorCli
1580
+ };