pi-agent-browser-native 0.6.16 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +18 -1
  2. package/README.md +59 -165
  3. package/dist/extensions/agent-browser/index.js +634 -501
  4. package/dist/extensions/agent-browser/lib/browser-transcript.js +33 -0
  5. package/dist/extensions/agent-browser/lib/command-taxonomy.js +8 -4
  6. package/dist/extensions/agent-browser/lib/execution-cwd.js +66 -0
  7. package/dist/extensions/agent-browser/lib/input-modes/job.js +2 -213
  8. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +1 -1
  9. package/dist/extensions/agent-browser/lib/input-modes/params.js +146 -142
  10. package/dist/extensions/agent-browser/lib/input-modes/script.js +64 -92
  11. package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +170 -24
  12. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +27 -20
  13. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -2
  14. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +26 -13
  15. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +21 -1
  16. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +4 -2
  17. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +1 -3
  18. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +41 -24
  19. package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +3 -1
  20. package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +79 -0
  21. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +3 -4
  22. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +19 -52
  23. package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +3 -3
  24. package/dist/extensions/agent-browser/lib/orchestration/operation-paths.js +76 -0
  25. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -3
  26. package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +91 -278
  27. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +9 -9
  28. package/dist/extensions/agent-browser/lib/playbook.js +46 -25
  29. package/dist/extensions/agent-browser/lib/process-identity.js +22 -6
  30. package/dist/extensions/agent-browser/lib/process.js +16 -13
  31. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +2 -2
  32. package/dist/extensions/agent-browser/lib/results/next-actions.js +1 -2
  33. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +25 -4
  34. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +6 -2
  35. package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -0
  36. package/dist/extensions/agent-browser/lib/results/presentation/content.js +39 -0
  37. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +3 -3
  38. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +41 -1
  39. package/dist/extensions/agent-browser/lib/results/presentation.js +16 -11
  40. package/dist/extensions/agent-browser/lib/runtime.js +18 -6
  41. package/dist/extensions/agent-browser/lib/session-page-state.js +3 -4
  42. package/dist/extensions/agent-browser/lib/tool-surface.js +131 -0
  43. package/dist/extensions/agent-browser/script-worker.js +4 -1
  44. package/docs/ARCHITECTURE.md +47 -61
  45. package/docs/COMMAND_REFERENCE.md +78 -137
  46. package/docs/ELECTRON.md +51 -49
  47. package/docs/RELEASE.md +31 -15
  48. package/docs/REQUIREMENTS.md +18 -14
  49. package/docs/SUPPORT_MATRIX.md +33 -15
  50. package/docs/TOOL_CONTRACT.md +218 -401
  51. package/package.json +4 -4
  52. package/scripts/doctor.mjs +2 -2
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { existsSync, readFileSync } from "node:fs";
2
3
  import { dirname, join } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
@@ -5,30 +6,35 @@ import { Text } from "@earendil-works/pi-tui";
5
6
  import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./lib/batch-lifecycle.js";
6
7
  import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
7
8
  import { SessionPageState } from "./lib/session-page-state.js";
8
- import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
9
- import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace } from "./lib/argv-grammar.js";
9
+ import { appendBrowserTransition, getBrowserResultMessage } from "./lib/browser-transcript.js";
10
+ import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractUpstreamCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, isRestorableManagedSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, redactInvocationArgs, redactSensitiveText, redactSensitiveValue, isPlainTextInspectionArgs, } from "./lib/runtime.js";
11
+ import { deleteIdentityKeysInNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } from "./lib/argv-grammar.js";
10
12
  import { parseArgvDescriptor } from "./lib/argv-descriptor.js";
11
13
  import { needsManagedSession } from "./lib/command-policy.js";
12
14
  import { ManagedSessionRestoreState, resolveOwnedManagedSessionContext, withOwnedManagedSessionContext } from "./lib/managed-session-restore.js";
13
15
  import { isRecord } from "./lib/parsing.js";
14
- import { runAgentBrowserProcess } from "./lib/process.js";
15
- import { getAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
16
+ import { getAgentBrowserProcessTimeoutMs, runAgentBrowserProcess } from "./lib/process.js";
17
+ import { getCommandAwareProcessTimeoutMs } from "./lib/orchestration/browser-run/prepare/wait-timeouts.js";
18
+ import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment, withIsolatedAgentBrowserEnvironment } from "./lib/process-environment.js";
16
19
  import { withNativeSessionDefaults } from "./lib/orchestration/native-session-defaults.js";
20
+ import { getBrowserCwdError, resolveExecutionCwd, restoreManagedSessionCwd } from "./lib/execution-cwd.js";
21
+ import { resolveOperationPaths } from "./lib/orchestration/operation-paths.js";
17
22
  import { closeManagedSession, inspectManagedSessionDaemon } from "./lib/orchestration/browser-run/managed-session-daemon-policy.js";
18
23
  import { MINIMUM_AGENT_BROWSER_VERSION, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, getAgentBrowserVersionValidationError, parseAgentBrowserVersionOutput, } from "./lib/upstream-version.js";
19
24
  import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
20
25
  import { isCloseAllCommand, isCloseCommand } from "./lib/command-taxonomy.js";
21
26
  import { hasLaunchScopedFlagToken } from "./lib/launch-scoped-flags.js";
22
27
  import { cleanupSecureTempArtifacts } from "./lib/temp.js";
23
- import { AGENT_BROWSER_PARAMS } from "./lib/input-modes/params.js";
24
- import { AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS, AGENT_BROWSER_SCRIPT_NAMESPACE, createAgentBrowserScriptSessionName, isAgentBrowserScriptSessionName, runAgentBrowserScript, } from "./lib/input-modes/script.js";
28
+ import { AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS, AGENT_BROWSER_SCRIPT_NAMESPACE, bindBrowserCodeCall, isAgentBrowserScriptSessionName, runAgentBrowserScript, } from "./lib/input-modes/script.js";
25
29
  import { canonicalizeExplicitArtifactDestination, getExplicitArtifactDestination, getRecordContactSheetDestination } from "./lib/orchestration/browser-run/artifact-paths.js";
26
- import { findElectronLaunchRecordForSession, getActiveElectronRecords, getSessionContextKey } from "./lib/orchestration/browser-run/session-state.js";
30
+ import { findElectronLaunchRecordForSession, getActiveElectronRecords, getPersistentSessionArtifactStore, getSessionContextKey } from "./lib/orchestration/browser-run/session-state.js";
27
31
  import { parseBatchCommandArgument, parseUserBatchStdin } from "./lib/orchestration/batch-stdin.js";
28
32
  import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
29
33
  import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
30
34
  import { applyAgentBrowserOutputPath, canWriteAgentBrowserOutput, normalizeRequestedOutputPath } from "./lib/orchestration/output-file.js";
31
- import { appendScriptSessionLease, buildScriptBrowserEnvelope, buildScriptToolResult, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
35
+ import { appendScriptSessionLease, createBrowserCodeOutput, getScriptSessionLeasesFromBranch } from "./lib/orchestration/script-mode.js";
36
+ import { resolveBrowserExecutionIdentity, withBrowserExecutionLock, withBrowserExecutionLocks } from "./lib/managed-session-policy-lock.js";
37
+ import { registerAgentBrowserToolSurface } from "./lib/tool-surface.js";
32
38
  import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingCommand, isSessionArtifactManifest, mergeSessionArtifactManifest, retirePendingRecordingManifestEntries } from "./lib/results/artifact-manifest.js";
33
39
  import { appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, applySessionToNextActions, buildNextToolAction } from "./lib/results/next-actions.js";
34
40
  import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
@@ -37,6 +43,12 @@ import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
37
43
  import { scopeReadConfirmationArgs } from "./lib/read-confirmation.js";
38
44
  import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
39
45
  import { AgentBrowserResultComponent, buildAgentBrowserToolResultPatch, formatAgentBrowserRenderCall, formatAgentBrowserRenderResult, } from "./lib/pi-tool-rendering.js";
