pi-agent-browser-native 0.6.16 → 0.6.17

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.
@@ -6,7 +6,7 @@ import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./
6
6
  import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
7
7
  import { SessionPageState } from "./lib/session-page-state.js";
8
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";
9
+ import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } 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, resolveOwnedManagedSessionContext, withOwnedManagedSessionContext } from "./lib/managed-session-restore.js";
@@ -14,6 +14,8 @@ 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
16
  import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
17
+ import { getBrowserCwdError, resolveExecutionCwd, restoreManagedSessionCwd } from "./lib/execution-cwd.js";
18
+ import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
17
19
  import { closeManagedSession, inspectManagedSessionDaemon } from "./lib/orchestration/browser-run/managed-session-daemon-policy.js";
18
20
  import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
19
21
  import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
@@ -1108,7 +1110,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1108
1110
  managedSessionHeadedAutosaveInterval = managedSessionActive
1109
1111
  ? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
1110
1112
  : undefined;
1111
- managedSessionCwd = ctx.cwd;
1113
+ managedSessionCwd = restoreManagedSessionCwd(branch, managedSessionName, managedSessionNamespace, ctx.cwd);
1112
1114
  freshSessionOrdinal = nextFreshSessionOrdinal;
1113
1115
  sessionPageState = SessionPageState.fromBranch(branch);
1114
1116
  traceOwners = new Map();
@@ -1159,7 +1161,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1159
1161
  continue;
1160
1162
  if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
1161
1163
  continue;
1162
- trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, ctx.cwd, {
1164
+ trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, restoreManagedSessionCwd(branch, identity.sessionName, identity.namespace, ctx.cwd), {
1163
1165
  branchOwned: true,
1164
1166
  compatibilityWorkaround: restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, identity.sessionName, identity.namespace),
1165
1167
  headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
@@ -1168,7 +1170,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1168
1170
  });
1169
1171
  }
1170
1172
  if (restoredState.active) {
1171
- trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, {
1173
+ trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, managedSessionCwd, {
1172
1174
  branchOwned: true,
1173
1175
  compatibilityWorkaround: managedSessionCompatibilityWorkaround,
1174
1176
  headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
@@ -1184,7 +1186,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1184
1186
  const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
1185
1187
  if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
1186
1188
  continue;
1187
- trackOwnedManagedSession(ownedManagedSessions, record.sessionName, ctx.cwd, {
1189
+ trackOwnedManagedSession(ownedManagedSessions, record.sessionName, restoreManagedSessionCwd(branch, record.sessionName, undefined, ctx.cwd), {
1188
1190
  branchOwned: true,
1189
1191
  headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
1190
1192
  headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
@@ -1243,7 +1245,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1243
1245
  notifyRecordingPersistence(ctx);
1244
1246
  }));
1245
1247
  });
1246
- // Additive native checkpoint event; the package's 0.84 validation types predate it.
1248
+ // Fork-only checkpoint event; official Pi validation types do not declare it.
1247
1249
  // Pi owns awaited tools/events (including their execution queues). Only script
1248
1250
  // children and retained browser/recording resources need extension-side checks.
1249
1251
  const checkpointPi = pi;
@@ -1433,477 +1435,514 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1433
1435
  component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
1434
1436
  return component;
1435
1437
  },
1436
- async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId) {
1438
+ async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd) {
1439
+ let operationCwd;
1440
+ try {
1441
+ operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
1442
+ }
1443
+ catch (error) {
1444
+ return buildValidationFailureResult({ kind: "invalid", status: "invalid", redactedArgs: [], toolArgs: [], validationError: error instanceof Error ? error.message : String(error) });
1445
+ }
1437
1446
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
1438
1447
  const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
1439
- const resolvedInput = resolveAgentBrowserInput({
1440
- getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: ctx.cwd, outputPath, stdin }),
1448
+ const admittedInput = resolveAgentBrowserInput({
1449
+ getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: operationCwd, outputPath, stdin }),
1441
1450
  params,
1442
1451
  });
