pi-agent-browser-native 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 - 2026-04-12
4
+
5
+ ### Changed
6
+ - `batch` now reuses the richer standalone renderers, so batched snapshots keep the compact main-content-first view and batched screenshots keep inline image attachments instead of degrading to raw JSON-ish text
7
+ - the tool schema now uses `sessionMode: "auto" | "fresh"` instead of the old implicit-session boolean so agents have a first-class way to request a fresh profiled/debug launch, and blocked startup-scoped reuse errors now include structured recovery hints
8
+ - plain-text inspection commands like `agent_browser --help` and `--version` are now always allowed, removing the old prompt-dependent inspection gate and making the inspection contract local and predictable
9
+ - navigation actions like `click`, `dblclick`, `back`, `forward`, and `reload` now include lightweight post-action title/url summaries when the wrapper can address the active session, reducing guess-and-check follow-up snapshots
10
+ - compact snapshot rendering is leaner by default: fewer additional sections, fewer refs, smaller role summaries, and the raw spill path now stays in `details.fullOutputPath` instead of dominating the visible snapshot body
11
+ - README and injected tool guidance now include a compact agent quick start with the core call shapes for `open` + `snapshot`, `click` + re-snapshot, `batch`, `eval --stdin`, and fresh profiled launches
12
+
13
+ ### Migration notes
14
+ - replace any use of `useActiveSession` with `sessionMode`
15
+ - use `sessionMode: "fresh"` when you need a new `--profile`, `--session-name`, or `--cdp` launch after the implicit session is already active
16
+
17
+ ## 0.1.6 - 2026-04-12
18
+
19
+ ### Changed
20
+ - hardened the implicit browser-session lifecycle so failed first launches no longer mark the convenience session active, startup-scoped flags behave correctly across launches and closes, and the highest-risk entrypoint paths now have direct automated and isolated-`pi` coverage
21
+ - added explicit temp-root ownership markers, aggregate spill-file disk budgeting, inline image size limits, and graceful fallback behavior when large snapshot or stdout artifacts exceed temp budgets
22
+ - consolidated the shared browser operating playbook across the injected system prompt and tool prompt guidance while adding direct extension-hook coverage for prompt injection, bash blocking, and session resets
23
+ - split the old result-rendering god module into focused envelope, presentation, shared, and snapshot modules, and made snapshot compaction fall back to a resilient outline mode when upstream raw snapshot formatting is unfamiliar
24
+ - refactored the release-package verification script into smaller testable helpers, preserved the retired autoload-shim guard, and aligned the tarball gate with the split result-rendering module layout
25
+
3
26
  ## 0.1.5 - 2026-04-12
4
27
 
5
28
  ### Changed
package/README.md CHANGED
@@ -87,6 +87,67 @@ This avoids duplicate `agent_browser` registrations if you also have the publish
87
87
 
88
88
  The native tool exposed to the agent is named `agent_browser`.
89
89
 
90
+ The primary session control parameter is `sessionMode`:
91
+
92
+ - `"auto"` (default) reuses the implicit `pi`-scoped session when possible
93
+ - `"fresh"` skips that implicit session so startup-scoped flags like `--profile`, `--session-name`, and `--cdp` can launch a fresh upstream session
94
+
95
+ ## Agent quick start
96
+
97
+ ### Mental model
98
+
99
+ - `args` — exact CLI args after `agent-browser`
100
+ - `stdin` — raw stdin only for `batch` and `eval --stdin`
101
+ - `sessionMode`
102
+ - `"auto"` — default, reuse the implicit `pi`-scoped session
103
+ - `"fresh"` — skip the implicit session for a new profile/debug launch
104
+
105
+ ### Common call shapes
106
+
107
+ Open a page, then take an interactive snapshot:
108
+
109
+ ```json
110
+ { "args": ["open", "https://example.com"] }
111
+ { "args": ["snapshot", "-i"] }
112
+ ```
113
+
114
+ Click a ref, then re-snapshot after navigation or a major DOM change:
115
+
116
+ ```json
117
+ { "args": ["click", "@e2"] }
118
+ { "args": ["snapshot", "-i"] }
119
+ ```
120
+
121
+ Run a multi-step browser flow in one tool call:
122
+
123
+ ```json
124
+ { "args": ["batch"], "stdin": "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }
125
+ ```
126
+
127
+ Evaluate page JavaScript via stdin:
128
+
129
+ ```json
130
+ { "args": ["eval", "--stdin"], "stdin": "document.title" }
131
+ ```
132
+
133
+ Start a fresh profiled launch after you already used the implicit session:
134
+
135
+ ```json
136
+ { "args": ["--profile", "Default", "open", "https://example.com/account"], "sessionMode": "fresh" }
137
+ ```
138
+
139
+ Name a new upstream session explicitly when you want to keep reusing it:
140
+
141
+ ```json
142
+ { "args": ["--session", "auth-flow", "open", "https://example.com"] }
143
+ ```
144
+
145
+ ### First useful prompt in a fresh `pi` session
146
+
147
+ ```text
148
+ Use the agent_browser tool to open https://react.dev and then take an interactive snapshot.
149
+ ```
150
+
90
151
  ## Local development
