pi-agent-browser-native 0.2.48 → 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 +17 -0
- package/README.md +16 -6
- 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/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- 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 -211
- 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 -527
- 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/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- 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 -267
- 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 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -0,0 +1,351 @@
|
|
|
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
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { readFile, rm } from "node:fs/promises";
|
|
11
|
+
import { dirname } from "node:path";
|
|
12
|
+
import { fetchCdpJson, parseCdpTargets, parseCdpVersion, } from "./cdp.js";
|
|
13
|
+
import { discoverElectronApps, inspectElectronAppPath, inspectElectronExecutablePath, } from "./discovery.js";
|
|
14
|
+
import { createSecureTempDirectory } from "../temp.js";
|
|
15
|
+
export const ELECTRON_LAUNCH_RECORD_VERSION = 1;
|
|
16
|
+
export const ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS = 15_000;
|
|
17
|
+
export const ELECTRON_LAUNCH_MAX_TIMEOUT_MS = 120_000;
|
|
18
|
+
const DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
|
|
19
|
+
export const ELECTRON_PROFILE_DIR_PREFIX = "electron-profile-";
|
|
20
|
+
const ELECTRON_DEFAULT_APP_ARGS = ["--disable-extensions", "--no-first-run", "--no-default-browser-check"];
|
|
21
|
+
const ELECTRON_DEVTOOLS_POLL_INTERVAL_MS = 100;
|
|
22
|
+
function normalizeTimeoutMs(timeoutMs) {
|
|
23
|
+
if (!Number.isSafeInteger(timeoutMs) || (timeoutMs ?? 0) <= 0)
|
|
24
|
+
return ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS;
|
|
25
|
+
return Math.min(timeoutMs, ELECTRON_LAUNCH_MAX_TIMEOUT_MS);
|
|
26
|
+
}
|
|
27
|
+
function sleep(ms) {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
function normalizeIdentifier(value) {
|
|
31
|
+
const trimmed = value?.trim().toLowerCase();
|
|
32
|
+
return trimmed && trimmed.length > 0 ? trimmed : undefined;
|
|
33
|
+
}
|
|
34
|
+
function appIdentifiers(app) {
|
|
35
|
+
return [app.name, app.bundleId, app.desktopId, app.appPath, app.executablePath]
|
|
36
|
+
.filter((value) => typeof value === "string" && value.trim().length > 0);
|
|
37
|
+
}
|
|
38
|
+
function policyEntryMatchesApp(entry, app) {
|
|
39
|
+
const normalizedEntry = normalizeIdentifier(entry);
|
|
40
|
+
if (!normalizedEntry)
|
|
41
|
+
return false;
|
|
42
|
+
return appIdentifiers(app).some((identifier) => identifier.toLowerCase().includes(normalizedEntry));
|
|
43
|
+
}
|
|
44
|
+
export function evaluateElectronLaunchPolicy(options) {
|
|
45
|
+
const denyEntry = options.deny?.find((entry) => policyEntryMatchesApp(entry, options.target));
|
|
46
|
+
if (denyEntry) {
|
|
47
|
+
return {
|
|
48
|
+
entry: denyEntry,
|
|
49
|
+
list: "deny",
|
|
50
|
+
message: `Electron launch blocked by caller deny policy: ${denyEntry}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (options.allow && options.allow.length > 0) {
|
|
54
|
+
const allowEntry = options.allow.find((entry) => policyEntryMatchesApp(entry, options.target));
|
|
55
|
+
if (!allowEntry) {
|
|
56
|
+
return {
|
|
57
|
+
list: "allow",
|
|
58
|
+
message: "Electron launch blocked because the resolved app did not match caller allow policy.",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
export async function resolveElectronLaunchTarget(options) {
|
|
65
|
+
if (options.appPath)
|
|
66
|
+
return inspectElectronAppPath(options.appPath);
|
|
67
|
+
if (options.executablePath)
|
|
68
|
+
return inspectElectronExecutablePath(options.executablePath);
|
|
69
|
+
const query = options.bundleId ?? options.appName;
|
|
70
|
+
const discovery = await discoverElectronApps({ maxResults: 200, query });
|
|
71
|
+
if (options.bundleId) {
|
|
72
|
+
const normalizedBundleId = normalizeIdentifier(options.bundleId);
|
|
73
|
+
return discovery.apps.find((app) => normalizeIdentifier(app.bundleId) === normalizedBundleId);
|
|
74
|
+
}
|
|
75
|
+
if (options.appName) {
|
|
76
|
+
const normalizedName = normalizeIdentifier(options.appName);
|
|
77
|
+
return discovery.apps.find((app) => normalizeIdentifier(app.name) === normalizedName) ?? discovery.apps[0];
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
function targetMatchesType(target, targetType) {
|
|
82
|
+
return targetType === undefined || targetType === "any" || target.type === targetType;
|
|
83
|
+
}
|
|
84
|
+
function selectElectronConnectArg(options) {
|
|
85
|
+
const targetWebSocket = options.targets.find((target) => targetMatchesType(target, options.targetType) && target.webSocketDebuggerUrl)?.webSocketDebuggerUrl;
|
|
86
|
+
return targetWebSocket ?? options.version.webSocketDebuggerUrl ?? String(options.port);
|
|
87
|
+
}
|
|
88
|
+
async function readDevToolsActivePort(userDataDir) {
|
|
89
|
+
const path = `${userDataDir}/${DEVTOOLS_ACTIVE_PORT_FILE}`;
|
|
90
|
+
try {
|
|
91
|
+
const text = await readFile(path, "utf8");
|
|
92
|
+
const [portLine] = text.split(/\r?\n/);
|
|
93
|
+
const port = Number(portLine?.trim());
|
|
94
|
+
return {
|
|
95
|
+
found: true,
|
|
96
|
+
path,
|
|
97
|
+
port: Number.isSafeInteger(port) && port > 0 && port <= 65_535 ? port : undefined,
|
|
98
|
+
...(Number.isSafeInteger(port) && port > 0 && port <= 65_535 ? {} : { error: "DevToolsActivePort did not contain a valid TCP port." }),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const code = error.code;
|
|
103
|
+
return {
|
|
104
|
+
error: code && code !== "ENOENT" ? `${code}: ${error instanceof Error ? error.message : String(error)}` : undefined,
|
|
105
|
+
found: false,
|
|
106
|
+
path,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function pollDevToolsActivePort(options) {
|
|
111
|
+
let devToolsActivePort;
|
|
112
|
+
while (Date.now() <= options.deadlineMs) {
|
|
113
|
+
const spawnError = options.getSpawnError();
|
|
114
|
+
if (spawnError)
|
|
115
|
+
return { devToolsActivePort, failure: "spawn-error", spawnError };
|
|
116
|
+
devToolsActivePort = await readDevToolsActivePort(options.userDataDir);
|
|
117
|
+
if (devToolsActivePort.port)
|
|
118
|
+
return { devToolsActivePort, port: devToolsActivePort.port };
|
|
119
|
+
const exit = options.getChildExit();
|
|
120
|
+
if (exit.code !== null || exit.signal !== null) {
|
|
121
|
+
return { devToolsActivePort, failure: exit.code === 0 ? "single-instance-conflict" : "spawn-error" };
|
|
122
|
+
}
|
|
123
|
+
await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
|
|
124
|
+
}
|
|
125
|
+
return { devToolsActivePort, failure: "timeout" };
|
|
126
|
+
}
|
|
127
|
+
async function pollCdpMetadata(port, deadlineMs) {
|
|
128
|
+
while (Date.now() <= deadlineMs) {
|
|
129
|
+
const version = parseCdpVersion(await fetchCdpJson(`http://127.0.0.1:${port}/json/version`));
|
|
130
|
+
if (version) {
|
|
131
|
+
const targets = parseCdpTargets(await fetchCdpJson(`http://127.0.0.1:${port}/json/list`));
|
|
132
|
+
return { targets, version };
|
|
133
|
+
}
|
|
134
|
+
await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
function buildLaunchArgs(userDataDir, appArgs) {
|
|
139
|
+
return [
|
|
140
|
+
...appArgs,
|
|
141
|
+
`--user-data-dir=${userDataDir}`,
|
|
142
|
+
"--remote-debugging-port=0",
|
|
143
|
+
...ELECTRON_DEFAULT_APP_ARGS,
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
async function waitForLaunchChildExit(child, deadlineMs) {
|
|
147
|
+
while (Date.now() <= deadlineMs) {
|
|
148
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
149
|
+
return true;
|
|
150
|
+
await sleep(50);
|
|
151
|
+
}
|
|
152
|
+
return child.exitCode !== null || child.signalCode !== null;
|
|
153
|
+
}
|
|
154
|
+
function isLaunchChildPidAlive(child) {
|
|
155
|
+
if (!child.pid)
|
|
156
|
+
return undefined;
|
|
157
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
158
|
+
return false;
|
|
159
|
+
try {
|
|
160
|
+
process.kill(child.pid, 0);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return error.code === "EPERM";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function terminateLaunchChild(child) {
|
|
168
|
+
if (!child.pid || child.exitCode !== null || child.signalCode !== null)
|
|
169
|
+
return undefined;
|
|
170
|
+
try {
|
|
171
|
+
child.kill("SIGTERM");
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
return error instanceof Error ? error.message : String(error);
|
|
175
|
+
}
|
|
176
|
+
if (await waitForLaunchChildExit(child, Date.now() + 1_000))
|
|
177
|
+
return undefined;
|
|
178
|
+
try {
|
|
179
|
+
child.kill("SIGKILL");
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return error instanceof Error ? error.message : String(error);
|
|
183
|
+
}
|
|
184
|
+
if (await waitForLaunchChildExit(child, Date.now() + 1_000))
|
|
185
|
+
return undefined;
|
|
186
|
+
return `PID ${child.pid} remained alive after failed Electron launch cleanup.`;
|
|
187
|
+
}
|
|
188
|
+
function buildLaunchRecord(options) {
|
|
189
|
+
return {
|
|
190
|
+
appName: options.target.name,
|
|
191
|
+
appPath: options.target.appPath,
|
|
192
|
+
bundleId: options.target.bundleId,
|
|
193
|
+
cleanupState: "active",
|
|
194
|
+
createdAtMs: options.createdAtMs,
|
|
195
|
+
desktopId: options.target.desktopId,
|
|
196
|
+
executablePath: options.target.executablePath,
|
|
197
|
+
launchId: `electron-${randomUUID()}`,
|
|
198
|
+
launchedByWrapper: true,
|
|
199
|
+
packageSource: options.target.packageSource,
|
|
200
|
+
pid: options.pid,
|
|
201
|
+
platform: options.target.platform,
|
|
202
|
+
port: options.port,
|
|
203
|
+
processGroupId: process.platform === "win32" ? undefined : options.pid,
|
|
204
|
+
targetType: options.targetType,
|
|
205
|
+
userDataDir: options.userDataDir,
|
|
206
|
+
version: ELECTRON_LAUNCH_RECORD_VERSION,
|
|
207
|
+
webSocketDebuggerUrl: options.version.webSocketDebuggerUrl,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function launchFailureMessage(reason, target, detail) {
|
|
211
|
+
const label = target ? `${target.name} (${target.appPath ?? target.executablePath})` : "target";
|
|
212
|
+
switch (reason) {
|
|
213
|
+
case "non-electron-target":
|
|
214
|
+
return `Electron launch rejected: ${label} does not have Electron framework evidence.`;
|
|
215
|
+
case "policy-blocked":
|
|
216
|
+
return detail ?? `Electron launch blocked by caller policy for ${label}.`;
|
|
217
|
+
case "single-instance-conflict":
|
|
218
|
+
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.`;
|
|
219
|
+
case "port-not-found":
|
|
220
|
+
return `Electron launch found a DevToolsActivePort for ${label}, but /json/version never returned a valid CDP payload.`;
|
|
221
|
+
case "spawn-error":
|
|
222
|
+
return `Electron launch failed while starting ${label}${detail ? `: ${detail}` : "."}`;
|
|
223
|
+
case "timeout":
|
|
224
|
+
return `Electron launch timed out waiting for DevToolsActivePort for ${label}.`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export async function launchElectronApp(options) {
|
|
228
|
+
const appArgs = options.appArgs ?? [];
|
|
229
|
+
const target = await resolveElectronLaunchTarget(options);
|
|
230
|
+
if (!target) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
failure: {
|
|
234
|
+
appArgs,
|
|
235
|
+
error: launchFailureMessage("non-electron-target", undefined),
|
|
236
|
+
reason: "non-electron-target",
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const policy = evaluateElectronLaunchPolicy({ allow: options.allow, deny: options.deny, target });
|
|
241
|
+
if (policy) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
failure: {
|
|
245
|
+
appArgs,
|
|
246
|
+
error: launchFailureMessage("policy-blocked", target, policy.message),
|
|
247
|
+
policy,
|
|
248
|
+
reason: "policy-blocked",
|
|
249
|
+
target,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const timeoutMs = normalizeTimeoutMs(options.timeoutMs);
|
|
254
|
+
const startedAtMs = Date.now();
|
|
255
|
+
const deadlineMs = startedAtMs + timeoutMs;
|
|
256
|
+
const userDataDir = await createSecureTempDirectory(ELECTRON_PROFILE_DIR_PREFIX);
|
|
257
|
+
let cleanupError;
|
|
258
|
+
let spawnError;
|
|
259
|
+
let exitCode = null;
|
|
260
|
+
let exitSignal = null;
|
|
261
|
+
const args = buildLaunchArgs(userDataDir, appArgs);
|
|
262
|
+
const child = spawn(target.executablePath, args, {
|
|
263
|
+
cwd: dirname(target.executablePath),
|
|
264
|
+
detached: process.platform !== "win32",
|
|
265
|
+
stdio: "ignore",
|
|
266
|
+
});
|
|
267
|
+
child.once("error", (error) => {
|
|
268
|
+
spawnError = error;
|
|
269
|
+
});
|
|
270
|
+
child.once("exit", (code, signal) => {
|
|
271
|
+
exitCode = code;
|
|
272
|
+
exitSignal = signal;
|
|
273
|
+
});
|
|
274
|
+
child.unref();
|
|
275
|
+
const buildFailureDiagnostics = (options = {}) => ({
|
|
276
|
+
cdpVersionReached: options.cdpVersionReached,
|
|
277
|
+
devToolsActivePort: options.devToolsActivePort,
|
|
278
|
+
elapsedMs: Math.max(0, Date.now() - startedAtMs),
|
|
279
|
+
exitCode,
|
|
280
|
+
exitSignal,
|
|
281
|
+
outputCaptured: false,
|
|
282
|
+
pid: child.pid,
|
|
283
|
+
pidAlive: isLaunchChildPidAlive(child),
|
|
284
|
+
port: options.port ?? options.devToolsActivePort?.port,
|
|
285
|
+
timeoutMs,
|
|
286
|
+
userDataDir,
|
|
287
|
+
});
|
|
288
|
+
const fail = async (reason, detail, diagnosticOptions) => {
|
|
289
|
+
const diagnostics = buildFailureDiagnostics(diagnosticOptions);
|
|
290
|
+
const processCleanupError = await terminateLaunchChild(child);
|
|
291
|
+
try {
|
|
292
|
+
await rm(userDataDir, { force: true, recursive: true });
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
cleanupError = error instanceof Error ? error.message : String(error);
|
|
296
|
+
}
|
|
297
|
+
cleanupError = [processCleanupError, cleanupError].filter((value) => value !== undefined).join("; ") || undefined;
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
failure: {
|
|
301
|
+
appArgs,
|
|
302
|
+
cleanupError,
|
|
303
|
+
diagnostics,
|
|
304
|
+
error: launchFailureMessage(reason, target, detail),
|
|
305
|
+
reason,
|
|
306
|
+
target,
|
|
307
|
+
userDataDir,
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
const portResult = await pollDevToolsActivePort({
|
|
312
|
+
deadlineMs,
|
|
313
|
+
getChildExit: () => ({ code: exitCode, signal: exitSignal }),
|
|
314
|
+
getSpawnError: () => spawnError,
|
|
315
|
+
userDataDir,
|
|
316
|
+
});
|
|
317
|
+
if (!portResult.port) {
|
|
318
|
+
return fail(portResult.failure ?? "timeout", portResult.spawnError?.message, { devToolsActivePort: portResult.devToolsActivePort });
|
|
319
|
+
}
|
|
320
|
+
const metadata = await pollCdpMetadata(portResult.port, deadlineMs);
|
|
321
|
+
if (!metadata) {
|
|
322
|
+
return fail("port-not-found", undefined, { cdpVersionReached: false, devToolsActivePort: portResult.devToolsActivePort, port: portResult.port });
|
|
323
|
+
}
|
|
324
|
+
const record = buildLaunchRecord({
|
|
325
|
+
createdAtMs: Date.now(),
|
|
326
|
+
pid: child.pid,
|
|
327
|
+
port: portResult.port,
|
|
328
|
+
target,
|
|
329
|
+
targetType: options.targetType,
|
|
330
|
+
userDataDir,
|
|
331
|
+
version: metadata.version,
|
|
332
|
+
});
|
|
333
|
+
const connectArg = selectElectronConnectArg({
|
|
334
|
+
port: portResult.port,
|
|
335
|
+
targets: metadata.targets,
|
|
336
|
+
targetType: options.targetType,
|
|
337
|
+
version: metadata.version,
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
ok: true,
|
|
341
|
+
value: {
|
|
342
|
+
appArgs,
|
|
343
|
+
child,
|
|
344
|
+
connectArg,
|
|
345
|
+
record,
|
|
346
|
+
target,
|
|
347
|
+
targets: metadata.targets,
|
|
348
|
+
version: metadata.version,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Usage: Imported by Electron host probes and browser-run Electron diagnostics.
|
|
6
6
|
* Invariants/Assumptions: Empty or whitespace-only strings stay omitted, and truncation keeps the prior ellipsis behavior.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
export function boundElectronProbeString(value, maxLength = 240) {
|
|
9
|
+
const trimmed = value?.trim();
|
|
10
|
+
if (!trimmed)
|
|
11
|
+
return undefined;
|
|
12
|
+
return trimmed.length > maxLength ? `${trimmed.slice(0, Math.max(0, maxLength - 3))}...` : trimmed;
|
|
13
13
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { access, stat } from "node:fs/promises";
|
|
3
|
+
import { delimiter, join } from "node:path";
|
|
4
|
+
export async function executableExistsOnPath(command) {
|
|
5
|
+
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
|
|
6
|
+
for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
7
|
+
for (const extension of extensions) {
|
|
8
|
+
try {
|
|
9
|
+
const candidate = join(directory, `${command}${extension}`);
|
|
10
|
+
await access(candidate, fsConstants.X_OK);
|
|
11
|
+
if ((await stat(candidate)).isFile())
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Try the next PATH candidate.
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { access, stat } from "node:fs/promises";
|
|
2
|
+
export async function pathExists(path) {
|
|
3
|
+
try {
|
|
4
|
+
await access(path);
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function directoryExists(path) {
|
|
12
|
+
try {
|
|
13
|
+
return (await stat(path)).isDirectory();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Compile top-level Electron wrapper inputs into validated Electron actions.
|
|
3
|
+
* Responsibilities: Enforce action-specific fields, launch-target rules, and wrapper-owned flag safety.
|
|
4
|
+
* Scope: Electron input-mode validation only; launch/probe/cleanup execution stays in the extension entrypoint.
|
|
5
|
+
*/
|
|
6
|
+
import { isRecord } from "../parsing.js";
|
|
7
|
+
import { AGENT_BROWSER_ELECTRON_ACTIONS, AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_LIST_FIELDS, AGENT_BROWSER_ELECTRON_PROBE_FIELDS, AGENT_BROWSER_ELECTRON_RESERVED_APP_ARGS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, } from "./types.js";
|
|
8
|
+
function validateOptionalNonEmptyString(input, fieldName) {
|
|
9
|
+
const value = input[fieldName];
|
|
10
|
+
if (value === undefined)
|
|
11
|
+
return {};
|
|
12
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
13
|
+
return { error: `electron.${fieldName} must be a non-empty string when provided.` };
|
|
14
|
+
}
|
|
15
|
+
return { value: value.trim() };
|
|
16
|
+
}
|
|
17
|
+
function validateOptionalElectronStringArray(input, fieldName) {
|
|
18
|
+
const value = input[fieldName];
|
|
19
|
+
if (value === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim().length === 0)) {
|
|
22
|
+
return `electron.${fieldName} must be an array of non-empty strings when provided.`;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function validateOptionalElectronEnum(input, fieldName, values) {
|
|
27
|
+
const value = input[fieldName];
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
31
|
+
return `electron.${fieldName} must be one of: ${values.join(", ")}.`;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function getReservedElectronAppArg(appArgs) {
|
|
36
|
+
return appArgs?.find((arg) => {
|
|
37
|
+
const trimmed = arg.trim();
|
|
38
|
+
return trimmed === "--" || AGENT_BROWSER_ELECTRON_RESERVED_APP_ARGS.some((reserved) => trimmed === reserved || trimmed.startsWith(`${reserved}=`));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function validateElectronLaunchAppArgs(appArgs) {
|
|
42
|
+
const reservedArg = getReservedElectronAppArg(appArgs);
|
|
43
|
+
return reservedArg
|
|
44
|
+
? `electron.appArgs must not include wrapper-owned launch flag ${reservedArg}.`
|
|
45
|
+
: undefined;
|
|
46
|
+
}
|
|
47
|
+
function validateOptionalElectronPositiveInteger(input, fieldName) {
|
|
48
|
+
const value = input[fieldName];
|
|
49
|
+
if (value === undefined)
|
|
50
|
+
return {};
|
|
51
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
52
|
+
return { error: `electron.${fieldName} must be a positive integer when provided.` };
|
|
53
|
+
}
|
|
54
|
+
return { value };
|
|
55
|
+
}
|
|
56
|
+
function onlyAllowedElectronFields(input, action, allowedFields) {
|
|
57
|
+
return Object.keys(input).find((fieldName) => !allowedFields.has(fieldName))
|
|
58
|
+
? `electron.${action} does not support electron.${Object.keys(input).find((fieldName) => !allowedFields.has(fieldName))}.`
|
|
59
|
+
: undefined;
|
|
60
|
+
}
|
|
61
|
+
export function compileAgentBrowserElectron(input) {
|
|
62
|
+
if (!isRecord(input))
|
|
63
|
+
return { error: "electron must be an object." };
|
|
64
|
+
const action = input.action;
|
|
65
|
+
if (typeof action !== "string" || !AGENT_BROWSER_ELECTRON_ACTIONS.includes(action)) {
|
|
66
|
+
return { error: `electron.action must be one of: ${AGENT_BROWSER_ELECTRON_ACTIONS.join(", ")}.` };
|
|
67
|
+
}
|
|
68
|
+
for (const fieldName of ["query", "appPath", "appName", "bundleId", "executablePath", "launchId"]) {
|
|
69
|
+
const validation = validateOptionalNonEmptyString(input, fieldName);
|
|
70
|
+
if (validation.error)
|
|
71
|
+
return { error: validation.error };
|
|
72
|
+
}
|
|
73
|
+
for (const fieldName of ["appArgs", "allow", "deny"]) {
|
|
74
|
+
const error = validateOptionalElectronStringArray(input, fieldName);
|
|
75
|
+
if (error)
|
|
76
|
+
return { error };
|
|
77
|
+
}
|
|
78
|
+
const handoffError = validateOptionalElectronEnum(input, "handoff", AGENT_BROWSER_ELECTRON_HANDOFFS);
|
|
79
|
+
if (handoffError)
|
|
80
|
+
return { error: handoffError };
|
|
81
|
+
const targetTypeError = validateOptionalElectronEnum(input, "targetType", AGENT_BROWSER_ELECTRON_TARGET_TYPES);
|
|
82
|
+
if (targetTypeError)
|
|
83
|
+
return { error: targetTypeError };
|
|
84
|
+
for (const fieldName of ["maxResults", "timeoutMs"]) {
|
|
85
|
+
const validation = validateOptionalElectronPositiveInteger(input, fieldName);
|
|
86
|
+
if (validation.error)
|
|
87
|
+
return { error: validation.error };
|
|
88
|
+
}
|
|
89
|
+
if (input.all !== undefined && input.all !== true) {
|
|
90
|
+
return { error: "electron.all must be true when provided." };
|
|
91
|
+
}
|
|
92
|
+
if (action === "list") {
|
|
93
|
+
const unsupportedListField = Object.keys(input).find((fieldName) => !AGENT_BROWSER_ELECTRON_LIST_FIELDS.has(fieldName));
|
|
94
|
+
if (unsupportedListField) {
|
|
95
|
+
return { error: `electron.list only supports query and maxResults; remove electron.${unsupportedListField}.` };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
compiled: {
|
|
99
|
+
action: "list",
|
|
100
|
+
maxResults: validateOptionalElectronPositiveInteger(input, "maxResults").value,
|
|
101
|
+
query: validateOptionalNonEmptyString(input, "query").value,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (action === "probe") {
|
|
106
|
+
const unsupportedProbeField = Object.keys(input).find((fieldName) => !AGENT_BROWSER_ELECTRON_PROBE_FIELDS.has(fieldName));
|
|
107
|
+
if (unsupportedProbeField) {
|
|
108
|
+
return { error: `electron.probe only supports action, launchId, and timeoutMs; remove electron.${unsupportedProbeField}.` };
|
|
109
|
+
}
|
|
110
|
+
const launchId = validateOptionalNonEmptyString(input, "launchId").value;
|
|
111
|
+
const timeoutMs = validateOptionalElectronPositiveInteger(input, "timeoutMs").value;
|
|
112
|
+
return {
|
|
113
|
+
compiled: {
|
|
114
|
+
action: "probe",
|
|
115
|
+
...(launchId ? { launchId } : {}),
|
|
116
|
+
...(timeoutMs ? { timeoutMs } : {}),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (action === "launch") {
|
|
121
|
+
const allowedFields = new Set(["action", "allow", "appArgs", "appName", "appPath", "bundleId", "deny", "executablePath", "handoff", "targetType", "timeoutMs"]);
|
|
122
|
+
const unsupportedFieldError = onlyAllowedElectronFields(input, action, allowedFields);
|
|
123
|
+
if (unsupportedFieldError)
|
|
124
|
+
return { error: unsupportedFieldError };
|
|
125
|
+
const appArgs = input.appArgs?.map((item) => item.trim());
|
|
126
|
+
const appArgsError = validateElectronLaunchAppArgs(appArgs);
|
|
127
|
+
if (appArgsError)
|
|
128
|
+
return { error: appArgsError };
|
|
129
|
+
const targetFields = ["appPath", "appName", "bundleId", "executablePath"];
|
|
130
|
+
const providedTargets = targetFields.filter((fieldName) => input[fieldName] !== undefined);
|
|
131
|
+
if (providedTargets.length !== 1) {
|
|
132
|
+
return { error: "electron.launch requires exactly one of appPath, appName, bundleId, or executablePath." };
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
compiled: {
|
|
136
|
+
action: "launch",
|
|
137
|
+
allow: input.allow?.map((item) => item.trim()),
|
|
138
|
+
appArgs,
|
|
139
|
+
deny: input.deny?.map((item) => item.trim()),
|
|
140
|
+
appName: validateOptionalNonEmptyString(input, "appName").value,
|
|
141
|
+
appPath: validateOptionalNonEmptyString(input, "appPath").value,
|
|
142
|
+
bundleId: validateOptionalNonEmptyString(input, "bundleId").value,
|
|
143
|
+
executablePath: validateOptionalNonEmptyString(input, "executablePath").value,
|
|
144
|
+
handoff: input.handoff ?? "snapshot",
|
|
145
|
+
targetType: input.targetType ?? "page",
|
|
146
|
+
timeoutMs: validateOptionalElectronPositiveInteger(input, "timeoutMs").value,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const allowedFields = new Set(["action", "all", "launchId", "timeoutMs"]);
|
|
151
|
+
const unsupportedFieldError = onlyAllowedElectronFields(input, action, allowedFields);
|
|
152
|
+
if (unsupportedFieldError)
|
|
153
|
+
return { error: unsupportedFieldError };
|
|
154
|
+
if (input.all === true && input.launchId !== undefined) {
|
|
155
|
+
return { error: `electron.${action} accepts launchId or all, not both.` };
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
compiled: {
|
|
159
|
+
action: action,
|
|
160
|
+
all: input.all === true || undefined,
|
|
161
|
+
launchId: validateOptionalNonEmptyString(input, "launchId").value,
|
|
162
|
+
timeoutMs: validateOptionalElectronPositiveInteger(input, "timeoutMs").value,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|