pi-agent-browser-native 0.2.71 → 0.2.74

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 (52) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -12
  3. package/dist/extensions/agent-browser/index.js +104 -16
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +124 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +12 -1
  6. package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
  7. package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
  8. package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
  9. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
  10. package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
  12. package/dist/extensions/agent-browser/lib/managed-session-restore.js +367 -0
  13. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
  14. package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
  15. package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
  22. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
  23. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
  24. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
  25. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
  26. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +625 -429
  27. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +136 -56
  28. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +28 -40
  29. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
  30. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
  31. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  32. package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
  33. package/dist/extensions/agent-browser/lib/process.js +270 -34
  34. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
  35. package/dist/extensions/agent-browser/lib/results/categories.js +21 -2
  36. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +80 -12
  38. package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
  39. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
  40. package/dist/extensions/agent-browser/lib/runtime.js +85 -85
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
  42. package/dist/extensions/agent-browser/lib/temp.js +13 -25
  43. package/docs/ARCHITECTURE.md +9 -8
  44. package/docs/COMMAND_REFERENCE.md +97 -32
  45. package/docs/ELECTRON.md +10 -10
  46. package/docs/RELEASE.md +3 -2
  47. package/docs/SUPPORT_MATRIX.md +22 -19
  48. package/docs/TOOL_CONTRACT.md +31 -28
  49. package/docs/platform-smoke.md +5 -5
  50. package/package.json +1 -1
  51. package/platform-smoke.config.mjs +3 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +45 -3