91
152
 
92
153
  Do not track or rely on a repo-local `.pi/extensions/agent-browser.ts` autoload shim for this package. When the package is also installed globally, that creates a duplicate `agent_browser` registration and blocks `pi` startup from this working directory.
@@ -116,14 +177,40 @@ Validated workflow examples:
116
177
  - run `batch` with JSON via `stdin`
117
178
  - run `eval --stdin`
118
179
  - take a screenshot with inline attachment support
119
- - inspect `agent_browser --help` and `--version`
180
+ - inspect `agent_browser --help` and `--version` via the tool's plain-text inspection fallback
181
+
182
+ Inspection commands like `agent_browser --help` and `--version` are always supported. They return plain text and are useful for debugging or capability checks, but they are not required for normal browsing workflows.
120
183
 
121
184
  Current cautions:
122
185
  - passing `--profile` is an explicit upstream choice; this extension does not add its own profile-cloning or isolation layer
123
- - startup-scoped flags like `--profile`, `--session-name`, and `--cdp` are for the first command that launches a session; if the implicit session is already active, the extension returns a validation error instead of silently letting upstream ignore those flags
186
+ - startup-scoped flags like `--profile`, `--session-name`, and `--cdp` are for the first command that launches a session; if the implicit session is already active, retry that call with `sessionMode: "fresh"` or provide an explicit `--session ...` for the new launch
124
187
  - implicit `piab-*` sessions are extension-managed convenience sessions; they are best-effort closed on `pi` shutdown, get an idle timeout to reduce stale background daemons, and clean up private temp spill artifacts on shutdown
125
188
  - explicit upstream sessions like `--session`, `--profile`, `--session-name`, and `--cdp` are treated as user-managed and are not auto-closed by the extension
126
189
 
190
+ ### Switching from public browsing to a fresh profile/debug launch
191
+
192
+ A common agent workflow is:
193
+
194
+ 1. browse a public page with the default implicit session
195
+ 2. then switch to a fresh authenticated/profile/debug launch
196
+
197
+ Use `sessionMode: "fresh"` for that transition instead of relying on the implicit session:
198
+
199
+ ```json
200
+ {
201
+ "args": ["--profile", "Default", "open", "https://example.com/account"],
202
+ "sessionMode": "fresh"
203
+ }
204
+ ```
205
+
206
+ If you want to name the new upstream session yourself, pass an explicit session instead:
207
+
208
+ ```json
209
+ {
210
+ "args": ["--session", "auth-flow", "--profile", "Default", "open", "https://example.com/account"]
211
+ }
212
+ ```
213
+
127
214
  ## Docs
128
215
 
129
216
  - [`docs/REQUIREMENTS.md`](docs/REQUIREMENTS.md) — product requirements and constraints
@@ -59,17 +59,19 @@ The published package should exclude agent-only and superseded repo materials su
59
59
 
60
60
  ### Default
61
61
 
62
- If the caller does not provide `--session`, the extension should use an implicit session name derived from the current `pi` session id.
62
+ If the caller does not provide `--session`, the extension should default to `sessionMode: "auto"` and use an implicit session name derived from the current `pi` session id.
63
63
 
64
64
  Why:
65
65
  - works out of the box
66
66
  - gives continuity across calls
67
67
  - avoids forcing the agent to invent session names for basic browsing
68
68
 
69
- ### Explicit upstream sessions
69
+ ### Explicit upstream sessions and fresh launches
70
70
 
71
71
  If the caller provides `--session`, `--profile`, `--cdp`, or similar upstream flags, the extension should respect them with minimal interference.
72
72
 
73
+ The tool should also expose a first-class `sessionMode: "fresh"` escape hatch so agents can intentionally skip the implicit session and launch a fresh upstream session without inventing a fixed explicit session name.
74
+
73
75
  ### Ownership
74
76
 
75
77
  V1 ownership rule:
@@ -92,7 +94,9 @@ The extension should surface that clearly and avoid hidden restart behavior in v
92
94
 
93
95
  That means explicit startup-scoping flags like `--profile`, `--session-name`, and `--cdp` should remain explicit upstream choices instead of being wrapped in extra hidden restart or cloning logic.
94
96
 
95
- If the implicit session is already active and one of those startup-scoped flags appears again, the extension should fail clearly instead of silently sending a command shape that upstream would ignore.
97
+ If the implicit session is already active and one of those startup-scoped flags appears again while `sessionMode` is still `"auto"`, the extension should fail clearly instead of silently sending a command shape that upstream would ignore.
98
+
99
+ That failure should include a structured recovery hint pointing to `sessionMode: "fresh"` as the first-line fix, while still allowing an explicit `--session` when the caller wants to name the new upstream session.
96
100
 
97
101
  ## Preferring the native tool
98
102
 
package/docs/RELEASE.md CHANGED
@@ -31,7 +31,7 @@ npm run verify:release
31
31
  - no repo-local `.pi/extensions/agent-browser.ts` autoload shim is present
