pi-agent-browser-native 0.6.10 → 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 (45) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +49 -6
  3. package/dist/extensions/agent-browser/index.js +416 -430
  4. package/dist/extensions/agent-browser/lib/argv-grammar.js +1 -1
  5. package/dist/extensions/agent-browser/lib/command-policy.js +41 -2
  6. package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
  7. package/dist/extensions/agent-browser/lib/input-modes/script.js +3 -2
  8. package/dist/extensions/agent-browser/lib/managed-session-restore.js +40 -10
  9. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +3 -0
  10. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +8 -2
  11. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +1 -0
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +3 -2
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +25 -14
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +56 -10
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +161 -0
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +3 -3
  17. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +2 -4
  18. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +68 -0
  19. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -6
  20. package/dist/extensions/agent-browser/lib/page-target-validation.js +9 -5
  21. package/dist/extensions/agent-browser/lib/playbook.js +10 -9
  22. package/dist/extensions/agent-browser/lib/process-environment.js +26 -8
  23. package/dist/extensions/agent-browser/lib/process.js +8 -5
  24. package/dist/extensions/agent-browser/lib/read-confirmation.js +59 -0
  25. package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -1
  26. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +8 -0
  27. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -5
  28. package/dist/extensions/agent-browser/lib/results/categories.js +4 -2
  29. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +60 -29
  30. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
  31. package/dist/extensions/agent-browser/lib/results/presentation/common.js +0 -20
  32. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +40 -38
  33. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +1 -0
  34. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +3 -3
  35. package/dist/extensions/agent-browser/lib/results/presentation.js +38 -9
  36. package/dist/extensions/agent-browser/lib/results/recording.js +50 -0
  37. package/dist/extensions/agent-browser/lib/runtime.js +72 -20
  38. package/dist/extensions/agent-browser/lib/session-page-state.js +23 -7
  39. package/dist/extensions/agent-browser/lib/temp.js +4 -0
  40. package/docs/ARCHITECTURE.md +26 -10
  41. package/docs/COMMAND_REFERENCE.md +35 -15
  42. package/docs/SUPPORT_MATRIX.md +7 -3
  43. package/docs/TOOL_CONTRACT.md +72 -20
  44. package/package.json +1 -1
  45. package/scripts/prepare.mjs +2 -4
