| Age | Commit message (Collapse) | Author |
|
Remove browser instruction text repeated across tool descriptions, parameter schemas, tool
guidelines and the global browser prompt. Tool names, schemas, handlers and concurrency are
unchanged; shared recovery and ownership guidance is now available before and after the browser
connects, and the prompt names which tools are serialized versus observations.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
|
cdp/attach.ts and cdp/window.ts were a module each for a single
`map(await session.callBrowser(...))` one-liner that took the session as
its first argument — two files, two imports and two names for what the
session can expose itself. They are now session.attach(targetId) and
session.windowId(targetId).
session.ts also repeated `targetId ? tabs.get(targetId) : undefined`
eight times; that is now currentTab().
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
form.ts exported resolveAndCall and detailsOf to form-batch.ts,
snapshot.ts exported buildTree and collectInteractiveTargets to
ref-resolve.ts, and screenshot.ts exported captureWithCrosshair to
click.ts — so importing one tool pulled in another tool's whole module.
The shared halves move out: ax-tree.ts (the AX tree model, its
construction and the queries over it), element-call.ts (calling a
function on a resolved element, and the PageResult it returns),
screenshot-capture.ts (capture, resize, crosshair overlay). Each tool
file now holds its tools and its own presentation, nothing else.
NavOutcome stops being exported; nothing outside navigate.ts read it.
target.ts stays a module rather than folding into click.ts as planned:
it is already helper-only and has its own test suite, and moving a tested
pure function into a tool file is the thing this commit removes. Having
one consumer is not the defect.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Four routes coexisted: cdpCall, a raw client.session().call, a session
held in a local and called directly, and client.evaluateJs — and five
files mixed two or three of them. The CdpError-to-ToolErr mapping was
hand-copied at nine sites, plus a private copy in isolated-tab.ts.
domains/cdp-call.ts now owns all of it: cdpErrToToolErr, cdpCall,
cdpCallOnTarget, cdpCallBrowser, evalJs. Every other file under
src/domains goes through them, which also removes a pile of
`if (!r.success) return err({ kind: "cdp_error", ... })` boilerplate in
favour of returning the failed Result as-is.
Two scanner rules keep it that way, scoped to src/domains/ so the layer
that legitimately owns the session — cdp/ and client.ts — is unaffected.
Scoping is new to the rule model; without it the rules fired on the
transport itself.
One deliberate behaviour change: a CDP timeout now surfaces as
kind "timeout" rather than "cdp_error". isolated-tab.ts already did this
and the other 13 sites flattened it; the shared mapper uses the honest
label rather than preserving the mislabel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Removes module banners, step markers, and comments restating the code.
Keeps one-line notes for safeJs's trust boundary, the select-match null
sentinels, the TUI width clamp, the single-window tab rule, the input
synthesis quirks (char events, keyCode, mousemove, focus, compositor),
the stale AX value, ignored-wrapper recursion, and isolated-tab routing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The ref-vs-x/y branch turned out to exist in one tool, not four; it is now
one helper with tests covering the half-specified-coordinate and stale-ref
cases. cdpCall folds the repeated cdp_error mapping at 24 active-session
call sites; target-scoped callOnTarget/callBrowser sites are left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
serialized: true was applied in registry.ts, away from the tools it
describes. Now a required concurrency field on each definition, with a
test pinning the serialized set so the classes cannot drift.
The lock check defaults to taking the mutex for any class that is not
explicitly "parallel", so a definition that bypasses defineBrowserTool at
runtime fails safe rather than racing on shared CDP state.
The test script gains --conditions=import: pi-coding-agent publishes no
"require" condition, so importing any tool definition from a CJS-loaded
test could not resolve it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Target.attachToTarget was decoded at 5 sites, Browser.getWindowForTarget
at 5, and Runtime.evaluate unwrapped at another. Each is now one typed
helper. Deletes the dead cdp/window-binding.ts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
* feat: pin the agent to a chosen browser profile (#15)
Harness tabs were created with a bare Target.createTarget, which places
them in Chrome's defaultBrowserContextId — and that value follows window
focus. Which profile the agent acted as therefore depended on which
browser window the user last clicked, so the same task could run as a
work account on one run and a personal account on the next.
/browser-profile lists the connected browser's profiles as "Name (email)"
and pins one to ~/.pi/agent/browser-harness.json, so the choice outlives
the session. The same picker appears on first browser_setup when nothing
is pinned; with no interactive UI, or on cancel, setup continues with a
note and the previous behavior.
Chrome offers no direct route to another profile: Target.createTarget
rejects a foreign browserContextId, and openerId does not inherit the
opener's context. The window is opened through the browser's own command
line (--profile-directory, handed to the running browser by Chromium's
ProcessSingleton) and identified by a unique file:// sentinel page, since
a bare about:blank#token argument is discarded. Further tabs come from
window.open evaluated with userGesture: true — the only CDP-reachable way
to place a tab in a non-default browser context — correlated by a unique
token so concurrent isolated tabs stay distinct. A failed handshake is
reported, never silently redirected to another profile.
All four tab-creation sites now funnel through cdp/target-factory, so
open_urls and the isolated tabs behind web_search / read_page cannot run
with a different profile's cookies than the visible tabs.
Also fixed along the way: snap Chromium is discovered, Windows uses
%LOCALAPPDATA%, browsers launched with an explicit --user-data-dir are
found, the running browser is chosen by ranking candidates against the
connected user-data-dir rather than taking the first process found, and
an installed-but-closed browser no longer reads as running.
CI runs the profile suites and a real-browser end-to-end test on ubuntu,
macOS, and Windows — profile discovery is the one part of the harness
whose behavior genuinely differs per OS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: single-flight harness window creation
Isolated tabs run concurrently (browser_read_page is registered
unserialized), so several could each find "no harness window" and create
one — two blank windows before this feature, two LAUNCHED profile windows
after it. ensureHarnessWindow now shares one in-flight creation per
client.
Sharing introduces a second hazard: freshlyCreated is a claim on the seed
tab, and if every waiter received it, they would all drive the same tab.
Only the caller that started the creation keeps the claim; the rest open
their own tab beside it.
Covered by test/manual/profile-concurrency-test.ts (five concurrent
isolated tabs, pinned and unpinned: distinct targets, one window, correct
profile), wired into CI on Linux only — the coordination is pure JS and
identical across platforms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: tilde expansion differs by purpose on Windows
CI on windows-latest caught this: join("~", "x") yields "~\x" there, and
expandTilde only handled "~/".
The two uses are not the same, so they no longer share a helper. The
agent dir stays byte-compatible with pi's own expandTildePath ("~/" only,
every platform) — being more lenient would put the pin file somewhere pi
never looks. $CHROME_USER_DATA_DIR is ours to interpret, so it accepts
"~\" as well, which is what a Windows user would type.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
- Add forms domain with a universal field setter that auto-detects element
type and fires framework-compatible input events (React native-setter trick)
- Add window-binding helper to capture and bind the real Chrome windowId as the
session's durable window identity on every new-window code path
- Wire supporting changes across CDP session/ownership, client, navigate,
isolated-tab, prompt, and state
- Add manual tests for forms and window binding
- Bump version 0.8.3 -> 0.9.0
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
* feat: Unix socket daemon with auto-reconnect for persistent Chrome CDP
Eliminates the 'Allow Remote Debugging' Chrome prompt that appeared on
every pi session by multiplexing pi instances through a single persistent
daemon process.
Key additions:
- Unix socket IPC (net.createServer + readline JSON framing)
- CdpBridge with ID multiplexer + event router
- DaemonTransport implementing CdpTransport interface
- Auto-spawn with ensureDaemon() (lazy, checks socket)
Fixes since initial implementation:
- Bridge auto-reconnects to Chrome with exponential backoff (1s-60s).
Previously the bridge tried once at startup and permanently failed
if Chrome wasn't ready — causing 'Chrome not connected' on every
browser tool call.
- Daemon spawn uses project-local tsx binary instead of global --import
flag, fixing silent spawn failure (ERR_MODULE_NOT_FOUND).
- Stale ctx crash: persistState in onOwnershipChange wrapped in
try/catch to prevent CDP event consumer crashes.
49 test assertions across 4 test files. Zero TypeScript errors.
* feat: on-demand browser initialization
Browser daemon no longer auto-starts on session_start. Instead:
- session_start creates the client lazily but does NOT connect or spawn
the daemon. Zero Chrome prompts for non-browser pi sessions.
- Browser tool wrapper checks for daemon socket before each call.
Socket missing → clear error: 'Run /browser-setup first'.
Socket exists → proceeds silently (daemon already running).
- /browser-setup is the explicit, one-time init point. It spawns
the daemon and connects to Chrome only when the user opts in.
- SKILL.md updated with Connection Setup instructions telling agents
to direct users to /browser-setup when socket is missing.
* fix: checkChromeRunning on macOS — match by substring not exact
ps -A -o comm= returns full executable paths on modern macOS
(e.g. /Applications/Google Chrome.app/Contents/MacOS/Google Chrome),
not the basename. The old exact-match check (browserProcesses.includes(l))
never matched, causing false 'No browser instance running' errors.
Changed to substring includes() with helper/renderer/crashpad filtering.
* feat: browser_setup as agent-callable tool
The agent can now self-recover from 'Browser harness not initialized'
errors by calling browser_setup directly — no user mediation needed.
- Extracted shared performSetup() from the slash command into setup.ts
- Created src/domains/setup.ts with ensureAlive:false to bypass the
socket guard (this tool IS the setup, can't require itself)
- Registered browser_setup in the tool registry
- Updated SKILL.md: agent calls browser_setup directly, not ask user
- Removed stale test/manual/spawn-test.ts
* release: v0.6.0 — per-tab isolation, Unix socket daemon, on-demand init
|
|
prompts
Adds two new tools and pivots the agent's default workflow away from
screenshots toward structured page data.
browser_snapshot — CDP accessibility tree (roles, names, states, hierarchy)
with click coordinates @(x,y) attached to every interactive element via
DOM.getBoxModel (parallel, 1.5s aggregate budget). Pass directly to
browser_click — no screenshot round-trip. Optional includeScreenshot:true
for visual confirmation. format:"json" returns a slim structure with full
box per node.
browser_network_requests — replaces the deprecated browser_get_network_log
placeholder. Filters by urlPattern (substring or /regex/), method, status,
resource type, recency. Optional response-body capture under a 5s aggregate
budget with 50KB per-body cap. Buffer is page-scoped (cleared on tab switch),
500 records/tab, with a bufferOverflowed flag on drain.
CDP plumbing: in-process aggregator in src/cdp/network-buffer.ts wired into
the existing single-consumer event loop. Accessibility domain enabled
alongside Page/DOM/Runtime/Network on every attach.
Ctrl+O expand/collapse on three tools' renderResult — browser_snapshot,
browser_network_requests, browser_execute_js. Compact-by-default views with
keyHint("app.tools.expand") that respects user keybinding remaps.
Tool prompts pivoted snapshot-first: browser_screenshot is documented as
LAST RESORT for visual verification only; browser_snapshot is the default
for understanding pages; browser_execute_js is the default for surgical
reads. browser_click and browser_open_urls guidance follows.
SKILL.md collapsed from ~342 to ~53 lines — keeping only what isn't already
in tool prompts: the decision tree, connection rules, browser_run_script
daemon bindings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
matching, download auto-mkdir, session recovery, docs updates
Bug fixes:
- list_tabs: show full 32-char targetIds (was truncated to 8 chars)
- switch_tab: prefix matching fallback for hex prefixes >= 8 chars
- download: auto-create directory with mkdir -p
- ensureAlive: Runtime.evaluate probe to recover from crashed page targets
Docs:
- dispatch_key: clarify DOM-only event behavior
- navigate: warn about Google anti-bot limitation
- network_log: update deprecated promptSnippet
- SKILL.md: document actual daemon API (evaluateJs, pageInfo, session.call)
- SKILL.md: add troubleshooting for sharp/maxDim and Google navigation
- sharp: add to optionalDependencies for screenshot maxDim auto-resize
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|