pi-agent-browser-native 0.6.9 → 0.6.11

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 (54) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +62 -19
  3. package/dist/extensions/agent-browser/index.js +424 -451
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -7
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +7 -1
  6. package/dist/extensions/agent-browser/lib/batch-lifecycle.js +4 -8
  7. package/dist/extensions/agent-browser/lib/command-policy.js +41 -2
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +15 -2
  9. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
  10. package/dist/extensions/agent-browser/lib/input-modes/script.js +3 -2
  11. package/dist/extensions/agent-browser/lib/managed-session-restore.js +42 -12
  12. package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +3 -5
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +6 -14
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -25
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +12 -6
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +1 -0
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +3 -2
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +38 -31
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +60 -20
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +161 -0
  21. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +5 -5
  22. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +2 -4
  23. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +68 -0
  24. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -6
  25. package/dist/extensions/agent-browser/lib/page-target-validation.js +9 -5
  26. package/dist/extensions/agent-browser/lib/playbook.js +13 -12
  27. package/dist/extensions/agent-browser/lib/process-environment.js +26 -8
  28. package/dist/extensions/agent-browser/lib/process.js +8 -5
  29. package/dist/extensions/agent-browser/lib/read-confirmation.js +59 -0
  30. package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -1
  31. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +8 -0
  32. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -5
  33. package/dist/extensions/agent-browser/lib/results/categories.js +4 -2
  34. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +76 -57
  35. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
  36. package/dist/extensions/agent-browser/lib/results/presentation/common.js +5 -25
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +40 -38
  38. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +1 -0
  39. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +3 -3
  40. package/dist/extensions/agent-browser/lib/results/presentation.js +38 -9
  41. package/dist/extensions/agent-browser/lib/results/recording.js +50 -0
  42. package/dist/extensions/agent-browser/lib/runtime.js +72 -20
  43. package/dist/extensions/agent-browser/lib/session-page-state.js +24 -8
  44. package/dist/extensions/agent-browser/lib/temp.js +4 -0
  45. package/dist/scripts/agent-browser-target.mjs +1 -1
  46. package/docs/ARCHITECTURE.md +29 -12
  47. package/docs/COMMAND_REFERENCE.md +67 -31
  48. package/docs/RELEASE.md +6 -4
  49. package/docs/SUPPORT_MATRIX.md +24 -16
  50. package/docs/TOOL_CONTRACT.md +82 -28
  51. package/package.json +1 -1
  52. package/scripts/agent-browser-capability-baseline.mjs +10 -3
  53. package/scripts/agent-browser-target.mjs +1 -1
  54. package/scripts/prepare.mjs +2 -4
@@ -5,14 +5,15 @@ import { Text } from "@earendil-works/pi-tui";
5
5
  import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
6
6
  import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
7
7
  import { SessionPageState } from "./lib/session-page-state.js";