1443
- if (resolvedInput.status === "invalid") {
1444
- return buildValidationFailureResult(resolvedInput);
1452
+ if (admittedInput.status === "invalid") {
1453
+ return buildValidationFailureResult(admittedInput);
1445
1454
  }
1446
- if (resolvedInput.kind !== "script")
1447
- await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
1448
- const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
1449
- const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
1450
- const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1451
- const rootSessionId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
1452
- ? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
1453
- return withNativeSessionDefaults(resolvedInput, ctx.cwd, signal, async (resolvedInput, withLaunchDefaults) => {
1454
- const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1455
- if (readConfirmation)
1456
- resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
1457
- if (resolvedInput.kind === "qa" && resolvedInput.compiledQaPreset.checks.attached && !managedSessionActive && !extractExplicitSessionName(resolvedInput.toolArgs)) {
1458
- 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." });
1455
+ const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
1456
+ // Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
1457
+ const managedAtAdmission = admittedInput.kind !== "script" && admittedInput.kind !== "electron"
1458
+ && !extractExplicitSessionName(admittedInput.toolArgs) && !explicitConfig;
1459
+ const artifactsAtAdmission = managedAtAdmission && commandTouchesArtifactLifecycle(admittedInput.toolArgs, admittedInput.toolStdin, outputPath);
1460
+ const withManagedQueue = (run) => managedAtAdmission ? run() : managedSessionExecutionQueue.run(run);
1461
+ const withArtifactQueue = (run) => artifactsAtAdmission ? run() : artifactExecutionQueue.run(run);
1462
+ const executeAdmitted = async () => {
1463
+ let resolvedInput = admittedInput;
1464
+ const requestedSession = extractExplicitSessionName(resolvedInput.toolArgs);
1465
+ const requestedNamespace = resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
1466
+ const requestedOwner = requestedSession ? ownedManagedSessions.get(getSessionContextKey(requestedSession, requestedNamespace) ?? requestedSession) : undefined;
1467
+ const browserCwd = (params.sessionMode === "fresh" && !requestedSession) || explicitConfig ? operationCwd
1468
+ : requestedSession ? requestedOwner?.cwd ?? ctx.cwd : managedSessionActive ? managedSessionCwd : ctx.cwd;
1469
+ const browserCwdError = resolvedInput.kind === "electron" ? undefined : getBrowserCwdError(browserCwd);
1470
+ if (browserCwdError)
1471
+ return buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: browserCwdError });
1472
+ if (!isPlainTextInspectionArgs(resolvedInput.toolArgs) && (operationCwd !== browserCwd || operationCwd !== ctx.cwd)) {
1473
+ const bound = resolveOperationPaths(resolvedInput.toolArgs, resolvedInput.toolStdin, operationCwd);
1474
+ resolvedInput = { ...resolvedInput, toolArgs: bound.args, toolStdin: bound.stdin };
1459
1475
  }
1460
- const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
1461
- if (!outputPath || !canWriteAgentBrowserOutput(result))
1462
- return warnRecordingPersistence(result);
1463
- return artifactExecutionQueue.run(async () => {
1464
- flushRecordingReservations();
1465
- const reservationError = getArtifactPreflightValidationError({
1466
- activeRecordingReservations: activeRecordingReservations.values(),
1467
- args: [],
1468
- cwd: ctx.cwd,
1469
- outputPath,
1470
- });
1471
- if (reservationError) {
1472
- return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1473
- }
1474
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
1475
- });
1476
- };
1477
- const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
1478
- const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
1479
- const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
1480
- if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
1481
- const versionFailure = await validateUpstreamVersion(ctx.cwd, signal);
1482
- if (versionFailure)
1483
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1484
- }
1485
- if (resolvedInput.kind === "script") {
1486
- if (!ctx.sessionManager.getSessionFile()) {
1487
- return buildValidationFailureResult({
1488
- attemptedKind: "script",
1489
- kind: "invalid",
1490
- redactedArgs: [],
1491
- status: "invalid",
1492
- toolArgs: [],
1493
- validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
1494
- });
1476
+ if (resolvedInput.kind !== "script")
1477
+ await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
1478
+ const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
1479
+ const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
1480
+ const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1481
+ const rootSessionId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
1482
+ ? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
1483
+ return withNativeSessionDefaults(resolvedInput, browserCwd, signal, async (resolvedInput, withLaunchDefaults) => {
1484
+ const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1485
+ if (readConfirmation)
1486
+ resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
1487
+ if (resolvedInput.kind === "qa" && resolvedInput.compiledQaPreset.checks.attached && !managedSessionActive && !extractExplicitSessionName(resolvedInput.toolArgs)) {
1488
+ 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." });
1495
1489
  }
1496
- const sessionName = createAgentBrowserScriptSessionName();
1497
- const innerResults = [];
1498
- const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
1499
- const deadline = Date.now() + scriptTimeoutMs;
1500
- let leased = false;
1501
- let cleanupError;
1502
- let run = {
1503
- callCount: 0,
1504
- emitCount: 0,
1505
- error: "Script sandbox execution failed.",
1506
- failureCategory: "upstream-error",
1507
- ok: false,
1508
- rejectedCallCount: 0,
1509
- steps: [],
1490
+ const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
1491
+ if (!outputPath || !canWriteAgentBrowserOutput(result))
1492
+ return warnRecordingPersistence(result);
1493
+ return withArtifactQueue(async () => {
1494
+ flushRecordingReservations();
1495
+ const reservationError = getArtifactPreflightValidationError({
1496
+ activeRecordingReservations: activeRecordingReservations.values(),
1497
+ args: [],
1498
+ cwd: operationCwd,
1499
+ outputPath,
1500
+ });
1501
+ if (reservationError) {
1502
+ return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1503
+ }
1504
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
1505
+ });
1510
1506
  };
1511
- const scriptController = new AbortController();
1512
- const abortScript = () => scriptController.abort();
1513
- signal?.addEventListener("abort", abortScript, { once: true });
1514
- if (signal?.aborted)
1515
- scriptController.abort();
1516
- activeScriptControllers.add(scriptController);
1517
- let finishScriptExecution;
1518
- const scriptExecution = new Promise((resolve) => {
1519
- finishScriptExecution = resolve;
1520
- });
1521
- activeScriptExecutions.add(scriptExecution);
1522
- try {
1523
- // Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
1524
- const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(ctx.cwd, scriptController.signal));
1507
+ const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
1508
+ const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
1509
+ const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
1510
+ if (resolvedInput.kind !== "script" && !electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
1511
+ const versionFailure = await validateUpstreamVersion(browserCwd, signal);
1525
1512
  if (versionFailure)
1526
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: versionFailure });
1527
- const pendingRun = runAgentBrowserScript({
1528
- beforeFirstCall() {
1529
- appendScriptSessionLease(pi, sessionName, "active");
1530
- trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
1531
- managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
1532
- // This fresh, unselectable lease owns any daemon its first read starts.
1533
- managedSessionRestoreState.recordDaemonRestoreKey(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE, null);
1534
- leased = true;
1535
- },
1536
- code: resolvedInput.compiledScript.code,
1537
- dispatch: async (innerParams, innerSignal) => {
1538
- const remainingMs = Math.max(1, deadline - Date.now());
1539
- const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
1540
- const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
1541
- args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
1542
- stdin: innerParams.stdin,
1543
- timeoutMs: innerTimeoutMs,
1544
- }, innerSignal, undefined, ctx, nativeToolCallId));
1545
- innerResults.push(innerResult);
1546
- return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
1547
- },
1548
- signal: scriptController.signal,
1549
- timeoutMs: scriptTimeoutMs,
1550
- });
1551
- run = await pendingRun;
1513
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: versionFailure });
1552
1514
  }