@@ -45,6 +45,7 @@ export const COMMAND_VALUE_FLAGS = [
45
45
  "--baseline",
46
46
  "--body",
47
47
  "--categories",
48
+ "--content",
48
49
  "--curl",
49
50
  "--depth",
50
51
  "-d",
@@ -68,6 +69,7 @@ export const COMMAND_VALUE_FLAGS = [
68
69
  "--selector",
69
70
  "-s",
70
71
  "--status",
72
+ "--tags",
71
73
  "--text",
72
74
  "--threshold",
73
75
  "--timeout",
@@ -89,6 +91,7 @@ export const GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES = new Set([
89
91
  "--content-boundaries",
90
92
  "--debug",
91
93
  "--headed",
94
+ "--hide-scrollbars",
92
95
  "--ignore-https-errors",
93
96
  "--json",
94
97
  "--no-auto-dialog",
@@ -98,6 +101,127 @@ export const GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES = new Set([
98
101
  "-v",
99
102
  "--webgpu",
100
103
  ]);
104
+ const SESSION_COMPONENT_ALPHANUMERIC = /^[\p{Alphabetic}\p{Number}]$/u;
105
+ /** Match upstream's last-wins, case-sensitive boolean semantics; only exact `false` disables a present flag. */
106
+ export function isBooleanFlagEnabled(args, flag) {
107
+ let enabled = false;
108
+ for (let index = 0; index < args.length; index += 1) {
109
+ const token = args[index];
110
+ if (token === flag) {
111
+ enabled = args[index + 1] !== "false";
112
+ if (["true", "false"].includes(args[index + 1] ?? ""))
113
+ index += 1;
114
+ continue;
115
+ }
116
+ if (PREVALIDATED_VALUE_FLAGS.has(token)) {
117
+ index += 1;
118
+ continue;
119
+ }
120
+ if (GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(token) && ["true", "false"].includes(args[index + 1] ?? ""))
121
+ index += 1;
122
+ }
123
+ return enabled;
124
+ }
125
+ /** Mirror upstream sanitize_session_component for namespace/socket/state identity. */
126
+ export function canonicalizeAgentBrowserNamespace(value) {
127
+ if (value === undefined)
128
+ return undefined;
129
+ let normalized = "";
130
+ let lastWasSeparator = false;
131
+ for (const character of value) {
132
+ if (SESSION_COMPONENT_ALPHANUMERIC.test(character)) {
133
+ normalized += character.toLowerCase();
134
+ lastWasSeparator = false;
135
+ }
136
+ else if (character === "-" || character === "_") {
137
+ if (normalized && !lastWasSeparator) {
138
+ normalized += character;
139
+ lastWasSeparator = true;
140
+ }
141
+ }
142
+ else if (normalized && !lastWasSeparator) {
143
+ normalized += "-";
144
+ lastWasSeparator = true;
145
+ }
146
+ }
147
+ return normalized.replace(/[-_]+$/u, "") || undefined;
148
+ }
149
+ function foldAgentBrowserFilesystemIdentity(value, platform) {
150
+ if (platform !== "darwin" && platform !== "win32")
151
+ return value;
152
+ // APFS aliases include full Unicode folds such as ß/SS and ς/Σ, not just ASCII case.
153
+ return value.normalize("NFC").toLowerCase().toUpperCase().toLowerCase().normalize("NFC");
154
+ }
155
+ export function getAgentBrowserSessionIdentityKey(sessionName, namespace, platform = process.platform) {
156
+ const canonicalNamespace = canonicalizeAgentBrowserNamespace(namespace);
157
+ const identityNamespace = canonicalNamespace ? foldAgentBrowserFilesystemIdentity(canonicalNamespace, platform) : undefined;
158
+ const canonicalSessionName = foldAgentBrowserFilesystemIdentity(sessionName, platform);
159
+ return identityNamespace ? `${identityNamespace}\0${canonicalSessionName}` : canonicalSessionName;
160
+ }
161
+ /** Mirror upstream 0.33.2 global parsing: full argv, no `--` sentinel, and only global value payloads are skipped. */
162
+ export function scanUpstreamGlobalFlagOccurrences(args, targetFlag) {
163
+ const occurrences = [];
164
+ for (let index = 0; index < args.length; index += 1) {
165
+ const token = args[index];
166
+ if (token === targetFlag) {
167
+ occurrences.push({ index, value: args[index + 1] });
168
+ index += 1;
169
+ continue;
170
+ }
171
+ if (PREVALIDATED_VALUE_FLAGS.has(token)) {
172
+ index += 1;
173
+ continue;
174
+ }
175
+ if (GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(token) && ["true", "false"].includes(args[index + 1] ?? ""))
176
+ index += 1;
177
+ }
178
+ return occurrences;
179
+ }
180
+ export function extractExplicitSessionName(args) {
181
+ return scanUpstreamGlobalFlagOccurrences(args, "--session").at(-1)?.value;
182
+ }
183
+ export function extractExplicitNamespace(args) {
184
+ return canonicalizeAgentBrowserNamespace(scanUpstreamGlobalFlagOccurrences(args, "--namespace").at(-1)?.value);
185
+ }
186
+ export function resolveAgentBrowserNamespace(args, envValue) {
187
+ const occurrences = scanUpstreamGlobalFlagOccurrences(args, "--namespace");
188
+ if (occurrences.length > 0)
189
+ return canonicalizeAgentBrowserNamespace(occurrences.at(-1)?.value) ?? "";
190
+ return canonicalizeAgentBrowserNamespace(envValue);
191
+ }
192
+ /** Mirror upstream's optional restore value and full-argv last-wins parsing. */
193
+ export function extractRequestedRestoreKey(args, sessionName, envValue) {
194
+ let restoreKey = envValue || null;
195
+ let seenCommand = false;
196
+ for (let index = 0; index < args.length; index += 1) {
197
+ const token = args[index];
198
+ if (token.startsWith("--restore=")) {
199
+ restoreKey = token.slice("--restore=".length) || sessionName;
200
+ continue;
201
+ }
202
+ if (token === "--restore") {
203
+ if (!seenCommand && optionalGlobalValueFlagConsumesNext(token, args[index + 1])) {
204
+ restoreKey = args[index + 1];
205
+ index += 1;
206
+ }
207
+ else {
208
+ restoreKey = sessionName;
209
+ }
210
+ continue;
211
+ }
212
+ if (PREVALIDATED_VALUE_FLAGS.has(token)) {
213
+ index += 1;
214
+ continue;
215
+ }
216
+ if (GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(token) && ["true", "false"].includes(args[index + 1] ?? "")) {
217
+ index += 1;
218
+ continue;
219
+ }
220
+ if (isKnownCommandToken(token))
221
+ seenCommand = true;
222
+ }
223
+ return restoreKey;
224
+ }
101
225
  export function getFlagName(token) {
102
226
  return token.split("=", 1)[0] ?? token;
103
227
  }
@@ -5,7 +5,7 @@
5
5
  * Scope: Static command capability taxonomy only; command-shape parsing, spawning, and formatting live elsewhere.
6
6
  */
7
7
  const ADDITIONAL_COMMAND_TOKENS = [
8
- "auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "window",
8
+ "a11y", "auth", "chat", "clipboard", "confirm", "connect", "dashboard", "deny", "device", "dialog", "diff", "doctor", "errors", "eval", "find", "frame", "get", "highlight", "inspect", "install", "is", "mcp", "plugin", "plugins", "profiles", "profiler", "react", "record", "removeinitscript", "session", "set", "skills", "snapshot", "state", "stream", "trace", "upgrade", "vitals", "wait", "web-vitals", "window",
9
9
  ];
10
10
  const COMMAND_CAPABILITIES = [
11
11
  {
@@ -81,6 +81,12 @@ const COMMAND_CAPABILITIES = [
81
81
  command: "errors",
82
82
  readOnlyDiagnosticSessionTarget: true,
83
83
  },
84
+ {
85
+ command: "eval",
86
+ invalidatesBatchRefs: true,
87
+ navigationObservable: true,
88
+ triggersPostMutationSnapshot: true,
89
+ },
84
90
  {
85
91
  command: "fill",
86
92
  eligibleForElectronHealthProbe: true,
@@ -305,6 +311,11 @@ export function isElectronPostCommandHealthCommand(command) {
305
311
  export function isNavigationObservableCommandName(command) {
306
312
  return hasCommandCapability(command, "navigationObservable");
307
313
  }
314
+ export function isUnverifiedPageTransitionCommand(command, subcommand) {
315
+ return ["back", "connect", "eval", "forward", "reload"].includes(command ?? "")
316
+ || (command === "state" && subcommand === "load")
317
+ || (command === "tab" && subcommand !== undefined && !["list", "new"].includes(subcommand));
318
+ }
308
319
  export function isPageMutationCommand(command) {
309
320
  return hasCommandCapability(command, "triggersPostMutationSnapshot");
310
321
  }
@@ -33,11 +33,11 @@ export function parseCdpTargets(value) {
33
33
  webSocketDebuggerUrl: asString(target.webSocketDebuggerUrl),
34
34
  }));
35
35
  }
36
- export async function fetchCdpJson(url) {
36
+ export async function fetchCdpJson(url, signal) {
37
37
  const controller = new AbortController();
38
38
  const timeout = setTimeout(() => controller.abort(), ELECTRON_CDP_FETCH_TIMEOUT_MS);
39
39
  try {
40
- const response = await fetch(url, { signal: controller.signal });
40
+ const response = await fetch(url, { signal: signal ? AbortSignal.any([signal, controller.signal]) : controller.signal });
41
41
  if (!response.ok)
42
42
  return undefined;
43
43
  return await response.json();
@@ -3,7 +3,7 @@
3
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
4
  * Scope: Host-side Electron lifecycle setup only; upstream agent-browser attach/presentation stays in the extension entrypoint.
5
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.
6
+ * Invariants/Assumptions: The wrapper only launches targets with Electron framework evidence, always uses an isolated temp profile, never accepts a caller-supplied remote debugging port, and cleans any spawned process/profile when cancellation interrupts readiness.
7
7
  */
8
8
  import { spawn } from "node:child_process";
9
9
  import { randomUUID } from "node:crypto";
@@ -24,8 +24,18 @@ function normalizeTimeoutMs(timeoutMs) {
24
24
  return ELECTRON_LAUNCH_DEFAULT_TIMEOUT_MS;
25
25
  return Math.min(timeoutMs, ELECTRON_LAUNCH_MAX_TIMEOUT_MS);
26
26
  }
27
- function sleep(ms) {
28
- return new Promise((resolve) => setTimeout(resolve, ms));
27
+ function sleep(ms, signal) {
28
+ if (signal?.aborted)
29
+ return Promise.resolve();
30
+ return new Promise((resolve) => {
31
+ const timer = setTimeout(done, ms);
32
+ function done() {
33
+ clearTimeout(timer);
34
+ signal?.removeEventListener("abort", done);
35
+ resolve();
36
+ }
37
+ signal?.addEventListener("abort", done, { once: true });
38
+ });
29
39
  }
30
40
  function normalizeIdentifier(value) {
31
41
  const trimmed = value?.trim().toLowerCase();
@@ -110,6 +120,8 @@ async function readDevToolsActivePort(userDataDir) {
110
120
  async function pollDevToolsActivePort(options) {
111
121
  let devToolsActivePort;
112
122
  while (Date.now() <= options.deadlineMs) {
123
+ if (options.signal?.aborted)
124
+ return { devToolsActivePort, failure: "aborted" };
113
125
  const spawnError = options.getSpawnError();
114
126
  if (spawnError)
115
127
  return { devToolsActivePort, failure: "spawn-error", spawnError };
@@ -120,20 +132,24 @@ async function pollDevToolsActivePort(options) {
120
132
  if (exit.code !== null || exit.signal !== null) {
121
133
  return { devToolsActivePort, failure: exit.code === 0 ? "single-instance-conflict" : "spawn-error" };
122
134
  }
123
- await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
135
+ await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS, options.signal);
124
136
  }
125
137
  return { devToolsActivePort, failure: "timeout" };
126
138
  }
127
- async function pollCdpMetadata(port, deadlineMs) {
139
+ async function pollCdpMetadata(port, deadlineMs, signal) {
128
140
  while (Date.now() <= deadlineMs) {
129
- const version = parseCdpVersion(await fetchCdpJson(`http://127.0.0.1:${port}/json/version`));
141
+ if (signal?.aborted)
142
+ return { aborted: true };
143
+ const version = parseCdpVersion(await fetchCdpJson(`http://127.0.0.1:${port}/json/version`, signal));
144
+ if (signal?.aborted)
145
+ return { aborted: true };
130
146
  if (version) {
131
- const targets = parseCdpTargets(await fetchCdpJson(`http://127.0.0.1:${port}/json/list`));
132
- return { targets, version };
147
+ const targets = parseCdpTargets(await fetchCdpJson(`http://127.0.0.1:${port}/json/list`, signal));
148
+ return signal?.aborted ? { aborted: true } : { aborted: false, metadata: { targets, version } };
133
149
  }
134
- await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS);
150
+ await sleep(ELECTRON_DEVTOOLS_POLL_INTERVAL_MS, signal);
135
151
  }
136
- return undefined;
152
+ return { aborted: false };
137
153
  }
138
154
  function buildLaunchArgs(userDataDir, appArgs) {
139
155
  return [
@@ -210,6 +226,8 @@ function buildLaunchRecord(options) {
210
226
  function launchFailureMessage(reason, target, detail) {
211
227
  const label = target ? `${target.name} (${target.appPath ?? target.executablePath})` : "target";
212
228
  switch (reason) {
229
+ case "aborted":
230
+ return `Electron launch was aborted${target ? ` before ${label} finished starting` : " before the app started"}.`;
213
231
  case "non-electron-target":
214
232
  return `Electron launch rejected: ${label} does not have Electron framework evidence.`;
215
233
  case "policy-blocked":
@@ -226,7 +244,11 @@ function launchFailureMessage(reason, target, detail) {
226
244
  }
227
245
  export async function launchElectronApp(options) {
228
246
  const appArgs = options.appArgs ?? [];
247
+ if (options.signal?.aborted)
248
+ return { ok: false, failure: { appArgs, error: launchFailureMessage("aborted", undefined), reason: "aborted" } };
229
249
  const target = await resolveElectronLaunchTarget(options);
250
+ if (options.signal?.aborted)
251
+ return { ok: false, failure: { appArgs, error: launchFailureMessage("aborted", target), reason: "aborted", target } };
230
252
  if (!target) {
231
253
  return {
232
254
  ok: false,
@@ -254,6 +276,16 @@ export async function launchElectronApp(options) {
254
276
  const startedAtMs = Date.now();
255
277
  const deadlineMs = startedAtMs + timeoutMs;
256
278
  const userDataDir = await createSecureTempDirectory(ELECTRON_PROFILE_DIR_PREFIX);
279
+ if (options.signal?.aborted) {
280
+ let cleanupError;
281
+ try {
282
+ await rm(userDataDir, { force: true, recursive: true });
283
+ }
284
+ catch (error) {
285
+ cleanupError = error instanceof Error ? error.message : String(error);
286
+ }
287
+ return { ok: false, failure: { appArgs, cleanupError, error: launchFailureMessage("aborted", target), reason: "aborted", target, userDataDir } };
288
+ }
257
289
  let cleanupError;
258
290
  let spawnError;
259
291
  let exitCode = null;
@@ -312,15 +344,19 @@ export async function launchElectronApp(options) {
312
344
  deadlineMs,
313
345
  getChildExit: () => ({ code: exitCode, signal: exitSignal }),
314
346
  getSpawnError: () => spawnError,
347
+ signal: options.signal,
315
348
  userDataDir,
316
349
  });
317
350
  if (!portResult.port) {
318
351
  return fail(portResult.failure ?? "timeout", portResult.spawnError?.message, { devToolsActivePort: portResult.devToolsActivePort });
319
352
  }
320
- const metadata = await pollCdpMetadata(portResult.port, deadlineMs);
321
- if (!metadata) {
353
+ const metadataResult = await pollCdpMetadata(portResult.port, deadlineMs, options.signal);
354
+ if (metadataResult.aborted)
355
+ return fail("aborted", undefined, { devToolsActivePort: portResult.devToolsActivePort, port: portResult.port });
356
+ if (!metadataResult.metadata) {
322
357
  return fail("port-not-found", undefined, { cdpVersionReached: false, devToolsActivePort: portResult.devToolsActivePort, port: portResult.port });
323
358
  }
359
+ const metadata = metadataResult.metadata;
324
360
  const record = buildLaunchRecord({
325
361
  createdAtMs: Date.now(),
326
362
  pid: child.pid,
@@ -7,138 +7,136 @@ import { JsonSchema } from "../json-schema.js";
7
7
  import { StringEnum as localStringEnum } from "../string-enum-schema.js";
8
8
  import { ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS, ELECTRON_DISCOVERY_MAX_RESULTS, } from "../electron/discovery.js";
9
9
  import { AGENT_BROWSER_ELECTRON_HANDOFFS, AGENT_BROWSER_ELECTRON_TARGET_TYPES, AGENT_BROWSER_JOB_STEP_ACTIONS, AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS, AGENT_BROWSER_QA_LOAD_STATES, AGENT_BROWSER_SEMANTIC_ACTIONS, AGENT_BROWSER_SEMANTIC_LOCATORS, DEFAULT_SESSION_MODE, SOURCE_LOOKUP_MAX_WORKSPACE_FILES, } from "./types.js";
10
+ // Keep descriptions terse: Pi sends this schema every turn; workflows belong in prompt guidance and docs.
10
11
  // ponytail: the four electron.launch variants differ only in their single target field
11
- // (appPath/appName/bundleId/executablePath); the action literal and the shared optional
12
- // launch fields are identical, so a helper keeps the duplicate schema blocks in sync.
12
+ // (appPath/appName/bundleId/executablePath); the action literal and shared launch fields
13
+ // are identical, so this helper keeps the schema variants in sync.
13
14
  function electronLaunchVariant(Type, StringEnum, targetField) {
14
15
  return Type.Object({
15
- action: StringEnum(["launch"], { description: "Launch an Electron app with an isolated wrapper-owned profile." }),
16
+ action: StringEnum(["launch"]),
16
17
  ...targetField,
17
- appArgs: Type.Optional(Type.Array(Type.String({ description: "Argument passed to the Electron application.", minLength: 1 }), { description: "Optional Electron app argv. Wrapper-owned lifecycle/debug flags are rejected." })),
18
- handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS, { description: "Post-launch handoff depth. Defaults to snapshot." })),
19
- targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES, { description: "Preferred CDP target type. Defaults to page." })),
20
- timeoutMs: Type.Optional(Type.Integer({ description: "Bounded launch timeout in milliseconds.", minimum: 1 })),
21
- allow: Type.Optional(Type.Array(Type.String({ description: "App identifier allowed by the caller for electron.launch.", minLength: 1 }), { description: "Optional caller-owned allow list for electron.launch policy checks." })),
22
- deny: Type.Optional(Type.Array(Type.String({ description: "App identifier denied by the caller for electron.launch.", minLength: 1 }), { description: "Optional caller-owned deny list for electron.launch policy checks; deny wins over allow." })),
18
+ appArgs: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
19
+ handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS)),
20
+ targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES)),
21
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
22
+ allow: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
23
+ deny: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
23
24
  }, { additionalProperties: false });
24
25
  }
25
26
  export function createAgentBrowserParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
26
27
  return Type.Object({
27
- args: Type.Optional(Type.Array(Type.String({ description: "Exact agent-browser CLI arguments, excluding the binary name. Do not pass --json; the wrapper injects it. First-call recipe: open → snapshot -i → click/fill @eN → snapshot -i." }), {
28
- description: "Exact agent-browser CLI arguments, excluding the binary name and any shell operators. Required unless semanticAction, job, qa, sourceLookup, networkSourceLookup, or electron is provided. Do not include --json (wrapper injects it). Typical first calls: open, snapshot -i, click/fill current @refs, then snapshot -i again after navigation or DOM changes.",
28
+ args: Type.Optional(Type.Array(Type.String(), {
29
+ description: "Raw agent-browser argv only: no binary, shell operators, or --json. Start with open snapshot -i act on current @refs; re-snapshot after page changes.",
29
30
  minItems: 1,
30
31
  })),
31
32
  semanticAction: Type.Optional(Type.Object({
32
- action: StringEnum(AGENT_BROWSER_SEMANTIC_ACTIONS, {
33
- description: "Intent action to compile to an existing agent-browser find command, direct selector/ref command, or upstream select when action=select.",
34
- }),
35
- locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, {
36
- description: "Upstream find locator family to use for check/click/fill actions.",
37
- })),
38
- value: Type.Optional(Type.String({ description: "Locator value for find actions, or a single option value for select actions. For locator=role, role may be supplied instead." })),
39
- values: Type.Optional(Type.Array(Type.String({ description: "Option value for select actions." }), { description: "One or more option values for select actions.", minItems: 1 })),
40
- selector: Type.Optional(Type.String({ description: "Selector or @ref for direct click/check/fill actions, or for select actions compiled to select <selector> <value...>." })),
41
- text: Type.Optional(Type.String({ description: "Text/value argument for fill actions." })),
42
- role: Type.Optional(Type.String({ description: "Role locator value for locator=role. May be used instead of value; when both are set they must match." })),
43
- name: Type.Optional(Type.String({ description: "Accessible name filter for locator=role; compiles to --name <name>." })),
44
- session: Type.Optional(Type.String({ description: "Optional upstream session name; prepends --session <name> before the compiled command." })),
45
- }, { additionalProperties: false })),
33
+ action: StringEnum(AGENT_BROWSER_SEMANTIC_ACTIONS),
34
+ locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Locator for check/click/fill." })),
35
+ value: Type.Optional(Type.String({ description: "Locator value, or one select option." })),
36
+ values: Type.Optional(Type.Array(Type.String(), { description: "Select options.", minItems: 1 })),
37
+ selector: Type.Optional(Type.String({ description: "Direct selector or @ref." })),
38
+ text: Type.Optional(Type.String({ description: "Fill text." })),
39
+ role: Type.Optional(Type.String({ description: "Role locator; alternative to value." })),
40
+ name: Type.Optional(Type.String({ description: "Accessible name." })),
41
+ session: Type.Optional(Type.String({ description: "Upstream session name." })),
42
+ }, { additionalProperties: false, description: "Stable locator or direct-selector action." })),
46
43
  qa: Type.Optional(Type.Union([
47
44
  Type.Object({
48
- attached: Type.Literal(true, { description: "Run the QA preset against the currently attached session instead of opening qa.url." }),
49
- expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], { description: "Text that must appear on the page." })),
50
- expectedSelector: Type.Optional(Type.String({ description: "Selector or @ref that must appear on the page." })),
51
- screenshotPath: Type.Optional(Type.String({ description: "Optional evidence screenshot path captured at the end of the QA preset." })),
52
- checkConsole: Type.Optional(Type.Boolean({ description: "Whether to inspect console messages and fail on console errors. Defaults to false for qa.attached because upstream buffers may predate the check." })),
53
- checkErrors: Type.Optional(Type.Boolean({ description: "Whether to inspect page errors and fail when errors are present. Defaults to false for qa.attached because upstream buffers may predate the check." })),
54
- checkNetwork: Type.Optional(Type.Boolean({ description: "Whether to inspect network requests and fail on actionable request failures; benign icon misses warn. Defaults to false for qa.attached because upstream buffers may predate the check." })),
55
- loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES, { description: "Page readiness state for the QA preset before assertions and diagnostics. Defaults to domcontentloaded; use networkidle only for pages without long-lived background requests." })),
45
+ attached: Type.Literal(true),
46
+ expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
47
+ expectedSelector: Type.Optional(Type.String()),
48
+ screenshotPath: Type.Optional(Type.String()),
49
+ checkConsole: Type.Optional(Type.Boolean()),
50
+ checkErrors: Type.Optional(Type.Boolean()),
51
+ checkNetwork: Type.Optional(Type.Boolean()),
52
+ loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
56
53
  }, { additionalProperties: false }),
57
54
  Type.Object({
58
- url: Type.String({ description: "URL to open for a lightweight QA preset." }),
59
- attached: Type.Optional(Type.Literal(false, { description: "When omitted or false, qa.url is required and opened before checks." })),
60
- expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], { description: "Text that must appear on the page." })),
61
- expectedSelector: Type.Optional(Type.String({ description: "Selector or @ref that must appear on the page." })),
62
- screenshotPath: Type.Optional(Type.String({ description: "Optional evidence screenshot path captured at the end of the QA preset." })),
63
- checkConsole: Type.Optional(Type.Boolean({ description: "Whether to fail on console error messages. Defaults to true." })),
64
- checkErrors: Type.Optional(Type.Boolean({ description: "Whether to fail on page errors. Defaults to true." })),
65
- checkNetwork: Type.Optional(Type.Boolean({ description: "Whether to inspect network requests and fail on actionable request failures; benign icon misses warn. Defaults to true." })),
66
- loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES, { description: "Page readiness state for the QA preset before assertions and diagnostics. Defaults to domcontentloaded; use networkidle only for pages without long-lived background requests." })),
55
+ url: Type.String(),
56
+ attached: Type.Optional(Type.Literal(false)),
57
+ expectedText: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())])),
58
+ expectedSelector: Type.Optional(Type.String()),
59
+ screenshotPath: Type.Optional(Type.String()),
60
+ checkConsole: Type.Optional(Type.Boolean()),
61
+ checkErrors: Type.Optional(Type.Boolean()),
62
+ checkNetwork: Type.Optional(Type.Boolean()),
63
+ loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES)),
67
64
  }, { additionalProperties: false }),
68
- ], { description: "Lightweight QA preset. Use qa.url to open a URL, or qa.attached=true to check the current attached session without opening a URL." })),
65
+ ], { description: "QA a URL or current session (attached=true). Default readiness: domcontentloaded; use networkidle only without long-lived requests. URL diagnostics default on, attached diagnostics off." })),
69
66
  sourceLookup: Type.Optional(Type.Object({
70
- selector: Type.Optional(Type.String({ description: "Visible selector or @ref whose DOM metadata should be inspected for source hints." })),
71
- reactFiberId: Type.Optional(Type.String({ description: "React fiber id to inspect with upstream react inspect. Requires a session opened with --enable react-devtools." })),
72
- componentName: Type.Optional(Type.String({ description: "Component name to correlate with react tree output and bounded local workspace search." })),
73
- includeDomHints: Type.Optional(Type.Boolean({ description: "Whether selector lookups should inspect DOM HTML attributes for source-like metadata. Defaults to true." })),
74
- maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Maximum local source files to scan when componentName is provided. Defaults to 2000 and cannot exceed 5000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
75
- }, { additionalProperties: false, description: "EXPERIMENTAL: local UI-to-source candidates only (confidence/evidence, not guaranteed mappings). Compiles to batch; mutually exclusive with other input modes." })),
67
+ selector: Type.Optional(Type.String({ description: "Visible selector or @ref." })),
68
+ reactFiberId: Type.Optional(Type.String({ description: "React fiber id; requires --enable react-devtools." })),
69
+ componentName: Type.Optional(Type.String({ description: "Component for local source search." })),
70
+ includeDomHints: Type.Optional(Type.Boolean({ description: "Inspect DOM source hints; default true." })),
71
+ maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
72
+ }, { additionalProperties: false, description: "EXPERIMENTAL UI-to-source candidates; not guaranteed mappings." })),
76
73
  networkSourceLookup: Type.Optional(Type.Object({
77
- filter: Type.Optional(Type.String({ description: "Optional upstream network requests filter pattern." })),
78
- namespace: Type.Optional(Type.String({ description: "Optional upstream namespace; prepends --namespace <name> before the generated batch." })),
79
- requestId: Type.Optional(Type.String({ description: "Optional network request id to inspect with network request <id>." })),
80
- session: Type.Optional(Type.String({ description: "Optional upstream session name; prepends --session <name> before the generated batch." })),
81
- url: Type.Optional(Type.String({ description: "Optional failed request URL or URL fragment to correlate with local source." })),
82
- maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Maximum local source files to scan for URL literals. Defaults to 2000 and cannot exceed 5000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
83
- }, { additionalProperties: false, description: "EXPERIMENTAL: failed-request-to-source candidates only (initiator metadata and bounded workspace URL literals; not definitive blame). Compiles to batch; mutually exclusive with other input modes." })),
74
+ filter: Type.Optional(Type.String({ description: "Network request filter." })),
75
+ namespace: Type.Optional(Type.String()),
76
+ requestId: Type.Optional(Type.String({ description: "Request id to inspect." })),
77
+ session: Type.Optional(Type.String()),
78
+ url: Type.Optional(Type.String({ description: "Failed URL or fragment." })),
79
+ maxWorkspaceFiles: Type.Optional(Type.Number({ description: "Source scan cap; default 2000.", minimum: 1, maximum: SOURCE_LOOKUP_MAX_WORKSPACE_FILES })),
80
+ }, { additionalProperties: false, description: "EXPERIMENTAL failed-request-to-source candidates; not proof." })),
84
81
  electron: Type.Optional(Type.Union([
85
82
  Type.Object({
86
- action: StringEnum(["list"], { description: "List discovered Electron apps." }),
87
- query: Type.Optional(Type.String({ description: "Optional case-insensitive substring filter for electron.list across app name, bundle id, desktop id, and paths.", minLength: 1 })),
88
- maxResults: Type.Optional(Type.Integer({ description: `Maximum electron.list apps to return. Defaults to ${ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS}; values above ${ELECTRON_DISCOVERY_MAX_RESULTS} are clamped.`, minimum: 1 })),
83
+ action: StringEnum(["list"]),
84
+ query: Type.Optional(Type.String({ description: "Case-insensitive app filter.", minLength: 1 })),
85
+ maxResults: Type.Optional(Type.Integer({ description: `Result cap; default ${ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS}, values over ${ELECTRON_DISCOVERY_MAX_RESULTS} are clamped.`, minimum: 1 })),
89
86
  }, { additionalProperties: false }),
90
- electronLaunchVariant(Type, StringEnum, { appPath: Type.String({ description: "Electron launch target: macOS .app bundle path. Exactly one launch target is required for electron.launch.", minLength: 1 }) }),
91
- electronLaunchVariant(Type, StringEnum, { appName: Type.String({ description: "Electron launch target: app display name discovered by electron.list. Exactly one launch target is required for electron.launch.", minLength: 1 }) }),
92
- electronLaunchVariant(Type, StringEnum, { bundleId: Type.String({ description: "Electron launch target: macOS bundle identifier discovered by electron.list. Exactly one launch target is required for electron.launch.", minLength: 1 }) }),
93
- electronLaunchVariant(Type, StringEnum, { executablePath: Type.String({ description: "Electron launch target: executable path. Discovery is not required when this is provided. Exactly one launch target is required for electron.launch.", minLength: 1 }) }),
87
+ electronLaunchVariant(Type, StringEnum, { appPath: Type.String({ description: "macOS .app path.", minLength: 1 }) }),
88
+ electronLaunchVariant(Type, StringEnum, { appName: Type.String({ description: "Name from electron.list.", minLength: 1 }) }),
89
+ electronLaunchVariant(Type, StringEnum, { bundleId: Type.String({ description: "Bundle id from electron.list.", minLength: 1 }) }),
90
+ electronLaunchVariant(Type, StringEnum, { executablePath: Type.String({ description: "Executable path.", minLength: 1 }) }),
94
91
  Type.Object({
95
- action: StringEnum(["status", "cleanup"], { description: "Inspect or cleanup one wrapper-tracked Electron launch by launchId." }),
96
- launchId: Type.String({ description: "Wrapper launch id for electron.status and electron.cleanup.", minLength: 1 }),
97
- timeoutMs: Type.Optional(Type.Integer({ description: "Bounded status/cleanup timeout in milliseconds.", minimum: 1 })),
92
+ action: StringEnum(["status", "cleanup"]),
93
+ launchId: Type.String({ description: "Tracked launch id.", minLength: 1 }),
94
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
98
95
  }, { additionalProperties: false }),
99
96
  Type.Object({
100
- action: StringEnum(["status", "cleanup"], { description: "Inspect or cleanup all wrapper-tracked Electron launches." }),
101
- all: Type.Literal(true, { description: "Apply electron.status or electron.cleanup to all wrapper-owned launches." }),
102
- timeoutMs: Type.Optional(Type.Integer({ description: "Bounded status/cleanup timeout in milliseconds.", minimum: 1 })),
97
+ action: StringEnum(["status", "cleanup"]),
98
+ all: Type.Literal(true),
99
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
103
100
  }, { additionalProperties: false }),
104
101
  Type.Object({
105
- action: StringEnum(["status", "cleanup"], { description: "Inspect or cleanup the only active wrapper-tracked Electron launch." }),
106
- timeoutMs: Type.Optional(Type.Integer({ description: "Bounded status/cleanup timeout in milliseconds.", minimum: 1 })),
102
+ action: StringEnum(["status", "cleanup"]),
103
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
107
104
  }, { additionalProperties: false }),
108
105
  Type.Object({
109
- action: StringEnum(["probe"], { description: "Probe the current attached Electron managed session; launchId is accepted for launch-scoped follow-up actions." }),
110
- launchId: Type.Optional(Type.String({ description: "Wrapper launch id for electron.probe follow-up targeting.", minLength: 1 })),
111
- timeoutMs: Type.Optional(Type.Integer({ description: "Bounded probe timeout in milliseconds.", minimum: 1 })),
106
+ action: StringEnum(["probe"]),
107
+ launchId: Type.Optional(Type.String({ description: "Tracked launch id.", minLength: 1 })),
108
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
112
109
  }, { additionalProperties: false }),
113
- ], { description: "Electron wrapper action. Fields are action-specific and unsupported fields are rejected." })),
110
+ ], { description: "Electron discovery, isolated-profile launch, status, probe, or cleanup. Launch defaults: handoff=snapshot, targetType=page; deny wins over allow; lifecycle/debug appArgs rejected." })),
114
111
  job: Type.Optional(Type.Object({
115
- failFast: Type.Optional(Type.Boolean({ description: "Stop the compiled batch on the first failed job step. Defaults to true so later mutating steps do not run after setup/assertion failures." })),
112
+ failFast: Type.Optional(Type.Boolean({ description: "Stop on first failure; default true." })),
116
113
  steps: Type.Array(Type.Object({
117
- action: StringEnum(AGENT_BROWSER_JOB_STEP_ACTIONS, {
118
- description: "Constrained one-call job step compiled to existing upstream batch commands.",
119
- }),
120
- url: Type.Optional(Type.String({ description: "URL for open steps; exact URL or * / ** glob-style URL pattern for assertUrl steps." })),
121
- loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES, { description: "Optional readiness wait to insert immediately after an open step; use domcontentloaded/load/networkidle when the next job step needs page hydration evidence before clicking or reading." })),
122
- selector: Type.Optional(Type.String({ description: "Selector or @ref for click/fill/type/select-like steps; omit when using semantic locator fields on click/fill steps." })),
123
- locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Semantic locator for click/fill steps when selector is omitted." })),
124
- role: Type.Optional(Type.String({ description: "Role locator value for click/fill steps when locator is role." })),
125
- name: Type.Optional(Type.String({ description: "Accessible name filter for role locator click/fill steps." })),
126
- text: Type.Optional(Type.String({ description: "Text for fill steps or visible text for assertText steps." })),
127
- value: Type.Optional(Type.String({ description: "Single option value for select steps, or locator value for semantic click/fill steps." })),
128
- values: Type.Optional(Type.Array(Type.String({ description: "Option value for select steps." }), { description: "One or more option values for select steps.", minItems: 1 })),
129
- path: Type.Optional(Type.String({ description: "Artifact/download path for waitForDownload or screenshot steps." })),
130
- delayMs: Type.Optional(Type.Integer({ description: `Optional per-character delay for type steps; when set, the job compiles to focus/keyboard type/wait steps instead of instant fill-like typing, capped at ${AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS} characters.`, minimum: 1 })),
131
- press: Type.Optional(Type.String({ description: "Optional key to press after a type step, for example Enter." })),
132
- milliseconds: Type.Optional(Type.Number({ description: "Milliseconds for wait steps." })),
114
+ action: StringEnum(AGENT_BROWSER_JOB_STEP_ACTIONS),
115
+ url: Type.Optional(Type.String({ description: "Open URL or assertUrl glob." })),
116
+ loadState: Type.Optional(StringEnum(AGENT_BROWSER_QA_LOAD_STATES, { description: "Readiness wait after open." })),
117
+ selector: Type.Optional(Type.String({ description: "Selector or @ref." })),
118
+ locator: Type.Optional(StringEnum(AGENT_BROWSER_SEMANTIC_LOCATORS, { description: "Locator when selector is omitted." })),
119
+ role: Type.Optional(Type.String({ description: "Role locator." })),
120
+ name: Type.Optional(Type.String({ description: "Accessible name." })),
121
+ text: Type.Optional(Type.String({ description: "Fill text or assertText target." })),
122
+ value: Type.Optional(Type.String({ description: "Select option or locator value." })),
123
+ values: Type.Optional(Type.Array(Type.String(), { description: "Select options.", minItems: 1 })),
124
+ path: Type.Optional(Type.String({ description: "Download or screenshot path." })),
125
+ delayMs: Type.Optional(Type.Integer({ description: `Per-character type delay; text is capped at ${AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS} characters.`, minimum: 1 })),
126
+ press: Type.Optional(Type.String({ description: "Key to press after typing." })),
127
+ milliseconds: Type.Optional(Type.Number({ description: "Wait duration in milliseconds." })),
133
128
  }, { additionalProperties: false }), { minItems: 1 }),
134
- }, { additionalProperties: false })),
135
- stdin: Type.Optional(Type.String({ description: "Optional raw stdin content; only supported for batch, eval --stdin, auth save --password-stdin, and is generated internally by job, qa, sourceLookup, or networkSourceLookup mode. Do not use with electron mode." })),
136
- outputPath: Type.Optional(Type.String({ description: "Optional workspace-relative or absolute file path that receives the model-facing command data/result after the browser command completes. Useful for eval/get/snapshot captures that should become durable local artifacts.", minLength: 1 })),
137
- timeoutMs: Type.Optional(Type.Integer({ description: "Optional per-call wrapper subprocess watchdog in milliseconds for browser CLI args/job/qa/source lookup calls. Use for long opens or large output captures; explicit long wait steps are forwarded, so set timeoutMs above the wait duration plus a small grace window when overriding the derived watchdog. Electron actions use electron.timeoutMs instead.", minimum: 1 })),
129
+ }, { additionalProperties: false, description: "Constrained multi-step batch." })),
130
+ stdin: Type.Optional(Type.String({ description: "Raw stdin for batch, eval --stdin, or auth save --password-stdin; unavailable with structured modes and electron." })),
131
+ outputPath: Type.Optional(Type.String({ description: "Workspace-relative or absolute result path.", minLength: 1 })),
132
+ timeoutMs: Type.Optional(Type.Integer({ description: "Wrapper timeout in ms; exceed explicit waits. Electron uses electron.timeoutMs.", minimum: 1 })),
138
133
  sessionMode: Type.Optional(StringEnum(["auto", "fresh"], {
139
- description: "Session handling mode. `auto` reuses the extension-managed pi-scoped session when possible. `fresh` switches that managed session to a fresh upstream launch so launch-scoped flags like --namespace, --restore, --restore-save, restore check flags, --profile, --executable-path, --webgpu, --session-name, --cdp, --state, --auto-connect, --init-script, --enable, -p/--provider, or iOS --device apply and later auto calls follow the new browser.",
134
+ description: "auto reuses the managed session; fresh starts one for launch-only flags, then makes it the managed session.",
140
135
  default: DEFAULT_SESSION_MODE,
141
136
  })),
142
- }, { additionalProperties: false });
137
+ }, {
138
+ additionalProperties: false,
139
+ description: "Choose one input mode: args, semanticAction, job, qa, sourceLookup, networkSourceLookup, or electron.",
140
+ });
143
141
  }
144
142
  export const AGENT_BROWSER_PARAMS = createAgentBrowserParamsSchema();