pi-agent-browser-native 0.2.48 → 0.2.49

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