1553
- catch { }
1554
- finally {
1555
- activeScriptControllers.delete(scriptController);
1556
- signal?.removeEventListener("abort", abortScript);
1557
- if (leased) {
1558
- try {
1559
- cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
1560
- flushRecordingReservations();
1561
- return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
1562
- }));
1563
- }
1564
- catch {
1565
- cleanupError = "The isolated script session cleanup operation failed.";
1566
- try {
1567
- appendScriptSessionLease(pi, sessionName, "failed");
1568
- }
1569
- catch { }
1570
- }
1515
+ if (resolvedInput.kind === "script") {
1516
+ if (!ctx.sessionManager.getSessionFile()) {
1517
+ return buildValidationFailureResult({
1518
+ attemptedKind: "script",
1519
+ kind: "invalid",
1520
+ redactedArgs: [],
1521
+ status: "invalid",
1522
+ toolArgs: [],
1523
+ validationError: "script requires a persisted Pi session so its isolated browser-session cleanup lease survives restart; relaunch Pi without --no-session.",
1524
+ });
1571
1525
  }
1572
- activeScriptExecutions.delete(scriptExecution);
1573
- finishScriptExecution();
1574
- }
1575
- let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
1576
- if (artifactManifest) {
1577
- scriptResult = {
1578
- ...scriptResult,
1579
- details: {
1580
- ...(isRecord(scriptResult.details) ? scriptResult.details : {}),
1581
- artifactManifest: redactSensitiveValue(artifactManifest),
1582
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1583
- },
1526
+ const sessionName = createAgentBrowserScriptSessionName();
1527
+ const innerResults = [];
1528
+ const scriptTimeoutMs = params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS;
1529
+ const deadline = Date.now() + scriptTimeoutMs;
1530
+ let leased = false;
1531
+ let cleanupError;
1532
+ let run = {
1533
+ callCount: 0,
1534
+ emitCount: 0,
1535
+ error: "Script sandbox execution failed.",
1536
+ failureCategory: "upstream-error",
1537
+ ok: false,
1538
+ rejectedCallCount: 0,
1539
+ steps: [],
1584
1540
  };
1585
- }
1586
- return applyUnserializedOutputPath(scriptResult);
1587
- }
1588
- const { toolArgs } = resolvedInput;
1589
- const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
1590
- const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
1591
- const runElectronHostInput = async () => {
1592
- const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
1593
- branchRecords: electronLaunchRecords,
1594
- compiledElectron,
1595
- ownedRecords: ownedElectronLaunchRecords,
1596
- });
1597
- let electronHostResult = await handleElectronHostInput({
1598
- attachedSessionKeys,
1599
- compiledElectron,
1600
- cwd: ctx.cwd,
1601
- electronChildProcesses,
1602
- electronLaunchRecords: electronHostLaunchRecords,
1603
- implicitSessionCloseTimeoutMs,
1604
- managedSessionActive,
1605
- managedSessionName,
1606
- managedSessionNamespace,
1607
- managedSessionRestoreState,
1608
- ownedManagedSessions,
1609
- redactedCompiledElectron,
1610
- sessionPageState,
1611
- signal,
1612
- });
1613
- if (electronHostResult && compiledElectron?.action === "cleanup") {
1614
- branchStateGeneration += 1;
1615
- const cleanupRecords = isRecord(electronHostResult.details)
1616
- && isRecord(electronHostResult.details.electron)
1617
- && isRecord(electronHostResult.details.electron.cleanup)
1618
- && Array.isArray(electronHostResult.details.electron.cleanup.results)
1619
- ? electronHostResult.details.electron.cleanup.results
1620
- : [];
1621
- const cleanedLaunchIds = new Set();
1622
- for (const cleanupResult of cleanupRecords) {
1623
- if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
1624
- cleanedLaunchIds.add(cleanupResult.record.launchId);
1625
- }
1541
+ const scriptController = new AbortController();
1542
+ const abortScript = () => scriptController.abort();
1543
+ signal?.addEventListener("abort", abortScript, { once: true });
1544
+ if (signal?.aborted)
1545
+ scriptController.abort();
1546
+ activeScriptControllers.add(scriptController);
1547
+ let finishScriptExecution;
1548
+ const scriptExecution = new Promise((resolve) => {
1549
+ finishScriptExecution = resolve;
1550
+ });
1551
+ activeScriptExecutions.add(scriptExecution);
1552
+ try {
1553
+ // Keep preflight inside shutdown tracking so quit cannot race into starting the sandbox afterward.
1554
+ const versionFailure = await withIsolatedAgentBrowserEnvironment(() => validateUpstreamVersion(browserCwd, scriptController.signal));
1555
+ if (versionFailure)
1556
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: versionFailure });
1557
+ const pendingRun = runAgentBrowserScript({
1558
+ beforeFirstCall() {
1559
+ appendScriptSessionLease(pi, sessionName, "active");
1560
+ trackOwnedManagedSession(ownedManagedSessions, sessionName, ctx.cwd, { namespace: AGENT_BROWSER_SCRIPT_NAMESPACE });
1561
+ managedSessionRestoreState.disable(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE);
1562
+ // This fresh, unselectable lease owns any daemon its first read starts.
1563
+ managedSessionRestoreState.recordDaemonRestoreKey(sessionName, AGENT_BROWSER_SCRIPT_NAMESPACE, null);
1564
+ leased = true;
1565
+ },
1566
+ code: resolvedInput.compiledScript.code,
1567
+ dispatch: async (innerParams, innerSignal) => {
1568
+ const remainingMs = Math.max(1, deadline - Date.now());
1569
+ const innerTimeoutMs = Math.min(innerParams.timeoutMs ?? remainingMs, remainingMs);
1570
+ const innerResult = await withIsolatedAgentBrowserEnvironment(() => agentBrowserTool.execute(`${toolCallId}:script:${innerResults.length + 1}`, {
1571
+ args: ["--namespace", AGENT_BROWSER_SCRIPT_NAMESPACE, "--session", sessionName, ...innerParams.args],
1572
+ stdin: innerParams.stdin,
1573
+ timeoutMs: innerTimeoutMs,
1574
+ }, innerSignal, undefined, ctx, nativeToolCallId, operationCwd));
1575
+ innerResults.push(innerResult);
1576
+ return await buildScriptBrowserEnvelope(innerResult, innerParams.args, sessionName);
1577
+ },
1578
+ signal: scriptController.signal,
1579
+ timeoutMs: scriptTimeoutMs,
1580
+ });
1581
+ run = await pendingRun;
1626
1582
  }
