@enterpriseai/cli 3.15.9 → 3.15.11

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 (37) hide show
  1. package/README.md +45 -11
  2. package/dist/commands/classifier.d.ts.map +1 -1
  3. package/dist/commands/classifier.js +4 -1
  4. package/dist/commands/classifier.js.map +1 -1
  5. package/dist/commands/docs.d.ts.map +1 -1
  6. package/dist/commands/docs.js +88 -31
  7. package/dist/commands/docs.js.map +1 -1
  8. package/dist/commands/init.d.ts +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +15 -10
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/start.d.ts +15 -0
  13. package/dist/commands/start.d.ts.map +1 -1
  14. package/dist/commands/start.js +111 -29
  15. package/dist/commands/start.js.map +1 -1
  16. package/dist/lib/agent-guide.js +2 -2
  17. package/dist/lib/agent-guide.js.map +1 -1
  18. package/dist/lib/ai-surface-installer.d.ts +38 -0
  19. package/dist/lib/ai-surface-installer.d.ts.map +1 -0
  20. package/dist/lib/ai-surface-installer.js +160 -0
  21. package/dist/lib/ai-surface-installer.js.map +1 -0
  22. package/dist/lib/ai-surfaces.d.ts +257 -6
  23. package/dist/lib/ai-surfaces.d.ts.map +1 -1
  24. package/dist/lib/ai-surfaces.js +3182 -170
  25. package/dist/lib/ai-surfaces.js.map +1 -1
  26. package/dist/lib/api.d.ts +16 -2
  27. package/dist/lib/api.d.ts.map +1 -1
  28. package/dist/lib/api.js +43 -8
  29. package/dist/lib/api.js.map +1 -1
  30. package/dist/lib/gofer-installer.js +1 -1
  31. package/package.json +2 -2
  32. package/resources/gofer/.gofer-version +17 -1
  33. package/resources/gofer/bash-scripts/install-optional-tools.sh +357 -16
  34. package/resources/gofer/bash-scripts/sync-implementation-status.sh +3 -4
  35. package/resources/gofer/powershell-scripts/install-optional-tools.ps1 +419 -11
  36. package/resources/gofer/references/platform/eai-service-patterns.md +84 -1
  37. package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
@@ -1,8 +1,21 @@
1
- import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
- import { mkdir, readFile, rename, writeFile, chmod } from 'node:fs/promises';
1
+ import { constants as fsConstants, closeSync, existsSync, fstatSync, lstatSync, opendirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync, } from 'node:fs';
2
+ import { mkdir, open, readFile, rename, rm } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
- import { basename, dirname, join, resolve, win32 as win32Path } from 'node:path';
4
+ import { basename, delimiter, dirname, join, relative, resolve, win32 as win32Path, } from 'node:path';
5
5
  import { spawn, spawnSync } from 'node:child_process';
