pi-agent-browser-native 0.2.48 → 0.2.50

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 (189) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +21 -11
  3. package/dist/extensions/agent-browser/index.js +808 -0
  4. package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
  5. package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
  6. package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
  7. package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
  8. package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
  9. package/dist/extensions/agent-browser/lib/config-policy.js +669 -0
  10. package/dist/extensions/agent-browser/lib/config.js +122 -0
  11. package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
  12. package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
  13. package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
  14. package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
  15. package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
  16. package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
  17. package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
  18. package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
  19. package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
  20. package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
  21. package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
  22. package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
  23. package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
  24. package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
  25. package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
  26. package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
  27. package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
  28. package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
  29. package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
  30. package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
  31. package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
  32. package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
  33. package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
  34. package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
  35. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
  36. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
  37. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
  38. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
  39. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
  40. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
  41. package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
  42. package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
  43. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
  44. package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
  45. package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
  46. package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
  47. package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
  48. package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
  49. package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
  50. package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
  51. package/dist/extensions/agent-browser/lib/playbook.js +121 -0
  52. package/dist/extensions/agent-browser/lib/process.js +363 -0
  53. package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
  54. package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
  55. package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
  56. package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
  57. package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
  58. package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
  59. package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
  60. package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
  61. package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
  62. package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
  63. package/dist/extensions/agent-browser/lib/results/network.js +73 -0
  64. package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
  65. package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
  66. package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
  67. package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
  68. package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
  69. package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
  70. package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +956 -0
  71. package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
  72. package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
  73. package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
  74. package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
  75. package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
  76. package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
  77. package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
  78. package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
  79. package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
  80. package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
  81. package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
  82. package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
  83. package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
  84. package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
  85. package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
  86. package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
  87. package/dist/extensions/agent-browser/lib/results/text.js +40 -0
  88. package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
  89. package/dist/extensions/agent-browser/lib/runtime.js +855 -0
  90. package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
  91. package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
  92. package/dist/extensions/agent-browser/lib/temp.js +498 -0
  93. package/dist/extensions/agent-browser/lib/web-search.js +562 -0
  94. package/docs/ARCHITECTURE.md +5 -5
  95. package/docs/COMMAND_REFERENCE.md +4 -4
  96. package/docs/RELEASE.md +22 -11
  97. package/docs/REQUIREMENTS.md +1 -1
  98. package/docs/SUPPORT_MATRIX.md +5 -4
  99. package/docs/TOOL_CONTRACT.md +1 -1
  100. package/package.json +9 -5
  101. package/scripts/config.mjs +14 -20
  102. package/scripts/doctor.mjs +8 -7
  103. package/extensions/agent-browser/index.ts +0 -961
  104. package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
  105. package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
  106. package/extensions/agent-browser/lib/bash-guard.ts +0 -205
  107. package/extensions/agent-browser/lib/command-policy.ts +0 -71
  108. package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
  109. package/extensions/agent-browser/lib/config-policy.js +0 -690
  110. package/extensions/agent-browser/lib/config.ts +0 -211
  111. package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
  112. package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
  113. package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
  114. package/extensions/agent-browser/lib/electron/launch.ts +0 -499
  115. package/extensions/agent-browser/lib/executable-path.ts +0 -19
  116. package/extensions/agent-browser/lib/fs-utils.ts +0 -18
  117. package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
  118. package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
  119. package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
  120. package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
  121. package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
  122. package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
  123. package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
  124. package/extensions/agent-browser/lib/input-modes.ts +0 -45
  125. package/extensions/agent-browser/lib/json-schema.ts +0 -73
  126. package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
  127. package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
  128. package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
  129. package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
  130. package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
  131. package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
  132. package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
  133. package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
  134. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
  135. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
  136. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
  137. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
  138. package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
  139. package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
  140. package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
  141. package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
  142. package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
  143. package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
  144. package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
  145. package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
  146. package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
  147. package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
  148. package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
  149. package/extensions/agent-browser/lib/playbook.ts +0 -142
  150. package/extensions/agent-browser/lib/process.ts +0 -516
  151. package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
  152. package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
  153. package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
  154. package/extensions/agent-browser/lib/results/categories.ts +0 -106
  155. package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
  156. package/extensions/agent-browser/lib/results/contracts.ts +0 -241
  157. package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
  158. package/extensions/agent-browser/lib/results/envelope.ts +0 -195
  159. package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
  160. package/extensions/agent-browser/lib/results/network.ts +0 -78
  161. package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
  162. package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
  163. package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
  164. package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
  165. package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
  166. package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
  167. package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
  168. package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
  169. package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
  170. package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
  171. package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
  172. package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
  173. package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
  174. package/extensions/agent-browser/lib/results/presentation.ts +0 -257
  175. package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
  176. package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
  177. package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
  178. package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
  179. package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
  180. package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
  181. package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
  182. package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
  183. package/extensions/agent-browser/lib/results/text.ts +0 -40
  184. package/extensions/agent-browser/lib/runtime.ts +0 -988
  185. package/extensions/agent-browser/lib/session-page-state.ts +0 -512
  186. package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
  187. package/extensions/agent-browser/lib/temp.ts +0 -577
  188. package/extensions/agent-browser/lib/web-search.ts +0 -728
  189. /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