1627
- replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
1628
- mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
1629
- const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
1630
- ? electronHostResult.details.namespace
1631
- : undefined;
1632
- const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
1633
- syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
1634
- for (const identity of closedSessionIdentities) {
1635
- retireRecordingSession(identity.sessionName, identity.namespace);
1636
- const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
1637
- clearSessionScopedBrowserState(closedSessionKey);
1638
- if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
1639
- managedSessionActive = false;
1640
- managedSessionCompatibilityWorkaround = undefined;
1641
- managedSessionHeadedAutosaveDisabled = false;
1642
- managedSessionHeadedAutosaveInterval = undefined;
1643
- managedSessionNamespace = undefined;
1644
- freshSessionOrdinal += 1;
1645
- managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
1583
+ catch { }
1584
+ finally {
1585
+ activeScriptControllers.delete(scriptController);
1586
+ signal?.removeEventListener("abort", abortScript);
1587
+ if (leased) {
1588
+ try {
1589
+ cleanupError = await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(() => {
1590
+ flushRecordingReservations();
1591
+ return closeScriptSessionLeaseWithinQueue(sessionName, ctx.cwd);
1592
+ }));
1593
+ }
1594
+ catch {
1595
+ cleanupError = "The isolated script session cleanup operation failed.";
1596
+ try {
1597
+ appendScriptSessionLease(pi, sessionName, "failed");
1598
+ }
1599
+ catch { }
1600
+ }
1646
1601
  }
1602
+ activeScriptExecutions.delete(scriptExecution);
1603
+ finishScriptExecution();
1647
1604
  }
1605
+ let scriptResult = buildScriptToolResult({ cleanupError, innerResults, run, sessionName: leased ? sessionName : undefined });
1648
1606
  if (artifactManifest) {
1649
- electronHostResult = {
1650
- ...electronHostResult,
1607
+ scriptResult = {
1608
+ ...scriptResult,
1651
1609
  details: {
1652
- ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1610
+ ...(isRecord(scriptResult.details) ? scriptResult.details : {}),
1653
1611
  artifactManifest: redactSensitiveValue(artifactManifest),
1654
1612
  artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1655
1613
  },
1656
1614
  };
1657
1615
  }
1616
+ return applyUnserializedOutputPath(scriptResult);
1658
1617
  }