@@ -5,7 +5,7 @@ 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";
8
+ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
9
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";
@@ -13,6 +13,7 @@ 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) {
@@ -157,26 +159,6 @@ function getResultFileArtifacts(result) {
157
159
  const details = isRecord(result.details) ? result.details : undefined;
158
160
  return Array.isArray(details?.artifacts) ? details.artifacts.filter(isResultFileArtifact) : [];
159
161
  }
160
- function reportsNoRecordingInProgress(value) {
161
- try {
162
- return /no recording in progress/i.test(JSON.stringify(value));
163
- }
164
- catch {
165
- return false;
166
- }
167
- }
168
- function batchStepReportsNoRecordingInProgress(step) {
169
- if (!isRecord(step) || step.success !== false)
170
- return false;
171
- const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? extractUpstreamCommandTokens(step.command) : [];
172
- return command[0] === "record" && command[1] === "stop" && reportsNoRecordingInProgress(step);
173
- }
174
- function resultReportsNoRecordingInProgress(result) {
175
- if (result.isError !== true)
176
- return false;
177
- const details = isRecord(result.details) ? result.details : undefined;
178
- return details?.command === "record" && details.subcommand === "stop" && reportsNoRecordingInProgress(result.content);
179
- }
180
162
  function restoreArtifactManifestFromBranch(branch) {
181
163
  let restoredManifest;
182
164
  for (const entry of branch) {
@@ -801,7 +783,7 @@ function shouldIncludeProjectConfig(ctx, argv = process.argv) {
801
783
  return false;
802
784
  return ctx?.isProjectTrusted?.() ?? true;
803
785
  }
804
- export default function agentBrowserExtension(pi) {
786
+ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
805
787
  const ephemeralSessionSeed = createEphemeralSessionSeed();
806
788
  const agentBrowserConfig = loadAgentBrowserConfigSync({
807
789
  cwd: process.cwd(),
@@ -913,6 +895,8 @@ export default function agentBrowserExtension(pi) {
913
895
  if (result.isError !== true)
914
896
  return result;
915
897
  const details = isRecord(result.details) ? result.details : {};
898
+ if (details.recordingRecovery)
899
+ return result;
916
900
  const cleanupOnly = details.managedSessionCleanupOnlyReason === "restore-disabled-daemon-without-provenance";
917
901
  const nextActions = (Array.isArray(details.nextActions) ? details.nextActions : [])
918
902
  .filter((action) => !cleanupOnly || action.id !== "stop-pending-recording");
@@ -973,12 +957,6 @@ export default function agentBrowserExtension(pi) {
973
957
  const command = Array.isArray(step.command) && step.command.every((token) => typeof token === "string") ? step.command : undefined;
974
958
  const commandTokens = command ? extractUpstreamCommandTokens(command) : [];
975
959
  const commandName = commandTokens[0];
976
- if (resultSessionName && batchStepReportsNoRecordingInProgress(step)) {
977
- const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
978
- retireRecordingSession(resultSessionName, resultNamespace, false);
979
- handledClosedSessionKeys.add(sessionKey);
980
- continue;
981
- }
982
960
  if (step.success === true && commandName && isCloseCommand(commandName) && resultSessionName) {
983
961
  const sessionKey = getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace);
984
962
  retireRecordingSession(resultSessionName, resultNamespace, false);
@@ -1148,7 +1126,7 @@ export default function agentBrowserExtension(pi) {
1148
1126
  }
1149
1127
  for (const [key, reservation] of activeRecordingReservations) {
1150
1128
  const restored = restoredRecordingState.active.get(key);
1151
- 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)
1152
1130
  recordingReservationsDirty = true;
1153
1131
  restoredRecordingState.active.set(key, reservation);
1154
1132
  }
@@ -1380,445 +1358,453 @@ export default function agentBrowserExtension(pi) {
1380
1358
  component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
1381
1359
  return component;
1382
1360
  },
1383
- async execute(toolCallId, params, signal, onUpdate, ctx) {
1361
+ async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId) {
1384
1362
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
1385
1363
  const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
1386
1364
  const resolvedInput = resolveAgentBrowserInput({
1387
1365
  getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: ctx.cwd, outputPath, stdin }),
1388
- managedSessionActive,
1389
1366
  params,
1390
1367
  });
1391
1368
  if (resolvedInput.status === "invalid") {
1392
1369
  return buildValidationFailureResult(resolvedInput);
1393
1370
  }
1394
- const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
1395
- if (!outputPath || result.isError === true || (isRecord(result.details) && result.details.resultCategory === "failure"))
1396
- return warnRecordingPersistence(result);
1397
- return artifactExecutionQueue.run(async () => {
1398
- flushRecordingReservations();
1399
- const reservationError = getArtifactPreflightValidationError({
1400
- activeRecordingReservations: activeRecordingReservations.values(),
1401
- args: [],
1402
- cwd: ctx.cwd,
1403
- outputPath,
1404
- });
1405
- if (reservationError) {
1406
- return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1407
- }
1408
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
1409
- });
1410
- };
1411
- const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
1412
- const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
1413
- const browserBackedVersionCheck = needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs));
1414
- if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
1415
- const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
1416
- if (versionFailure)
1417
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1418
- }
1419
- if (resolvedInput.kind === "script") {
1420
- if (!ctx.sessionManager.getSessionFile()) {
1421
- return buildValidationFailureResult({
1422
- attemptedKind: "script",
1423
- kind: "invalid",
1424
- redactedArgs: [],
1425
- status: "invalid",
1426
- toolArgs: [],
1427
- validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
1428
- });
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." });
1429
1379
  }
1430
- const sessionName = createAgentBrowserScriptSessionName();
1431
- const innerResults = [];
1432
- const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
1433
- const deadline = Date.now() + scriptTimeoutMs;
1434
- let leased = false;
1435
- let cleanupError;
1436
- let run = {
1437
- callCount: 0,
1438
- emitCount: 0,
1439
- error: "Script sandbox execution failed.",
1440
- failureCategory: "upstream-error",
1441
- ok: false,
1442
- rejectedCallCount: 0,
1443
- 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
+ });
1444
1396
  };
1445
- const scriptController = new AbortController();
1446
- const abortScript = () => scriptController.abort();
1447
- signal?.addEventListener("abort", abortScript, { once: true });
1448
- if (signal?.aborted)
1449
- scriptController.abort();
1450
- activeScriptControllers.add(scriptController);
1451
- let finishScriptExecution;
1452
- const scriptExecution = new Promise((resolve) => {
1453
- finishScriptExecution = resolve;
1454
- });
1455
- activeScriptExecutions.add(scriptExecution);
1456
- try {
1457
- // Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
1458
- 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);
1459
1402
  if (versionFailure)
1460
1403
  return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1461
- const pendingRun = runAgentBrowserScript({
1462
- beforeFirstCall() {
1463
- appendScriptSessionLease(pi, sessionName, "active");
1464
- trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
1465
- managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
1466
- leased = true;
1467
- },
1468
- code: resolvedInput.compiledScript.code,
1469
- dispatch: async (innerParams, innerSignal) => {
1470
- const remainingMs = Math.max(1, deadline - Date.now());
1471
- const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
1472
- const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
1473
- args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
1474
- stdin: innerParams.stdin,
1475
- timeoutMs: innerTimeoutMs,
1476
- }, innerSignal, undefined, ctx));
1477
- innerResults.push(innerResult);
1478
- return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
1479
- },
1480
- signal: scriptController.signal,
1481
- timeoutMs: scriptTimeoutMs,
1482
- });
1483
- run = await pendingRun;
1484
1404
  }