32
32
  - `LICENSE` exists in the repo and the packed tarball
33
33
  - canonical published docs are present
34
- - extension source files are present
34
+ - extension source files are present, including the split result-rendering modules required by the published facade
35
35
  - agent-only and superseded docs are absent from the tarball
36
36
 
37
37
  Current forbidden packed files include:
@@ -32,7 +32,7 @@ The tool also needs an operating playbook, not just a capability list. The model
32
32
  {
33
33
  "args": ["open", "https://example.com"],
34
34
  "stdin": "optional raw stdin content",
35
- "useActiveSession": true
35
+ "sessionMode": "auto"
36
36
  }
37
37
  ```
38
38
 
@@ -69,15 +69,20 @@ Examples:
69
69
  { "args": ["batch"], "stdin": "[[\"open\",\"https://example.com\"],[\"snapshot\",\"-i\"]]" }
70
70
  ```
71
71
 
72
- ### `useActiveSession`
72
+ ### `sessionMode`
73
73
 
74
- - type: `boolean`
74
+ - type: `"auto" | "fresh"`
75
75
  - optional
76
- - default: `true`
76
+ - default: `"auto"`
77
77
 
78
78
  Behavior:
79
79
  - if `args` already include `--session`, upstream session choice wins
80
- - otherwise the extension prepends its implicit active session when `useActiveSession` is `true`
80
+ - `"auto"` prepends the implicit active session when appropriate
81
+ - `"fresh"` skips the implicit session so startup-scoped flags like `--profile`, `--session-name`, or `--cdp` can launch a fresh upstream session
82
+
83
+ Recommended use:
84
+ - use `"auto"` for the common browse/snapshot/click flow inside one `pi` session
85
+ - use `"fresh"` when switching from an already-active implicit session to a new profile/debug/auth launch without inventing a fixed explicit session name
81
86
 
82
87
  ## Wrapper behavior
83
88
 
@@ -87,8 +92,8 @@ The extension should:
87
92
  - parse JSON output into tool details
88
93
  - handle observed JSON result shapes, including the array returned by `batch --json`
89
94
  - allow plain-text fallback for inspection commands like `--help` and `--version`
90
- - discourage exploratory inspection calls unless the user explicitly asks or debugging requires them
91
- - deflect normal-task `--help` inspection back into the standard browser workflow instead of letting the model relearn the tool from scratch each session
95
+ - support those inspection commands unconditionally so the tool contract stays local and predictable
96
+ - still describe normal browser workflows in guidance so models do not overuse inspection for routine tasks
92
97
  - surface stderr and non-zero exits clearly
93
98
  - attach images when the result points to a screenshot-like artifact
94
99
 
@@ -104,7 +109,8 @@ Primary content should be:
104
109
 
105
110
  Examples:
106
111
  - small `snapshot` results should include the actual snapshot text
107
- - oversized `snapshot` results should switch to a compact view that preserves the primary content, nearby sections, high-value refs, and a path to the spilled full raw snapshot
112
+ - oversized `snapshot` results should switch to a compact view that preserves the primary content, nearby sections, and a trimmed set of high-value refs, while exposing the full raw snapshot path via `details.fullOutputPath`
113
+ - successful navigation actions like `click`, `back`, `forward`, and `reload` should include a lightweight post-action title/url summary when the wrapper can address the active session
108
114
  - `tab list` should include a readable tab summary
109
115
  - `screenshot` should include the saved-path summary plus the inline image attachment when available
110
116
 