1659
- return electronHostResult;
1660
- };
1661
- const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
1662
- ? managedSessionExecutionQueue.run(runElectronHostInput)
1663
- : runElectronHostInput();
1664
- const electronHostResult = compiledElectron?.action === "cleanup"
1665
- ? await artifactExecutionQueue.run(async () => {
1666
- flushRecordingReservations();
1667
- const reservationError = outputPath ? getArtifactPreflightValidationError({
1668
- activeRecordingReservations: activeRecordingReservations.values(),
1669
- args: [],
1618
+ const { toolArgs } = resolvedInput;
1619
+ const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
1620
+ const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
1621
+ const runElectronHostInput = async () => {
1622
+ const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
1623
+ branchRecords: electronLaunchRecords,
1624
+ compiledElectron,
1625
+ ownedRecords: ownedElectronLaunchRecords,
1626
+ });
1627
+ let electronHostResult = await handleElectronHostInput({
1628
+ attachedSessionKeys,
1629
+ compiledElectron,
1670
1630
  cwd: ctx.cwd,
1671
- outputPath,
1672
- }) : undefined;
1673
- if (reservationError) {
1674
- return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1631
+ electronChildProcesses,
1632
+ electronLaunchRecords: electronHostLaunchRecords,
1633
+ implicitSessionCloseTimeoutMs,
1634
+ managedSessionActive,
1635
+ managedSessionName,
1636
+ managedSessionNamespace,
1637
+ managedSessionRestoreState,
1638
+ ownedManagedSessions,
1639
+ redactedCompiledElectron,
1640
+ sessionPageState,
1641
+ signal,
1642
+ });
1643
+ if (electronHostResult && compiledElectron?.action === "cleanup") {
1644
+ branchStateGeneration += 1;
1645
+ const cleanupRecords = isRecord(electronHostResult.details)
1646
+ && isRecord(electronHostResult.details.electron)
1647
+ && isRecord(electronHostResult.details.electron.cleanup)
1648
+ && Array.isArray(electronHostResult.details.electron.cleanup.results)
1649
+ ? electronHostResult.details.electron.cleanup.results
1650
+ : [];
1651
+ const cleanedLaunchIds = new Set();
1652
+ for (const cleanupResult of cleanupRecords) {
1653
+ if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
1654
+ cleanedLaunchIds.add(cleanupResult.record.launchId);
1655
+ }
1656
+ }
1657
+ replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
1658
+ mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
1659
+ const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
1660
+ ? electronHostResult.details.namespace
1661
+ : undefined;
1662
+ const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
1663
+ syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
1664
+ for (const identity of closedSessionIdentities) {
1665
+ retireRecordingSession(identity.sessionName, identity.namespace);
1666
+ const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
1667
+ clearSessionScopedBrowserState(closedSessionKey);
1668
+ if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
1669
+ managedSessionActive = false;
1670
+ managedSessionCompatibilityWorkaround = undefined;
1671
+ managedSessionHeadedAutosaveDisabled = false;
1672
+ managedSessionHeadedAutosaveInterval = undefined;
1673
+ managedSessionNamespace = undefined;
1674
+ freshSessionOrdinal += 1;
1675
+ managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
1676
+ }
1677
+ }
1678
+ if (artifactManifest) {
1679
+ electronHostResult = {
1680
+ ...electronHostResult,
1681
+ details: {
1682
+ ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1683
+ artifactManifest: redactSensitiveValue(artifactManifest),
1684
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1685
+ },
1686
+ };
1687
+ }
1675
1688
  }
