pi-agent-browser-native 0.2.48 → 0.2.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +21 -11
  3. package/dist/extensions/agent-browser/index.js +808 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +669 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +363 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +956 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +855 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/ARCHITECTURE.md +5 -5
  95. package/docs/COMMAND_REFERENCE.md +4 -4
  96. package/docs/RELEASE.md +22 -11
  97. package/docs/REQUIREMENTS.md +1 -1
  98. package/docs/SUPPORT_MATRIX.md +5 -4
  99. package/docs/TOOL_CONTRACT.md +1 -1
  100. package/package.json +9 -5
  101. package/scripts/config.mjs +14 -20
  102. package/scripts/doctor.mjs +8 -7
  103. package/extensions/agent-browser/index.ts +0 -961
  104. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  105. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  106. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  107. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  108. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  109. package/extensions/agent-browser/lib/config-policy.js +0 -690
  110. package/extensions/agent-browser/lib/config.ts +0 -211
  111. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  112. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  113. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  114. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  115. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  116. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  117. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  118. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  119. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  120. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  121. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  122. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  123. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  124. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  125. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  126. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  127. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  128. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  129. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  130. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  131. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  132. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  133. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  139. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  140. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  141. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  142. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  143. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  144. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  145. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  146. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  147. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  148. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  149. package/extensions/agent-browser/lib/playbook.ts +0 -142
  150. package/extensions/agent-browser/lib/process.ts +0 -516
  151. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  152. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  153. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  154. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  155. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  156. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  157. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  158. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  159. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  160. package/extensions/agent-browser/lib/results/network.ts +0 -78
  161. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  162. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  163. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  164. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  165. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  166. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  167. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  168. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  169. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  170. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  171. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  172. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  173. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  174. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  175. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  176. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  177. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  178. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  179. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  180. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  181. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  182. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  183. package/extensions/agent-browser/lib/results/text.ts +0 -40
  184. package/extensions/agent-browser/lib/runtime.ts +0 -988
  185. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  186. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  187. package/extensions/agent-browser/lib/temp.ts +0 -577
  188. package/extensions/agent-browser/lib/web-search.ts +0 -728
  189. /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