1485
- catch { }
1486
- finally {
1487
- activeScriptControllers.delete(scriptController);
1488
- signal?.removeEventListener("abort", abortScript);
1489
- if (leased) {
1490
- try {
1491
- cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
1492
- flushRecordingReservations();
1493
- return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
1494
- }));
1495
- }
1496
- catch {
1497
- cleanupError = "The isolated script session cleanup operation failed.";
1498
- try {
1499
- appendScriptSessionLease(pi, sessionName, "failed");
1500
- }
1501
- catch { }
1502
- }
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
+ });
1503
1415
  }
1504
- activeScriptExecutions.delete(scriptExecution);
1505
- finishScriptExecution();
1506
- }
1507
- let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
1508
- if (artifactManifest) {
1509
- scriptResult = {
1510
- ...scriptResult,
1511
- details: {
1512
- ...(isRecord(scriptResult.details) ? scriptResult.details : {}),
1513
- artifactManifest,
1514
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1515
- },
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: [],
1516
1430
  };
1517
- }
1518
- return applyUnserializedOutputPath(scriptResult);
1519
- }
1520
- const { toolArgs } = resolvedInput;
1521
- const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
1522
- const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
1523
- const runElectronHostInput = async () => {
1524
- const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
1525
- branchRecords: electronLaunchRecords,
1526
- compiledElectron,
1527
- ownedRecords: ownedElectronLaunchRecords,
1528
- });
1529
- let electronHostResult = await handleElectronHostInput({
1530
- attachedSessionKeys,
1531
- compiledElectron,
1532
- cwd: ctx.cwd,
1533
- electronChildProcesses,
1534
- electronLaunchRecords: electronHostLaunchRecords,
1535
- implicitSessionCloseTimeoutMs,
1536
- managedSessionActive,
1537
- managedSessionName,
1538
- managedSessionNamespace,
1539
- managedSessionRestoreState,
1540
- ownedManagedSessions,
1541
- redactedCompiledElectron,
1542
- sessionPageState,
1543
- signal,
1544
- });
1545
- if (electronHostResult && compiledElectron?.action === "cleanup") {
1546
- branchStateGeneration += 1;
1547
- const cleanupRecords = isRecord(electronHostResult.details)
1548
- && isRecord(electronHostResult.details.electron)
1549
- && isRecord(electronHostResult.details.electron.cleanup)
1550
- && Array.isArray(electronHostResult.details.electron.cleanup.results)
1551
- ? electronHostResult.details.electron.cleanup.results
1552
- : [];
1553
- const cleanedLaunchIds = new Set();
1554
- for (const cleanupResult of cleanupRecords) {
1555
- if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
1556
- cleanedLaunchIds.add(cleanupResult.record.launchId);
1557
- }
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;
1558
1472
  }