8
- import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, isPlainTextInspectionArgs, } from "./lib/runtime.js";
9
- import { extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
8
+ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
9
+ import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
10
10
  import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
11
11
  import { needsManagedSession } from "./lib/command-policy.js";
12
12
  import { ManagedSessionRestoreState } from "./lib/managed-session-restore.js";
13
13
  import { isRecord } from "./lib/parsing.js";
14
14
  import { runAgentBrowserProcess } from "./lib/process.js";
15
15
  import { getAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
16
+ import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
16
17
  import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
17
18
  import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
18
19
  import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
@@ -26,13 +27,14 @@ import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./
26
27
  import { parseBatchCommandArgument, parseUserBatchStdin } from "./lib/orchestration/batch-stdin.js";
27
28
  import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
28
29
  import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
29
- import { applyAgentBrowserOutputPath, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
30
+ import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
30
31
  import { appendScriptSessionLease, buildScriptBrowserEnvelope, buildScriptToolResult, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
31
32
  import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
32
33
  import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
33
34
  import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
34
35
  import { appendRecordingReservationTransition, applyRecordingArtifactsToReservations, restoreRecordingReservationStateFromBranch, retireRecordingReservation, } from "./lib/recording-reservations.js";
35
36
  import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
37
+ import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
36
38
  import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
37
39
  import { AgentBrowserResultComponent, buildAgentBrowserToolResultPatch, formatAgentBrowserRenderCall, formatAgentBrowserRenderResult, } from "./lib/pi-tool-rendering.js";
38
40
  function isBashToolCallEvent(event) {
@@ -67,7 +69,7 @@ function getArtifactPreflightValidationError(options) {
67
69
  return error;
68
70
  const activeRecordingDestinations = new Set();
69
71
  const cleanupOnly = steps.length > 0 && steps.every((step) => {
70
- const [command, subcommand] = extractUpstreamCommandTokens(step);
72
+ const [command, subcommand] = step;
71
73
  return isCloseCommand(command) || (command === "record" && subcommand === "stop");
72
74
  });
73
75
  for (const reservation of options.activeRecordingReservations ?? []) {
@@ -93,10 +95,9 @@ function getArtifactPreflightValidationError(options) {
93
95
  }
94
96
  const artifactDestinations = new Map();
95
97
  let sawBatchClose = false;
96
- for (const [index, step] of steps.entries()) {
97
- const commandStep = extractUpstreamCommandTokens(step);
98
+ for (const [index, commandStep] of steps.entries()) {
98
99
  if (batch) {
99
- const stepValidationError = validateToolArgs(step, { batchStep: true });
100
+ const stepValidationError = validateToolArgs(commandStep, { batchStep: true });
100
101
  if (stepValidationError)
101
102
  return `Unsupported batch step ${index + 1}: ${stepValidationError}`;
102
103
  if (sawBatchClose && commandStep[0] === "record" && (commandStep[1] === "start" || commandStep[1] === "restart")) {
@@ -127,7 +128,7 @@ function getArtifactPreflightValidationError(options) {
127
128
  }
128
129
  artifactDestinations.set(canonicalDestination, index);
129
130
  }
130
- if (batch && commandStep[0] === "screenshot" && step.includes("--annotate")) {
131
+ if (batch && commandStep[0] === "screenshot" && commandStep.includes("--annotate")) {
131
132
  return [
132
133
  `Unsupported batch screenshot annotation in step ${index + 1}: put --annotate in top-level args, not inside the batch step.`,
133
134
  `Use: { "args": ["--annotate", "batch"], "stdin": "[[\\"screenshot\\",\\"/path/to/image.png\\"]]" }`,
@@ -138,7 +139,7 @@ function getArtifactPreflightValidationError(options) {
138
139
  }
139
140
  function commandClosesAllSessions(args, stdin) {
140
141
  const parsed = getArtifactCommandSteps(args, stdin);
141
- return !parsed.error && parsed.steps.some((step) => isCloseAllCommand(extractUpstreamCommandTokens(step)));
142
+ return !parsed.error && parsed.steps.some(isCloseAllCommand);
142
143
  }
143
144
  function commandTouchesArtifactLifecycle(args, stdin, outputPath) {
144
145
  if (outputPath)
@@ -146,10 +147,7 @@ function commandTouchesArtifactLifecycle(args, stdin, outputPath) {
146
147
  const parsed = getArtifactCommandSteps(args, stdin);
147
148
  if (parsed.error)
148
149
  return true;
149
- return parsed.steps.some((step) => {
150
- const commandStep = extractUpstreamCommandTokens(step);
151
- return getExplicitArtifactDestination(commandStep) !== undefined || commandStep[0] === "record" || commandStep[0] === "screenshot" || isCloseCommand(commandStep[0]);
152
- });
150
+ return parsed.steps.some((step) => getExplicitArtifactDestination(step) !== undefined || step[0] === "record" || step[0] === "screenshot" || isCloseCommand(step[0]));
153
151
  }
154
152
  function isResultFileArtifact(artifact) {
155
153
  return isRecord(artifact)
@@ -161,26 +159,6 @@ function getResultFileArtifacts(result) {
161
159
  const details = isRecord(result.details) ? result.details : undefined;
162
160
  return Array.isArray(details?.artifacts) ? details.artifacts.filter(isResultFileArtifact) : [];
163
161
  }
164
- function reportsNoRecordingInProgress(value) {
165
- try {
166
- return /no recording in progress/i.test(JSON.stringify(value));
167
- }
168
- catch {
169
- return false;
170
- }
171
- }
172
- function batchStepReportsNoRecordingInProgress(step) {
173
- if (!isRecord(step) || step.success !== false)
174
- return false;
175
- const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? extractUpstreamCommandTokens(step.command) : [];
176
- return command[0] === "record" && command[1] === "stop" && reportsNoRecordingInProgress(step);
177
- }
178
- function resultReportsNoRecordingInProgress(result) {
179
- if (result.isError !== true)
180
- return false;
181
- const details = isRecord(result.details) ? result.details : undefined;
182
- return details?.command === "record" && details.subcommand === "stop" && reportsNoRecordingInProgress(result.content);
183
- }
184
162
  function restoreArtifactManifestFromBranch(branch) {
185
163
  let restoredManifest;
186
164
  for (const entry of branch) {
@@ -299,12 +277,6 @@ function detailsReportCloseAllApplied(details, succeeded) {
299
277
  || (succeeded && isCloseAllCommand(extractUpstreamCommandTokens(args)))
300
278
  || batchHasSuccessfulCloseAll(details.batchSteps);
301
279
  }
302
- function deleteIdentityKeysInNamespace(entries, namespace) {
303
- for (const key of entries.keys()) {
304
- if (isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
305
- entries.delete(key);
306
- }
307
- }
308
280
  function isAttachedBrowserInvocation(args, env = getAgentBrowserProcessEnvironment()) {
309
281
  const autoConnectEnv = env.AGENT_BROWSER_AUTO_CONNECT;
310
282
  return extractUpstreamCommandTokens(args)[0] === "connect"
@@ -663,9 +635,6 @@ async function closeOwnedManagedSessionsExcept(sessions, restoreState, keepSessi
663
635
  }
664
636
  }
665
637
  }
666
- async function closeOwnedManagedSessions(sessions, restoreState, timeoutMs, attachedSessionKeys, onClosed) {
667
- await closeOwnedManagedSessionsExcept(sessions, restoreState, undefined, timeoutMs, attachedSessionKeys, undefined, onClosed);
668
- }
669
638
  function getOffBranchOwnedElectronLaunchRecords(ownedRecords, branchRecords) {
670
639
  const activeBranchLaunchIds = new Set(getActiveElectronRecords(branchRecords).map((record) => record.launchId));
671
640
  const offBranchRecords = new Map();
@@ -814,7 +783,7 @@ function shouldIncludeProjectConfig(ctx, argv = process.argv) {
814
783
  return false;
815
784
  return ctx?.isProjectTrusted?.() ?? true;
816
785
  }
817
- export default function agentBrowserExtension(pi) {
786
+ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
818
787
  const ephemeralSessionSeed = createEphemeralSessionSeed();
819
788
  const agentBrowserConfig = loadAgentBrowserConfigSync({
820
789
  cwd: process.cwd(),
@@ -926,6 +895,8 @@ export default function agentBrowserExtension(pi) {
926
895
  if (result.isError !== true)
927
896
  return result;
928
897
  const details = isRecord(result.details) ? result.details : {};
898
+ if (details.recordingRecovery)
899
+ return result;
929
900
  const cleanupOnly = details.managedSessionCleanupOnlyReason === "restore-disabled-daemon-without-provenance";
930
901
  const nextActions = (Array.isArray(details.nextActions) ? details.nextActions : [])
931
902
  .filter((action) => !cleanupOnly || action.id !== "stop-pending-recording");
@@ -986,12 +957,6 @@ export default function agentBrowserExtension(pi) {
986
957
  const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? step.command : undefined;
987
958
  const commandTokens = command ? extractUpstreamCommandTokens(command) : [];
988
959
  const commandName = commandTokens[0];
989
- if (resultSessionName && batchStepReportsNoRecordingInProgress(step)) {
990
- const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
991
- retireRecordingSession(resultSessionName, resultNamespace, false);
992
- handledClosedSessionKeys.add(sessionKey);
993
- continue;
994
- }
995
960
  if (step.success === true && commandName && isCloseCommand(commandName) && resultSessionName) {
996
961
  const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
997
962
  retireRecordingSession(resultSessionName, resultNamespace, false);
@@ -1161,7 +1126,7 @@ export default function agentBrowserExtension(pi) {
1161
1126
  }
1162
1127
  for (const [key, reservation] of activeRecordingReservations) {
1163
1128
  const restored = restoredRecordingState.active.get(key);
1164
- if (restored?.absolutePath !== reservation.absolutePath || restored.cwd !== reservation.cwd)
1129
+ if (restored?.absolutePath !== reservation.absolutePath || restored.cwd !== reservation.cwd || restored.recordingId !== reservation.recordingId || restored.startedAtMs !== reservation.startedAtMs)
1165
1130
  recordingReservationsDirty = true;
1166
1131
  restoredRecordingState.active.set(key, reservation);
1167
1132
  }
@@ -1308,7 +1273,7 @@ export default function agentBrowserExtension(pi) {
1308
1273
  for (const identity of getCleanupResultsClosedManagedSessionIdentities(electronCleanupResults))
1309
1274
  retireRecordingSession(identity.sessionName, identity.namespace);
1310
1275
  if (quitting) {
1311
- await closeOwnedManagedSessions(ownedManagedSessions, managedSessionRestoreState, implicitSessionCloseTimeoutMs, attachedSessionKeys, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
1276
+ await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, undefined, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
1312
1277
  }
1313
1278
  else {
1314
1279
  await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionRestoreState, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs, attachedSessionKeys, managedSessionActive ? managedSessionNamespace : undefined, (owner) => retireRecordingSession(owner.sessionName, owner.namespace));
@@ -1393,445 +1358,453 @@ export default function agentBrowserExtension(pi) {
1393
1358
  component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
1394
1359
  return component;
1395
1360
  },
1396
- async execute(toolCallId, params, signal, onUpdate, ctx) {
1361
+ async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId) {
1397
1362
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
1398
1363
  const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
1399
1364
  const resolvedInput = resolveAgentBrowserInput({
1400
1365
  getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: ctx.cwd, outputPath, stdin }),
1401
- managedSessionActive,
1402
1366
  params,
1403
1367
  });
1404
1368
  if (resolvedInput.status === "invalid") {
1405
1369
  return buildValidationFailureResult(resolvedInput);
1406
1370
  }
1407
- const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
1408
- if (!outputPath || result.isError === true || (isRecord(result.details) && result.details.resultCategory === "failure"))
1409
- return warnRecordingPersistence(result);
1410
- return artifactExecutionQueue.run(async () => {
1411
- flushRecordingReservations();
1412
- const reservationError = getArtifactPreflightValidationError({
1413
- activeRecordingReservations: activeRecordingReservations.values(),
1414
- args: [],
1415
- cwd: ctx.cwd,
1416
- outputPath,
1417
- });
1418
- if (reservationError) {
1419
- return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1420
- }
1421
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
1422
- });
1423
- };
1424
- const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
1425
- const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
1426
- const browserBackedVersionCheck = needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs));
1427
- if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
1428
- const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
1429
- if (versionFailure)
1430
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1431
- }
1432
- if (resolvedInput.kind === "script") {
1433
- if (!ctx.sessionManager.getSessionFile()) {
1434
- return buildValidationFailureResult({
1435
- attemptedKind: "script",
1436
- kind: "invalid",
1437
- redactedArgs: [],
1438
- status: "invalid",
1439
- toolArgs: [],
1440
- validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
1441
- });
1371
+ if (resolvedInput.kind !== "script")
1372
+ await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
1373
+ return withNativeSessionDefaults(resolvedInput, ctx.cwd, signal, async (resolvedInput) => {
1374
+ const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1375
+ if (readConfirmation)
1376
+ resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
1377
+ if (resolvedInput.kind === "qa" && resolvedInput.compiledQaPreset.checks.attached && !managedSessionActive && !extractExplicitSessionName(resolvedInput.toolArgs)) {
1378
+ return buildValidationFailureResult({ ...resolvedInput, attemptedKind: "qa", kind: "invalid", status: "invalid", validationError: "qa.attached requires an active attached session. Run electron.launch or connect to an Electron debug port first, or configure a native shared session." });
1442
1379
  }
1443
- const sessionName = createAgentBrowserScriptSessionName();
1444
- const innerResults = [];
1445
- const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
1446
- const deadline = Date.now() + scriptTimeoutMs;
1447
- let leased = false;
1448
- let cleanupError;
1449
- let run = {
1450
- callCount: 0,
1451
- emitCount: 0,
1452
- error: "Script sandbox execution failed.",
1453
- failureCategory: "upstream-error",
1454
- ok: false,
1455
- rejectedCallCount: 0,
1456
- steps: [],
1380
+ const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
1381
+ if (!outputPath || !canWriteAgentBrowserOutput(result))
1382
+ return warnRecordingPersistence(result);
1383
+ return artifactExecutionQueue.run(async () => {
1384
+ flushRecordingReservations();
1385
+ const reservationError = getArtifactPreflightValidationError({
1386
+ activeRecordingReservations: activeRecordingReservations.values(),
1387
+ args: [],
1388
+ cwd: ctx.cwd,
1389
+ outputPath,
1390
+ });
1391
+ if (reservationError) {
1392
+ return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1393
+ }
1394
+ return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
1395
+ });
1457
1396
  };
1458
- const scriptController = new AbortController();
1459
- const abortScript = () => scriptController.abort();
1460
- signal?.addEventListener("abort", abortScript, { once: true });
1461
- if (signal?.aborted)
1462
- scriptController.abort();
1463
- activeScriptControllers.add(scriptController);
1464
- let finishScriptExecution;
1465
- const scriptExecution = new Promise((resolve) => {
1466
- finishScriptExecution = resolve;
1467
- });
1468
- activeScriptExecutions.add(scriptExecution);
1469
- try {
1470
- // Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
1471
- const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, scriptController.signal));
1397
+ const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
1398
+ const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
1399
+ const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
1400
+ if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
1401
+ const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
1472
1402
  if (versionFailure)
1473
1403
  return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1474
- const pendingRun = runAgentBrowserScript({
1475
- beforeFirstCall() {
1476
- appendScriptSessionLease(pi, sessionName, "active");
1477
- trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
1478
- managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
1479
- leased = true;
1480
- },
1481
- code: resolvedInput.compiledScript.code,
1482
- dispatch: async (innerParams, innerSignal) => {
1483
- const remainingMs = Math.max(1, deadline - Date.now());
1484
- const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
1485
- const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
1486
- args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
1487
- stdin: innerParams.stdin,
1488
- timeoutMs: innerTimeoutMs,
1489
- }, innerSignal, undefined, ctx));
1490
- innerResults.push(innerResult);
1491
- return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
1492
- },
1493
- signal: scriptController.signal,
1494
- timeoutMs: scriptTimeoutMs,
1495
- });
1496
- run = await pendingRun;
1497
1404
  }
1498
- catch { }
1499
- finally {
1500
- activeScriptControllers.delete(scriptController);
1501
- signal?.removeEventListener("abort", abortScript);
1502
- if (leased) {
1503
- try {
1504
- cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
1505
- flushRecordingReservations();
1506
- return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
1507
- }));
1508
- }
1509
- catch {
1510
- cleanupError = "The isolated script session cleanup operation failed.";
1511
- try {
1512
- appendScriptSessionLease(pi, sessionName, "failed");
1513
- }
1514
- catch { }
1515
- }
1405
+ if (resolvedInput.kind === "script") {
1406
+ if (!ctx.sessionManager.getSessionFile()) {
1407
+ return buildValidationFailureResult({
1408
+ attemptedKind: "script",
1409
+ kind: "invalid",
1410
+ redactedArgs: [],
1411
+ status: "invalid",
1412
+ toolArgs: [],
1413
+ validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
1414
+ });
1516
1415
  }
1517
- activeScriptExecutions.delete(scriptExecution);
1518
- finishScriptExecution();
1519
- }
1520
- let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
1521
- if (artifactManifest) {
1522
- scriptResult = {
1523
- ...scriptResult,
1524
- details: {
1525
- ...(isRecord(scriptResult.details) ? scriptResult.details : {}),
1526
- artifactManifest,
1527
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1528
- },
1416
+ const sessionName = createAgentBrowserScriptSessionName();
1417
+ const innerResults = [];
1418
+ const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
1419
+ const deadline = Date.now() + scriptTimeoutMs;
1420
+ let leased = false;
1421
+ let cleanupError;
1422
+ let run = {
1423
+ callCount: 0,
1424
+ emitCount: 0,
1425
+ error: "Script sandbox execution failed.",
1426
+ failureCategory: "upstream-error",
1427
+ ok: false,
1428
+ rejectedCallCount: 0,
1429
+ steps: [],
1529
1430
  };
1530
- }
1531
- return applyUnserializedOutputPath(scriptResult);
1532
- }
1533
- const { toolArgs } = resolvedInput;
1534
- const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
1535
- const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
1536
- const runElectronHostInput = async () => {
1537
- const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
1538
- branchRecords: electronLaunchRecords,
1539
- compiledElectron,
1540
- ownedRecords: ownedElectronLaunchRecords,
1541
- });
1542
- let electronHostResult = await handleElectronHostInput({
1543
- attachedSessionKeys,
1544
- compiledElectron,
1545
- cwd: ctx.cwd,
1546
- electronChildProcesses,
1547
- electronLaunchRecords: electronHostLaunchRecords,
1548
- implicitSessionCloseTimeoutMs,
1549
- managedSessionActive,
1550
- managedSessionName,
1551
- managedSessionNamespace,
1552
- managedSessionRestoreState,
1553
- ownedManagedSessions,
1554
- redactedCompiledElectron,
1555
- sessionPageState,
1556
- signal,
1557
- });
1558
- if (electronHostResult && compiledElectron?.action === "cleanup") {
1559
- branchStateGeneration += 1;
1560
- const cleanupRecords = isRecord(electronHostResult.details)
1561
- && isRecord(electronHostResult.details.electron)
1562
- && isRecord(electronHostResult.details.electron.cleanup)
1563
- && Array.isArray(electronHostResult.details.electron.cleanup.results)
1564
- ? electronHostResult.details.electron.cleanup.results
1565
- : [];
1566
- const cleanedLaunchIds = new Set();
1567
- for (const cleanupResult of cleanupRecords) {
1568
- if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
1569
- cleanedLaunchIds.add(cleanupResult.record.launchId);
1570
- }
1431
+ const scriptController = new AbortController();
1432
+ const abortScript = () => scriptController.abort();
1433
+ signal?.addEventListener("abort", abortScript, { once: true });
1434
+ if (signal?.aborted)
1435
+ scriptController.abort();
1436
+ activeScriptControllers.add(scriptController);
1437
+ let finishScriptExecution;
1438
+ const scriptExecution = new Promise((resolve) => {
1439
+ finishScriptExecution = resolve;
1440
+ });
1441
+ activeScriptExecutions.add(scriptExecution);
1442
+ try {
1443
+ // Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
1444
+ const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, scriptController.signal));
1445
+ if (versionFailure)
1446
+ return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1447
+ const pendingRun = runAgentBrowserScript({
1448
+ beforeFirstCall() {
1449
+ appendScriptSessionLease(pi, sessionName, "active");
1450
+ trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
1451
+ managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
1452
+ // This fresh, unselectable lease owns any daemon its first read starts.
1453
+ managedSessionRestoreState.recordDaemonRestoreKey(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE, null);
1454
+ leased = true;
1455
+ },
1456
+ code: resolvedInput.compiledScript.code,
1457
+ dispatch: async (innerParams, innerSignal) => {
1458
+ const remainingMs = Math.max(1, deadline - Date.now());
1459
+ const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
1460
+ const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
1461
+ args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
1462
+ stdin: innerParams.stdin,
1463
+ timeoutMs: innerTimeoutMs,
1464
+ }, innerSignal, undefined, ctx, nativeToolCallId));
1465
+ innerResults.push(innerResult);
1466
+ return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
1467
+ },
1468
+ signal: scriptController.signal,
1469
+ timeoutMs: scriptTimeoutMs,
1470
+ });
1471
+ run = await pendingRun;
1571
1472
  }
1572
- replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
1573
- mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
1574
- const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
1575
- ? electronHostResult.details.namespace
1576
- : undefined;
1577
- const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
1578
- syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
1579
- for (const identity of closedSessionIdentities) {
1580
- retireRecordingSession(identity.sessionName, identity.namespace);
1581
- const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
1582
- clearSessionScopedBrowserState(closedSessionKey);
1583
- if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
1584
- managedSessionActive = false;
1585
- managedSessionCompatibilityWorkaround = undefined;
1586
- managedSessionHeadedAutosaveDisabled = false;
1587
- managedSessionHeadedAutosaveInterval = undefined;
1588
- managedSessionNamespace = undefined;
1589
- freshSessionOrdinal += 1;
1590
- managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
1473
+ catch { }
1474
+ finally {
1475
+ activeScriptControllers.delete(scriptController);
1476
+ signal?.removeEventListener("abort", abortScript);
1477
+ if (leased) {
1478
+ try {
1479
+ cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
1480
+ flushRecordingReservations();
1481
+ return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
1482
+ }));
1483
+ }
1484
+ catch {
1485
+ cleanupError = "The isolated script session cleanup operation failed.";
1486
+ try {
1487
+ appendScriptSessionLease(pi, sessionName, "failed");
1488
+ }
1489
+ catch { }
1490
+ }
1591
1491
  }
1492
+ activeScriptExecutions.delete(scriptExecution);
1493
+ finishScriptExecution();
1592
1494
  }
1495
+ let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
1593
1496
  if (artifactManifest) {
1594
- electronHostResult = {
1595
- ...electronHostResult,
1497
+ scriptResult = {
1498
+ ...scriptResult,
1596
1499
  details: {
1597
- ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1598
- artifactManifest,
1500
+ ...(isRecord(scriptResult.details) ? scriptResult.details : {}),
1501
+ artifactManifest: redactSensitiveValue(artifactManifest),
1599
1502
  artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1600
1503
  },
1601
1504
  };
1602
1505
  }
1506
+ return applyUnserializedOutputPath(scriptResult);
1603
1507
  }
1604
- return electronHostResult;
1605
- };
1606
- const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
1607
- ? managedSessionExecutionQueue.run(runElectronHostInput)
1608
- : runElectronHostInput();
1609
- const electronHostResult = compiledElectron?.action === "cleanup"
1610
- ? await artifactExecutionQueue.run(async () => {
1611
- flushRecordingReservations();
1612
- const reservationError = outputPath ? getArtifactPreflightValidationError({
1613
- activeRecordingReservations: activeRecordingReservations.values(),
1614
- args: [],
1508
+ const { toolArgs } = resolvedInput;
1509
+ const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
1510
+ const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
1511
+ const runElectronHostInput = async () => {
1512
+ const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
1513
+ branchRecords: electronLaunchRecords,
1514
+ compiledElectron,
1515
+ ownedRecords: ownedElectronLaunchRecords,
1516
+ });
1517
+ let electronHostResult = await handleElectronHostInput({
1518
+ attachedSessionKeys,
1519
+ compiledElectron,
1615
1520
  cwd: ctx.cwd,
1616
- outputPath,
1617
- }) : undefined;
1618
- if (reservationError) {
1619
- return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1521
+ electronChildProcesses,
1522
+ electronLaunchRecords: electronHostLaunchRecords,
1523
+ implicitSessionCloseTimeoutMs,
1524
+ managedSessionActive,
1525
+ managedSessionName,
1526
+ managedSessionNamespace,
1527
+ managedSessionRestoreState,
1528
+ ownedManagedSessions,
1529
+ redactedCompiledElectron,
1530
+ sessionPageState,
1531
+ signal,
1532
+ });
1533
+ if (electronHostResult && compiledElectron?.action === "cleanup") {
1534
+ branchStateGeneration += 1;
1535
+ const cleanupRecords = isRecord(electronHostResult.details)
1536
+ && isRecord(electronHostResult.details.electron)
1537
+ && isRecord(electronHostResult.details.electron.cleanup)
1538
+ && Array.isArray(electronHostResult.details.electron.cleanup.results)
1539
+ ? electronHostResult.details.electron.cleanup.results
1540
+ : [];
1541
+ const cleanedLaunchIds = new Set();
1542
+ for (const cleanupResult of cleanupRecords) {
1543
+ if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
1544
+ cleanedLaunchIds.add(cleanupResult.record.launchId);
1545
+ }
1546
+ }
1547
+ replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
1548
+ mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
1549
+ const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
1550
+ ? electronHostResult.details.namespace
1551
+ : undefined;
1552
+ const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
1553
+ syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
1554
+ for (const identity of closedSessionIdentities) {
1555
+ retireRecordingSession(identity.sessionName, identity.namespace);
1556
+ const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
1557
+ clearSessionScopedBrowserState(closedSessionKey);
1558
+ if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
1559
+ managedSessionActive = false;
1560
+ managedSessionCompatibilityWorkaround = undefined;
1561
+ managedSessionHeadedAutosaveDisabled = false;
1562
+ managedSessionHeadedAutosaveInterval = undefined;
1563
+ managedSessionNamespace = undefined;
1564
+ freshSessionOrdinal += 1;
1565
+ managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
1566
+ }
1567
+ }
1568
+ if (artifactManifest) {
1569
+ electronHostResult = {
1570
+ ...electronHostResult,
1571
+ details: {
1572
+ ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1573
+ artifactManifest: redactSensitiveValue(artifactManifest),
1574
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1575
+ },
1576
+ };
1577
+ }
1620
1578
  }
1621
- const result = await runSerializedElectronHostInput();
1622
- return result ? applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: warnRecordingPersistence(result) }) : result;
1623
- })
1624
- : await runSerializedElectronHostInput();
1625
- if (electronHostResult) {
1626
- return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
1627
- }
1628
- const explicitSessionName = extractExplicitSessionName(toolArgs);
1629
- const callerOwnedSessionNamespace = explicitSessionName
1630
- ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
1631
- : undefined;
1632
- const serializeBrowserCommand = shouldSerializeBrowserCommand({
1633
- namespace: callerOwnedSessionNamespace,
1634
- explicitSessionName,
1635
- managedSessionName,
1636
- ownedElectronLaunchRecords,
1637
- ownedManagedSessions,
1638
- });
1639
- const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
1640
- ? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
1641
- : undefined;
1642
- const runBrowserCommand = async () => {
1643
- flushRecordingReservations();
1644
- const branchRestoreGenerationAtStart = branchRestoreGeneration;
1645
- const generationAtStart = branchStateGeneration;
1646
- const sessionPageStateUpdate = sessionPageState.beginUpdate();
1647
- const browserRunState = {
1648
- artifactManifest,
1649
- attachedSessionKeys,
1650
- closedManagedSessionNames: new Set(),
1651
- electronChildProcesses,
1652
- electronLaunchRecords,
1653
- ephemeralSessionSeed,
1654
- freshSessionOrdinal,
1655
- managedSessionActive,
1656
- managedSessionBaseName,
1657
- managedSessionCompatibilityWorkaround,
1658
- managedSessionHeadedAutosaveDisabled,
1659
- managedSessionHeadedAutosaveInterval,
1660
- managedSessionCwd,
1579
+ return electronHostResult;
1580
+ };
1581
+ const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
1582
+ ? managedSessionExecutionQueue.run(runElectronHostInput)
1583
+ : runElectronHostInput();
1584
+ const electronHostResult = compiledElectron?.action === "cleanup"
1585
+ ? await artifactExecutionQueue.run(async () => {
1586
+ flushRecordingReservations();
1587
+ const reservationError = outputPath ? getArtifactPreflightValidationError({
1588
+ activeRecordingReservations: activeRecordingReservations.values(),
1589
+ args: [],
1590
+ cwd: ctx.cwd,
1591
+ outputPath,
1592
+ }) : undefined;
1593
+ if (reservationError) {
1594
+ return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1595
+ }
1596
+ const result = await runSerializedElectronHostInput();
1597
+ return result ? applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: warnRecordingPersistence(result) }) : result;
1598
+ })
1599
+ : await runSerializedElectronHostInput();
1600
+ if (electronHostResult) {
1601
+ return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
1602
+ }
1603
+ const explicitSessionName = extractExplicitSessionName(toolArgs);
1604
+ const callerOwnedSessionNamespace = explicitSessionName
1605
+ ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
1606
+ : undefined;
1607
+ const serializeBrowserCommand = shouldSerializeBrowserCommand({
1608
+ namespace: callerOwnedSessionNamespace,
1609
+ explicitSessionName,
1661
1610
  managedSessionName,
1662
- managedSessionNamespace,
1663
- managedSessionRestoreState,
1664
- networkRoutesBySession,
1611
+ ownedElectronLaunchRecords,
1665
1612
  ownedManagedSessions,
1666
- sessionPageState,
1667
- traceOwners,
1668
- };
1669
- const selectedPlan = buildExecutionPlan(toolArgs, {
1670
- freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
1671
- managedSessionActive: browserRunState.managedSessionActive,
1672
- managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
1673
- managedSessionName: browserRunState.managedSessionName,
1674
- managedSessionNamespace: browserRunState.managedSessionNamespace,
1675
- sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1676
1613
  });
1677
- const initialArtifactManifest = browserRunState.artifactManifest;
1678
- const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1679
- const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1680
- || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1681
- const allocatesFreshManagedSession = explicitSessionName === undefined
1682
- && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1683
- const reusableSessionKey = allocatesFreshManagedSession
1684
- ? undefined
1685
- : getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
1686
- ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1687
- const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
1688
- let result = await runAgentBrowserTool({
1689
- ctx,
1690
- cwd: ctx.cwd,
1691
- electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
1692
- electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
1693
- implicitSessionCloseTimeoutMs,
1694
- implicitSessionIdleTimeoutMs,
1695
- input: resolvedInput,
1696
- onUpdate,
1697
- params,
1698
- establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1699
- preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
1700
- promptPolicy,
1701
- sessionPageStateUpdate,
1702
- signal,
1703
- state: browserRunState,
1704
- });
1705
- const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1706
- const resultDetails = isRecord(result.details) ? result.details : undefined;
1707
- const resultSessionName = typeof resultDetails?.sessionName === "string"
1708
- ? resultDetails.sessionName
1709
- : extractExplicitSessionName(toolArgs);
1710
- const resultNamespace = typeof resultDetails?.namespace === "string"
1711
- ? resultDetails.namespace
1712
- : selectedPlan.namespace;
1713
- if (branchRestoreStillCurrent) {
1714
- const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
1715
- const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1716
- const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1717
- const closeAllApplied = resultDetails?.closeAllApplied === true;
1718
- const attachedSessionRemainsActive = result.isError !== true
1719
- || ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
1720
- const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
1721
- || resultBatchCloseLifecycle?.endsClosed === true;
1722
- if (closeAllApplied) {
1723
- deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
1724
- if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
1614
+ const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
1615
+ ? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
1616
+ : undefined;
1617
+ const runBrowserCommand = async () => {
1618
+ flushRecordingReservations();
1619
+ const branchRestoreGenerationAtStart = branchRestoreGeneration;
1620
+ const generationAtStart = branchStateGeneration;
1621
+ const sessionPageStateUpdate = sessionPageState.beginUpdate();
1622
+ const browserRunState = {
1623
+ activeRecordingReservations,
1624
+ artifactManifest,
1625
+ attachedSessionKeys,
1626
+ closedManagedSessionNames: new Set(),
1627
+ electronChildProcesses,
1628
+ electronLaunchRecords,
1629
+ ephemeralSessionSeed,
1630
+ freshSessionOrdinal,
1631
+ managedSessionActive,
1632
+ managedSessionBaseName,
1633
+ managedSessionCompatibilityWorkaround,
1634
+ managedSessionHeadedAutosaveDisabled,
1635
+ managedSessionHeadedAutosaveInterval,
1636
+ managedSessionCwd,
1637
+ managedSessionName,
1638
+ managedSessionNamespace,
1639
+ managedSessionRestoreState,
1640
+ networkRoutesBySession,
1641
+ ownedManagedSessions,
1642
+ sessionPageState,
1643
+ traceOwners,
1644
+ };
1645
+ const selectedPlan = buildExecutionPlan(toolArgs, {
1646
+ freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
1647
+ managedSessionActive: browserRunState.managedSessionActive,
1648
+ managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
1649
+ managedSessionName: browserRunState.managedSessionName,
1650
+ managedSessionNamespace: browserRunState.managedSessionNamespace,
1651
+ sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1652
+ stdin: resolvedInput.toolStdin,
1653
+ browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
1654
+ });
1655
+ const initialArtifactManifest = browserRunState.artifactManifest;
1656
+ const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1657
+ const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1658
+ || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1659
+ const allocatesFreshManagedSession = explicitSessionName === undefined
1660
+ && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1661
+ const reusableSessionKey = allocatesFreshManagedSession
1662
+ ? undefined
1663
+ : getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
1664
+ ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1665
+ const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
1666
+ let result = await runAgentBrowserTool({
1667
+ ctx,
1668
+ cwd: ctx.cwd,
1669
+ electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
1670
+ electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
1671
+ implicitSessionCloseTimeoutMs,
1672
+ implicitSessionIdleTimeoutMs,
1673
+ input: resolvedInput,
1674
+ onUpdate,
1675
+ params,
1676
+ establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1677
+ preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
1678
+ promptPolicy,
1679
+ sessionPageStateUpdate,
1680
+ signal,
1681
+ state: browserRunState,
1682
+ });
1683
+ const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1684
+ const resultDetails = isRecord(result.details) ? result.details : undefined;
1685
+ const resultSessionName = typeof resultDetails?.sessionName === "string"
1686
+ ? resultDetails.sessionName
1687
+ : extractExplicitSessionName(toolArgs);
1688
+ const resultNamespace = typeof resultDetails?.namespace === "string"
1689
+ ? resultDetails.namespace
1690
+ : selectedPlan.namespace;
1691
+ if (branchRestoreStillCurrent) {
1692
+ const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
1693
+ const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1694
+ const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1695
+ const closeAllApplied = resultDetails?.closeAllApplied === true;
1696
+ const attachedSessionRemainsActive = result.isError !== true
1697
+ || ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
1698
+ const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
1699
+ || resultBatchCloseLifecycle?.endsClosed === true;
1700
+ if (closeAllApplied) {
1701
+ deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
1702
+ if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
1703
+ attachedSessionKeys.add(resultSessionKey);
1704
+ result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1705
+ }
1706
+ }
1707
+ else if (resultSessionKey && closesAttachedSession)
1708
+ attachedSessionKeys.delete(resultSessionKey);
1709
+ else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1725
1710
  attachedSessionKeys.add(resultSessionKey);
1726
1711
  result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1727
1712
  }
1728
1713
  }
1729
- else if (resultSessionKey && closesAttachedSession)
1730
- attachedSessionKeys.delete(resultSessionKey);
1731
- else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1732
- attachedSessionKeys.add(resultSessionKey);
1733
- result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1734
- }
1735
- }
1736
- if (branchRestoreStillCurrent) {
1737
- networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
1738
- artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
1739
- const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
1740
- if (resultDetails?.closeAllApplied === true) {
1741
- for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
1742
- if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
1743
- retireRecordingSession(reservation.sessionName, reservation.namespace);
1714
+ if (branchRestoreStillCurrent) {
1715
+ networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
1716
+ artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
1717
+ const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
1718
+ if (resultDetails?.closeAllApplied === true) {
1719
+ for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
1720
+ if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
1721
+ retireRecordingSession(reservation.sessionName, reservation.namespace);
1722
+ }
1744
1723
  }
1745
1724
  }
1725
+ for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
1726
+ if (handledBatchCloseKeys.has(closedSessionKey))
1727
+ continue;
1728
+ const reservation = activeRecordingReservations.get(closedSessionKey);
1729
+ if (reservation)
1730
+ retireRecordingSession(reservation.sessionName, reservation.namespace);
1731
+ }
1732
+ if (resultSessionName) {
1733
+ const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
1734
+ if (reservation)
1735
+ result = appendActiveRecordingCleanupAction(result, reservation);
1736
+ }
1737
+ if (artifactManifest) {
1738
+ result = {
1739
+ ...result,
1740
+ details: {
1741
+ ...(isRecord(result.details) ? result.details : {}),
1742
+ artifactManifest: redactSensitiveValue(artifactManifest),
1743
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1744
+ },
1745
+ };
1746
+ }
1746
1747
  }
1747
- for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
1748
- if (handledBatchCloseKeys.has(closedSessionKey))
1749
- continue;
1750
- const reservation = activeRecordingReservations.get(closedSessionKey);
1751
- if (reservation)
1752
- retireRecordingSession(reservation.sessionName, reservation.namespace);
1753
- }
1754
- if (resultSessionName && resultReportsNoRecordingInProgress(result)) {
1755
- retireRecordingSession(resultSessionName, resultNamespace);
1756
- }
1757
- if (resultSessionName) {
1758
- const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
1759
- if (reservation)
1760
- result = appendActiveRecordingCleanupAction(result, reservation);
1761
- }
1762
- if (artifactManifest) {
1763
- result = {
1764
- ...result,
1765
- details: {
1766
- ...(isRecord(result.details) ? result.details : {}),
1767
- artifactManifest,
1768
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1769
- },
1770
- };
1771
- }
1772
- }
1773
- const branchStateStillCurrent = generationAtStart === branchStateGeneration;
1774
- if (serializeBrowserCommand || branchStateStillCurrent) {
1775
- freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1776
- managedSessionActive = browserRunState.managedSessionActive;
1777
- managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1778
- managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1779
- managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1780
- managedSessionCwd = browserRunState.managedSessionCwd;
1781
- managedSessionName = browserRunState.managedSessionName;
1782
- managedSessionNamespace = browserRunState.managedSessionNamespace;
1783
- for (const closedSessionName of browserRunState.closedManagedSessionNames) {
1784
- untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
1748
+ const branchStateStillCurrent = generationAtStart === branchStateGeneration;
1749
+ if (serializeBrowserCommand || branchStateStillCurrent) {
1750
+ freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1751
+ managedSessionActive = browserRunState.managedSessionActive;
1752
+ managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1753
+ managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1754
+ managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1755
+ managedSessionCwd = browserRunState.managedSessionCwd;
1756
+ managedSessionName = browserRunState.managedSessionName;
1757
+ managedSessionNamespace = browserRunState.managedSessionNamespace;
1758
+ for (const closedSessionName of browserRunState.closedManagedSessionNames) {
1759
+ untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
1760
+ }
1761
+ syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
1762
+ mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
1763
+ branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
1764
+ touchedLaunchIds: !result.isError
1765
+ ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
1766
+ : undefined,
1767
+ });
1768
+ if (serializeBrowserCommand)
1769
+ branchStateGeneration += 1;
1785
1770
  }
1786
- syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
1787
- mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
1788
- branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
1789
- touchedLaunchIds: !result.isError
1790
- ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
1791
- : undefined,
1792
- });
1793
- if (serializeBrowserCommand)
1794
- branchStateGeneration += 1;
1795
- }
1796
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
1797
- };
1798
- const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
1799
- const runWithinSessionQueue = () => {
1800
- if (closesAllSessions)
1801
- return managedSessionExecutionQueue.run(() => {
1802
- const plan = buildExecutionPlan(toolArgs, {
1803
- freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1804
- managedSessionActive,
1805
- managedSessionCompatibilityWorkaround,
1806
- managedSessionName,
1807
- managedSessionNamespace,
1808
- sessionMode: params.sessionMode ?? "auto",
1771
+ return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
1772
+ };
1773
+ const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
1774
+ const runWithinSessionQueue = () => {
1775
+ if (closesAllSessions)
1776
+ return managedSessionExecutionQueue.run(() => {
1777
+ const plan = buildExecutionPlan(toolArgs, {
1778
+ freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1779
+ managedSessionActive,
1780
+ managedSessionCompatibilityWorkaround,
1781
+ managedSessionName,
1782
+ managedSessionNamespace,
1783
+ sessionMode: params.sessionMode ?? "auto",
1784
+ stdin: resolvedInput.toolStdin,
1785
+ });
1786
+ return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runBrowserCommand);
1809
1787
  });
1810
- return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runBrowserCommand);
1811
- });
1812
- if (serializeBrowserCommand)
1813
- return managedSessionExecutionQueue.run(runBrowserCommand);
1814
- return callerOwnedSessionQueueKey
1815
- ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runBrowserCommand)
1816
- : runBrowserCommand();
1817
- };
1818
- if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
1819
- return runWithinSessionQueue();
1820
- return artifactExecutionQueue.run(async () => {
1821
- const artifactValidationError = getArtifactPreflightValidationError({
1822
- activeRecordingReservations: activeRecordingReservations.values(),
1823
- args: toolArgs,
1824
- cwd: ctx.cwd,
1825
- outputPath,
1826
- stdin: resolvedInput.toolStdin,
1827
- });
1828
- if (!artifactValidationError)
1788
+ if (serializeBrowserCommand)
1789
+ return managedSessionExecutionQueue.run(runBrowserCommand);
1790
+ return callerOwnedSessionQueueKey
1791
+ ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runBrowserCommand)
1792
+ : runBrowserCommand();
1793
+ };
1794
+ if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
1829
1795
  return runWithinSessionQueue();
