@enterpriseai/cli 3.15.9 → 3.15.10

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