6
+ import { createHash, randomBytes } from 'node:crypto';
7
+ export const AI_SURFACE_COMPANION_CLIS = Object.freeze({
8
+ 'vscode-copilot': 'copilot-cli',
9
+ 'copilot-desktop': 'copilot-cli',
10
+ 'antigravity-desktop': 'antigravity-cli',
11
+ 'claude-desktop': 'claude-cli',
12
+ 'codex-desktop': 'codex-cli',
13
+ 'grok-bot': 'grok-cli',
14
+ });
15
+ export function companionCliForSurface(surfaceId) {
16
+ return AI_SURFACE_COMPANION_CLIS[surfaceId]
17
+ ?? (surfaceId.endsWith('-cli') ? surfaceId : null);
18
+ }
6
19
  export const EAI_FIRST_PROMPT = [
7
20
  'Use the repository EAI skill to start this app.',
8
21
  'Ask me for the business outcome first.',
@@ -10,7 +23,197 @@ export const EAI_FIRST_PROMPT = [
10
23
  'Keep internal numbered delivery stages hidden.',
11
24
  'Pause for my approval of the business specification, then continue unless a material business, security, cost, deployment, or destructive decision needs approval.',
12
25
  ].join(' ');
13
- export const AI_SURFACES = [
26
+ const AI_SURFACE_DEFINITIONS = [
27
+ {
28
+ id: 'vscode-copilot',
29
+ name: 'GitHub Copilot in VS Code',
30
+ provider: 'GitHub',
31
+ kind: 'editor',
32
+ installUrl: 'https://code.visualstudio.com/docs/copilot/setup',
33
+ launchSupport: 'project-and-prompt',
34
+ commands: ['code'],
35
+ macApplications: ['/Applications/Visual Studio Code.app'],
36
+ windowsProgramFilesApplications: ['Microsoft VS Code/Code.exe'],
37
+ },
38
+ {
39
+ id: 'copilot-cli',
40
+ name: 'GitHub Copilot CLI',
41
+ provider: 'GitHub',
42
+ kind: 'cli',
43
+ installUrl: 'https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli',
44
+ launchSupport: 'project-and-prompt',
45
+ commands: ['copilot'],
46
+ macExecutables: ['.local/bin/copilot'],
47
+ windowsExecutables: [
48
+ 'AppData/Local/Microsoft/WinGet/Links/copilot.exe',
49
+ 'AppData/Roaming/npm/copilot.cmd',
50
+ ],
51
+ linuxExecutables: ['.local/bin/copilot'],
52
+ },
53
+ {
54
+ id: 'copilot-desktop',
55
+ name: 'GitHub Copilot app',
56
+ provider: 'GitHub',
57
+ kind: 'desktop',
58
+ installUrl: 'https://docs.github.com/en/copilot/get-started/quickstart-copilot-app',
59
+ launchSupport: 'manual-project',
60
+ commands: [],
61
+ macApplications: ['/Applications/GitHub Copilot.app'],
62
+ windowsApplications: ['AppData/Local/Programs/GitHub Copilot/github.exe'],
63
+ windowsProgramFilesApplications: ['GitHub Copilot/github.exe'],
64
+ linuxApplications: ['/usr/bin/github'],
65
+ },
66
+ {
67
+ id: 'antigravity-desktop',
68
+ name: 'Google Antigravity 2.0',
69
+ provider: 'Google',
70
+ kind: 'desktop',
71
+ installUrl: 'https://antigravity.google/download',
72
+ launchSupport: 'manual-project',
73
+ commands: [],
74
+ macApplications: ['/Applications/Antigravity.app'],
75
+ windowsApplications: ['AppData/Local/Programs/antigravity/Antigravity.exe'],
76
+ linuxApplications: [
77
+ 'Applications/Antigravity-arm64/antigravity',
78
+ 'Applications/Antigravity-x64/antigravity',
79
+ 'Downloads/Antigravity-arm64/antigravity',
80
+ 'Downloads/Antigravity-x64/antigravity',
81
+ '/opt/Antigravity-arm64/antigravity',
82
+ '/opt/Antigravity-x64/antigravity',
83
+ ],
84
+ },
85
+ {
86
+ id: 'antigravity-cli',
87
+ name: 'Antigravity CLI (agy)',
88
+ provider: 'Google',
89
+ kind: 'cli',
90
+ installUrl: 'https://antigravity.google/docs/cli/install/',
91
+ launchSupport: 'project-and-prompt',
92
+ commands: ['agy'],
93
+ macExecutables: ['.local/bin/agy'],
94
+ windowsExecutables: ['AppData/Local/agy/bin/agy.exe'],
95
+ linuxExecutables: ['.local/bin/agy'],
96
+ },
97
+ {
98
+ id: 'claude-desktop',
99
+ name: 'Claude Desktop',
100
+ provider: 'Anthropic',
101
+ kind: 'desktop',
102
+ installUrl: 'https://claude.com/download',
103
+ launchSupport: 'project-and-prompt',
104
+ commands: [],
105
+ macApplications: ['/Applications/Claude.app'],
106
+ windowsApplications: [
107
+ 'AppData/Local/AnthropicClaude/Claude.exe',
108
+ 'AppData/Local/Programs/Claude/Claude.exe',
109
+ ],
110
+ windowsAppxPackageNames: ['Claude'],
111
+ linuxApplications: ['/usr/bin/claude-desktop', '/usr/lib/claude-desktop/claude-desktop'],
112
+ },
113
+ {
114
+ id: 'claude-cli',
115
+ name: 'Claude Code',
116
+ provider: 'Anthropic',
117
+ kind: 'cli',
118
+ installUrl: 'https://code.claude.com/docs/en/setup',
119
+ launchSupport: 'project-and-prompt',
120
+ commands: ['claude'],
121
+ macExecutables: ['.local/bin/claude'],
122
+ windowsExecutables: ['.local/bin/claude.exe'],
123
+ linuxExecutables: ['.local/bin/claude'],
124
+ },
125
+ {
126
+ id: 'codex-desktop',
127
+ name: 'ChatGPT desktop (Codex)',
128
+ provider: 'OpenAI',
129
+ kind: 'desktop',
130
+ installUrl: 'https://learn.chatgpt.com/docs/app',
131
+ launchSupport: 'manual-project',
132
+ commands: [],
133
+ macApplications: ['/Applications/ChatGPT.app', '/Applications/Codex.app'],
134
+ windowsApplications: [
135
+ 'AppData/Local/Programs/ChatGPT/ChatGPT.exe',
136
+ 'AppData/Local/Programs/Codex/Codex.exe',
137
+ ],
138
+ windowsAppxPackageNames: ['OpenAI.Codex'],
139
+ linuxApplications: ['/usr/bin/chatgpt', '/usr/lib/chatgpt/ChatGPT'],
140
+ },
141
+ {
142
+ id: 'codex-cli',
143
+ name: 'Codex CLI',
144
+ provider: 'OpenAI',
145
+ kind: 'cli',
146
+ installUrl: 'https://learn.chatgpt.com/docs/codex/cli',
147
+ launchSupport: 'project-and-prompt',
148
+ commands: ['codex'],
149
+ macExecutables: ['.local/bin/codex'],
150
+ windowsExecutables: ['AppData/Local/Programs/OpenAI/Codex/bin/codex.exe'],
151
+ linuxExecutables: ['.local/bin/codex'],
152
+ },
153
+ {
154
+ id: 'grok-bot',
155
+ name: 'Grok Bot',
156
+ provider: 'xAI',
157
+ kind: 'desktop',
158
+ installUrl: 'https://x.ai/bot',
159
+ launchSupport: 'launch-only',
160
+ commands: [],
161
+ macApplications: ['/Applications/Grok Bot.app'],
162
+ windowsApplications: ['AppData/Local/Programs/Grok Bot/Grok Bot.exe'],
163
+ linuxApplications: ['/opt/Grok Bot/grok-bot', '/usr/bin/grok-bot'],
164
+ },
165
+ {
166
+ id: 'grok-cli',
167
+ name: 'Grok Build',
168
+ provider: 'xAI',
169
+ kind: 'cli',
170
+ installUrl: 'https://x.ai/build',
171
+ launchSupport: 'project-and-prompt',
172
+ commands: ['grok'],
173
+ macExecutables: ['.grok/bin/grok'],
174
+ windowsExecutables: ['.grok/bin/grok.exe'],
175
+ linuxExecutables: ['.grok/bin/grok'],
176
+ },
177
+ ];
178
+ const V2_AI_SURFACE_IDS = [
179
+ 'vscode-copilot',
180
+ 'copilot-desktop',
181
+ 'antigravity-desktop',
182
+ 'claude-desktop',
183
+ 'codex-desktop',
184
+ 'grok-bot',
185
+ 'copilot-cli',
186
+ 'antigravity-cli',
187
+ 'claude-cli',
188
+ 'codex-cli',
189
+ 'grok-cli',
190
+ ];
191
+ const AI_SURFACE_DEFINITIONS_BY_ID = new Map(AI_SURFACE_DEFINITIONS.map((definition) => [definition.id, definition]));
192
+ if (AI_SURFACE_DEFINITIONS_BY_ID.size !== V2_AI_SURFACE_IDS.length
193
+ || AI_SURFACE_DEFINITIONS.length !== V2_AI_SURFACE_IDS.length) {
194
+ throw new Error('EAI AI surface definitions must contain each canonical v2 surface exactly once.');
195
+ }
196
+ export const AI_SURFACES = Object.freeze(V2_AI_SURFACE_IDS.map((id) => {
197
+ const definition = AI_SURFACE_DEFINITIONS_BY_ID.get(id);
198
+ if (!definition)
199
+ throw new Error(`EAI AI surface definition is missing: ${id}`);
200
+ for (const values of [
201
+ definition.commands,
202
+ definition.macApplications,
203
+ definition.windowsApplications,
204
+ definition.windowsProgramFilesApplications,
205
+ definition.windowsAppxPackageNames,
206
+ definition.linuxApplications,
207
+ definition.macExecutables,
208
+ definition.windowsExecutables,
209
+ definition.linuxExecutables,
210
+ ]) {
211
+ if (values)
212
+ Object.freeze(values);
213
+ }
214
+ return Object.freeze(definition);
215
+ }));
216
+ const LEGACY_AI_SURFACES = [
14
217
  {
15
218
  id: 'vscode-copilot',
16
219
  name: 'GitHub Copilot in VS Code',
@@ -54,7 +257,10 @@ export const AI_SURFACES = [
54
257
  launchSupport: 'project-and-prompt',
55
258
  commands: [],
56
259
  macApplications: ['/Applications/Claude.app'],
57
- windowsApplications: ['AppData/Local/AnthropicClaude/Claude.exe', 'AppData/Local/Programs/Claude/Claude.exe'],
260
+ windowsApplications: [
261
+ 'AppData/Local/AnthropicClaude/Claude.exe',
262
+ 'AppData/Local/Programs/Claude/Claude.exe',
263
+ ],
58
264
  },
59
265
  {
60
266
  id: 'claude-cli',
@@ -108,187 +314,2369 @@ export async function readAiPreferences(home = homedir()) {
108
314
  return { version: 1, ...(validSurface ? { lastAiSurface: parsed.lastAiSurface } : {}) };
109
315
  }
110
316
  catch {
111
- return { version: 1 };
317
+ return { version: 1 };
318
+ }
319
+ }
320
+ export async function rememberAiSurface(surfaceId, home = homedir()) {
321
+ if (!AI_SURFACE_DEFINITIONS_BY_ID.has(surfaceId)) {
322
+ throw new Error(`Unknown AI surface: ${String(surfaceId)}`);
323
+ }
324
+ const target = preferencesPath(home);
325
+ const preferencesDirectory = dirname(target);
326
+ try {
327
+ await mkdir(preferencesDirectory, { mode: 0o700 });
328
+ }
329
+ catch (error) {
330
+ if (error.code !== 'EEXIST')
331
+ throw error;
332
+ }
333
+ const directoryStatus = lstatSync(preferencesDirectory);
334
+ const currentUid = typeof process.getuid === 'function' ? process.getuid() : null;
335
+ if (!directoryStatus.isDirectory()
336
+ || directoryStatus.isSymbolicLink()
337
+ || (process.platform !== 'win32'
338
+ && ((currentUid !== null && directoryStatus.uid !== currentUid)
339
+ || (directoryStatus.mode & 0o022) !== 0))) {
340
+ throw new Error('EAI refused to write preferences through an untrusted directory.');
341
+ }
342
+ const temporary = join(preferencesDirectory, `.preferences.${process.pid}.${randomBytes(16).toString('hex')}.tmp`);
343
+ let handle = null;
344
+ try {
345
+ handle = await open(temporary, 'wx', 0o600);
346
+ await handle.writeFile(`${JSON.stringify({ version: 1, lastAiSurface: surfaceId }, null, 2)}\n`, { encoding: 'utf8' });
347
+ await handle.sync();
348
+ await handle.close();
349
+ handle = null;
350
+ await rename(temporary, target);
351
+ }
352
+ finally {
353
+ await handle?.close().catch(() => undefined);
354
+ await rm(temporary, { force: true }).catch(() => undefined);
355
+ }
356
+ }
357
+ function windowsEnvironmentValue(environment, name) {
358
+ const exact = environment[name];
359
+ if (exact)
360
+ return exact;
361
+ const key = Object.keys(environment).find((candidate) => candidate.toLowerCase() === name.toLowerCase());
362
+ return key ? environment[key] : undefined;
363
+ }
364
+ const WINDOWS_CMD_META_CHARACTERS = /([()\][%!^"`<>&|;, *?])/g;
365
+ const WINDOWS_CMD = 'C:\\Windows\\System32\\cmd.exe';
366
+ function escapeWindowsCmdCommand(value) {
367
+ return value.replace(WINDOWS_CMD_META_CHARACTERS, '^$1');
368
+ }
369
+ function escapeWindowsCmdArgument(value) {
370
+ // Match CommandLineToArgvW quoting first, then protect every cmd.exe metacharacter.
371
+ // This is the same non-shell interpolation strategy used by established Windows
372
+ // process launchers: cmd.exe receives one already-escaped command string and Node
373
+ // is told not to quote that string again.
374
+ let escaped = value.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
375
+ escaped = escaped.replace(/(?=(\\+?)?)\1$/, '$1$1');
376
+ escaped = `"${escaped}"`;
377
+ return escaped.replace(WINDOWS_CMD_META_CHARACTERS, '^$1');
378
+ }
379
+ export function buildWindowsCommandInvocation(command, args, action, _environment = process.env) {
380
+ if ([command, ...args].some((value) => /[\0\r\n]/.test(value))) {
381
+ throw new Error('EAI refused to execute a Windows command containing control characters.');
382
+ }
383
+ const normalizedCommand = win32Path.normalize(command);
384
+ const escapedCommand = /\.(?:cmd|bat)$/i.test(normalizedCommand)
385
+ ? escapeWindowsCmdCommand(normalizedCommand)
386
+ : escapeWindowsCmdArgument(normalizedCommand);
387
+ const shellCommand = [escapedCommand, ...args.map(escapeWindowsCmdArgument)].join(' ');
388
+ return {
389
+ command: WINDOWS_CMD,
390
+ args: ['/d', '/v:off', '/s', action, `"${shellCommand}"`],
391
+ windowsVerbatimArguments: true,
392
+ };
393
+ }
394
+ export function buildCommandOutputInvocation(command, args, platform = process.platform, environment = process.env) {
395
+ if (platform !== 'win32' || !/\.(?:cmd|bat)$/i.test(command)) {
396
+ return { command, args: [...args] };
397
+ }
398
+ return buildWindowsCommandInvocation(command, args, '/c', environment);
399
+ }
400
+ function defaultCommandPath(command) {
401
+ if (!/^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(command))
402
+ return null;
403
+ const pathValue = process.env.PATH ?? '';
404
+ const extensions = process.platform === 'win32'
405
+ ? (process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean)
406
+ : [''];
407
+ const commandHasExtension = process.platform === 'win32' && /\.[A-Za-z0-9]+$/.test(command);
408
+ for (const directory of pathValue.split(delimiter)) {
409
+ if (!directory)
410
+ continue;
411
+ for (const extension of commandHasExtension ? [''] : extensions) {
412
+ const candidate = process.platform === 'win32'
413
+ ? win32Path.resolve(directory, `${command}${extension}`)
414
+ : resolve(directory, command);
415
+ try {
416
+ const status = statSync(candidate);
417
+ if (!status.isFile())
418
+ continue;
419
+ if (process.platform !== 'win32' && (status.mode & 0o111) === 0)
420
+ continue;
421
+ return realpathSync(candidate);
422
+ }
423
+ catch {
424
+ // PATH entries are untrusted input. A missing or inaccessible entry is
425
+ // simply not a detected command.
426
+ }
427
+ }
428
+ }
429
+ return null;
430
+ }
431
+ function defaultFileStatus(path) {
432
+ try {
433
+ const status = lstatSync(path);
434
+ return {
435
+ isFile: status.isFile(),
436
+ executable: process.platform === 'win32' || (status.mode & 0o111) !== 0,
437
+ mode: status.mode,
438
+ size: status.size,
439
+ isSymbolicLink: status.isSymbolicLink(),
440
+ uid: status.uid,
441
+ gid: status.gid,
442
+ device: status.dev,
443
+ inode: status.ino,
444
+ modifiedTimeMs: status.mtimeMs,
445
+ };
446
+ }
447
+ catch {
448
+ return null;
449
+ }
450
+ }
451
+ function defaultFileHeader(path, length) {
452
+ if (!Number.isSafeInteger(length) || length <= 0 || length > 4096)
453
+ return null;
454
+ let descriptor = null;
455
+ try {
456
+ const flags = process.platform === 'win32'
457
+ ? fsConstants.O_RDONLY
458
+ : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
459
+ descriptor = openSync(path, flags);
460
+ const status = fstatSync(descriptor);
461
+ if (!status.isFile() || (process.platform !== 'win32' && (status.mode & 0o022) !== 0))
462
+ return null;
463
+ const bytes = Buffer.alloc(length);
464
+ const bytesRead = readSync(descriptor, bytes, 0, length, 0);
465
+ return bytes.subarray(0, bytesRead);
466
+ }
467
+ catch {
468
+ return null;
469
+ }
470
+ finally {
471
+ if (descriptor !== null)
472
+ closeSync(descriptor);
473
+ }
474
+ }
475
+ function defaultFileSha256(path) {
476
+ const buffer = Buffer.alloc(1024 * 1024);
477
+ const hash = createHash('sha256');
478
+ let descriptor = null;
479
+ try {
480
+ const flags = process.platform === 'win32'
481
+ ? fsConstants.O_RDONLY
482
+ : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
483
+ descriptor = openSync(path, flags);
484
+ const status = fstatSync(descriptor);
485
+ if (!status.isFile() || (process.platform !== 'win32' && (status.mode & 0o022) !== 0))
486
+ return null;
487
+ let position = 0;
488
+ while (true) {
489
+ const bytesRead = readSync(descriptor, buffer, 0, buffer.length, position);
490
+ if (bytesRead === 0)
491
+ break;
492
+ position += bytesRead;
493
+ hash.update(buffer.subarray(0, bytesRead));
494
+ }
495
+ return hash.digest('hex');
496
+ }
497
+ catch {
498
+ return null;
499
+ }
500
+ finally {
501
+ if (descriptor !== null)
502
+ closeSync(descriptor);
503
+ }
504
+ }
505
+ const MAX_CATALOG_TREE_FILES = 10_000;
506
+ const MAX_CATALOG_TREE_BYTES = 2 * 1024 * 1024 * 1024;
507
+ const MAX_CATALOG_TREE_FILE_BYTES = 1024 * 1024 * 1024;
508
+ const MAX_CATALOG_TREE_DIRECTORIES = 10_000;
509
+ const MAX_CATALOG_TREE_DEPTH = 64;
510
+ function boundedDirectoryEntries(path, maximumEntries) {
511
+ if (!Number.isSafeInteger(maximumEntries) || maximumEntries < 0)
512
+ return null;
513
+ const entries = [];
514
+ let directory = null;
515
+ try {
516
+ directory = opendirSync(path);
517
+ while (true) {
518
+ const entry = directory.readSync();
519
+ if (entry === null)
520
+ break;
521
+ if (entries.length >= maximumEntries)
522
+ return null;
523
+ entries.push(entry);
524
+ }
525
+ return entries;
526
+ }
527
+ catch {
528
+ return null;
529
+ }
530
+ finally {
531
+ try {
532
+ directory?.closeSync();
533
+ }
534
+ catch {
535
+ // Reading to EOF may already have closed the directory handle.
536
+ }
537
+ }
538
+ }
539
+ function catalogTreeNodeIsTrusted(path, expectDirectory) {
540
+ try {
541
+ const status = lstatSync(path);
542
+ if (status.isSymbolicLink()
543
+ || (expectDirectory ? !status.isDirectory() : !status.isFile()))
544
+ return false;
545
+ if (process.platform === 'linux') {
546
+ // The Microsoft Debian package uses root:root files that may be
547
+ // group-writable. Group 0 is privileged, so reject any other owner/group
548
+ // and every world-writable node while accepting that exact package mode.
549
+ if (status.uid !== 0 || status.gid !== 0 || (status.mode & 0o002) !== 0)
550
+ return false;
551
+ }
552
+ return true;
553
+ }
554
+ catch {
555
+ return false;
556
+ }
557
+ }
558
+ function catalogFileSha256(path) {
559
+ const hash = createHash('sha256');
560
+ const buffer = Buffer.alloc(1024 * 1024);
561
+ let descriptor = null;
562
+ try {
563
+ if (!catalogTreeNodeIsTrusted(path, false))
564
+ return null;
565
+ const before = lstatSync(path);
566
+ const flags = process.platform === 'win32'
567
+ ? fsConstants.O_RDONLY
568
+ : fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
569
+ descriptor = openSync(path, flags);
570
+ const opened = fstatSync(descriptor);
571
+ if (!opened.isFile()
572
+ || opened.dev !== before.dev
573
+ || opened.ino !== before.ino
574
+ || opened.size !== before.size
575
+ || (process.platform === 'linux'
576
+ && (opened.uid !== 0 || opened.gid !== 0 || (opened.mode & 0o002) !== 0)))
577
+ return null;
578
+ let position = 0;
579
+ while (true) {
580
+ const bytesRead = readSync(descriptor, buffer, 0, buffer.length, position);
581
+ if (bytesRead === 0)
582
+ break;
583
+ position += bytesRead;
584
+ hash.update(buffer.subarray(0, bytesRead));
585
+ }
586
+ const after = fstatSync(descriptor);
587
+ if (after.dev !== opened.dev
588
+ || after.ino !== opened.ino
589
+ || after.size !== opened.size
590
+ || after.mtimeMs !== opened.mtimeMs)
591
+ return null;
592
+ return hash.digest('hex');
593
+ }
594
+ catch {
595
+ return null;
596
+ }
597
+ finally {
598
+ if (descriptor !== null)
599
+ closeSync(descriptor);
600
+ }
601
+ }
602
+ function defaultDirectoryTreeIdentity(path) {
603
+ if (process.platform !== 'win32' && process.platform !== 'linux')
604
+ return null;
605
+ let realRoot;
606
+ try {
607
+ if (!catalogTreeNodeIsTrusted(path, true))
608
+ return null;
609
+ realRoot = realpathSync(path);
610
+ if (realRoot !== resolve(path))
611
+ return null;
612
+ let ancestor = realRoot;
613
+ while (true) {
614
+ if (!catalogTreeNodeIsTrusted(ancestor, true))
615
+ return null;
616
+ const parent = dirname(ancestor);
617
+ if (parent === ancestor)
618
+ break;
619
+ ancestor = parent;
620
+ }
621
+ }
622
+ catch {
623
+ return null;
624
+ }
625
+ const files = [];
626
+ const directories = [];
627
+ const pending = [{ path: realRoot, depth: 0 }];
628
+ let discoveredBytes = 0;
629
+ try {
630
+ while (pending.length > 0) {
631
+ const current = pending.pop();
632
+ if (!current
633
+ || current.depth > MAX_CATALOG_TREE_DEPTH
634
+ || directories.length >= MAX_CATALOG_TREE_DIRECTORIES
635
+ || !catalogTreeNodeIsTrusted(current.path, true))
636
+ return null;
637
+ const directoryStatus = lstatSync(current.path);
638
+ const maximumEntries = (MAX_CATALOG_TREE_FILES - files.length)
639
+ + (MAX_CATALOG_TREE_DIRECTORIES - directories.length - 1);
640
+ const entries = boundedDirectoryEntries(current.path, maximumEntries);
641
+ if (!entries)
642
+ return null;
643
+ const entryNames = entries.map((entry) => entry.name).sort((left, right) => (Buffer.from(left).compare(Buffer.from(right))));
644
+ directories.push({
645
+ path: current.path,
646
+ entries: entryNames,
647
+ device: directoryStatus.dev,
648
+ inode: directoryStatus.ino,
649
+ });
650
+ for (const entry of entries) {
651
+ const candidate = join(current.path, entry.name);
652
+ if (entry.isSymbolicLink())
653
+ return null;
654
+ if (entry.isDirectory()) {
655
+ pending.push({ path: candidate, depth: current.depth + 1 });
656
+ continue;
657
+ }
658
+ if (!entry.isFile() || !catalogTreeNodeIsTrusted(candidate, false))
659
+ return null;
660
+ const relativePath = relative(realRoot, candidate).split('\\').join('/');
661
+ if (!relativePath
662
+ || relativePath.startsWith('../')
663
+ || /[\0\r\n]/.test(relativePath))
664
+ return null;
665
+ const status = lstatSync(candidate);
666
+ if (!Number.isSafeInteger(status.size)
667
+ || status.size < 0
668
+ || status.size > MAX_CATALOG_TREE_FILE_BYTES
669
+ || files.length >= MAX_CATALOG_TREE_FILES
670
+ || discoveredBytes > MAX_CATALOG_TREE_BYTES - status.size)
671
+ return null;
672
+ discoveredBytes += status.size;
673
+ files.push({ path: candidate, relativePath, size: status.size });
674
+ }
675
+ }
676
+ }
677
+ catch {
678
+ return null;
679
+ }
680
+ files.sort((left, right) => Buffer.from(left.relativePath).compare(Buffer.from(right.relativePath)));
681
+ const aggregate = createHash('sha256');
682
+ let totalBytes = 0;
683
+ for (const file of files) {
684
+ const sha256 = catalogFileSha256(file.path);
685
+ if (!sha256)
686
+ return null;
687
+ totalBytes += file.size;
688
+ if (!Number.isSafeInteger(totalBytes) || totalBytes > MAX_CATALOG_TREE_BYTES)
689
+ return null;
690
+ aggregate.update(file.relativePath);
691
+ aggregate.update('\0');
692
+ aggregate.update(String(file.size));
693
+ aggregate.update('\0');
694
+ aggregate.update(sha256);
695
+ aggregate.update('\n');
696
+ }
697
+ try {
698
+ for (const directory of directories) {
699
+ if (!catalogTreeNodeIsTrusted(directory.path, true))
700
+ return null;
701
+ const status = lstatSync(directory.path);
702
+ if (status.dev !== directory.device || status.ino !== directory.inode)
703
+ return null;
704
+ const relisted = boundedDirectoryEntries(directory.path, directory.entries.length);
705
+ if (!relisted)
706
+ return null;
707
+ const entries = relisted.map((entry) => entry.name)
708
+ .sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
709
+ if (entries.length !== directory.entries.length
710
+ || entries.some((entry, index) => entry !== directory.entries[index]))
711
+ return null;
712
+ }
713
+ }
714
+ catch {
715
+ return null;
716
+ }
717
+ return {
718
+ realPath: realRoot,
719
+ fileCount: files.length,
720
+ totalBytes,
721
+ sha256: aggregate.digest('hex'),
722
+ };
723
+ }
724
+ const POSIX_METADATA_HELPERS = new Set([
725
+ '/usr/bin/codesign',
726
+ '/usr/bin/dpkg',
727
+ '/usr/bin/dpkg-query',
728
+ '/usr/bin/plutil',
729
+ '/usr/bin/xdg-mime',
730
+ ]);
731
+ function runPosixMetadataHelper(command, args) {
732
+ if (!POSIX_METADATA_HELPERS.has(command))
733
+ return null;
734
+ try {
735
+ const status = statSync(command);
736
+ if (!status.isFile() || status.uid !== 0 || (status.mode & 0o022) !== 0)
737
+ return null;
738
+ }
739
+ catch {
740
+ return null;
741
+ }
742
+ const result = spawnSync(command, [...args], {
743
+ encoding: 'utf8',
744
+ windowsHide: true,
745
+ timeout: 5000,
746
+ env: { PATH: '/usr/bin:/bin:/usr/sbin:/sbin', LANG: 'C', LC_ALL: 'C' },
747
+ });
748
+ if (result.status !== 0)
749
+ return null;
750
+ return { stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
751
+ }
752
+ function machOArchitectures(path) {
753
+ const headerBytes = defaultFileHeader(path, 4096);
754
+ if (!headerBytes || headerBytes.length < 8)
755
+ return null;
756
+ const header = Buffer.from(headerBytes.buffer, headerBytes.byteOffset, headerBytes.byteLength);
757
+ const architectures = [];
758
+ const addCpuType = (cpuType) => {
759
+ if (cpuType === 0x01000007)
760
+ architectures.push('x64');
761
+ else if (cpuType === 0x0100000c)
762
+ architectures.push('arm64');
763
+ else
764
+ return false;
765
+ return true;
766
+ };
767
+ const magic = header.readUInt32BE(0);
768
+ if (magic === 0xfeedfacf || magic === 0xfeedface) {
769
+ if (!addCpuType(header.readUInt32BE(4)))
770
+ return null;
771
+ }
772
+ else if (magic === 0xcffaedfe || magic === 0xcefaedfe) {
773
+ if (!addCpuType(header.readUInt32LE(4)))
774
+ return null;
775
+ }
776
+ else if (magic === 0xcafebabe
777
+ || magic === 0xbebafeca
778
+ || magic === 0xcafebabf
779
+ || magic === 0xbfbafeca) {
780
+ const littleEndian = magic === 0xbebafeca || magic === 0xbfbafeca;
781
+ const recordSize = magic === 0xcafebabf || magic === 0xbfbafeca ? 32 : 20;
782
+ const readUInt32 = (offset) => littleEndian
783
+ ? header.readUInt32LE(offset)
784
+ : header.readUInt32BE(offset);
785
+ const count = readUInt32(4);
786
+ if (count < 1 || count > 64 || 8 + count * recordSize > header.length)
787
+ return null;
788
+ for (let index = 0; index < count; index += 1) {
789
+ if (!addCpuType(readUInt32(8 + index * recordSize)))
790
+ return null;
791
+ }
792
+ }
793
+ else {
794
+ return null;
795
+ }
796
+ return [...new Set(architectures)];
797
+ }
798
+ function defaultMacApplicationIdentity(path, expected) {
799
+ if (process.platform !== 'darwin')
800
+ return null;
801
+ if (!expected
802
+ || !/^[A-Za-z0-9.-]+$/.test(expected.bundleIdentifier)
803
+ || !/^[A-Z0-9]{10}$/.test(expected.teamIdentifier))
804
+ return null;
805
+ const applicationPath = trustedPosixDirectory(path);
806
+ if (!applicationPath?.endsWith('.app'))
807
+ return null;
808
+ const trustedDeveloperId = runPosixMetadataHelper('/usr/bin/codesign', [
809
+ '--verify',
810
+ '--deep',
811
+ '--strict',
812
+ '--test-requirement',
813
+ `=identifier "${expected.bundleIdentifier}" and anchor apple generic and certificate leaf[subject.OU] = "${expected.teamIdentifier}"`,
814
+ applicationPath,
815
+ ]);
816
+ if (!trustedDeveloperId)
817
+ return null;
818
+ const infoPlist = join(applicationPath, 'Contents', 'Info.plist');
819
+ const plistJson = runPosixMetadataHelper('/usr/bin/plutil', [
820
+ '-convert',
821
+ 'json',
822
+ '-o',
823
+ '-',
824
+ infoPlist,
825
+ ])?.stdout;
826
+ let plist;
827
+ try {
828
+ plist = JSON.parse(plistJson ?? '');
829
+ }
830
+ catch {
831
+ return null;
832
+ }
833
+ const bundleIdentifier = typeof plist.CFBundleIdentifier === 'string'
834
+ ? plist.CFBundleIdentifier
835
+ : null;
836
+ if (bundleIdentifier !== expected.bundleIdentifier)
837
+ return null;
838
+ const executableName = typeof plist.CFBundleExecutable === 'string'
839
+ ? plist.CFBundleExecutable
840
+ : null;
841
+ if (!executableName || /[\\/\0\r\n]/.test(executableName))
842
+ return null;
843
+ let executablePath;
844
+ try {
845
+ executablePath = realpathSync(join(applicationPath, 'Contents', 'MacOS', executableName));
846
+ if (!executablePath.startsWith(`${applicationPath}/Contents/MacOS/`))
847
+ return null;
848
+ const status = statSync(executablePath);
849
+ if (!status.isFile() || (status.mode & 0o111) === 0 || (status.mode & 0o022) !== 0)
850
+ return null;
851
+ }
852
+ catch {
853
+ return null;
854
+ }
855
+ const architectures = machOArchitectures(executablePath);
856
+ if (!architectures)
857
+ return null;
858
+ const urlTypes = Array.isArray(plist.CFBundleURLTypes)
859
+ ? plist.CFBundleURLTypes
860
+ : [];
861
+ const urlSchemes = urlTypes.flatMap((entry) => Array.isArray(entry.CFBundleURLSchemes)
862
+ ? entry.CFBundleURLSchemes.filter((value) => typeof value === 'string')
863
+ : []);
864
+ return {
865
+ bundleIdentifier,
866
+ teamIdentifier: expected.teamIdentifier,
867
+ executablePath,
868
+ architectures,
869
+ urlSchemes,
870
+ };
871
+ }
872
+ function trustedPosixAncestors(path) {
873
+ if (process.platform === 'win32')
874
+ return false;
875
+ const currentUid = typeof process.getuid === 'function' ? process.getuid() : null;
876
+ try {
877
+ let cursor = path;
878
+ while (true) {
879
+ const status = lstatSync(cursor);
880
+ // macOS application and package-manager directories commonly use 0775.
881
+ // Group-writable ancestors are safe for signed artifacts: a replacement
882
+ // must still pass the provider signature and launch-file binding checks.
883
+ const unsafeWritePermission = (status.mode & 0o002) !== 0;
884
+ if (status.isSymbolicLink()
885
+ || unsafeWritePermission
886
+ || (currentUid !== null && status.uid !== 0 && status.uid !== currentUid))
887
+ return false;
888
+ const parent = dirname(cursor);
889
+ if (parent === cursor)
890
+ break;
891
+ cursor = parent;
892
+ }
893
+ }
894
+ catch {
895
+ return false;
896
+ }
897
+ return true;
898
+ }
899
+ function trustedPosixDirectory(path) {
900
+ if (process.platform === 'win32')
901
+ return null;
902
+ try {
903
+ const original = lstatSync(path);
904
+ if (!original.isDirectory() || original.isSymbolicLink())
905
+ return null;
906
+ const realPath = realpathSync(path);
907
+ return realPath === resolve(path) && trustedPosixAncestors(realPath) ? realPath : null;
908
+ }
909
+ catch {
910
+ return null;
911
+ }
912
+ }
913
+ function rootOwnedProtectedExecutable(path) {
914
+ if (process.platform === 'win32')
915
+ return false;
916
+ try {
917
+ const realPath = realpathSync(path);
918
+ if (realPath !== path)
919
+ return false;
920
+ let cursor = realPath;
921
+ while (true) {
922
+ const status = lstatSync(cursor);
923
+ if (status.isSymbolicLink() || status.uid !== 0 || (status.mode & 0o022) !== 0)
924
+ return false;
925
+ if (cursor === realPath && (!status.isFile() || (status.mode & 0o111) === 0))
926
+ return false;
927
+ const parent = dirname(cursor);
928
+ if (parent === cursor)
929
+ break;
930
+ cursor = parent;
931
+ }
932
+ return true;
933
+ }
934
+ catch {
935
+ return false;
936
+ }
937
+ }
938
+ function trustedPosixFile(path) {
939
+ if (process.platform === 'win32')
940
+ return null;
941
+ let realPath;
942
+ try {
943
+ const original = lstatSync(path);
944
+ if (!original.isFile() || original.isSymbolicLink() || (original.mode & 0o111) === 0)
945
+ return null;
946
+ realPath = realpathSync(path);
947
+ if (realPath !== resolve(path) || !trustedPosixAncestors(realPath))
948
+ return null;
949
+ }
950
+ catch {
951
+ return null;
952
+ }
953
+ const status = defaultFileStatus(realPath);
954
+ return status?.isFile && status.executable && !status.isSymbolicLink
955
+ ? { realPath, status }
956
+ : null;
957
+ }
958
+ function defaultMacExecutableIdentity(path, expected) {
959
+ if (process.platform !== 'darwin'
960
+ || !expected
961
+ || !/^[A-Za-z0-9.-]+$/.test(expected.identifier)
962
+ || !/^[A-Z0-9]{10}$/.test(expected.teamIdentifier))
963
+ return null;
964
+ const trustedFile = trustedPosixFile(path);
965
+ if (!trustedFile)
966
+ return null;
967
+ const verified = runPosixMetadataHelper('/usr/bin/codesign', [
968
+ '--verify',
969
+ '--strict',
970
+ '--test-requirement',
971
+ `=identifier "${expected.identifier}" and anchor apple generic and certificate leaf[subject.OU] = "${expected.teamIdentifier}"`,
972
+ trustedFile.realPath,
973
+ ]);
974
+ const architectures = verified ? machOArchitectures(trustedFile.realPath) : null;
975
+ if (!architectures)
976
+ return null;
977
+ return {
978
+ identifier: expected.identifier,
979
+ teamIdentifier: expected.teamIdentifier,
980
+ architectures,
981
+ };
982
+ }
983
+ const WINDOWS_POWERSHELL = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
984
+ const WINDOWS_REG = 'C:\\Windows\\System32\\reg.exe';
985
+ function runWindowsMetadataHelper(command, args, environment = {}, timeout = 5000) {
986
+ if (process.platform !== 'win32' || !existsSync(command))
987
+ return null;
988
+ const result = spawnSync(command, [...args], {
989
+ encoding: 'utf8',
990
+ windowsHide: true,
991
+ timeout,
992
+ env: {
993
+ SystemRoot: 'C:\\Windows',
994
+ WINDIR: 'C:\\Windows',
995
+ PATH: 'C:\\Windows\\System32;C:\\Windows',
996
+ ...environment,
997
+ },
998
+ });
999
+ if (result.status !== 0)
1000
+ return null;
1001
+ return { stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
1002
+ }
1003
+ function defaultWindowsExecutableIdentity(path) {
1004
+ if (process.platform !== 'win32')
1005
+ return null;
1006
+ const script = [
1007
+ "$ErrorActionPreference='Stop'",
1008
+ '$item = Get-Item -LiteralPath $env:EAI_SURFACE_EXECUTABLE',
1009
+ 'if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { exit 1 }',
1010
+ '$ancestor = $item.Directory; while ($null -ne $ancestor) { if (($ancestor.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { exit 1 }; $ancestor = $ancestor.Parent }',
1011
+ '$signature = Get-AuthenticodeSignature -LiteralPath $env:EAI_SURFACE_EXECUTABLE',
1012
+ "if ($signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate) { exit 1 }",
1013
+ '$stream = [System.IO.File]::OpenRead($env:EAI_SURFACE_EXECUTABLE)',
1014
+ 'try { $reader = [System.IO.BinaryReader]::new($stream); if ($reader.ReadUInt16() -ne 0x5A4D) { exit 1 }; $stream.Position = 0x3C; $peOffset = $reader.ReadInt32(); $stream.Position = $peOffset; if ($reader.ReadUInt32() -ne 0x00004550) { exit 1 }; $machine = $reader.ReadUInt16() } finally { $stream.Dispose() }',
1015
+ "$architecture = switch ($machine) { 0x8664 { 'x64' } 0xAA64 { 'arm64' } default { exit 1 } }",
1016
+ '[pscustomobject]@{ productName = [string]$item.VersionInfo.ProductName; companyName = [string]$item.VersionInfo.CompanyName; publisher = [string]$signature.SignerCertificate.Subject; architecture = $architecture } | ConvertTo-Json -Compress',
1017
+ ].join('; ');
1018
+ const result = runWindowsMetadataHelper(WINDOWS_POWERSHELL, ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script], { EAI_SURFACE_EXECUTABLE: path });
1019
+ if (!result)
1020
+ return null;
1021
+ try {
1022
+ const parsed = JSON.parse(result.stdout.trim());
1023
+ if (typeof parsed.productName !== 'string'
1024
+ || typeof parsed.companyName !== 'string'
1025
+ || typeof parsed.publisher !== 'string'
1026
+ || (parsed.architecture !== 'x64' && parsed.architecture !== 'arm64'))
1027
+ return null;
1028
+ return {
1029
+ productName: parsed.productName,
1030
+ companyName: parsed.companyName,
1031
+ publisher: parsed.publisher,
1032
+ architecture: parsed.architecture,
1033
+ };
1034
+ }
1035
+ catch {
1036
+ return null;
1037
+ }
1038
+ }
1039
+ function defaultWindowsVsCodeInstallationAcl(path) {
1040
+ if (process.platform !== 'win32')
1041
+ return false;
1042
+ const script = [
1043
+ "$ErrorActionPreference='Stop'",
1044
+ "$trustedWriters=@('S-1-5-18','S-1-5-32-544','S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')",
1045
+ "$trustedOwners=@('S-1-5-18','S-1-5-32-544','S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464')",
1046
+ '$writeMask=[int64]0x500D0156',
1047
+ 'function Test-ProtectedNode($item,[bool]$requireProtected,[bool]$ignoreInheritOnly){ if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { exit 1 }; $acl=Get-Acl -LiteralPath $item.FullName; if (-not $acl.AreAccessRulesCanonical -or ($requireProtected -and -not $acl.AreAccessRulesProtected) -or $acl.Sddl -match "NO_ACCESS_CONTROL") { exit 1 }; $owner=$acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value; if ($trustedOwners -notcontains $owner) { exit 1 }; foreach($rule in $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])) { if ($rule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or $trustedWriters -contains $rule.IdentityReference.Value) { continue }; if ($ignoreInheritOnly -and (($rule.PropagationFlags -band [System.Security.AccessControl.PropagationFlags]::InheritOnly) -ne 0)) { continue }; if (([int64]$rule.FileSystemRights -band $writeMask) -ne 0) { exit 1 } } }',
1048
+ '$programFiles=[System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::ProgramFiles)',
1049
+ '$programFilesItem=Get-Item -Force -LiteralPath $programFiles',
1050
+ "$expected=[System.IO.Path]::GetFullPath((Join-Path $programFilesItem.FullName 'Microsoft VS Code')).TrimEnd([char]92)",
1051
+ '$root=Get-Item -Force -LiteralPath $env:EAI_VSCODE_INSTALLATION',
1052
+ '$rootPath=[System.IO.Path]::GetFullPath($root.FullName).TrimEnd([char]92)',
1053
+ 'if (-not [string]::Equals($rootPath,$expected,[System.StringComparison]::OrdinalIgnoreCase)) { exit 1 }',
1054
+ 'Test-ProtectedNode $programFilesItem $true $true',
1055
+ 'Test-ProtectedNode $root $true $false',
1056
+ '$pending=[System.Collections.Generic.Stack[System.IO.DirectoryInfo]]::new(); $pending.Push($root); $count=0',
1057
+ 'while($pending.Count -gt 0) { $directory=$pending.Pop(); foreach($item in Get-ChildItem -Force -LiteralPath $directory.FullName) { $count++; if ($count -gt 20000) { exit 1 }; Test-ProtectedNode $item $false $false; if ($item.PSIsContainer) { $pending.Push($item) } } }',
1058
+ "Write-Output 'ok'",
1059
+ ].join('; ');
1060
+ const result = runWindowsMetadataHelper(WINDOWS_POWERSHELL, ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script], { EAI_VSCODE_INSTALLATION: path }, 20_000);
1061
+ return result?.stdout.trim() === 'ok';
1062
+ }
1063
+ function defaultWindowsAppxIdentity(packageName) {
1064
+ if (process.platform !== 'win32' || !/^[A-Za-z0-9.-]+$/.test(packageName))
1065
+ return null;
1066
+ const script = [
1067
+ "$ErrorActionPreference='Stop'",
1068
+ '$package = Get-AppxPackage -Name $env:EAI_SURFACE_PACKAGE | Sort-Object Version -Descending | Select-Object -First 1',
1069
+ 'if ($null -eq $package) { exit 1 }',
1070
+ '$manifest = Get-AppxPackageManifest -Package $package.PackageFullName',
1071
+ '$applicationIds = @($manifest.Package.Applications.Application | ForEach-Object { [string]$_.Id })',
1072
+ 'if ($applicationIds.Count -eq 0 -or @($applicationIds | Where-Object { $_ -notmatch "^[A-Za-z0-9._-]+$" }).Count -gt 0) { exit 1 }',
1073
+ '[pscustomobject]@{ packageName = [string]$package.Name; publisher = [string]$package.Publisher; publisherId = [string]$package.PublisherId; familyName = [string]$package.PackageFamilyName; applicationIds = [string[]]$applicationIds; architecture = [string]$package.Architecture; signatureKind = [string]$package.SignatureKind; status = [string]$package.Status } | ConvertTo-Json -Compress',
1074
+ ].join('; ');
1075
+ const result = runWindowsMetadataHelper(WINDOWS_POWERSHELL, ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script], { EAI_SURFACE_PACKAGE: packageName });
1076
+ if (!result)
1077
+ return null;
1078
+ try {
1079
+ const parsed = JSON.parse(result.stdout.trim());
1080
+ const architecture = typeof parsed.architecture === 'string' ? parsed.architecture.toLowerCase() : '';
1081
+ const applicationIds = Array.isArray(parsed.applicationIds)
1082
+ ? parsed.applicationIds.filter((value) => typeof value === 'string')
1083
+ : typeof parsed.applicationIds === 'string' ? [parsed.applicationIds] : [];
1084
+ if (typeof parsed.packageName !== 'string'
1085
+ || typeof parsed.publisher !== 'string'
1086
+ || typeof parsed.publisherId !== 'string'
1087
+ || typeof parsed.familyName !== 'string'
1088
+ || (architecture !== 'x64' && architecture !== 'arm64')
1089
+ || (parsed.signatureKind !== 'Developer' && parsed.signatureKind !== 'Store')
1090
+ || parsed.status !== 'Ok'
1091
+ || applicationIds.length === 0)
1092
+ return null;
1093
+ return {
1094
+ packageName: parsed.packageName,
1095
+ publisher: parsed.publisher,
1096
+ publisherId: parsed.publisherId,
1097
+ familyName: parsed.familyName,
1098
+ applicationIds,
1099
+ architecture,
1100
+ signatureKind: parsed.signatureKind,
1101
+ status: 'Ok',
1102
+ };
1103
+ }
1104
+ catch {
1105
+ return null;
1106
+ }
1107
+ }
1108
+ function windowsRegistryExecutable(output) {
1109
+ const value = output
1110
+ .split(/\r?\n/)
1111
+ .map((line) => line.match(/\bREG_(?:EXPAND_)?SZ\s+(.+)$/i)?.[1]?.trim())
1112
+ .find((candidate) => Boolean(candidate));
1113
+ if (!value || /[\0\r\n]/.test(value))
1114
+ return null;
1115
+ const quoted = value.match(/^"([A-Za-z]:\\[^"<>|?*]+\.exe)"\s+(?:"%1"|%1)$/i);
1116
+ if (quoted)
1117
+ return win32Path.normalize(quoted[1]);
1118
+ const unquoted = value.match(/^([A-Za-z]:\\[^\s"<>|?*]+\.exe)\s+(?:"%1"|%1)$/i);
1119
+ return unquoted ? win32Path.normalize(unquoted[1]) : null;
1120
+ }
1121
+ function defaultWindowsUrlHandlerExecutable(scheme) {
1122
+ const result = runWindowsMetadataHelper(WINDOWS_REG, ['query', `HKCR\\${scheme}\\shell\\open\\command`, '/ve']);
1123
+ return result ? windowsRegistryExecutable(result.stdout) : null;
1124
+ }
1125
+ function nodeArchitectureFromLinux(value) {
1126
+ if (value === 'amd64' || value === 'x86_64')
1127
+ return 'x64';
1128
+ if (value === 'arm64' || value === 'aarch64')
1129
+ return 'arm64';
1130
+ return null;
1131
+ }
1132
+ const COPILOT_LINUX_VERSION = '1.1.15';
1133
+ const COPILOT_LINUX_ORIGIN = `https://github.com/github/app/releases/tag/v${COPILOT_LINUX_VERSION}`;
1134
+ const VERIFIED_COPILOT_LINUX_DEBS = {
1135
+ arm64: {
1136
+ artifactSha256: '587445c4ad98917638204f6a66f15b1868b8795b317725cbed37fc635df07e10',
1137
+ executableSize: 969_511_224,
1138
+ executableSha256: 'accf69bea75cb15c2afbb88b9011fa98a0705d8196ace29c49fb802548b27f0c',
1139
+ packageManifestSize: 5_892,
1140
+ packageManifestSha256: '1b54b99c5bcfb24e17d4d3ba1e73ffc80c8985021ed185cb0ac857890c541236',
1141
+ },
1142
+ x64: {
1143
+ artifactSha256: 'cf884fd0b9e5418285e82318c4fe712d74955f66982bae6a5d5c43aa1f44233e',
1144
+ executableSize: 982_846_656,
1145
+ executableSha256: '6033d7d87d60e5e86f00cf79f87174392051e016e6fad439d0eb657e6a5fdc55',
1146
+ packageManifestSize: 5_892,
1147
+ packageManifestSha256: '12d527ed0e0d9dd3f73bc88f99f15b1e49765f2d3157ab0f159158685c9ad1ec',
1148
+ },
1149
+ };
1150
+ const VSCODE_CATALOG_VERSION = '1.136.1';
1151
+ const VSCODE_CATALOG_COMMIT = 'a44adf7f53e00964ab890f9f8758a334f1fc15bc';
1152
+ const VSCODE_CATALOG_COMMIT_DIRECTORY = VSCODE_CATALOG_COMMIT.slice(0, 10);
1153
+ const VSCODE_CATALOG_LAUNCH_ENVIRONMENT = Object.freeze({
1154
+ ELECTRON_RUN_AS_NODE: '1',
1155
+ VSCODE_DEV: '',
1156
+ VSCODE_IPC_HOOK_CLI: '',
1157
+ });
1158
+ const WINDOWS_VSCODE_INSTALLATION_ENTRIES = Object.freeze([
1159
+ VSCODE_CATALOG_COMMIT_DIRECTORY,
1160
+ 'bin',
1161
+ 'Code.exe',
1162
+ 'Code.VisualElementsManifest.xml',
1163
+ 'unins000.dat',
1164
+ 'unins000.exe',
1165
+ 'unins000.msg',
1166
+ ]);
1167
+ const WINDOWS_VSCODE_UNINSTALLER_FILES = Object.freeze([
1168
+ 'unins000.dat',
1169
+ 'unins000.exe',
1170
+ 'unins000.msg',
1171
+ ]);
1172
+ const WINDOWS_VSCODE_BIN_ENTRIES = Object.freeze([
1173
+ 'code',
1174
+ 'code-tunnel.exe',
1175
+ 'code.cmd',
1176
+ ]);
1177
+ // Exact payloads provisioned by the EAI Setup ARM64 release harness. The
1178
+ // executable alone is insufficient for Electron: bind every application runtime
1179
+ // file, including sibling native libraries, so modified code cannot inherit a
1180
+ // valid executable signature. Update these values only from a newly verified
1181
+ // official asset.
1182
+ const VERIFIED_VSCODE_CATALOGS = {
1183
+ win32: {
1184
+ arm64: {
1185
+ artifactSha256: '57454d84d55f07b532fcf42295c57a5445054006be668ad7b1c19c9de9f68e31',
1186
+ executable: {
1187
+ size: 218_732_896,
1188
+ sha256: 'c8e8f54f217223f3d4adff4dbd1f529aa7386c3a1705dbe214ecf187b963423e',
1189
+ },
1190
+ applicationTree: {
1191
+ fileCount: 2_463,
1192
+ totalBytes: 782_690_305,
1193
+ sha256: '11009193bf07e51892a0ae9f6030188c7f8914e79ef4344e2f6a3a344807753f',
1194
+ },
1195
+ auxiliaryTree: {
1196
+ fileCount: 3,
1197
+ totalBytes: 25_558_542,
1198
+ sha256: '73fbdad4bf097af9408bdcbe75f4c5cc1741b88b9eedf9d946b338124457408b',
1199
+ },
1200
+ visualManifest: {
1201
+ size: 398,
1202
+ sha256: 'cff3bb59579080b4ac4e69fc8d936c35bbb41455f66f9188ed54f4e31882e68b',
1203
+ },
1204
+ packageJson: {
1205
+ size: 15_233,
1206
+ sha256: '18ce306138992d44d6c0537c386943b277621865d5161e1932e792b09ef766f0',
1207
+ },
1208
+ productJson: {
1209
+ size: 71_150,
1210
+ sha256: '4bdbecbf1cd1a700f4f738216bd0e6f08f561a52e1fd9ca915ea9ff9a6a7a7a6',
1211
+ },
1212
+ copilotPackageJson: {
1213
+ size: 200_790,
1214
+ sha256: '586aa5105751792bedcb7c3ef785d0c1cb1bac8c97812afd50f1349f0a6c1a09',
1215
+ },
1216
+ cliJs: {
1217
+ size: 291_407,
1218
+ sha256: '487137301c6d9ac59dc846a93c17e2f52ba98fdf1670c164dac24b4eb5acad61',
1219
+ },
1220
+ origin: `https://update.code.visualstudio.com/${VSCODE_CATALOG_VERSION}/win32-arm64/stable`,
1221
+ },
1222
+ },
1223
+ linux: {
1224
+ arm64: {
1225
+ artifactSha256: 'baa72f92d3feaa76d015202c57271475ea15809e635587279171a972cdd612a4',
1226
+ executable: {
1227
+ size: 212_536_608,
1228
+ sha256: '8872f37cb828d74498b0451ed923a141e5b144dfd9de9c8f41265bf90b0af98d',
1229
+ },
1230
+ applicationTree: {
1231
+ fileCount: 2_415,
1232
+ totalBytes: 1_035_373_322,
1233
+ sha256: '437e3d7f2338684372876807533f7d38c0e9b56bd8be9238b520dfc2a01ef8e6',
1234
+ },
1235
+ packageJson: {
1236
+ size: 15_266,
1237
+ sha256: 'ba6a64fd136466074831d91c20ec6d3dd110c127a84a8ac9b47cd4f075172ad9',
1238
+ },
1239
+ productJson: {
1240
+ size: 67_081,
1241
+ sha256: '0fb7ff5e7c78427f0601f864ec8adc8e163a384f8da1c49e9e99adbffaa7e547',
1242
+ },
1243
+ copilotPackageJson: {
1244
+ size: 200_790,
1245
+ sha256: '586aa5105751792bedcb7c3ef785d0c1cb1bac8c97812afd50f1349f0a6c1a09',
1246
+ },
1247
+ cliJs: {
1248
+ size: 291_436,
1249
+ sha256: 'c26b66d4031d946523eb9b951da9bc56aba16417e91d84534ce6367e815fe342',
1250
+ },
1251
+ packageVersion: '1.136.1-1788414014',
1252
+ origin: `https://update.code.visualstudio.com/${VSCODE_CATALOG_VERSION}/linux-deb-arm64/stable`,
1253
+ },
1254
+ },
1255
+ };
1256
+ function vsCodeCatalog(platform, architecture) {
1257
+ if (platform !== 'win32' && platform !== 'linux')
1258
+ return null;
1259
+ return VERIFIED_VSCODE_CATALOGS[platform]?.[architecture] ?? null;
1260
+ }
1261
+ function rootOwnedReadOnlyFileContent(path) {
1262
+ try {
1263
+ const realPath = realpathSync(path);
1264
+ if (realPath !== resolve(path))
1265
+ return null;
1266
+ let cursor = realPath;
1267
+ while (true) {
1268
+ const status = lstatSync(cursor);
1269
+ if (status.isSymbolicLink() || status.uid !== 0 || (status.mode & 0o022) !== 0)
1270
+ return null;
1271
+ const parent = dirname(cursor);
1272
+ if (parent === cursor)
1273
+ break;
1274
+ cursor = parent;
1275
+ }
1276
+ const status = lstatSync(realPath);
1277
+ if (!status.isFile())
1278
+ return null;
1279
+ return readFileSync(path, 'utf8');
1280
+ }
1281
+ catch {
1282
+ return null;
1283
+ }
1284
+ }
1285
+ function immutableCopilotDpkgIdentityMatches(packageName, architecture, version, ownedPath) {
1286
+ const expected = VERIFIED_COPILOT_LINUX_DEBS[architecture];
1287
+ if (packageName !== 'github'
1288
+ || !expected
1289
+ || version !== COPILOT_LINUX_VERSION
1290
+ || ownedPath !== '/usr/bin/github')
1291
+ return null;
1292
+ const status = defaultFileStatus(ownedPath);
1293
+ const header = defaultFileHeader(ownedPath, 64);
1294
+ const packageManifest = '/var/lib/dpkg/info/github.md5sums';
1295
+ const packageManifestStatus = defaultFileStatus(packageManifest);
1296
+ return status?.isFile
1297
+ && status.executable
1298
+ && (status.mode & 0o022) === 0
1299
+ && status.size === expected.executableSize
1300
+ && header
1301
+ && validElfHeader(header, architecture)
1302
+ && defaultFileSha256(ownedPath) === expected.executableSha256
1303
+ && rootOwnedReadOnlyFileContent(packageManifest) !== null
1304
+ && packageManifestStatus?.size === expected.packageManifestSize
1305
+ && defaultFileSha256(packageManifest) === expected.packageManifestSha256
1306
+ ? expected
1307
+ : null;
1308
+ }
1309
+ function immutableVsCodeDpkgIdentityMatches(packageName, architecture, version, ownedPath) {
1310
+ const expected = vsCodeCatalog('linux', architecture);
1311
+ if (packageName !== 'code'
1312
+ || !expected?.packageVersion
1313
+ || version !== expected.packageVersion
1314
+ || ownedPath !== '/usr/share/code/code')
1315
+ return null;
1316
+ const status = defaultFileStatus(ownedPath);
1317
+ const header = defaultFileHeader(ownedPath, 64);
1318
+ return status?.isFile
1319
+ && status.executable
1320
+ && status.uid === 0
1321
+ && status.gid === 0
1322
+ && (status.mode & 0o022) === 0
1323
+ && status.size === expected.executable.size
1324
+ && header
1325
+ && validElfHeader(header, architecture)
1326
+ && defaultFileSha256(ownedPath) === expected.executable.sha256
1327
+ ? expected
1328
+ : null;
1329
+ }
1330
+ function defaultLinuxPackageIdentity(path) {
1331
+ if (process.platform !== 'linux')
1332
+ return null;
1333
+ let ownedPath;
1334
+ try {
1335
+ ownedPath = realpathSync(path);
1336
+ }
1337
+ catch {
1338
+ return null;
1339
+ }
1340
+ const owner = runPosixMetadataHelper('/usr/bin/dpkg-query', ['-S', ownedPath])?.stdout.trim();
1341
+ const ownerMatch = owner?.match(/^([A-Za-z0-9+.-]+)(?::[A-Za-z0-9_-]+)?:\s+(.+)$/);
1342
+ if (ownerMatch && ownerMatch[2] === ownedPath) {
1343
+ const packageName = ownerMatch[1];
1344
+ const metadata = runPosixMetadataHelper('/usr/bin/dpkg-query', [
1345
+ '-W',
1346
+ '-f=${Package}\t${Architecture}\t${Version}\t${Status}',
1347
+ packageName,
1348
+ ])?.stdout.trim();
1349
+ const match = metadata?.match(/^([A-Za-z0-9+.-]+)\t([A-Za-z0-9_-]+)\t(\S+)\tinstall ok installed$/);
1350
+ const architecture = match ? nodeArchitectureFromLinux(match[2]) : null;
1351
+ const verified = runPosixMetadataHelper('/usr/bin/dpkg', ['--verify', packageName]);
1352
+ const immutableCopilot = match && architecture
1353
+ ? immutableCopilotDpkgIdentityMatches(packageName, architecture, match[3], ownedPath)
1354
+ : null;
1355
+ const immutableVsCode = match && architecture
1356
+ ? immutableVsCodeDpkgIdentityMatches(packageName, architecture, match[3], ownedPath)
1357
+ : null;
1358
+ if (match?.[1] === packageName
1359
+ && architecture
1360
+ && verified
1361
+ && verified.stdout.trim() === ''
1362
+ && immutableCopilot) {
1363
+ return {
1364
+ packageName,
1365
+ architecture,
1366
+ version: match[3],
1367
+ manager: 'dpkg',
1368
+ ownedPath,
1369
+ origin: COPILOT_LINUX_ORIGIN,
1370
+ signatureVerified: false,
1371
+ catalogArtifactSha256: immutableCopilot.artifactSha256,
1372
+ executableSha256: immutableCopilot.executableSha256,
1373
+ };
1374
+ }
1375
+ if (match?.[1] === packageName
1376
+ && architecture
1377
+ && verified
1378
+ && verified.stdout.trim() === ''
1379
+ && immutableVsCode) {
1380
+ return {
1381
+ packageName,
1382
+ architecture,
1383
+ version: match[3],
1384
+ manager: 'dpkg',
1385
+ ownedPath,
1386
+ origin: immutableVsCode.origin,
1387
+ signatureVerified: false,
1388
+ catalogArtifactSha256: immutableVsCode.artifactSha256,
1389
+ executableSha256: immutableVsCode.executable.sha256,
1390
+ };
1391
+ }
1392
+ }
1393
+ return null;
1394
+ }
1395
+ function defaultLinuxUrlSchemeDesktopId(scheme) {
1396
+ if (process.platform !== 'linux')
1397
+ return null;
1398
+ const value = runPosixMetadataHelper('/usr/bin/xdg-mime', ['query', 'default', `x-scheme-handler/${scheme}`])?.stdout.trim();
1399
+ return value && /^[A-Za-z0-9][A-Za-z0-9._-]*\.desktop$/.test(value) ? value : null;
1400
+ }
1401
+ export const systemSurfaceProbe = {
1402
+ commandPath: defaultCommandPath,
1403
+ fileExists: existsSync,
1404
+ fileContent(path) {
1405
+ try {
1406
+ return readFileSync(path, 'utf8');
1407
+ }
1408
+ catch {
1409
+ return null;
1410
+ }
1411
+ },
1412
+ realPath(path) {
1413
+ try {
1414
+ return realpathSync(path);
1415
+ }
1416
+ catch {
1417
+ return null;
1418
+ }
1419
+ },
1420
+ directoryEntries(path) {
1421
+ try {
1422
+ return readdirSync(path);
1423
+ }
1424
+ catch {
1425
+ return [];
1426
+ }
1427
+ },
1428
+ directoryTreeIdentity: defaultDirectoryTreeIdentity,
1429
+ fileStatus: defaultFileStatus,
1430
+ fileHeader: defaultFileHeader,
1431
+ fileSha256: defaultFileSha256,
1432
+ macApplicationIdentity: defaultMacApplicationIdentity,
1433
+ macExecutableIdentity: defaultMacExecutableIdentity,
1434
+ windowsExecutableIdentity: defaultWindowsExecutableIdentity,
1435
+ windowsVsCodeInstallationAcl: defaultWindowsVsCodeInstallationAcl,
1436
+ windowsAppxIdentity: defaultWindowsAppxIdentity,
1437
+ windowsUrlHandlerExecutable: defaultWindowsUrlHandlerExecutable,
1438
+ linuxPackageIdentity: defaultLinuxPackageIdentity,
1439
+ linuxUrlSchemeDesktopId: defaultLinuxUrlSchemeDesktopId,
1440
+ };
1441
+ function windowsUserPathCandidates(paths, home, environment) {
1442
+ const candidates = [];
1443
+ const localAppData = windowsEnvironmentValue(environment, 'LOCALAPPDATA');
1444
+ const appData = windowsEnvironmentValue(environment, 'APPDATA');
1445
+ for (const path of paths) {
1446
+ if (/^[A-Za-z]:[\\/]/.test(path)) {
1447
+ candidates.push(path);
1448
+ continue;
1449
+ }
1450
+ const localSuffix = path.match(/^AppData[\\/]Local[\\/](.+)$/i)?.[1];
1451
+ const roamingSuffix = path.match(/^AppData[\\/]Roaming[\\/](.+)$/i)?.[1];
1452
+ if (localSuffix && localAppData)
1453
+ candidates.push(win32Path.join(localAppData, localSuffix));
1454
+ if (roamingSuffix && appData)
1455
+ candidates.push(win32Path.join(appData, roamingSuffix));
1456
+ candidates.push(win32Path.join(home, path));
1457
+ }
1458
+ return [...new Set(candidates)];
1459
+ }
1460
+ function candidateApplicationPaths(surface, platform, home, environment) {
1461
+ if (platform === 'darwin') {
1462
+ return (surface.macApplications ?? []).flatMap((path) => [
1463
+ path,
1464
+ path.startsWith('/Applications/')
1465
+ ? join(home, 'Applications', path.slice('/Applications/'.length))
1466
+ : join(home, 'Applications', basename(path)),
1467
+ ]);
1468
+ }
1469
+ if (platform === 'win32') {
1470
+ const candidates = windowsUserPathCandidates(surface.windowsApplications ?? [], home, environment);
1471
+ const programFilesRoots = [
1472
+ windowsEnvironmentValue(environment, 'ProgramW6432'),
1473
+ windowsEnvironmentValue(environment, 'ProgramFiles'),
1474
+ 'C:\\Program Files',
1475
+ ].filter((value) => Boolean(value));
1476
+ for (const root of [...new Set(programFilesRoots)]) {
1477
+ for (const path of surface.windowsProgramFilesApplications ?? []) {
1478
+ candidates.push(win32Path.join(root, path));
1479
+ }
1480
+ }
1481
+ return [...new Set(candidates)];
1482
+ }
1483
+ if (platform === 'linux') {
1484
+ return (surface.linuxApplications ?? []).map((path) => path.startsWith('/') ? path : join(home, path));
1485
+ }
1486
+ return [];
1487
+ }
1488
+ function candidateExecutablePaths(surface, platform, home, environment) {
1489
+ if (platform === 'darwin') {
1490
+ return (surface.macExecutables ?? []).map((path) => path.startsWith('/') ? path : join(home, path));
1491
+ }
1492
+ if (platform === 'win32') {
1493
+ return windowsUserPathCandidates(surface.windowsExecutables ?? [], home, environment);
1494
+ }
1495
+ if (platform === 'linux') {
1496
+ return (surface.linuxExecutables ?? []).map((path) => path.startsWith('/') ? path : join(home, path));
1497
+ }
1498
+ return [];
1499
+ }
1500
+ function resolvedRegularExecutable(executable, platform, probe) {
1501
+ const realExecutable = probe.realPath?.(executable);
1502
+ if (!realExecutable)
1503
+ return null;
1504
+ const status = probe.fileStatus?.(realExecutable);
1505
+ if (!status?.isFile || !status.executable)
1506
+ return null;
1507
+ if (status.isSymbolicLink || (platform !== 'win32' && (status.mode & 0o022) !== 0))
1508
+ return null;
1509
+ return realExecutable;
1510
+ }
1511
+ function launchFileBinding(path, probe, platform = process.platform) {
1512
+ const realPath = probe.realPath?.(path);
1513
+ const status = realPath ? probe.fileStatus?.(realPath) : null;
1514
+ const sha256 = realPath ? probe.fileSha256?.(realPath)?.toLowerCase() : null;
1515
+ if (!realPath
1516
+ || !status?.isFile
1517
+ || !status.executable
1518
+ || status.isSymbolicLink
1519
+ || (platform !== 'win32' && (status.mode & 0o022) !== 0)
1520
+ || !sha256
1521
+ || !/^[0-9a-f]{64}$/.test(sha256))
1522
+ return null;
1523
+ return {
1524
+ realPath,
1525
+ size: status.size,
1526
+ sha256,
1527
+ ...(status.device === undefined ? {} : { device: status.device }),
1528
+ ...(status.inode === undefined ? {} : { inode: status.inode }),
1529
+ ...(status.modifiedTimeMs === undefined ? {} : { modifiedTimeMs: status.modifiedTimeMs }),
1530
+ };
1531
+ }
1532
+ const EXPECTED_MAC_APPLICATION_IDENTITIES = {
1533
+ 'vscode-copilot': { bundleIdentifier: 'com.microsoft.VSCode', teamIdentifier: 'UBF8T346G9' },
1534
+ 'copilot-desktop': {
1535
+ bundleIdentifier: 'com.github.githubapp',
1536
+ teamIdentifier: 'VEKTX9H2N7',
1537
+ requiredScheme: 'ghapp',
1538
+ },
1539
+ 'antigravity-desktop': { bundleIdentifier: 'com.google.antigravity', teamIdentifier: 'EQHXZ8M8AV' },
1540
+ 'claude-desktop': {
1541
+ bundleIdentifier: 'com.anthropic.claudefordesktop',
1542
+ teamIdentifier: 'Q6L2SF6YDW',
1543
+ requiredScheme: 'claude',
1544
+ },
1545
+ 'codex-desktop': { bundleIdentifier: 'com.openai.codex', teamIdentifier: '2DC432GLL2' },
1546
+ 'grok-bot': { bundleIdentifier: 'com.anysphere.sand', teamIdentifier: 'DCNK4UB866' },
1547
+ };
1548
+ const EXPECTED_MAC_EXECUTABLE_IDENTITIES = {
1549
+ 'copilot-cli': { identifier: 'copilot', teamIdentifier: 'VEKTX9H2N7' },
1550
+ 'antigravity-cli': { identifier: 'cli', teamIdentifier: 'EQHXZ8M8AV' },
1551
+ 'claude-cli': { identifier: 'com.anthropic.claude-code', teamIdentifier: 'Q6L2SF6YDW' },
1552
+ 'codex-cli': { identifier: 'codex', teamIdentifier: '2DC432GLL2' },
1553
+ 'grok-cli': { identifier: 'xai-grok-pager', teamIdentifier: '5Y6N3AJ54S' },
1554
+ };
1555
+ const EXPECTED_WINDOWS_PUBLISHERS = {
1556
+ 'vscode-copilot': ['CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'],
1557
+ 'copilot-cli': ['CN=GitHub, Inc., O=GitHub, Inc., C=US'],
1558
+ 'copilot-desktop': ['CN=GitHub, Inc., O=GitHub, Inc., C=US'],
1559
+ 'antigravity-cli': ['CN=Google LLC, O=Google LLC, L=Mountain View, S=California, C=US'],
1560
+ 'antigravity-desktop': ['CN=Google LLC, O=Google LLC, L=Mountain View, S=California, C=US'],
1561
+ 'claude-cli': ['CN=Anthropic PBC, O=Anthropic PBC, C=US'],
1562
+ 'claude-desktop': ['CN=Anthropic PBC, O=Anthropic PBC, C=US'],
1563
+ 'codex-cli': ['CN=OpenAI, L.L.C., O=OpenAI, L.L.C., C=US'],
1564
+ 'codex-desktop': ['CN=OpenAI, L.L.C., O=OpenAI, L.L.C., C=US'],
1565
+ 'grok-cli': ['CN=X.AI Corporation, O=X.AI Corporation, C=US'],
1566
+ 'grok-bot': ['CN=Anysphere Incorporated, O=Anysphere Incorporated, C=US'],
1567
+ };
1568
+ function supportedCliArchitecture(architecture) {
1569
+ return architecture === 'x64' || architecture === 'arm64';
1570
+ }
1571
+ function authenticatedCliTarget(surface, platform, architecture, home, environment, probe) {
1572
+ if (surface.kind !== 'cli')
1573
+ return null;
1574
+ const candidates = [
1575
+ ...surface.commands.map((command) => probe.commandPath(command)).filter((value) => Boolean(value)),
1576
+ ...candidateExecutablePaths(surface, platform, home, environment),
1577
+ ];
1578
+ for (const candidate of [...new Set(candidates)]) {
1579
+ const executable = resolvedRegularExecutable(candidate, platform, probe);
1580
+ if (!executable)
1581
+ continue;
1582
+ const binding = launchFileBinding(executable, probe, platform);
1583
+ if (!binding)
1584
+ continue;
1585
+ if (platform === 'darwin') {
1586
+ const expected = EXPECTED_MAC_EXECUTABLE_IDENTITIES[surface.id];
1587
+ const identity = expected ? probe.macExecutableIdentity?.(executable, expected) : null;
1588
+ if (!expected
1589
+ || !identity
1590
+ || identity.identifier !== expected.identifier
1591
+ || identity.teamIdentifier !== expected.teamIdentifier
1592
+ || !identity.architectures.includes(architecture))
1593
+ continue;
1594
+ return {
1595
+ executable: binding.realPath,
1596
+ launchArgsPrefix: [],
1597
+ launchEnvironment: {},
1598
+ capabilities: [],
1599
+ verification: {
1600
+ kind: 'mac-executable',
1601
+ ...binding,
1602
+ architecture,
1603
+ identifier: identity.identifier,
1604
+ teamIdentifier: identity.teamIdentifier,
1605
+ },
1606
+ };
1607
+ }
1608
+ if (platform === 'win32' && supportedCliArchitecture(architecture)) {
1609
+ const identity = probe.windowsExecutableIdentity?.(executable);
1610
+ const publishers = EXPECTED_WINDOWS_PUBLISHERS[surface.id];
1611
+ if (!identity || !publishers?.includes(identity.publisher) || identity.architecture !== architecture)
1612
+ continue;
1613
+ return {
1614
+ executable: binding.realPath,
1615
+ launchArgsPrefix: [],
1616
+ launchEnvironment: {},
1617
+ capabilities: [],
1618
+ verification: {
1619
+ kind: 'windows-executable',
1620
+ ...binding,
1621
+ architecture: identity.architecture,
1622
+ productName: identity.productName,
1623
+ ...(identity.companyName ? { companyName: identity.companyName } : {}),
1624
+ publisher: identity.publisher,
1625
+ },
1626
+ };
1627
+ }
1628
+ // Linux CLIs fail closed until each provider has an immutable artifact or
1629
+ // signed package identity. An ELF header and an install path are not proof.
1630
+ }
1631
+ return null;
1632
+ }
1633
+ function windowsApplicationIdentityMatches(surface, executable, architecture, probe) {
1634
+ const identity = probe.windowsExecutableIdentity?.(executable);
1635
+ return Boolean(identity && windowsApplicationIdentityValueMatches(surface, identity, architecture));
1636
+ }
1637
+ function windowsApplicationIdentityValueMatches(surface, identity, architecture) {
1638
+ if (!identity || identity.architecture !== architecture)
1639
+ return false;
1640
+ const publishers = EXPECTED_WINDOWS_PUBLISHERS[surface.id];
1641
+ if (!publishers?.includes(identity.publisher) || !identity.productName.trim())
1642
+ return false;
1643
+ if (surface.id === 'vscode-copilot')
1644
+ return identity.productName.trim() === 'Visual Studio Code';
1645
+ if (surface.id === 'claude-desktop') {
1646
+ return identity.productName.trim() === 'Claude' || identity.productName.trim() === 'Claude Desktop';
1647
+ }
1648
+ if (surface.id === 'copilot-desktop')
1649
+ return identity.productName.trim() === 'GitHub Copilot';
1650
+ return true;
1651
+ }
1652
+ function linuxPackageIdentityMatches(executable, packageName, architecture, probe) {
1653
+ const identity = probe.linuxPackageIdentity?.(executable);
1654
+ const realExecutable = probe.realPath?.(executable);
1655
+ if (!identity
1656
+ || !realExecutable
1657
+ || identity.packageName !== packageName
1658
+ || identity.architecture !== architecture
1659
+ || identity.ownedPath !== realExecutable
1660
+ || !/^\S+$/.test(identity.version))
1661
+ return false;
1662
+ if (packageName === 'github') {
1663
+ const expected = VERIFIED_COPILOT_LINUX_DEBS[architecture];
1664
+ return Boolean(expected
1665
+ && identity.manager === 'dpkg'
1666
+ && identity.version === COPILOT_LINUX_VERSION
1667
+ && identity.origin === COPILOT_LINUX_ORIGIN
1668
+ && identity.signatureVerified === false
1669
+ && identity.catalogArtifactSha256 === expected.artifactSha256
1670
+ && identity.executableSha256 === expected.executableSha256);
1671
+ }
1672
+ if (packageName === 'code') {
1673
+ const expected = vsCodeCatalog('linux', architecture);
1674
+ return Boolean(expected
1675
+ && expected.packageVersion
1676
+ && identity.manager === 'dpkg'
1677
+ && identity.version === expected.packageVersion
1678
+ && identity.origin === expected.origin
1679
+ && identity.signatureVerified === false
1680
+ && identity.catalogArtifactSha256 === expected.artifactSha256
1681
+ && identity.executableSha256 === expected.executable.sha256);
1682
+ }
1683
+ // Repository configuration plus package-manager ownership does not bind the
1684
+ // installed payload to a signed repository index. These packages remain
1685
+ // fail-closed until an immutable signed-index/package/payload catalog exists.
1686
+ return false;
1687
+ }
1688
+ function applicationLaunchVerification(surface, executable, platform, architecture, probe) {
1689
+ if (platform === 'darwin') {
1690
+ const expected = EXPECTED_MAC_APPLICATION_IDENTITIES[surface.id];
1691
+ const identity = probe.macApplicationIdentity?.(executable, expected);
1692
+ const realApplication = probe.realPath?.(executable);
1693
+ const realExecutable = identity ? probe.realPath?.(identity.executablePath) : null;
1694
+ const executableBinding = realExecutable ? launchFileBinding(realExecutable, probe, platform) : null;
1695
+ if (!expected
1696
+ || !identity
1697
+ || !realApplication
1698
+ || !realExecutable
1699
+ || !executableBinding
1700
+ || identity.bundleIdentifier !== expected.bundleIdentifier
1701
+ || identity.teamIdentifier !== expected.teamIdentifier
1702
+ || !identity.architectures.includes(architecture)
1703
+ || !realExecutable.startsWith(`${realApplication}/Contents/MacOS/`)
1704
+ || (expected.requiredScheme !== undefined
1705
+ && !identity.urlSchemes.includes(expected.requiredScheme)))
1706
+ return null;
1707
+ return {
1708
+ kind: 'mac-application',
1709
+ realPath: realApplication,
1710
+ executablePath: realExecutable,
1711
+ executableSize: executableBinding.size,
1712
+ executableSha256: executableBinding.sha256,
1713
+ ...(executableBinding.device === undefined ? {} : { executableDevice: executableBinding.device }),
1714
+ ...(executableBinding.inode === undefined ? {} : { executableInode: executableBinding.inode }),
1715
+ ...(executableBinding.modifiedTimeMs === undefined
1716
+ ? {}
1717
+ : { executableModifiedTimeMs: executableBinding.modifiedTimeMs }),
1718
+ architecture,
1719
+ bundleIdentifier: identity.bundleIdentifier,
1720
+ teamIdentifier: identity.teamIdentifier,
1721
+ ...(expected.requiredScheme ? { requiredScheme: expected.requiredScheme } : {}),
1722
+ };
1723
+ }
1724
+ if (platform === 'win32') {
1725
+ const identity = probe.windowsExecutableIdentity?.(executable);
1726
+ const binding = launchFileBinding(executable, probe, platform);
1727
+ if (!identity
1728
+ || !binding
1729
+ || !windowsApplicationIdentityValueMatches(surface, identity, architecture))
1730
+ return null;
1731
+ return {
1732
+ kind: 'windows-executable',
1733
+ ...binding,
1734
+ architecture: identity.architecture,
1735
+ productName: identity.productName,
1736
+ ...(identity.companyName ? { companyName: identity.companyName } : {}),
1737
+ publisher: identity.publisher,
1738
+ };
1739
+ }
1740
+ if (platform !== 'linux')
1741
+ return null;
1742
+ const identity = probe.linuxPackageIdentity?.(executable);
1743
+ const binding = launchFileBinding(executable, probe, platform);
1744
+ if (identity
1745
+ && binding
1746
+ && identity.catalogArtifactSha256
1747
+ && identity.executableSha256 === binding.sha256
1748
+ && surface.id === 'copilot-desktop'
1749
+ && linuxPackageIdentityMatches(executable, 'github', architecture, probe)) {
1750
+ return {
1751
+ kind: 'linux-package',
1752
+ ...binding,
1753
+ architecture: identity.architecture,
1754
+ packageName: identity.packageName,
1755
+ version: identity.version,
1756
+ manager: identity.manager,
1757
+ origin: identity.origin,
1758
+ catalogArtifactSha256: identity.catalogArtifactSha256,
1759
+ };
1760
+ }
1761
+ if (!binding || !supportedCliArchitecture(architecture))
1762
+ return null;
1763
+ const header = probe.fileHeader?.(binding.realPath, 64);
1764
+ if (!header || !validElfHeader(header, architecture))
1765
+ return null;
1766
+ return {
1767
+ kind: 'linux-application',
1768
+ ...binding,
1769
+ surfaceId: surface.id,
1770
+ architecture,
1771
+ };
1772
+ }
1773
+ function hasExpectedApplicationIdentity(surface, executable, platform, architecture, probe) {
1774
+ return applicationLaunchVerification(surface, executable, platform, architecture, probe) !== null;
1775
+ }
1776
+ function sameCatalogPath(left, right, platform) {
1777
+ return platform === 'win32'
1778
+ ? win32Path.normalize(left).toLowerCase() === win32Path.normalize(right).toLowerCase()
1779
+ : resolve(left) === resolve(right);
1780
+ }
1781
+ function catalogFileMatches(path, expected, platform, probe) {
1782
+ const realPath = probe.realPath?.(path);
1783
+ const status = realPath ? probe.fileStatus?.(realPath) : null;
1784
+ return Boolean(realPath
1785
+ && sameCatalogPath(realPath, path, platform)
1786
+ && status?.isFile
1787
+ && !status.isSymbolicLink
1788
+ && status.size === expected.size
1789
+ && probe.fileSha256?.(realPath)?.toLowerCase() === expected.sha256);
1790
+ }
1791
+ function catalogFileBinding(path, expected, platform, probe) {
1792
+ if (!catalogFileMatches(path, expected, platform, probe))
1793
+ return null;
1794
+ const realPath = probe.realPath?.(path);
1795
+ const status = realPath ? probe.fileStatus?.(realPath) : null;
1796
+ if (!realPath || !status)
1797
+ return null;
1798
+ return {
1799
+ realPath,
1800
+ size: status.size,
1801
+ sha256: expected.sha256,
1802
+ ...(status.device === undefined ? {} : { device: status.device }),
1803
+ ...(status.inode === undefined ? {} : { inode: status.inode }),
1804
+ ...(status.modifiedTimeMs === undefined ? {} : { modifiedTimeMs: status.modifiedTimeMs }),
1805
+ };
1806
+ }
1807
+ function vscodeCatalogMetadataMatches(resourceRoot, expected, platform, probe) {
1808
+ const pathFor = (...segments) => platform === 'win32'
1809
+ ? win32Path.join(resourceRoot, ...segments)
1810
+ : join(resourceRoot, ...segments);
1811
+ const packagePath = pathFor('package.json');
1812
+ const productPath = pathFor('product.json');
1813
+ const copilotPath = pathFor('extensions', 'copilot', 'package.json');
1814
+ const cliPath = pathFor('out', 'cli.js');
1815
+ if (!catalogFileMatches(packagePath, expected.packageJson, platform, probe)
1816
+ || !catalogFileMatches(productPath, expected.productJson, platform, probe)
1817
+ || !catalogFileMatches(copilotPath, expected.copilotPackageJson, platform, probe)
1818
+ || !catalogFileMatches(cliPath, expected.cliJs, platform, probe))
1819
+ return false;
1820
+ try {
1821
+ const packageMetadata = JSON.parse(probe.fileContent?.(packagePath) ?? '');
1822
+ const productMetadata = JSON.parse(probe.fileContent?.(productPath) ?? '');
1823
+ const copilotMetadata = JSON.parse(probe.fileContent?.(copilotPath) ?? '');
1824
+ const engines = copilotMetadata.engines;
1825
+ return packageMetadata.name === 'Code'
1826
+ && packageMetadata.version === VSCODE_CATALOG_VERSION
1827
+ && packageMetadata.main === './out/main.js'
1828
+ && productMetadata.nameLong === 'Visual Studio Code'
1829
+ && productMetadata.applicationName === 'code'
1830
+ && productMetadata.commit === VSCODE_CATALOG_COMMIT
1831
+ && productMetadata.quality === 'stable'
1832
+ && copilotMetadata.publisher === 'GitHub'
1833
+ && copilotMetadata.name === 'copilot-chat'
1834
+ && copilotMetadata.version === '0.64.1'
1835
+ && engines?.vscode === `^${VSCODE_CATALOG_VERSION}`;
1836
+ }
1837
+ catch {
1838
+ return false;
112
1839
  }
113
1840
  }
114
- export async function rememberAiSurface(surfaceId, home = homedir()) {
115
- const target = preferencesPath(home);
116
- await mkdir(dirname(target), { recursive: true });
117
- const temporary = `${target}.${process.pid}.tmp`;
118
- await writeFile(temporary, `${JSON.stringify({ version: 1, lastAiSurface: surfaceId }, null, 2)}\n`, {
119
- encoding: 'utf8',
120
- mode: 0o600,
1841
+ function vscodeCatalogTreeMatches(applicationRoot, expected, platform, probe) {
1842
+ const identity = probe.directoryTreeIdentity?.(applicationRoot);
1843
+ return identity
1844
+ && sameCatalogPath(identity.realPath, applicationRoot, platform)
1845
+ && identity.fileCount === expected.fileCount
1846
+ && identity.totalBytes === expected.totalBytes
1847
+ && identity.sha256 === expected.sha256
1848
+ ? identity
1849
+ : null;
1850
+ }
1851
+ function exactStringRecord(actual, expected) {
1852
+ const actualKeys = Object.keys(actual).sort();
1853
+ const expectedKeys = Object.keys(expected).sort();
1854
+ return actualKeys.length === expectedKeys.length
1855
+ && actualKeys.every((key, index) => (key === expectedKeys[index] && actual[key] === expected[key]));
1856
+ }
1857
+ function windowsDirectoryHasExactEntries(directory, expectedEntries, probe) {
1858
+ const entries = probe.directoryEntries?.(directory);
1859
+ if (!entries || new Set(entries.map((entry) => entry.toLowerCase())).size !== entries.length)
1860
+ return false;
1861
+ const actual = [...entries].map((entry) => entry.toLowerCase()).sort();
1862
+ const expected = expectedEntries.map((entry) => entry.toLowerCase()).sort();
1863
+ return actual.length === expected.length
1864
+ && actual.every((entry, index) => entry === expected[index]);
1865
+ }
1866
+ function windowsVsCodeInstallationRootMatches(installationRoot, probe) {
1867
+ if (!windowsDirectoryHasExactEntries(installationRoot, WINDOWS_VSCODE_INSTALLATION_ENTRIES, probe) || !windowsDirectoryHasExactEntries(win32Path.join(installationRoot, 'bin'), WINDOWS_VSCODE_BIN_ENTRIES, probe))
1868
+ return false;
1869
+ return WINDOWS_VSCODE_UNINSTALLER_FILES.every((file) => {
1870
+ const path = win32Path.join(installationRoot, file);
1871
+ const realPath = probe.realPath?.(path);
1872
+ const status = realPath ? probe.fileStatus?.(realPath) : null;
1873
+ return Boolean(realPath
1874
+ && sameCatalogPath(realPath, path, 'win32')
1875
+ && status?.isFile
1876
+ && !status.isSymbolicLink);
121
1877
  });
122
- await chmod(temporary, 0o600).catch(() => undefined);
123
- await rename(temporary, target);
124
1878
  }
125
- function defaultCommandPath(command) {
126
- const resolver = process.platform === 'win32' ? 'where.exe' : 'which';
127
- const result = spawnSync(resolver, [command], { encoding: 'utf8', windowsHide: true });
128
- if (result.status !== 0)
1879
+ function catalogTreeShapeMatches(actual, realPath, expected, platform) {
1880
+ return Boolean(actual
1881
+ && expected
1882
+ && sameCatalogPath(actual.realPath, realPath, platform)
1883
+ && actual.fileCount === expected.fileCount
1884
+ && actual.totalBytes === expected.totalBytes
1885
+ && actual.sha256 === expected.sha256);
1886
+ }
1887
+ function vscodeCatalogVerificationShapeMatches(surface, platform) {
1888
+ const verification = surface.verification;
1889
+ if (surface.id !== 'vscode-copilot'
1890
+ || (platform !== 'win32' && platform !== 'linux')
1891
+ || verification?.kind !== 'vscode-catalog'
1892
+ || verification.platform !== platform
1893
+ || surface.executable === null
1894
+ || !sameCatalogPath(surface.executable, verification.realPath, platform)
1895
+ || !equalStringArrays(surface.launchArgsPrefix, [verification.cliPath])
1896
+ || !exactStringRecord(surface.launchEnvironment, VSCODE_CATALOG_LAUNCH_ENVIRONMENT)
1897
+ || verification.version !== VSCODE_CATALOG_VERSION
1898
+ || verification.commit !== VSCODE_CATALOG_COMMIT)
1899
+ return false;
1900
+ const expected = vsCodeCatalog(platform, verification.architecture);
1901
+ if (!expected
1902
+ || verification.catalogArtifactSha256 !== expected.artifactSha256
1903
+ || verification.size !== expected.executable.size
1904
+ || verification.sha256 !== expected.executable.sha256)
1905
+ return false;
1906
+ if (platform === 'win32') {
1907
+ const installationRoot = win32Path.dirname(verification.realPath);
1908
+ const applicationRoot = win32Path.join(installationRoot, VSCODE_CATALOG_COMMIT_DIRECTORY);
1909
+ const resourceRoot = win32Path.join(applicationRoot, 'resources', 'app');
1910
+ const auxiliaryRoot = win32Path.join(installationRoot, 'bin');
1911
+ const visualManifestPath = win32Path.join(installationRoot, 'Code.VisualElementsManifest.xml');
1912
+ return win32Path.basename(verification.realPath).toLowerCase() === 'code.exe'
1913
+ && sameCatalogPath(verification.installationRoot, installationRoot, 'win32')
1914
+ && sameCatalogPath(verification.cliPath, win32Path.join(resourceRoot, 'out', 'cli.js'), 'win32')
1915
+ && catalogTreeShapeMatches(verification.applicationTree, applicationRoot, expected.applicationTree, 'win32')
1916
+ && catalogTreeShapeMatches(verification.auxiliaryTree, auxiliaryRoot, expected.auxiliaryTree, 'win32')
1917
+ && Boolean(expected.visualManifest
1918
+ && verification.visualManifest
1919
+ && sameCatalogPath(verification.visualManifest.realPath, visualManifestPath, 'win32')
1920
+ && verification.visualManifest.size === expected.visualManifest.size
1921
+ && verification.visualManifest.sha256 === expected.visualManifest.sha256);
1922
+ }
1923
+ return sameCatalogPath(verification.realPath, '/usr/share/code/code', 'linux')
1924
+ && sameCatalogPath(verification.installationRoot, '/usr/share/code', 'linux')
1925
+ && sameCatalogPath(verification.cliPath, '/usr/share/code/resources/app/out/cli.js', 'linux')
1926
+ && catalogTreeShapeMatches(verification.applicationTree, '/usr/share/code', expected.applicationTree, 'linux')
1927
+ && verification.auxiliaryTree === undefined
1928
+ && verification.visualManifest === undefined;
1929
+ }
1930
+ function windowsVsCodeTarget(surface, architecture, home, environment, probe) {
1931
+ const expected = vsCodeCatalog('win32', architecture);
1932
+ if (!expected || (architecture !== 'x64' && architecture !== 'arm64'))
129
1933
  return null;
130
- const candidates = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
131
- if (process.platform === 'win32') {
132
- return candidates.find((candidate) => /\.(?:exe|com|cmd|bat)$/i.test(candidate)) ?? candidates[0] ?? null;
1934
+ for (const candidate of candidateApplicationPaths(surface, 'win32', home, environment)) {
1935
+ if (win32Path.basename(candidate).toLowerCase() !== 'code.exe'
1936
+ || !probe.fileExists(candidate))
1937
+ continue;
1938
+ const candidateInstallationRoot = win32Path.dirname(candidate);
1939
+ if (!probe.windowsVsCodeInstallationAcl?.(candidateInstallationRoot))
1940
+ continue;
1941
+ const binding = launchFileBinding(candidate, probe, 'win32');
1942
+ const identity = probe.windowsExecutableIdentity?.(candidate);
1943
+ if (!binding
1944
+ || !sameCatalogPath(binding.realPath, candidate, 'win32')
1945
+ || binding.size !== expected.executable.size
1946
+ || binding.sha256 !== expected.executable.sha256
1947
+ || !identity
1948
+ || !windowsApplicationIdentityValueMatches(surface, identity, architecture))
1949
+ continue;
1950
+ const installationRoot = win32Path.dirname(binding.realPath);
1951
+ const applicationRoot = win32Path.join(installationRoot, VSCODE_CATALOG_COMMIT_DIRECTORY);
1952
+ const resourceRoot = win32Path.join(applicationRoot, 'resources', 'app');
1953
+ const auxiliaryRoot = win32Path.join(installationRoot, 'bin');
1954
+ const visualManifestPath = win32Path.join(installationRoot, 'Code.VisualElementsManifest.xml');
1955
+ const applicationTree = vscodeCatalogTreeMatches(applicationRoot, expected.applicationTree, 'win32', probe);
1956
+ const auxiliaryTree = expected.auxiliaryTree
1957
+ ? vscodeCatalogTreeMatches(auxiliaryRoot, expected.auxiliaryTree, 'win32', probe)
1958
+ : null;
1959
+ const visualManifest = expected.visualManifest
1960
+ ? catalogFileBinding(visualManifestPath, expected.visualManifest, 'win32', probe)
1961
+ : null;
1962
+ if (!applicationTree
1963
+ || !auxiliaryTree
1964
+ || !visualManifest
1965
+ || !windowsVsCodeInstallationRootMatches(installationRoot, probe)
1966
+ || !vscodeCatalogMetadataMatches(resourceRoot, expected, 'win32', probe))
1967
+ continue;
1968
+ const cliPath = win32Path.join(resourceRoot, 'out', 'cli.js');
1969
+ return {
1970
+ executable: binding.realPath,
1971
+ launchArgsPrefix: [cliPath],
1972
+ launchEnvironment: { ...VSCODE_CATALOG_LAUNCH_ENVIRONMENT },
1973
+ capabilities: ['initial-prompt'],
1974
+ verification: {
1975
+ kind: 'vscode-catalog',
1976
+ ...binding,
1977
+ platform: 'win32',
1978
+ architecture,
1979
+ version: VSCODE_CATALOG_VERSION,
1980
+ commit: VSCODE_CATALOG_COMMIT,
1981
+ installationRoot,
1982
+ cliPath,
1983
+ catalogArtifactSha256: expected.artifactSha256,
1984
+ applicationTree,
1985
+ auxiliaryTree,
1986
+ visualManifest,
1987
+ },
1988
+ };
133
1989
  }
134
- return candidates[0] ?? null;
1990
+ return null;
135
1991
  }
136
- export const systemSurfaceProbe = {
137
- commandPath: defaultCommandPath,
138
- fileExists: existsSync,
139
- fileContent(path) {
140
- try {
141
- return readFileSync(path, 'utf8');
1992
+ function vscodeHasCopilotMetadata(_home, _platform, probe, builtInCandidates) {
1993
+ // Only metadata inside the already authenticated application/package may be
1994
+ // used. User-writable extension manifests are not publisher authentication.
1995
+ return builtInCandidates.some((candidate) => probe.fileExists(candidate));
1996
+ }
1997
+ function macVsCodeTarget(surface, architecture, home, environment, probe) {
1998
+ for (const application of candidateApplicationPaths(surface, 'darwin', home, environment)) {
1999
+ if (!probe.fileExists(application))
2000
+ continue;
2001
+ const verification = applicationLaunchVerification(surface, application, 'darwin', architecture, probe);
2002
+ if (!verification || verification.kind !== 'mac-application')
2003
+ continue;
2004
+ const realApplication = probe.realPath?.(application);
2005
+ if (!realApplication)
2006
+ continue;
2007
+ const command = join(realApplication, 'Contents', 'Resources', 'app', 'bin', 'code');
2008
+ const realCommand = resolvedRegularExecutable(command, 'darwin', probe);
2009
+ const commandBinding = realCommand ? launchFileBinding(realCommand, probe, 'darwin') : null;
2010
+ if (!realCommand
2011
+ || !commandBinding
2012
+ || !realCommand.startsWith(`${realApplication}/Contents/Resources/app/bin/`))
2013
+ continue;
2014
+ const builtInCopilot = join(realApplication, 'Contents', 'Resources', 'app', 'extensions', 'copilot');
2015
+ if (!vscodeHasCopilotMetadata(home, 'darwin', probe, [builtInCopilot]))
2016
+ continue;
2017
+ return {
2018
+ executable: realCommand,
2019
+ launchArgsPrefix: [],
2020
+ launchEnvironment: {},
2021
+ verification: {
2022
+ ...verification,
2023
+ commandRealPath: commandBinding.realPath,
2024
+ commandSize: commandBinding.size,
2025
+ commandSha256: commandBinding.sha256,
2026
+ ...(commandBinding.device === undefined ? {} : { commandDevice: commandBinding.device }),
2027
+ ...(commandBinding.inode === undefined ? {} : { commandInode: commandBinding.inode }),
2028
+ ...(commandBinding.modifiedTimeMs === undefined
2029
+ ? {}
2030
+ : { commandModifiedTimeMs: commandBinding.modifiedTimeMs }),
2031
+ },
2032
+ };
2033
+ }
2034
+ return null;
2035
+ }
2036
+ function linuxVsCodeTarget(surface, architecture, _home, probe) {
2037
+ const expected = vsCodeCatalog('linux', architecture);
2038
+ const executable = '/usr/share/code/code';
2039
+ if (!expected
2040
+ || (architecture !== 'x64' && architecture !== 'arm64')
2041
+ || !probe.fileExists(executable))
2042
+ return null;
2043
+ const binding = launchFileBinding(executable, probe, 'linux');
2044
+ const header = probe.fileHeader?.(executable, 64);
2045
+ if (!binding
2046
+ || binding.size !== expected.executable.size
2047
+ || binding.sha256 !== expected.executable.sha256
2048
+ || !header
2049
+ || !validElfHeader(header, architecture)
2050
+ || !linuxPackageIdentityMatches(executable, 'code', architecture, probe))
2051
+ return null;
2052
+ const installationRoot = '/usr/share/code';
2053
+ const resourceRoot = join(installationRoot, 'resources', 'app');
2054
+ const applicationTree = vscodeCatalogTreeMatches(installationRoot, expected.applicationTree, 'linux', probe);
2055
+ if (!applicationTree
2056
+ || !vscodeCatalogMetadataMatches(resourceRoot, expected, 'linux', probe))
2057
+ return null;
2058
+ const cliPath = join(resourceRoot, 'out', 'cli.js');
2059
+ return {
2060
+ executable: binding.realPath,
2061
+ launchArgsPrefix: [cliPath],
2062
+ launchEnvironment: { ...VSCODE_CATALOG_LAUNCH_ENVIRONMENT },
2063
+ capabilities: ['initial-prompt'],
2064
+ verification: {
2065
+ kind: 'vscode-catalog',
2066
+ ...binding,
2067
+ platform: 'linux',
2068
+ architecture,
2069
+ version: VSCODE_CATALOG_VERSION,
2070
+ commit: VSCODE_CATALOG_COMMIT,
2071
+ installationRoot,
2072
+ cliPath,
2073
+ catalogArtifactSha256: expected.artifactSha256,
2074
+ applicationTree,
2075
+ },
2076
+ };
2077
+ }
2078
+ function parseDesktopEntry(content) {
2079
+ const values = new Map();
2080
+ let inDesktopEntry = false;
2081
+ for (const rawLine of content.replace(/^\uFEFF/, '').split(/\r?\n/)) {
2082
+ const line = rawLine.trim();
2083
+ if (!line || line.startsWith('#'))
2084
+ continue;
2085
+ if (line.startsWith('[') && line.endsWith(']')) {
2086
+ inDesktopEntry = line === '[Desktop Entry]';
2087
+ continue;
142
2088
  }
143
- catch {
2089
+ if (!inDesktopEntry)
2090
+ continue;
2091
+ const separator = rawLine.indexOf('=');
2092
+ if (separator <= 0)
144
2093
  return null;
145
- }
146
- },
147
- realPath(path) {
148
- try {
149
- return realpathSync(path);
150
- }
151
- catch {
2094
+ const key = rawLine.slice(0, separator).trim();
2095
+ // Locale-qualified keys (for example `Name[en_AU]`) are valid desktop-entry
2096
+ // metadata. Keep them separate from the unqualified values used for launch
2097
+ // validation, while still rejecting duplicate or malformed keys.
2098
+ if (!/^[A-Za-z][A-Za-z0-9-]*(?:\[[A-Za-z0-9_.@-]+\])?$/.test(key) || values.has(key))
152
2099
  return null;
2100
+ values.set(key, rawLine.slice(separator + 1).trim());
2101
+ }
2102
+ return values.size > 0 ? values : null;
2103
+ }
2104
+ function decodeDesktopValue(value) {
2105
+ let decoded = '';
2106
+ for (let index = 0; index < value.length; index += 1) {
2107
+ const character = value[index];
2108
+ if (character !== '\\') {
2109
+ decoded += character;
2110
+ continue;
153
2111
  }
154
- },
155
- commandOutput(command, args, environment) {
156
- const result = spawnSync(command, [...args], {
157
- encoding: 'utf8',
158
- windowsHide: true,
159
- timeout: 5000,
160
- env: { ...process.env, ...environment },
161
- });
162
- if (result.status !== 0)
2112
+ const escaped = value[index + 1];
2113
+ if (escaped === undefined)
163
2114
  return null;
164
- return `${result.stdout ?? ''}\n${result.stderr ?? ''}`.trim();
165
- },
166
- };
167
- function candidateApplicationPaths(surface, platform, home) {
168
- if (platform === 'darwin') {
169
- return (surface.macApplications ?? []).flatMap((path) => [
170
- path,
171
- path.startsWith('/Applications/')
172
- ? join(home, 'Applications', path.slice('/Applications/'.length))
173
- : join(home, 'Applications', basename(path)),
174
- ]);
175
- }
176
- if (platform === 'win32') {
177
- return (surface.windowsApplications ?? []).map((path) => /^[A-Za-z]:[\\/]/.test(path) ? path : win32Path.join(home, path));
2115
+ const replacement = {
2116
+ s: ' ',
2117
+ n: '\n',
2118
+ t: '\t',
2119
+ r: '\r',
2120
+ '\\': '\\',
2121
+ };
2122
+ if (!(escaped in replacement))
2123
+ return null;
2124
+ decoded += replacement[escaped];
2125
+ index += 1;
178
2126
  }
179
- return [];
2127
+ return decoded;
180
2128
  }
181
- function windowsVsCodeTarget(surface, home, probe) {
182
- const shims = new Set();
183
- const executables = new Set();
184
- for (const command of surface.commands) {
185
- const found = probe.commandPath(command);
186
- if (!found)
2129
+ function parseDesktopExec(value) {
2130
+ if (!value || /[\0\r\n]/.test(value))
2131
+ return null;
2132
+ const tokens = [];
2133
+ let current = '';
2134
+ let quoted = false;
2135
+ let escaped = false;
2136
+ const flush = () => {
2137
+ if (current)
2138
+ tokens.push(current);
2139
+ current = '';
2140
+ };
2141
+ for (const character of value) {
2142
+ if (escaped) {
2143
+ current += character;
2144
+ escaped = false;
187
2145
  continue;
188
- if (/\.(?:cmd|bat)$/i.test(found))
189
- shims.add(found);
190
- else if (/\.(?:exe|com)$/i.test(found))
191
- executables.add(found);
192
- }
193
- for (const candidate of candidateApplicationPaths(surface, 'win32', home)) {
194
- if (/\.(?:cmd|bat)$/i.test(candidate))
195
- shims.add(candidate);
196
- else if (/\.(?:exe|com)$/i.test(candidate))
197
- executables.add(candidate);
198
- }
199
- for (const executable of executables) {
200
- shims.add(win32Path.join(win32Path.dirname(executable), 'bin', 'code.cmd'));
201
- }
202
- for (const shim of shims) {
203
- if (!probe.fileExists(shim))
2146
+ }
2147
+ if (character === '\\') {
2148
+ escaped = true;
2149
+ continue;
2150
+ }
2151
+ if (character === '"') {
2152
+ quoted = !quoted;
204
2153
  continue;
205
- const executable = win32Path.resolve(win32Path.dirname(shim), '..', 'Code.exe');
206
- if (!probe.fileExists(executable))
2154
+ }
2155
+ if (/\s/.test(character) && !quoted) {
2156
+ flush();
207
2157
  continue;
208
- const content = probe.fileContent?.(shim) ?? '';
209
- const cliMatch = content.match(/"%~dp0([^"\r\n]*\\resources\\app\\out\\cli\.js)"/i);
210
- if (!cliMatch)
2158
+ }
2159
+ current += character;
2160
+ }
2161
+ if (escaped || quoted)
2162
+ return null;
2163
+ flush();
2164
+ if (tokens.length === 0)
2165
+ return null;
2166
+ const result = [];
2167
+ for (const token of tokens) {
2168
+ if (/^%[fFuUdDnNickvm]$/.test(token))
211
2169
  continue;
212
- const cliScript = win32Path.resolve(win32Path.dirname(shim), cliMatch[1]);
213
- const installRoot = `${win32Path.dirname(executable).toLowerCase()}\\`;
214
- if (!cliScript.toLowerCase().startsWith(installRoot) || !probe.fileExists(cliScript))
2170
+ const literal = token.replace(/%%/g, '%');
2171
+ if (/%[A-Za-z%]/.test(literal))
2172
+ return null;
2173
+ result.push(literal);
2174
+ }
2175
+ return result.length > 0 ? result : null;
2176
+ }
2177
+ function linuxApplicationDirectories(home, environment) {
2178
+ const dataHome = environment.XDG_DATA_HOME && environment.XDG_DATA_HOME.startsWith('/')
2179
+ ? environment.XDG_DATA_HOME
2180
+ : join(home, '.local', 'share');
2181
+ const dataDirectories = (environment.XDG_DATA_DIRS ?? '/usr/local/share:/usr/share')
2182
+ .split(':')
2183
+ .filter((candidate) => candidate.startsWith('/'));
2184
+ return [...new Set([dataHome, ...dataDirectories].map((directory) => join(directory, 'applications')))];
2185
+ }
2186
+ function desktopEntryPath(desktopId, home, environment, probe) {
2187
+ if (!validDesktopFileName(desktopId))
2188
+ return null;
2189
+ for (const directory of linuxApplicationDirectories(home, environment)) {
2190
+ const candidate = join(directory, desktopId);
2191
+ if (probe.fileExists(candidate) && probe.fileContent?.(candidate))
2192
+ return candidate;
2193
+ }
2194
+ return null;
2195
+ }
2196
+ function validDesktopFileName(fileName) {
2197
+ return /^[A-Za-z0-9][A-Za-z0-9._-]*\.desktop$/.test(fileName)
2198
+ || fileName === 'GitHub Copilot.desktop';
2199
+ }
2200
+ function desktopEntryHasScheme(entry, scheme) {
2201
+ const mimeTypes = entry.get('MimeType')?.split(';').filter(Boolean) ?? [];
2202
+ return mimeTypes.includes(`x-scheme-handler/${scheme}`);
2203
+ }
2204
+ function regularExecutable(path, probe) {
2205
+ const status = probe.fileStatus?.(path);
2206
+ return Boolean(status?.isFile && status.executable && (status.mode & 0o022) === 0);
2207
+ }
2208
+ function expectedElfMachine(architecture) {
2209
+ if (architecture === 'x64')
2210
+ return 62;
2211
+ if (architecture === 'arm64')
2212
+ return 183;
2213
+ return null;
2214
+ }
2215
+ function validElfHeader(header, architecture) {
2216
+ const machine = expectedElfMachine(architecture);
2217
+ return machine !== null
2218
+ && header.length >= 20
2219
+ && header[0] === 0x7f
2220
+ && header[1] === 0x45
2221
+ && header[2] === 0x4c
2222
+ && header[3] === 0x46
2223
+ && header[4] === 2
2224
+ && header[5] === 1
2225
+ && (header[18] | (header[19] << 8)) === machine;
2226
+ }
2227
+ function validAppImageHeader(header, architecture) {
2228
+ const appImageType = header[10];
2229
+ return validElfHeader(header, architecture)
2230
+ && header[8] === 0x41
2231
+ && header[9] === 0x49
2232
+ && (appImageType === 1 || appImageType === 2);
2233
+ }
2234
+ // The current Antigravity 2 Linux distribution is an unsigned tar archive.
2235
+ // Bind the extracted executable to the immutable official archive payload;
2236
+ // legacy apt 1.x packages are intentionally not accepted as Antigravity 2.
2237
+ const VERIFIED_ANTIGRAVITY_LINUX_EXECUTABLES = {
2238
+ arm64: {
2239
+ version: '2.12.2',
2240
+ size: 197_528_864,
2241
+ sha256: 'e706505fdd89003390c256b084ee44625e4ab0aacd068b8f62290af8b0a6f6ed',
2242
+ },
2243
+ x64: {
2244
+ version: '2.12.2',
2245
+ size: 206_036_184,
2246
+ sha256: 'b0d127772d2983a93771055a93b673d5fdd1726d6e47db8e269b204e665972d6',
2247
+ },
2248
+ };
2249
+ function verifiedLinuxAntigravityTarget(surface, architecture, home, environment, probe) {
2250
+ const expected = VERIFIED_ANTIGRAVITY_LINUX_EXECUTABLES[architecture];
2251
+ if (!expected)
2252
+ return null;
2253
+ for (const candidate of candidateApplicationPaths(surface, 'linux', home, environment)) {
2254
+ if (!probe.fileExists(candidate))
215
2255
  continue;
216
- const environment = { ELECTRON_RUN_AS_NODE: '1' };
217
- const builtInCopilot = win32Path.resolve(win32Path.dirname(cliScript), '..', 'extensions', 'copilot');
218
- const installedExtensions = probe.commandOutput(executable, [cliScript, '--list-extensions'], environment)?.toLowerCase() ?? '';
219
- if (!probe.fileExists(builtInCopilot) && !installedExtensions.includes('github.copilot'))
2256
+ const realPath = probe.realPath?.(candidate);
2257
+ const status = realPath ? probe.fileStatus?.(realPath) : null;
2258
+ const header = realPath ? probe.fileHeader?.(realPath, 64) : null;
2259
+ const sha256 = realPath ? probe.fileSha256?.(realPath)?.toLowerCase() : null;
2260
+ if (!realPath
2261
+ || !status?.isFile
2262
+ || !status.executable
2263
+ || (status.mode & 0o022) !== 0
2264
+ || status.size !== expected.size
2265
+ || !header
2266
+ || !validElfHeader(header, architecture)
2267
+ || sha256 !== expected.sha256)
220
2268
  continue;
221
- return { executable, launchArgsPrefix: [cliScript], launchEnvironment: environment };
2269
+ return {
2270
+ executable: realPath,
2271
+ launchArgsPrefix: [],
2272
+ launchEnvironment: {},
2273
+ verification: {
2274
+ kind: 'portable-elf',
2275
+ realPath,
2276
+ size: status.size,
2277
+ sha256,
2278
+ architecture,
2279
+ },
2280
+ };
222
2281
  }
223
2282
  return null;
224
2283
  }
225
- function vscodeHasCopilot(command, probe) {
226
- const extensions = probe.commandOutput(command, ['--list-extensions'])?.toLowerCase() ?? '';
227
- if (extensions.includes('github.copilot'))
228
- return true;
229
- const resolvedCommand = probe.realPath?.(command) ?? command;
230
- const commandDirectory = dirname(resolvedCommand);
231
- return [
232
- resolve(commandDirectory, '..', 'extensions', 'copilot'),
233
- resolve(commandDirectory, '..', 'resources', 'app', 'extensions', 'copilot'),
234
- ].some((candidate) => probe.fileExists(candidate));
235
- }
236
- function registeredUrlSchemeTarget(scheme, platform, probe) {
2284
+ // The vendor's current AppImages have empty AppImage signature sections, so
2285
+ // verify the complete assets against independently recorded SHA-256 digests.
2286
+ // Source: the xAI-linked Cursor updater's stable `sand` channel, 2026-09-06.
2287
+ const VERIFIED_GROK_BOT_APPIMAGES = {
2288
+ '0.43.0': {
2289
+ arm64: {
2290
+ size: 132_756_388,
2291
+ sha256: '798757a576dd4a2f3953344af45197dd4dcf1fba792da850f20733a033ea4236',
2292
+ },
2293
+ x64: {
2294
+ size: 132_012_959,
2295
+ sha256: 'd810e4e1f49da15bc178f54cfc5840c780fb8efe1341d4220898fc4f244ba75b',
2296
+ },
2297
+ },
2298
+ };
2299
+ const VERIFIED_COPILOT_APPIMAGES = {
2300
+ '1.1.15': {
2301
+ arm64: {
2302
+ size: 485_141_000,
2303
+ sha256: '0d02335a7accea8e2f2faf0a6ab76f2ed9f0ed217793f900ed36ae110342f33a',
2304
+ },
2305
+ x64: {
2306
+ size: 535_726_584,
2307
+ sha256: '2f41db0a46b3c1d75d307bb47109440f4aa5faaf7f1b95df9f679ccbc35e2809',
2308
+ },
2309
+ },
2310
+ };
2311
+ function verifiedAppImage(executable, entry, architecture, expectedName, catalog, probe) {
2312
+ const realExecutable = probe.realPath?.(executable);
2313
+ if (!realExecutable?.startsWith('/') || !/\.AppImage$/i.test(realExecutable))
2314
+ return null;
2315
+ if (!regularExecutable(realExecutable, probe))
2316
+ return null;
2317
+ const header = probe.fileHeader?.(realExecutable, 64);
2318
+ if (!header || !validAppImageHeader(header, architecture))
2319
+ return null;
2320
+ const name = decodeDesktopValue(entry.get('Name') ?? '');
2321
+ const version = entry.get('X-AppImage-Version');
2322
+ if (name !== expectedName
2323
+ || entry.get('Type') !== 'Application'
2324
+ || entry.get('Terminal') === 'true'
2325
+ || (version !== undefined && !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(version))) {
2326
+ return null;
2327
+ }
2328
+ const status = probe.fileStatus?.(realExecutable);
2329
+ if (!status)
2330
+ return null;
2331
+ const release = Object.entries(catalog)
2332
+ .map(([releaseVersion, architectures]) => ({ releaseVersion, asset: architectures[architecture] }))
2333
+ .find((candidate) => candidate.asset?.size === status.size);
2334
+ if (!release?.asset || (version !== undefined && version !== release.releaseVersion))
2335
+ return null;
2336
+ const actualDigest = probe.fileSha256?.(realExecutable)?.toLowerCase();
2337
+ if (actualDigest !== release.asset.sha256)
2338
+ return null;
2339
+ const declaredDigest = entry.get('X-AppImage-SHA256');
2340
+ if (declaredDigest !== undefined) {
2341
+ if (!/^[0-9a-f]{64}$/i.test(declaredDigest))
2342
+ return null;
2343
+ if (actualDigest !== declaredDigest.toLowerCase())
2344
+ return null;
2345
+ }
2346
+ return { realPath: realExecutable, size: status.size, sha256: actualDigest };
2347
+ }
2348
+ function verifiedGrokBotAppImage(executable, entry, architecture, probe) {
2349
+ return verifiedAppImage(executable, entry, architecture, 'Grok Bot', VERIFIED_GROK_BOT_APPIMAGES, probe)?.realPath ?? null;
2350
+ }
2351
+ function registeredLinuxCopilotAppImageTarget(home, environment, architecture, probe) {
2352
+ for (const directory of linuxApplicationDirectories(home, environment)) {
2353
+ for (const fileName of probe.directoryEntries?.(directory) ?? []) {
2354
+ if (!validDesktopFileName(fileName))
2355
+ continue;
2356
+ const entry = parseDesktopEntry(probe.fileContent?.(join(directory, fileName)) ?? '');
2357
+ if (!entry || !desktopEntryHasScheme(entry, 'ghapp'))
2358
+ continue;
2359
+ const invocation = parseDesktopExec(entry.get('Exec') ?? '');
2360
+ if (!invocation || invocation.length !== 1 || !invocation[0].startsWith('/'))
2361
+ continue;
2362
+ const verified = verifiedAppImage(invocation[0], entry, architecture, 'GitHub Copilot', VERIFIED_COPILOT_APPIMAGES, probe);
2363
+ if (!verified)
2364
+ continue;
2365
+ return {
2366
+ executable: verified.realPath,
2367
+ launchArgsPrefix: [],
2368
+ launchEnvironment: {},
2369
+ deepLinkScheme: 'ghapp',
2370
+ verification: {
2371
+ kind: 'copilot-appimage',
2372
+ realPath: verified.realPath,
2373
+ size: verified.size,
2374
+ sha256: verified.sha256,
2375
+ architecture,
2376
+ },
2377
+ };
2378
+ }
2379
+ }
2380
+ return null;
2381
+ }
2382
+ function registeredLinuxGrokBotAppImageTarget(home, environment, architecture, probe) {
2383
+ for (const directory of linuxApplicationDirectories(home, environment)) {
2384
+ for (const fileName of probe.directoryEntries?.(directory) ?? []) {
2385
+ if (!validDesktopFileName(fileName))
2386
+ continue;
2387
+ const path = join(directory, fileName);
2388
+ const entry = parseDesktopEntry(probe.fileContent?.(path) ?? '');
2389
+ if (!entry)
2390
+ continue;
2391
+ const invocation = parseDesktopExec(entry.get('Exec') ?? '');
2392
+ if (!invocation || !invocation[0].startsWith('/'))
2393
+ continue;
2394
+ const launchArgsPrefix = invocation.slice(1);
2395
+ if (launchArgsPrefix.length > 1
2396
+ || (launchArgsPrefix.length === 1 && launchArgsPrefix[0] !== '--ozone-platform-hint=auto')) {
2397
+ continue;
2398
+ }
2399
+ const executable = verifiedGrokBotAppImage(invocation[0], entry, architecture, probe);
2400
+ if (!executable)
2401
+ continue;
2402
+ const status = probe.fileStatus?.(executable);
2403
+ const sha256 = probe.fileSha256?.(executable)?.toLowerCase();
2404
+ if (!status || !sha256)
2405
+ continue;
2406
+ return {
2407
+ executable,
2408
+ launchArgsPrefix,
2409
+ launchEnvironment: {},
2410
+ verification: {
2411
+ kind: 'grok-appimage',
2412
+ realPath: executable,
2413
+ size: status.size,
2414
+ sha256,
2415
+ architecture,
2416
+ },
2417
+ };
2418
+ }
2419
+ }
2420
+ return null;
2421
+ }
2422
+ function registeredUrlSchemeTarget(surface, scheme, platform, home, environment, architecture, probe) {
2423
+ if (platform === 'darwin') {
2424
+ const application = candidateApplicationPaths(surface, platform, home, environment).find((candidate) => probe.fileExists(candidate)
2425
+ && hasExpectedApplicationIdentity(surface, candidate, platform, architecture, probe));
2426
+ const executable = application ? probe.realPath?.(application) : null;
2427
+ const verification = application
2428
+ ? applicationLaunchVerification(surface, application, platform, architecture, probe)
2429
+ : null;
2430
+ return executable && verification
2431
+ ? { executable, launchArgsPrefix: [], launchEnvironment: {}, deepLinkScheme: scheme, verification }
2432
+ : null;
2433
+ }
237
2434
  if (platform === 'win32') {
238
- const registered = probe.commandOutput('reg.exe', ['query', `HKCR\\${scheme}\\shell\\open\\command`, '/ve']);
239
- return registered ? { executable: `${scheme}://`, launchArgsPrefix: [], launchEnvironment: {} } : null;
2435
+ const executable = probe.windowsUrlHandlerExecutable?.(scheme);
2436
+ if (!executable || !probe.fileExists(executable))
2437
+ return null;
2438
+ if (!windowsApplicationIdentityMatches(surface, executable, architecture, probe))
2439
+ return null;
2440
+ const realExecutable = probe.realPath?.(executable);
2441
+ const verification = applicationLaunchVerification(surface, executable, platform, architecture, probe);
2442
+ return realExecutable && verification
2443
+ ? { executable: realExecutable, launchArgsPrefix: [], launchEnvironment: {}, deepLinkScheme: scheme, verification }
2444
+ : null;
240
2445
  }
241
2446
  if (platform === 'linux') {
242
- const desktopHandler = probe.commandOutput('xdg-mime', ['query', 'default', `x-scheme-handler/${scheme}`])?.trim();
243
- return desktopHandler ? { executable: `${scheme}://`, launchArgsPrefix: [], launchEnvironment: {} } : null;
2447
+ const desktopId = probe.linuxUrlSchemeDesktopId?.(scheme) ?? '';
2448
+ const path = desktopEntryPath(desktopId, home, environment, probe);
2449
+ const entry = path ? parseDesktopEntry(probe.fileContent?.(path) ?? '') : null;
2450
+ if (!entry || entry.get('Type') !== 'Application' || !desktopEntryHasScheme(entry, scheme))
2451
+ return null;
2452
+ const invocation = parseDesktopExec(entry.get('Exec') ?? '');
2453
+ if (!invocation || invocation.length !== 1)
2454
+ return null;
2455
+ const resolvedExecutable = invocation[0].startsWith('/')
2456
+ ? invocation[0]
2457
+ : probe.commandPath(invocation[0]);
2458
+ if (!resolvedExecutable || !probe.fileExists(resolvedExecutable))
2459
+ return null;
2460
+ const expectedExecutable = surface.id === 'claude-desktop' ? '/usr/bin/claude-desktop' : '/usr/bin/github';
2461
+ if (resolvedExecutable !== expectedExecutable)
2462
+ return null;
2463
+ if (!hasExpectedApplicationIdentity(surface, resolvedExecutable, platform, architecture, probe))
2464
+ return null;
2465
+ const realExecutable = probe.realPath?.(resolvedExecutable);
2466
+ const verification = applicationLaunchVerification(surface, resolvedExecutable, platform, architecture, probe);
2467
+ return realExecutable && verification
2468
+ ? { executable: realExecutable, launchArgsPrefix: [], launchEnvironment: {}, deepLinkScheme: scheme, verification }
2469
+ : null;
244
2470
  }
245
2471
  return null;
246
2472
  }
247
- function findSurfaceTarget(surface, platform, home, probe) {
248
- if (platform === 'win32' && surface.id === 'vscode-copilot') {
249
- return windowsVsCodeTarget(surface, home, probe);
2473
+ const EXPECTED_WINDOWS_APPX_IDENTITIES = {
2474
+ 'claude-desktop': {
2475
+ packageName: 'Claude',
2476
+ publisher: 'CN="Anthropic, PBC", O="Anthropic, PBC", L=San Francisco, S=California, C=US, SERIALNUMBER=4860621, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US',
2477
+ publisherId: 'pzs8sxrjxfjjc',
2478
+ familyName: 'Claude_pzs8sxrjxfjjc',
2479
+ applicationId: 'Claude',
2480
+ signatureKinds: ['Developer'],
2481
+ },
2482
+ 'codex-desktop': {
2483
+ packageName: 'OpenAI.Codex',
2484
+ publisher: 'CN=50BDFD77-8903-4850-9FFE-6E8522F64D5B',
2485
+ publisherId: '2p2nqsd0c76g0',
2486
+ familyName: 'OpenAI.Codex_2p2nqsd0c76g0',
2487
+ applicationId: 'App',
2488
+ signatureKinds: ['Store'],
2489
+ },
2490
+ };
2491
+ function windowsAppxTarget(surface, architecture, probe) {
2492
+ const expected = EXPECTED_WINDOWS_APPX_IDENTITIES[surface.id];
2493
+ if (!expected || (architecture !== 'x64' && architecture !== 'arm64'))
2494
+ return null;
2495
+ for (const packageName of surface.windowsAppxPackageNames ?? []) {
2496
+ const identity = probe.windowsAppxIdentity?.(packageName);
2497
+ if (!identity
2498
+ || identity.packageName !== expected.packageName
2499
+ || !identity.publisher.trim()
2500
+ || (expected.publisher !== undefined && identity.publisher !== expected.publisher)
2501
+ || identity.publisherId !== expected.publisherId
2502
+ || identity.familyName !== expected.familyName
2503
+ || !identity.applicationIds.includes(expected.applicationId)
2504
+ || identity.architecture !== architecture
2505
+ || !expected.signatureKinds.includes(identity.signatureKind)
2506
+ || identity.status !== 'Ok')
2507
+ continue;
2508
+ return {
2509
+ executable: `shell:AppsFolder\\${identity.familyName}!${expected.applicationId}`,
2510
+ launchArgsPrefix: [],
2511
+ launchEnvironment: {},
2512
+ verification: {
2513
+ kind: 'windows-appx',
2514
+ packageName: identity.packageName,
2515
+ publisher: identity.publisher,
2516
+ publisherId: identity.publisherId,
2517
+ familyName: identity.familyName,
2518
+ applicationId: expected.applicationId,
2519
+ architecture: identity.architecture,
2520
+ signatureKind: identity.signatureKind,
2521
+ },
2522
+ };
250
2523
  }
251
- for (const command of surface.commands) {
252
- const found = probe.commandPath(command);
253
- if (found) {
254
- if (surface.id === 'vscode-copilot' && !vscodeHasCopilot(found, probe))
255
- continue;
256
- return { executable: found, launchArgsPrefix: [], launchEnvironment: {} };
257
- }
2524
+ return null;
2525
+ }
2526
+ function findSurfaceTarget(surface, platform, architecture, home, environment, probe) {
2527
+ if (surface.id === 'vscode-copilot') {
2528
+ if (platform === 'darwin')
2529
+ return macVsCodeTarget(surface, architecture, home, environment, probe);
2530
+ if (platform === 'win32')
2531
+ return windowsVsCodeTarget(surface, architecture, home, environment, probe);
2532
+ if (platform === 'linux')
2533
+ return linuxVsCodeTarget(surface, architecture, home, probe);
2534
+ return null;
2535
+ }
2536
+ if (platform === 'linux' && surface.id === 'antigravity-desktop') {
2537
+ const verified = verifiedLinuxAntigravityTarget(surface, architecture, home, environment, probe);
2538
+ if (verified)
2539
+ return verified;
2540
+ }
2541
+ if (platform === 'linux' && surface.id === 'copilot-desktop') {
2542
+ const appImage = registeredLinuxCopilotAppImageTarget(home, environment, architecture, probe);
2543
+ if (appImage)
2544
+ return appImage;
2545
+ }
2546
+ if (platform === 'linux' && surface.id === 'grok-bot') {
2547
+ const appImage = registeredLinuxGrokBotAppImageTarget(home, environment, architecture, probe);
2548
+ if (appImage)
2549
+ return appImage;
2550
+ }
2551
+ if (surface.kind === 'cli') {
2552
+ return authenticatedCliTarget(surface, platform, architecture, home, environment, probe);
2553
+ }
2554
+ let applicationTarget = null;
2555
+ for (const application of candidateApplicationPaths(surface, platform, home, environment)) {
2556
+ if (!probe.fileExists(application))
2557
+ continue;
2558
+ const verification = applicationLaunchVerification(surface, application, platform, architecture, probe);
2559
+ const realApplication = verification ? probe.realPath?.(application) : null;
2560
+ if (!verification || !realApplication)
2561
+ continue;
2562
+ applicationTarget = {
2563
+ executable: realApplication,
2564
+ launchArgsPrefix: [],
2565
+ launchEnvironment: {},
2566
+ verification,
2567
+ };
2568
+ break;
258
2569
  }
2570
+ const appx = platform === 'win32' ? windowsAppxTarget(surface, architecture, probe) : null;
259
2571
  if (surface.id === 'claude-desktop') {
260
- const handler = registeredUrlSchemeTarget('claude', platform, probe);
2572
+ const handler = registeredUrlSchemeTarget(surface, 'claude', platform, home, environment, architecture, probe);
261
2573
  if (handler)
262
2574
  return handler;
263
2575
  }
264
2576
  if (surface.id === 'copilot-desktop') {
265
- const handler = registeredUrlSchemeTarget('ghapp', platform, probe);
2577
+ const handler = registeredUrlSchemeTarget(surface, 'ghapp', platform, home, environment, architecture, probe);
266
2578
  if (handler)
267
2579
  return handler;
268
2580
  }
269
- const executable = candidateApplicationPaths(surface, platform, home).find((path) => probe.fileExists(path));
270
- if (executable)
271
- return { executable, launchArgsPrefix: [], launchEnvironment: {} };
272
- return null;
2581
+ if (appx)
2582
+ return appx;
2583
+ return applicationTarget;
2584
+ }
2585
+ function resolvedLaunchSupport(surface, target, targets, platform) {
2586
+ if (!target)
2587
+ return surface.launchSupport;
2588
+ if (surface.id === 'vscode-copilot') {
2589
+ return target.capabilities?.includes('initial-prompt') ? 'project-and-prompt' : 'project-only';
2590
+ }
2591
+ if (surface.id === 'claude-desktop') {
2592
+ return target.deepLinkScheme === 'claude' ? 'project-and-prompt' : 'manual-project';
2593
+ }
2594
+ if (surface.id === 'copilot-cli'
2595
+ || surface.id === 'antigravity-cli'
2596
+ || surface.id === 'claude-cli'
2597
+ || surface.id === 'codex-cli'
2598
+ || surface.id === 'grok-cli') {
2599
+ return target.capabilities?.includes('initial-prompt') ? 'project-and-prompt' : 'project-only';
2600
+ }
2601
+ if (surface.id === 'copilot-desktop') {
2602
+ return targets.get('copilot-cli')?.capabilities?.includes('desktop-project-launch')
2603
+ ? 'project-only'
2604
+ : 'manual-project';
2605
+ }
2606
+ if (surface.id === 'codex-desktop') {
2607
+ return platform === 'darwin'
2608
+ && targets.get('codex-cli')?.capabilities?.includes('desktop-project-launch')
2609
+ ? 'project-only'
2610
+ : 'manual-project';
2611
+ }
2612
+ return surface.launchSupport;
273
2613
  }
274
2614
  export async function detectAiSurfaces(options = {}) {
275
2615
  const platform = options.platform ?? process.platform;
2616
+ if (options.platform !== undefined
2617
+ && options.platform !== process.platform
2618
+ && options.architecture === undefined) {
2619
+ throw new Error(`An explicit architecture is required when detecting ${options.platform} AI surfaces on ${process.platform}.`);
2620
+ }
2621
+ const architecture = options.architecture ?? process.arch;
276
2622
  const home = options.home ?? homedir();
277
- const probe = options.probe ?? systemSurfaceProbe;
2623
+ const environment = options.environment ?? process.env;
2624
+ const baseProbe = options.probe ?? systemSurfaceProbe;
2625
+ const macIdentityCache = new Map();
2626
+ const probe = platform === 'darwin' && baseProbe.macApplicationIdentity
2627
+ ? {
2628
+ ...baseProbe,
2629
+ macApplicationIdentity(path, expected) {
2630
+ const cacheKey = JSON.stringify([
2631
+ path,
2632
+ expected?.bundleIdentifier ?? '',
2633
+ expected?.teamIdentifier ?? '',
2634
+ expected?.requiredScheme ?? '',
2635
+ ]);
2636
+ if (!macIdentityCache.has(cacheKey)) {
2637
+ macIdentityCache.set(cacheKey, baseProbe.macApplicationIdentity?.(path, expected) ?? null);
2638
+ }
2639
+ return macIdentityCache.get(cacheKey) ?? null;
2640
+ },
2641
+ }
2642
+ : baseProbe;
278
2643
  const preferences = options.preferredSurface === undefined ? await readAiPreferences(home) : { version: 1, lastAiSurface: options.preferredSurface ?? undefined };
279
- const installed = AI_SURFACES.map((surface) => ({
280
- surface,
281
- target: findSurfaceTarget(surface, platform, home, probe),
2644
+ const targets = new Map(AI_SURFACES.map((surface) => {
2645
+ const target = findSurfaceTarget(surface, platform, architecture, home, environment, probe);
2646
+ return [surface.id, target?.verification ? target : null];
282
2647
  }));
2648
+ const installed = AI_SURFACES.map((definition) => {
2649
+ const target = targets.get(definition.id) ?? null;
2650
+ const surface = {
2651
+ ...definition,
2652
+ launchSupport: resolvedLaunchSupport(definition, target, targets, platform),
2653
+ };
2654
+ return { surface, target };
2655
+ });
283
2656
  const availableIds = new Set(installed.filter((item) => item.target).map((item) => item.surface.id));
284
2657
  const preferredSurface = preferences.lastAiSurface && availableIds.has(preferences.lastAiSurface)
285
2658
  ? preferences.lastAiSurface
286
2659
  : null;
2660
+ const launchSupportPriority = {
2661
+ 'project-and-prompt': 4,
2662
+ 'project-only': 3,
2663
+ 'manual-project': 2,
2664
+ 'launch-only': 1,
2665
+ };
2666
+ const bestInstalled = installed
2667
+ .filter((item) => item.target)
2668
+ .reduce((best, item) => {
2669
+ if (!best)
2670
+ return item;
2671
+ return launchSupportPriority[item.surface.launchSupport] > launchSupportPriority[best.surface.launchSupport]
2672
+ ? item
2673
+ : best;
2674
+ }, null);
287
2675
  const recommendedSurface = preferredSurface
288
- ?? installed.find((item) => item.target)?.surface.id
2676
+ ?? bestInstalled?.surface.id
289
2677
  ?? 'vscode-copilot';
290
2678
  return {
291
- contractVersion: 'eai.ai-surfaces/v1',
2679
+ contractVersion: 'eai.ai-surfaces/v2',
292
2680
  platform,
293
2681
  projectDirectory: platform === 'win32'
294
2682
  ? win32Path.resolve(options.projectDirectory ?? process.cwd())
@@ -301,6 +2689,9 @@ export async function detectAiSurfaces(options = {}) {
301
2689
  executable: target?.executable ?? null,
302
2690
  launchArgsPrefix: target?.launchArgsPrefix ?? [],
303
2691
  launchEnvironment: target?.launchEnvironment ?? {},
2692
+ capabilities: target?.capabilities ?? [],
2693
+ ...(target?.deepLinkScheme ? { deepLinkScheme: target.deepLinkScheme } : {}),
2694
+ ...(target?.verification ? { verification: target.verification } : {}),
304
2695
  recommended: surface.id === recommendedSurface,
305
2696
  previouslyUsed: surface.id === preferredSurface,
306
2697
  status: target ? 'ready' : 'not-installed',
@@ -310,6 +2701,185 @@ export async function detectAiSurfaces(options = {}) {
310
2701
  })),
311
2702
  };
312
2703
  }
2704
+ function isLegacyAiSurfaceId(surfaceId) {
2705
+ return surfaceId !== null && LEGACY_AI_SURFACES.some((surface) => surface.id === surfaceId);
2706
+ }
2707
+ function equalStringArrays(left, right) {
2708
+ return left.length === right.length && left.every((value, index) => value === right[index]);
2709
+ }
2710
+ function expectedInventoryLaunchSupport(definition, detected, detectedById, platform) {
2711
+ if (!detected.installed)
2712
+ return definition.launchSupport;
2713
+ if (definition.id === 'vscode-copilot') {
2714
+ return detected.capabilities.includes('initial-prompt') ? 'project-and-prompt' : 'project-only';
2715
+ }
2716
+ if (definition.id === 'claude-desktop') {
2717
+ return detected.deepLinkScheme === 'claude' ? 'project-and-prompt' : 'manual-project';
2718
+ }
2719
+ if (definition.id === 'copilot-cli'
2720
+ || definition.id === 'antigravity-cli'
2721
+ || definition.id === 'claude-cli'
2722
+ || definition.id === 'codex-cli'
2723
+ || definition.id === 'grok-cli') {
2724
+ return detected.capabilities.includes('initial-prompt') ? 'project-and-prompt' : 'project-only';
2725
+ }
2726
+ if (definition.id === 'copilot-desktop') {
2727
+ const cli = detectedById.get('copilot-cli');
2728
+ return cli?.installed && cli.capabilities.includes('desktop-project-launch')
2729
+ ? 'project-only'
2730
+ : 'manual-project';
2731
+ }
2732
+ if (definition.id === 'codex-desktop') {
2733
+ const cli = detectedById.get('codex-cli');
2734
+ return platform === 'darwin'
2735
+ && cli?.installed
2736
+ && cli.capabilities.includes('desktop-project-launch')
2737
+ ? 'project-only'
2738
+ : 'manual-project';
2739
+ }
2740
+ return definition.launchSupport;
2741
+ }
2742
+ function authenticatedSurfaceCapabilities(surface, platform) {
2743
+ return vscodeCatalogVerificationShapeMatches(surface, platform)
2744
+ ? ['initial-prompt']
2745
+ : [];
2746
+ }
2747
+ function canonicalV2InventoryOrThrow(inventory) {
2748
+ if (inventory.contractVersion !== 'eai.ai-surfaces/v2'
2749
+ || inventory.surfaces.length !== V2_AI_SURFACE_IDS.length) {
2750
+ throw new Error('EAI refused to serialize a noncanonical v2 AI surface inventory.');
2751
+ }
2752
+ const detectedById = new Map();
2753
+ for (const [index, detected] of inventory.surfaces.entries()) {
2754
+ const expectedId = V2_AI_SURFACE_IDS[index];
2755
+ const definition = AI_SURFACES[index];
2756
+ if (!definition
2757
+ || detected.id !== expectedId
2758
+ || detectedById.has(detected.id)
2759
+ || detected.name !== definition.name
2760
+ || detected.provider !== definition.provider
2761
+ || detected.kind !== definition.kind
2762
+ || detected.installUrl !== definition.installUrl
2763
+ || !equalStringArrays(detected.commands, definition.commands)) {
2764
+ throw new Error('EAI refused to serialize a tampered or reordered v2 AI surface inventory.');
2765
+ }
2766
+ detectedById.set(detected.id, detected);
2767
+ }
2768
+ const validReference = (surfaceId) => surfaceId === null
2769
+ || detectedById.has(surfaceId);
2770
+ if (!validReference(inventory.preferredSurface)
2771
+ || !validReference(inventory.recommendedSurface)
2772
+ || (inventory.preferredSurface !== null
2773
+ && !detectedById.get(inventory.preferredSurface)?.installed)) {
2774
+ throw new Error('EAI refused to serialize a v2 inventory with a dangling surface reference.');
2775
+ }
2776
+ for (const [index, detected] of inventory.surfaces.entries()) {
2777
+ const definition = AI_SURFACES[index];
2778
+ const authenticatedCapabilities = detected.installed
2779
+ ? authenticatedSurfaceCapabilities(detected, inventory.platform)
2780
+ : [];
2781
+ if (!Array.isArray(detected.capabilities)
2782
+ || new Set(detected.capabilities).size !== detected.capabilities.length
2783
+ || !equalStringArrays(detected.capabilities, authenticatedCapabilities)) {
2784
+ throw new Error(`EAI refused to serialize invalid capabilities for ${detected.id}.`);
2785
+ }
2786
+ const installed = detected.installed === true;
2787
+ if (installed !== Boolean(detected.executable && detected.verification)
2788
+ || detected.status !== (installed ? 'ready' : 'not-installed')
2789
+ || (!installed && (detected.executable !== null
2790
+ || detected.verification !== undefined
2791
+ || detected.capabilities.length !== 0))
2792
+ || detected.recommended !== (detected.id === inventory.recommendedSurface)
2793
+ || detected.previouslyUsed !== (detected.id === inventory.preferredSurface)
2794
+ || detected.nextAction !== (installed
2795
+ ? `Start EAI in ${definition.name}`
2796
+ : `Get ${definition.name} from ${definition.provider}`)
2797
+ || detected.launchSupport !== expectedInventoryLaunchSupport(definition, detected, detectedById, inventory.platform)) {
2798
+ throw new Error(`EAI refused to serialize inconsistent v2 state for ${detected.id}.`);
2799
+ }
2800
+ if (installed
2801
+ && detected.id === 'vscode-copilot'
2802
+ && ((inventory.platform === 'win32' || inventory.platform === 'linux')
2803
+ ? (detected.verification?.kind !== 'vscode-catalog'
2804
+ || !vscodeCatalogVerificationShapeMatches(detected, inventory.platform))
2805
+ : inventory.platform === 'darwin'
2806
+ ? detected.verification?.kind !== 'mac-application'
2807
+ : true)) {
2808
+ throw new Error('EAI refused to serialize unauthenticated VS Code catalog evidence.');
2809
+ }
2810
+ }
2811
+ return inventory.surfaces;
2812
+ }
2813
+ export function serializeAiSurfaceInventory(inventory, contractVersion) {
2814
+ if (contractVersion === 'v2') {
2815
+ const canonicalDetected = canonicalV2InventoryOrThrow(inventory);
2816
+ return {
2817
+ contractVersion: 'eai.ai-surfaces/v2',
2818
+ platform: inventory.platform,
2819
+ projectDirectory: inventory.projectDirectory,
2820
+ preferredSurface: inventory.preferredSurface,
2821
+ recommendedSurface: inventory.recommendedSurface,
2822
+ surfaces: AI_SURFACES.map((definition, index) => {
2823
+ const companionCli = AI_SURFACE_COMPANION_CLIS[definition.id];
2824
+ const companion = companionCli
2825
+ ? canonicalDetected.find((surface) => surface.id === companionCli)
2826
+ : undefined;
2827
+ return {
2828
+ id: definition.id,
2829
+ name: definition.name,
2830
+ provider: definition.provider,
2831
+ kind: definition.kind,
2832
+ installUrl: definition.installUrl,
2833
+ launchSupport: canonicalDetected[index].launchSupport,
2834
+ capabilities: [...canonicalDetected[index].capabilities],
2835
+ installed: canonicalDetected[index].installed,
2836
+ recommended: canonicalDetected[index].recommended,
2837
+ previouslyUsed: canonicalDetected[index].previouslyUsed,
2838
+ status: canonicalDetected[index].status,
2839
+ nextAction: canonicalDetected[index].nextAction,
2840
+ ...(companionCli && companion ? {
2841
+ companionCli,
2842
+ companionCliInstalled: companion.installed,
2843
+ companionCliStatus: companion.installed ? 'ready' : 'not-installed',
2844
+ companionCliError: null,
2845
+ } : {}),
2846
+ };
2847
+ }),
2848
+ };
2849
+ }
2850
+ const detectedById = new Map(inventory.surfaces.map((surface) => [surface.id, surface]));
2851
+ const preferredSurface = isLegacyAiSurfaceId(inventory.preferredSurface)
2852
+ && detectedById.get(inventory.preferredSurface)?.installed
2853
+ ? inventory.preferredSurface
2854
+ : null;
2855
+ const recommendedSurface = preferredSurface
2856
+ ?? LEGACY_AI_SURFACES.find((surface) => detectedById.get(surface.id)?.installed)?.id
2857
+ ?? 'vscode-copilot';
2858
+ return {
2859
+ contractVersion: 'eai.ai-surfaces/v1',
2860
+ platform: inventory.platform,
2861
+ projectDirectory: inventory.projectDirectory,
2862
+ preferredSurface,
2863
+ recommendedSurface,
2864
+ surfaces: LEGACY_AI_SURFACES.map((definition) => {
2865
+ const detected = detectedById.get(definition.id);
2866
+ const installed = Boolean(detected?.installed);
2867
+ return {
2868
+ ...definition,
2869
+ installed,
2870
+ executable: detected?.executable ?? null,
2871
+ launchArgsPrefix: detected ? [...detected.launchArgsPrefix] : [],
2872
+ launchEnvironment: detected ? { ...detected.launchEnvironment } : {},
2873
+ recommended: definition.id === recommendedSurface,
2874
+ previouslyUsed: definition.id === preferredSurface,
2875
+ status: installed ? 'ready' : 'not-installed',
2876
+ nextAction: installed
2877
+ ? `Start EAI in ${definition.name}`
2878
+ : `Get ${definition.name} from ${definition.provider}`,
2879
+ };
2880
+ }),
2881
+ };
2882
+ }
313
2883
  function surfaceOrThrow(inventory, surfaceId) {
314
2884
  const surface = inventory.surfaces.find((candidate) => candidate.id === surfaceId);
315
2885
  if (!surface)
@@ -321,29 +2891,78 @@ export function buildAiLaunchPlan(inventory, surfaceId) {
321
2891
  if (!surface.installed || !surface.executable) {
322
2892
  throw new Error(`${surface.name} is not installed. Open ${surface.installUrl} to get it.`);
323
2893
  }
2894
+ if (!surface.verification) {
2895
+ throw new Error(`EAI refused to launch ${surface.name} without authenticated launch evidence.`);
2896
+ }
2897
+ if (!equalStringArrays(surface.capabilities, authenticatedSurfaceCapabilities(surface, inventory.platform))) {
2898
+ throw new Error(`EAI refused unproven optional launch capabilities for ${surface.name}.`);
2899
+ }
324
2900
  const project = inventory.projectDirectory;
325
2901
  const common = {
326
2902
  surfaceId,
327
2903
  surfaceName: surface.name,
328
2904
  projectDirectory: project,
329
2905
  cwd: project,
2906
+ verification: surface.verification,
330
2907
  };
331
2908
  switch (surfaceId) {
332
2909
  case 'vscode-copilot':
333
- return { ...common, mode: 'process', command: surface.executable, args: [...surface.launchArgsPrefix, 'chat', '-m', 'agent', EAI_FIRST_PROMPT], environment: surface.launchEnvironment, preparedPrompt: true, userMessage: 'VS Code will open this project and start an EAI Copilot chat.' };
2910
+ if (surface.verification.kind === 'vscode-catalog') {
2911
+ if (!vscodeCatalogVerificationShapeMatches(surface, inventory.platform)) {
2912
+ throw new Error('EAI refused unauthenticated VS Code catalog evidence.');
2913
+ }
2914
+ return {
2915
+ ...common,
2916
+ mode: 'process',
2917
+ command: surface.verification.realPath,
2918
+ args: [surface.verification.cliPath, 'chat', '-m', 'agent', EAI_FIRST_PROMPT],
2919
+ environment: { ...VSCODE_CATALOG_LAUNCH_ENVIRONMENT },
2920
+ preparedPrompt: true,
2921
+ userMessage: 'VS Code will open this project and start an EAI Copilot chat.',
2922
+ };
2923
+ }
2924
+ if (inventory.platform !== 'darwin' || surface.verification.kind !== 'mac-application') {
2925
+ throw new Error('EAI refused unauthenticated VS Code launch evidence.');
2926
+ }
2927
+ return {
2928
+ ...common,
2929
+ mode: 'process',
2930
+ command: surface.executable,
2931
+ args: [project],
2932
+ environment: {},
2933
+ preparedPrompt: false,
2934
+ userMessage: 'VS Code will open this project. Open Copilot Chat and ask it to use the repository EAI skill.',
2935
+ };
334
2936
  case 'copilot-cli':
335
- return { ...common, mode: 'terminal', command: surface.executable, args: ['-C', project, '-i', EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive EAI Copilot session.' };
336
- case 'copilot-desktop':
337
- return surface.executable === 'ghapp://'
338
- ? { ...common, mode: 'url', command: 'ghapp://recent', args: [], preparedPrompt: false, userMessage: 'GitHub Copilot will open. Choose this project folder once, then ask it to use the repository EAI skill.' }
339
- : { ...common, mode: 'application', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'GitHub Copilot will open. Choose this project folder once, then ask it to use the repository EAI skill.' };
2937
+ return surface.capabilities.includes('initial-prompt')
2938
+ ? { ...common, mode: 'terminal', command: surface.executable, args: ['-C', project, '-i', EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive EAI Copilot session.' }
2939
+ : { ...common, mode: 'terminal', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'A terminal will open GitHub Copilot CLI in this project. Ask it to use the repository EAI skill.' };
2940
+ case 'copilot-desktop': {
2941
+ const copilotCli = inventory.surfaces.find((candidate) => candidate.id === 'copilot-cli');
2942
+ if (copilotCli?.installed
2943
+ && copilotCli.executable
2944
+ && copilotCli.capabilities.includes('desktop-project-launch')) {
2945
+ if (!copilotCli.verification)
2946
+ throw new Error('EAI refused to delegate to an unverified GitHub Copilot CLI.');
2947
+ return { ...common, verification: copilotCli.verification, mode: 'process', command: copilotCli.executable, args: ['app'], preparedPrompt: false, userMessage: 'GitHub Copilot will open a new desktop session in this project. Ask it to use the repository EAI skill.' };
2948
+ }
2949
+ return surface.deepLinkScheme === 'ghapp'
2950
+ ? { ...common, mode: 'application', command: surface.executable, args: ['ghapp://recent'], preparedPrompt: false, userMessage: 'GitHub Copilot will open. Choose this project folder once, then ask it to use the repository EAI skill.', ...(surface.verification ? { verification: surface.verification } : {}) }
2951
+ : { ...common, mode: 'application', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'GitHub Copilot will open. Choose this project folder once, then ask it to use the repository EAI skill.', ...(surface.verification ? { verification: surface.verification } : {}) };
2952
+ }
2953
+ case 'antigravity-desktop':
2954
+ return { ...common, mode: 'application', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'Google Antigravity will open. Add this project folder, create or select its project, then ask it to use the repository EAI skill.', ...(surface.verification ? { verification: surface.verification } : {}) };
2955
+ case 'antigravity-cli':
2956
+ return surface.capabilities.includes('initial-prompt')
2957
+ ? { ...common, mode: 'terminal', command: surface.executable, args: ['-i', EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive Antigravity EAI session in this project.' }
2958
+ : { ...common, mode: 'terminal', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'A terminal will open Antigravity CLI in this project. Ask it to use the repository EAI skill.' };
340
2959
  case 'claude-desktop':
341
- return surface.executable === 'claude://'
2960
+ return surface.deepLinkScheme === 'claude'
342
2961
  ? {
343
2962
  ...common,
344
- mode: 'url',
345
- command: `claude://code/new?q=${encodeURIComponent(EAI_FIRST_PROMPT)}&folder=${encodeURIComponent(project)}`,
346
- args: [],
2963
+ mode: 'application',
2964
+ command: surface.executable,
2965
+ args: [`claude://code/new?q=${encodeURIComponent(EAI_FIRST_PROMPT)}&folder=${encodeURIComponent(project)}`],
347
2966
  preparedPrompt: true,
348
2967
  userMessage: 'Claude Desktop will open a Code session for this project with the EAI starting prompt ready to review.',
349
2968
  }
@@ -356,17 +2975,32 @@ export function buildAiLaunchPlan(inventory, surfaceId) {
356
2975
  userMessage: 'Claude Desktop will open. Choose this project folder, start a Code session, then ask it to use the repository EAI skill.',
357
2976
  };
358
2977
  case 'claude-cli':
359
- return { ...common, mode: 'terminal', command: surface.executable, args: [EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive Claude EAI session.' };
2978
+ return surface.capabilities.includes('initial-prompt')
2979
+ ? { ...common, mode: 'terminal', command: surface.executable, args: [EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive Claude EAI session.' }
2980
+ : { ...common, mode: 'terminal', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'A terminal will open Claude Code in this project. Ask it to use the repository EAI skill.' };
360
2981
  case 'codex-desktop': {
361
2982
  const codexCli = inventory.surfaces.find((candidate) => candidate.id === 'codex-cli');
362
- return codexCli?.installed && codexCli.executable
363
- ? { ...common, mode: 'process', command: codexCli.executable, args: ['app', project], preparedPrompt: false, userMessage: 'ChatGPT Desktop will open this project in Codex. Ask it to use the repository EAI skill.' }
2983
+ return inventory.platform === 'darwin'
2984
+ && codexCli?.installed
2985
+ && codexCli.executable
2986
+ && codexCli.capabilities.includes('desktop-project-launch')
2987
+ ? (() => {
2988
+ if (!codexCli.verification)
2989
+ throw new Error('EAI refused to delegate to an unverified Codex CLI.');
2990
+ return { ...common, verification: codexCli.verification, mode: 'process', command: codexCli.executable, args: ['app', project], preparedPrompt: false, userMessage: 'ChatGPT Desktop will open this project in Codex. Ask it to use the repository EAI skill.' };
2991
+ })()
364
2992
  : { ...common, mode: 'application', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'ChatGPT Desktop will open. Choose this project folder, select Codex, then ask it to use the repository EAI skill.' };
365
2993
  }
366
2994
  case 'codex-cli':
367
- return { ...common, mode: 'terminal', command: surface.executable, args: [EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive Codex EAI session.' };
2995
+ return surface.capabilities.includes('initial-prompt')
2996
+ ? { ...common, mode: 'terminal', command: surface.executable, args: [EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive Codex EAI session.' }
2997
+ : { ...common, mode: 'terminal', command: surface.executable, args: [], preparedPrompt: false, userMessage: 'A terminal will open Codex CLI in this project. Ask it to use the repository EAI skill.' };
2998
+ case 'grok-bot':
2999
+ return { ...common, mode: 'application', command: surface.executable, args: [...surface.launchArgsPrefix], preparedPrompt: false, userMessage: 'Grok Bot will open. It manages cloud Bots and does not automatically open this local project; use Grok Build for a local coding workspace.', ...(surface.verification ? { verification: surface.verification } : {}) };
368
3000
  case 'grok-cli':
369
- return { ...common, mode: 'terminal', command: surface.executable, args: ['--cwd', project, '-p', EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open a Grok EAI session for this project.' };
3001
+ return surface.capabilities.includes('initial-prompt')
3002
+ ? { ...common, mode: 'terminal', command: surface.executable, args: ['--cwd', project, EAI_FIRST_PROMPT], preparedPrompt: true, userMessage: 'A terminal will open an interactive Grok Build EAI session for this project.' }
3003
+ : { ...common, mode: 'terminal', command: surface.executable, args: ['--cwd', project], preparedPrompt: false, userMessage: 'A terminal will open Grok Build in this project. Ask it to use the repository EAI skill.' };
370
3004
  }
371
3005
  }
372
3006
  function shellQuote(value, platform) {
@@ -374,14 +3008,30 @@ function shellQuote(value, platform) {
374
3008
  return `"${value.replace(/"/g, '""')}"`;
375
3009
  return `'${value.replace(/'/g, `'"'"'`)}'`;
376
3010
  }
377
- function spawnDetached(command, args, cwd, environment) {
3011
+ function sanitizedVsCodeRuntimeEnvironment(platform, ambient = process.env) {
3012
+ const sanitized = {};
3013
+ for (const [key, value] of Object.entries(ambient)) {
3014
+ const normalized = key.toUpperCase();
3015
+ if (value === undefined
3016
+ || normalized.startsWith('NODE_')
3017
+ || normalized.startsWith('ELECTRON_')
3018
+ || normalized.startsWith('VSCODE_')
3019
+ || (platform === 'linux'
3020
+ && (normalized.startsWith('LD_') || normalized.startsWith('DYLD_'))))
3021
+ continue;
3022
+ sanitized[key] = value;
3023
+ }
3024
+ return { ...sanitized, ...VSCODE_CATALOG_LAUNCH_ENVIRONMENT };
3025
+ }
3026
+ function spawnDetached(command, args, cwd, environment, windowsVerbatimArguments, windowsHide = true, replaceEnvironment = false) {
378
3027
  return new Promise((resolvePromise, rejectPromise) => {
379
3028
  const child = spawn(command, [...args], {
380
3029
  cwd,
381
3030
  detached: true,
382
3031
  stdio: 'ignore',
383
- windowsHide: true,
384
- env: { ...process.env, ...environment },
3032
+ windowsHide,
3033
+ env: replaceEnvironment ? { ...environment } : { ...process.env, ...environment },
3034
+ ...(windowsVerbatimArguments ? { windowsVerbatimArguments } : {}),
385
3035
  });
386
3036
  child.once('spawn', () => {
387
3037
  child.unref();
@@ -390,55 +3040,417 @@ function spawnDetached(command, args, cwd, environment) {
390
3040
  child.once('error', rejectPromise);
391
3041
  });
392
3042
  }
3043
+ async function spawnWindowsCommand(command, args, action, cwd, environment, windowsHide = true) {
3044
+ const runtimeEnvironment = { ...process.env, ...environment };
3045
+ const invocation = buildWindowsCommandInvocation(command, args, action, runtimeEnvironment);
3046
+ await spawnDetached(invocation.command, invocation.args, cwd, environment, invocation.windowsVerbatimArguments, windowsHide);
3047
+ }
393
3048
  export async function openExternalUrl(url, platform = process.platform) {
394
3049
  if (!/^https:\/\//.test(url)) {
395
3050
  throw new Error('EAI refused to open a non-HTTPS provider location.');
396
3051
  }
397
3052
  if (platform === 'darwin')
398
- await spawnDetached('open', [url]);
3053
+ await spawnDetached('/usr/bin/open', [url]);
399
3054
  else if (platform === 'win32')
400
- await spawnDetached('rundll32.exe', ['url.dll,FileProtocolHandler', url]);
3055
+ await spawnDetached('C:\\Windows\\System32\\rundll32.exe', ['url.dll,FileProtocolHandler', url]);
401
3056
  else
402
- await spawnDetached('xdg-open', [url]);
3057
+ await spawnDetached('/usr/bin/xdg-open', [url]);
3058
+ }
3059
+ function trustedSurfaceUrlIsSupported(url) {
3060
+ return /^(?:claude:\/\/code\/new(?:[?#]|$)|ghapp:\/\/recent(?:[?#]|$))/.test(url);
403
3061
  }
404
3062
  async function openTrustedSurfaceUrl(url, platform) {
405
- if (!/^(?:claude:\/\/code\/new(?:[?#]|$)|ghapp:\/\/recent(?:[?#]|$))/.test(url)) {
3063
+ if (!trustedSurfaceUrlIsSupported(url)) {
406
3064
  throw new Error('EAI refused to open an unsupported AI workspace location.');
407
3065
  }
408
3066
  if (platform === 'darwin')
409
- await spawnDetached('open', [url]);
3067
+ await spawnDetached('/usr/bin/open', [url]);
410
3068
  else if (platform === 'win32')
411
- await spawnDetached('rundll32.exe', ['url.dll,FileProtocolHandler', url]);
3069
+ await spawnDetached('C:\\Windows\\System32\\rundll32.exe', ['url.dll,FileProtocolHandler', url]);
412
3070
  else
413
- await spawnDetached('xdg-open', [url]);
3071
+ await spawnDetached('/usr/bin/xdg-open', [url]);
3072
+ }
3073
+ export function buildLinuxTerminalInvocation(plan, commandPath = defaultCommandPath) {
3074
+ const candidates = [
3075
+ { name: 'xdg-terminal-exec', trustedRealPaths: ['/usr/bin/xdg-terminal-exec'], args: () => ['--', plan.command, ...plan.args] },
3076
+ { name: 'kgx', trustedRealPaths: ['/usr/bin/kgx'], args: () => [`--working-directory=${plan.cwd}`, '--', plan.command, ...plan.args] },
3077
+ { name: 'gnome-terminal', trustedRealPaths: ['/usr/bin/gnome-terminal'], args: () => [`--working-directory=${plan.cwd}`, '--', plan.command, ...plan.args] },
3078
+ { name: 'konsole', trustedRealPaths: ['/usr/bin/konsole'], args: () => ['--workdir', plan.cwd, '-e', plan.command, ...plan.args] },
3079
+ { name: 'kitty', trustedRealPaths: ['/usr/bin/kitty'], args: () => ['--directory', plan.cwd, '--', plan.command, ...plan.args] },
3080
+ { name: 'wezterm', trustedRealPaths: ['/usr/bin/wezterm'], args: () => ['start', '--cwd', plan.cwd, '--', plan.command, ...plan.args] },
3081
+ { name: 'alacritty', trustedRealPaths: ['/usr/bin/alacritty'], args: () => ['--working-directory', plan.cwd, '-e', plan.command, ...plan.args] },
3082
+ { name: 'xfce4-terminal', trustedRealPaths: ['/usr/bin/xfce4-terminal'], args: () => ['--working-directory', plan.cwd, '--execute', plan.command, ...plan.args] },
3083
+ { name: 'mate-terminal', trustedRealPaths: ['/usr/bin/mate-terminal'], args: () => [`--working-directory=${plan.cwd}`, '--', plan.command, ...plan.args] },
3084
+ { name: 'foot', trustedRealPaths: ['/usr/bin/foot'], args: () => [`--working-directory=${plan.cwd}`, '--', plan.command, ...plan.args] },
3085
+ {
3086
+ name: 'x-terminal-emulator',
3087
+ trustedRealPaths: [
3088
+ '/usr/bin/gnome-terminal', '/usr/bin/konsole', '/usr/bin/xfce4-terminal',
3089
+ '/usr/bin/mate-terminal', '/usr/bin/xterm',
3090
+ ],
3091
+ args: () => ['-e', plan.command, ...plan.args],
3092
+ },
3093
+ { name: 'xterm', trustedRealPaths: ['/usr/bin/xterm'], args: () => ['-e', plan.command, ...plan.args] },
3094
+ ];
3095
+ for (const candidate of candidates) {
3096
+ const executable = commandPath(candidate.name);
3097
+ if (executable
3098
+ && candidate.trustedRealPaths.includes(executable)
3099
+ && (commandPath !== defaultCommandPath || rootOwnedProtectedExecutable(executable))) {
3100
+ return { command: executable, args: candidate.args() };
3101
+ }
3102
+ }
3103
+ throw new Error('EAI could not find a supported Linux terminal emulator. Install GNOME Console, GNOME Terminal, Konsole, Kitty, WezTerm, Alacritty, Xfce Terminal, MATE Terminal, foot, xterm, or xdg-terminal-exec and retry.');
3104
+ }
3105
+ function unchangedFileBinding(expected, path, platform, probe) {
3106
+ const actual = launchFileBinding(path, probe, platform);
3107
+ return Boolean(actual
3108
+ && actual.realPath === expected.realPath
3109
+ && actual.size === expected.size
3110
+ && actual.sha256 === expected.sha256
3111
+ && (expected.device === undefined || actual.device === expected.device)
3112
+ && (expected.inode === undefined || actual.inode === expected.inode)
3113
+ && (expected.modifiedTimeMs === undefined || actual.modifiedTimeMs === expected.modifiedTimeMs));
3114
+ }
3115
+ function unchangedCatalogFileBinding(expected, path, catalog, platform, probe) {
3116
+ const actual = catalogFileBinding(path, catalog, platform, probe);
3117
+ return Boolean(actual
3118
+ && sameCatalogPath(actual.realPath, expected.realPath, platform)
3119
+ && actual.size === expected.size
3120
+ && actual.sha256 === expected.sha256
3121
+ && (expected.device === undefined || actual.device === expected.device)
3122
+ && (expected.inode === undefined || actual.inode === expected.inode)
3123
+ && (expected.modifiedTimeMs === undefined || actual.modifiedTimeMs === expected.modifiedTimeMs));
3124
+ }
3125
+ function launchArtifactStillAuthenticated(plan, platform, probe) {
3126
+ const verification = plan.verification;
3127
+ if (!verification)
3128
+ return false;
3129
+ const surface = AI_SURFACE_DEFINITIONS_BY_ID.get(plan.surfaceId);
3130
+ if (!surface)
3131
+ return false;
3132
+ if (verification.kind === 'copilot-appimage'
3133
+ || verification.kind === 'grok-appimage'
3134
+ || verification.kind === 'portable-elf') {
3135
+ if (platform !== 'linux'
3136
+ || !unchangedFileBinding(verification, plan.command, platform, probe))
3137
+ return false;
3138
+ const header = probe.fileHeader?.(verification.realPath, 64);
3139
+ const appImageCatalog = verification.kind === 'grok-appimage'
3140
+ ? VERIFIED_GROK_BOT_APPIMAGES
3141
+ : verification.kind === 'copilot-appimage'
3142
+ ? VERIFIED_COPILOT_APPIMAGES
3143
+ : null;
3144
+ const knownArtifact = appImageCatalog
3145
+ ? Object.values(appImageCatalog).some((architectures) => {
3146
+ const artifact = architectures[verification.architecture];
3147
+ return artifact?.size === verification.size && artifact.sha256 === verification.sha256;
3148
+ })
3149
+ : (() => {
3150
+ const artifact = VERIFIED_ANTIGRAVITY_LINUX_EXECUTABLES[verification.architecture];
3151
+ return artifact?.size === verification.size && artifact.sha256 === verification.sha256;
3152
+ })();
3153
+ const expectedSurface = verification.kind === 'grok-appimage'
3154
+ ? plan.surfaceId === 'grok-bot'
3155
+ : verification.kind === 'copilot-appimage'
3156
+ ? plan.surfaceId === 'copilot-desktop'
3157
+ : plan.surfaceId === 'antigravity-desktop';
3158
+ return Boolean(knownArtifact
3159
+ && expectedSurface
3160
+ && header
3161
+ && (appImageCatalog
3162
+ ? validAppImageHeader(header, verification.architecture)
3163
+ : validElfHeader(header, verification.architecture)));
3164
+ }
3165
+ if (verification.kind === 'mac-executable') {
3166
+ if (platform !== 'darwin'
3167
+ || !unchangedFileBinding(verification, plan.command, platform, probe))
3168
+ return false;
3169
+ const expectedSurfaceId = plan.surfaceId === 'copilot-desktop'
3170
+ ? 'copilot-cli'
3171
+ : plan.surfaceId === 'codex-desktop'
3172
+ ? 'codex-cli'
3173
+ : plan.surfaceId;
3174
+ const expected = EXPECTED_MAC_EXECUTABLE_IDENTITIES[expectedSurfaceId];
3175
+ const identity = probe.macExecutableIdentity?.(verification.realPath, expected);
3176
+ return Boolean(expected
3177
+ && identity
3178
+ && verification.identifier === expected.identifier
3179
+ && verification.teamIdentifier === expected.teamIdentifier
3180
+ && identity.identifier === expected.identifier
3181
+ && identity.teamIdentifier === expected.teamIdentifier
3182
+ && identity.architectures.includes(verification.architecture));
3183
+ }
3184
+ if (verification.kind === 'mac-application') {
3185
+ if (platform !== 'darwin')
3186
+ return false;
3187
+ const expected = EXPECTED_MAC_APPLICATION_IDENTITIES[plan.surfaceId];
3188
+ if (!expected
3189
+ || verification.bundleIdentifier !== expected.bundleIdentifier
3190
+ || verification.teamIdentifier !== expected.teamIdentifier
3191
+ || verification.requiredScheme !== expected.requiredScheme
3192
+ || probe.realPath?.(verification.realPath) !== verification.realPath)
3193
+ return false;
3194
+ const identity = probe.macApplicationIdentity?.(verification.realPath, expected);
3195
+ if (!identity
3196
+ || identity.bundleIdentifier !== expected.bundleIdentifier
3197
+ || identity.teamIdentifier !== expected.teamIdentifier
3198
+ || identity.executablePath !== verification.executablePath
3199
+ || !identity.architectures.includes(verification.architecture)
3200
+ || (expected.requiredScheme !== undefined
3201
+ && !identity.urlSchemes.includes(expected.requiredScheme)))
3202
+ return false;
3203
+ const executableBinding = {
3204
+ realPath: verification.executablePath,
3205
+ size: verification.executableSize,
3206
+ sha256: verification.executableSha256,
3207
+ ...(verification.executableDevice === undefined ? {} : { device: verification.executableDevice }),
3208
+ ...(verification.executableInode === undefined ? {} : { inode: verification.executableInode }),
3209
+ ...(verification.executableModifiedTimeMs === undefined
3210
+ ? {}
3211
+ : { modifiedTimeMs: verification.executableModifiedTimeMs }),
3212
+ };
3213
+ if (!unchangedFileBinding(executableBinding, verification.executablePath, platform, probe))
3214
+ return false;
3215
+ if (verification.commandRealPath !== undefined) {
3216
+ if (verification.commandSize === undefined || verification.commandSha256 === undefined)
3217
+ return false;
3218
+ const commandBinding = {
3219
+ realPath: verification.commandRealPath,
3220
+ size: verification.commandSize,
3221
+ sha256: verification.commandSha256,
3222
+ ...(verification.commandDevice === undefined ? {} : { device: verification.commandDevice }),
3223
+ ...(verification.commandInode === undefined ? {} : { inode: verification.commandInode }),
3224
+ ...(verification.commandModifiedTimeMs === undefined
3225
+ ? {}
3226
+ : { modifiedTimeMs: verification.commandModifiedTimeMs }),
3227
+ };
3228
+ return unchangedFileBinding(commandBinding, plan.command, platform, probe);
3229
+ }
3230
+ return probe.realPath?.(plan.command) === verification.realPath;
3231
+ }
3232
+ if (verification.kind === 'vscode-catalog') {
3233
+ if (plan.surfaceId !== 'vscode-copilot'
3234
+ || plan.surfaceName !== surface.name
3235
+ || plan.mode !== 'process'
3236
+ || plan.cwd !== plan.projectDirectory
3237
+ || plan.preparedPrompt !== true
3238
+ || !sameCatalogPath(plan.command, verification.realPath, verification.platform)
3239
+ || !exactStringRecord(plan.environment ?? {}, VSCODE_CATALOG_LAUNCH_ENVIRONMENT)
3240
+ || !equalStringArrays(plan.args, [verification.cliPath, 'chat', '-m', 'agent', EAI_FIRST_PROMPT])
3241
+ || platform !== verification.platform
3242
+ || (verification.architecture !== 'x64' && verification.architecture !== 'arm64')
3243
+ || verification.version !== VSCODE_CATALOG_VERSION
3244
+ || verification.commit !== VSCODE_CATALOG_COMMIT)
3245
+ return false;
3246
+ const installationRoot = verification.platform === 'win32'
3247
+ ? win32Path.dirname(verification.realPath)
3248
+ : '/usr/share/code';
3249
+ if (verification.platform === 'win32'
3250
+ && !probe.windowsVsCodeInstallationAcl?.(installationRoot))
3251
+ return false;
3252
+ const expected = vsCodeCatalog(verification.platform, verification.architecture);
3253
+ if (!expected
3254
+ || verification.catalogArtifactSha256 !== expected.artifactSha256
3255
+ || verification.size !== expected.executable.size
3256
+ || verification.sha256 !== expected.executable.sha256
3257
+ || !unchangedFileBinding(verification, plan.command, platform, probe))
3258
+ return false;
3259
+ const applicationRoot = verification.platform === 'win32'
3260
+ ? win32Path.join(installationRoot, VSCODE_CATALOG_COMMIT_DIRECTORY)
3261
+ : installationRoot;
3262
+ const resourceRoot = verification.platform === 'win32'
3263
+ ? win32Path.join(applicationRoot, 'resources', 'app')
3264
+ : join(applicationRoot, 'resources', 'app');
3265
+ const expectedCliPath = verification.platform === 'win32'
3266
+ ? win32Path.join(resourceRoot, 'out', 'cli.js')
3267
+ : join(resourceRoot, 'out', 'cli.js');
3268
+ if (!sameCatalogPath(verification.installationRoot, installationRoot, verification.platform)
3269
+ || !sameCatalogPath(verification.cliPath, expectedCliPath, verification.platform)
3270
+ || !catalogTreeShapeMatches(verification.applicationTree, applicationRoot, expected.applicationTree, verification.platform)
3271
+ || !vscodeCatalogMetadataMatches(resourceRoot, expected, verification.platform, probe))
3272
+ return false;
3273
+ const applicationTree = vscodeCatalogTreeMatches(applicationRoot, expected.applicationTree, verification.platform, probe);
3274
+ if (!applicationTree
3275
+ || !sameCatalogPath(applicationTree.realPath, verification.applicationTree.realPath, verification.platform)
3276
+ || applicationTree.fileCount !== verification.applicationTree.fileCount
3277
+ || applicationTree.totalBytes !== verification.applicationTree.totalBytes
3278
+ || applicationTree.sha256 !== verification.applicationTree.sha256)
3279
+ return false;
3280
+ if (verification.platform === 'win32') {
3281
+ const auxiliaryRoot = win32Path.join(installationRoot, 'bin');
3282
+ const auxiliaryTree = expected.auxiliaryTree
3283
+ ? vscodeCatalogTreeMatches(auxiliaryRoot, expected.auxiliaryTree, 'win32', probe)
3284
+ : null;
3285
+ const visualManifestPath = win32Path.join(installationRoot, 'Code.VisualElementsManifest.xml');
3286
+ if (!windowsVsCodeInstallationRootMatches(installationRoot, probe)
3287
+ || !auxiliaryTree
3288
+ || !catalogTreeShapeMatches(verification.auxiliaryTree, auxiliaryRoot, expected.auxiliaryTree, 'win32')
3289
+ || auxiliaryTree.fileCount !== verification.auxiliaryTree?.fileCount
3290
+ || auxiliaryTree.totalBytes !== verification.auxiliaryTree.totalBytes
3291
+ || auxiliaryTree.sha256 !== verification.auxiliaryTree.sha256
3292
+ || !expected.visualManifest
3293
+ || !verification.visualManifest
3294
+ || !unchangedCatalogFileBinding(verification.visualManifest, visualManifestPath, expected.visualManifest, 'win32', probe))
3295
+ return false;
3296
+ const identity = probe.windowsExecutableIdentity?.(verification.realPath);
3297
+ return Boolean(identity
3298
+ && windowsApplicationIdentityValueMatches(surface, identity, verification.architecture));
3299
+ }
3300
+ if (verification.auxiliaryTree !== undefined || verification.visualManifest !== undefined)
3301
+ return false;
3302
+ const header = probe.fileHeader?.(verification.realPath, 64);
3303
+ return Boolean(header
3304
+ && validElfHeader(header, verification.architecture)
3305
+ && linuxPackageIdentityMatches(verification.realPath, 'code', verification.architecture, probe));
3306
+ }
3307
+ if (verification.kind === 'windows-executable') {
3308
+ if (platform !== 'win32'
3309
+ || !unchangedFileBinding(verification, plan.command, platform, probe))
3310
+ return false;
3311
+ const identity = probe.windowsExecutableIdentity?.(verification.realPath);
3312
+ return Boolean(identity
3313
+ && windowsApplicationIdentityValueMatches(surface, identity, verification.architecture)
3314
+ && identity.productName === verification.productName
3315
+ && identity.companyName === verification.companyName
3316
+ && identity.publisher === verification.publisher);
3317
+ }
3318
+ if (verification.kind === 'linux-cli') {
3319
+ return false;
3320
+ }
3321
+ if (verification.kind === 'linux-application') {
3322
+ if (platform !== 'linux'
3323
+ || plan.surfaceId !== verification.surfaceId
3324
+ || !unchangedFileBinding(verification, plan.command, platform, probe))
3325
+ return false;
3326
+ const header = probe.fileHeader?.(verification.realPath, 64);
3327
+ return Boolean(header && validElfHeader(header, verification.architecture));
3328
+ }
3329
+ if (verification.kind === 'windows-appx') {
3330
+ if (platform !== 'win32')
3331
+ return false;
3332
+ const expected = EXPECTED_WINDOWS_APPX_IDENTITIES[plan.surfaceId];
3333
+ const identity = probe.windowsAppxIdentity?.(verification.packageName);
3334
+ return Boolean(expected
3335
+ && identity
3336
+ && plan.command === `shell:AppsFolder\\${verification.familyName}!${verification.applicationId}`
3337
+ && verification.packageName === expected.packageName
3338
+ && verification.applicationId === expected.applicationId
3339
+ && verification.publisherId === expected.publisherId
3340
+ && verification.familyName === expected.familyName
3341
+ && (expected.publisher === undefined || verification.publisher === expected.publisher)
3342
+ && expected.signatureKinds.includes(verification.signatureKind)
3343
+ && identity.packageName === verification.packageName
3344
+ && identity.publisher === verification.publisher
3345
+ && identity.publisherId === verification.publisherId
3346
+ && identity.familyName === verification.familyName
3347
+ && identity.applicationIds.includes(verification.applicationId)
3348
+ && identity.architecture === verification.architecture
3349
+ && identity.signatureKind === verification.signatureKind
3350
+ && identity.status === 'Ok');
3351
+ }
3352
+ if (verification.kind === 'linux-package') {
3353
+ if (platform !== 'linux'
3354
+ || plan.surfaceId !== 'copilot-desktop'
3355
+ || !unchangedFileBinding(verification, plan.command, platform, probe))
3356
+ return false;
3357
+ const identity = probe.linuxPackageIdentity?.(verification.realPath);
3358
+ return Boolean(identity
3359
+ && linuxPackageIdentityMatches(verification.realPath, 'github', verification.architecture, probe)
3360
+ && identity.packageName === verification.packageName
3361
+ && identity.version === verification.version
3362
+ && identity.manager === verification.manager
3363
+ && identity.origin === verification.origin
3364
+ && identity.catalogArtifactSha256 === verification.catalogArtifactSha256
3365
+ && identity.executableSha256 === verification.sha256);
3366
+ }
3367
+ return false;
414
3368
  }
415
- export async function executeAiLaunchPlan(plan, platform = process.platform) {
3369
+ export async function executeAiLaunchPlan(plan, platform = process.platform, options = {}) {
3370
+ const dispatch = { dispatched: true, confirmed: false };
3371
+ if (plan.mode === 'url' && !trustedSurfaceUrlIsSupported(plan.command)) {
3372
+ throw new Error('EAI refused to open an unsupported AI workspace location.');
3373
+ }
3374
+ const deepLinkArguments = plan.args.filter((argument) => /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(argument));
3375
+ if (deepLinkArguments.some((argument) => !trustedSurfaceUrlIsSupported(argument))
3376
+ || (plan.surfaceId === 'claude-desktop'
3377
+ && deepLinkArguments.some((argument) => !argument.startsWith('claude://code/new')))
3378
+ || (plan.surfaceId === 'copilot-desktop'
3379
+ && deepLinkArguments.some((argument) => !argument.startsWith('ghapp://recent')))) {
3380
+ throw new Error('EAI refused to open an unsupported AI workspace location.');
3381
+ }
3382
+ if (plan.mode === 'application') {
3383
+ const safeApplicationArguments = (() => {
3384
+ if (plan.surfaceId === 'claude-desktop') {
3385
+ return plan.args.length === 0
3386
+ || (plan.args.length === 1 && /^claude:\/\/code\/new(?:[?#]|$)/.test(plan.args[0]));
3387
+ }
3388
+ if (plan.surfaceId === 'copilot-desktop') {
3389
+ return plan.args.length === 0
3390
+ || (plan.args.length === 1 && /^ghapp:\/\/recent(?:[?#]|$)/.test(plan.args[0]));
3391
+ }
3392
+ if (plan.surfaceId === 'grok-bot') {
3393
+ return plan.args.length === 0
3394
+ || (plan.args.length === 1 && plan.args[0] === '--ozone-platform-hint=auto');
3395
+ }
3396
+ return plan.args.length === 0;
3397
+ })();
3398
+ if (!safeApplicationArguments) {
3399
+ throw new Error('EAI refused unexpected application launch arguments.');
3400
+ }
3401
+ }
3402
+ if (plan.surfaceId === 'vscode-copilot'
3403
+ && ((platform === 'win32' || platform === 'linux')
3404
+ ? plan.verification?.kind !== 'vscode-catalog'
3405
+ : platform === 'darwin'
3406
+ ? plan.verification?.kind !== 'mac-application'
3407
+ : true)) {
3408
+ throw new Error('EAI refused to launch VS Code without platform-authenticated catalog evidence.');
3409
+ }
3410
+ const probe = options.probe ?? systemSurfaceProbe;
3411
+ if (!launchArtifactStillAuthenticated(plan, platform, probe)) {
3412
+ throw new Error(`EAI refused to launch ${plan.surfaceName} because its authenticated application changed after detection.`);
3413
+ }
416
3414
  if (plan.mode === 'url') {
417
3415
  await openTrustedSurfaceUrl(plan.command, platform);
418
- return;
3416
+ return dispatch;
419
3417
  }
420
3418
  if (plan.mode === 'application') {
421
3419
  if (platform === 'darwin')
422
- await spawnDetached('open', ['-a', plan.command, ...plan.args]);
3420
+ await spawnDetached('/usr/bin/open', ['-a', plan.command, ...plan.args]);
3421
+ else if (platform === 'win32' && plan.command.startsWith('shell:AppsFolder\\')) {
3422
+ await spawnDetached('C:\\Windows\\explorer.exe', [plan.command]);
3423
+ }
423
3424
  else
424
3425
  await spawnDetached(plan.command, plan.args, plan.cwd);
425
- return;
3426
+ return dispatch;
426
3427
  }
427
3428
  if (plan.mode === 'process') {
3429
+ if (platform === 'win32' && /\.(?:cmd|bat)$/i.test(plan.command)) {
3430
+ await spawnWindowsCommand(plan.command, plan.args, '/c', plan.cwd, plan.environment);
3431
+ return dispatch;
3432
+ }
3433
+ if (plan.verification?.kind === 'vscode-catalog') {
3434
+ await spawnDetached(plan.command, plan.args, plan.cwd, sanitizedVsCodeRuntimeEnvironment(plan.verification.platform), undefined, true, true);
3435
+ return dispatch;
3436
+ }
428
3437
  await spawnDetached(plan.command, plan.args, plan.cwd, plan.environment);
429
- return;
3438
+ return dispatch;
3439
+ }
3440
+ if (platform === 'win32') {
3441
+ await spawnWindowsCommand(plan.command, plan.args, '/k', plan.cwd, plan.environment, false);
3442
+ return dispatch;
430
3443
  }
431
- const commandLine = [plan.command, ...plan.args].map((value) => shellQuote(value, platform)).join(' ');
432
3444
  if (platform === 'darwin') {
3445
+ const commandLine = [plan.command, ...plan.args].map((value) => shellQuote(value, platform)).join(' ');
433
3446
  const escaped = `cd ${shellQuote(plan.cwd, platform)} && ${commandLine}`.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
434
- await spawnDetached('osascript', ['-e', `tell application "Terminal" to do script "${escaped}"`]);
435
- }
436
- else if (platform === 'win32') {
437
- await spawnDetached('cmd.exe', ['/k', `cd /d ${shellQuote(plan.cwd, platform)} && ${commandLine}`]);
3447
+ await spawnDetached('/usr/bin/osascript', ['-e', `tell application "Terminal" to do script "${escaped}"`]);
438
3448
  }
439
3449
  else {
440
- await spawnDetached('x-terminal-emulator', ['-e', 'sh', '-lc', `cd ${shellQuote(plan.cwd, platform)} && ${commandLine}`]);
3450
+ const terminal = buildLinuxTerminalInvocation(plan, options.commandPath);
3451
+ await spawnDetached(terminal.command, terminal.args, plan.cwd, plan.environment);
441
3452
  }
3453
+ return dispatch;
442
3454
  }
443
3455
  export function getAiSurface(inventory, surfaceId) {
444
3456
  return surfaceOrThrow(inventory, surfaceId);