1676
- const result = await runSerializedElectronHostInput();
1677
- return result ? applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: warnRecordingPersistence(result) }) : result;
1678
- })
1679
- : await runSerializedElectronHostInput();
1680
- if (electronHostResult) {
1681
- return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
1682
- }
1683
- const explicitSessionName = extractExplicitSessionName(toolArgs);
1684
- const callerOwnedSessionNamespace = explicitSessionName
1685
- ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
1686
- : undefined;
1687
- const serializeBrowserCommand = shouldSerializeBrowserCommand({
1688
- namespace: callerOwnedSessionNamespace,
1689
- explicitSessionName,
1690
- managedSessionName,
1691
- ownedElectronLaunchRecords,
1692
- ownedManagedSessions,
1693
- });
1694
- const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
1695
- ? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
1696
- : undefined;
1697
- const runBrowserCommand = async (daemonInactive) => {
1698
- // Load execution-only preparation/output code inside the existing session queue,
1699
- // before capturing state. Registration, rendering, and restore stay synchronous/eager.
1700
- const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
1701
- flushRecordingReservations();
1702
- const branchRestoreGenerationAtStart = branchRestoreGeneration;
1703
- const generationAtStart = branchStateGeneration;
1704
- const sessionPageStateUpdate = sessionPageState.beginUpdate();
1705
- const browserRunState = {
1706
- activeRecordingReservations,
1707
- artifactManifest,
1708
- attachedSessionKeys,
1709
- closedManagedSessionNames: new Set(),
1710
- electronChildProcesses,
1711
- electronLaunchRecords,
1712
- ephemeralSessionSeed,
1713
- freshSessionOrdinal,
1714
- managedSessionActive,
1715
- managedSessionBaseName,
1716
- managedSessionCompatibilityWorkaround,
1717
- managedSessionHeadedAutosaveDisabled,
1718
- managedSessionHeadedAutosaveInterval,
1719
- managedSessionCwd,
1689
+ return electronHostResult;
1690
+ };
1691
+ const runSerializedElectronHostInput = () => shouldSerializeElectronHostInput(compiledElectron)
1692
+ ? managedSessionExecutionQueue.run(runElectronHostInput)
1693
+ : runElectronHostInput();
1694
+ const electronHostResult = compiledElectron?.action === "cleanup"
1695
+ ? await artifactExecutionQueue.run(async () => {
1696
+ flushRecordingReservations();
1697
+ const reservationError = outputPath ? getArtifactPreflightValidationError({
1698
+ activeRecordingReservations: activeRecordingReservations.values(),
1699
+ args: [],
1700
+ cwd: operationCwd,
1701
+ outputPath,
1702
+ }) : undefined;
1703
+ if (reservationError) {
1704
+ return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1705
+ }
1706
+ const result = await runSerializedElectronHostInput();
1707
+ return result ? applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: warnRecordingPersistence(result) }) : result;
1708
+ })
1709
+ : await runSerializedElectronHostInput();
1710
+ if (electronHostResult) {
1711
+ return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
1712
+ }
1713
+ const explicitSessionName = extractExplicitSessionName(toolArgs);
1714
+ const callerOwnedSessionNamespace = explicitSessionName
1715
+ ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
1716
+ : undefined;
1717
+ const serializeBrowserCommand = shouldSerializeBrowserCommand({
1718
+ namespace: callerOwnedSessionNamespace,
1719
+ explicitSessionName,
1720
1720
  managedSessionName,
1721
- managedSessionNamespace,
1722
- managedSessionRestoreState,
1723
- networkRoutesBySession,
1721
+ ownedElectronLaunchRecords,
1724
1722
  ownedManagedSessions,
1725
- sessionPageState,
1726
- traceOwners,
1727
- };
1728
- const selectedPlan = buildExecutionPlan(toolArgs, {
1729
- freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
1730
- managedSessionActive: browserRunState.managedSessionActive,
1731
- managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
1732
- managedSessionName: browserRunState.managedSessionName,
1733
- managedSessionNamespace: browserRunState.managedSessionNamespace,
1734
- sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1735
- stdin: resolvedInput.toolStdin,
1736
- browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
1737
- });
1738
- const initialArtifactManifest = browserRunState.artifactManifest;
1739
- const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1740
- const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1741
- || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1742
- const allocatesFreshManagedSession = explicitSessionName === undefined
1743
- && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1744
- const reusableSessionKey = allocatesFreshManagedSession
1745
- ? undefined
1746
- : getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
1747
- ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1748
- const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
1749
- let result = await runAgentBrowserTool({
1750
- daemonInactive,
1751
- ctx,
1752
- cwd: ctx.cwd,
1753
- electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
1754
- electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
1755
- implicitSessionCloseTimeoutMs,
1756
- implicitSessionIdleTimeoutMs,
1757
- input: resolvedInput,
1758
- onUpdate,
1759
- params,
1760
- establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1761
- preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
1762
- promptPolicy,
1763
- sessionPageStateUpdate,
1764
- signal,
1765
- state: browserRunState,
1766
1723
  });
1767
- const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1768
- const resultDetails = isRecord(result.details) ? result.details : undefined;
1769
- const resultSessionName = typeof resultDetails?.sessionName === "string"
1770
- ? resultDetails.sessionName
1771
- : extractExplicitSessionName(toolArgs);
1772
- const resultNamespace = typeof resultDetails?.namespace === "string"
1773
- ? resultDetails.namespace
1774
- : selectedPlan.namespace;
1775
- if (branchRestoreStillCurrent) {
1776
- const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
1777
- const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1778
- const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1779
- const closeAllApplied = resultDetails?.closeAllApplied === true;
1780
- const attachedSessionRemainsActive = result.isError !== true
1781
- || ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
1782
- const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
1783
- || resultBatchCloseLifecycle?.endsClosed === true;
1784
- if (closeAllApplied) {
1785
- deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
1786
- if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
1724
+ const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
1725
+ ? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
1726
+ : undefined;
1727
+ const runBrowserCommand = async (daemonInactive) => {
1728
+ // Load execution-only preparation/output code inside the existing session queue,
1729
+ // before capturing state. Registration, rendering, and restore stay synchronous/eager.
1730
+ const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
1731
+ flushRecordingReservations();
1732
+ const branchRestoreGenerationAtStart = branchRestoreGeneration;
1733
+ const generationAtStart = branchStateGeneration;
1734
+ const sessionPageStateUpdate = sessionPageState.beginUpdate();
1735
+ const browserRunState = {
1736
+ activeRecordingReservations,
1737
+ artifactManifest,
1738
+ attachedSessionKeys,
1739
+ closedManagedSessionNames: new Set(),
1740
+ electronChildProcesses,
1741
+ electronLaunchRecords,
1742
+ ephemeralSessionSeed,
1743
+ freshSessionOrdinal,
1744
+ managedSessionActive,
1745
+ managedSessionBaseName,
1746
+ managedSessionCompatibilityWorkaround,
1747
+ managedSessionHeadedAutosaveDisabled,
1748
+ managedSessionHeadedAutosaveInterval,
1749
+ managedSessionCwd,
1750
+ managedSessionName,
1751
+ managedSessionNamespace,
1752
+ managedSessionRestoreState,
1753
+ networkRoutesBySession,
1754
+ ownedManagedSessions,
1755
+ sessionPageState,
1756
+ traceOwners,
1757
+ };
1758
+ const selectedPlan = buildExecutionPlan(toolArgs, {
1759
+ freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
1760
+ managedSessionActive: browserRunState.managedSessionActive,
1761
+ managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
1762
+ managedSessionName: browserRunState.managedSessionName,
1763
+ managedSessionNamespace: browserRunState.managedSessionNamespace,
1764
+ sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1765
+ stdin: resolvedInput.toolStdin,
1766
+ browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
1767
+ });
1768
+ const initialArtifactManifest = browserRunState.artifactManifest;
1769
+ const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1770
+ const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1771
+ || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1772
+ const allocatesFreshManagedSession = explicitSessionName === undefined
1773
+ && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1774
+ const reusableSessionKey = allocatesFreshManagedSession
1775
+ ? undefined
1776
+ : getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
1777
+ ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1778
+ const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
1779
+ let result = await runAgentBrowserTool({
1780
+ daemonInactive,
1781
+ ctx,
1782
+ cwd: browserCwd,
1783
+ operationCwd,
1784
+ electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
1785
+ electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
1786
+ implicitSessionCloseTimeoutMs,
1787
+ implicitSessionIdleTimeoutMs,
1788
+ input: resolvedInput,
1789
+ onUpdate,
1790
+ params,
1791
+ establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1792
+ preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
1793
+ promptPolicy,
1794
+ sessionPageStateUpdate,
1795
+ signal,
1796
+ state: browserRunState,
1797
+ });
1798
+ const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1799
+ const resultDetails = isRecord(result.details) ? result.details : undefined;
1800
+ if (resultDetails?.sessionName === browserRunState.managedSessionName && resultDetails.namespace === browserRunState.managedSessionNamespace && browserRunState.managedSessionActive) {
1801
+ resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
1802
+ }
1803
+ const resultSessionName = typeof resultDetails?.sessionName === "string"
1804
+ ? resultDetails.sessionName
1805
+ : extractExplicitSessionName(toolArgs);
1806
+ const resultNamespace = typeof resultDetails?.namespace === "string"
1807
+ ? resultDetails.namespace
1808
+ : selectedPlan.namespace;
1809
+ if (branchRestoreStillCurrent) {
1810
+ const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
1811
+ const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1812
+ const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1813
+ const closeAllApplied = resultDetails?.closeAllApplied === true;
1814
+ const attachedSessionRemainsActive = result.isError !== true
1815
+ || ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
1816
+ const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
1817
+ || resultBatchCloseLifecycle?.endsClosed === true;
1818
+ if (closeAllApplied) {
1819
+ deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
1820
+ if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
1821
+ attachedSessionKeys.add(resultSessionKey);
1822
+ result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1823
+ }
1824
+ }
1825
+ else if (resultSessionKey && closesAttachedSession)
1826
+ attachedSessionKeys.delete(resultSessionKey);
1827
+ else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1787
1828
  attachedSessionKeys.add(resultSessionKey);
1788
1829
  result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1789
1830
  }
1790
1831
  }
1791
- else if (resultSessionKey && closesAttachedSession)
1792
- attachedSessionKeys.delete(resultSessionKey);
1793
- else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1794
- attachedSessionKeys.add(resultSessionKey);
1795
- result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1796
- }
1797
- }
1798
- if (branchRestoreStillCurrent) {
1799
- networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
1800
- artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
1801
- const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
1802
- if (resultDetails?.closeAllApplied === true) {
1803
- for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
1804
- if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
1805
- retireRecordingSession(reservation.sessionName, reservation.namespace);
1832
+ if (branchRestoreStillCurrent) {
1833
+ networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
1834
+ artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
1835
+ const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
1836
+ if (resultDetails?.closeAllApplied === true) {
1837
+ for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
1838
+ if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
1839
+ retireRecordingSession(reservation.sessionName, reservation.namespace);
1840
+ }
1806
1841
  }
1807
1842
  }
1843
+ for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
1844
+ if (handledBatchCloseKeys.has(closedSessionKey))
1845
+ continue;
1846
+ const reservation = activeRecordingReservations.get(closedSessionKey);
1847
+ if (reservation)
1848
+ retireRecordingSession(reservation.sessionName, reservation.namespace);
1849
+ }
1850
+ if (resultSessionName) {
1851
+ const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
1852
+ if (reservation)
1853
+ result = appendActiveRecordingCleanupAction(result, reservation);
1854
+ }
1855
+ if (artifactManifest) {
1856
+ result = {
1857
+ ...result,
1858
+ details: {
1859
+ ...(isRecord(result.details) ? result.details : {}),
1860
+ artifactManifest: redactSensitiveValue(artifactManifest),
1861
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1862
+ },
1863
+ };
1864
+ }
1808
1865
  }