1830
- flushRecordingReservations();
1831
- return applyAgentBrowserOutputPath({
1832
- cwd: ctx.cwd,
1833
- outputPath,
1834
- result: warnRecordingPersistence(buildValidationFailureResult({
1796
+ return artifactExecutionQueue.run(async () => {
1797
+ const artifactValidationError = getArtifactPreflightValidationError({
1798
+ activeRecordingReservations: activeRecordingReservations.values(),
1799
+ args: toolArgs,
1800
+ cwd: ctx.cwd,
1801
+ outputPath,
1802
+ stdin: resolvedInput.toolStdin,
1803
+ });
1804
+ if (!artifactValidationError)
1805
+ return runWithinSessionQueue();
1806
+ flushRecordingReservations();
1807
+ return warnRecordingPersistence(buildValidationFailureResult({
1835
1808
  attemptedKind: resolvedInput.kind,
1836
1809
  kind: "invalid",
1837
1810
  redactedArgs: resolvedInput.redactedArgs,
@@ -1839,11 +1812,11 @@ export default function agentBrowserExtension(pi) {
1839
1812
  toolArgs: resolvedInput.toolArgs,
1840
1813
  toolStdin: resolvedInput.toolStdin,
1841
1814
  validationError: artifactValidationError,
1842
- })),
1815
+ }));
1843
1816
  });
1844
1817
  });
1845
1818
  },
1846
1819
  };
1847
- pi.registerTool(agentBrowserTool);
1820
+ pi.registerTool(beforeExecute ? { ...agentBrowserTool, executionMode: "sequential" } : agentBrowserTool);
1848
1821
  registerWebSearchToolIfAvailable(agentBrowserConfig);
1849
1822
  }