From 7cc2c394dd09ef5262c0f963725e85e9367e6281 Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 30 Jun 2026 16:50:15 -0700 Subject: [PATCH 1/3] feat(cli): make `browse snapshot` lean by default; add --full for ref maps browse snapshot previously emitted the formatted tree plus xpathMap+urlMap (~217KB / ~60K tokens on content-heavy pages) on every call. Refs resolve from a server-side cache, so the printed maps were dead weight in agent context and ~18x larger than Playwright MCP / agent-browser / our own managed Agents output. - Default output is now the formatted tree only (no ref maps). - Add --full to restore tree + xpathMap + urlMap (the previous default). - --compact is now a deprecated no-op alias of the default (stderr + TTY-gated notice). - browse refs still prints the cached maps on demand; ref-based commands unaffected. - evals(browse_cli): derive refCount from the tree when maps are absent; add BROWSE_SNAPSHOT_FULL toggle for the core-tier represent() path. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/lean-browse-snapshot.md | 7 +++++++ packages/cli/skills/browse/SKILL.md | 4 ++-- packages/cli/src/commands/snapshot.ts | 23 +++++++++++++++++++---- packages/evals/core/tools/browse_cli.ts | 12 ++++++++++-- 4 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 .changeset/lean-browse-snapshot.md diff --git a/.changeset/lean-browse-snapshot.md b/.changeset/lean-browse-snapshot.md new file mode 100644 index 000000000..b83adcfce --- /dev/null +++ b/.changeset/lean-browse-snapshot.md @@ -0,0 +1,7 @@ +--- +"browse": patch +--- + +`browse snapshot` is now lean by default: it prints the formatted accessibility tree only, omitting the `xpathMap`/`urlMap` ref maps (~217KB / ~60K tokens on a content-heavy page) that were previously included on every snapshot. + +Ref-based element commands (`click`, `fill`, `select`, etc.) are unaffected — the ref maps are still captured and cached server-side, so refs resolve exactly as before. To get the maps in the output, pass the new `--full` flag. The `--compact` flag is now a deprecated no-op alias of the default (it prints a stderr-only, TTY-gated deprecation notice). diff --git a/packages/cli/skills/browse/SKILL.md b/packages/cli/skills/browse/SKILL.md index b4d2c38af..639590a7f 100644 --- a/packages/cli/skills/browse/SKILL.md +++ b/packages/cli/skills/browse/SKILL.md @@ -132,8 +132,8 @@ browse wait selector "#result" Page state: ```bash -browse snapshot -browse snapshot --compact +browse snapshot # formatted tree only +browse snapshot --full # also include ref maps (xpathMap, urlMap) browse get url browse get title browse get text body diff --git a/packages/cli/src/commands/snapshot.ts b/packages/cli/src/commands/snapshot.ts index 7c1110a27..cb7ce506a 100644 --- a/packages/cli/src/commands/snapshot.ts +++ b/packages/cli/src/commands/snapshot.ts @@ -8,19 +8,24 @@ import { export default class Snapshot extends BrowseCommand { static override description = - "Print the active page accessibility snapshot and cache refs for element commands."; + "Print the active page accessibility snapshot and cache refs for element commands. Lean by default (formatted tree only); pass --full to also include the ref maps (xpathMap, urlMap). The ref maps are always cached for element commands regardless, and can be printed on demand with `browse refs`."; static override examples = [ "browse snapshot", - "browse snapshot --compact", + "browse snapshot --full", "browse snapshot --filter submit", "browse snapshot --max-depth 4", ]; static override flags = { ...driverCommandFlags, + full: Flags.boolean({ + description: + "Include the ref maps (xpathMap, urlMap) in addition to the formatted tree. Restores the pre-lean default output.", + }), compact: Flags.boolean({ - description: "Print only the formatted tree, without ref maps.", + description: + "Deprecated: snapshots are lean by default, so this flag has no effect. Use --full to include the ref maps.", }), filter: Flags.string({ description: @@ -35,10 +40,20 @@ export default class Snapshot extends BrowseCommand { async run(): Promise { const { flags } = await this.parse(Snapshot); + // `--compact` is now the default. Keep accepting it as a no-op alias so + // existing callers don't break, but nudge interactive users toward --full. + // Warn on stderr only, gated on a TTY, so agents parsing stdout JSON (or + // capturing stderr) get no extra noise. + if (flags.compact && process.stderr.isTTY) { + this.warn( + "`--compact` is deprecated: `browse snapshot` is lean by default. Use `--full` to include the ref maps.", + ); + } await runDriverCommandFromFlags( "snapshot", { - compact: flags.compact, + // Lean (no ref maps) unless --full is requested. + compact: !flags.full, filter: flags.filter, maxDepth: flags["max-depth"], }, diff --git a/packages/evals/core/tools/browse_cli.ts b/packages/evals/core/tools/browse_cli.ts index 0e8ec5c9a..140631524 100644 --- a/packages/evals/core/tools/browse_cli.ts +++ b/packages/evals/core/tools/browse_cli.ts @@ -619,11 +619,15 @@ class BrowseCliPageHandle implements CorePageHandle { } async represent(): Promise { + // A/B toggle for the lean-vs-full snapshot eval. `browse snapshot` is now + // lean by default (formatted tree only, no ref maps); set + // BROWSE_SNAPSHOT_FULL=1 to restore the pre-lean full output (tree + maps). + const full = process.env.BROWSE_SNAPSHOT_FULL === "1"; const snapshot = await this.runCommandAfterSelecting<{ tree: string; xpathMap?: Record; urlMap?: Record; - }>(["snapshot"]); + }>(full ? ["snapshot", "--full"] : ["snapshot"]); const content = snapshot.tree; return { @@ -632,7 +636,11 @@ class BrowseCliPageHandle implements CorePageHandle { metadata: { bytes: Buffer.byteLength(content, "utf8"), tokenEstimate: Math.ceil(content.length / 4), - refCount: Object.keys(snapshot.xpathMap ?? {}).length, + // xpathMap is only present with --full; otherwise derive the count from + // the formatted tree so this metric stays mode-independent. + refCount: snapshot.xpathMap + ? Object.keys(snapshot.xpathMap).length + : (content.match(/\[\d+-\d+\]/g)?.length ?? 0), }, raw: snapshot, }; From 1a2e881a9c535fc3501ca8c1069710dcfe32c046 Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 30 Jun 2026 16:59:10 -0700 Subject: [PATCH 2/3] refactor(cli): decouple ref-map omission from tree pruning in snapshot Addresses PR review: - Default snapshot now emits the full tree (no line pruning) and only omits the ref maps; --full adds xpathMap/urlMap. Verified default tree === --full tree. - Driver snapshot handler takes `full` instead of overloading `compact`. - Simplify the command description and drop verbose/historical comments. - Add a driver test: default omits maps, --full includes them, maps cached either way. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/src/commands/snapshot.ts | 16 +++------- .../cli/src/lib/driver/commands/snapshot.ts | 19 ++++++------ packages/cli/tests/driver-commands.test.ts | 31 +++++++++++++++++++ packages/evals/core/tools/browse_cli.ts | 7 ++--- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/packages/cli/src/commands/snapshot.ts b/packages/cli/src/commands/snapshot.ts index cb7ce506a..0060dc12a 100644 --- a/packages/cli/src/commands/snapshot.ts +++ b/packages/cli/src/commands/snapshot.ts @@ -8,7 +8,7 @@ import { export default class Snapshot extends BrowseCommand { static override description = - "Print the active page accessibility snapshot and cache refs for element commands. Lean by default (formatted tree only); pass --full to also include the ref maps (xpathMap, urlMap). The ref maps are always cached for element commands regardless, and can be printed on demand with `browse refs`."; + "Print the active page accessibility snapshot and cache refs for element commands. Pass --full to also include the ref maps (xpathMap, urlMap), or run `browse refs` to print them."; static override examples = [ "browse snapshot", @@ -20,12 +20,11 @@ export default class Snapshot extends BrowseCommand { static override flags = { ...driverCommandFlags, full: Flags.boolean({ - description: - "Include the ref maps (xpathMap, urlMap) in addition to the formatted tree. Restores the pre-lean default output.", + description: "Also include the ref maps (xpathMap, urlMap).", }), compact: Flags.boolean({ description: - "Deprecated: snapshots are lean by default, so this flag has no effect. Use --full to include the ref maps.", + "Deprecated and has no effect; use --full to include the ref maps.", }), filter: Flags.string({ description: @@ -40,20 +39,15 @@ export default class Snapshot extends BrowseCommand { async run(): Promise { const { flags } = await this.parse(Snapshot); - // `--compact` is now the default. Keep accepting it as a no-op alias so - // existing callers don't break, but nudge interactive users toward --full. - // Warn on stderr only, gated on a TTY, so agents parsing stdout JSON (or - // capturing stderr) get no extra noise. if (flags.compact && process.stderr.isTTY) { this.warn( - "`--compact` is deprecated: `browse snapshot` is lean by default. Use `--full` to include the ref maps.", + "`--compact` is deprecated and has no effect; use `--full` to include the ref maps.", ); } await runDriverCommandFromFlags( "snapshot", { - // Lean (no ref maps) unless --full is requested. - compact: !flags.full, + full: flags.full, filter: flags.filter, maxDepth: flags["max-depth"], }, diff --git a/packages/cli/src/lib/driver/commands/snapshot.ts b/packages/cli/src/lib/driver/commands/snapshot.ts index f174833a5..e77bf5d69 100644 --- a/packages/cli/src/lib/driver/commands/snapshot.ts +++ b/packages/cli/src/lib/driver/commands/snapshot.ts @@ -5,9 +5,9 @@ import type { DriverCommandHandlers } from "./types.js"; export const snapshotHandlers: DriverCommandHandlers = { async snapshot(manager, params) { - const { compact, filter, maxDepth } = z + const { full, filter, maxDepth } = z .object({ - compact: z.boolean().optional(), + full: z.boolean().optional(), filter: z.string().optional(), maxDepth: z.number().int().nonnegative().optional(), }) @@ -20,18 +20,17 @@ export const snapshotHandlers: DriverCommandHandlers = { }); const tree = formatSnapshotTree(snapshot.formattedTree, { - compact, filter, maxDepth, }); - if (compact) { - return { tree }; + if (full) { + return { + tree, + urlMap: snapshot.urlMap, + xpathMap: snapshot.xpathMap, + }; } - return { - tree, - urlMap: snapshot.urlMap, - xpathMap: snapshot.xpathMap, - }; + return { tree }; }, }; diff --git a/packages/cli/tests/driver-commands.test.ts b/packages/cli/tests/driver-commands.test.ts index b028c6171..faa2c43f6 100644 --- a/packages/cli/tests/driver-commands.test.ts +++ b/packages/cli/tests/driver-commands.test.ts @@ -7,6 +7,7 @@ import { describe, expect, it, vi } from "vitest"; import { resolveSelector } from "../src/lib/driver/commands/selectors.js"; import { formatSnapshotTree } from "../src/lib/driver/commands/snapshot-format.js"; +import { snapshotHandlers } from "../src/lib/driver/commands/snapshot.js"; import { runtimeHandlers } from "../src/lib/driver/commands/runtime.js"; import { tabHandlers } from "../src/lib/driver/commands/tabs.js"; import { DRIVER_COMMAND_NAMES } from "../src/lib/driver/commands/types.js"; @@ -335,6 +336,36 @@ describe("driver commands", () => { ); }); + it("omits ref maps by default and includes them with --full", async () => { + const snap = { + formattedTree: "[0-1] RootWebArea: Test\n [0-2] button: Go", + urlMap: { "0-2": "https://example.com/go" }, + xpathMap: { "0-1": "/", "0-2": "/button[1]" }, + }; + const setRefMaps = vi.fn(); + const manager = { + activePage: async () => ({ snapshot: async () => snap }), + setRefMaps, + } as unknown as Parameters< + NonNullable<(typeof snapshotHandlers)["snapshot"]> + >[0]; + + const lean = await snapshotHandlers.snapshot!(manager, {}); + expect(lean).not.toHaveProperty("xpathMap"); + expect(lean).not.toHaveProperty("urlMap"); + + const full = await snapshotHandlers.snapshot!(manager, { full: true }); + expect(full).toMatchObject({ + urlMap: snap.urlMap, + xpathMap: snap.xpathMap, + }); + + expect(setRefMaps).toHaveBeenCalledWith({ + urlMap: snap.urlMap, + xpathMap: snap.xpathMap, + }); + }); + it("exposes descriptive help for the new driver command surface", async () => { const commands = [ ["open"], diff --git a/packages/evals/core/tools/browse_cli.ts b/packages/evals/core/tools/browse_cli.ts index 140631524..b877f15b6 100644 --- a/packages/evals/core/tools/browse_cli.ts +++ b/packages/evals/core/tools/browse_cli.ts @@ -619,9 +619,7 @@ class BrowseCliPageHandle implements CorePageHandle { } async represent(): Promise { - // A/B toggle for the lean-vs-full snapshot eval. `browse snapshot` is now - // lean by default (formatted tree only, no ref maps); set - // BROWSE_SNAPSHOT_FULL=1 to restore the pre-lean full output (tree + maps). + // BROWSE_SNAPSHOT_FULL=1 includes the ref maps in the snapshot. const full = process.env.BROWSE_SNAPSHOT_FULL === "1"; const snapshot = await this.runCommandAfterSelecting<{ tree: string; @@ -636,8 +634,7 @@ class BrowseCliPageHandle implements CorePageHandle { metadata: { bytes: Buffer.byteLength(content, "utf8"), tokenEstimate: Math.ceil(content.length / 4), - // xpathMap is only present with --full; otherwise derive the count from - // the formatted tree so this metric stays mode-independent. + // Count refs from xpathMap when present, else from the tree. refCount: snapshot.xpathMap ? Object.keys(snapshot.xpathMap).length : (content.match(/\[\d+-\d+\]/g)?.length ?? 0), From c05d2a4eb4ba8aebb86325868db46e2bdc5f4e7a Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 30 Jun 2026 17:05:20 -0700 Subject: [PATCH 3/3] test(cli): assert ref maps are cached on the lean snapshot call Verify setRefMaps runs on the default (lean) call specifically via call-count + last-called-with, so caching moving into the --full path would fail the test. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/tests/driver-commands.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/cli/tests/driver-commands.test.ts b/packages/cli/tests/driver-commands.test.ts index faa2c43f6..9d3afef09 100644 --- a/packages/cli/tests/driver-commands.test.ts +++ b/packages/cli/tests/driver-commands.test.ts @@ -353,17 +353,18 @@ describe("driver commands", () => { const lean = await snapshotHandlers.snapshot!(manager, {}); expect(lean).not.toHaveProperty("xpathMap"); expect(lean).not.toHaveProperty("urlMap"); - - const full = await snapshotHandlers.snapshot!(manager, { full: true }); - expect(full).toMatchObject({ + expect(setRefMaps).toHaveBeenCalledTimes(1); + expect(setRefMaps).toHaveBeenLastCalledWith({ urlMap: snap.urlMap, xpathMap: snap.xpathMap, }); - expect(setRefMaps).toHaveBeenCalledWith({ + const full = await snapshotHandlers.snapshot!(manager, { full: true }); + expect(full).toMatchObject({ urlMap: snap.urlMap, xpathMap: snap.xpathMap, }); + expect(setRefMaps).toHaveBeenCalledTimes(2); }); it("exposes descriptive help for the new driver command surface", async () => {