@@ -116,6 +122,7 @@ Recommended details:
116
122
  {
117
123
  "args": ["snapshot", "-i"],
118
124
  "effectiveArgs": ["--session", "pi-abc123", "--json", "snapshot", "-i"],
125
+ "sessionMode": "auto",
119
126
  "sessionName": "pi-abc123",
120
127
  "usedImplicitSession": true,
121
128
  "data": {
@@ -136,7 +143,8 @@ For oversized snapshots, details should switch to a compact metadata object and
136
143
 
137
144
  Worth doing in v1:
138
145
  - screenshots → inline image attachment
139
- - snapshots → origin + ref count + main-content-first compact preview, with full raw snapshot spill files when the inline result would otherwise be too large
146
+ - snapshots → origin + ref count + main-content-first compact preview, with the raw snapshot spill path kept in `details.fullOutputPath` when the inline result would otherwise be too large
147
+ - navigation actions like `click`, `back`, `forward`, and `reload` → lightweight post-action title/url summary when available
140
148
  - tab lists → compact summary/table
141
149
  - stream status → enabled/connected/port summary
142
150
 
@@ -158,7 +166,7 @@ If `agent-browser` is not on `PATH`, fail with a message that:
158
166
  - clean up private temp spill artifacts owned by the implicit session on shutdown
159
167
  - treat explicit upstream session choices like `--session`, `--profile`, `--session-name`, and `--cdp` as user-managed
160
168
  - pass explicit `--profile` straight through to upstream `agent-browser`; no profile-cloning or isolation layer is added in v1
161
- - if startup-scoped flags like `--profile`, `--session-name`, or `--cdp` are supplied after the implicit session is already active, return a validation error instead of silently relying on upstream to ignore them
169
+ - if startup-scoped flags like `--profile`, `--session-name`, or `--cdp` are supplied after the implicit session is already active while `sessionMode` is `"auto"`, return a validation error with a structured recovery hint that recommends `sessionMode: "fresh"`
162
170
 
163
171
  ## Non-goals
164
172
 
@@ -18,14 +18,16 @@ import {
18
18
  buildPromptPolicy,
19
19
  createEphemeralSessionSeed,
20
20
  createImplicitSessionName,
21
+ getImplicitSessionCloseTimeoutMs,
22
+ getImplicitSessionIdleTimeoutMs,
21
23
  getLatestUserPrompt,
22
24
  hasUsableBraveApiKey,
25
+ resolveImplicitSessionActiveState,
23
26
  validateToolArgs,
24
27
  } from "./lib/runtime.js";
25
28
  import { cleanupSecureTempArtifacts } from "./lib/temp.js";
26
29
 
27
- const IMPLICIT_SESSION_IDLE_TIMEOUT_MS = "900000";
28
- const IMPLICIT_SESSION_CLOSE_TIMEOUT_MS = 5_000;
30
+ const DEFAULT_SESSION_MODE = "auto" as const;
29
31
 
30
32
  const AGENT_BROWSER_PARAMS = Type.Object({
31
33
  args: Type.Array(Type.String({ description: "Exact agent-browser CLI arguments, excluding the binary name." }), {
@@ -33,13 +35,45 @@ const AGENT_BROWSER_PARAMS = Type.Object({
33
35
  minItems: 1,
34
36
  }),
35
37
  stdin: Type.Optional(Type.String({ description: "Optional raw stdin content for commands like eval --stdin or batch." })),
36
- useActiveSession: Type.Optional(
37
- Type.Boolean({
38
- description: "When true and no explicit --session is present, inject the implicit session for this pi session.",
39
- default: true,
38
+ sessionMode: Type.Optional(
39
+ Type.Union([Type.Literal("auto"), Type.Literal("fresh")], {
40
+ description:
41
+ "Session handling mode. `auto` reuses the implicit pi-scoped session when possible. `fresh` skips the implicit session so startup-scoped flags like --profile, --session-name, or --cdp can launch a fresh upstream session.",
42
+ default: DEFAULT_SESSION_MODE,
40
43
  }),
41
44
  ),
42
45
  });
46
+ const PROJECT_RULE_PROMPT =
47
+ "Project rule: when browser automation is needed, prefer the native `agent_browser` tool. Do not run direct `agent-browser` bash commands unless the user explicitly asks for a bash-oriented workflow or browser-integration debugging.";
48
+ const QUICK_START_GUIDELINES = [
49
+ "Quick start mental model: args are the exact agent-browser CLI args after the binary; stdin is only for batch and eval --stdin; sessionMode=fresh starts a fresh upstream launch when you need new --profile, --session-name, or --cdp state.",
50
+ "Common first calls: { args: [\"open\", \"https://example.com\"] } then { args: [\"snapshot\", \"-i\"] }; after navigation, use { args: [\"click\", \"@e2\"] } then { args: [\"snapshot\", \"-i\"] }.",
51
+ "Common advanced calls: { args: [\"batch\"], stdin: \"[[\\\"open\\\",\\\"https://example.com\\\"],[\\\"snapshot\\\",\\\"-i\\\"]]\" }, { args: [\"eval\", \"--stdin\"], stdin: \"document.title\" }, and { args: [\"--profile\", \"Default\", \"open\", \"https://example.com/account\"], sessionMode: \"fresh\" }.",
52
+ ] as const;
53
+ const BRAVE_SEARCH_PROMPT_GUIDELINE =
54
+ "When a non-empty BRAVE_API_KEY is available in the current environment, prefer the Brave Search API via bash/curl to discover specific destination URLs, then open the chosen URL with agent_browser instead of browsing a search engine results page just to find the target.";
55
+ const SHARED_BROWSER_PLAYBOOK_GUIDELINES = [
56
+ "Standard workflow: open the page, snapshot -i, interact using refs, and re-snapshot after navigation or major DOM changes.",
57
+ "For authenticated or user-specific content like feeds, inboxes, dashboards, and accounts, prefer --profile Default on the first browser call and let the implicit session carry continuity. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.",
58
+ "Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.",
59
+ "When using --profile, --session-name, or --cdp, put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.",
60
+ "If you already used the implicit session and now need startup-scoped flags like --profile, --session-name, or --cdp, retry with sessionMode set to fresh or pass an explicit --session for the new launch.",
61
+ "If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <n> / snapshot -i to recover state before retrying different URLs or fallback strategies. Only use wait with an explicit argument like milliseconds, --load, --url, --fn, or --text.",
62
+ "For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
63
+ "For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
64
+ "When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
65
+ "When using eval --stdin for extraction, return the value you want instead of relying on console.log as the primary result channel.",
66
+ "Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
67
+ ] as const;
68
+ const TOOL_PROMPT_GUIDELINES_PREFIX = ["Use this tool whenever the task requires a real browser or live web content."] as const;
69
+ const TOOL_PROMPT_GUIDELINES_SUFFIX = [
70
+ "Prefer this tool over bash for opening sites, reading docs on the web, clicking, filling, screenshots, eval, and batch workflows.",
71
+ "Do not fall back to osascript, AppleScript, or generic browser-driving bash commands when this tool can do the job.",
72
+ "Pass exact agent-browser CLI arguments in args, excluding the binary name.",
73
+ "Use stdin for commands like eval --stdin and batch instead of shell heredocs.",
74
+ "Let the implicit session handle the common path unless you explicitly need a fresh launch for upstream flags like --profile, --session-name, or --cdp.",
75
+ "Use sessionMode=fresh when switching from an existing implicit session to a new profile/debug launch without inventing a fixed explicit session name.",
76
+ ] as const;
43
77
 
44
78
  function buildMissingBinaryMessage(): string {
45
79
  return [
@@ -68,25 +102,119 @@ function isPlainTextInspectionArgs(args: string[]): boolean {
68
102
  return args.includes("--help") || args.includes("-h") || args.includes("--version") || args.includes("-V");
69
103
  }
70
104
 
71
- function buildInspectionDeflectionMessage(): string {
105
+ const NAVIGATION_SUMMARY_COMMANDS = new Set(["back", "click", "dblclick", "forward", "reload"]);
106
+
107
+ interface NavigationSummary {
108
+ title?: string;
109
+ url?: string;
110
+ }
111
+
112
+ function isRecord(value: unknown): value is Record<string, unknown> {
113
+ return typeof value === "object" && value !== null;
114
+ }
115
+
116
+ function shouldCaptureNavigationSummary(command: string | undefined, data: unknown): boolean {
117
+ return (
118
+ command !== undefined &&
119
+ NAVIGATION_SUMMARY_COMMANDS.has(command) &&
120
+ (!isRecord(data) || (typeof data.title !== "string" && typeof data.url !== "string"))
121
+ );
122
+ }
123
+
124
+ function extractStringResultField(data: unknown, fieldName: "title" | "url"): string | undefined {
125
+ if (typeof data === "string") {
126
+ const text = data.trim();
127
+ return text.length > 0 ? text : undefined;
128
+ }
129
+ if (!isRecord(data) || typeof data[fieldName] !== "string") {
130
+ return undefined;
131
+ }
132
+ const text = data[fieldName].trim();
133
+ return text.length > 0 ? text : undefined;
134
+ }
135
+
136
+ async function collectNavigationSummary(options: {
137
+ cwd: string;
138
+ sessionName?: string;
139
+ signal?: AbortSignal;
140
+ }): Promise<NavigationSummary | undefined> {
141
+ const { cwd, sessionName, signal } = options;
142
+ if (!sessionName) return undefined;
143
+
144
+ const readField = async (fieldName: "title" | "url"): Promise<string | undefined> => {
145
+ const processResult = await runAgentBrowserProcess({
146
+ args: ["--json", "--session", sessionName, "get", fieldName],
147
+ cwd,
148
+ signal,
149
+ });
150
+ if (processResult.aborted || processResult.spawnError || processResult.exitCode !== 0) {
151
+ return undefined;
152
+ }
153
+ const parsed = await parseAgentBrowserEnvelope({
154
+ stdout: processResult.stdout,
155
+ stdoutPath: processResult.stdoutSpillPath,
156
+ });
157
+ try {
158
+ if (parsed.parseError || parsed.envelope?.success === false) {
159
+ return undefined;
160
+ }
161
+ return extractStringResultField(parsed.envelope?.data, fieldName);
162
+ } finally {
163
+ if (processResult.stdoutSpillPath) {
164
+ await rm(processResult.stdoutSpillPath, { force: true }).catch(() => undefined);
165
+ }
166
+ }
167
+ };
168
+
169
+ const title = await readField("title");
170
+ const url = await readField("url");
171
+ if (!title && !url) return undefined;
172
+ return { title, url };
173
+ }
174
+
175
+ function mergeNavigationSummaryIntoData(data: unknown, navigationSummary: NavigationSummary): unknown {
176
+ if (isRecord(data)) {
177
+ return { ...data, navigationSummary };
178
+ }
179
+ return { navigationSummary, result: data };
180
+ }
181
+
182
+ function buildSharedBrowserPlaybookGuidelines(hasBraveApiKey: boolean): string[] {
72
183
  return [
73
- "Do not inspect agent_browser help for a normal browser task.",
74
- "Use the workflow directly:",
75
- "1. open the target URL",
76
- "2. snapshot -i",
77
- "3. interact using refs and re-snapshot after navigation or major DOM changes",
78
- "For authenticated or user-specific content like feeds, inboxes, dashboards, or accounts, start with an authenticated strategy such as --profile Default on the first browser call and let the implicit session carry continuity. Use --auto-connect only if profile-based reuse is unavailable.",
184
+ SHARED_BROWSER_PLAYBOOK_GUIDELINES[0],
185
+ ...(hasBraveApiKey ? [BRAVE_SEARCH_PROMPT_GUIDELINE] : []),
186
+ ...SHARED_BROWSER_PLAYBOOK_GUIDELINES.slice(1),
187
+ ];
188
+ }
189
+
190
+ function buildBrowserSystemPromptAppendix(hasBraveApiKey: boolean): string {
191
+ return [
192
+ PROJECT_RULE_PROMPT,
193
+ "",
194
+ "Quick start:",
195
+ ...QUICK_START_GUIDELINES.map((guideline) => `- ${guideline}`),
196
+ "",
197
+ "Browser operating playbook:",
198
+ ...buildSharedBrowserPlaybookGuidelines(hasBraveApiKey).map((guideline) => `- ${guideline}`),
79
199
  ].join("\n");
80
200
  }
81
201
 
82
- function buildBraveSearchGuidance(hasBraveApiKey: boolean): string {
83
- if (!hasBraveApiKey) return "";
84
- return "\n- A non-empty `BRAVE_API_KEY` is available in the current environment. For web search or URL discovery, prefer the Brave Search API via `bash`/`curl` to find the destination URL, then open that URL with `agent_browser` instead of using browser automation to drive Google or another search engine results page. If the Brave request fails, fall back to the normal workflow.";
202
+ function buildToolPromptGuidelines(hasBraveApiKey: boolean): string[] {
203
+ return [
204
+ ...TOOL_PROMPT_GUIDELINES_PREFIX,
205
+ ...QUICK_START_GUIDELINES,
206
+ ...buildSharedBrowserPlaybookGuidelines(hasBraveApiKey),
207
+ ...TOOL_PROMPT_GUIDELINES_SUFFIX,
208
+ ];
85
209
  }
86
210
 
87
211
  export default function agentBrowserExtension(pi: ExtensionAPI) {
88
212
  const ephemeralSessionSeed = createEphemeralSessionSeed();
89
- const braveSearchGuidance = buildBraveSearchGuidance(hasUsableBraveApiKey());
213
+ const hasBraveApiKey = hasUsableBraveApiKey();
214
+ const browserSystemPromptAppendix = buildBrowserSystemPromptAppendix(hasBraveApiKey);
215
+ const toolPromptGuidelines = buildToolPromptGuidelines(hasBraveApiKey);
216
+ const implicitSessionIdleTimeoutMs = getImplicitSessionIdleTimeoutMs();
217
+ const implicitSessionCloseTimeoutMs = getImplicitSessionCloseTimeoutMs();
90
218
  let implicitSessionActive = false;
91
219
  let implicitSessionName = createImplicitSessionName(undefined, process.cwd(), ephemeralSessionSeed);
92
220
  let implicitSessionCwd = process.cwd();
@@ -100,7 +228,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
100
228
  pi.on("session_shutdown", async () => {
101
229
  implicitSessionActive = false;
102
230
  const controller = new AbortController();
103
- const timer = setTimeout(() => controller.abort(), IMPLICIT_SESSION_CLOSE_TIMEOUT_MS);
231
+ const timer = setTimeout(() => controller.abort(), implicitSessionCloseTimeoutMs);
104
232
  try {
105
233
  await runAgentBrowserProcess({
106
234
  args: ["--session", implicitSessionName, "close"],
@@ -117,10 +245,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
117
245
 
118
246
  pi.on("before_agent_start", async (event) => {
119
247
  return {
120
- systemPrompt:
121
- event.systemPrompt +
122
- "\n\nProject rule: when browser automation is needed, prefer the native `agent_browser` tool. Do not run direct `agent-browser` bash commands unless the user explicitly asks for a bash-oriented workflow or browser-integration debugging.\n\nBrowser operating playbook:\n- Standard workflow: open the page, then snapshot -i, then interact via refs, then re-snapshot after navigation or major DOM changes.\n- For user-specific or authenticated content like feeds, inboxes, dashboards, and accounts, start with an authenticated browser strategy instead of public browsing. Prefer `--profile Default` on the first browser call and let the current implicit session carry continuity. Use `--auto-connect` only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.\n- Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.\n- When using startup-scoped flags like `--profile`, `--session-name`, or `--cdp`, put them on the first command for that session. If you intentionally use an explicit `--session`, keep using that same explicit session for follow-ups.\n- If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an `open` call returns blocked, blank, or otherwise unexpected results, use `tab list`, `tab <n>`, and `snapshot -i` to recover state before retrying different URLs or fallback strategies. Only use `wait` with an explicit argument like milliseconds, `--load`, `--url`, `--fn`, or `--text`.\n- For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.\n- For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or `eval --stdin` on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.\n- When using `eval --stdin`, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.\n- When using `eval --stdin` for extraction, return the value you want instead of relying on `console.log` as the primary result channel.\n- Do not use `agent_browser --help` for normal browsing tasks." +
123
- braveSearchGuidance,
248
+ systemPrompt: `${event.systemPrompt}\n\n${browserSystemPromptAppendix}`,
124
249
  };
125
250
  });
126
251
 
@@ -146,41 +271,9 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
146
271
  "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.",
147
272
  promptSnippet:
148
273
  "Browse websites, read live docs, click and fill pages, extract browser content, take screenshots, and automate real web workflows.",
149
- promptGuidelines: [
150
- "Use this tool whenever the task requires a real browser or live web content.",
151
- "Standard workflow: open the page, snapshot -i, interact using refs, and re-snapshot after navigation or major DOM changes.",
152
- ...(braveSearchGuidance
153
- ? [
154
- "When a non-empty BRAVE_API_KEY is available in the current environment, prefer the Brave Search API via bash/curl to discover specific destination URLs, then open the chosen URL with agent_browser instead of browsing a search engine results page just to find the target.",
155
- ]
156
- : []),
157
- "For authenticated or user-specific content like feeds, inboxes, dashboards, and accounts, prefer --profile Default on the first browser call and let the implicit session carry continuity. Use --auto-connect only if profile-based reuse is unavailable or the task is specifically about attaching to a running debug-enabled browser.",
158
- "Do not invent fixed explicit session names for routine tasks. Use the implicit session unless you truly need multiple isolated browser sessions in the same conversation.",
159
- "When using --profile, --session-name, or --cdp, put them on the first command for that session. If you intentionally use an explicit --session, keep using that same explicit session for follow-ups.",
160
- "If a session lands on the wrong page or tab, an interaction changes origin unexpectedly, or an open call returns blocked, blank, or otherwise unexpected results, use tab list / tab <n> / snapshot -i to recover state before retrying different URLs or fallback strategies. Only use wait with an explicit argument like milliseconds, --load, --url, --fn, or --text.",
161
- "For feed, timeline, or inbox reading tasks, focus on the main timeline/list region and read the first item there rather than unrelated composer or sidebar content.",
162
- "For read-only browsing tasks, prefer extracting the answer from the current snapshot, structured ref labels, or eval --stdin on the current page before navigating away. Only click into media viewers, detail routes, or new pages when the current view does not contain the needed information.",
163
- "When using eval --stdin, scope checks and actions to the target element or route whenever possible instead of relying on broad page-wide text heuristics.",
164
- "When using eval --stdin for extraction, return the value you want instead of relying on console.log as the primary result channel.",
165
- "Prefer this tool over bash for opening sites, reading docs on the web, clicking, filling, screenshots, eval, and batch workflows.",
166
- "Do not call --help or other exploratory inspection commands unless the user explicitly asks for them or debugging the browser integration is necessary.",
167
- "Do not fall back to osascript, AppleScript, or generic browser-driving bash commands when this tool can do the job.",
168
- "Pass exact agent-browser CLI arguments in args, excluding the binary name.",
169
- "Use stdin for commands like eval --stdin and batch instead of shell heredocs.",
170
- "Let the implicit session handle the common path unless you explicitly need upstream flags like --session, --profile, or --cdp.",
171
- ],
274
+ promptGuidelines: toolPromptGuidelines,
172
275
  parameters: AGENT_BROWSER_PARAMS,
173
276
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
174
- const promptPolicy = buildPromptPolicy(getLatestUserPrompt(ctx.sessionManager.getBranch()));
175
- if (!promptPolicy.allowAgentBrowserInspection && isPlainTextInspectionArgs(params.args)) {
176
- const errorText = buildInspectionDeflectionMessage();
177
- return {
178
- content: [{ type: "text", text: errorText }],
179
- details: { args: params.args, inspectionBlocked: true },
180
- isError: true,
181
- };
182
- }
183
-
184
277
  const validationError = validateToolArgs(params.args);
185
278
  if (validationError) {
186
279
  return {
@@ -190,10 +283,11 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
190
283
  };
191
284
  }
192
285
 
286
+ const sessionMode = params.sessionMode ?? DEFAULT_SESSION_MODE;
193
287
  const executionPlan = buildExecutionPlan(params.args, {
194
288
  implicitSessionActive,
195
289
  implicitSessionName,
196
- useActiveSession: params.useActiveSession ?? true,
290
+ sessionMode,
197
291
  });
198
292
 
199
293
  if (executionPlan.validationError) {
@@ -201,6 +295,8 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
201
295
  content: [{ type: "text", text: executionPlan.validationError }],
202
296
  details: {
203
297
  args: params.args,
298
+ sessionMode,
299
+ sessionRecoveryHint: executionPlan.recoveryHint,
204
300
  startupScopedFlags: executionPlan.startupScopedFlags,
205
301
  validationError: executionPlan.validationError,
206
302
  },
@@ -212,6 +308,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
212
308
  content: [{ type: "text", text: `Running agent-browser ${buildInvocationPreview(executionPlan.effectiveArgs)}` }],
213
309
  details: {
214
310
  effectiveArgs: executionPlan.effectiveArgs,
311
+ sessionMode,
215
312
  sessionName: executionPlan.sessionName,
216
313
  usedImplicitSession: executionPlan.usedImplicitSession,
217
314
  },
@@ -221,16 +318,12 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
221
318
  args: executionPlan.effectiveArgs,
222
319
  cwd: ctx.cwd,
223
320
  env: executionPlan.usedImplicitSession
224
- ? { AGENT_BROWSER_IDLE_TIMEOUT_MS: IMPLICIT_SESSION_IDLE_TIMEOUT_MS }
321
+ ? { AGENT_BROWSER_IDLE_TIMEOUT_MS: implicitSessionIdleTimeoutMs }
225
322
  : undefined,
226
323
  signal,
227
324
  stdin: params.stdin,
228
325
  });
229
326
 
230
- if (executionPlan.usedImplicitSession && !processResult.aborted && !processResult.spawnError) {
231
- implicitSessionActive = executionPlan.commandInfo.command !== "close";
232
- }
233
-
234
327
  if (processResult.spawnError?.message.includes("ENOENT")) {
235
328
  const errorText = buildMissingBinaryMessage();
236
329
  return {
@@ -238,6 +331,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
238
331
  details: {
239
332
  args: params.args,
240
333
  effectiveArgs: executionPlan.effectiveArgs,
334
+ sessionMode,
241
335
  spawnError: processResult.spawnError.message,
242
336
  },
243
337
  isError: true,
@@ -249,12 +343,35 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
249
343
  stdout: processResult.stdout,
250
344
  stdoutPath: processResult.stdoutSpillPath,
251
345
  });
346
+ let presentationEnvelope = parsed.envelope;
252
347
  const processSucceeded = !processResult.aborted && !processResult.spawnError && processResult.exitCode === 0;
253
348
  const plainTextInspection = isPlainTextInspectionArgs(params.args) && processSucceeded && parsed.parseError !== undefined;
254
349
  const envelopeSuccess = plainTextInspection ? true : parsed.envelope?.success !== false;
255
350
  const parseSucceeded = plainTextInspection || parsed.parseError === undefined;
256
351
  const succeeded = processSucceeded && parseSucceeded && envelopeSuccess;
257
352
 
353
+ let navigationSummary: NavigationSummary | undefined;
354
+ if (succeeded && shouldCaptureNavigationSummary(executionPlan.commandInfo.command, parsed.envelope?.data)) {
355
+ navigationSummary = await collectNavigationSummary({
356
+ cwd: ctx.cwd,
357
+ sessionName: executionPlan.sessionName,
358
+ signal,
359
+ });
360
+ if (navigationSummary && presentationEnvelope) {
361
+ presentationEnvelope = {
362
+ ...presentationEnvelope,
363
+ data: mergeNavigationSummaryIntoData(presentationEnvelope.data, navigationSummary),
364
+ };
365
+ }
366
+ }
367
+
368
+ implicitSessionActive = resolveImplicitSessionActiveState({
369
+ command: executionPlan.commandInfo.command,
370
+ priorActive: implicitSessionActive,
371
+ succeeded,
372
+ usedImplicitSession: executionPlan.usedImplicitSession,
373
+ });
374
+
258
375
  const errorText = getAgentBrowserErrorText({
259
376
  aborted: processResult.aborted,
260
377
  envelope: parsed.envelope,
@@ -274,7 +391,7 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
274
391
  : await buildToolPresentation({
275
392
  commandInfo: executionPlan.commandInfo,
276
393
  cwd: ctx.cwd,
277
- envelope: parsed.envelope,
394
+ envelope: presentationEnvelope,
278
395
  errorText,
279
396
  });
280
397
 
@@ -282,16 +399,22 @@ export default function agentBrowserExtension(pi: ExtensionAPI) {
282
399
  content: presentation.content,
283
400
  details: {
284
401
  args: params.args,
402
+ batchSteps: presentation.batchSteps,
285
403
  command: executionPlan.commandInfo.command,
286
404
  subcommand: executionPlan.commandInfo.subcommand,
287
405
  data: presentation.data,
288
406
  error: parsed.envelope?.error,
407
+ navigationSummary,
289
408
  effectiveArgs: executionPlan.effectiveArgs,
290
409
  exitCode: processResult.exitCode,
291
410
  fullOutputPath: presentation.fullOutputPath,
411
+ fullOutputPaths: presentation.fullOutputPaths,
292
412
  imagePath: presentation.imagePath,
413
+ imagePaths: presentation.imagePaths,
293
414
  parseError: parsed.parseError,
415
+ sessionMode,
294
416
  sessionName: executionPlan.sessionName,
417
+ sessionRecoveryHint: executionPlan.recoveryHint,
295
418
  startupScopedFlags: executionPlan.startupScopedFlags,
296
419
  stderr: processResult.stderr || undefined,
297
420
  stdout: parseSucceeded ? undefined : processResult.stdout,