1559
- replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
1560
- mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
1561
- const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
1562
- ? electronHostResult.details.namespace
1563
- : undefined;
1564
- const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
1565
- syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
1566
- for (const identity of closedSessionIdentities) {
1567
- retireRecordingSession(identity.sessionName, identity.namespace);
1568
- const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
1569
- clearSessionScopedBrowserState(closedSessionKey);
1570
- if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
1571
- managedSessionActive = false;
1572
- managedSessionCompatibilityWorkaround = undefined;
1573
- managedSessionHeadedAutosaveDisabled = false;
1574
- managedSessionHeadedAutosaveInterval = undefined;
1575
- managedSessionNamespace = undefined;
1576
- freshSessionOrdinal += 1;
1577
- 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
+ }
1578
1491
  }
1492
+ activeScriptExecutions.delete(scriptExecution);
1493
+ finishScriptExecution();
1579
1494
  }
1495
+ let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
1580
1496
  if (artifactManifest) {
1581
- electronHostResult = {
1582
- ...electronHostResult,
1497
+ scriptResult = {
1498
+ ...scriptResult,
1583
1499
  details: {
1584
- ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1585
- artifactManifest,
1500
+ ...(isRecord(scriptResult.details) ? scriptResult.details : {}),
1501
+ artifactManifest: redactSensitiveValue(artifactManifest),
1586
1502
  artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1587
1503
  },
1588
1504
  };
1589
1505
  }
1506
+ return applyUnserializedOutputPath(scriptResult);
1590
1507
  }
