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.
- package/CHANGELOG.md +63 -19
- package/README.md +52 -19
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/ARCHITECTURE.md +10 -10
- package/docs/COMMAND_REFERENCE.md +35 -21
- package/docs/ELECTRON.md +3 -3
- package/docs/RELEASE.md +46 -26
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +35 -106
- package/docs/TOOL_CONTRACT.md +23 -21
- package/package.json +12 -8
- package/scripts/agent-browser-capability-baseline.mjs +6 -3
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +19 -17
- package/scripts/platform-smoke.mjs +1 -1
- package/extensions/agent-browser/index.ts +0 -952
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -209
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -451
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -257
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -912
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -512
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -1481
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -564
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -564
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -252
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -721
- /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
|
-
}
|