1809
- for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
1810
- if (handledBatchCloseKeys.has(closedSessionKey))
1811
- continue;
1812
- const reservation = activeRecordingReservations.get(closedSessionKey);
1813
- if (reservation)
1814
- retireRecordingSession(reservation.sessionName, reservation.namespace);
1815
- }
1816
- if (resultSessionName) {
1817
- const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
1818
- if (reservation)
1819
- result = appendActiveRecordingCleanupAction(result, reservation);
1820
- }
1821
- if (artifactManifest) {
1822
- result = {
1823
- ...result,
1824
- details: {
1825
- ...(isRecord(result.details) ? result.details : {}),
1826
- artifactManifest: redactSensitiveValue(artifactManifest),
1827
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1828
- },
1829
- };
1830
- }
1831
- }
1832
- const branchStateStillCurrent = generationAtStart === branchStateGeneration;
1833
- if (serializeBrowserCommand || branchStateStillCurrent) {
1834
- freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1835
- managedSessionActive = browserRunState.managedSessionActive;
1836
- managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1837
- managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1838
- managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1839
- managedSessionCwd = browserRunState.managedSessionCwd;
1840
- managedSessionName = browserRunState.managedSessionName;
1841
- managedSessionNamespace = browserRunState.managedSessionNamespace;
1842
- for (const closedSessionName of browserRunState.closedManagedSessionNames) {
1843
- untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
1866
+ const branchStateStillCurrent = generationAtStart === branchStateGeneration;
1867
+ if (serializeBrowserCommand || branchStateStillCurrent) {
1868
+ freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1869
+ managedSessionActive = browserRunState.managedSessionActive;
1870
+ managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1871
+ managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1872
+ managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1873
+ managedSessionCwd = browserRunState.managedSessionCwd;
1874
+ managedSessionName = browserRunState.managedSessionName;
1875
+ managedSessionNamespace = browserRunState.managedSessionNamespace;
1876
+ for (const closedSessionName of browserRunState.closedManagedSessionNames) {
1877
+ untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
1878
+ }
1879
+ syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
1880
+ mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
1881
+ branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
1882
+ touchedLaunchIds: !result.isError
1883
+ ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
1884
+ : undefined,
1885
+ });
1886
+ if (serializeBrowserCommand)
1887
+ branchStateGeneration += 1;
1844
1888
  }
