pi-agent-browser-native 0.2.47 → 0.2.49

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 (185) hide show
  1. package/CHANGELOG.md +63 -19
  2. package/README.md +52 -19
  3. package/dist/extensions/agent-browser/index.js +785 -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 +686 -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 +448 -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 +960 -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 +816 -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 +10 -10
  95. package/docs/COMMAND_REFERENCE.md +35 -21
  96. package/docs/ELECTRON.md +3 -3
  97. package/docs/RELEASE.md +46 -26
  98. package/docs/REQUIREMENTS.md +1 -1
  99. package/docs/SUPPORT_MATRIX.md +35 -106
  100. package/docs/TOOL_CONTRACT.md +23 -21
  101. package/package.json +12 -8
  102. package/scripts/agent-browser-capability-baseline.mjs +6 -3
  103. package/scripts/config.mjs +8 -2
  104. package/scripts/doctor.mjs +19 -17
  105. package/scripts/platform-smoke.mjs +1 -1
  106. package/extensions/agent-browser/index.ts +0 -952
  107. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  108. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  109. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  110. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  111. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  112. package/extensions/agent-browser/lib/config-policy.js +0 -690
  113. package/extensions/agent-browser/lib/config.ts +0 -209
  114. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  115. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  116. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  117. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  118. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  119. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  120. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  121. package/extensions/agent-browser/lib/input-modes/job.ts +0 -451
  122. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  123. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  124. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  125. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  126. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  127. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  128. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  129. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  130. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  131. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  132. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -257
  133. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -912
  134. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -512
  135. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -1481
  137. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -564
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  139. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  140. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -564
  141. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  142. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  143. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  144. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -252
  145. package/extensions/agent-browser/lib/playbook.ts +0 -142
  146. package/extensions/agent-browser/lib/process.ts +0 -516
  147. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  148. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  149. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  150. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  151. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  152. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  153. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  154. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  155. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  156. package/extensions/agent-browser/lib/results/network.ts +0 -78
  157. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  158. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  159. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  160. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  161. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  162. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  163. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  164. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  165. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  166. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  167. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  168. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  169. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  170. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  171. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  172. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  173. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  174. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  175. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  176. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  177. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  178. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  179. package/extensions/agent-browser/lib/results/text.ts +0 -40
  180. package/extensions/agent-browser/lib/runtime.ts +0 -988
  181. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  182. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  183. package/extensions/agent-browser/lib/temp.ts +0 -577
  184. package/extensions/agent-browser/lib/web-search.ts +0 -721
  185. /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
- }