46
+ function browserExecutionFailure(error, signal, fallbackCategory = "upstream-error") {
47
+ const timeout = error instanceof Error && error.name === "TimeoutError";
48
+ const summary = redactSensitiveText(error instanceof Error ? error.message : String(error));
49
+ const failureCategory = timeout ? "timeout" : signal?.aborted ? "aborted" : fallbackCategory;
50
+ return { content: [{ type: "text", text: summary }], details: { error: summary, summary, resultCategory: "failure", failureCategory }, isError: true };
51
+ }
40
52
  function isBashToolCallEvent(event) {
41
53
  if (!isRecord(event) || event.toolName !== "bash" || !isRecord(event.input))
42
54
  return false;
@@ -165,10 +177,8 @@ function getResultFileArtifacts(result) {
165
177
  function restoreArtifactManifestFromBranch(branch) {
166
178
  let restoredManifest;
167
179
  for (const entry of branch) {
168
- if (!isRecord(entry) || entry.type !== "message")
169
- continue;
170
- const message = isRecord(entry.message) ? entry.message : undefined;
171
- if (!message || message.toolName !== "agent_browser")
180
+ const message = getBrowserResultMessage(entry);
181
+ if (!message)
172
182
  continue;
173
183
  const details = isRecord(message.details) ? message.details : undefined;
174
184
  if (isSessionArtifactManifest(details?.artifactManifest) && (!restoredManifest || details.artifactManifest.updatedAtMs >= restoredManifest.updatedAtMs)) {
@@ -187,10 +197,8 @@ function restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, sessionN
187
197
  let restored;
188
198
  const targetKey = getSessionContextKey(sessionName, namespace);
189
199
  for (const entry of branch) {
190
- if (!isRecord(entry) || entry.type !== "message")
191
- continue;
192
- const message = isRecord(entry.message) ? entry.message : undefined;
193
- if (!message || message.toolName !== "agent_browser")
200
+ const message = getBrowserResultMessage(entry);
201
+ if (!message)
194
202
  continue;
195
203
  const details = isRecord(message.details) ? message.details : undefined;
196
204
  if (!details)
@@ -220,10 +228,8 @@ function restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, sessionNa
220
228
  let restored = false;
221
229
  const targetKey = getSessionContextKey(sessionName, namespace);
222
230
  for (const entry of branch) {
223
- if (!isRecord(entry) || entry.type !== "message")
224
- continue;
225
- const message = isRecord(entry.message) ? entry.message : undefined;
226
- if (!message || message.toolName !== "agent_browser")
231
+ const message = getBrowserResultMessage(entry);
232
+ if (!message)
227
233
  continue;
228
234
  const details = isRecord(message.details) ? message.details : undefined;
229
235
  if (!details)
@@ -244,10 +250,8 @@ function restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, sessionNa
244
250
  let restored;
245
251
  const targetKey = getSessionContextKey(sessionName, namespace);
246
252
  for (const entry of branch) {
247
- if (!isRecord(entry) || entry.type !== "message")
248
- continue;
249
- const message = isRecord(entry.message) ? entry.message : undefined;
250
- if (!message || message.toolName !== "agent_browser")
253
+ const message = getBrowserResultMessage(entry);
254
+ if (!message)
251
255
  continue;
252
256
  const details = isRecord(message.details) ? message.details : undefined;
253
257
  if (!details)
@@ -291,10 +295,8 @@ function isAttachedBrowserInvocation(args, env = getAgentBrowserProcessEnvironme
291
295
  function restoreAttachedSessionKeysFromBranch(branch) {
292
296
  const attachedSessionKeys = new Set();
293
297
  for (const entry of branch) {
294
- if (!isRecord(entry) || entry.type !== "message")
295
- continue;
296
- const message = isRecord(entry.message) ? entry.message : undefined;
297
- if (!message || message.toolName !== "agent_browser")
298
+ const message = getBrowserResultMessage(entry);
299
+ if (!message)
298
300
  continue;
299
301
  const details = isRecord(message.details) ? message.details : undefined;
300
302
  if (!details)
@@ -537,10 +539,8 @@ function collectBranchManagedResourceEvents(branch) {
537
539
  };
538
540
  let eventRank = 0;
539
541
  for (const entry of branch) {
540
- if (!isRecord(entry) || entry.type !== "message")
541
- continue;
542
- const message = isRecord(entry.message) ? entry.message : undefined;
543
- if (!message || message.toolName !== "agent_browser")
542
+ const message = getBrowserResultMessage(entry);
543
+ if (!message)
544
544
  continue;
545
545
  const details = isRecord(message.details) ? message.details : undefined;
546
546
  if (!details)
@@ -659,36 +659,56 @@ function shouldSerializeBrowserCommand(options) {
659
659
  // Serializes managed-session read/modify/write work so overlapping tool calls cannot promote stale state or close an in-use session.
660
660
  class AsyncExecutionQueue {
661
661
  tail = Promise.resolve();
662
- run(work) {
662
+ active = new AsyncLocalStorage();
663
+ isCurrent() { return this.active.getStore()?.active === true; }
664
+ run(work, signal, barrier = Promise.resolve()) {
665
+ if (this.isCurrent()) {
666
+ signal?.throwIfAborted();
667
+ return work();
668
+ }
663
669
  const previous = this.tail;
664
670
  let release;
665
671
  this.tail = new Promise((resolve) => {
666
672
  release = resolve;
667
673
  });
668
- return (async () => {
669
- await previous;
674
+ let rejectWaiting;
675
+ const cancelled = new Promise((_resolve, reject) => { rejectWaiting = reject; });
676
+ const abortWaiting = () => rejectWaiting(signal?.reason);
677
+ signal?.addEventListener("abort", abortWaiting, { once: true });
678
+ if (signal?.aborted)
679
+ abortWaiting();
680
+ const execution = (async () => {
681
+ await Promise.all([previous, barrier]);
682
+ signal?.removeEventListener("abort", abortWaiting);
670
683
  try {
671
- return await work();
684
+ signal?.throwIfAborted();
685
+ const scope = { active: true };
686
+ try {
687
+ return await this.active.run(scope, work);
688
+ }
689
+ finally {
690
+ scope.active = false;
691
+ }
672
692
  }
673
693
  finally {
674
694
  release();
675
695
  }
676
696
  })();
697
+ return signal ? Promise.race([execution, cancelled]) : execution;
677
698
  }
678
699
  }
679
700
  export class KeyedAsyncExecutionQueue {
680
701
  barriers = new Map();
681
702
  entries = new Map();
682
- async run(key, namespace, work) {
703
+ async run(key, namespace, work, signal) {
683
704
  const entry = this.entries.get(key) ?? { queue: new AsyncExecutionQueue(), users: 0 };
705
+ if (entry.queue.isCurrent())
706
+ return work();
684
707
  const barrier = this.barriers.get(getAgentBrowserSessionIdentityKey("", namespace)) ?? Promise.resolve();
685
708
  entry.users += 1;
686
709
  this.entries.set(key, entry);
687
710
  try {
688
- return await entry.queue.run(async () => {
689
- await barrier;
690
- return await work();
691
- });
711
+ return await entry.queue.run(work, signal, barrier);
692
712
  }
693
713
  finally {
694
714
  entry.users -= 1;
@@ -1108,7 +1128,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1108
1128
  managedSessionHeadedAutosaveInterval = managedSessionActive
1109
1129
  ? restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, managedSessionName, managedSessionNamespace)
1110
1130
  : undefined;
1111
- managedSessionCwd = ctx.cwd;
1131
+ managedSessionCwd = restoreManagedSessionCwd(branch, managedSessionName, managedSessionNamespace, ctx.cwd);
1112
1132
  freshSessionOrdinal = nextFreshSessionOrdinal;
1113
1133
  sessionPageState = SessionPageState.fromBranch(branch);
1114
1134
  traceOwners = new Map();
@@ -1159,7 +1179,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1159
1179
  continue;
1160
1180
  if (!isRestorableManagedSessionName(identity.sessionName, managedSessionBaseName))
1161
1181
  continue;
1162
- trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, ctx.cwd, {
1182
+ trackOwnedManagedSession(ownedManagedSessions, identity.sessionName, restoreManagedSessionCwd(branch, identity.sessionName, identity.namespace, ctx.cwd), {
1163
1183
  branchOwned: true,
1164
1184
  compatibilityWorkaround: restoreManagedSessionCompatibilityWorkaroundFromBranch(branch, identity.sessionName, identity.namespace),
1165
1185
  headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, identity.sessionName, identity.namespace),
@@ -1168,7 +1188,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1168
1188
  });
1169
1189
  }
1170
1190
  if (restoredState.active) {
1171
- trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, {
1191
+ trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, managedSessionCwd, {
1172
1192
  branchOwned: true,
1173
1193
  compatibilityWorkaround: managedSessionCompatibilityWorkaround,
1174
1194
  headedManagedAutosaveDisabled: managedSessionHeadedAutosaveDisabled,
@@ -1184,7 +1204,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1184
1204
  const closeRank = branchResourceEvents.managedSessionCloseRanks.get(sessionKey);
1185
1205
  if (activeRank === undefined || (closeRank !== undefined && closeRank >= activeRank))
1186
1206
  continue;
1187
- trackOwnedManagedSession(ownedManagedSessions, record.sessionName, ctx.cwd, {
1207
+ trackOwnedManagedSession(ownedManagedSessions, record.sessionName, restoreManagedSessionCwd(branch, record.sessionName, undefined, ctx.cwd), {
1188
1208
  branchOwned: true,
1189
1209
  headedManagedAutosaveDisabled: restoreManagedSessionHeadedAutosaveDisabledFromBranch(branch, record.sessionName),
1190
1210
  headedManagedAutosaveInterval: restoreManagedSessionHeadedAutosaveIntervalFromBranch(branch, record.sessionName),
@@ -1226,31 +1246,32 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1226
1246
  cwd: ctx.cwd,
1227
1247
  includeProjectConfig: shouldIncludeProjectConfig(ctx),
1228
1248
  }));
1229
- await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(async () => {
1249
+ await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
1230
1250
  await recoverScriptSessionLeasesWithinQueue(ctx);
1231
1251
  flushRecordingReservations();
1232
1252
  notifyRecordingPersistence(ctx);
1233
1253
  }));
1234
1254
  });
1235
1255
  pi.on("session_tree", async (_event, ctx) => {
1256
+ branchRestoreGeneration += 1;
1236
1257
  for (const controller of activeScriptControllers)
1237
1258
  controller.abort();
1238
1259
  await Promise.allSettled([...activeScriptExecutions]);
1239
- await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(async () => {
1260
+ await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
1240
1261
  restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
1241
1262
  await recoverScriptSessionLeasesWithinQueue(ctx);
1242
1263
  flushRecordingReservations();
1243
1264
  notifyRecordingPersistence(ctx);
1244
1265
  }));
1245
1266
  });
1246
- // Additive native checkpoint event; the package's 0.84 validation types predate it.
1247
- // Pi owns awaited tools/events (including their execution queues). Only script
1248
- // children and retained browser/recording resources need extension-side checks.
1267
+ // Fork-only checkpoint event; official Pi validation types do not declare it.
1268
+ // Pi owns awaited tools/events (including their execution queues). Detached code
1269
+ // children and retained browser/recording resources still need extension-side checks.
1249
1270
  const checkpointPi = pi;
1250
1271
  checkpointPi.on("session_checkpoint", async (event, ctx) => {
1251
1272
  const blocked = (reason) => ({ sleepReady: false, reason });
1252
1273
  if (activeScriptControllers.size || activeScriptExecutions.size)
1253
- return blocked("Browser script execution or cleanup is pending");
1274
+ return blocked("Browser code execution is pending");
1254
1275
  flushRecordingReservations();
1255
1276
  if (recordingReservationsDirty)
1256
1277
  return blocked("Browser recording journal persistence is dirty");
@@ -1276,9 +1297,10 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1276
1297
  if (managedSessionActive)
1277
1298
  trackOwnedManagedSession(sessions, managedSessionName, managedSessionCwd, { namespace: managedSessionNamespace });
1278
1299
  for (const entry of entries) {
1279
- if (entry.type !== "message" || entry.message.role !== "toolResult" || entry.message.toolName !== "agent_browser")
1300
+ const message = getBrowserResultMessage(entry);
1301
+ if (!message)
1280
1302
  continue;
1281
- const details = isRecord(entry.message.details) ? entry.message.details : undefined;
1303
+ const details = isRecord(message.details) ? message.details : undefined;
1282
1304
  // Helpers can launch before a main-process failure. Conversely, native
1283
1305
  // launch flags on sessionless reads can open the unnamed default browser.
1284
1306
  const sessionName = typeof details?.sessionName === "string" ? details.sessionName
@@ -1322,7 +1344,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1322
1344
  branchRestoreGeneration += 1;
1323
1345
  branchStateGeneration += 1;
1324
1346
  let preservedElectronProfileDirs = [];
1325
- await artifactExecutionQueue.run(() => managedSessionExecutionQueue.run(async () => {
1347
+ await managedSessionExecutionQueue.run(() => artifactExecutionQueue.run(async () => {
1326
1348
  const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
1327
1349
  const quitting = event?.reason === "quit";
1328
1350
  preservedElectronProfileDirs = quitting
@@ -1396,9 +1418,7 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1396
1418
  const runtimeConfigPrompt = browserGuidance.length > 0
1397
1419
  ? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
1398
1420
  : "";
1399
- return {
1400
- systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`,
1401
- };
1421
+ event.systemPromptOptions.sections.agent_browser = `${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`;
1402
1422
  });
1403
1423
  pi.on("tool_call", async (event, ctx) => {
1404
1424
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
@@ -1415,12 +1435,6 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1415
1435
  });
1416
1436
  pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
1417
1437
  const agentBrowserTool = {
1418
- name: "agent_browser",
1419
- label: "Agent Browser",
1420
- description: "Browse and interact with websites using agent-browser. Use this for reading live pages, opening known URLs, taking snapshots or screenshots, clicking links, filling forms, extracting page content, and authenticated/profile-based browser work. Input choice: `script` for one-shot JavaScript orchestration; default `args` for open → snapshot -i → click/fill @refs; `semanticAction` for stable role/text/label targets; `job` or `qa` for multi-step checks; `electron` only for desktop apps; experimental `sourceLookup` / `networkSourceLookup` for candidates only.",
1421
- promptSnippet: "Browse websites, read live docs, click and fill pages, extract browser content, take screenshots, and automate real web workflows.",
1422
- promptGuidelines: toolPromptGuidelines,
1423
- parameters: AGENT_BROWSER_PARAMS,
1424
1438
  renderCall(args, theme, context) {
1425
1439
  const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
1426
1440
  text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
@@ -1433,479 +1447,598 @@ export default function agentBrowserExtension(pi, { beforeExecute } = {}) {
1433
1447
  component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
1434
1448
  return component;
1435
1449
  },
1436
- async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId) {
1450
+ async execute(toolCallId, params, signal, onUpdate, ctx, nativeToolCallId = toolCallId, capturedCwd, modelVisible = true) {
1451
+ let operationCwd;
1452
+ try {
1453
+ operationCwd = capturedCwd ?? resolveExecutionCwd(pi, ctx);
1454
+ }
1455
+ catch (error) {
1456
+ return buildValidationFailureResult({ kind: "invalid", status: "invalid", redactedArgs: [], toolArgs: [], validationError: error instanceof Error ? error.message : String(error) });
1457
+ }
1437
1458
  const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
1438
1459
  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 }),
1460
+ const admittedInput = resolveAgentBrowserInput({
1461
+ getBatchPreflightValidationError: (args, stdin) => getArtifactPreflightValidationError({ args, cwd: operationCwd, outputPath, stdin }),
1441
1462
  params,
1442
1463
  });
1443
- if (resolvedInput.status === "invalid") {
1444
- return buildValidationFailureResult(resolvedInput);
1464
+ if (admittedInput.status === "invalid") {
1465
+ return buildValidationFailureResult(admittedInput);
1445
1466
  }
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." });
1467
+ const explicitConfig = scanUpstreamGlobalFlagOccurrences(admittedInput.toolArgs, "--config").length > 0 || getAgentBrowserProcessEnvironment().AGENT_BROWSER_CONFIG !== undefined;
1468
+ // Managed selection must follow earlier queued fresh launches, while file paths keep their admission snapshot.
1469
+ const managedAtAdmission = admittedInput.kind !== "electron"
1470
+ && !extractExplicitSessionName(admittedInput.toolArgs) && !explicitConfig;
1471
+ const executeAdmitted = async () => {
1472
+ let resolvedInput = admittedInput;
1473
+ const requestedSession = extractExplicitSessionName(resolvedInput.toolArgs);
1474
+ const requestedNamespace = resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
1475
+ const requestedOwner = requestedSession ? ownedManagedSessions.get(getSessionContextKey(requestedSession, requestedNamespace) ?? requestedSession) : undefined;
1476
+ const browserCwd = (params.sessionMode === "fresh" && !requestedSession) || explicitConfig ? operationCwd
1477
+ : requestedSession ? requestedOwner?.cwd ?? ctx.cwd : managedSessionActive ? managedSessionCwd : ctx.cwd;
1478
+ const browserCwdError = resolvedInput.kind === "electron" ? undefined : getBrowserCwdError(browserCwd);
1479
+ if (browserCwdError)
1480
+ return buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: browserCwdError });
1481
+ if (!isPlainTextInspectionArgs(resolvedInput.toolArgs) && (operationCwd !== browserCwd || operationCwd !== ctx.cwd)) {
1482
+ const bound = resolveOperationPaths(resolvedInput.toolArgs, resolvedInput.toolStdin, operationCwd);
1483
+ resolvedInput = { ...resolvedInput, toolArgs: bound.args, toolStdin: bound.stdin };
1459
1484
  }
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
- });
1485
+ const executionTimeoutMs = (resolvedInput.kind === "electron" && "timeoutMs" in resolvedInput.compiledElectron ? resolvedInput.compiledElectron.timeoutMs : undefined) ?? params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(resolvedInput.toolArgs), resolvedInput.toolStdin) ?? getAgentBrowserProcessTimeoutMs();
1486
+ await beforeExecute?.(nativeToolCallId, { ...ctx, signal });
1487
+ const runtimeBrowserConfig = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
1488
+ const rootProfile = runtimeBrowserConfig.trustedBrowserDefaultProfile;
1489
+ const pendingReadConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1490
+ const rootSessionId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
1491
+ ? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
1492
+ return withNativeSessionDefaults(resolvedInput, browserCwd, signal, async (resolvedInput, withLaunchDefaults) => {
1493
+ const readConfirmation = sessionPageState.findReadConfirmation(resolvedInput.toolArgs, resolveAgentBrowserNamespace(resolvedInput.toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE));
1494
+ if (readConfirmation)
1495
+ resolvedInput = { ...resolvedInput, toolArgs: scopeReadConfirmationArgs(resolvedInput.toolArgs, readConfirmation) };
1496
+ if (resolvedInput.kind === "qa" && resolvedInput.compiledQaPreset.checks.attached && !managedSessionActive && !extractExplicitSessionName(resolvedInput.toolArgs)) {
1497
+ 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
1498
  }
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: [],
1499
+ const applyUnserializedOutputPath = async (result, preserveTextContent = false) => {
1500
+ if (!outputPath || !canWriteAgentBrowserOutput(result))
1501
+ return warnRecordingPersistence(result);
1502
+ return artifactExecutionQueue.run(async () => {
1503
+ flushRecordingReservations();
1504
+ const reservationError = getArtifactPreflightValidationError({
1505
+ activeRecordingReservations: activeRecordingReservations.values(),
1506
+ args: [],
1507
+ cwd: operationCwd,
1508
+ outputPath,
1509
+ });
1510
+ if (reservationError) {
1511
+ return warnRecordingPersistence(buildValidationFailureResult({ attemptedKind: resolvedInput.kind, kind: "invalid", redactedArgs: resolvedInput.redactedArgs, status: "invalid", toolArgs: resolvedInput.toolArgs, toolStdin: resolvedInput.toolStdin, validationError: reservationError }));
1512
+ }
1513
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent, result: warnRecordingPersistence(result) });
1514
+ });
1510
1515
  };
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));
1516
+ const versionCheckCommand = extractUpstreamCommandTokens(resolvedInput.toolArgs)[0];
1517
+ const electronHostOnlyAction = resolvedInput.kind === "electron" && ["cleanup", "list", "status"].includes(resolvedInput.compiledElectron.action);
1518
+ const browserBackedVersionCheck = readConfirmation?.capabilities?.readRequiresConfirmation !== true && needsManagedSession(parseArgvDescriptor(resolvedInput.toolArgs), resolvedInput.toolStdin);
1519
+ if (!electronHostOnlyAction && browserBackedVersionCheck && !isPlainTextInspectionArgs(resolvedInput.toolArgs) && !isCloseCommand(versionCheckCommand) && signal?.aborted !== true) {
1520
+ const versionFailure = await validateUpstreamVersion(browserCwd, signal);
1525
1521
  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;
1522
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: versionFailure });
1552
1523
  }
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
- }));
1524
+ const { toolArgs } = resolvedInput;
1525
+ const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
1526
+ const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
1527
+ const runElectronHostInput = async (executionSignal = signal) => {
1528
+ const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
1529
+ branchRecords: electronLaunchRecords,
1530
+ compiledElectron,
1531
+ ownedRecords: ownedElectronLaunchRecords,
1532
+ });
1533
+ let electronHostResult = await handleElectronHostInput({
1534
+ attachedSessionKeys,
1535
+ compiledElectron,
1536
+ cwd: ctx.cwd,
1537
+ electronChildProcesses,
1538
+ electronLaunchRecords: electronHostLaunchRecords,
1539
+ implicitSessionCloseTimeoutMs,
1540
+ managedSessionActive,
1541
+ managedSessionName,
1542
+ managedSessionNamespace,
1543
+ managedSessionRestoreState,
1544
+ ownedManagedSessions,
1545
+ redactedCompiledElectron,
1546
+ sessionPageState,
1547
+ signal: executionSignal,
1548
+ });
1549
+ if (electronHostResult && compiledElectron?.action === "cleanup") {
1550
+ branchStateGeneration += 1;
1551
+ const cleanupRecords = isRecord(electronHostResult.details)
1552
+ && isRecord(electronHostResult.details.electron)
1553
+ && isRecord(electronHostResult.details.electron.cleanup)
1554
+ && Array.isArray(electronHostResult.details.electron.cleanup.results)
1555
+ ? electronHostResult.details.electron.cleanup.results
1556
+ : [];
1557
+ const cleanedLaunchIds = new Set();
1558
+ for (const cleanupResult of cleanupRecords) {
1559
+ if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
1560
+ cleanedLaunchIds.add(cleanupResult.record.launchId);
1561
+ }
1563
1562
  }
1564
- catch {
1565
- cleanupError = "The isolated script session cleanup operation failed.";
1566
- try {
1567
- appendScriptSessionLease(pi, sessionName, "failed");
1563
+ replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
1564
+ mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
1565
+ const cleanupNamespace = isRecord(electronHostResult.details) && typeof electronHostResult.details.namespace === "string"
1566
+ ? electronHostResult.details.namespace
1567
+ : undefined;
1568
+ const closedSessionIdentities = getCleanupResultsClosedManagedSessionIdentities(cleanupRecords, cleanupNamespace);
1569
+ syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords, cleanupNamespace);
1570
+ for (const identity of closedSessionIdentities) {
1571
+ retireRecordingSession(identity.sessionName, identity.namespace);
1572
+ const closedSessionKey = getSessionContextKey(identity.sessionName, identity.namespace) ?? identity.sessionName;
1573
+ clearSessionScopedBrowserState(closedSessionKey);
1574
+ if (closedSessionKey === (getSessionContextKey(managedSessionName, managedSessionNamespace) ?? managedSessionName)) {
1575
+ managedSessionActive = false;
1576
+ managedSessionCompatibilityWorkaround = undefined;
1577
+ managedSessionHeadedAutosaveDisabled = false;
1578
+ managedSessionHeadedAutosaveInterval = undefined;
1579
+ managedSessionNamespace = undefined;
1580
+ freshSessionOrdinal += 1;
1581
+ managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
1568
1582
  }
1569
- catch { }
1570
1583
  }
1571
- }
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
- },
1584
- };
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);
1584
+ if (artifactManifest) {
1585
+ electronHostResult = {
1586
+ ...electronHostResult,
1587
+ details: {
1588
+ ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1589
+ artifactManifest: redactSensitiveValue(artifactManifest),
1590
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1591
+ },
1592
+ };
1625
1593
  }
1626
1594
  }
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);
1646
- }
1595
+ if (electronHostResult && executionSignal?.aborted) {
1596
+ const failure = browserExecutionFailure(executionSignal.reason, executionSignal);
1597
+ return { ...electronHostResult, ...failure, details: { ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}), ...(isRecord(failure.details) ? failure.details : {}) } };
1647
1598
  }
1648
- if (artifactManifest) {
1649
- electronHostResult = {
1650
- ...electronHostResult,
1651
- details: {
1652
- ...(isRecord(electronHostResult.details) ? electronHostResult.details : {}),
1653
- artifactManifest: redactSensitiveValue(artifactManifest),
1654
- artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1655
- },
1656
- };
1599
+ return electronHostResult;
1600
+ };
1601
+ const runCoordinatedElectronHostInput = async () => {
1602
+ const execute = (executionSignal = signal) => compiledElectron?.action === "cleanup"
1603
+ ? artifactExecutionQueue.run(async () => {
1604
+ flushRecordingReservations();
1605
+ const error = outputPath ? getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: [], cwd: operationCwd, outputPath }) : undefined;
1606
+ if (error)
1607
+ return warnRecordingPersistence(buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: error }));
1608
+ const result = await runElectronHostInput(executionSignal);
1609
+ return result ? applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, result: warnRecordingPersistence(result) }) : result;
1610
+ }) : runElectronHostInput(executionSignal);
1611
+ if (!shouldSerializeElectronHostInput(compiledElectron))
1612
+ return execute();
1613
+ const records = getElectronHostLaunchRecordsForInput({ branchRecords: electronLaunchRecords, compiledElectron, ownedRecords: ownedElectronLaunchRecords });
1614
+ const launchId = compiledElectron && "launchId" in compiledElectron ? compiledElectron.launchId : undefined;
1615
+ const selected = launchId ? [records.get(launchId)].filter((record) => record !== undefined) : getActiveElectronRecords(records);
1616
+ const sessions = selected.flatMap(record => record.sessionName ? [{ sessionName: record.sessionName, namespace: record.namespace }] : []);
1617
+ if (compiledElectron?.action === "probe" && !launchId && managedSessionActive)
1618
+ sessions.push({ sessionName: managedSessionName, namespace: managedSessionNamespace });
1619
+ if (sessions.length === 0)
1620
+ return execute();
1621
+ try {
1622
+ const identities = await Promise.all(sessions.map(session => resolveBrowserExecutionIdentity({ ...session, ownedManagedSession: true })));
1623
+ return await withBrowserExecutionLocks({ identities, signal, deadline: Date.now() + executionTimeoutMs, waitOnly: true }, execute);
1657
1624
  }
1658
- }
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: [],
1670
- 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 }));
1625
+ catch (error) {
1626
+ return browserExecutionFailure(error, signal);
1675
1627
  }
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,
1628
+ };
1629
+ const electronHostResult = await (shouldSerializeElectronHostInput(compiledElectron)
1630
+ ? managedSessionExecutionQueue.run(runCoordinatedElectronHostInput, signal)
1631
+ : runCoordinatedElectronHostInput());
1632
+ if (electronHostResult) {
1633
+ return compiledElectron?.action === "cleanup" ? electronHostResult : applyUnserializedOutputPath(electronHostResult);
1634
+ }
1635
+ const explicitSessionName = extractExplicitSessionName(toolArgs);
1636
+ const callerOwnedSessionNamespace = explicitSessionName
1637
+ ? resolveAgentBrowserNamespace(toolArgs, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE)
1638
+ : undefined;
1639
+ const serializeBrowserCommand = shouldSerializeBrowserCommand({
1640
+ namespace: callerOwnedSessionNamespace,
1641
+ explicitSessionName,
1720
1642
  managedSessionName,
1721
- managedSessionNamespace,
1722
- managedSessionRestoreState,
1723
- networkRoutesBySession,
1643
+ ownedElectronLaunchRecords,
1724
1644
  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
1645
  });
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) {
1646
+ const callerOwnedSessionQueueKey = !serializeBrowserCommand && explicitSessionName
1647
+ ? getSessionContextKey(explicitSessionName, callerOwnedSessionNamespace) ?? explicitSessionName
1648
+ : undefined;
1649
+ const runBrowserCommand = async (daemonInactive, executionSignal = signal) => {
1650
+ // Load execution-only preparation/output code inside the existing session queue,
1651
+ // before capturing state. Registration, rendering, and restore stay synchronous/eager.
1652
+ const { runAgentBrowserTool } = await import("./lib/orchestration/browser-run/index.js");
1653
+ flushRecordingReservations();
1654
+ const branchRestoreGenerationAtStart = branchRestoreGeneration;
1655
+ const generationAtStart = branchStateGeneration;
1656
+ const sessionPageStateUpdate = sessionPageState.beginUpdate();
1657
+ const browserRunState = {
1658
+ activeRecordingReservations,
1659
+ artifactManifest,
1660
+ attachedSessionKeys,
1661
+ closedManagedSessionNames: new Set(),
1662
+ electronChildProcesses,
1663
+ electronLaunchRecords,
1664
+ ephemeralSessionSeed,
1665
+ freshSessionOrdinal,
1666
+ managedSessionActive,
1667
+ managedSessionBaseName,
1668
+ managedSessionCompatibilityWorkaround,
1669
+ managedSessionHeadedAutosaveDisabled,
1670
+ managedSessionHeadedAutosaveInterval,
1671
+ managedSessionCwd,
1672
+ managedSessionName,
1673
+ managedSessionNamespace,
1674
+ managedSessionRestoreState,
1675
+ networkRoutesBySession,
1676
+ ownedManagedSessions,
1677
+ sessionPageState,
1678
+ traceOwners,
1679
+ };
1680
+ const selectedPlan = buildExecutionPlan(toolArgs, {
1681
+ freshSessionName: createFreshSessionName(browserRunState.managedSessionBaseName, browserRunState.ephemeralSessionSeed, browserRunState.freshSessionOrdinal + 1),
1682
+ managedSessionActive: browserRunState.managedSessionActive,
1683
+ managedSessionCompatibilityWorkaround: browserRunState.managedSessionCompatibilityWorkaround,
1684
+ managedSessionName: browserRunState.managedSessionName,
1685
+ managedSessionNamespace: browserRunState.managedSessionNamespace,
1686
+ sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1687
+ stdin: resolvedInput.toolStdin,
1688
+ browserIndependentReadConfirmation: readConfirmation?.capabilities?.readRequiresConfirmation === true,
1689
+ });
1690
+ const initialArtifactManifest = browserRunState.artifactManifest;
1691
+ const initialNetworkRoutesBySession = browserRunState.networkRoutesBySession;
1692
+ const attachedSessionRequested = isAttachedBrowserInvocation(toolArgs)
1693
+ || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch");
1694
+ const allocatesFreshManagedSession = explicitSessionName === undefined
1695
+ && (params.sessionMode === "fresh" || (resolvedInput.kind === "electron" && resolvedInput.compiledElectron.action === "launch"));
1696
+ const reusableSessionKey = allocatesFreshManagedSession
1697
+ ? undefined
1698
+ : getSessionContextKey(selectedPlan.sessionName, selectedPlan.namespace)
1699
+ ?? getSessionContextKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace);
1700
+ const attachedSessionKnown = reusableSessionKey !== undefined && attachedSessionKeys.has(reusableSessionKey);
1701
+ let result = await runAgentBrowserTool({
1702
+ daemonInactive,
1703
+ ctx,
1704
+ cwd: browserCwd,
1705
+ operationCwd,
1706
+ electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
1707
+ electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
1708
+ implicitSessionCloseTimeoutMs,
1709
+ implicitSessionIdleTimeoutMs,
1710
+ input: resolvedInput,
1711
+ modelVisible,
1712
+ onUpdate,
1713
+ params,
1714
+ establishAttachedBrowserSession: attachedSessionRequested && !attachedSessionKnown,
1715
+ preserveAttachedBrowserSession: attachedSessionRequested || attachedSessionKnown,
1716
+ promptPolicy,
1717
+ sessionPageStateUpdate,
1718
+ signal: executionSignal,
1719
+ state: browserRunState,
1720
+ });
1721
+ const branchRestoreStillCurrent = branchRestoreGenerationAtStart === branchRestoreGeneration;
1722
+ const resultDetails = isRecord(result.details) ? result.details : undefined;
1723
+ if (typeof resultDetails?.sessionName === "string" && browserRunState.managedSessionActive
1724
+ && getAgentBrowserSessionIdentityKey(resultDetails.sessionName, typeof resultDetails.namespace === "string" ? resultDetails.namespace : undefined) === getAgentBrowserSessionIdentityKey(browserRunState.managedSessionName, browserRunState.managedSessionNamespace)) {
1725
+ resultDetails.managedSessionCwd = browserRunState.managedSessionCwd;
1726
+ }
1727
+ const resultSessionName = typeof resultDetails?.sessionName === "string"
1728
+ ? resultDetails.sessionName
1729
+ : extractExplicitSessionName(toolArgs);
1730
+ const resultNamespace = typeof resultDetails?.namespace === "string"
1731
+ ? resultDetails.namespace
1732
+ : selectedPlan.namespace;
1733
+ if (branchRestoreStillCurrent) {
1734
+ const resultBatchCloseLifecycle = getSuccessfulBatchCloseLifecycle(resultDetails?.batchSteps);
1735
+ const resultSessionKey = getSessionContextKey(resultSessionName, resultNamespace) ?? resultSessionName;
1736
+ const managedSessionOutcome = isRecord(resultDetails?.managedSessionOutcome) ? resultDetails.managedSessionOutcome : undefined;
1737
+ const closeAllApplied = resultDetails?.closeAllApplied === true;
1738
+ const attachedSessionRemainsActive = result.isError !== true
1739
+ || ((attachedSessionRequested || attachedSessionKnown) && managedSessionOutcome?.activeAfter === true);
1740
+ const closesAttachedSession = (result.isError !== true && isCloseCommand(extractUpstreamCommandTokens(toolArgs)[0]))
1741
+ || resultBatchCloseLifecycle?.endsClosed === true;
1742
+ if (closeAllApplied) {
1743
+ deleteIdentityKeysInNamespace(attachedSessionKeys, resultNamespace);
1744
+ if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown) && resultBatchCloseLifecycle?.endsClosed === false) {
1745
+ attachedSessionKeys.add(resultSessionKey);
1746
+ result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1747
+ }
1748
+ }
1749
+ else if (resultSessionKey && closesAttachedSession)
1750
+ attachedSessionKeys.delete(resultSessionKey);
1751
+ else if (resultSessionKey && attachedSessionRemainsActive && (attachedSessionRequested || attachedSessionKnown)) {
1787
1752
  attachedSessionKeys.add(resultSessionKey);
1788
1753
  result = { ...result, details: { ...(resultDetails ?? {}), attachedBrowserSession: true } };
1789
1754
  }
1790
1755
  }
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);
1756
+ if (branchRestoreStillCurrent) {
1757
+ networkRoutesBySession = mergeBrowserRunMap(networkRoutesBySession, initialNetworkRoutesBySession, browserRunState.networkRoutesBySession);
1758
+ artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, initialArtifactManifest, browserRunState.artifactManifest);
1759
+ const handledBatchCloseKeys = syncRecordingReservationsFromResult(result);
1760
+ if (resultDetails?.closeAllApplied === true) {
1761
+ for (const [sessionKey, reservation] of [...activeRecordingReservations]) {
1762
+ if (isAgentBrowserSessionIdentityKeyInNamespace(sessionKey, resultNamespace)) {
1763
+ retireRecordingSession(reservation.sessionName, reservation.namespace);
1764
+ }
1806
1765
  }
1807
1766
  }
1767
+ for (const closedSessionKey of browserRunState.closedManagedSessionNames) {
1768
+ if (handledBatchCloseKeys.has(closedSessionKey))
1769
+ continue;
1770
+ const reservation = activeRecordingReservations.get(closedSessionKey);
1771
+ if (reservation)
1772
+ retireRecordingSession(reservation.sessionName, reservation.namespace);
1773
+ }
1774
+ if (resultSessionName) {
1775
+ const reservation = activeRecordingReservations.get(getAgentBrowserSessionIdentityKey(resultSessionName, resultNamespace));
1776
+ if (reservation)
1777
+ result = appendActiveRecordingCleanupAction(result, reservation);
1778
+ }
1779
+ if (artifactManifest) {
1780
+ result = {
1781
+ ...result,
1782
+ details: {
1783
+ ...(isRecord(result.details) ? result.details : {}),
1784
+ artifactManifest: redactSensitiveValue(artifactManifest),
1785
+ artifactRetentionSummary: formatSessionArtifactRetentionSummary(artifactManifest),
1786
+ },
1787
+ };
1788
+ }
1808
1789
  }
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);
1790
+ const branchStateStillCurrent = generationAtStart === branchStateGeneration;
1791
+ if (serializeBrowserCommand || branchStateStillCurrent) {
1792
+ freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
1793
+ managedSessionActive = browserRunState.managedSessionActive;
1794
+ managedSessionCompatibilityWorkaround = browserRunState.managedSessionCompatibilityWorkaround;
1795
+ managedSessionHeadedAutosaveDisabled = browserRunState.managedSessionHeadedAutosaveDisabled === true;
1796
+ managedSessionHeadedAutosaveInterval = browserRunState.managedSessionHeadedAutosaveInterval;
1797
+ managedSessionCwd = browserRunState.managedSessionCwd;
1798
+ managedSessionName = browserRunState.managedSessionName;
1799
+ managedSessionNamespace = browserRunState.managedSessionNamespace;
1800
+ for (const closedSessionName of browserRunState.closedManagedSessionNames) {
1801
+ untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
1802
+ }
1803
+ syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
1804
+ mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
1805
+ branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
1806
+ touchedLaunchIds: !result.isError
1807
+ ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords, resultNamespace)
1808
+ : undefined,
1809
+ });
1810
+ if (serializeBrowserCommand)
1811
+ branchStateGeneration += 1;
1820
1812
  }
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
- };
1813
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result: warnRecordingPersistence(result) });
1814
+ };
1815
+ const closesAllSessions = commandClosesAllSessions(toolArgs, resolvedInput.toolStdin);
1816
+ const runWithLaunchDefaults = async () => {
1817
+ // Electron's browser identity is known before its host process supplies the CDP port.
1818
+ const plan = buildExecutionPlan(compiledElectron?.action === "launch" ? ["connect"] : toolArgs, {
1819
+ freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1820
+ managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionName, managedSessionNamespace,
1821
+ sessionMode: compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto",
1822
+ stdin: resolvedInput.toolStdin,
1823
+ });
1824
+ const run = (executionSignal = signal) => {
1825
+ const execute = () => withLaunchDefaults
1826
+ ? withLaunchDefaults(inactive => runBrowserCommand(inactive, executionSignal), executionSignal)
1827
+ : runBrowserCommand(undefined, executionSignal);
1828
+ if (!commandTouchesArtifactLifecycle(toolArgs, resolvedInput.toolStdin, outputPath))
1829
+ return execute();
1830
+ return artifactExecutionQueue.run(async () => {
1831
+ const error = getArtifactPreflightValidationError({ activeRecordingReservations: activeRecordingReservations.values(), args: toolArgs, cwd: operationCwd, outputPath, stdin: resolvedInput.toolStdin });
1832
+ if (!error)
1833
+ return execute();
1834
+ flushRecordingReservations();
1835
+ return warnRecordingPersistence(buildValidationFailureResult({ ...resolvedInput, attemptedKind: resolvedInput.kind, kind: "invalid", status: "invalid", validationError: error }));
1836
+ });
1837
+ };
1838
+ if (plan.validationError || (!plan.sessionName && plan.commandInfo.command !== "read" && !closesAllSessions))
1839
+ return run();
1840
+ try {
1841
+ const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace));
1842
+ const identities = [await resolveBrowserExecutionIdentity({ namespace: plan.namespace, ownedManagedSession: owned, sessionName: closesAllSessions ? undefined : plan.sessionName ?? "default" })];
1843
+ if (managedSessionActive && (params.sessionMode === "fresh" || compiledElectron?.action === "launch") && !explicitSessionName) {
1844
+ identities.push(await resolveBrowserExecutionIdentity({ namespace: managedSessionNamespace, ownedManagedSession: true, sessionName: managedSessionName }));
1845
+ }
1846
+ const activePageUrl = sessionPageState.get(getAgentBrowserSessionIdentityKey(plan.sessionName ?? "default", plan.namespace)).tabTarget?.url;
1847
+ const deadline = Date.now() + (params.timeoutMs ?? getCommandAwareProcessTimeoutMs(extractUpstreamCommandTokens(toolArgs), resolvedInput.toolStdin, activePageUrl) ?? getAgentBrowserProcessTimeoutMs());
1848
+ return await withBrowserExecutionLocks({ identities, signal, deadline, waitOnly: true }, run);
1830
1849
  }
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);
1850
+ catch (error) {
1851
+ return browserExecutionFailure(error, signal);
1844
1852
  }
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,
1853
+ };
1854
+ const runWithinSessionQueue = () => {
1855
+ if (closesAllSessions)
1856
+ return managedSessionExecutionQueue.run(() => {
1857
+ const plan = buildExecutionPlan(toolArgs, {
1858
+ freshSessionName: createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1),
1859
+ managedSessionActive,
1860
+ managedSessionCompatibilityWorkaround,
1861
+ managedSessionName,
1862
+ managedSessionNamespace,
1863
+ sessionMode: params.sessionMode ?? "auto",
1864
+ stdin: resolvedInput.toolStdin,
1865
+ });
1866
+ return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
1870
1867
  });
1871
- return callerOwnedSessionExecutionQueues.runExclusive(plan.namespace, runWithLaunchDefaults);
1872
- });
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))
1868
+ if (serializeBrowserCommand)
1869
+ return managedSessionExecutionQueue.run(runWithLaunchDefaults, signal);
1870
+ return callerOwnedSessionQueueKey
1871
+ ? callerOwnedSessionExecutionQueues.run(callerOwnedSessionQueueKey, callerOwnedSessionNamespace, runWithLaunchDefaults, signal)
1872
+ : runWithLaunchDefaults();
1873
+ };
1880
1874
  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,
1888
- });
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
- }));
1875
+ }, managedSessionActive || freshSessionOrdinal > 0 || params.sessionMode === "fresh" || pendingReadConfirmation ? undefined : {
1876
+ id: rootSessionId,
1877
+ profile: rootProfile?.policy === "always" && !/[\\/~]/.test(rootProfile.name) ? rootProfile.name : undefined,
1878
+ executablePath: runtimeBrowserConfig.trustedBrowserExecutablePath,
1901
1879
  });
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
- });
1880
+ };
1881
+ if (!managedAtAdmission)
1882
+ return executeAdmitted();
1883
+ return managedSessionExecutionQueue.run(executeAdmitted, signal);
1907
1884
  },
1908
1885
  };
1909
- pi.registerTool(beforeExecute ? { ...agentBrowserTool, executionMode: "sequential" } : agentBrowserTool);
1886
+ const executeCode = async (toolCallId, params, signal, _onUpdate, ctx) => {
1887
+ if (!ctx.sessionManager.getSessionFile())
1888
+ return browserExecutionFailure(new Error("agent_browser_code requires a persisted Pi session for ordered browser-state recovery; relaunch without --no-session."), signal, "validation-error");
1889
+ let operationCwd;
1890
+ try {
1891
+ operationCwd = resolveExecutionCwd(pi, ctx);
1892
+ }
1893
+ catch (error) {
1894
+ return browserExecutionFailure(error, signal, "validation-error");
1895
+ }
1896
+ const controller = new AbortController();
1897
+ const abort = () => controller.abort(signal?.reason);
1898
+ signal?.addEventListener("abort", abort, { once: true });
1899
+ if (signal?.aborted)
1900
+ abort();
1901
+ activeScriptControllers.add(controller);
1902
+ let finish;
1903
+ const execution = new Promise(resolve => { finish = resolve; });
1904
+ activeScriptExecutions.add(execution);
1905
+ const deadline = Date.now() + (params.timeoutMs ?? AGENT_BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS);
1906
+ const deadlineTimer = setTimeout(() => controller.abort(new DOMException("Browser code deadline exceeded.", "TimeoutError")), Math.max(0, deadline - Date.now()));
1907
+ const generation = branchRestoreGeneration;
1908
+ const output = createBrowserCodeOutput();
1909
+ const nativeEnvironment = getAgentBrowserProcessEnvironment();
1910
+ try {
1911
+ const args = [
1912
+ ...(params.namespace !== undefined ? ["--namespace", params.namespace] : []),
1913
+ ...(params.session !== undefined ? ["--session", params.session] : []),
1914
+ "get", "url",
1915
+ ];
1916
+ const input = resolveAgentBrowserInput({ params: { args }, getBatchPreflightValidationError: () => undefined });
1917
+ if (input.status === "invalid")
1918
+ return buildValidationFailureResult(input);
1919
+ const config = loadAgentBrowserConfigSync({ cwd: ctx.cwd, includeProjectConfig: shouldIncludeProjectConfig(ctx) });
1920
+ const profile = config.trustedBrowserDefaultProfile;
1921
+ const rootId = process.env.PI_SUBAGENT_CHILD === "1" && process.env.PI_SUBAGENT_ROOT_SESSION_ID
1922
+ ? process.env.PI_SUBAGENT_ROOT_SESSION_ID : ctx.sessionManager.getSessionId();
1923
+ const runAdmitted = async () => {
1924
+ const owner = params.session ? ownedManagedSessions.get(getAgentBrowserSessionIdentityKey(params.session, params.namespace ?? nativeEnvironment.AGENT_BROWSER_NAMESPACE)) : undefined;
1925
+ const browserCwd = nativeEnvironment.AGENT_BROWSER_CONFIG !== undefined ? operationCwd
1926
+ : params.session ? owner?.cwd ?? ctx.cwd : managedSessionActive ? managedSessionCwd : ctx.cwd;
1927
+ const cwdError = getBrowserCwdError(browserCwd);
1928
+ if (cwdError)
1929
+ return browserExecutionFailure(new Error(cwdError), controller.signal, "validation-error");
1930
+ return withNativeSessionDefaults(input, browserCwd, controller.signal, async (input) => {
1931
+ const plan = buildExecutionPlan(input.toolArgs, { freshSessionName: managedSessionName, managedSessionActive, managedSessionName, managedSessionNamespace, sessionMode: "auto" });
1932
+ const sessionName = plan.sessionName ?? "default";
1933
+ const namespace = plan.namespace || undefined;
1934
+ const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
1935
+ const owned = plan.managedSessionName !== undefined || ownedManagedSessions.has(key);
1936
+ const run = async () => {
1937
+ const identity = await resolveBrowserExecutionIdentity({ sessionName, namespace, ownedManagedSession: owned });
1938
+ return withBrowserExecutionLock({ identity, deadline, signal: controller.signal }, async (executionSignal) => {
1939
+ let journalFailed = false;
1940
+ const run = await runAgentBrowserScript({
1941
+ code: params.code, signal: executionSignal, timeoutMs: Math.max(1, deadline - Date.now()),
1942
+ emitImage: output.emitImage,
1943
+ dispatch: async (inner, innerSignal) => {
1944
+ if (journalFailed)
1945
+ throw new Error("Browser-state persistence failed. Inspect the current page before continuing in another tool call.");
1946
+ const { args } = bindBrowserCodeCall(inner, { sessionName, namespace });
1947
+ if (plan.usedImplicitSession && !managedSessionActive && managedSessionName === sessionName && extractExplicitSessionName(inner.args) === undefined)
1948
+ args.splice(args.indexOf("--session"), 2);
1949
+ const pending = { args: redactInvocationArgs(args), sessionName, namespace, resultCategory: "failure", sessionTabTargetUnknown: true };
1950
+ try {
1951
+ appendBrowserTransition(pi, toolCallId, pending, true);
1952
+ }
1953
+ catch {
1954
+ journalFailed = true;
1955
+ throw new Error("Could not persist browser-state intent; the browser command was not run.");
1956
+ }
1957
+ // Resolve launch defaults from the real command, never the routing-only get-url probe.
1958
+ const result = await withAgentBrowserProcessEnvironment({ AGENT_BROWSER_SESSION: nativeEnvironment.AGENT_BROWSER_SESSION }, () => agentBrowserTool.execute(toolCallId, { args, stdin: inner.stdin, timeoutMs: Math.min(inner.timeoutMs ?? deadline - Date.now(), Math.max(1, deadline - Date.now())) }, innerSignal, undefined, ctx, toolCallId, operationCwd, false));
1959
+ const page = sessionPageState.get(key);
1960
+ const details = { ...(isRecord(result.details) ? result.details : {}), sessionName, namespace, sessionTabTarget: page.tabTarget, sessionTabTargetUnknown: page.tabTargetUnknown, sessionTabReopenPending: page.tabReopenPending, refSnapshot: page.refSnapshot, refSnapshotInvalidation: page.refSnapshotInvalidation };
1961
+ if (generation === branchRestoreGeneration) {
1962
+ try {
1963
+ appendBrowserTransition(pi, toolCallId, details, result.isError === true);
1964
+ }
1965
+ catch {
1966
+ journalFailed = true;
1967
+ throw new Error("The browser command finished, but its state could not be persisted. Inspect before retrying; changes may already have happened.");
1968
+ }
1969
+ }
1970
+ return output.observe(result);
1971
+ },
1972
+ });
1973
+ const result = await output.finish(run, sessionName, namespace);
1974
+ if (artifactManifest && isRecord(result.details))
1975
+ result.details.artifactManifest = redactSensitiveValue(artifactManifest);
1976
+ if (!params.outputPath)
1977
+ return result;
1978
+ return artifactExecutionQueue.run(async () => {
1979
+ const error = getArtifactPreflightValidationError({ args: [], cwd: operationCwd, outputPath: params.outputPath, activeRecordingReservations: activeRecordingReservations.values() });
1980
+ if (error) {
1981
+ const failure = browserExecutionFailure(new Error(error), undefined, "validation-error");
1982
+ return { ...result, isError: true, content: [...failure.content, ...result.content], details: { ...(isRecord(result.details) ? result.details : {}), ...(isRecord(failure.details) ? failure.details : {}) } };
1983
+ }
1984
+ return applyAgentBrowserOutputPath({ cwd: operationCwd, outputPath: params.outputPath, preserveTextContent: true, result });
1985
+ });
1986
+ });
1987
+ };
1988
+ return shouldSerializeBrowserCommand({ namespace, explicitSessionName: sessionName, managedSessionName, ownedElectronLaunchRecords, ownedManagedSessions })
1989
+ ? managedSessionExecutionQueue.run(run, controller.signal)
1990
+ : callerOwnedSessionExecutionQueues.run(key, namespace, run, controller.signal);
1991
+ }, managedSessionActive || freshSessionOrdinal > 0 ? undefined : { id: rootId, profile: profile?.policy === "always" && !/[\\/~]/.test(profile.name) ? profile.name : undefined, executablePath: config.trustedBrowserExecutablePath });
1992
+ };
1993
+ return await (!params.session && nativeEnvironment.AGENT_BROWSER_CONFIG === undefined
1994
+ ? managedSessionExecutionQueue.run(runAdmitted, controller.signal) : runAdmitted());
1995
+ }
1996
+ catch (error) {
1997
+ return browserExecutionFailure(error, controller.signal);
1998
+ }
1999
+ finally {
2000
+ clearTimeout(deadlineTimer);
2001
+ activeScriptControllers.delete(controller);
2002
+ signal?.removeEventListener("abort", abort);
2003
+ activeScriptExecutions.delete(execution);
2004
+ finish();
2005
+ }
2006
+ };
2007
+ const finalizeObservation = async (result, params, ctx) => {
2008
+ if ("args" in params && params.args && isPlainTextInspectionArgs(params.args))
2009
+ return result;
2010
+ const { renderAgentBrowserObservation } = await import("./lib/results/presentation/large-output.js");
2011
+ const details = isRecord(result.details) ? result.details : {};
2012
+ if (result.isError && details.error === undefined)
2013
+ details.error = details.validationError ?? details.summary ?? result.content.filter(item => item.type === "text").map(item => item.text).join("\n");
2014
+ const priorManifest = isSessionArtifactManifest(details.artifactManifest) ? details.artifactManifest : undefined;
2015
+ const rendered = await renderAgentBrowserObservation({ content: result.content, details, json: "code" in params || params.args?.includes("--json") === true, succeeded: result.isError !== true, persistentArtifactStore: getPersistentSessionArtifactStore(ctx) });
2016
+ artifactManifest = mergeBrowserRunArtifactManifest(artifactManifest, priorManifest, rendered.artifactManifest);
2017
+ return { ...result, content: rendered.content, details: { ...details, ...(artifactManifest ? { artifactManifest: redactSensitiveValue(artifactManifest) } : {}) } };
2018
+ };
2019
+ registerAgentBrowserToolSurface(pi, {
2020
+ async execute(id, params, signal, onUpdate, ctx) {
2021
+ let result;
2022
+ try {
2023
+ result = await agentBrowserTool.execute(id, params, signal, onUpdate, ctx);
2024
+ }
2025
+ catch (error) {
2026
+ result = browserExecutionFailure(error, signal);
2027
+ }
2028
+ return finalizeObservation(result, params, ctx);
2029
+ },
2030
+ async executeCode(id, params, signal, onUpdate, ctx) {
2031
+ const result = await executeCode(id, params, signal, onUpdate, ctx);
2032
+ return finalizeObservation(result, params, ctx);
2033
+ },
2034
+ executionMode: beforeExecute ? "sequential" : undefined,
2035
+ renderCodeCall(args, theme, context) {
2036
+ const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
2037
+ text.setText(formatAgentBrowserRenderCall(args, theme, context.expanded));
2038
+ return text;
2039
+ },
2040
+ promptGuidelines: toolPromptGuidelines,
2041
+ renderCall: agentBrowserTool.renderCall, renderResult: agentBrowserTool.renderResult,
2042
+ });
1910
2043
  registerWebSearchToolIfAvailable(agentBrowserConfig);
1911
2044
  }