pi-agent-browser-native 0.9.1 → 0.9.3

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 (42) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +35 -8
  3. package/dist/extensions/agent-browser/index.js +39 -16
  4. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +6 -3
  5. package/dist/extensions/agent-browser/lib/browser-transcript.js +2 -1
  6. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +4 -0
  7. package/dist/extensions/agent-browser/lib/native-output.js +2 -1
  8. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +2 -8
  9. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +12 -2
  10. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +62 -10
  11. package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +18 -12
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +33 -24
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +111 -53
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +6 -2
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +32 -20
  16. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +37 -32
  17. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +30 -11
  18. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -1
  19. package/dist/extensions/agent-browser/lib/page-target-validation.js +10 -3
  20. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +2 -1
  21. package/dist/extensions/agent-browser/lib/playbook.js +7 -6
  22. package/dist/extensions/agent-browser/lib/process-environment.js +12 -3
  23. package/dist/extensions/agent-browser/lib/process.js +6 -6
  24. package/dist/extensions/agent-browser/lib/read-confirmation.js +56 -16
  25. package/dist/extensions/agent-browser/lib/results/confirmation.js +3 -0
  26. package/dist/extensions/agent-browser/lib/results/envelope.js +5 -1
  27. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +10 -1
  28. package/dist/extensions/agent-browser/lib/results/presentation/common.js +1 -0
  29. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +34 -1
  30. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +5 -1
  31. package/dist/extensions/agent-browser/lib/results/presentation.js +15 -2
  32. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +5 -5
  33. package/dist/extensions/agent-browser/lib/runtime.js +5 -1
  34. package/dist/extensions/agent-browser/lib/session-page-state.js +35 -16
  35. package/docs/ARCHITECTURE.md +10 -9
  36. package/docs/COMMAND_REFERENCE.md +37 -13
  37. package/docs/RELEASE.md +6 -5
  38. package/docs/REQUIREMENTS.md +3 -1
  39. package/docs/SESSION_STORAGE.md +1 -1
  40. package/docs/SUPPORT_MATRIX.md +12 -3
  41. package/docs/TOOL_CONTRACT.md +29 -20
  42. package/package.json +4 -4
@@ -6,6 +6,7 @@ import { getPageTargetValidationError } from "../../page-target-validation.js";
6
6
  import { isRecord } from "../../parsing.js";
7
7
  import { getBrowserResultMessage } from "../../browser-transcript.js";
8
8
  import { withAttachedBrowserSessionContext } from "../../process.js";
9
+ import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment } from "../../process-environment.js";
9
10
  import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
10
11
  import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
11
12
  import { appendUniqueAgentBrowserNextActions } from "../../results/next-actions.js";