1591
- return electronHostResult;
1592
- };
1593
- const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
1594
- ? managedSessionExecutionQueue.run(runElectronHostInput)
1595
- : runElectronHostInput();
1596
- const electronHostResult = compiledElectron?.action === "cleanup"
1597
- ? await artifactExecutionQueue.run(async () => {
1598
- flushRecordingReservations();
1599
- const reservationError = outputPath ? getArtifactPreflightValidationError({
1600
- activeRecordingReservations: activeRecordingReservations.values(),
1601
- 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,
1602
1520
  cwd: ctx.cwd,
1603
- outputPath,
1604
- }) : undefined;
1605
- if (reservationError) {
1606
- 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
+ }
1607
1578
  }
1608
- const result = await runSerializedElectronHostInput();
1609
- return result ? applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: warnRecordingPersistence(result) }) : result;
1610
- })
1611
- : await runSerializedElectronHostInput();
1612
- if (electronHostResult) {
1613
- return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
1614
- }
1615
- const explicitSessionName = extractExplicitSessionName(toolArgs);
1616
- const callerOwnedSessionNamespace = explicitSessionName
1617
- ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
1618
- : undefined;
1619
- const serializeBrowserCommand = shouldSerializeBrowserCommand({
1620
- namespace: callerOwnedSessionNamespace,
1621
- explicitSessionName,
1622
- managedSessionName,
1623
- ownedElectronLaunchRecords,
1624
- ownedManagedSessions,
1625
- });
1626
- const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
1627
- ? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
1628
- : undefined;
1629
- const runBrowserCommand = async () => {
1630
- flushRecordingReservations();
1631
- const branchRestoreGenerationAtStart = branchRestoreGeneration;
1632
- const generationAtStart = branchStateGeneration;
1633
- const sessionPageStateUpdate = sessionPageState.beginUpdate();
1634
- const browserRunState = {
1635
- artifactManifest,
1636
- attachedSessionKeys,
1637
- closedManagedSessionNames: new Set(),
1638
- electronChildProcesses,
1639
- electronLaunchRecords,
1640
- ephemeralSessionSeed,
1641
- freshSessionOrdinal,
1642
- managedSessionActive,
1643
- managedSessionBaseName,
1644
- managedSessionCompatibilityWorkaround,
1645
- managedSessionHeadedAutosaveDisabled,
1646
- managedSessionHeadedAutosaveInterval,
1647
- 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,
1648
1610
  managedSessionName,
1649
- managedSessionNamespace,
1650
- managedSessionRestoreState,
1651
- networkRoutesBySession,
1611
+ ownedElectronLaunchRecords,
1652
1612
  ownedManagedSessions,
1653
- sessionPageState,
1654
- traceOwners,
1655
- };
1656
- const selectedPlan = buildExecutionPlan(toolArgs, {
1657
- freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
1658
- managedSessionActive: browserRunState.managedSessionActive,
1659
- managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
1660
- managedSessionName: browserRunState.managedSessionName,
1661
- managedSessionNamespace: browserRunState.managedSessionNamespace,
1662
- sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1663
- });
1664
- const initialArtifactManifest = browserRunState.artifactManifest;
1665
- const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1666
- const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1667
- || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1668
- const allocatesFreshManagedSession = explicitSessionName === undefined
1669
- && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1670
- const reusableSessionKey = allocatesFreshManagedSession
1671
- ? undefined
1672
- : getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
1673
- ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1674
- const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
1675
- let result = await runAgentBrowserTool({
1676
- ctx,
1677
- cwd: ctx.cwd,
1678
- electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
1679
- electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
1680
- implicitSessionCloseTimeoutMs,
1681
- implicitSessionIdleTimeoutMs,
1682
- input: resolvedInput,
1683
- onUpdate,
1684
- params,
1685
- establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1686
- preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
1687
- promptPolicy,
1688
- sessionPageStateUpdate,
1689
- signal,
1690
- state: browserRunState,
1691
1613
  });
1692
- const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1693
- const resultDetails = isRecord(result.details) ? result.details : undefined;
1694
- const resultSessionName = typeof resultDetails?.sessionName === "string"
1695
- ? resultDetails.sessionName
1696
- : extractExplicitSessionName(toolArgs);
1697
- const resultNamespace = typeof resultDetails?.namespace === "string"
1698
- ? resultDetails.namespace
1699
- : selectedPlan.namespace;
1700
- if (branchRestoreStillCurrent) {
1701
- const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
1702
- const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1703
- const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1704
- const closeAllApplied = resultDetails?.closeAllApplied === true;
1705
- const attachedSessionRemainsActive = result.isError !== true
1706
- || ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
1707
- const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
1708
- || resultBatchCloseLifecycle?.endsClosed === true;
1709
- if (closeAllApplied) {
1710
- deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
1711
- 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)) {
1712
1710
  attachedSessionKeys.add(resultSessionKey);
1713
1711
  result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1714
1712
  }
1715
1713
  }
1716
- else if (resultSessionKey && closesAttachedSession)
1717
- attachedSessionKeys.delete(resultSessionKey);
1718
- else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1719
- attachedSessionKeys.add(resultSessionKey);
1720
- result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1721
- }
1722
- }
1723
- if (branchRestoreStillCurrent) {
1724
- networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
1725
- artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
1726
- const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
1727
- if (resultDetails?.closeAllApplied === true) {
1728
- for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
1729
- if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
1730
- 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
+ }
1731
1723
  }
1732
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
+ }
1733
1747
  }