@@ -1,499 +0,0 @@
1
- /**
2
- * Purpose: Launch wrapper-owned Electron applications and discover their CDP endpoint.
3
- * Responsibilities: Resolve Electron targets, enforce caller-owned allow/deny policy, create isolated userDataDir profiles, launch with remote debugging on an OS-chosen port, poll DevToolsActivePort, and read bounded CDP version/target metadata.
4
- * Scope: Host-side Electron lifecycle setup only; upstream agent-browser attach/presentation stays in the extension entrypoint.
5
- * Usage: Called by the agent_browser electron.launch shorthand before routing through upstream `connect`.
6
- * Invariants/Assumptions: The wrapper only launches targets with Electron framework evidence, always uses an isolated temp profile, and never accepts a caller-supplied remote debugging port.
7
- */
8
-
9
- import { spawn, type ChildProcess } from "node:child_process";
10
- import { randomUUID } from "node:crypto";
11
- import { readFile, rm } from "node:fs/promises";
12
- import { dirname } from "node:path";
13
-
14
- import {
15
- fetchCdpJson,
16
- parseCdpTargets,
17
- parseCdpVersion,
18
- type ElectronCdpTarget,
19
- type ElectronCdpVersion,
20
- } from "./cdp.js";
21
- import {
22
- discoverElectronApps,
23
- inspectElectronAppPath,
24
- inspectElectronExecutablePath,
25
- type ElectronAppDiscovery,
26
- } from "./discovery.js";
27
- import { createSecureTempDirectory } from "../temp.js";
28
-
29
- export type { ElectronCdpTarget, ElectronCdpVersion } from "./cdp.js";
30
-
31
- export const ELECTRON_LAUNCH_RECORD_VERSION = 1;
32
- export const ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS = 15_000;
33
- export const ELECTRON_LAUNCH_MAX_TIMEOUT_MS = 120_000;
34
-
35
- const DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
36
- export const ELECTRON_PROFILE_DIR_PREFIX = "electron-profile-";
37
- const ELECTRON_DEFAULT_APP_ARGS = ["--disable-extensions", "--no-first-run", "--no-default-browser-check"] as const;
38
- const ELECTRON_DEVTOOLS_POLL_INTERVAL_MS = 100;
39
-
40
- export interface ElectronDevToolsActivePortRead {
41
- error?: string;
42
- found: boolean;
43
- path: string;
44
- port?: number;
45
- }
46
-
47
- export interface ElectronLaunchFailureDiagnostics {
48
- cdpVersionReached?: boolean;
49
- devToolsActivePort?: ElectronDevToolsActivePortRead;
50
- elapsedMs?: number;
51
- exitCode?: number | null;
52
- exitSignal?: NodeJS.Signals | null;
53
- outputCaptured: false;
54
- pid?: number;
55
- pidAlive?: boolean;
56
- port?: number;
57
- timeoutMs?: number;
58
- userDataDir?: string;
59
- }
60
-
61
- export type ElectronLaunchCleanupState = "active" | "cleaned" | "dead" | "failed" | "partial";
62
- export type ElectronLaunchFailureReason =
63
- | "non-electron-target"
64
- | "policy-blocked"
65
- | "port-not-found"
66
- | "single-instance-conflict"
67
- | "spawn-error"
68
- | "timeout";
69
-
70
- export interface ElectronLaunchRecord {
71
- appName: string;
72
- appPath?: string;
73
- bundleId?: string;
74
- cleanupState: ElectronLaunchCleanupState;
75
- createdAtMs: number;
76
- desktopId?: string;
77
- executablePath: string;
78
- launchId: string;
79
- launchedByWrapper: true;
80
- packageSource?: string;
81
- pid?: number;
82
- platform?: string;
83
- port: number;
84
- processGroupId?: number;
85
- sessionName?: string;
86
- targetType?: "any" | "page" | "webview";
87
- userDataDir: string;
88
- version: typeof ELECTRON_LAUNCH_RECORD_VERSION;
89
- webSocketDebuggerUrl?: string;
90
- }
91
-
92
- export interface ElectronPolicyBlock {
93
- entry?: string;
94
- list: "allow" | "deny";
95
- message: string;
96
- }
97
-
98
- export interface ElectronLaunchSuccess {
99
- appArgs: string[];
100
- child: ChildProcess;
101
- connectArg: string;
102
- record: ElectronLaunchRecord;
103
- target: ElectronAppDiscovery;
104
- targets: ElectronCdpTarget[];
105
- version: ElectronCdpVersion;
106
- }
107
-
108
- export interface ElectronLaunchFailure {
109
- appArgs: string[];
110
- cleanupError?: string;
111
- diagnostics?: ElectronLaunchFailureDiagnostics;
112
- error: string;
113
- policy?: ElectronPolicyBlock;
114
- reason: ElectronLaunchFailureReason;
115
- target?: ElectronAppDiscovery;
116
- userDataDir?: string;
117
- }
118
-
119
- export type ElectronLaunchResult = { ok: true; value: ElectronLaunchSuccess } | { ok: false; failure: ElectronLaunchFailure };
120
-
121
- export interface ResolveElectronTargetOptions {
122
- appName?: string;
123
- appPath?: string;
124
- bundleId?: string;
125
- executablePath?: string;
126
- }
127
-
128
- function normalizeTimeoutMs(timeoutMs: number | undefined): number {
129
- if (!Number.isSafeInteger(timeoutMs) || (timeoutMs ?? 0) <= 0) return ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS;
130
- return Math.min(timeoutMs as number, ELECTRON_LAUNCH_MAX_TIMEOUT_MS);
131
- }
132
-
133
- function sleep(ms: number): Promise<void> {
134
- return new Promise((resolve) => setTimeout(resolve, ms));
135
- }
136
-
137
- function normalizeIdentifier(value: string | undefined): string | undefined {
138
- const trimmed = value?.trim().toLowerCase();
139
- return trimmed && trimmed.length > 0 ? trimmed : undefined;
140
- }
141
-
142
- function appIdentifiers(app: ElectronAppDiscovery): string[] {
143
- return [app.name, app.bundleId, app.desktopId, app.appPath, app.executablePath]
144
- .filter((value): value is string => typeof value === "string" && value.trim().length > 0);
145
- }
146
-
147
- function policyEntryMatchesApp(entry: string, app: ElectronAppDiscovery): boolean {
148
- const normalizedEntry = normalizeIdentifier(entry);
149
- if (!normalizedEntry) return false;
150
- return appIdentifiers(app).some((identifier) => identifier.toLowerCase().includes(normalizedEntry));
151
- }
152
-
153
- export function evaluateElectronLaunchPolicy(options: {
154
- allow?: string[];
155
- deny?: string[];
156
- target: ElectronAppDiscovery;
157
- }): ElectronPolicyBlock | undefined {
158
- const denyEntry = options.deny?.find((entry) => policyEntryMatchesApp(entry, options.target));
159
- if (denyEntry) {
160
- return {
161
- entry: denyEntry,
162
- list: "deny",
163
- message: `Electron launch blocked by caller deny policy: ${denyEntry}`,
164
- };
165
- }
166
- if (options.allow && options.allow.length > 0) {
167
- const allowEntry = options.allow.find((entry) => policyEntryMatchesApp(entry, options.target));
168
- if (!allowEntry) {
169
- return {
170
- list: "allow",
171
- message: "Electron launch blocked because the resolved app did not match caller allow policy.",
172
- };
173
- }
174
- }
175
- return undefined;
176
- }
177
-
178
- export async function resolveElectronLaunchTarget(options: ResolveElectronTargetOptions): Promise<ElectronAppDiscovery | undefined> {
179
- if (options.appPath) return inspectElectronAppPath(options.appPath);
180
- if (options.executablePath) return inspectElectronExecutablePath(options.executablePath);
181
- const query = options.bundleId ?? options.appName;
182
- const discovery = await discoverElectronApps({ maxResults: 200, query });
183
- if (options.bundleId) {
184
- const normalizedBundleId = normalizeIdentifier(options.bundleId);
185
- return discovery.apps.find((app) => normalizeIdentifier(app.bundleId) === normalizedBundleId);
186
- }
187
- if (options.appName) {
188
- const normalizedName = normalizeIdentifier(options.appName);
189
- return discovery.apps.find((app) => normalizeIdentifier(app.name) === normalizedName) ?? discovery.apps[0];
190
- }
191
- return undefined;
192
- }
193
-
194
- function targetMatchesType(target: ElectronCdpTarget, targetType: "any" | "page" | "webview" | undefined): boolean {
195
- return targetType === undefined || targetType === "any" || target.type === targetType;
196
- }
197
-
198
- function selectElectronConnectArg(options: {
199
- port: number;
200
- targets: ElectronCdpTarget[];
201
- targetType?: "any" | "page" | "webview";
202
- version: ElectronCdpVersion;
203
- }): string {
204
- const targetWebSocket = options.targets.find((target) => targetMatchesType(target, options.targetType) && target.webSocketDebuggerUrl)?.webSocketDebuggerUrl;
205
- return targetWebSocket ?? options.version.webSocketDebuggerUrl ?? String(options.port);
206
- }
207
-
208
- async function readDevToolsActivePort(userDataDir: string): Promise<ElectronDevToolsActivePortRead> {
209
- const path = `${userDataDir}/${DEVTOOLS_ACTIVE_PORT_FILE}`;
210
- try {
211
- const text = await readFile(path, "utf8");
212
- const [portLine] = text.split(/\r?\n/);
213
- const port = Number(portLine?.trim());
214
- return {
215
- found: true,
216
- path,
217
- port: Number.isSafeInteger(port) && port > 0 && port <= 65_535 ? port : undefined,
218
- ...(Number.isSafeInteger(port) && port > 0 && port <= 65_535 ? {} : { error: "DevToolsActivePort did not contain a valid TCP port." }),
219
- };
220
- } catch (error) {
221
- const code = (error as NodeJS.ErrnoException).code;
222
- return {
223
- error: code && code !== "ENOENT" ? `${code}: ${error instanceof Error ? error.message : String(error)}` : undefined,
224
- found: false,
225
- path,
226
- };
227
- }
228
- }
229
-
230
- async function pollDevToolsActivePort(options: {
231
- deadlineMs: number;
232
- getChildExit: () => { code: number | null; signal: NodeJS.Signals | null };
233
- getSpawnError: () => Error | undefined;
234
- userDataDir: string;
235
- }): Promise<{ devToolsActivePort?: ElectronDevToolsActivePortRead; failure?: ElectronLaunchFailureReason; port?: number; spawnError?: Error }> {
236
- let devToolsActivePort: ElectronDevToolsActivePortRead | undefined;
237
- while (Date.now() <= options.deadlineMs) {
238
- const spawnError = options.getSpawnError();
239
- if (spawnError) return { devToolsActivePort, failure: "spawn-error", spawnError };
240
- devToolsActivePort = await readDevToolsActivePort(options.userDataDir);
241
- if (devToolsActivePort.port) return { devToolsActivePort, port: devToolsActivePort.port };
242
- const exit = options.getChildExit();
243
- if (exit.code !== null || exit.signal !== null) {
244
- return { devToolsActivePort, failure: exit.code === 0 ? "single-instance-conflict" : "spawn-error" };
245
- }
246
- await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
247
- }
248
- return { devToolsActivePort, failure: "timeout" };
249
- }
250
-
251
- async function pollCdpMetadata(port: number, deadlineMs: number): Promise<{ targets: ElectronCdpTarget[]; version: ElectronCdpVersion } | undefined> {
252
- while (Date.now() <= deadlineMs) {
253
- const version = parseCdpVersion(await fetchCdpJson(`http://127.0.0.1:${port}/json/version`));
254
- if (version) {
255
- const targets = parseCdpTargets(await fetchCdpJson(`http://127.0.0.1:${port}/json/list`));
256
- return { targets, version };
257
- }
258
- await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
259
- }
260
- return undefined;
261
- }
262
-
263
- function buildLaunchArgs(userDataDir: string, appArgs: string[]): string[] {
264
- return [
265
- ...appArgs,
266
- `--user-data-dir=${userDataDir}`,
267
- "--remote-debugging-port=0",
268
- ...ELECTRON_DEFAULT_APP_ARGS,
269
- ];
270
- }
271
-
272
- async function waitForLaunchChildExit(child: ChildProcess, deadlineMs: number): Promise<boolean> {
273
- while (Date.now() <= deadlineMs) {
274
- if (child.exitCode !== null || child.signalCode !== null) return true;
275
- await sleep(50);
276
- }
277
- return child.exitCode !== null || child.signalCode !== null;
278
- }
279
-
280
- function isLaunchChildPidAlive(child: ChildProcess): boolean | undefined {
281
- if (!child.pid) return undefined;
282
- if (child.exitCode !== null || child.signalCode !== null) return false;
283
- try {
284
- process.kill(child.pid, 0);
285
- return true;
286
- } catch (error) {
287
- return (error as NodeJS.ErrnoException).code === "EPERM";
288
- }
289
- }
290
-
291
- async function terminateLaunchChild(child: ChildProcess): Promise<string | undefined> {
292
- if (!child.pid || child.exitCode !== null || child.signalCode !== null) return undefined;
293
- try {
294
- child.kill("SIGTERM");
295
- } catch (error) {
296
- return error instanceof Error ? error.message : String(error);
297
- }
298
- if (await waitForLaunchChildExit(child, Date.now() + 1_000)) return undefined;
299
- try {
300
- child.kill("SIGKILL");
301
- } catch (error) {
302
- return error instanceof Error ? error.message : String(error);
303
- }
304
- if (await waitForLaunchChildExit(child, Date.now() + 1_000)) return undefined;
305
- return `PID ${child.pid} remained alive after failed Electron launch cleanup.`;
306
- }
307
-
308
- function buildLaunchRecord(options: {
309
- createdAtMs: number;
310
- pid?: number;
311
- port: number;
312
- target: ElectronAppDiscovery;
313
- targetType?: "any" | "page" | "webview";
314
- userDataDir: string;
315
- version: ElectronCdpVersion;
316
- }): ElectronLaunchRecord {
317
- return {
318
- appName: options.target.name,
319
- appPath: options.target.appPath,
320
- bundleId: options.target.bundleId,
321
- cleanupState: "active",
322
- createdAtMs: options.createdAtMs,
323
- desktopId: options.target.desktopId,
324
- executablePath: options.target.executablePath,
325
- launchId: `electron-${randomUUID()}`,
326
- launchedByWrapper: true,
327
- packageSource: options.target.packageSource,
328
- pid: options.pid,
329
- platform: options.target.platform,
330
- port: options.port,
331
- processGroupId: process.platform === "win32" ? undefined : options.pid,
332
- targetType: options.targetType,
333
- userDataDir: options.userDataDir,
334
- version: ELECTRON_LAUNCH_RECORD_VERSION,
335
- webSocketDebuggerUrl: options.version.webSocketDebuggerUrl,
336
- };
337
- }
338
-
339
- function launchFailureMessage(reason: ElectronLaunchFailureReason, target: ElectronAppDiscovery | undefined, detail?: string): string {
340
- const label = target ? `${target.name} (${target.appPath ?? target.executablePath})` : "target";
341
- switch (reason) {
342
- case "non-electron-target":
343
- return `Electron launch rejected: ${label} does not have Electron framework evidence.`;
344
- case "policy-blocked":
345
- return detail ?? `Electron launch blocked by caller policy for ${label}.`;
346
- case "single-instance-conflict":
347
- return `Electron launch did not expose a debug port for ${label}; the app may already be running as a single-instance Electron app. Quit the running app and retry.`;
348
- case "port-not-found":
349
- return `Electron launch found a DevToolsActivePort for ${label}, but /json/version never returned a valid CDP payload.`;
350
- case "spawn-error":
351
- return `Electron launch failed while starting ${label}${detail ? `: ${detail}` : "."}`;
352
- case "timeout":
353
- return `Electron launch timed out waiting for DevToolsActivePort for ${label}.`;
354
- }
355
- }
356
-
357
- export async function launchElectronApp(options: {
358
- allow?: string[];
359
- appArgs?: string[];
360
- deny?: string[];
361
- appName?: string;
362
- appPath?: string;
363
- bundleId?: string;
364
- executablePath?: string;
365
- targetType?: "any" | "page" | "webview";
366
- timeoutMs?: number;
367
- }): Promise<ElectronLaunchResult> {
368
- const appArgs = options.appArgs ?? [];
369
- const target = await resolveElectronLaunchTarget(options);
370
- if (!target) {
371
- return {
372
- ok: false,
373
- failure: {
374
- appArgs,
375
- error: launchFailureMessage("non-electron-target", undefined),
376
- reason: "non-electron-target",
377
- },
378
- };
379
- }
380
-
381
- const policy = evaluateElectronLaunchPolicy({ allow: options.allow, deny: options.deny, target });
382
- if (policy) {
383
- return {
384
- ok: false,
385
- failure: {
386
- appArgs,
387
- error: launchFailureMessage("policy-blocked", target, policy.message),
388
- policy,
389
- reason: "policy-blocked",
390
- target,
391
- },
392
- };
393
- }
394
-
395
- const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
396
- const startedAtMs = Date.now();
397
- const deadlineMs = startedAtMs + timeoutMs;
398
- const userDataDir = await createSecureTempDirectory(ELECTRON_PROFILE_DIR_PREFIX);
399
- let cleanupError: string | undefined;
400
- let spawnError: Error | undefined;
401
- let exitCode: number | null = null;
402
- let exitSignal: NodeJS.Signals | null = null;
403
- const args = buildLaunchArgs(userDataDir, appArgs);
404
- const child = spawn(target.executablePath, args, {
405
- cwd: dirname(target.executablePath),
406
- detached: process.platform !== "win32",
407
- stdio: "ignore",
408
- });
409
- child.once("error", (error) => {
410
- spawnError = error;
411
- });
412
- child.once("exit", (code, signal) => {
413
- exitCode = code;
414
- exitSignal = signal;
415
- });
416
- child.unref();
417
-
418
- const buildFailureDiagnostics = (options: {
419
- cdpVersionReached?: boolean;
420
- devToolsActivePort?: ElectronDevToolsActivePortRead;
421
- port?: number;
422
- } = {}): ElectronLaunchFailureDiagnostics => ({
423
- cdpVersionReached: options.cdpVersionReached,
424
- devToolsActivePort: options.devToolsActivePort,
425
- elapsedMs: Math.max(0, Date.now() - startedAtMs),
426
- exitCode,
427
- exitSignal,
428
- outputCaptured: false,
429
- pid: child.pid,
430
- pidAlive: isLaunchChildPidAlive(child),
431
- port: options.port ?? options.devToolsActivePort?.port,
432
- timeoutMs,
433
- userDataDir,
434
- });
435
-
436
- const fail = async (reason: ElectronLaunchFailureReason, detail?: string, diagnosticOptions?: Parameters<typeof buildFailureDiagnostics>[0]): Promise<ElectronLaunchResult> => {
437
- const diagnostics = buildFailureDiagnostics(diagnosticOptions);
438
- const processCleanupError = await terminateLaunchChild(child);
439
- try {
440
- await rm(userDataDir, { force: true, recursive: true });
441
- } catch (error) {
442
- cleanupError = error instanceof Error ? error.message : String(error);
443
- }
444
- cleanupError = [processCleanupError, cleanupError].filter((value): value is string => value !== undefined).join("; ") || undefined;
445
- return {
446
- ok: false,
447
- failure: {
448
- appArgs,
449
- cleanupError,
450
- diagnostics,
451
- error: launchFailureMessage(reason, target, detail),
452
- reason,
453
- target,
454
- userDataDir,
455
- },
456
- };
457
- };
458
-
459
- const portResult = await pollDevToolsActivePort({
460
- deadlineMs,
461
- getChildExit: () => ({ code: exitCode, signal: exitSignal }),
462
- getSpawnError: () => spawnError,
463
- userDataDir,
464
- });
465
- if (!portResult.port) {
466
- return fail(portResult.failure ?? "timeout", portResult.spawnError?.message, { devToolsActivePort: portResult.devToolsActivePort });
467
- }
468
- const metadata = await pollCdpMetadata(portResult.port, deadlineMs);
469
- if (!metadata) {
470
- return fail("port-not-found", undefined, { cdpVersionReached: false, devToolsActivePort: portResult.devToolsActivePort, port: portResult.port });
471
- }
472
- const record = buildLaunchRecord({
473
- createdAtMs: Date.now(),
474
- pid: child.pid,
475
- port: portResult.port,
476
- target,
477
- targetType: options.targetType,
478
- userDataDir,
479
- version: metadata.version,
480
- });
481
- const connectArg = selectElectronConnectArg({
482
- port: portResult.port,
483
- targets: metadata.targets,
484
- targetType: options.targetType,
485
- version: metadata.version,
486
- });
487
- return {
488
- ok: true,
489
- value: {
490
- appArgs,
491
- child,
492
- connectArg,
493
- record,
494
- target,
495
- targets: metadata.targets,
496
- version: metadata.version,
497
- },
498
- };
499
- }
@@ -1,19 +0,0 @@
1
- import { constants as fsConstants } from "node:fs";
2
- import { access, stat } from "node:fs/promises";
3
- import { delimiter, join } from "node:path";
4
-
5
- export async function executableExistsOnPath(command: string): Promise<boolean> {
6
- const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
7
- for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
8
- for (const extension of extensions) {
9
- try {
10
- const candidate = join(directory, `${command}${extension}`);
11
- await access(candidate, fsConstants.X_OK);
12
- if ((await stat(candidate)).isFile()) return true;
13
- } catch {
14
- // Try the next PATH candidate.
15
- }
16
- }
17
- }
18
- return false;
19
- }
@@ -1,18 +0,0 @@
1
- import { access, stat } from "node:fs/promises";
2
-
3
- export async function pathExists(path: string): Promise<boolean> {
4
- try {
5
- await access(path);
6
- return true;
7
- } catch {
8
- return false;
9
- }
10
- }
11
-
12
- export async function directoryExists(path: string): Promise<boolean> {
13
- try {
14
- return (await stat(path)).isDirectory();
15
- } catch {
16
- return false;
17
- }
18
- }