@@ -397,37 +398,39 @@ function getElectronProbeSummary(probe) {
397
398
  class ElectronManagedSessionPolicyError extends Error {
398
399
  }
399
400
  async function withOwnedElectronManagedSessionPolicy(options, run) {
400
- const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(options.headedManagedAutosaveInterval);
401
- if (autosavePolicyChangeError)
402
- throw new ElectronManagedSessionPolicyError(autosavePolicyChangeError);
403
- const context = buildOwnedManagedSessionRestoreContext({
404
- args: ["--namespace", options.namespace ?? "", "--session", options.sessionName, ...options.args],
405
- cwd: options.cwd,
406
- headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
407
- headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
408
- managedSessionName: options.sessionName,
409
- namespace: options.namespace,
410
- restoreState: options.restoreState,
401
+ return withAgentBrowserProcessEnvironment({ AGENT_BROWSER_CONFIRM_ACTIONS: options.confirmActions }, async () => {
402
+ const autosavePolicyChangeError = getRunningHeadedAutosavePolicyChangeError(options.headedManagedAutosaveInterval);
403
+ if (autosavePolicyChangeError)
404
+ throw new ElectronManagedSessionPolicyError(autosavePolicyChangeError);
405
+ const context = buildOwnedManagedSessionRestoreContext({
406
+ args: ["--namespace", options.namespace ?? "", "--session", options.sessionName, ...options.args],
407
+ cwd: options.cwd,
408
+ headedManagedAutosaveDisabled: options.headedManagedAutosaveDisabled,
409
+ headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
410
+ managedSessionName: options.sessionName,
411
+ namespace: options.namespace,
412
+ restoreState: options.restoreState,
413
+ });
414
+ if (!context)
415
+ throw new ElectronManagedSessionPolicyError("Electron helper could not establish wrapper ownership for its managed session.");
416
+ let policy;
417
+ try {
418
+ policy = await acquireOwnedManagedSessionDaemonPolicy({ context, electronLaunchRecord: options.electronLaunchRecord, electronVerificationTimeoutMs: options.timeoutMs, signal: options.signal });
419
+ }
420
+ catch (error) {
421
+ throw new ElectronManagedSessionPolicyError(error instanceof Error ? error.message : String(error), { cause: error });
422
+ }
423
+ try {
424
+ if (policy.error)
425
+ throw new ElectronManagedSessionPolicyError(policy.error);
426
+ if (!policy.lock)
427
+ throw new ElectronManagedSessionPolicyError(options.signal?.aborted ? "Electron helper was aborted." : "Electron helper could not acquire managed-session policy coordination.");
428
+ return await withOwnedManagedSessionContext(context, run);
429
+ }
430
+ finally {
431
+ await policy.lock?.release();
432
+ }
411
433
  });
412
- if (!context)
413
- throw new ElectronManagedSessionPolicyError("Electron helper could not establish wrapper ownership for its managed session.");
414
- let policy;
415
- try {
416
- policy = await acquireOwnedManagedSessionDaemonPolicy({ context, electronLaunchRecord: options.electronLaunchRecord, electronVerificationTimeoutMs: options.timeoutMs, signal: options.signal });
417
- }
418
- catch (error) {
419
- throw new ElectronManagedSessionPolicyError(error instanceof Error ? error.message : String(error), { cause: error });
420
- }
421
- try {
422
- if (policy.error)
423
- throw new ElectronManagedSessionPolicyError(policy.error);
424
- if (!policy.lock)
425
- throw new ElectronManagedSessionPolicyError(options.signal?.aborted ? "Electron helper was aborted." : "Electron helper could not acquire managed-session policy coordination.");
426
- return await withOwnedManagedSessionContext(context, run);
427
- }
428
- finally {
429
- await policy.lock?.release();
430
- }
431
434
  }
432
435
  async function collectOwnedElectronManagedSessionTarget(options) {
433
436
  try {
@@ -603,7 +606,7 @@ export async function cleanupTrackedElectronHostLaunches(options) {
603
606
  const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
604
607
  const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
605
608
  const managedSessionCloseError = record.sessionName
606
- ? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, socketDir: managedSessionOwner?.socketDir, timeoutMs: options.timeoutMs })
609
+ ? await closeManagedSession({ confirmActions: options.sessionPageState.get(sessionKey ?? record.sessionName).confirmActions ?? process.env.AGENT_BROWSER_CONFIRM_ACTIONS, cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, socketDir: managedSessionOwner?.socketDir, timeoutMs: options.timeoutMs })
607
610
  : undefined;
608
611
  const managedSessionStep = record.sessionName
609
612
  ? managedSessionCloseError
@@ -673,6 +676,7 @@ async function handleElectronHostInputInContext(options) {
673
676
  .map((record) => {
674
677
  const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
675
678
  return collectOwnedElectronManagedSessionTarget({
679
+ confirmActions: sessionPageState.get(sessionKey).confirmActions ?? getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIRM_ACTIONS,
676
680
  cwd,
677
681
  electronLaunchRecord: record,
678
682
  headedManagedAutosaveDisabled: ownedManagedSessions.get(sessionKey)?.headedManagedAutosaveDisabled,
@@ -749,6 +753,7 @@ async function handleElectronHostInputInContext(options) {
749
753
  const headedManagedAutosaveInterval = managedSessionOwner?.headedManagedAutosaveInterval;
750
754
  const probe = await withOwnedElectronManagedSessionPolicy({
751
755
  args: ["snapshot", "-i"],
756
+ confirmActions: currentPageState.confirmActions ?? getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIRM_ACTIONS,
752
757
  cwd,
753
758
  electronLaunchRecord: launchRecord,
754
759
  headedManagedAutosaveDisabled,
@@ -821,7 +826,7 @@ async function handleElectronHostInputInContext(options) {
821
826
  const selection = selectElectronRecords(compiledElectron, electronLaunchRecords);
822
827
  if (selection.error)
823
828
  return buildElectronHostFailureResult({ compiledElectron: redactedCompiledElectron ?? compiledElectron, errorText: selection.error, failureCategory: "validation-error" });
824
- const cleanupResults = await cleanupTrackedElectronHostLaunches({ attachedSessionKeys, cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, ownedManagedSessions, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
829
+ const cleanupResults = await cleanupTrackedElectronHostLaunches({ attachedSessionKeys, cwd, electronChildProcesses, electronLaunchRecords, managedSessionRestoreState, ownedManagedSessions, sessionPageState, records: selection.records ?? [], timeoutMs: compiledElectron.timeoutMs ?? implicitSessionCloseTimeoutMs });
825
830
  return buildElectronCleanupResult(redactedCompiledElectron ?? compiledElectron, cleanupResults);
826
831
  }
827
832
  return undefined;
@@ -48,7 +48,7 @@ async function readNativeIdentity(path, cwd, signal, rootFallback, browserComman
48
48
  return {};
49
49
  const rootLaunchConfig = browserCommand && (hasLocalLaunchDefaults(config, {}) || ["cdp", "autoConnect", "provider"].some((key) => config[key] !== undefined))
50
50
  || rootFallback && ["restore", "sessionName", "state", "allowedDomains", "profile", "executablePath"].some((key) => config[key] !== undefined);
51
- if (!rootLaunchConfig && typeof config.session !== "string" && typeof config.namespace !== "string")
51
+ if (!rootLaunchConfig && config.confirmActions === undefined && typeof config.session !== "string" && typeof config.namespace !== "string")
52
52
  return {};
53
53
  const result = await runAgentBrowserProcess({ args: ["--config", path, "--json", "session"], cwd, signal, timeoutMs: 5_000 });
54
54
  try {
@@ -73,9 +73,7 @@ async function readNativeIdentity(path, cwd, signal, rootFallback, browserComman
73
73
  }
74
74
  }
75
75
  export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
76
- if (input.kind === "electron" && input.compiledElectron.action === "launch")
77
- return withAgentBrowserProcessEnvironment({ AGENT_BROWSER_SESSION: undefined }, () => run(input));
78
- if (input.kind === "electron" || isPlainTextInspectionArgs(input.toolArgs))
76
+ if (isPlainTextInspectionArgs(input.toolArgs))
79
77
  return run(input);
80
78
  const env = getAgentBrowserProcessEnvironment();
81
79
  const configArg = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--config")[0];
@@ -85,12 +83,19 @@ export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
85
83
  : [join(homedir(), ".agent-browser", "config.json"), join(cwd, "agent-browser.json")];
86
84
  const rootName = root && rootBrowserSessionName(root.id);
87
85
  const explicitSession = extractExplicitSessionName(input.toolArgs);
86
+ const browserCommand = input.kind !== "electron" && needsManagedSession(parseArgvDescriptor(input.toolArgs), input.toolStdin);
88
87
  const rootFallback = root !== undefined && env.AGENT_BROWSER_SESSION === undefined && (explicitSession === undefined || explicitSession === rootName)
89
- && needsManagedSession(parseArgvDescriptor(input.toolArgs), input.toolStdin);
88
+ && browserCommand;
90
89
  let identity = {};
91
- const browserCommand = needsManagedSession(parseArgvDescriptor(input.toolArgs), input.toolStdin);
92
90
  for (const path of paths)
93
91
  identity = { ...identity, ...await readNativeIdentity(path, cwd, signal, rootFallback, browserCommand) };
92
+ if (input.kind === "electron") {
93
+ const nativeConfirmActions = env.AGENT_BROWSER_CONFIRM_ACTIONS ?? (typeof identity.confirmActions === "string" ? identity.confirmActions : undefined);
94
+ return withAgentBrowserProcessEnvironment({
95
+ ...(input.compiledElectron.action === "launch" ? { AGENT_BROWSER_SESSION: undefined } : {}),
96
+ ...(configPath !== undefined ? { AGENT_BROWSER_CONFIG: resolve(cwd, configPath) } : {}),
97
+ }, () => run({ ...input, nativeConfirmActions }));
98
+ }
94
99
  // Native ORs environment/config booleans; only CLI false overrides configured auto-connect.
95
100
  const autoConnect = getBooleanFlagValue(input.toolArgs, "--auto-connect")
96
101
  ?? (isUpstreamEnvFlagEnabled(env.AGENT_BROWSER_AUTO_CONNECT) || identity.autoConnect === true);
@@ -100,11 +105,12 @@ export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
100
105
  const configuredSession = env.AGENT_BROWSER_SESSION ?? identity.session;
101
106
  const launchArgs = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--args").at(-1)?.value ?? env.AGENT_BROWSER_ARGS ?? identity.args;
102
107
  const engine = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--engine").at(-1)?.value ?? env.AGENT_BROWSER_ENGINE ?? identity.engine;
108
+ const chromeEngine = engine === undefined || engine === "chrome";
103
109
  const provider = scanUpstreamGlobalFlagOccurrences(input.toolArgs, "--provider").at(-1)?.value ?? scanUpstreamGlobalFlagOccurrences(input.toolArgs, "-p").at(-1)?.value ?? env.AGENT_BROWSER_PROVIDER ?? identity.provider;
104
110
  const tokens = parseArgvDescriptor(input.toolArgs).upstreamCommandTokens;
105
111
  const batchAttaches = requestsConnection(tokens, input.toolStdin);
106
112
  const localChrome = browserCommand && !attachment && !batchAttaches && provider === undefined
107
- && (engine === undefined || engine === "chrome");
113
+ && chromeEngine;
108
114
  const chromeStartupArgs = localChrome ? ["--no-startup-window", ...(typeof launchArgs === "string" ? [launchArgs] : [])].join(",") : undefined;
109
115
  const persistentChromeArgs = hasLocalLaunchDefaults(identity, env) ? chromeStartupArgs : undefined;
110
116
  const configuredChromeLaunch = persistentChromeArgs !== undefined || [...LOCAL_VALUE_DEFAULTS, ...LOCAL_BOOLEAN_DEFAULTS, ...LOCAL_ARRAY_DEFAULTS, "hideScrollbars", "clearCaCert"].some(key => {
@@ -122,12 +128,22 @@ export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
122
128
  if (session !== undefined && extractExplicitSessionName(args) === undefined)
123
129
  args = ["--session", session, ...args];
124
130
  const idleTimeout = scanUpstreamGlobalFlagOccurrences(args, "--idle-timeout").at(-1)?.value;
131
+ const actionPolicy = scanUpstreamGlobalFlagOccurrences(args, "--action-policy").at(-1)?.value;
132
+ const confirmActions = scanUpstreamGlobalFlagOccurrences(args, "--confirm-actions").at(-1)?.value;
133
+ const nativeConfirmActions = confirmActions ?? env.AGENT_BROWSER_CONFIRM_ACTIONS
134
+ ?? (typeof identity.confirmActions === "string" ? identity.confirmActions : undefined);
135
+ const debug = getBooleanFlagValue(args, "--debug");
136
+ const noAutoDialog = getBooleanFlagValue(args, "--no-auto-dialog");
125
137
  return withAgentBrowserProcessEnvironment({
126
138
  ...(idleTimeout !== undefined ? { AGENT_BROWSER_IDLE_TIMEOUT_MS: idleTimeout } : {}),
139
+ ...(actionPolicy !== undefined ? { AGENT_BROWSER_ACTION_POLICY: actionPolicy } : {}),
140
+ ...(confirmActions !== undefined ? { AGENT_BROWSER_CONFIRM_ACTIONS: confirmActions } : {}),
141
+ ...(debug !== undefined ? { AGENT_BROWSER_DEBUG: debug ? "1" : undefined } : {}),
142
+ ...(noAutoDialog !== undefined ? { AGENT_BROWSER_NO_AUTO_DIALOG: noAutoDialog ? "1" : "0" } : {}),
127
143
  ...(configPath !== undefined ? { AGENT_BROWSER_CONFIG: resolve(cwd, configPath) } : {}),
128
144
  ...(session !== undefined ? { AGENT_BROWSER_SESSION: session } : {}),
129
145
  ...(namespace !== undefined ? { AGENT_BROWSER_NAMESPACE: namespace } : {}),
130
- }, () => run({ ...input, toolArgs: args, chromeStartupArgs, persistentChromeArgs, configuredChromeLaunch }, !rootDefault ? undefined : async (browserRun, launchSignal = signal) => {
146
+ }, () => run({ ...input, toolArgs: args, nativeConfirmActions, chromeStartupArgs, persistentChromeArgs, configuredChromeLaunch }, !rootDefault ? undefined : async (browserRun, launchSignal = signal) => {
131
147
  const daemon = await inspectManagedSessionDaemon({ cwd, signal: launchSignal, sessionName: rootDefault,
132
148
  namespace: scanUpstreamGlobalFlagOccurrences(args, "--namespace").at(-1)?.value ?? namespace, timeoutMs: 5_000 });
133
149
  if (daemon.status === "missing-binary")
@@ -147,13 +163,16 @@ export async function withNativeSessionDefaults(input, cwd, signal, run, root) {
147
163
  if (restoreEligible && bootstrap)
148
164
  restore = rootDefault;
149
165
  const profile = scanUpstreamGlobalFlagOccurrences(args, "--profile").at(-1)?.value
150
- ?? (bootstrap && restoreEligible && identity.profile === undefined && env.AGENT_BROWSER_PROFILE === undefined ? root?.profile : undefined);
166
+ ?? (bootstrap && chromeEngine && restoreEligible && identity.profile === undefined && env.AGENT_BROWSER_PROFILE === undefined ? root?.profile : undefined);
151
167
  const executablePath = scanUpstreamGlobalFlagOccurrences(args, "--executable-path").at(-1)?.value
152
- ?? (bootstrap && identity.executablePath === undefined && env.AGENT_BROWSER_EXECUTABLE_PATH === undefined ? root?.executablePath : undefined);
168
+ ?? (bootstrap && chromeEngine && identity.executablePath === undefined && env.AGENT_BROWSER_EXECUTABLE_PATH === undefined ? root?.executablePath : undefined);
153
169
  return withAgentBrowserProcessEnvironment({
154
170
  ...(restore !== undefined ? { AGENT_BROWSER_RESTORE: restore } : {}),
155
171
  ...(profile !== undefined ? { AGENT_BROWSER_PROFILE: profile } : {}),
156
172
  ...(executablePath !== undefined ? { AGENT_BROWSER_EXECUTABLE_PATH: executablePath } : {}),
157
173
  }, () => withChromeStartupArgs(bootstrap || configuredChromeLaunch ? chromeStartupArgs : undefined, () => browserRun(bootstrap)));
158
- }));
174
+ }), [
175
+ ...(debug !== undefined ? [["--debug", String(debug)]] : []),
176
+ ...(noAutoDialog !== undefined ? [["--no-auto-dialog", String(noAutoDialog)]] : []),
177
+ ]); // Native ORs env/config booleans; helpers need CLI false to override true config.
159
178
  }
@@ -1,6 +1,7 @@
1
1
  import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, extname, isAbsolute, resolve } from "node:path";
3
3
  import { isRecord } from "../parsing.js";
4
+ import { getBooleanFlagValue } from "../argv-grammar.js";
4
5
  import { isSessionArtifactManifest } from "../results/artifact-manifest.js";
5
6
  import { parseCommandInfo, redactSensitiveValue } from "../runtime.js";
6
7
  export function normalizeRequestedOutputPath(path) {
@@ -80,8 +81,9 @@ function serializeOutputPayload(value) {
80
81
  function appendOutputFileNotice(result, message, failed = false) {
81
82
  const content = [...(result.content ?? [])];
82
83
  if (content[0]?.type === "text") {
84
+ const nativeText = isRecord(result.details) && Array.isArray(result.details.args) && getBooleanFlagValue(result.details.args, "--json") === false;
83
85
  try {
84
- const json = JSON.parse(content[0].text);
86
+ const json = nativeText ? undefined : JSON.parse(content[0].text);
85
87
  if (isRecord(json) && typeof json.success === "boolean") {
86
88
  content[0] = { type: "text", text: JSON.stringify({ ...json, ...(failed ? { success: false, error: message } : {}), outputFileNotice: message }, null, 2) };
87
89
  return content;
@@ -2,6 +2,8 @@ import { parseArgvDescriptor } from "./argv-descriptor.js";
2
2
  import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS, } from "./argv-grammar.js";
3
3
  import { isBrowserIndependentRead, needsManagedSession } from "./command-policy.js";
4
4
  import { isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
5
+ import { isRecord } from "./parsing.js";
6
+ import { detectConfirmationRequired } from "./results/confirmation.js";
5
7
  import { parseBatchCommandArgument, parseUserBatchStdin } from "./orchestration/batch-stdin.js";
6
8
  const UNVERIFIED_PAGE_MESSAGE = "The active page became unverified after a tab, attachment, history, script, or state-load transition. Run get url or navigate explicitly before page-content inspection.";
7
9
  const BATCH_UNVERIFIED_PAGE_MESSAGE = `${UNVERIFIED_PAGE_MESSAGE} In a batch, put get url after the transition before later content steps, or split the batch at that boundary.`;
@@ -31,7 +33,7 @@ function getPositionalOperands(commandTokens) {
31
33
  }
32
34
  return values;
33
35
  }
34
- function getExplicitNavigationTarget(args) {
36
+ export function getExplicitNavigationTarget(args) {
35
37
  const descriptor = parseArgvDescriptor(args);
36
38
  const positionals = getPositionalOperands(descriptor.upstreamCommandTokens);
37
39
  if (EXPLICIT_NAVIGATION_COMMANDS.has(descriptor.commandInfo.command ?? ""))
@@ -85,6 +87,8 @@ function getBatchCommandSteps(args, stdin) {
85
87
  if (parsedStdin.error)
86
88
  return { error: parsedStdin.error, steps: [] };
87
89
  for (const step of parsedStdin.steps ?? []) {
90
+ if (step.length === 0)
91
+ continue;
88
92
  if (parseArgvDescriptor(step).commandInfo.command === "batch")
89
93
  return { error: NESTED_BATCH_ARGUMENT_MESSAGE, steps: [] };
90
94
  steps.push(step);
@@ -153,7 +157,10 @@ export function getResultingPageTargetState(options) {
153
157
  || isUnverifiedPageTransitionCommand(descriptor.commandInfo.command, descriptor.commandInfo.subcommand);
154
158
  return { ...getResultingPageState({ ...state, args: options.args, trustedBatchTabSelection: false }), pageTargetMayHaveChanged };
155
159
  }
156
- for (const step of options.executedBatchSteps) {
160
+ for (const [index, step] of options.executedBatchSteps.entries()) {
161
+ const row = Array.isArray(options.batchResults) ? options.batchResults[index] : undefined;
162
+ if (isRecord(row) && detectConfirmationRequired(row.result))
163
+ continue;
157
164
  const stepDescriptor = parseArgvDescriptor(step);
158
165
  pageTargetMayHaveChanged ||= getExplicitNavigationTarget(step) !== undefined
159
166
  || isUnverifiedPageTransitionCommand(stepDescriptor.commandInfo.command, stepDescriptor.commandInfo.subcommand);
@@ -211,7 +218,7 @@ export function getPageTargetValidationError(options) {
211
218
  let directError;
212
219
  let failedNavigationHazard = false;
213
220
  for (const state of possibleStates) {
214
- const error = getUnverifiedPageError({
221
+ const error = options.allowFirstBatchConfirmation === true && index === 0 ? undefined : getUnverifiedPageError({
215
222
  allowUnverifiedPageTransitions: options.allowUnverifiedPageTransitions,
216
223
  args: step,
217
224
  pageUrlUnknown: state.pageUrlUnknown,
@@ -5,6 +5,7 @@ import { compileAgentBrowserNetworkSourceLookup, compileAgentBrowserSourceLookup
5
5
  import { compileAgentBrowserSemanticAction } from "./input-modes/semantic-action.js";
6
6
  import { isRecord } from "./parsing.js";
7
7
  import { redactInvocationArgs } from "./runtime.js";
8
+ import { isBooleanFlagEnabled } from "./argv-grammar.js";
8
9
  const TUI_INVOCATION_PREVIEW_MAX_CHARS = 160;
9
10
  const TUI_COLLAPSED_OUTPUT_MAX_LINES = 12;
10
11
  const ANSI_CONTROL_SEQUENCE_PATTERN = /\x1B(?:\][^\x07\x1B\r\n\u2028\u2029]*(?:\x07|\x1B\\)|\[[0-?]*[ -/]*[@-~]|P[^\x1B\r\n\u2028\u2029]*(?:\x1B\\)|_[^\x1B\r\n\u2028\u2029]*(?:\x1B\\)|\^[^\x1B\r\n\u2028\u2029]*(?:\x1B\\)|[@-Z\\-_])/g;
@@ -188,7 +189,7 @@ function agentBrowserToolResultRequestedJson(result, input) {
188
189
  const details = isRecord(result.details) ? result.details : undefined;
189
190
  const detailArgs = Array.isArray(details?.args) ? details.args : undefined;
190
191
  const inputArgs = isRecord(input) && Array.isArray(input.args) ? input.args : undefined;
191
- return detailArgs?.includes("--json") === true || inputArgs?.includes("--json") === true;
192
+ return isBooleanFlagEnabled(detailArgs ?? [], "--json") || isBooleanFlagEnabled(inputArgs ?? [], "--json");
192
193
  }
193
194
  function agentBrowserToolResultHasParseableJsonContent(content) {
194
195
  return content.some((item) => {
@@ -14,7 +14,7 @@ export const QUICK_START_GUIDELINES = [
14
14
  "Example agent_browser_code: { code: 'const result = await browser({args:[\"get\",\"title\"]}); if (!result.success) { emit(result); } else { emit({title:result.data}); }' }. Native batch still avoids extra CLI processes for fixed segments inside code. Explicit session/namespace controls select the browser for this cell without changing the default browser.",
15
15
  "Call agent_browser_tools with no fields for inventory, or { enable: [\"action\", \"qa\", \"electron\", \"source\", \"network\"] } for advanced native tools. It adds agent_browser_action, agent_browser_qa, agent_browser_electron, agent_browser_source, and agent_browser_network_source without disabling other tools. The browser instructions cover these capabilities; activation reveals their selected tool declarations.",
16
16
  "Use read <url> for readable web text; get title/url, get text/html/value/count <selector>, or get attr <selector> <name> for targeted state. For eval --stdin, put JavaScript in stdin and return the desired value. outputPath saves result data and must differ from screenshot/download/recording paths. Use --json only when JSON text is needed.",
17
- "For artifacts, verify artifactVerification and artifacts before reporting success. A pending recording is not verified output; install ffmpeg before starting and pair start with stop. Native download owns the click and saves Blob/redirect downloads. Close keeps explicitly saved files. Save promptGuard-required paths before close. Screenshot pixels are not browser CSS coordinates: use imageObservations geometry (dimensions, viewport, crop/origin, scroll and DPR) before any visual-coordinate action; full-page and element captures are not current-viewport coordinates, and Pi resizing only maps sent pixels to original image pixels.",
17
+ "For artifacts, verify artifactVerification and artifacts before reporting success. A pending recording is not verified output; install ffmpeg before starting and pair start with stop. Native download owns the click and saves Blob/redirect downloads. Close keeps explicitly saved files. Save promptGuard-required paths before close. Screenshot pixels are not browser CSS coordinates: use imageObservations geometry (dimensions, viewport, crop/origin, scroll and DPR) before any visual-coordinate action; full-page and element captures are not current-viewport coordinates, and Pi resizing only maps sent pixels to original image pixels. Lightpanda images are text-rendered, not graphical layout; rendering:text never supports CSS/mouse-coordinate input.",
18
18
  "Follow model-visible nextActions when present, including identity and stdin, instead of guessing commands. Treat native pending confirmations and unknown mutation outcomes as inspection boundaries. For dense snapshots, inspect Omitted high-value controls before reading a full spill.",
19
19
  ];
20
20
  export const ADVANCED_TOOL_PROMPT_GUIDELINES = {
@@ -61,6 +61,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
61
61
  "Cooperating Pi processes serialize each complete browser operation and code cell by native socket context, namespace, and session, including target/ref helpers and state updates. Different identities remain concurrent; namespace-wide close waits for matching operations. All participating Pi instances must load this release; humans, third-party clients, and older extensions are outside that coordination. It is not a transaction or rollback. For raw batches whose later content depends on navigation, use exact batch --bail or split the calls.",
62
62
  "After a successful `connect`, `--cdp`, or enabled `--auto-connect` call, verify with get url and keep using the resulting session without repeating the attach flag. The wrapper remembers that attachment across active-branch reload/resume and live-checks the URL before later page reads/interactions because an attached browser can drift externally; caller config, file access, launch arguments, and environment pass through unchanged. A successful close clears the marker. When several named sessions share one Chrome, pass --pin-tab once (AGENT_BROWSER_PIN_TAB) so a closed bound tab fails as tab_gone instead of acting on a neighbor; recover with tab new or tab list. --no-pin-tab turns the sticky pin off. tab list includes each tab's CDP targetId, accepted as a tab ref.",
63
63
  `If you already used the implicit session and now need launch-scoped flags (${LAUNCH_SCOPED_FLAG_LABEL}), retry with top-level sessionMode set to fresh or pass an explicit --session for the new launch; never pass --session-mode inside args. After a successful unnamed fresh launch, later auto calls follow that new session.`,
64
+ "Chrome is the default. Use separately installed Lightpanda only when explicitly selected with --engine lightpanda, environment or native config; --engine is launch-scoped, so use sessionMode:fresh to change a managed browser. Lightpanda text-rendered screenshots do not prove graphical layout/visibility or mouse coordinates. Chrome profiles, headed mode, multi-tab, WebGPU and native WebMCP are not parity capabilities; choose Chrome explicitly when needed, never assume automatic fallback.",
64
65
  "For WebGPU pages, use args [\"--webgpu\", \"open\", \"<url>\"] on a fresh local browser launch; use doctor --webgpu (or --headed on Linux/Windows capture paths) to prove rendering before trusting a non-black screenshot. WebGPU cannot be combined with --cdp, --auto-connect, or provider launches unless --webgpu false overrides an enabled config/environment default.",
65
66
  "For experimental WebMCP page tools, use webmcp list, then webmcp invoke <tool> with --params and optional --frame/--detach/--timeout; use webmcp result or cancel for detached calls. Locally managed Chrome enables WebMCP by default; a positive navigation hint means the page has tools to list. --no-webmcp is launch-scoped and requires a fresh session; invoke/result/cancel can mutate or navigate, so refresh snapshot refs afterward.",
66
67
  "For --allowed-domains, use a fresh local Chrome context. Upstream rejects CDP/auto-connect, profiles, restore/state replay, direct-page providers, iOS/Safari, and startup/profile Chrome args because they cannot guarantee containment; Chromium also disables RTCPeerConnection while the allowlist is active.",
@@ -72,7 +73,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
72
73
  "For non-core families, pass current upstream commands through the native tool directly: network requests, network route <url>, network har start/stop [path], diff snapshot, diff screenshot --baseline <file>, diff url <u1> <u2>, trace start, trace stop [path], profiler start, profiler stop [path], record start <path>, record stop, console/errors [--clear], highlight <selector>, inspect, clipboard read, clipboard write <text>, clipboard copy/paste, stream enable/disable/status, dashboard start/stop, device list for iOS simulator inventory, and chat <message>. For compact network requests output, prefer details.nextActions for request detail, route-mock diagnostics, actionable failed-request agent_browser_network_source, filtering, clearing the aggregate buffer before repro, or HAR capture follow-ups instead of guessing request-id syntax. Artifact-producing commands report details.artifacts and verification state; long-running starts such as stream, dashboard, trace/profiler, and record should be paired with the matching stop/disable command when the task is done; stream enable already-enabled outcomes are treated as idempotent success with status/disable follow-ups.",
73
74
  "For Electron desktop apps, enable agent_browser_electron through agent_browser_tools for wrapper-owned discovery, isolated launch, status, compact probe, and cleanup: list first, treat likely-sensitive annotations as hints rather than enforcement, launch with the default snapshot handoff unless handoff: \"tabs\" is the safer diagnostic starting point, use agent_browser_electron probe or snapshot -i/agent_browser_qa attached:true for current-session state, and always cleanup the returned launchId when done. agent_browser_electron launch uses an isolated temporary profile; it does not reuse the app's normal signed-in profile or attach to an already-running authenticated app. For signed-in local app state, host-launch the normal app with --remote-debugging-port when appropriate, then use raw args connect <port|url>; after connect, run get url to verify the active target before page-content reads, inspect tab list, select the stable tab id such as tab t2, verify it again with get url, then run a condition wait or snapshot -i before using refs. close commands (`close`, `quit`, or `exit`) only close the browser/CDP session; leave manually launched app shutdown, profile cleanup, and explicit artifacts to the host owner.",
74
75
  "For provider or specialized app workflows, load version-matched upstream guidance with skills get agentcore|electron|slack|dogfood|vercel-sandbox|derive-client through the native tool; add --full when you need references/templates, and use skills get --all only for broad skill audits. Use derive-client when recording HAR traffic to generate a standalone API client; prefer network har start (text bodies by default) or network har start --content all|none before multi-step capture. For accessibility audits use a11y or a11y --tags wcag2a,wcag2aa (CDP browsers only). Hosted sandbox workflows should use upstream @agent-browser/sandbox helpers outside this wrapper. Provider launches such as -p ios, --provider browserbase/kernel/browseruse/browserless/agentcore, and iOS --device are upstream-owned setup paths; use sessionMode fresh when switching providers and expect external credentials or local Appium/Xcode setup to be required.",
75
- "For dialogs and frames, use dialog status/accept/dismiss and frame <selector|main> through native args; dialog commands and eval snippets that look like alert/confirm/prompt/dialog triggers are shorter-bounded than normal browser calls, and timed-out dialog-like interactions may add inspect-dialog-after-timeout, dismiss-dialog-after-timeout, or recover-fresh-session-after-dialog-timeout nextActions. When --confirm-actions produces a pending confirmation, use details.nextActions or exact confirm <id> / deny <id> calls instead of inventing ids.",
76
+ "For dialogs and frames, use dialog status/accept/dismiss and frame <selector|main> through native args; dialog commands and eval snippets that look like alert/confirm/prompt/dialog triggers are shorter-bounded than normal browser calls, and timed-out dialog-like interactions may add inspect-dialog-after-timeout, dismiss-dialog-after-timeout, or recover-fresh-session-after-dialog-timeout nextActions. Set --confirm-actions on the session's first command; selected strings, including an explicit empty string, follow same-identity helpers and resumed follow-ups until successful close. Native unset keeps ordinary native defaults. Changing a running daemon's policy can discard pending confirmations. When it produces a pending confirmation, including a cold-reopen helper, follow the exact details.nextActions or confirm <id> / deny <id> calls without repeating the policy or inventing ids. Matching native guarded controls avoid page helpers that could replace the pending action; this is not native ID-validation proof or browser independence. In native batch, only a matching first effective decision gets that helper suppression; later page/ref checks remain. Nested decisions remain pending; failed inner actions fail, and completed browser actions reconcile their page state. A pending tab_new transport may show a validated after_confirmation open request and conditional guidance: follow it in the same session and new tab only after successful tab approval, never after denial. The extension does not execute that continuation. Confirmed full snapshots install returned refs; a final confirmed capture supplies the next same-identity call's freshness sample once, including semantic role click/check/fill and unique accessible select resolution, with target/membership/invalidation checks intact. On this one use, live verification compares the complete capture URL, including fragments, and rejects refs when it changes—including wrapper-owned managed sessions and warm sessions without resume. Ordinary ref/tab URL comparisons remain fragment-insensitive. Pending snapshots display confirmation required, not an empty page. Intervening calls or later rows consume it; DOM-only renames on the same page after that sample are not resampled on this one use.",
76
77
  "If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <tab-id-or-label> / snapshot -i to recover state before retrying different URLs or fallback strategies. For headed demos, put --headed on the first launch with sessionMode=fresh and verify with screenshot/tab/get-url evidence because tool success cannot prove the OS window is visible to the user. For desktop readiness, prefer real conditions first: wait --text, wait --url, wait --fn, wait --load <state>, wait --download, or agent_browser_qa attached:true; for disappearance checks, use wait --fn predicates instead of stale upstream-help examples like wait <selector> --state hidden. Use agent_browser_electron probe/status for wrapper-owned launch health or target mismatch. Fixed waits are a last resort: their duration is positional (wait <ms>, not wait --time <ms>). Use explicit --timeout or top-level timeoutMs for legitimately slow waits, and treat a successful payload like \"waited\":\"timeout\" as elapsed time only—verify completion with an observed condition, fresh snapshot, or screenshot.",
77
78
  "For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
78
79
  "For read-only browsing tasks, use read <url> for documentation or other unstructured text without requiring a Chrome page, or read with no URL for rendered active-tab DOM. Prefer the current snapshot, structured ref labels, getters, or scoped eval --stdin when you need interactive structure or targeted page state. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
@@ -80,7 +81,7 @@ export const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
80
81
  "On dashboards with nested scroll containers, verify scroll with a screenshot or fresh snapshot -i; if the viewport did not move, details.data.scrolled may be false/noMovement true and you should prefer scrollintoview <@ref> or target the actual scrollable region with scroll <selector> <dir> [px|percent]. For native selects, use select <selector> <value...> (or agent_browser_action select) instead of clicking option refs; for custom comboboxes, a click/agent_browser_action may only focus the field, so re-snapshot and use keyboard type <text> for focused input, press ArrowDown or press Enter, or visible option refs. Raw type requires both <selector> and <text>.",
81
82
  "When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
82
83
  "When using eval --stdin for extraction, pass the JavaScript through the native tool stdin field, not as an extra args token after --stdin, and return the value you want instead of relying on console.log as the primary result channel. Prefer plain expressions like ({ title: document.title }) or explicitly invoked functions like (() => ({ title: document.title }))(); use outputPath when the eval/get/snapshot data should be saved as a durable local file, but never reuse a screenshot, download, recording, or other browser artifact destination as outputPath. If a function-shaped snippet returns {}, details.evalStdinHint may warn that the function was serialized instead of called. Local file pages and caller-selected output paths are supported when upstream allows them. If get text on a broad CSS selector surfaces details.selectorTextVisibility or selectorTextVisibilityAll, prefer a visible @ref, a more specific selector, or the inspect-visible-text-candidates nextAction over hidden tab content.",
83
- "When details.pageChangeSummary is present, use changeType and summary as a compact signal for navigation, DOM mutation, confirmations, or artifacts; when nextActionIds is set, match those ids to entries in details.nextActions (or per-step nextActions inside batch) for concrete follow-up payloads instead of inferring from prose alone. Click-dispatch probes require native get attr readback of a temporary marker on the candidate target; unproven ref or XPath identity leaves the native click unverified, not failed. If details.clickDispatch reports a click-dispatch miss, refresh/inspect/retry the real click first; for static local fixtures only, an explicit eval --stdin programmatic .click() can exercise app handlers, but treat it as an untrusted scripted workaround and never use it to bypass stop-before-submit/order/purchase boundaries. If an upstream click failure says the target is covered by another element at the target's click point, use the inspect-overlay-state nextAction to refresh refs and inspect the blocker before deciding whether to retry; do not blindly repeat the blocked click. If a no-navigation click surfaces details.overlayBlockers, inspect the fresh snapshot evidence before using a close/dismiss candidate nextAction; ordinary page chrome without dialog/alertdialog evidence should not trigger this diagnostic.",
84
+ "When details.pageChangeSummary is present, use changeType and summary as a compact signal for navigation, DOM mutation, confirmations, or artifacts; when nextActionIds is set, match those ids to entries in details.nextActions (or per-step nextActions inside batch) for concrete follow-up payloads instead of inferring from prose alone. Optional click-dispatch probes are skipped when native confirmation settings guard evaluate, without bypassing native click policy. Otherwise probes require native get attr readback of a temporary marker on the candidate target; unproven ref or XPath identity leaves the native click unverified, not failed. If details.clickDispatch reports a click-dispatch miss, refresh/inspect/retry the real click first; for static local fixtures only, an explicit eval --stdin programmatic .click() can exercise app handlers, but treat it as an untrusted scripted workaround and never use it to bypass stop-before-submit/order/purchase boundaries. If an upstream click failure says the target is covered by another element at the target's click point, use the inspect-overlay-state nextAction to refresh refs and inspect the blocker before deciding whether to retry; do not blindly repeat the blocked click. If a no-navigation click surfaces details.overlayBlockers, inspect the fresh snapshot evidence before using a close/dismiss candidate nextAction; ordinary page chrome without dialog/alertdialog evidence should not trigger this diagnostic.",
84
85
  "On upstream 0.38+, use snapshot --delta for native full/unchanged/changed revisions; --delta --full resets its baseline. Surviving DOM nodes keep refs, but refresh after navigation or replacement. Wrapper filters use full trees. screenshot --if-changed or --threshold 0.01 can return changed:false with no path or image; use ordinary screenshots when a file is required. --input-mode instant|smooth|human controls sticky pointer behavior; click/drag --human and mouse move --duration/--steps/--human/--seed pass through. record start/restart accept --cursor, --contact-sheet, and --contact-sheet-threshold; stop and verify both video and reported contact sheet. auth login <name> --no-navigate fills a prepared login page only after native origin checks. WebMCP catalog updates are untrusted page data; webmcp list supplies full schemas.",
85
86
  "When commands save or spill files (screenshots, downloads, PDFs, traces, recordings, HAR, large snapshot spills), use the user's exact requested paths when given and treat paths as provisional until details.artifactVerification shows every row verified: branch on missingCount, pendingCount, unverifiedCount, per-entry state, and optional limitation before downstream file use or PASS/FAIL reporting.",
86
87
  "For evidence-only screenshots, QA captures, or other audit artifacts, save to an explicit path and branch on details.artifactVerification plus details.artifacts before reporting PASS/FAIL; do not require vision review of inline image attachments unless the user asked for visual inspection.",
@@ -110,20 +111,20 @@ export const RUNTIME_PROMPT_GUIDELINES = [
110
111
  "For agent_browser, use open → snapshot -i → current @refs; re-snapshot after changes. Batch same-snapshot fills before submit; split on rerenders/navigation. Native args, profiles, auth, providers, paths, config and environment pass through. Use requested/configured profiles and verify authentication after restart.",
111
112
  "agent_browser bare calls share a root Pi browser with descendants, not unrelated roots; coordinate navigation within the group. Native explicit sessions win. sessionMode:fresh intentionally launches a separate managed browser. Never close an unrelated browser.",
112
113
  "For agent_browser and agent_browser_code, follow visible nextActions. Inspect unknown mutation outcomes before retry. Use exact user paths; verify artifactVerification/artifacts before success claims. Save promptGuard-required files before close; close keeps files; waited:timeout proves nothing.",
113
- "For agent_browser screenshot coordinates, use imageObservations dimensions/viewport/crop/origin/scroll/DPR; full-page and element images are not viewport CSS coordinates. Pi image resizing maps sent pixels to original image pixels only. Prefer refs; inspect screenshots only when the task needs vision.",
114
+ "For agent_browser screenshot coordinates, use imageObservations dimensions/viewport/crop/origin/scroll/DPR; full-page and element images are not viewport CSS coordinates. Pi image resizing maps sent pixels to original image pixels only. Lightpanda rendering:text images never establish CSS/mouse coordinates. Prefer refs; inspect screenshots only when the task needs vision.",
114
115
  "In agent_browser workflows, ordinary requested non-destructive submissions may proceed in authenticated unattended/auto-approved flows. Honor explicit stops; require explicit authorization for purchases, production-control, destructive/irreversible, or account/security/privacy changes.",
115
116
  "agent_browser: read <url> for text, get title/url, get text/html/value/count <selector>, get attr <selector> <name>; heed visibility warnings and Omitted high-value controls. Verify nested scrolling via snapshot/screenshot. Use --json only for JSON text; recording needs ffmpeg before start and a verified stop.",
116
117
  ];
117
118
  export function buildBrowserExecutablePathGuideline(executablePath) {
118
119
  if (!executablePath)
119
120
  return undefined;
120
- return `agent_browser config sets browser.executablePath to ${JSON.stringify(executablePath)}. Global/override values bootstrap inactive automatic root browsers; bare calls preserve active native launch settings. Project-only values remain guidance for explicit --executable-path launches. Use sessionMode:fresh only for an intentionally separate managed browser, not routine root-group browsing. Upstream profiles lists Chrome profiles only; use a supported explicit profile path for other Chromium browsers.`;
121
+ return `agent_browser config sets browser.executablePath to ${JSON.stringify(executablePath)}. Global/override values bootstrap inactive automatic root Chrome browsers, not Lightpanda; bare calls preserve active native launch settings. Project-only values remain guidance for explicit --executable-path launches. Use sessionMode:fresh only for an intentionally separate managed browser, not routine root-group browsing. Upstream profiles lists Chrome profiles only; use a supported explicit profile path for other Chromium browsers.`;
121
122
  }
122
123
  export function buildBrowserDefaultProfileGuideline(profile) {
123
124
  if (!profile || profile.policy === "explicit-only")
124
125
  return undefined;
125
126
  if (profile.policy === "always") {
126
- return `agent_browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)} with policy always. Global/override Chrome profile names bootstrap automatic root browsers through independent native copies with per-root restore; bare calls and helpers preserve the active native launch settings without resending bootstrap options. Paths and project-only settings stay advisory. Explicit unrelated sessions are unchanged. Verify actual app auth after restart: native JSON restore omits new IndexedDB and memory-only credentials; use a supported per-root persistent profile if needed. Profile content is model-visible user data.`;
127
+ return `agent_browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)} with policy always. Global/override Chrome profile names bootstrap automatic root Chrome browsers, not Lightpanda, through independent native copies with per-root restore; bare calls and helpers preserve the active native launch settings without resending bootstrap options. Paths and project-only settings stay advisory. Explicit unrelated sessions are unchanged. Verify actual app auth after restart: native JSON restore omits new IndexedDB and memory-only credentials; use a supported per-root persistent profile if needed. Profile content is model-visible user data.`;
127
128
  }
128
129
  return `agent_browser config sets browser.defaultProfile.name to ${JSON.stringify(profile.name)}; for signed-in/account-specific browser tasks, reuse a configured native shared session with bare calls, otherwise start with --profile ${JSON.stringify(profile.name)} plus sessionMode:fresh unless the user asks for a different profile.`;
129
130
  }
@@ -1,5 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { rm } from "node:fs/promises";
3
+ import { getBooleanFlagValue } from "./argv-grammar.js";
3
4
  import { writeSecureTempFile } from "./temp.js";
4
5
  const isolatedAgentBrowserEnvironment = new AsyncLocalStorage();
5
6
  const agentBrowserProcessEnvironment = new AsyncLocalStorage();
@@ -7,7 +8,7 @@ const PROXY_ENV_NAMES = new Set(["ALL_PROXY", "HTTPS_PROXY", "HTTP_PROXY", "NO_P
7
8
  export function getAgentBrowserProcessEnvironment(baseEnv = process.env) {
8
9
  const isolatedConfig = isolatedAgentBrowserEnvironment.getStore();
9
10
  if (isolatedConfig === undefined)
10
- return { ...baseEnv, ...agentBrowserProcessEnvironment.getStore() };
11
+ return { ...baseEnv, ...agentBrowserProcessEnvironment.getStore()?.env };
11
12
  return {
12
13
  ...Object.fromEntries(Object.entries(baseEnv).filter(([name]) => {
13
14
  const normalizedName = name.toUpperCase();
@@ -16,8 +17,16 @@ export function getAgentBrowserProcessEnvironment(baseEnv = process.env) {
16
17
  AGENT_BROWSER_CONFIG: isolatedConfig,
17
18
  };
18
19
  }
19
- export function withAgentBrowserProcessEnvironment(env, run) {
20
- return agentBrowserProcessEnvironment.run({ ...agentBrowserProcessEnvironment.getStore(), ...env }, run);
20
+ export function getAgentBrowserProcessArgs(args) {
21
+ if (isolatedAgentBrowserEnvironment.getStore() !== undefined)
22
+ return args;
23
+ const overrides = agentBrowserProcessEnvironment.getStore()?.booleanArgs ?? [];
24
+ const missing = overrides.flatMap(([flag, value]) => getBooleanFlagValue(args, flag) === undefined ? [flag, value] : []);
25
+ return missing.length ? [...missing, ...args] : args;
26
+ }
27
+ export function withAgentBrowserProcessEnvironment(env, run, booleanArgs) {
28
+ const parent = agentBrowserProcessEnvironment.getStore();
29
+ return agentBrowserProcessEnvironment.run({ env: { ...parent?.env, ...env }, booleanArgs: booleanArgs ?? parent?.booleanArgs ?? [] }, run);
21
30
  }
22
31
  export async function withIsolatedAgentBrowserEnvironment(run) {
23
32
  if (isolatedAgentBrowserEnvironment.getStore() !== undefined)
@@ -9,7 +9,7 @@ import { extractExplicitSessionName, resolveAgentBrowserNamespace, scanUpstreamG
9
9
  import { commitManagedSessionRestoreSuppression, getManagedSessionRestoreEnv, getManagedSessionRestoreProtectedEnv, getOwnedManagedSessionCompatibilityEnv, getOwnedManagedSessionNamespaceEnv, isOwnedManagedSessionTarget, validateManagedSessionRestoreContextForSpawn, } from "./managed-session-restore.js";
10
10
  import { getPageTargetValidationError, } from "./page-target-validation.js";
11
11
  import { getImplicitSessionIdleTimeoutMs } from "./runtime.js";
12
- import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
12
+ import { getAgentBrowserProcessArgs, getAgentBrowserProcessEnvironment } from "./process-environment.js";
13
13
  import { openSecureTempFile, writeSecureTempChunk } from "./temp.js";
14
14
  import { resolveWindowsStockLauncher } from "./windows-stock-launcher.js";
15
15
  const MAX_BUFFERED_STDOUT_BYTES = 512 * 1_024;
@@ -328,11 +328,11 @@ export function buildAgentBrowserProcessEnv(baseEnv = processEnv, overrides = un
328
328
  clampUpstreamDefaultTimeout(childEnv);
329
329
  return childEnv;
330
330
  }
331
- function getManagedPreSpawnPolicyError(options, currentPageUrl, pageUrlUnknown = false, browserIndependentReadConfirmation = false) {
331
+ function getManagedPreSpawnPolicyError(options, currentPageUrl, pageUrlUnknown = false, nativeConfirmationDecision = false) {
332
332
  if (!validateManagedSessionRestoreContextForSpawn(options)) {
333
333
  return "Managed session restore policy, storage, or checkout identity changed after planning; refusing to start agent-browser.";
334
334
  }
335
- if (browserIndependentReadConfirmation)
335
+ if (nativeConfirmationDecision)
336
336
  return undefined;
337
337
  return getPageTargetValidationError({
338
338
  args: options.args,
@@ -361,7 +361,7 @@ export async function runAgentBrowserProcess(options) {
361
361
  restoreState: managedSessionRestoreState,
362
362
  stdin,
363
363
  };
364
- const planningPolicyError = getManagedPreSpawnPolicyError(managedSessionRestoreOptions, managedStateCurrentPageUrl, managedStatePageUrlUnknown, options.browserIndependentReadConfirmation);
364
+ const planningPolicyError = getManagedPreSpawnPolicyError(managedSessionRestoreOptions, managedStateCurrentPageUrl, managedStatePageUrlUnknown, options.nativeConfirmationDecision);
365
365
  if (planningPolicyError) {
366
366
  return {
367
367
  aborted: false,
@@ -509,13 +509,13 @@ export async function runAgentBrowserProcess(options) {
509
509
  });
510
510
  });
511
511
  };
512
- const spawnPolicyError = getManagedPreSpawnPolicyError(managedSessionRestoreOptions, managedStateCurrentPageUrl, managedStatePageUrlUnknown, options.browserIndependentReadConfirmation);
512
+ const spawnPolicyError = getManagedPreSpawnPolicyError(managedSessionRestoreOptions, managedStateCurrentPageUrl, managedStatePageUrlUnknown, options.nativeConfirmationDecision);
513
513
  if (spawnPolicyError) {
514
514
  resolve({ aborted: false, agentBrowserStarted: false, exitCode: 1, spawnError: new Error(spawnPolicyError), stderr: "", stdout: "", timedOut: false });
515
515
  return;
516
516
  }
517
517
  const spawnBrowser = processPlatform === "win32" && !stockLauncher ? crossSpawn : spawn;
518
- const child = spawnBrowser(stockLauncher ?? "agent-browser", prepareAgentBrowserSpawnArgs(args, ownedManagedSessionCompatibilityEnv.AGENT_BROWSER_USER_AGENT, preserveAttachedBrowserSession, chromeStartupArgsContext.getStore()), {
518
+ const child = spawnBrowser(stockLauncher ?? "agent-browser", prepareAgentBrowserSpawnArgs(getAgentBrowserProcessArgs(args), ownedManagedSessionCompatibilityEnv.AGENT_BROWSER_USER_AGENT, preserveAttachedBrowserSession, chromeStartupArgsContext.getStore()), {
519
519
  cwd,
520
520
  env: childEnv,
521
521
  stdio: ["pipe", "pipe", "pipe"],