1734
- for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
1735
- if (handledBatchCloseKeys.has(closedSessionKey))
1736
- continue;
1737
- const reservation = activeRecordingReservations.get(closedSessionKey);
1738
- if (reservation)
1739
- retireRecordingSession(reservation.sessionName, reservation.namespace);
1740
- }
1741
- if (resultSessionName && resultReportsNoRecordingInProgress(result)) {
1742
- retireRecordingSession(resultSessionName, resultNamespace);
1743
- }
1744
- if (resultSessionName) {
1745
- const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
1746
- if (reservation)
1747
- result = appendActiveRecordingCleanupAction(result, reservation);
1748
- }
1749
- if (artifactManifest) {
1750
- result = {
1751
- ...result,
1752
- details: {
1753
- ...(isRecord(result.details) ? result.details : {}),
1754
- artifactManifest,
1755
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1756
- },
1757
- };
1758
- }
1759
- }
1760
- const branchStateStillCurrent = generationAtStart === branchStateGeneration;
1761
- if (serializeBrowserCommand || branchStateStillCurrent) {
1762
- freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1763
- managedSessionActive = browserRunState.managedSessionActive;
1764
- managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1765
- managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1766
- managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1767
- managedSessionCwd = browserRunState.managedSessionCwd;
1768
- managedSessionName = browserRunState.managedSessionName;
1769
- managedSessionNamespace = browserRunState.managedSessionNamespace;
1770
- for (const closedSessionName of browserRunState.closedManagedSessionNames) {
1771
- 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;
1772
1770
  }
1773
- syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
1774
- mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
1775
- branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
1776
- touchedLaunchIds: !result.isError
1777
- ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
1778
- : undefined,
1779
- });
1780
- if (serializeBrowserCommand)
1781
- branchStateGeneration += 1;
1782
- }
1783
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
1784
- };
1785
- const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
1786
- const runWithinSessionQueue = () => {
1787
- if (closesAllSessions)
1788
- return managedSessionExecutionQueue.run(() => {
1789
- const plan = buildExecutionPlan(toolArgs, {
1790
- freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1791
- managedSessionActive,
1792
- managedSessionCompatibilityWorkaround,
1793
- managedSessionName,
1794
- managedSessionNamespace,
1795
- 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);
1796
1787
  });
1797
- return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runBrowserCommand);
1798
- });
1799
- if (serializeBrowserCommand)
1800
- return managedSessionExecutionQueue.run(runBrowserCommand);
1801
- return callerOwnedSessionQueueKey
1802
- ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runBrowserCommand)
1803
- : runBrowserCommand();
1804
- };
1805
- if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
1806
- return runWithinSessionQueue();
1807
- return artifactExecutionQueue.run(async () => {
1808
- const artifactValidationError = getArtifactPreflightValidationError({
1809
- activeRecordingReservations: activeRecordingReservations.values(),
1810
- args: toolArgs,
1811
- cwd: ctx.cwd,
1812
- outputPath,
1813
- stdin: resolvedInput.toolStdin,
1814
- });
1815
- 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))
1816
1795
  return runWithinSessionQueue();
1817
- flushRecordingReservations();
1818
- return applyAgentBrowserOutputPath({
1819
- cwd: ctx.cwd,
1820
- outputPath,
1821
- 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({
1822
1808
  attemptedKind: resolvedInput.kind,
1823
1809
  kind: "invalid",
1824
1810
  redactedArgs: resolvedInput.redactedArgs,
@@ -1826,11 +1812,11 @@ export default function agentBrowserExtension(pi) {
1826
1812
  toolArgs: resolvedInput.toolArgs,
1827
1813
  toolStdin: resolvedInput.toolStdin,
1828
1814
  validationError: artifactValidationError,
1829
- })),
1815
+ }));
1830
1816
  });
1831
1817
  });
1832
1818
  },
1833
1819
  };
1834
- pi.registerTool(agentBrowserTool);
1820
+ pi.registerTool(beforeExecute ? { ...agentBrowserTool, executionMode: "sequential" } : agentBrowserTool);
1835
1821
  registerWebSearchToolIfAvailable(agentBrowserConfig);
1836
1822
  }