pi-agent-browser-native 0.2.34 → 0.2.35

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 (38) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +14 -14
  3. package/docs/ARCHITECTURE.md +19 -13
  4. package/docs/COMMAND_REFERENCE.md +257 -42
  5. package/docs/ELECTRON.md +3 -3
  6. package/docs/RELEASE.md +11 -11
  7. package/docs/REQUIREMENTS.md +5 -5
  8. package/docs/SUPPORT_MATRIX.md +23 -21
  9. package/docs/TOOL_CONTRACT.md +38 -27
  10. package/extensions/agent-browser/index.ts +518 -2402
  11. package/extensions/agent-browser/lib/argv-descriptor.ts +90 -0
  12. package/extensions/agent-browser/lib/argv-grammar.ts +128 -0
  13. package/extensions/agent-browser/lib/command-policy.ts +71 -0
  14. package/extensions/agent-browser/lib/command-taxonomy.ts +336 -0
  15. package/extensions/agent-browser/lib/electron/cleanup.ts +1 -0
  16. package/extensions/agent-browser/lib/executable-path.ts +19 -0
  17. package/extensions/agent-browser/lib/input-modes/params.ts +6 -6
  18. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +65 -0
  19. package/extensions/agent-browser/lib/orchestration/browser-run/browser-action-model.ts +154 -0
  20. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +149 -0
  21. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +10 -28
  22. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +6 -2
  23. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +33 -27
  24. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +48 -22
  25. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +33 -10
  26. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +93 -0
  27. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +19 -123
  28. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +26 -1
  29. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +860 -0
  30. package/extensions/agent-browser/lib/playbook.ts +9 -9
  31. package/extensions/agent-browser/lib/prompt-policy.ts +122 -0
  32. package/extensions/agent-browser/lib/results/action-recommendations.ts +3 -23
  33. package/extensions/agent-browser/lib/results/presentation/navigation.ts +2 -34
  34. package/extensions/agent-browser/lib/runtime.ts +93 -227
  35. package/extensions/agent-browser/lib/session-page-state.ts +31 -14
  36. package/extensions/agent-browser/lib/temp.ts +148 -23
  37. package/package.json +4 -4
  38. package/scripts/agent-browser-capability-baseline.mjs +198 -1
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Purpose: Parse raw agent-browser argv once into a stable command descriptor for planners and policy.
3
+ * Responsibilities: Own command-token extraction, command/subcommand identification, and descriptor construction.
4
+ * Scope: Pure argv parsing; runtime planning and session policy consume descriptors instead of re-parsing tokens.
5
+ */
6
+
7
+ import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "./argv-grammar.js";
8
+ import { isOpenNavigationCommand } from "./command-taxonomy.js";
9
+
10
+ export interface CommandInfo {
11
+ command?: string;
12
+ subcommand?: string;
13
+ }
14
+
15
+ export interface ArgvDescriptor {
16
+ commandInfo: CommandInfo;
17
+ commandTokens: string[];
18
+ }
19
+
20
+ function isBooleanLiteral(token: string | undefined): boolean {
21
+ const normalized = token?.trim().toLowerCase();
22
+ return normalized === "true" || normalized === "false";
23
+ }
24
+
25
+ export function findCommandStartIndex(args: string[]): number | undefined {
26
+ for (let index = 0; index < args.length; index += 1) {
27
+ const token = args[index];
28
+ if (token.startsWith("--session=")) {
29
+ continue;
30
+ }
31
+ if (token.startsWith("-")) {
32
+ const normalizedToken = token.split("=", 1)[0] ?? token;
33
+ if (VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
34
+ index += 1;
35
+ } else if (
36
+ GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(normalizedToken) &&
37
+ !token.includes("=") &&
38
+ isBooleanLiteral(args[index + 1])
39
+ ) {
40
+ index += 1;
41
+ }
42
+ continue;
43
+ }
44
+ return index;
45
+ }
46
+ return undefined;
47
+ }
48
+
49
+ export function extractCommandTokens(args: string[]): string[] {
50
+ const commandStartIndex = findCommandStartIndex(args);
51
+ return commandStartIndex === undefined ? [] : args.slice(commandStartIndex);
52
+ }
53
+
54
+ function getOpenCommandTarget(commandTokens: string[]): string | undefined {
55
+ for (let index = 1; index < commandTokens.length; index += 1) {
56
+ const token = commandTokens[index];
57
+ if (token === "--init-script" || token === "--enable") {
58
+ index += 1;
59
+ continue;
60
+ }
61
+ if (token.startsWith("--init-script=") || token.startsWith("--enable=")) {
62
+ continue;
63
+ }
64
+ if (token.startsWith("-")) {
65
+ continue;
66
+ }
67
+ return token;
68
+ }
69
+ return undefined;
70
+ }
71
+
72
+ export function parseCommandInfoFromTokens(commandTokens: string[]): CommandInfo {
73
+ const command = commandTokens[0];
74
+ return {
75
+ command,
76
+ subcommand: isOpenNavigationCommand(command) ? getOpenCommandTarget(commandTokens) : commandTokens[1],
77
+ };
78
+ }
79
+
80
+ export function parseCommandInfo(args: string[]): CommandInfo {
81
+ return parseCommandInfoFromTokens(extractCommandTokens(args));
82
+ }
83
+
84
+ export function parseArgvDescriptor(args: string[]): ArgvDescriptor {
85
+ const commandTokens = extractCommandTokens(args);
86
+ return {
87
+ commandInfo: parseCommandInfoFromTokens(commandTokens),
88
+ commandTokens,
89
+ };
90
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Purpose: Shared argv flag-shape metadata and helpers for command discovery and sessionless policy checks.
3
+ * Responsibilities: Own global/command value-flag sets and boolean/value-flag validation used during argv parsing.
4
+ * Scope: Pure token grammar; command semantics and subprocess execution live elsewhere.
5
+ */
6
+
7
+ export const GLOBAL_VALUE_FLAGS = [
8
+ "--session",
9
+ "--cdp",
10
+ "--config",
11
+ "--profile",
12
+ "--session-name",
13
+ "--proxy",
14
+ "--proxy-bypass",
15
+ "--headers",
16
+ "--executable-path",
17
+ "--extension",
18
+ "--init-script",
19
+ "--enable",
20
+ "--provider",
21
+ "-p",
22
+ "--engine",
23
+ "--state",
24
+ "--download-path",
25
+ "--screenshot-dir",
26
+ "--screenshot-format",
27
+ "--screenshot-quality",
28
+ "--color-scheme",
29
+ "--device",
30
+ "--args",
31
+ "--user-agent",
32
+ "--allowed-domains",
33
+ "--action-policy",
34
+ "--confirm-actions",
35
+ "--max-output",
36
+ "--model",
37
+ "--idle-timeout",
38
+ ] as const;
39
+
40
+ export const COMMAND_VALUE_FLAGS = [
41
+ "--baseline",
42
+ "--body",
43
+ "--categories",
44
+ "--curl",
45
+ "--depth",
46
+ "-d",
47
+ "--domain",
48
+ "--expires",
49
+ "--filter",
50
+ "--fn",
51
+ "--label",
52
+ "--load",
53
+ "--method",
54
+ "--name",
55
+ "--older-than",
56
+ "--output",
57
+ "--path",
58
+ "--port",
59
+ "--resource-type",
60
+ "--resource-types",
61
+ "--sameSite",
62
+ "--selector",
63
+ "-s",
64
+ "--status",
65
+ "--text",
66
+ "--threshold",
67
+ "--timeout",
68
+ "--type",
69
+ "--url",
70
+ "--username",
71
+ "--password",
72
+ "--wait-until",
73
+ ] as const;
74
+
75
+ export const VALUE_FLAGS: ReadonlySet<string> = new Set([...GLOBAL_VALUE_FLAGS, ...COMMAND_VALUE_FLAGS]);
76
+ export const PREVALIDATED_VALUE_FLAGS: ReadonlySet<string> = new Set(GLOBAL_VALUE_FLAGS);
77
+ export const GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE: ReadonlySet<string> = new Set(["--args"]);
78
+ export const GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES: ReadonlySet<string> = new Set([
79
+ "--allow-file-access",
80
+ "--annotate",
81
+ "--auto-connect",
82
+ "--confirm-interactive",
83
+ "--content-boundaries",
84
+ "--debug",
85
+ "--headed",
86
+ "--ignore-https-errors",
87
+ "--json",
88
+ "--no-auto-dialog",
89
+ "--quiet",
90
+ "-q",
91
+ "--verbose",
92
+ "-v",
93
+ ]);
94
+
95
+ export function getFlagName(token: string): string {
96
+ return token.split("=", 1)[0] ?? token;
97
+ }
98
+
99
+ export function isNonFlagToken(token: string | undefined): token is string {
100
+ return typeof token === "string" && !token.startsWith("-");
101
+ }
102
+
103
+ export function hasOnlyBooleanFlags(tokens: readonly string[], allowedFlags: ReadonlySet<string>): boolean {
104
+ return tokens.every((token) => token.startsWith("-") && allowedFlags.has(getFlagName(token)));
105
+ }
106
+
107
+ export function hasOnlyOptionFlags(
108
+ tokens: readonly string[],
109
+ allowedBooleanFlags: ReadonlySet<string>,
110
+ allowedValueFlags: ReadonlySet<string>,
111
+ ): boolean {
112
+ for (let index = 0; index < tokens.length; index += 1) {
113
+ const token = tokens[index];
114
+ if (!token.startsWith("-")) return false;
115
+ const flagName = getFlagName(token);
116
+ if (allowedBooleanFlags.has(flagName)) continue;
117
+ if (!allowedValueFlags.has(flagName)) return false;
118
+ if (token.includes("=")) continue;
119
+ const value = tokens[index + 1];
120
+ if (!isNonFlagToken(value)) return false;
121
+ index += 1;
122
+ }
123
+ return true;
124
+ }
125
+
126
+ export function stripSessionlessShapeGlobalFlags(commandTokens: readonly string[]): string[] {
127
+ return commandTokens.filter((token) => token !== "--json");
128
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Purpose: Own upstream command-shape policies that decide whether the wrapper should allocate a managed browser session.
3
+ * Responsibilities: Keep local/sessionless command grammar out of the runtime execution planner while preserving exact upstream shapes.
4
+ * Scope: Pure argv-token policy; command discovery, subprocess execution, and presentation live in focused modules.
5
+ */
6
+
7
+ import type { ArgvDescriptor } from "./argv-descriptor.js";
8
+ import { hasOnlyBooleanFlags, hasOnlyOptionFlags, isNonFlagToken, stripSessionlessShapeGlobalFlags } from "./argv-grammar.js";
9
+
10
+ const SESSIONLESS_AUTH_SUBCOMMANDS = new Set(["save", "list", "show", "delete", "remove"]);
11
+ const EMPTY_BOOLEAN_FLAGS = new Set<string>();
12
+ const JSON_BOOLEAN_FLAGS = new Set(["--json"]);
13
+ const AUTH_SAVE_BOOLEAN_FLAGS = new Set(["--json", "--password-stdin"]);
14
+ const AUTH_SAVE_VALUE_FLAGS = new Set(["--password", "--password-selector", "--submit-selector", "--url", "--username", "--username-selector"]);
15
+ const DASHBOARD_SUBCOMMANDS = new Set(["start", "stop"]);
16
+ const DASHBOARD_START_VALUE_FLAGS = new Set(["--port"]);
17
+ const DOCTOR_BOOLEAN_FLAGS = new Set(["--fix", "--json", "--offline", "--quick"]);
18
+ const INSTALL_BOOLEAN_FLAGS = new Set(["--with-deps", "-d"]);
19
+ const STATE_SESSIONLESS_SUBCOMMANDS = new Set(["list", "show", "clear", "clean", "rename"]);
20
+ const STATE_CLEAN_VALUE_FLAGS = new Set(["--older-than"]);
21
+
22
+ function isSessionlessAuthCommand(commandTokens: readonly string[]): boolean {
23
+ const [, subcommand, target, ...rest] = commandTokens;
24
+ if (!SESSIONLESS_AUTH_SUBCOMMANDS.has(subcommand ?? "")) return false;
25
+ if (subcommand === "list") return target === undefined;
26
+ if (!isNonFlagToken(target)) return false;
27
+ if (subcommand === "save") return hasOnlyOptionFlags(rest, AUTH_SAVE_BOOLEAN_FLAGS, AUTH_SAVE_VALUE_FLAGS);
28
+ return rest.length === 0;
29
+ }
30
+
31
+ function isSessionlessDashboardCommand(commandTokens: readonly string[]): boolean {
32
+ const [, subcommand, ...rest] = commandTokens;
33
+ if (subcommand === undefined) return true;
34
+ if (!DASHBOARD_SUBCOMMANDS.has(subcommand)) return false;
35
+ return subcommand === "start" ? hasOnlyOptionFlags(rest, JSON_BOOLEAN_FLAGS, DASHBOARD_START_VALUE_FLAGS) : rest.length === 0;
36
+ }
37
+
38
+ function isSessionlessStateCommand(commandTokens: readonly string[]): boolean {
39
+ const [, subcommand, firstArg, secondArg, ...rest] = commandTokens;
40
+ if (!STATE_SESSIONLESS_SUBCOMMANDS.has(subcommand ?? "")) return false;
41
+ if (subcommand === "list") return firstArg === undefined;
42
+ if (subcommand === "show") return isNonFlagToken(firstArg) && secondArg === undefined;
43
+ if (subcommand === "rename") return isNonFlagToken(firstArg) && isNonFlagToken(secondArg) && rest.length === 0;
44
+ if (subcommand === "clean") {
45
+ const optionTokens = commandTokens.slice(2);
46
+ return optionTokens.length > 0 && hasOnlyOptionFlags(optionTokens, EMPTY_BOOLEAN_FLAGS, STATE_CLEAN_VALUE_FLAGS);
47
+ }
48
+ if (subcommand !== "clear") return false;
49
+ if ((firstArg === "--all" || firstArg === "-a") && secondArg === undefined) return true;
50
+ if (!isNonFlagToken(firstArg)) return false;
51
+ return secondArg === undefined || (secondArg === "--all" && rest.length === 0);
52
+ }
53
+
54
+ function isSessionlessCommand(commandTokens: readonly string[]): boolean {
55
+ const normalizedTokens = stripSessionlessShapeGlobalFlags(commandTokens);
56
+ const [command, subcommand] = normalizedTokens;
57
+ if (command === "skills") return ["list", "get", "path"].includes(subcommand ?? "");
58
+ if (command === "auth") return isSessionlessAuthCommand(normalizedTokens);
59
+ if (command === "dashboard") return isSessionlessDashboardCommand(normalizedTokens);
60
+ if (command === "device") return normalizedTokens.length === 2 && subcommand === "list";
61
+ if (command === "doctor") return hasOnlyBooleanFlags(normalizedTokens.slice(1), DOCTOR_BOOLEAN_FLAGS);
62
+ if (command === "install") return hasOnlyBooleanFlags(normalizedTokens.slice(1), INSTALL_BOOLEAN_FLAGS);
63
+ if (command === "profiles" || command === "upgrade") return normalizedTokens.length === 1;
64
+ if (command === "session") return normalizedTokens.length === 2 && subcommand === "list";
65
+ if (command === "state") return isSessionlessStateCommand(normalizedTokens);
66
+ return false;
67
+ }
68
+
69
+ export function needsManagedSession(descriptor: ArgvDescriptor): boolean {
70
+ return !isSessionlessCommand(descriptor.commandTokens);
71
+ }
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Purpose: Centralize upstream agent-browser command capabilities that wrapper behavior depends on.
3
+ * Responsibilities: Normalize command aliases once and expose capability predicates for runtime planning,
4
+ * session/ref guards, result recommendations, and presentation summaries without coupling unrelated behaviors.
5
+ * Scope: Static command capability taxonomy only; command-shape parsing, spawning, and formatting live elsewhere.
6
+ */
7
+
8
+ type CommandCapabilityFlag =
9
+ | "closesSession"
10
+ | "openNavigation"
11
+ | "readOnlyDiagnosticSessionTarget"
12
+ | "excludedFromPinning"
13
+ | "excludedFromPostCommandCorrection"
14
+ | "guardsPageRefs"
15
+ | "invalidatesBatchRefs"
16
+ | "eligibleForElectronHealthProbe"
17
+ | "navigationObservable"
18
+ | "triggersPostMutationSnapshot"
19
+ | "eligibleForPageChangeSummary";
20
+
21
+ interface CommandCapabilityEntry extends Partial<Record<CommandCapabilityFlag, true>> {
22
+ aliases?: readonly string[];
23
+ command: string;
24
+ }
25
+
26
+ const COMMAND_CAPABILITIES: readonly CommandCapabilityEntry[] = [
27
+ {
28
+ command: "back",
29
+ eligibleForElectronHealthProbe: true,
30
+ eligibleForPageChangeSummary: true,
31
+ invalidatesBatchRefs: true,
32
+ navigationObservable: true,
33
+ triggersPostMutationSnapshot: true,
34
+ },
35
+ {
36
+ command: "batch",
37
+ excludedFromPostCommandCorrection: true,
38
+ },
39
+ {
40
+ command: "check",
41
+ eligibleForElectronHealthProbe: true,
42
+ eligibleForPageChangeSummary: true,
43
+ guardsPageRefs: true,
44
+ invalidatesBatchRefs: true,
45
+ triggersPostMutationSnapshot: true,
46
+ },
47
+ {
48
+ command: "click",
49
+ eligibleForElectronHealthProbe: true,
50
+ eligibleForPageChangeSummary: true,
51
+ guardsPageRefs: true,
52
+ invalidatesBatchRefs: true,
53
+ navigationObservable: true,
54
+ triggersPostMutationSnapshot: true,
55
+ },
56
+ {
57
+ aliases: ["quit", "exit"],
58
+ closesSession: true,
59
+ command: "close",
60
+ excludedFromPinning: true,
61
+ excludedFromPostCommandCorrection: true,
62
+ },
63
+ {
64
+ command: "console",
65
+ readOnlyDiagnosticSessionTarget: true,
66
+ },
67
+ {
68
+ command: "cookies",
69
+ readOnlyDiagnosticSessionTarget: true,
70
+ },
71
+ {
72
+ command: "dblclick",
73
+ eligibleForElectronHealthProbe: true,
74
+ eligibleForPageChangeSummary: true,
75
+ guardsPageRefs: true,
76
+ invalidatesBatchRefs: true,
77
+ navigationObservable: true,
78
+ triggersPostMutationSnapshot: true,
79
+ },
80
+ {
81
+ command: "dialog",
82
+ eligibleForPageChangeSummary: true,
83
+ invalidatesBatchRefs: true,
84
+ triggersPostMutationSnapshot: true,
85
+ },
86
+ {
87
+ command: "download",
88
+ eligibleForPageChangeSummary: true,
89
+ guardsPageRefs: true,
90
+ },
91
+ {
92
+ command: "drag",
93
+ guardsPageRefs: true,
94
+ invalidatesBatchRefs: true,
95
+ },
96
+ {
97
+ command: "errors",
98
+ readOnlyDiagnosticSessionTarget: true,
99
+ },
100
+ {
101
+ command: "fill",
102
+ eligibleForElectronHealthProbe: true,
103
+ eligibleForPageChangeSummary: true,
104
+ guardsPageRefs: true,
105
+ triggersPostMutationSnapshot: true,
106
+ },
107
+ {
108
+ command: "find",
109
+ eligibleForElectronHealthProbe: true,
110
+ },
111
+ {
112
+ command: "focus",
113
+ guardsPageRefs: true,
114
+ },
115
+ {
116
+ command: "forward",
117
+ eligibleForElectronHealthProbe: true,
118
+ eligibleForPageChangeSummary: true,
119
+ invalidatesBatchRefs: true,
120
+ navigationObservable: true,
121
+ triggersPostMutationSnapshot: true,
122
+ },
123
+ {
124
+ command: "hover",
125
+ eligibleForPageChangeSummary: true,
126
+ guardsPageRefs: true,
127
+ invalidatesBatchRefs: true,
128
+ triggersPostMutationSnapshot: true,
129
+ },
130
+ {
131
+ command: "keydown",
132
+ eligibleForElectronHealthProbe: true,
133
+ eligibleForPageChangeSummary: true,
134
+ invalidatesBatchRefs: true,
135
+ triggersPostMutationSnapshot: true,
136
+ },
137
+ {
138
+ command: "keyboard",
139
+ eligibleForElectronHealthProbe: true,
140
+ eligibleForPageChangeSummary: true,
141
+ guardsPageRefs: true,
142
+ invalidatesBatchRefs: true,
143
+ triggersPostMutationSnapshot: true,
144
+ },
145
+ {
146
+ command: "keyup",
147
+ eligibleForElectronHealthProbe: true,
148
+ eligibleForPageChangeSummary: true,
149
+ invalidatesBatchRefs: true,
150
+ triggersPostMutationSnapshot: true,
151
+ },
152
+ {
153
+ command: "mouse",
154
+ eligibleForElectronHealthProbe: true,
155
+ guardsPageRefs: true,
156
+ invalidatesBatchRefs: true,
157
+ },
158
+ {
159
+ command: "network",
160
+ readOnlyDiagnosticSessionTarget: true,
161
+ },
162
+ {
163
+ aliases: ["goto", "navigate"],
164
+ command: "open",
165
+ eligibleForPageChangeSummary: true,
166
+ excludedFromPinning: true,
167
+ invalidatesBatchRefs: true,
168
+ openNavigation: true,
169
+ },
170
+ {
171
+ command: "pdf",
172
+ eligibleForPageChangeSummary: true,
173
+ },
174
+ {
175
+ aliases: ["key"],
176
+ command: "press",
177
+ eligibleForElectronHealthProbe: true,
178
+ eligibleForPageChangeSummary: true,
179
+ guardsPageRefs: true,
180
+ invalidatesBatchRefs: true,
181
+ triggersPostMutationSnapshot: true,
182
+ },
183
+ {
184
+ command: "pushstate",
185
+ eligibleForPageChangeSummary: true,
186
+ invalidatesBatchRefs: true,
187
+ triggersPostMutationSnapshot: true,
188
+ },
189
+ {
190
+ command: "reload",
191
+ eligibleForElectronHealthProbe: true,
192
+ eligibleForPageChangeSummary: true,
193
+ invalidatesBatchRefs: true,
194
+ navigationObservable: true,
195
+ triggersPostMutationSnapshot: true,
196
+ },
197
+ {
198
+ command: "screenshot",
199
+ eligibleForPageChangeSummary: true,
200
+ },
201
+ {
202
+ command: "scroll",
203
+ eligibleForPageChangeSummary: true,
204
+ invalidatesBatchRefs: true,
205
+ triggersPostMutationSnapshot: true,
206
+ },
207
+ {
208
+ aliases: ["scrollinto"],
209
+ command: "scrollintoview",
210
+ eligibleForPageChangeSummary: true,
211
+ guardsPageRefs: true,
212
+ invalidatesBatchRefs: true,
213
+ triggersPostMutationSnapshot: true,
214
+ },
215
+ {
216
+ command: "select",
217
+ eligibleForElectronHealthProbe: true,
218
+ eligibleForPageChangeSummary: true,
219
+ guardsPageRefs: true,
220
+ invalidatesBatchRefs: true,
221
+ triggersPostMutationSnapshot: true,
222
+ },
223
+ {
224
+ command: "session",
225
+ excludedFromPinning: true,
226
+ excludedFromPostCommandCorrection: true,
227
+ },
228
+ {
229
+ command: "storage",
230
+ readOnlyDiagnosticSessionTarget: true,
231
+ },
232
+ {
233
+ command: "swipe",
234
+ eligibleForPageChangeSummary: true,
235
+ invalidatesBatchRefs: true,
236
+ triggersPostMutationSnapshot: true,
237
+ },
238
+ {
239
+ command: "tab",
240
+ excludedFromPinning: true,
241
+ excludedFromPostCommandCorrection: true,
242
+ },
243
+ {
244
+ command: "tap",
245
+ eligibleForElectronHealthProbe: true,
246
+ eligibleForPageChangeSummary: true,
247
+ guardsPageRefs: true,
248
+ invalidatesBatchRefs: true,
249
+ triggersPostMutationSnapshot: true,
250
+ },
251
+ {
252
+ command: "type",
253
+ eligibleForElectronHealthProbe: true,
254
+ eligibleForPageChangeSummary: true,
255
+ guardsPageRefs: true,
256
+ invalidatesBatchRefs: true,
257
+ triggersPostMutationSnapshot: true,
258
+ },
259
+ {
260
+ command: "uncheck",
261
+ eligibleForElectronHealthProbe: true,
262
+ eligibleForPageChangeSummary: true,
263
+ guardsPageRefs: true,
264
+ invalidatesBatchRefs: true,
265
+ triggersPostMutationSnapshot: true,
266
+ },
267
+ {
268
+ command: "upload",
269
+ guardsPageRefs: true,
270
+ invalidatesBatchRefs: true,
271
+ },
272
+ ];
273
+
274
+ const COMMAND_CAPABILITY_BY_NAME = new Map<string, CommandCapabilityEntry>();
275
+ for (const entry of COMMAND_CAPABILITIES) {
276
+ COMMAND_CAPABILITY_BY_NAME.set(entry.command, entry);
277
+ for (const alias of entry.aliases ?? []) {
278
+ COMMAND_CAPABILITY_BY_NAME.set(alias, entry);
279
+ }
280
+ }
281
+
282
+ function getCommandCapability(command: string | undefined): CommandCapabilityEntry | undefined {
283
+ return command === undefined ? undefined : COMMAND_CAPABILITY_BY_NAME.get(command);
284
+ }
285
+
286
+ function hasCommandCapability(command: string | undefined, capability: CommandCapabilityFlag): boolean {
287
+ return getCommandCapability(command)?.[capability] === true;
288
+ }
289
+
290
+ export function normalizeCommandName(command: string | undefined): string | undefined {
291
+ return getCommandCapability(command)?.command ?? command;
292
+ }
293
+
294
+ export function isCloseCommand(command: string | undefined): boolean {
295
+ return hasCommandCapability(command, "closesSession");
296
+ }
297
+
298
+ export function isOpenNavigationCommand(command: string | undefined): boolean {
299
+ return hasCommandCapability(command, "openNavigation");
300
+ }
301
+
302
+ export function isReadOnlyDiagnosticSessionTargetCommand(command: string | undefined, _subcommand?: string): boolean {
303
+ return hasCommandCapability(command, "readOnlyDiagnosticSessionTarget");
304
+ }
305
+
306
+ export function isSessionTabPinningExcludedCommand(command: string | undefined): boolean {
307
+ return hasCommandCapability(command, "excludedFromPinning");
308
+ }
309
+
310
+ export function isSessionTabPostCommandCorrectionExcludedCommand(command: string | undefined): boolean {
311
+ return hasCommandCapability(command, "excludedFromPostCommandCorrection");
312
+ }
313
+
314
+ export function isRefInvalidatingBatchCommand(command: string | undefined): boolean {
315
+ return hasCommandCapability(command, "invalidatesBatchRefs");
316
+ }
317
+
318
+ export function isRefGuardedCommand(command: string | undefined): boolean {
319
+ return hasCommandCapability(command, "guardsPageRefs");
320
+ }
321
+
322
+ export function isElectronPostCommandHealthCommand(command: string | undefined): boolean {
323
+ return hasCommandCapability(command, "eligibleForElectronHealthProbe");
324
+ }
325
+
326
+ export function isNavigationObservableCommandName(command: string | undefined): boolean {
327
+ return hasCommandCapability(command, "navigationObservable");
328
+ }
329
+
330
+ export function isPageMutationCommand(command: string | undefined): boolean {
331
+ return hasCommandCapability(command, "triggersPostMutationSnapshot");
332
+ }
333
+
334
+ export function isPageChangeSummaryCommand(command: string | undefined): boolean {
335
+ return hasCommandCapability(command, "eligibleForPageChangeSummary");
336
+ }
@@ -33,6 +33,7 @@ export interface ElectronLaunchStatus {
33
33
  export interface ElectronCleanupStep {
34
34
  error?: string;
35
35
  resource: "debug-port" | "managed-session" | "process" | "user-data-dir";
36
+ sessionName?: string;
36
37
  state: "already-gone" | "failed" | "removed" | "skipped";
37
38
  }
38
39
 
@@ -0,0 +1,19 @@
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
+ }