@@ -0,0 +1,808 @@
1
+ /**
2
+ * Purpose: Register the native agent_browser tool for pi so agents can invoke agent-browser without going through bash.
3
+ * Responsibilities: Define the tool schema, inject thin wrapper behavior around the upstream CLI, manage extension-owned browser session convenience, and return pi-friendly content/details.
4
+ * Scope: Native tool registration and orchestration only; the wrapper intentionally stays close to the upstream agent-browser CLI.
5
+ * Usage: Loaded by pi through the package manifest in this package, or explicitly via `pi --no-extensions -e .` during local checkout development.
6
+ * Invariants/Assumptions: agent-browser is installed separately on PATH, the wrapper targets the current locally installed upstream version only, and no backward-compatibility shims are provided.
7
+ */
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { Text } from "@earendil-works/pi-tui";
11
+ import { PROJECT_RULE_PROMPT, buildBrowserDefaultProfileGuideline, buildBrowserExecutablePathGuideline, buildToolPromptGuidelines, } from "./lib/playbook.js";
12
+ import { SessionPageState } from "./lib/session-page-state.js";
13
+ import { createEphemeralSessionSeed, createFreshSessionName, createImplicitSessionName, extractCommandTokens, getImplicitSessionCloseTimeoutMs, getImplicitSessionIdleTimeoutMs, extractExplicitSessionName, restoreManagedSessionStateFromBranch, validateToolArgs, } from "./lib/runtime.js";
14
+ import { isRecord } from "./lib/parsing.js";
15
+ import { buildPromptPolicy, getLatestUserPrompt, shouldAppendBrowserSystemPrompt } from "./lib/prompt-policy.js";
16
+ import { isCloseCommand } from "./lib/command-taxonomy.js";
17
+ import { cleanupSecureTempArtifacts, } from "./lib/temp.js";
18
+ import { AGENT_BROWSER_PARAMS, } from "./lib/input-modes.js";
19
+ import { parseAllowedDomainsPolicyFromArgs } from "./lib/navigation-policy.js";
20
+ import { closeManagedSession, runAgentBrowserTool } from "./lib/orchestration/browser-run.js";
21
+ import { findElectronLaunchRecordForSession, getActiveElectronRecords } from "./lib/orchestration/browser-run/session-state.js";
22
+ import { parseBatchStdinJsonArray } from "./lib/orchestration/batch-stdin.js";
23
+ import { ELECTRON_POST_COMMAND_STATUS_SETTLE_MS, ELECTRON_PROFILE_ISOLATION_DETAILS, cleanupActiveElectronHostLaunches, handleElectronHostInput, restoreElectronLaunchRecordsFromBranch, } from "./lib/orchestration/electron-host/index.js";
24
+ import { buildValidationFailureResult, resolveAgentBrowserInput } from "./lib/orchestration/input-plan.js";
25
+ import { applyAgentBrowserOutputPath } from "./lib/orchestration/output-file.js";
26
+ import { isSessionArtifactManifest, } from "./lib/results/artifact-manifest.js";
27
+ import { canRegisterWebSearchTool, loadAgentBrowserConfigSync } from "./lib/config.js";
28
+ import { createAgentBrowserWebSearchTool } from "./lib/web-search.js";
29
+ import { isDirectAgentBrowserBashAllowed, isHarmlessAgentBrowserInspectionCommand, looksLikeDirectAgentBrowserBash, } from "./lib/bash-guard.js";
30
+ import { AgentBrowserResultComponent, buildAgentBrowserToolResultPatch, formatAgentBrowserRenderCall, formatAgentBrowserRenderResult, } from "./lib/pi-tool-rendering.js";
31
+ const DEFAULT_SESSION_MODE = "auto";
32
+ function isBashToolCallEvent(event) {
33
+ if (!isRecord(event) || event.toolName !== "bash" || !isRecord(event.input))
34
+ return false;
35
+ return typeof event.input.command === "string";
36
+ }
37
+ function getBatchPreflightValidationError(args, stdin) {
38
+ const commandTokens = extractCommandTokens(args);
39
+ if (commandTokens[0] !== "batch" || stdin === undefined) {
40
+ return undefined;
41
+ }
42
+ const parsed = parseBatchStdinJsonArray(stdin);
43
+ if (parsed.error || parsed.steps === undefined) {
44
+ return undefined;
45
+ }
46
+ for (const [index, step] of parsed.steps.entries()) {
47
+ if (!Array.isArray(step) || !step.every((token) => typeof token === "string") || step.length === 0)
48
+ continue;
49
+ const stepValidationError = validateToolArgs(step);
50
+ if (stepValidationError)
51
+ return `Unsupported batch step ${index + 1}: ${stepValidationError}`;
52
+ if (step[0] === "screenshot" && step.includes("--annotate")) {
53
+ return [
54
+ `Unsupported batch screenshot annotation in step ${index + 1}: put --annotate in top-level args, not inside the batch step.`,
55
+ `Use: { "args": ["--annotate", "batch"], "stdin": "[[\\"screenshot\\",\\"/path/to/image.png\\"]]" }`,
56
+ ].join("\n");
57
+ }
58
+ }
59
+ return undefined;
60
+ }
61
+ function restoreArtifactManifestFromBranch(branch) {
62
+ let restoredManifest;
63
+ for (const entry of branch) {
64
+ if (!isRecord(entry) || entry.type !== "message")
65
+ continue;
66
+ const message = isRecord(entry.message) ? entry.message : undefined;
67
+ if (!message || message.toolName !== "agent_browser")
68
+ continue;
69
+ const details = isRecord(message.details) ? message.details : undefined;
70
+ if (isSessionArtifactManifest(details?.artifactManifest)) {
71
+ restoredManifest = details.artifactManifest;
72
+ }
73
+ }
74
+ return restoredManifest;
75
+ }
76
+ function getToolResultArgs(details) {
77
+ if (Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string"))
78
+ return details.args;
79
+ if (Array.isArray(details.effectiveArgs) && details.effectiveArgs.every((arg) => typeof arg === "string"))
80
+ return details.effectiveArgs;
81
+ return [];
82
+ }
83
+ function restoreAllowedDomainsBySessionFromBranch(branch) {
84
+ const restoredPolicies = new Map();
85
+ for (const entry of branch) {
86
+ if (!isRecord(entry) || entry.type !== "message")
87
+ continue;
88
+ const message = isRecord(entry.message) ? entry.message : undefined;
89
+ if (!message || message.toolName !== "agent_browser")
90
+ continue;
91
+ const details = isRecord(message.details) ? message.details : undefined;
92
+ if (!details)
93
+ continue;
94
+ const succeeded = getSuccessfulToolResult(details, message);
95
+ const args = getToolResultArgs(details);
96
+ const command = typeof details.command === "string" ? details.command : extractCommandTokens(args)[0];
97
+ const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
98
+ const explicitSessionName = extractExplicitSessionName(args);
99
+ const outcome = getManagedSessionOutcome(details);
100
+ const outcomeSucceeded = outcome?.succeeded === true;
101
+ const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
102
+ const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
103
+ const outcomeAttemptedSessionName = typeof outcome?.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
104
+ if (outcomeSucceeded && outcomeStatus === "closed") {
105
+ const closedSessionName = outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName;
106
+ if (closedSessionName)
107
+ restoredPolicies.delete(closedSessionName);
108
+ }
109
+ if (outcomeSucceeded && outcomeStatus === "replaced") {
110
+ const replacedSessionName = typeof outcome.replacedSessionName === "string" ? outcome.replacedSessionName : undefined;
111
+ if (replacedSessionName)
112
+ restoredPolicies.delete(replacedSessionName);
113
+ }
114
+ if (succeeded && isCloseCommand(command)) {
115
+ const closedSessionName = explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName;
116
+ if (closedSessionName)
117
+ restoredPolicies.delete(closedSessionName);
118
+ }
119
+ const electron = isRecord(details.electron) ? details.electron : undefined;
120
+ const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
121
+ const cleanupResults = Array.isArray(cleanup?.results) ? cleanup.results : [];
122
+ for (const cleanupResult of cleanupResults) {
123
+ for (const closedSessionName of getCleanupResultClosedManagedSessionNames(cleanupResult))
124
+ restoredPolicies.delete(closedSessionName);
125
+ }
126
+ const outcomeKeepsSessionCurrent = outcome?.activeAfter === true
127
+ && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")
128
+ && outcomeCurrentSessionName === sessionName;
129
+ const policy = (succeeded || outcomeKeepsSessionCurrent) && sessionName && !isCloseCommand(command) ? parseAllowedDomainsPolicyFromArgs(args) : undefined;
130
+ if (policy && sessionName)
131
+ restoredPolicies.set(sessionName, policy);
132
+ }
133
+ return restoredPolicies;
134
+ }
135
+ function trackOwnedManagedSession(sessions, sessionName, cwd, options = {}) {
136
+ if (!sessionName)
137
+ return;
138
+ const existing = sessions.get(sessionName);
139
+ const branchOwned = existing && !existing.branchOwned ? false : options.branchOwned === true;
140
+ sessions.set(sessionName, { branchOwned, cwd });
141
+ }
142
+ function untrackOwnedManagedSession(sessions, sessionName) {
143
+ if (sessionName)
144
+ sessions.delete(sessionName);
145
+ }
146
+ function untrackOwnedManagedSessionFromBranchClose(sessions, sessionName, activeBranchRank, closeBranchRank) {
147
+ if (!sessionName || closeBranchRank === undefined)
148
+ return;
149
+ const ownedSession = sessions.get(sessionName);
150
+ if (!ownedSession?.branchOwned)
151
+ return;
152
+ if (activeBranchRank !== undefined && closeBranchRank <= activeBranchRank)
153
+ return;
154
+ sessions.delete(sessionName);
155
+ }
156
+ function syncOwnedManagedSessionsFromResult(sessions, result, cwd) {
157
+ const details = isRecord(result.details) ? result.details : undefined;
158
+ const outcome = isRecord(details?.managedSessionOutcome) ? details.managedSessionOutcome : undefined;
159
+ if (!outcome)
160
+ return;
161
+ const succeeded = outcome.succeeded === true;
162
+ const status = typeof outcome.status === "string" ? outcome.status : undefined;
163
+ const currentSessionName = typeof outcome.currentSessionName === "string" ? outcome.currentSessionName : undefined;
164
+ const attemptedSessionName = typeof outcome.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
165
+ if (outcome.activeAfter === true && (status === "created" || status === "replaced" || status === "unchanged")) {
166
+ trackOwnedManagedSession(sessions, currentSessionName, cwd);
167
+ }
168
+ if (succeeded && status === "closed") {
169
+ untrackOwnedManagedSession(sessions, attemptedSessionName ?? currentSessionName);
170
+ }
171
+ }
172
+ function getTouchedElectronLaunchIds(sessionName, records) {
173
+ const record = findElectronLaunchRecordForSession(sessionName, records);
174
+ return record ? new Set([record.launchId]) : undefined;
175
+ }
176
+ function mergeActiveElectronLaunchRecords(target, source, options = {}) {
177
+ for (const record of getActiveElectronRecords(source)) {
178
+ const alreadyRuntimeOwned = target.has(record.launchId) && options.branchOwnedLaunchIds?.has(record.launchId) === false;
179
+ target.set(record.launchId, record);
180
+ if (options.branchOwnedLaunchIds) {
181
+ if (alreadyRuntimeOwned) {
182
+ // Already runtime-owned from a prior live result; keep it that way.
183
+ }
184
+ else if (options.markBranchOwned === true) {
185
+ options.branchOwnedLaunchIds.add(record.launchId);
186
+ }
187
+ else if (options.touchedLaunchIds?.has(record.launchId)) {
188
+ options.branchOwnedLaunchIds.delete(record.launchId);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ function removeInactiveOwnedElectronLaunchRecords(target, branchOwnedLaunchIds, source, activeBranchRanks, cleanupBranchRanks) {
194
+ const activeLaunchIds = new Set(getActiveElectronRecords(source).map((record) => record.launchId));
195
+ const launchIds = new Set([...source.keys(), ...cleanupBranchRanks.keys()]);
196
+ for (const launchId of launchIds) {
197
+ if (!target.has(launchId) || !branchOwnedLaunchIds.has(launchId))
198
+ continue;
199
+ const activeBranchRank = activeBranchRanks.get(launchId);
200
+ const cleanupBranchRank = cleanupBranchRanks.get(launchId);
201
+ const restoredInactiveRecord = source.has(launchId) && !activeLaunchIds.has(launchId);
202
+ const cleanupIsLatest = cleanupBranchRank !== undefined && (activeBranchRank === undefined || cleanupBranchRank > activeBranchRank);
203
+ if (!restoredInactiveRecord && !cleanupIsLatest)
204
+ continue;
205
+ target.delete(launchId);
206
+ branchOwnedLaunchIds.delete(launchId);
207
+ }
208
+ }
209
+ function mergeElectronLaunchRecordMaps(...maps) {
210
+ const merged = new Map();
211
+ for (const map of maps) {
212
+ for (const [launchId, record] of map)
213
+ merged.set(launchId, record);
214
+ }
215
+ return merged;
216
+ }
217
+ function replaceWithActiveElectronLaunchRecords(target, source, branchOwnedLaunchIds, cleanedLaunchIds) {
218
+ target.clear();
219
+ if (branchOwnedLaunchIds) {
220
+ if (cleanedLaunchIds) {
221
+ for (const launchId of cleanedLaunchIds)
222
+ branchOwnedLaunchIds.delete(launchId);
223
+ }
224
+ else {
225
+ branchOwnedLaunchIds.clear();
226
+ }
227
+ }
228
+ mergeActiveElectronLaunchRecords(target, source, branchOwnedLaunchIds ? { branchOwnedLaunchIds } : {});
229
+ }
230
+ function shouldSerializeElectronHostInput(compiledElectron) {
231
+ return compiledElectron?.action === "status" || compiledElectron?.action === "probe" || compiledElectron?.action === "cleanup";
232
+ }
233
+ function getElectronHostLaunchRecordsForInput(options) {
234
+ if (options.compiledElectron?.action === "status" ||
235
+ options.compiledElectron?.action === "cleanup" ||
236
+ (options.compiledElectron?.action === "probe" && options.compiledElectron.launchId)) {
237
+ return mergeElectronLaunchRecordMaps(options.branchRecords, options.ownedRecords);
238
+ }
239
+ return options.branchRecords;
240
+ }
241
+ function getCleanupResultClosedManagedSessionNames(result) {
242
+ if (!isRecord(result) || !Array.isArray(result.steps))
243
+ return [];
244
+ const closedSessionNames = new Set();
245
+ const record = isRecord(result.record) ? result.record : undefined;
246
+ for (const step of result.steps) {
247
+ if (!isRecord(step) || step.resource !== "managed-session")
248
+ continue;
249
+ if (step.state !== "removed" && step.state !== "already-gone")
250
+ continue;
251
+ const sessionName = typeof step.sessionName === "string"
252
+ ? step.sessionName
253
+ : typeof record?.sessionName === "string" ? record.sessionName : undefined;
254
+ if (sessionName)
255
+ closedSessionNames.add(sessionName);
256
+ }
257
+ return [...closedSessionNames];
258
+ }
259
+ function getCleanupResultsClosedManagedSessionNames(cleanupResults) {
260
+ const closedSessionNames = new Set();
261
+ for (const result of cleanupResults) {
262
+ for (const sessionName of getCleanupResultClosedManagedSessionNames(result))
263
+ closedSessionNames.add(sessionName);
264
+ }
265
+ return [...closedSessionNames];
266
+ }
267
+ function isElectronLaunchRecord(value) {
268
+ if (!isRecord(value))
269
+ return false;
270
+ return value.version === 1
271
+ && value.launchedByWrapper === true
272
+ && typeof value.launchId === "string"
273
+ && typeof value.appName === "string"
274
+ && typeof value.executablePath === "string"
275
+ && typeof value.userDataDir === "string"
276
+ && typeof value.port === "number"
277
+ && typeof value.createdAtMs === "number";
278
+ }
279
+ function getCleanupResultsElectronRecords(cleanupResults) {
280
+ return cleanupResults
281
+ .map((result) => isRecord(result) ? result.record : undefined)
282
+ .filter(isElectronLaunchRecord);
283
+ }
284
+ function mergeElectronCleanupRecords(target, cleanupResults) {
285
+ for (const record of getCleanupResultsElectronRecords(cleanupResults)) {
286
+ target.set(record.launchId, record);
287
+ }
288
+ }
289
+ function getManagedSessionOutcome(details) {
290
+ return isRecord(details.managedSessionOutcome) ? details.managedSessionOutcome : undefined;
291
+ }
292
+ function getSuccessfulToolResult(details, message) {
293
+ const messageIsError = typeof message.isError === "boolean" ? message.isError : undefined;
294
+ const exitCode = typeof details.exitCode === "number" ? details.exitCode : undefined;
295
+ return messageIsError === undefined ? exitCode === undefined || exitCode === 0 : !messageIsError;
296
+ }
297
+ function setBranchRankForString(map, value, rank) {
298
+ if (typeof value === "string" && value.length > 0)
299
+ map.set(value, rank);
300
+ }
301
+ function collectBranchManagedResourceEvents(branch) {
302
+ const events = {
303
+ electronLaunchActiveRanks: new Map(),
304
+ electronLaunchCleanupRanks: new Map(),
305
+ managedSessionActiveRanks: new Map(),
306
+ managedSessionCloseRanks: new Map(),
307
+ };
308
+ let eventRank = 0;
309
+ for (const entry of branch) {
310
+ if (!isRecord(entry) || entry.type !== "message")
311
+ continue;
312
+ const message = isRecord(entry.message) ? entry.message : undefined;
313
+ if (!message || message.toolName !== "agent_browser")
314
+ continue;
315
+ const details = isRecord(message.details) ? message.details : undefined;
316
+ if (!details)
317
+ continue;
318
+ eventRank += 1;
319
+ const succeeded = getSuccessfulToolResult(details, message);
320
+ const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
321
+ const command = typeof details.command === "string" ? details.command : extractCommandTokens(args)[0];
322
+ const sessionName = typeof details.sessionName === "string" ? details.sessionName : undefined;
323
+ const sessionMode = details.sessionMode === "fresh" || details.sessionMode === "auto" ? details.sessionMode : undefined;
324
+ const usedImplicitSession = details.usedImplicitSession === true;
325
+ const explicitSessionName = extractExplicitSessionName(args);
326
+ const outcome = getManagedSessionOutcome(details);
327
+ const outcomeSucceeded = outcome?.succeeded === true;
328
+ const outcomeStatus = typeof outcome?.status === "string" ? outcome.status : undefined;
329
+ const outcomeCurrentSessionName = typeof outcome?.currentSessionName === "string" ? outcome.currentSessionName : undefined;
330
+ const outcomeAttemptedSessionName = typeof outcome?.attemptedSessionName === "string" ? outcome.attemptedSessionName : undefined;
331
+ if (outcomeSucceeded && outcome.activeAfter === true && (outcomeStatus === "created" || outcomeStatus === "replaced" || outcomeStatus === "unchanged")) {
332
+ setBranchRankForString(events.managedSessionActiveRanks, outcomeCurrentSessionName, eventRank);
333
+ }
334
+ if (outcomeSucceeded && outcomeStatus === "closed") {
335
+ setBranchRankForString(events.managedSessionCloseRanks, outcomeAttemptedSessionName ?? outcomeCurrentSessionName ?? sessionName, eventRank);
336
+ }
337
+ if (outcomeSucceeded && outcomeStatus === "replaced") {
338
+ setBranchRankForString(events.managedSessionCloseRanks, outcome.replacedSessionName, eventRank);
339
+ }
340
+ if (succeeded && !isCloseCommand(command) && sessionName && (usedImplicitSession || sessionMode === "fresh")) {
341
+ events.managedSessionActiveRanks.set(sessionName, eventRank);
342
+ }
343
+ if (succeeded && isCloseCommand(command)) {
344
+ setBranchRankForString(events.managedSessionCloseRanks, explicitSessionName ?? sessionName ?? outcomeAttemptedSessionName ?? outcomeCurrentSessionName, eventRank);
345
+ }
346
+ const electron = isRecord(details.electron) ? details.electron : undefined;
347
+ const launch = electron && isElectronLaunchRecord(electron.launch) ? electron.launch : undefined;
348
+ if (launch && getActiveElectronRecords(new Map([[launch.launchId, launch]])).length > 0) {
349
+ events.electronLaunchActiveRanks.set(launch.launchId, eventRank);
350
+ }
351
+ const cleanup = isRecord(electron?.cleanup) ? electron.cleanup : undefined;
352
+ const cleanupRecords = Array.isArray(cleanup?.records) ? cleanup.records : [];
353
+ for (const cleanupRecord of cleanupRecords) {
354
+ if (isElectronLaunchRecord(cleanupRecord))
355
+ events.electronLaunchCleanupRanks.set(cleanupRecord.launchId, eventRank);
356
+ }
357
+ const cleanupResults = Array.isArray(cleanup?.results) ? cleanup.results : [];
358
+ for (const cleanupResult of cleanupResults) {
359
+ if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
360
+ events.electronLaunchCleanupRanks.set(cleanupResult.record.launchId, eventRank);
361
+ }
362
+ for (const closedSessionName of getCleanupResultClosedManagedSessionNames(cleanupResult)) {
363
+ events.managedSessionCloseRanks.set(closedSessionName, eventRank);
364
+ }
365
+ }
366
+ }
367
+ return events;
368
+ }
369
+ function getCleanupResultsPreservedUserDataDirs(cleanupResults) {
370
+ const userDataDirs = new Set();
371
+ for (const result of cleanupResults) {
372
+ if (!isRecord(result) || !Array.isArray(result.steps) || !isElectronLaunchRecord(result.record))
373
+ continue;
374
+ const userDataDirStep = result.steps.find((step) => isRecord(step) && step.resource === "user-data-dir");
375
+ if (!isRecord(userDataDirStep))
376
+ continue;
377
+ if (userDataDirStep.state === "skipped" || userDataDirStep.state === "failed")
378
+ userDataDirs.add(result.record.userDataDir);
379
+ }
380
+ return [...userDataDirs];
381
+ }
382
+ function syncElectronCleanupManagedSessions(sessions, cleanupResults) {
383
+ for (const sessionName of getCleanupResultsClosedManagedSessionNames(cleanupResults)) {
384
+ untrackOwnedManagedSession(sessions, sessionName);
385
+ }
386
+ }
387
+ async function closeOwnedManagedSessionsExcept(sessions, keepSessionName, timeoutMs) {
388
+ for (const [sessionName, owner] of [...sessions]) {
389
+ if (sessionName === keepSessionName)
390
+ continue;
391
+ const error = await closeManagedSession({ cwd: owner.cwd, sessionName, timeoutMs });
392
+ if (!error)
393
+ sessions.delete(sessionName);
394
+ }
395
+ }
396
+ async function closeOwnedManagedSessions(sessions, timeoutMs) {
397
+ await closeOwnedManagedSessionsExcept(sessions, undefined, timeoutMs);
398
+ }
399
+ function getOffBranchOwnedElectronLaunchRecords(ownedRecords, branchRecords) {
400
+ const activeBranchLaunchIds = new Set(getActiveElectronRecords(branchRecords).map((record) => record.launchId));
401
+ const offBranchRecords = new Map();
402
+ for (const record of getActiveElectronRecords(ownedRecords)) {
403
+ if (!activeBranchLaunchIds.has(record.launchId))
404
+ offBranchRecords.set(record.launchId, record);
405
+ }
406
+ return offBranchRecords;
407
+ }
408
+ function shouldSerializeBrowserCommand(options) {
409
+ if (!options.explicitSessionName)
410
+ return true;
411
+ if (options.explicitSessionName === options.managedSessionName)
412
+ return true;
413
+ if (options.ownedManagedSessions.has(options.explicitSessionName))
414
+ return true;
415
+ return getActiveElectronRecords(options.ownedElectronLaunchRecords).some((record) => record.sessionName === options.explicitSessionName);
416
+ }
417
+ // Serializes managed-session read/modify/write work so overlapping tool calls cannot promote stale state or close an in-use session.
418
+ class AsyncExecutionQueue {
419
+ tail = Promise.resolve();
420
+ run(work) {
421
+ const previous = this.tail;
422
+ let release;
423
+ this.tail = new Promise((resolve) => {
424
+ release = resolve;
425
+ });
426
+ return (async () => {
427
+ await previous;
428
+ try {
429
+ return await work();
430
+ }
431
+ finally {
432
+ release();
433
+ }
434
+ })();
435
+ }
436
+ }
437
+ function getInstalledDocsPaths() {
438
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
439
+ return {
440
+ readmePath: join(packageRoot, "README.md"),
441
+ commandReferencePath: join(packageRoot, "docs", "COMMAND_REFERENCE.md"),
442
+ toolContractPath: join(packageRoot, "docs", "TOOL_CONTRACT.md"),
443
+ };
444
+ }
445
+ function hasArgvFlag(argv, longFlag, shortFlag) {
446
+ return argv.includes(longFlag) || argv.includes(shortFlag);
447
+ }
448
+ function shouldIncludeProjectConfig(ctx, argv = process.argv) {
449
+ if (hasArgvFlag(argv, "--no-approve", "-na"))
450
+ return false;
451
+ return ctx?.isProjectTrusted?.() ?? true;
452
+ }
453
+ export default function agentBrowserExtension(pi) {
454
+ const ephemeralSessionSeed = createEphemeralSessionSeed();
455
+ const agentBrowserConfig = loadAgentBrowserConfigSync({
456
+ cwd: process.cwd(),
457
+ includeProjectConfig: false,
458
+ });
459
+ const webSearchToolAvailable = canRegisterWebSearchTool(agentBrowserConfig);
460
+ const toolPromptGuidelines = buildToolPromptGuidelines({
461
+ browserDefaultProfile: agentBrowserConfig.trustedBrowserDefaultProfile,
462
+ browserExecutablePath: agentBrowserConfig.trustedBrowserExecutablePath,
463
+ includeWebSearch: webSearchToolAvailable,
464
+ docs: getInstalledDocsPaths(),
465
+ });
466
+ const implicitSessionIdleTimeoutMs = String(getImplicitSessionIdleTimeoutMs());
467
+ const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
468
+ let webSearchToolRegistered = false;
469
+ let managedSessionActive = false;
470
+ let managedSessionBaseName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
471
+ let managedSessionName = managedSessionBaseName;
472
+ let managedSessionCwd = process.cwd();
473
+ let freshSessionOrdinal = 0;
474
+ let sessionPageState = new SessionPageState();
475
+ let traceOwners = new Map();
476
+ let artifactManifest;
477
+ let allowedDomainsBySession = new Map();
478
+ let networkRoutesBySession = new Map();
479
+ let electronLaunchRecords = new Map();
480
+ let ownedElectronLaunchRecords = new Map();
481
+ let branchOwnedElectronLaunchIds = new Set();
482
+ let electronChildProcesses = new Map();
483
+ const ownedManagedSessions = new Map();
484
+ const managedSessionExecutionQueue = new AsyncExecutionQueue();
485
+ let branchStateGeneration = 0;
486
+ const clearSessionScopedBrowserState = (sessionName) => {
487
+ allowedDomainsBySession.delete(sessionName);
488
+ networkRoutesBySession.delete(sessionName);
489
+ sessionPageState.clearSession(sessionName);
490
+ };
491
+ const restoreBranchBackedState = (ctx, options) => {
492
+ branchStateGeneration += 1;
493
+ const previousManagedSessionActive = managedSessionActive;
494
+ const previousManagedSessionName = managedSessionName;
495
+ const previousFreshSessionOrdinal = freshSessionOrdinal;
496
+ managedSessionBaseName = createImplicitSessionName(ctx.sessionManager.getSessionId(), ctx.cwd, ephemeralSessionSeed);
497
+ const branch = ctx.sessionManager.getBranch();
498
+ const branchResourceEvents = collectBranchManagedResourceEvents(branch);
499
+ const restoredState = restoreManagedSessionStateFromBranch(branch, managedSessionBaseName);
500
+ managedSessionActive = restoredState.active;
501
+ const restoredFreshSessionOrdinal = options.resetRuntimeOwnership
502
+ ? restoredState.freshSessionOrdinal
503
+ : Math.max(previousFreshSessionOrdinal, restoredState.freshSessionOrdinal);
504
+ const shouldReservePostCloseSession = !restoredState.active && restoredState.closedSessionName === restoredState.sessionName;
505
+ const alreadyReservedPostCloseSession = shouldReservePostCloseSession
506
+ && !options.resetRuntimeOwnership
507
+ && !previousManagedSessionActive
508
+ && previousFreshSessionOrdinal > restoredState.freshSessionOrdinal
509
+ && previousFreshSessionOrdinal === restoredFreshSessionOrdinal
510
+ && previousManagedSessionName === createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, restoredFreshSessionOrdinal);
511
+ const nextFreshSessionOrdinal = shouldReservePostCloseSession && !alreadyReservedPostCloseSession
512
+ ? restoredFreshSessionOrdinal + 1
513
+ : restoredFreshSessionOrdinal;
514
+ managedSessionName = shouldReservePostCloseSession
515
+ ? alreadyReservedPostCloseSession
516
+ ? previousManagedSessionName
517
+ : createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, nextFreshSessionOrdinal)
518
+ : restoredState.sessionName;
519
+ managedSessionCwd = ctx.cwd;
520
+ freshSessionOrdinal = nextFreshSessionOrdinal;
521
+ sessionPageState = SessionPageState.fromBranch(branch);
522
+ traceOwners = new Map();
523
+ artifactManifest = restoreArtifactManifestFromBranch(branch);
524
+ allowedDomainsBySession = restoreAllowedDomainsBySessionFromBranch(branch);
525
+ networkRoutesBySession = new Map();
526
+ electronLaunchRecords = restoreElectronLaunchRecordsFromBranch(branch);
527
+ if (options.resetRuntimeOwnership) {
528
+ ownedManagedSessions.clear();
529
+ ownedElectronLaunchRecords = new Map();
530
+ branchOwnedElectronLaunchIds = new Set();
531
+ }
532
+ else {
533
+ for (const [sessionName, closeRank] of branchResourceEvents.managedSessionCloseRanks) {
534
+ untrackOwnedManagedSessionFromBranchClose(ownedManagedSessions, sessionName, branchResourceEvents.managedSessionActiveRanks.get(sessionName), closeRank);
535
+ }
536
+ removeInactiveOwnedElectronLaunchRecords(ownedElectronLaunchRecords, branchOwnedElectronLaunchIds, electronLaunchRecords, branchResourceEvents.electronLaunchActiveRanks, branchResourceEvents.electronLaunchCleanupRanks);
537
+ }
538
+ if (restoredState.active) {
539
+ trackOwnedManagedSession(ownedManagedSessions, restoredState.sessionName, ctx.cwd, { branchOwned: true });
540
+ }
541
+ mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
542
+ branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
543
+ markBranchOwned: true,
544
+ });
545
+ };
546
+ const registerWebSearchToolIfAvailable = (configState) => {
547
+ if (webSearchToolRegistered || !canRegisterWebSearchTool(configState))
548
+ return;
549
+ pi.registerTool(createAgentBrowserWebSearchTool(configState, {
550
+ loadConfigState(ctx) {
551
+ return loadAgentBrowserConfigSync({
552
+ cwd: ctx.cwd,
553
+ includeProjectConfig: shouldIncludeProjectConfig(ctx),
554
+ });
555
+ },
556
+ }));
557
+ webSearchToolRegistered = true;
558
+ };
559
+ pi.on("session_start", async (_event, ctx) => {
560
+ restoreBranchBackedState(ctx, { resetRuntimeOwnership: true });
561
+ electronChildProcesses = new Map();
562
+ registerWebSearchToolIfAvailable(loadAgentBrowserConfigSync({
563
+ cwd: ctx.cwd,
564
+ includeProjectConfig: shouldIncludeProjectConfig(ctx),
565
+ }));
566
+ });
567
+ pi.on("session_tree", async (_event, ctx) => {
568
+ await managedSessionExecutionQueue.run(async () => {
569
+ restoreBranchBackedState(ctx, { resetRuntimeOwnership: false });
570
+ });
571
+ });
572
+ pi.on("session_shutdown", async (event, ctx) => {
573
+ let preservedElectronProfileDirs = [];
574
+ await managedSessionExecutionQueue.run(async () => {
575
+ const shutdownCwd = ctx?.cwd ?? managedSessionCwd;
576
+ const quitting = event?.reason === "quit";
577
+ preservedElectronProfileDirs = quitting
578
+ ? []
579
+ : getActiveElectronRecords(electronLaunchRecords).map((record) => record.userDataDir);
580
+ const electronRecordsToCleanup = quitting
581
+ ? ownedElectronLaunchRecords
582
+ : getOffBranchOwnedElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords);
583
+ const electronCleanupResults = await cleanupActiveElectronHostLaunches({
584
+ cwd: shutdownCwd,
585
+ electronChildProcesses,
586
+ electronLaunchRecords: electronRecordsToCleanup,
587
+ timeoutMs: implicitSessionCloseTimeoutMs,
588
+ });
589
+ preservedElectronProfileDirs = [...new Set([
590
+ ...preservedElectronProfileDirs,
591
+ ...getCleanupResultsPreservedUserDataDirs(electronCleanupResults),
592
+ ])];
593
+ syncElectronCleanupManagedSessions(ownedManagedSessions, electronCleanupResults);
594
+ if (quitting) {
595
+ await closeOwnedManagedSessions(ownedManagedSessions, implicitSessionCloseTimeoutMs);
596
+ }
597
+ else {
598
+ await closeOwnedManagedSessionsExcept(ownedManagedSessions, managedSessionActive ? managedSessionName : undefined, implicitSessionCloseTimeoutMs);
599
+ }
600
+ });
601
+ managedSessionActive = false;
602
+ sessionPageState.reset();
603
+ traceOwners = new Map();
604
+ artifactManifest = undefined;
605
+ allowedDomainsBySession = new Map();
606
+ networkRoutesBySession = new Map();
607
+ electronLaunchRecords = new Map();
608
+ ownedElectronLaunchRecords = new Map();
609
+ branchOwnedElectronLaunchIds = new Set();
610
+ electronChildProcesses = new Map();
611
+ ownedManagedSessions.clear();
612
+ await cleanupSecureTempArtifacts({ preservePaths: preservedElectronProfileDirs });
613
+ });
614
+ pi.on("before_agent_start", async (event, ctx) => {
615
+ if (!shouldAppendBrowserSystemPrompt(event.prompt)) {
616
+ return undefined;
617
+ }
618
+ const runtimeConfig = loadAgentBrowserConfigSync({
619
+ cwd: ctx.cwd,
620
+ includeProjectConfig: shouldIncludeProjectConfig(ctx),
621
+ });
622
+ const browserGuidance = [
623
+ runtimeConfig.browserExecutablePathScope === "project"
624
+ ? buildBrowserExecutablePathGuideline(runtimeConfig.browserExecutablePath)
625
+ : undefined,
626
+ runtimeConfig.browserDefaultProfileScope === "project"
627
+ ? buildBrowserDefaultProfileGuideline(runtimeConfig.browserDefaultProfile)
628
+ : undefined,
629
+ ].filter((line) => typeof line === "string" && line.length > 0);
630
+ const runtimeConfigPrompt = browserGuidance.length > 0
631
+ ? `\n\nProject agent_browser config guidance:\n${browserGuidance.map((line) => `- ${line}`).join("\n")}`
632
+ : "";
633
+ return {
634
+ systemPrompt: `${event.systemPrompt}\n\n${PROJECT_RULE_PROMPT}${runtimeConfigPrompt}`,
635
+ };
636
+ });
637
+ pi.on("tool_call", async (event, ctx) => {
638
+ const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
639
+ if (isBashToolCallEvent(event) &&
640
+ !promptPolicy.allowLegacyAgentBrowserBash &&
641
+ looksLikeDirectAgentBrowserBash(event.input.command) &&
642
+ !isHarmlessAgentBrowserInspectionCommand(event.input.command) &&
643
+ !(await isDirectAgentBrowserBashAllowed(ctx.cwd))) {
644
+ return {
645
+ block: true,
646
+ reason: "Use the native agent_browser tool instead of bash for agent-browser in this environment.",
647
+ };
648
+ }
649
+ });
650
+ pi.on("tool_result", async (event) => buildAgentBrowserToolResultPatch(event));
651
+ pi.registerTool({
652
+ name: "agent_browser",
653
+ label: "Agent Browser",
654
+ description: "Browse and interact with websites using agent-browser. Use this for web research, reading live docs, opening pages, taking snapshots or screenshots, clicking links, filling forms, extracting page content, and authenticated/profile-based browser work. Input choice: 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.",
655
+ promptSnippet: "Browse websites, read live docs, click and fill pages, extract browser content, take screenshots, and automate real web workflows.",
656
+ promptGuidelines: toolPromptGuidelines,
657
+ parameters: AGENT_BROWSER_PARAMS,
658
+ renderCall(args, theme, context) {
659
+ const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
660
+ text.setText(formatAgentBrowserRenderCall(args, theme));
661
+ return text;
662
+ },
663
+ renderResult(result, options, theme, context) {
664
+ const component = context.lastComponent instanceof AgentBrowserResultComponent
665
+ ? context.lastComponent
666
+ : new AgentBrowserResultComponent();
667
+ component.setState(formatAgentBrowserRenderResult(result, options, theme, context.isError), options.expanded, theme);
668
+ return component;
669
+ },
670
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
671
+ const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
672
+ const outputPath = isRecord(params) && typeof params.outputPath === "string" ? params.outputPath : undefined;
673
+ const resolvedInput = resolveAgentBrowserInput({
674
+ getBatchPreflightValidationError,
675
+ managedSessionActive,
676
+ params,
677
+ });
678
+ if (resolvedInput.status === "invalid") {
679
+ return buildValidationFailureResult(resolvedInput);
680
+ }
681
+ const { toolArgs } = resolvedInput;
682
+ const compiledElectron = resolvedInput.kind === "electron" ? resolvedInput.compiledElectron : undefined;
683
+ const redactedCompiledElectron = resolvedInput.kind === "electron" ? resolvedInput.redactedCompiledElectron : undefined;
684
+ const runElectronHostInput = async () => {
685
+ const electronHostLaunchRecords = getElectronHostLaunchRecordsForInput({
686
+ branchRecords: electronLaunchRecords,
687
+ compiledElectron,
688
+ ownedRecords: ownedElectronLaunchRecords,
689
+ });
690
+ const electronHostResult = await handleElectronHostInput({
691
+ compiledElectron,
692
+ cwd: ctx.cwd,
693
+ electronChildProcesses,
694
+ electronLaunchRecords: electronHostLaunchRecords,
695
+ implicitSessionCloseTimeoutMs,
696
+ managedSessionActive,
697
+ managedSessionName,
698
+ redactedCompiledElectron,
699
+ sessionPageState,
700
+ signal,
701
+ });
702
+ if (electronHostResult && compiledElectron?.action === "cleanup") {
703
+ branchStateGeneration += 1;
704
+ const cleanupRecords = isRecord(electronHostResult.details)
705
+ && isRecord(electronHostResult.details.electron)
706
+ && isRecord(electronHostResult.details.electron.cleanup)
707
+ && Array.isArray(electronHostResult.details.electron.cleanup.results)
708
+ ? electronHostResult.details.electron.cleanup.results
709
+ : [];
710
+ const cleanedLaunchIds = new Set();
711
+ for (const cleanupResult of cleanupRecords) {
712
+ if (isRecord(cleanupResult) && isElectronLaunchRecord(cleanupResult.record)) {
713
+ cleanedLaunchIds.add(cleanupResult.record.launchId);
714
+ }
715
+ }
716
+ replaceWithActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronHostLaunchRecords, branchOwnedElectronLaunchIds, cleanedLaunchIds);
717
+ mergeElectronCleanupRecords(electronLaunchRecords, cleanupRecords);
718
+ const closedSessionNames = getCleanupResultsClosedManagedSessionNames(cleanupRecords);
719
+ syncElectronCleanupManagedSessions(ownedManagedSessions, cleanupRecords);
720
+ for (const closedSessionName of closedSessionNames) {
721
+ clearSessionScopedBrowserState(closedSessionName);
722
+ if (closedSessionName === managedSessionName) {
723
+ managedSessionActive = false;
724
+ freshSessionOrdinal += 1;
725
+ managedSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal);
726
+ }
727
+ }
728
+ }
729
+ return electronHostResult;
730
+ };
731
+ const electronHostResult = shouldSerializeElectronHostInput(compiledElectron)
732
+ ? await managedSessionExecutionQueue.run(runElectronHostInput)
733
+ : await runElectronHostInput();
734
+ if (electronHostResult) {
735
+ return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, result: electronHostResult });
736
+ }
737
+ const explicitSessionName = extractExplicitSessionName(toolArgs);
738
+ const serializeBrowserCommand = shouldSerializeBrowserCommand({
739
+ explicitSessionName,
740
+ managedSessionName,
741
+ ownedElectronLaunchRecords,
742
+ ownedManagedSessions,
743
+ });
744
+ const runBrowserCommand = async () => {
745
+ const generationAtStart = branchStateGeneration;
746
+ const sessionPageStateUpdate = sessionPageState.beginUpdate();
747
+ const browserRunState = {
748
+ allowedDomainsBySession,
749
+ artifactManifest,
750
+ closedManagedSessionNames: new Set(),
751
+ electronChildProcesses,
752
+ electronLaunchRecords,
753
+ ephemeralSessionSeed,
754
+ freshSessionOrdinal,
755
+ managedSessionActive,
756
+ managedSessionBaseName,
757
+ managedSessionCwd,
758
+ managedSessionName,
759
+ networkRoutesBySession,
760
+ sessionPageState,
761
+ traceOwners,
762
+ };
763
+ const result = await runAgentBrowserTool({
764
+ ctx,
765
+ cwd: ctx.cwd,
766
+ electronPostCommandStatusSettleMs: ELECTRON_POST_COMMAND_STATUS_SETTLE_MS,
767
+ electronProfileIsolationDetails: ELECTRON_PROFILE_ISOLATION_DETAILS,
768
+ implicitSessionCloseTimeoutMs,
769
+ implicitSessionIdleTimeoutMs,
770
+ input: resolvedInput,
771
+ onUpdate,
772
+ params,
773
+ promptPolicy,
774
+ sessionPageStateUpdate,
775
+ signal,
776
+ state: browserRunState,
777
+ });
778
+ const branchStateStillCurrent = generationAtStart === branchStateGeneration;
779
+ if (serializeBrowserCommand || branchStateStillCurrent) {
780
+ allowedDomainsBySession = browserRunState.allowedDomainsBySession;
781
+ networkRoutesBySession = browserRunState.networkRoutesBySession;
782
+ artifactManifest = browserRunState.artifactManifest;
783
+ freshSessionOrdinal = Math.max(freshSessionOrdinal, browserRunState.freshSessionOrdinal);
784
+ managedSessionActive = browserRunState.managedSessionActive;
785
+ managedSessionCwd = browserRunState.managedSessionCwd;
786
+ managedSessionName = browserRunState.managedSessionName;
787
+ for (const closedSessionName of browserRunState.closedManagedSessionNames) {
788
+ untrackOwnedManagedSession(ownedManagedSessions, closedSessionName);
789
+ }
790
+ syncOwnedManagedSessionsFromResult(ownedManagedSessions, result, browserRunState.managedSessionCwd);
791
+ mergeActiveElectronLaunchRecords(ownedElectronLaunchRecords, electronLaunchRecords, {
792
+ branchOwnedLaunchIds: branchOwnedElectronLaunchIds,
793
+ touchedLaunchIds: !result.isError
794
+ ? getTouchedElectronLaunchIds(explicitSessionName ?? browserRunState.managedSessionName, electronLaunchRecords)
795
+ : undefined,
796
+ });
797
+ if (serializeBrowserCommand)
798
+ branchStateGeneration += 1;
799
+ }
800
+ return applyAgentBrowserOutputPath({ cwd: ctx.cwd, outputPath, preserveTextContent: Array.isArray(params.args) && params.args.includes("--json"), result });
801
+ };
802
+ return serializeBrowserCommand
803
+ ? managedSessionExecutionQueue.run(runBrowserCommand)
804
+ : runBrowserCommand();
805
+ },
806
+ });
807
+ registerWebSearchToolIfAvailable(agentBrowserConfig);
808
+ }