1845
- syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
1846
- mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
1847
- branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
1848
- touchedLaunchIds: !result.isError
1849
- ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
1850
- : undefined,
1851
- });
1852
- if (serializeBrowserCommand)
1853
- branchStateGeneration += 1;
1854
- }
1855
- return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
1856
- };
1857
- const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
1858
- const runWithLaunchDefaults = () => withLaunchDefaults ? withLaunchDefaults(runBrowserCommand) : runBrowserCommand();
1859
- const runWithinSessionQueue = () => {
1860
- if (closesAllSessions)
1861
- return managedSessionExecutionQueue.run(() => {
1862
- const plan = buildExecutionPlan(toolArgs, {
1863
- freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1864
- managedSessionActive,
1865
- managedSessionCompatibilityWorkaround,
1866
- managedSessionName,
1867
- managedSessionNamespace,
1868
- sessionMode: params.sessionMode ?? "auto",
1869
- stdin: resolvedInput.toolStdin,
1889
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
1890
+ };
1891
+ const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
1892
+ const runWithLaunchDefaults = () => withLaunchDefaults ? withLaunchDefaults(runBrowserCommand) : runBrowserCommand();
1893
+ const runWithinSessionQueue = () => {
1894
+ if (closesAllSessions)
1895
+ return withManagedQueue(() => {
1896
+ const plan = buildExecutionPlan(toolArgs, {
1897
+ freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1898
+ managedSessionActive,
1899
+ managedSessionCompatibilityWorkaround,
1900
+ managedSessionName,
1901
+ managedSessionNamespace,
1902
+ sessionMode: params.sessionMode ?? "auto",
1903
+ stdin: resolvedInput.toolStdin,
1904
+ });
1905
+ return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
1870
1906
  });
1871
- return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
1907
+ if (serializeBrowserCommand)
1908
+ return withManagedQueue(runWithLaunchDefaults);
1909
+ return callerOwnedSessionQueueKey
1910
+ ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults)
1911
+ : runWithLaunchDefaults();
1912
+ };
1913
+ if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
1914
+ return runWithinSessionQueue();
1915
+ return withArtifactQueue(async () => {
1916
+ const artifactValidationError = getArtifactPreflightValidationError({
1917
+ activeRecordingReservations: activeRecordingReservations.values(),
1918
+ args: toolArgs,
1919
+ cwd: operationCwd,
1920
+ outputPath,
1921
+ stdin: resolvedInput.toolStdin,
1872
1922
  });
1873
- if (serializeBrowserCommand)
1874
- return managedSessionExecutionQueue.run(runWithLaunchDefaults);
1875
- return callerOwnedSessionQueueKey
1876
- ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults)
1877
- : runWithLaunchDefaults();
1878
- };
1879
- if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
1880
- return runWithinSessionQueue();
1881
- return artifactExecutionQueue.run(async () => {
1882
- const artifactValidationError = getArtifactPreflightValidationError({
1883
- activeRecordingReservations: activeRecordingReservations.values(),
1884
- args: toolArgs,
1885
- cwd: ctx.cwd,
1886
- outputPath,
1887
- stdin: resolvedInput.toolStdin,
1923
+ if (!artifactValidationError)
1924
+ return runWithinSessionQueue();
1925
+ flushRecordingReservations();
1926
+ return warnRecordingPersistence(buildValidationFailureResult({
1927
+ attemptedKind: resolvedInput.kind,
1928
+ kind: "invalid",
1929
+ redactedArgs: resolvedInput.redactedArgs,
1930
+ status: "invalid",
1931
+ toolArgs: resolvedInput.toolArgs,
1932
+ toolStdin: resolvedInput.toolStdin,
1933
+ validationError: artifactValidationError,
1934
+ }));
1888
1935
  });
1889
- if (!artifactValidationError)
1890
- return runWithinSessionQueue();
1891
- flushRecordingReservations();
1892
- return warnRecordingPersistence(buildValidationFailureResult({
1893
- attemptedKind: resolvedInput.kind,
1894
- kind: "invalid",
1895
- redactedArgs: resolvedInput.redactedArgs,
1896
- status: "invalid",
1897
- toolArgs: resolvedInput.toolArgs,
1898
- toolStdin: resolvedInput.toolStdin,
1899
- validationError: artifactValidationError,
1900
- }));
1936
+ }, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
1937
+ id: rootSessionId,
1938
+ profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
1939
+ executablePath: runtimeBrowserConfig.trustedBrowserExecutablePath,
1901
1940
  });
1902
- }, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
1903
- id: rootSessionId,
1904
- profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
1905
- executablePath: runtimeBrowserConfig.trustedBrowserExecutablePath,
1906
- });
1941
+ };
1942
+ if (!managedAtAdmission)
1943
+ return executeAdmitted();
1944
+ const executeManaged = () => managedSessionExecutionQueue.run(executeAdmitted);
1945
+ return artifactsAtAdmission ? artifactExecutionQueue.run(executeManaged) : executeManaged();
1907
1946
  },
1908
1947
  };
1909
1948
  pi.registerTool(beforeExecute ? { ...agentBrowserTool, executionMode: "sequential" } : agentBrowserTool);