all repos — sz-chrome-remote-cdp @ cdcfb137a83d178d0d22df0eb97aae768e8abf9c

pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search

cdcfb137
docs: /browser-target, and why switching restarts the daemon
Igor Soarez igor@soarez.org
Wed, 29 Jul 2026 10:41:47 +0100
08e55875
feat(cdp): /browser-target to switch browsers without leaving pi

Switching between the shared browser and this machine's own previously meant
leaving pi and relaunching with BU_CDP_HTTP — and, less obviously, killing the
daemon first, or the switch silently did not happen.

  /browser-target                 show the current target
  /browser-target local           this machine's browser
  /browser-target default         the built-in default
  /browser-target host:port       somewhere else

Precedence becomes env > stored > default. The environment still wins so a
one-off `BU_CDP_HTTP=… pi` is never silently overridden by a choice made days
ago; when it is set, the command says so rather than pretending to obey.
Clearing falls back to the default rather than to local — "use the usual
browser", not "go local".

The daemon is the reason this needed more than a settings write. It is
machine-wide, resolves its target once per process, and holds the WebSocket to
the old browser, so re-reading the store would leave tool calls going to the
old browser while /browser-status reported the new one. The command therefore
replaces the process: the daemon now honours an inbound control/shutdown (the
action already existed in the protocol, previously only sent daemon→client),
and stopDaemon() asks for it and polls until the process is really gone before
ensureDaemon() respawns it against the new target. /browser-status now reads
the store too, so it cannot disagree with what the tools will do.

Persistence mirrors the profile pin: atomic temp-file-plus-rename, and every
read failure degrades to "nothing stored" so a corrupt file falls back to the
default instead of breaking browser control. It is a separate file from the
pin, so damage to one cannot cost you the other. The stored value stays in
BU_CDP_HTTP's vocabulary so there is exactly one resolution path.

62 tests: the full precedence table including the stored tier, that a bad value
is blamed on the source that set it rather than sending you hunting through
shell config, that the command only accepts values resolution can actually use,
and eight ways of damaging the store file.
Igor Soarez igor@soarez.org
Wed, 29 Jul 2026 10:38:13 +0100
6eda4b9c
docs: castle is now the default; document the override table

BU_CDP_HTTP is an override rather than a requirement, and /browser-status
explains the lazy connection instead of showing a bare red light.
Igor Soarez igor@soarez.org
Tue, 28 Jul 2026 22:09:36 +0100
95ff5e8e
feat(cdp): default to the shared browser, and say so in /browser-status

Two follow-ups to BU_CDP_HTTP.

Default the endpoint. The remote host is now a built-in default
(DEFAULT_REMOTE_CDP), so nothing has to export BU_CDP_HTTP just to get the
usual setup. The variable becomes an override rather than a requirement.

Precedence is deliberately asymmetric, in src/cdp/endpoint.ts:

  BU_CDP_HTTP=host:port   that endpoint; unreachable is a hard error
  BU_CDP_HTTP=local|off   upstream behaviour, this machine's own browser
  unset                   the default, falling back to local when unreachable

An explicitly configured endpoint is an instruction, so failing to reach it must
not quietly automate a different browser - that would drive the operator's own
signed-in Chrome instead of the shared one. The built-in default is only a
preference, so a laptop away from the network still gets the local browser the
extension was originally written for.

Resolution is pure, with no I/O, so the status command can report the
configured intent without touching the network and the daemon can resolve the
same way in its own process.

/browser-status no longer reports a bare "Disconnected". The connection is lazy
- nothing dials the browser until the first browser_* tool runs - so a fresh
session always showed a red light that reads as a fault. It now distinguishes
"not connected yet, connects on first use" from "target unreachable", and names
the target with a reachability check, so the common confusion answers itself.

26 tests for the resolution table, covering the aliases, malformed values that
must be rejected rather than coerced, and that the default constant is itself
well-formed - if it ever stopped parsing, every machine would silently fall
back to local.
Igor Soarez igor@soarez.org
Tue, 28 Jul 2026 22:08:44 +0100
bfb533fc
docs: fork notes — why BU_CDP_HTTP exists, install, upstream merges

Kept out of README.md so upstream's own README continues to merge cleanly.
Igor Soarez igor@soarez.org
Tue, 28 Jul 2026 21:48:33 +0100
233f29ae
feat(cdp): BU_CDP_HTTP to attach to a browser on another host

Upstream can only drive a browser on localhost. The pi extension always goes
through the daemon (src/index.ts uses createDaemonTransport), the daemon
resolves Chrome with discoverWsUrl() (src/daemon/bridge.ts), and that probe is
hardcoded to 127.0.0.1 (src/cdp/discovery.ts). BU_CDP_WS is read only by the
in-process client (src/client.ts), which that path never touches, so it has no
effect on the extension - the only workaround is relaying the remote browser
onto localhost first.

BU_CDP_HTTP="host:port" points at the remote CDP HTTP endpoint. It is resolved
inside discoverEndpoint(), so the daemon and the client both honour it.

Deliberately an HTTP endpoint rather than a WebSocket URL: it is re-resolved on
every discovery call, so the browser UUID is re-read after the remote browser
restarts. Chrome re-mints that UUID on every launch, so a pinned WebSocket URL
goes stale as soon as the remote browser is recycled.

Also skips performSetup's local-process precondition when BU_CDP_HTTP is set:
isBrowserRunning() enumerates local processes and can never see a remote
browser. Step 4 still fails loudly if it is unreachable.

No userDataDir is returned for remote endpoints, since the profile registry
lives on the remote host - profile enumeration degrades to the pre-profile
behaviour rather than reading the wrong machine's Local State.
Igor Soarez igor@soarez.org
Tue, 28 Jul 2026 21:46:00 +0100
384688bf
fix(daemon): let pi exit after a session has connected to the daemon (#40)

The client's daemon socket is kept open across sessions by design; in pi -p it was the last
handle keeping Node alive, so a run that had connected to the browser hung after its answer.
Unref the socket; in-flight requests hold their own timers.

Fixes #39

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Mon, 12 Oct 2026 00:41:12 +0530
2601c90b
fix: browser_download no longer hijacks the user's own downloads (#33)

Browser.setDownloadBehavior was sent without a browserContextId, so the override applied to the
user's default context and outlived the session. browser_download now takes restore:true, tracks
the override this process set, and a clean session shutdown restores Chrome's default with a
bounded call that is skipped when the browser is already gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Uttkarsh 38376852+uttkarsh-26@users.noreply.github.com
Mon, 12 Oct 2026 00:09:18 +0530
014de0ec
refactor: remove duplicate browser prompt instructions (#35)

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>
Patrick Barattin p.barattin@gmail.com
Sun, 11 Oct 2026 20:25:38 +0200
79526b01
test: keep host-provided packages out of dependencies (#36)

Adds a manifest guard (scripts/check-manifest.ts + test/util/manifest-test.ts) so npm run check
fails if a host-provided package such as typebox lands in dependencies, plus a daemon-startup
troubleshooting section in the skill.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Uttkarsh 38376852+uttkarsh-26@users.noreply.github.com
Sun, 11 Oct 2026 23:40:29 +0530
0fe64593
fix(daemon): stop importing typebox in the daemon process (#38)

pi installs packages with dev and peer dependencies omitted and supplies typebox only to the
extension host. The daemon is a separate Node process whose import graph reached typebox via
src/cdp/discovery.ts, so from a pi-managed install it crashed with "Cannot find module 'typebox'"
before binding its socket. Parse /json/version with the package's own guards, and add a test that
keeps every package reachable from the daemon a runtime dependency.

Fixes #34

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sun, 11 Oct 2026 23:33:25 +0530
7833e2ec
fix: append browser state to the session only when it changes (#37)

persistState appended a browser-harness-state entry on every ownership callback, session_tree
and shutdown, so the append-only session file and Pi Web's "+N custom" chain grew without bound.
A snapshot is now written only when it differs from the last one persisted or restored.

Fixes #26

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sun, 11 Oct 2026 23:28:39 +0530
ff449d9f
fix(daemon): resolve tsx from the filesystem when Node's resolver cannot see node_modules (#28)

Inside a Bun-compiled pi binary (pi.exe), createRequire().resolve("tsx/cli") is rooted at the
embedded virtual filesystem and cannot see the extension's real node_modules, so browser_setup
failed instantly. The resolver result is now checked on disk and, when it fails, the tsx CLI is
found by walking up the node_modules chain. Under Bun the daemon runs on `node` from PATH
(process.execPath is the pi binary itself); PI_BROWSER_NODE overrides it.

Fixes #27

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PhysicistLeon physicistleon@gmail.com
Sun, 11 Oct 2026 19:36:56 +0300
8d7a067c
feat: add per-tab request headers (#30)

Expose explicit set and clear tools so environment-routed pages can be opened without leaking header values or carrying headers across unrelated browsing.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

Co-authored-by: zouzhigang <zouzhigang@bytedance.com>
astarring zouzhigang85@gmail.com
Wed, 09 Sep 2026 02:35:42 +0800
6f455ed0
chore(release): 0.11.1 (#24)
Uttkarsh 38376852+uttkarsh-26@users.noreply.github.com
Thu, 13 Aug 2026 14:03:46 +0530
d49068d9
fix: accept Windows script paths and start the daemon from a real install (#23)

Two independent Windows/production defects reported in #22.

browser_run_script rejected every absolute path on Windows. The allowed-root
check compared with a hardcoded `${root}/`, but path.resolve returns
backslash-separated paths there, so a file inside os.tmpdir() was refused even
though the rejection message listed tmpdir as allowed. Decide containment with
path.relative instead: it is separator-agnostic and carries each platform's own
case rules, so it also accepts a differing drive-letter case that a path.sep
patch would still reject. Escapes via `..` and sibling directories sharing the
root's name prefix stay rejected, as before.

The daemon could not start under `npm omit=dev`: it is spawned through tsx,
which was declared only as a dev dependency. Moving tsx to dependencies is not
enough on its own — pi installs extensions into one flat hoisted tree, so tsx
lands beside the package and the fixed <pkg>/node_modules/.bin/tsx lookup still
misses, falling through to an `npx tsx` that resolves against the wrong cwd and
dies against stdio: "ignore". Resolve the CLI entry with Node's own resolver,
which walks up to the hoisted copy. That removes the shell entirely, and with it
the Windows .cmd shim, windowsVerbatimArguments, and the manual quoting that
paths containing spaces needed.

Both fixes live in platform-branching code, so their tests are registered in the
three-OS CI matrix that already exists for that purpose — a Linux-only run
cannot exercise either one.
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Thu, 06 Aug 2026 00:47:15 +0530
d9bc46b5
fix: reconnect to Chrome only on explicit browser demand (#18)

* fix: reconnect to Chrome only on browser demand

* fix(daemon): gate stale CDP bridge reconnect attempts

* Harden bridge reconnect lifecycle against stale socket callbacks

* fix(daemon): handle stale attempt failures without deferred settlement

* test: align waitFor assertions and stale-socket case labels

* fix(daemon): re-check socket after connect wait in handleRequest

Restores the post-wait re-check from the original PR branch (dropped during
the main rebase merge); TypeScript needs the local narrowing before send.

* fix(daemon): reject in-flight requests when an open connection drops

`failCurrentAttempt` delegated callback rejection to `settleAttempt`, but an
attempt that reached `open` had already settled, so the close path returned
early and never answered its in-flight requests. They hung until the 10s
command timeout instead of failing immediately, which the on-demand reconnect
change had promised to preserve.

Reject the attempt's callbacks directly in `failCurrentAttempt`, before the
close handler notifies clients.

Move the connection-lifecycle tests out of test/manual/ — which nothing runs —
into test/daemon/, where `npm test` picks them up, and convert the hand-rolled
pass/fail counters to node:test. Adds a case covering the drop path above; it
fails without the fix.

---------

Co-authored-by: Hermes Subagent <hermes-subagent@example.com>
Co-authored-by: Hermes Agent <agent@example.com>
Co-authored-by: uttkarsh-26 <uttkarsh@users.noreply.github.com>
Co-authored-by: amankumarsingh77 <amankumarsingh7702@gmail.com>
Uttkarsh 38376852+uttkarsh-26@users.noreply.github.com
Thu, 06 Aug 2026 00:20:49 +0530
31c1b27a
chore: remove the .rpiv folder

Four planning artifacts from the per-tab-isolation and multi-agent-safety
work, all of which has since landed. Ignored going forward so local .rpiv
scratch stays out of the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 02 Aug 2026 19:20:21 +0530
da645200
docs: remove the docs/ folder

The plans, specs, and audit reports under docs/superpowers/ described work
that has since landed, and ARCHITECTURE.md duplicated what the boundary
scanner and the tool contract already enforce in code. CONTRIBUTING.md now
points at the source that enforces each rule instead of at prose about it.

.gitignore keeps docs/ ignored so local scratch docs stay out of the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 02 Aug 2026 19:17:20 +0530
f20fdf7f
chore(release): 0.11.0

Breaking: the deep-research skill, command, and subagent are gone.
Also carries the 0.10.3 fixes, which were tagged but never published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 02 Aug 2026 19:10:09 +0530
85a716ce
chore: ignore .worktrees/ and .harness/ pipeline artifacts

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 23:17:27 +0530
dcb79fc6
refactor: merge nine files whose reason to exist was gone

src/ goes from 84 files to 75 at the same line count — every merge below
had exactly one consumer.

cdp/event-queue.ts and cdp/pending-requests.ts become daemon/queue.ts.
I split these in the daemon-internals commit on the grounds that they
held two unrelated concerns, but the transport commit had already deleted
the second transport, so both halves had one consumer and the split
bought nothing. They also sat in cdp/ while only daemon/ used them.

cdp/transport.ts held only the CdpTransport interface after the second
transport went; five of its six consumers import it type-only, so it
moves to cdp/types.ts. cdp/evaluate.ts moves into cdp/session.ts rather
than into its lone consumer isolated-tab.ts — it reaches session.call
directly, so under src/domains/ it would trip the raw-cdp-call rule.

util/keycodes.ts and util/time.ts fold into their only callers
(keyboard.ts, page.ts); util/bytes.ts into util/truncate.ts, which
already owns output-size formatting; readpage/capture.ts and
search/extract.ts into the tools beside them; profile/picker.ts into
profile/command.ts.

Kept deliberately: mutex.ts and domains/target.ts each have their own
test suite, and folding target.ts into click.ts would make a tool file
export a helper. sharp-shim.ts is an isolation boundary carrying two
allowlisted casts. result.ts, guards.ts, js-template.ts and paths.ts are
small but have 44, 12, 8 and 3 consumers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 22:41:18 +0530
b484c017
refactor: act on the fallow audit

Unused exports it found that my own sweep missed, because I had grepped
by name and these names occur as local variables elsewhere: deadline and
the Deadline type in util/time.ts were genuinely unreferenced and are
gone; isWireMessage, resolveUserDataDir, CLEAR_SELECTION_LABEL and the
ExpandableText type are used only inside their own file and no longer
export. isWireMessage became internal when server.ts moved to the shared
serialize/deserialize codec.

Duplication: console-buffer and network-buffer were the same
capacity-bounded map with an overflow flag, sharing eviction, the
drain tail and clear() across three clone groups. That is now
cdp/record-store.ts, with tests — neither buffer had any. The three
Network follow-up events also repeated the same decode-and-find preamble;
one `update` helper covers them, and drops the redundant optional
chaining that survived a `!id` guard. formatBytes existed verbatim in
js.ts and network.ts; it is now util/bytes.ts. Duplication overall:
2.7% to 2.0%, and none of the remainder is in src.

.fallowrc.json records the two entry points the tool cannot infer — the
pi extension (declared under pi.extensions) and the daemon (spawned by
path) — without which five reachable files read as dead. sharp is
ignored because it is loaded through a variable specifier in a dynamic
import so it can stay optional. agentDir is exported solely for a test's
dynamic import, so it is suppressed by name rather than globally
enabling ignoreExportsUsedInFile, which would have hidden four of the
five findings above.

Not acted on: the "split high-impact file" suggestions for session.ts,
client.ts and ref-resolve.ts (119 LOC) are size and fan-in heuristics
with no defect behind them, and splitting them would add the indirection
this refactor set out to remove. The claim that state.ts is 100% dead is
an artifact of the missing entry point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 22:29:16 +0530
1b5c4240
docs: record the new contract and the two knobs that now work

ARCHITECTURE.md: the layer map gains domains/cdp-call.ts as the single
door and the helper-module/tool-module split; the "cdpCall vs
session.call" section is replaced, since snapshot.ts no longer mixes
styles — it swallows failures through cdpCall instead of around it. Rule 1
now says tool files export only tools, rule 7 names the enforced scanner
rules, and the two 250-line exceptions are recorded as largely settled
(snapshot 477 to 256, form 409 to 283). Also notes the one honest gap:
session.attach and session.windowId perform CDP calls the rules do not
match.

README: BU_CDP_WS carries its daemon-restart caveat, BH_DEBUG_CLICKS is
documented as the flag's equivalent, and the tool count matches the
registry (38 to 40).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 22:11:24 +0530
b913f2d4
refactor: daemon internals, and fix a leak the merge exposed

IdMultiplexer and EventRouter were the only classes in src/; they are now
createIdMultiplexer/createEventRouter closures like every other factory
here. The multiplexer kept {clientId, localId} per daemon id while the
bridge kept {send, clientId, localId, timer, isAttach} under the same key
— two maps of overlapping data, and mux.resolve()'s return value was
discarded at three of four call sites. One map now holds the union.

That merge fixes a real leak: removeClient cleared the multiplexer but
never the callbacks map, so a disconnected client's timers stayed armed
and later fired a response at a socket that was gone. clearClient now
cancels them, and a test pins it.

handleRequest polled every 250ms for up to 15s per request when Chrome
was down; N queued requests now await one connect signal.

server.ts uses its own protocol's serialize/deserialize instead of
hand-rolled JSON, and broadcast no longer routes through `this` — the
only `this` in the codebase, and the one thing that breaks when the
server object is destructured.

cdp/event-queue.ts held two unrelated concerns; the request/timeout half
moves to cdp/pending-requests.ts. cdp/daemon-transport.ts is the client
half of the daemon protocol, so it moves to daemon/transport.ts.

test/daemon/ needs adding to the test glob to run at all — there was no
daemon test directory before because none of this was reachable without
live Chrome.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 22:08:16 +0530
c8808251
refactor: attach and windowId become session methods

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 22:01:47 +0530
86045450
refactor: helpers live in helper modules, not in tool files

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 22:00:33 +0530
632600ec
refactor: one door from a tool to the browser

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 21:55:55 +0530
893a9d33
refactor: one CDP transport, not two

createCdpTransport was a second full WebSocket-to-Chrome implementation
reachable only through client.ts's `?? createCdpTransport()` fallback,
which production never took — every session and every automated test
passes the daemon transport. transport.ts now holds only the CdpTransport
interface, and the client requires a transport instead of inventing one.

The "Session with given id not found" match that both transports carried
becomes classifyRemoteError in cdp/errors.ts, next to the error kind it
produces.

test/manual/profile-concurrency-test.ts was the sole caller of the
fallback: it drives its own Chrome on a private port. It now spawns a
daemon per scenario with BU_CDP_WS set, and refuses to run when a daemon
is already up, since that one is bound to a different browser.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 21:50:38 +0530
6d300099
fix: make --browser-debug-clicks and BU_CDP_WS actually work

Both were documented and inert.

--browser-debug-clicks was registered as a flag and never read; the only
real switch was the undocumented BH_DEBUG_CLICKS env var. The flag now
feeds util/debug.ts, which the click tool consults, and the env var keeps
working as an equivalent.

BU_CDP_WS was read in the client and handed to transport.connect(), but
the daemon transport discards that argument and always dials the daemon
socket — and the daemon's own discovery never looked at the variable, so
setting it did nothing. The override now lives in discoverEndpoint, which
is what both processes go through, and the client's redundant read is
gone. The variable must be set before the daemon spawns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 21:48:35 +0530
aedbb88a
refactor: delete exports and knobs that nothing calls

isInternalUrl, SPECIAL_KEYS, andThen and mapErr had no callers in src or
test. BrowserState.remoteBrowserId was parsed on restore and never
written — the field that is actually read lives on DaemonStatus.

IpcServer.disconnectClient and IpcServer.clients() were unreachable, as
was EventRouter.allClients and a write-only lineCount counter.
DAEMON_STALE_SOCKET_CLEANUP was a hardcoded true with an if around it, so
the unlink now runs unconditionally.

CdpBridge.isAlive and getSessionOwner look equally dead from src/ alone,
but test/manual/bridge-test.ts drives both — they are the only seams that
suite has, so they stay.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 21:47:21 +0530
ea11182a
feat!: remove the deep-research feature

The harness ships browser tools; orchestrating a multi-agent research
workflow on top of them is the agent's job, not this extension's. Drops
the skill, the /deep-research command, and the web-search-researcher
subagent config.

browser_web_search and browser_read_page are untouched — the two tests
that covered their pure parsers move to test/domains/, where the test
glob still picks them up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 21:17:13 +0530
91f5ad12
docs: warn that the bug-audit line numbers predate the comment strip

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:41:29 +0530
3c4e0b21
chore: make the boundary scanner see every cast, not just the unsafe forms

The scanner banned as any, as {, as unknown as, non-null assertions and
ts-ignore, but said nothing about `as SomeNamedType` — so a new cast over
CDP or JSON data could be added and CI would stay green, which is the exact
regression the refactor exists to prevent. It also could not see a cast
whose target type sits on the following line.

Adds named-cast, function-cast and trailing-cast. All six surviving
assertions in src/ are now visible to the scanner and individually
exempted with a stated reason, so the allowlist is the complete inventory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:40:58 +0530
a9bdbd85
refactor: tidy the whitespace the comment removal left behind

Drops leading blank lines where a file header was removed, collapses
double blank lines, and closes the empty catch/handler blocks whose only
content had been a comment. Whitespace only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:34:49 +0530
34e5cfdc
refactor(test): strip comment noise

Removes suite banners, section dividers, and test-id markers that restate
the test names. Keeps one-line notes for the deleted-binary fixture order,
the never-arriving event consumer, the absent-vs-undefined key distinction,
the unexpected-method-echo rationale, and the real-browser teardown and
Local State flush quirks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:32:57 +0530
91a2fdef
refactor: strip comment noise from the root modules

Removes install/command banners and comments restating the code. Keeps
one-line notes for the per-session profile-pin re-read, the deliberately
surviving transport on shutdown, seed-before-attach under a pin, and the
starts-with-return wrap rule. src/prompt.ts is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:31:40 +0530
b67faca6
refactor(domains): strip comment noise

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:30:49 +0530
a6ec1d09
refactor(profile): strip comment noise

Removes module banners and comments restating the code. Keeps one-line
notes for the ProcessSingleton user-data-dir rule, the sentinel-URL and
async-spawn-error quirks, the replicated getAgentDir, pi's tilde-expansion
mirroring, the Windows wmic removal, the macOS/Linux process-identification
quirks, the picker's undefined-means-cancel contract, and the deliberately
unpersisted browserContextId.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:29:29 +0530
a2588bb3
refactor(daemon): strip comment noise

Removes module banners, section dividers, reference lists, and comments
restating the code. Keeps one-line notes for the Windows named-pipe
endpoint, the Windows unlink no-op, the do-not-unlink-on-rejection rule,
the `.cmd` shell requirement, shell-quoting of spaced paths, and the
in-place client promotion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:27:07 +0530
330767d8
refactor(cdp): strip comment noise

Removes module banners, section dividers, and comments restating the code.
Keeps one-line notes for the window.open/browserContextId constraint, the
seed-tab claim, the unverifiable-pin reuse rule, the SeedTargetProvider
rationale, the deliberate dialog retention, the discovery-time browser
quirks, and the guarded CDP decoder assertion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:25:38 +0530
12d7a38b
refactor(util): strip comment noise

Removes file-header banners and comments that restate the code. Keeps
one-line notes at the optional-sharp dynamic import, the sharp .default
probe, the `never` contravariance trick, the sanctioned renderCall cast,
and safeJs's interpolation rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:23:12 +0530
2b3eb8ad
docs: document the layer rules and tool authoring contract

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:13:30 +0530
758be304
style: drop the comments added by the bug-audit fixes

The audit fixes were required to add no comments; two doc blocks slipped through
review. Removed, leaving only comments that predate this task.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:01:05 +0530
5767a84e
docs: bug audit report

Thirty findings plus six investigated-not-bugs from the read-every-file sweep of
src/, with resolution status for the six the user approved for fixing and the
live-Chrome experiment F12 still needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 20:00:11 +0530
8ccff6c9
fix(domains): report a page-side throw instead of blaming the ref

runOnElement checked only whether Runtime.callFunctionOn reached Chrome, not
whether the function it ran threw. A page-side exception left result.value
undefined, the result guard rejected it, and browser_fill, browser_focus and
browser_select_option all reported "No element matched: <ref>" for an element
that was there — sending the agent to re-snapshot a ref that was fine and
discarding the real error. The throw is now surfaced as cdp_error carrying the
exception text, which is distinguishable from both the no-match and stale-ref
paths; those still fire for a genuinely missing or stale element. The selector
path already reported throws correctly via evaluateJs.

Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f2

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:57:27 +0530
cfa1892e
fix(domains): one fill engine, so browser_fill stops lying about checkboxes

browser_fill and browser_fill_form carried separate in-page implementations that
disagreed on the same element. The worst case was silent: browser_fill on a
checkbox fired the HTMLInputElement value setter, which writes the submit value
and leaves `checked` alone, then reported status ok — a no-op the agent had no
reason to doubt. Both tools now share one engine, with browser_fill's documented
<select> rejection kept as an explicit pre-check and per-tool focus behaviour kept
as a parameter.

Deliberate, user-approved behaviour changes:
  - browser_fill on a DISABLED input now returns an error instead of success. The
    old success was the bug; browser_fill_form already refused it.
  - browser_fill on a checkbox/radio now toggles `checked` instead of the value.
  - browser_fill_form on a contenteditable now uses execCommand("insertText"),
    so it drives ProseMirror/Slack-style editors as browser_fill already did.

browser_fill keeps appending interactiveDiff and republishing refs; fill_form
still does not.

Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f5

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:55:53 +0530
254de089
fix(domains): keep element refs stable across a mutation

interactiveDiff re-assigned refs by tree position after every fill, click and
select, so any element appearing or disappearing shifted the numbering and a ref
the agent was still holding silently resolved to a different element — while the
tools kept promising that refs survive re-renders. Refs are now carried over by
backendNodeId and only genuinely new elements draw a new number.

Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f6

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:50:02 +0530
ce1240fa
fix(tool): stop the socket gate rejecting every tool call on Windows

The tool gate ran access() on DAEMON_SOCKET_PATH, which on Windows is the named
pipe \\.\pipe\pi-browser-daemon. Named pipes are not filesystem entries, so
access() always fails there and every browser_* call returned "Browser harness
not initialized" no matter how healthy the daemon was. spawn.ts already skipped
the identical check for this reason; that knowledge is now one shared
daemonSocketMayExist() used by both call sites, with platform and path as
parameters so the branch is testable off-platform.

Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f1

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:46:57 +0530
226e814d
fix(cdp): route background-tab events to the tab they belong to

The event loop dropped every event whose sessionId was not the active tab's, so
the per-tab console and network buffers could only ever hold data captured while
their tab was in the foreground. The filter now drops only sessions this instance
never attached, and the dialog, frameNavigated and loadEventFired arms resolve
their tab from the event's sessionId instead of assuming the active tab — without
that, a background tab's dialog would have been recorded against the foreground
one. Tabs are also registered before the domain-enable round trips, so events
arriving during attach are no longer dropped as unknown.

Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f4

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:44:17 +0530
ce30547a
fix(cdp): re-enable domains when re-attaching to a known tab

Every Target.attachToTarget mints a new sessionId and CDP domain enablement is
per-session, but switchTo only enabled domains on a tab's first visit. Switching
away and back left the refreshed session with Page, Network, Runtime and Log
disabled, so that tab silently stopped collecting console and network records and
stopped reporting dialogs and load events for the rest of the session.

Report: docs/superpowers/reports/2026-07-26-bug-audit.md#f3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:42:25 +0530
d3fb25a8
test: fake browser client over a stub transport

Wires a real CdpSession to a canned-response transport so domain tools can be
driven without Chrome. Unconfigured client methods return an error rather than a
success, so a test cannot pass by accident. Report:
docs/superpowers/reports/2026-07-26-bug-audit.md#f30

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:40:45 +0530
0fa62d81
chore: fail the build on unguarded type assertions

A clean tsc does not prove the absence of casts. This scans src/ for
as any, ts-ignore, non-null assertions, inline object casts, and double
casts, wired into a new npm run check that CI runs as a single step.

The escape hatch is an external allowlist module rather than an inline
comment marker: comments are not used in this codebase, and an entry
carries a count, so adding a new cast to an already-exempted file still
fails until someone raises a number that shows up in the diff. Exactly
one exemption is needed today, for the dynamic sharp import.

scripts/ was gitignored wholesale, so the two new files are un-ignored
individually and everything else dropped there stays ignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 19:10:30 +0530
f9dcdf7f
refactor: close the remaining unguarded type assertions

Types the event-queue iterator's TReturn as undefined so the terminal
result needs no cast; declares the isError field the pi runtime reads by
duck-typing; binds socket to a local const in the daemon transport;
replaces the @ts-ignore on the optional sharp import with a non-literal
specifier so src/ still typechecks where sharp was not installed.

Adds src/util/guards.ts as the single home for narrowing over unknown —
isRecord plus scalar/array readers — and converts the two private
isObject copies in cdp/types.ts and daemon/protocol.ts along with every
inline `as Record<string, unknown>` in domains/js.ts and domains/render.ts.

Gives Accessibility.getFullAXTree a real result schema, closing the last
unvalidated CDP boundary: only nodeId is required, every object carries
additionalProperties, and each AX value stays Unknown, so the tree
decodes as a whole or fails as a whole rather than being filtered.

Guards the five tool-result `details` readers, the daemon attach
response, the persisted session state, the browser-namespace flag, and
the fs errno narrowing, each feeding an already-present failure path.

The daemon-transport socket binding is not a bug fix: send() runs
synchronously inside sendWithTimeout's promise executor, so socket cannot
be reassigned between the check and the write. It removes the assertion
and makes the write immune to a later refactor that adds an await.

Two assertions survive by design and are documented in the task report:
the never->unknown re-widening in util/tool.ts, which the type-erased
tool registry makes unavoidable, and the AsyncFunction constructor lookup
in domains/js.ts, which JavaScript exposes no typed binding for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 01:19:58 +0530
b9c77bb6
refactor: validate JSON at every parse boundary

Ten sites cast or implicitly typed JSON.parse output; JSON.parse("null")
would have satisfied all of them. parseJson returns a Result and validates
against a typebox schema. js.ts keeps unknown — user script output has no
fixed shape. daemon/bridge.ts assigned JSON.parse straight to CdpRawMessage
with no guard at all; it now uses isCdpRawMessage like the transport does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 00:53:19 +0530
1c73f4af
fix(domains): stop fill_form misreporting select-by-label, let it fail

Three review fixes on the restored fill_form:

- FILL_FN matches a <select> option by value, label or text but returns
  el.value, so filling by visible label ("Canada" -> value "ca") tripped
  the read-back mismatch check and reported a correctly-set field as
  "Filled 0/1 fields; issues: ref e4 (value mismatch)". The mismatch test
  now also accepts res.text, which FILL_FN sets only for selects, so
  mismatch detection is preserved for every other field kind.
- fill_form always returned ok(), so total failure surfaced as a
  successful result reading "Filled 0/4 fields". It now returns
  invalid_state when okCount is 0, carrying the per-field issue list as
  the error message. Partial success stays ok().
- Removed the two comment blocks this task had added to
  test/manual/forms-test.ts.

Neither behaviour fix has automated coverage; the manual suite that would
exercise them still needs a live Chrome and was not run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 00:40:22 +0530
e46f080c
fix(domains): restore fill_form and set_checked, drop the stale fork

domains/forms.ts was a stale fork of form.ts imported only by a manual
test. It held two working tools the registry never registered: fill_form
and set_checked. Ported both onto the current pattern and deleted the
fork; form.ts versions of the shared tools win.

The fork's tools took ref as Type.Integer (a raw CDP backendNodeId).
browser_snapshot has since moved to string refs ('e7') resolved through
the session ref map, so as written the tools were not callable at all.
Ported them onto resolveRefToObjectId and Type.String refs; the only
user-facing strings that changed are the four that documented the old
[ref=N] syntax. All page-side JS is carried byte-for-byte.

form.ts exceeded 250 lines, so fillForm moved to form-batch.ts per the
single-field/batch split. form.ts is still 425 lines — the pre-existing
three tools account for that, so the split does not reach the target.

test/manual/forms-test.ts is repointed and adapted to publish an 'eN'
ref map, but NOT executed — it needs a live Chrome. Type-checked only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 00:28:45 +0530
bfba73fa
refactor(domains): share target resolution and CDP error mapping

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 00:17:13 +0530
5cfa7ccc
refactor(tools): declare concurrency in the tool definition

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Tue, 28 Jul 2026 00:01:32 +0530
753c2ad6
refactor(cdp): validate event params against a typed table

Malformed or unknown events are now dropped rather than hand-narrowed;
consumers get typed params. Unknown methods are ignored, since the
transport delivers every event Chrome emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 23:46:25 +0530
d54d8e9e
fix(domains): restore fail-closed focus check in browser_type

Adopting isFocusProbe inverted the safety default: the guard was ANDed into
the condition gating the check, so a probe result the guard rejected skipped
the check and fell through to Input.insertText — text typed into nothing,
silently, which is the exact failure the block exists to prevent. The guard
now feeds a value that defaults to { ok: false }, so a rejected shape
produces the error instead of bypassing it.

The other four guard adoptions (isFillResult, isFocusResult, isSelectResult,
isPageResult) were already fail-closed: each feeds a value whose rejection
lands on a pre-existing error branch.

Also assert x/y in the box centre test — the fields snapshot's off-viewport
filter reads — and wrap the zero-size message to the surrounding width.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 23:37:04 +0530
9f2f75dd
refactor(domains): drop CDP casts, share the box-model unwrap

ref-resolve.ts and snapshot.ts had the same box-model unwrap, both using
data.model! to work around an untyped response. Extracted to domains/box.ts;
the typed schema makes the assertions unnecessary. Both derived the centre
identically (quad corner + half the model width/height), so centreOf keeps
that derivation; the shared zero-size rejection moved into boxOf, and
snapshot keeps its own off-viewport (x/y < 0) filter.

evalInIsolatedTab now goes through evaluateJson with the tab's own sessionId
and a caller-supplied guard; both callers evaluate expressions ending in
JSON.stringify, so they pass isJsonText rather than a check that checks
nothing. Runtime.evaluate/callFunctionOn result casts in form.ts, forms.ts
and keyboard.ts are replaced by named guards over the in-page result shapes.

Runtime.callFunctionOn's params schema gained arguments/returnByValue/
awaitPromise/userGesture: four domain call sites legitimately pass them and
typebox's additionalProperties:true does not widen the static type, so the
params type was rejecting valid CDP fields. Params schemas are type-only
(only results are compiled), so this is a compile-time widening with no
runtime effect.

tsc is green again for both tsconfigs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 23:26:50 +0530
93157644
refactor(profile): drop CDP casts in favour of the typed boundary

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 23:10:18 +0530
44e3271c
fix(cdp): let evaluateJson target a specific session
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 23:02:59 +0530
9e9bf2a3
refactor(cdp): extract attachTo, getWindowId, evaluateJson helpers

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>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 01:17:32 +0530
d1b57558
test(cdp): cover call/callOnTarget/callBrowser through a stub transport; collapse decode into one path

session-call-test.ts previously only exercised decodeResult (Task 3's
surface, and mostly duplicating commands-test.ts) — call, callOnTarget,
callBrowser, and the session's internal req helper had zero coverage.
Add a stub CdpTransport (no socket, no browser) and test: a valid payload
decodes to typed data, a garbage payload becomes invalid_response without
throwing, a transport-level error passes through with its original kind
(not relabeled), call/callOnTarget/callBrowser route the session id
correctly, and opts.timeoutMs reaches the transport only when provided.

Also collapse the four near-identical transport-then-decode bodies (req,
call, callOnTarget, callBrowser) into one: the three public methods now
delegate to req, which is the sole place unknown ends for every CDP call
the session makes, internal or public. Public signatures are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 01:07:05 +0530
9f2ef1cf
refactor(cdp): make session.call generic over the command table

call/callOnTarget/callBrowser now take a known method and return its
decoded result type. The session is where unknown ends; transport keeps
returning unknown. Removes the casts in session.ts and target-factory.ts,
including the session's own internal Target.*/Browser.* calls, which now
decode through the same path as the public call methods.

Also completes two params-schema gaps in commands.ts surfaced by this
migration: Target.createTarget was missing openerId and Runtime.evaluate
was missing userGesture, both real CDP fields src/cdp/ already sends.

tsc is red project-wide (6 errors, all in src/domains/) — the sanctioned
state between Tasks 4 and 7.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 00:48:45 +0530
ae3a6dcd
feat(cdp): add typed command table with validated decoders

One typebox schema per CDP command is the source of both the TS types and
the runtime validator, so they cannot drift. Malformed responses become
invalid_response errors instead of TypeErrors several frames later.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 00:35:38 +0530
15a537a9
test: port remaining suites to node:test

Mechanical port of the hand-rolled check() harness. Import-after-env
ordering preserved so suites still read their sandbox dirs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 00:27:08 +0530
ce5d808c
fix(test): split P5/P6/P10 into one node:test per original check() label

Task 1 review found three ported checks collapsed two original labels
into one test(), losing the per-assertion granularity the brief's Step 7
requires. Splits P5, P6, and P10 back into two test() blocks each, using
the original labels verbatim, with each block doing its own dynamic
import after before() sets PI_CODING_AGENT_DIR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 00:19:46 +0530
3607b552
chore(build): split tsconfig, typecheck tests, add node:test runner

test/ was never type-checked and had no runner. Adds tsconfig.base.json
shared by a src project and a test project, an npm test script scoped to
non-manual suites, and a CI step. Ports the profile store suite as proof.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 27 Jul 2026 00:13:06 +0530
84ebb018
docs: add codebase refactor design

Covers comment removal, CDP type-safety boundary, tool authoring
contract, duplication/stale-code fixes, and commit sequencing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 21:05:54 +0530
78cc35a2
chore: release 0.10.3

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 20:51:43 +0530
7d2256e5
fix(profile): resolve replaced binaries and surface real launch failures

Two failures made a profile launch report the wrong cause.

Linux appends " (deleted)" to /proc/<pid>/exe once the running binary is
unlinked, which is what a google-chrome package upgrade does while the
browser stays open. detectRunningBrowser passed that literal string on as
the executable path, so every launch tried to spawn
"/opt/google/chrome/chrome (deleted)" and hit ENOENT. Executable paths are
now validated against disk, with the marker stripped and argv[0] used as a
fallback; a binary genuinely named "… (deleted)" still wins.

spawn() reports a missing or non-executable binary asynchronously via an
'error' event rather than throwing, and that event was discarded — so
openProfileWindow returned success, no window opened, and the caller
blamed the browser 15s later with a sentinel timeout. The launch now
awaits 'spawn'/'error', returns the real cause, and removes its handshake
page on the failure path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 20:51:43 +0530
083012ef
chore: release 0.10.2

Also resyncs package-lock.json, which was left at 0.9.0 by the 0.10.0 and
0.10.1 bumps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 11:59:13 +0530
37756958
fix(client): guard page-info evaluation against a missing documentElement

The page-info probe read document.documentElement.scrollWidth/scrollHeight
directly, which throws when documentElement is absent. Fall back to zeroed
dimensions so evaluation returns a usable PageInfo instead of failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 11:35:13 +0530
352ac1f9
docs: replace real emails with example.com placeholders in README
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 00:52:04 +0530
693b5e86
fix: kill leaked Chrome processes on profile seed failure

When a profile is pinned and ProcessSingleton fails to delegate, the
spawned Chrome process starts a full second browser instance. The
sentinel never appeared, but the spawned process was never killed
(detached + unref'd + reference discarded), accumulating zombie
browsers that consume RAM.

Three changes:
- Store child process ref and expose kill() on ProfileWindowHandle.
  Call kill() when the sentinel doesn't appear within the timeout.
- Only clear profileContextId when the browser UUID changes (Chrome
  mints a fresh UUID on every launch), avoiding unnecessary re-spawns.
- Don't clear profileContextId in setProfilePin when the pin hasn't
  changed, preventing gratuitous re-spawns on session_start.
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 26 Jul 2026 00:33:44 +0530
ad7f511b
chore: update 0.10.0 release date
amankumarsingh77 amankumarsingh7702@gmail.com
Sat, 25 Jul 2026 23:52:20 +0530
6dd79f7c
feat: pin the agent to a chosen browser profile (#16)

* 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>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sat, 25 Jul 2026 00:23:05 +0530
0fc1ece6
docs: rewrite README for accuracy and readability

The README taught the coordinate-first interaction model that the harness
replaced with stable [eN] refs, and documented 22 of the 38 registered
tools. Restructured so each section answers one question, and rewrote the
content against the actual tool registry.

- Teach the ref-first model: snapshot yields [eN] handles that re-resolve
  at action time, with coordinates as the fallback rather than the default.
- Document the 16 missing tools: the forms path (fill/select_option/focus),
  console, wait_for, dispatch_key, drag_and_drop, upload_file, download,
  print_to_pdf, viewport_resize, close_tab, and setup.
- Rebuild the comparison table as prose cells grouped by dimension. Drops
  the unverifiable "unknown" rows and the duplicated click/dispatch claims,
  and adds a provenance line so competitor claims stay correctable.
- Remove stale guidance: /browser-reload-daemon as the fix for a Chrome
  restart (auto-reconnect landed in 0.6.0), and the unbenchmarked "10-50x
  faster" claim on http_get.
- Add sections for concurrency and tab isolation, flags and environment
  variables (BU_CDP_WS, BU_CDP_PORTS, BH_SCRIPT_DIR), and a troubleshooting
  table keyed to the errors the harness actually emits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Fri, 24 Jul 2026 22:47:33 +0530
0a98de19
docs: add 0.8.3 and 0.9.0 changelog entries

The changelog stopped at 0.8.1 while npm was on 0.8.3 and main had
already bumped to 0.9.0. Backfills the 0.8.3 stale-socket fix and
documents the 0.9.0 cycle: web_search / read_page / deep-research,
the forms domain and durable window binding, the Windows daemon fix
(#7), Brave + channel support and stale DevToolsActivePort discovery
(#4), and the removed footer status chip (#10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Fri, 24 Jul 2026 22:15:26 +0530
fe00d336
feat: add forms domain and durable window binding (v0.9.0) (#14)

- 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>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Wed, 22 Jul 2026 00:59:58 +0530
7de0beaf
feat: support Brave + Chrome channels, fix stale DevToolsActivePort discovery (#13)

* feat: support Brave + Chrome channels, fix stale DevToolsActivePort discovery

Broadens browser detection and hardens CDP endpoint discovery, based on the
investigation and approach in #4 (Brave Beta on macOS with a leftover Chrome
DevToolsActivePort file).

Browser detection (src/setup.ts, src/cdp/discovery.ts):
- checkChromeRunning() now recognizes Brave (macOS "brave browser", Linux
  brave/brave-browser, Windows brave.exe), alongside the existing Chrome/
  Chromium/Edge detection. Also excludes gpu/updater sub-processes that can
  linger after a browser quits.
- profileDirs() gains Brave (Stable/Beta/Nightly/Dev) and Chrome Beta/Dev/
  Canary/Chromium profile paths across macOS, Linux, and Windows.

Stale DevToolsActivePort handling (src/cdp/discovery.ts):
Previously discovery trusted the first readable DevToolsActivePort file. A
browser that has quit leaves its file behind, so if another browser later binds
the same port (e.g. 9222), discovery returned a WS URL carrying the dead
browser's UUID against the live browser's server, and the connection failed.
discoverWsUrl() now:
- collects all readable candidates instead of committing to the first;
- when candidates share a port, keeps the most-recently-written file (mtime);
- skips candidates whose port isn't live via a fast single-shot probe (no
  30s waitForPort block on stale files);
- asks each live browser for its canonical webSocketDebuggerUrl via
  /json/version, falling back to the file's WS path when that endpoint is
  disabled (some browsers only expose it via --remote-debugging-port);
- falls back to probing well-known ports (9222, plus BU_CDP_PORTS) when no
  profile file is readable — covering sandboxed harnesses (EPERM/EACCES) and
  non-default install locations.

Setup guidance updated to mention brave://inspect / edge://inspect and the
--remote-debugging-port launch flag.

Verified: typecheck clean; queryLiveWsUrl / mtime-dedup / fallback-port logic
exercised against a mock /json/version server; checkChromeRunning() matches the
single main process against live macOS ps output, excluding all helpers.

Closes #4

Co-authored-by: Trevor Whitney <trevorjwhitney@gmail.com>

* fix: harden discovery against malformed port files + stale-then-fallback

Addresses two issues raised in review of the discovery rewrite:

- Validate the port parsed from DevToolsActivePort before building a
  candidate. A truncated/corrupt file (non-numeric or out-of-range first
  line) yielded NaN, and net.connect throws ERR_SOCKET_BAD_PORT
  synchronously for NaN / <=0 / >=65536 — which escaped discoverWsUrl as an
  unhandled rejection instead of surfacing as a Result error. Such entries
  are now skipped.

- Fall back to well-known ports (9222 / BU_CDP_PORTS) after all discovered
  candidates prove stale. Previously the fallback ran only when no port file
  was readable, so a leftover file for a quit browser caused a ~30s
  waitForPort block and failure without ever trying a live fallback port.
  Ports already covered by a candidate are skipped to avoid re-probing.

---------

Co-authored-by: Trevor Whitney <trevorjwhitney@gmail.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Mon, 13 Jul 2026 00:47:35 +0530
cf92c10e
fix: make browser daemon setup work on Windows (#7) (#12)

The Unix socket daemon introduced in v0.6.0 made several POSIX-only
assumptions that break setup on Windows:

- protocol.ts hardcoded a Unix socket path (/tmp/pi-browser-daemon.sock).
  Node IPC on Windows requires a named pipe (\\.\pipe\<name>); a /tmp
  path is not a valid net listen/connect target there.
- spawn.ts spawned the bare tsx binary / `npx` and probed the non-Windows
  .bin/tsx shim. On Windows npm binaries are .cmd shims that must run via a
  shell, and spawning them directly fails with EINVAL / ENOENT.
- isDaemonRunning() pre-checked the socket with fs.access(), which always
  fails for a named pipe and would report a live daemon as dead.
- stale-socket unlink() would throw on a named pipe path.

Changes:
- DAEMON_SOCKET_PATH is now platform-aware: named pipe on win32, Unix
  socket elsewhere.
- spawnDaemon() resolves tsx.cmd / npx.cmd on Windows, runs via shell with
  per-token quoting (paths with spaces) and windowsVerbatimArguments.
- isDaemonRunning() skips the fs.access pre-check on Windows and probes the
  pipe directly.
- stale-socket cleanup is a no-op on Windows (named pipes self-clean).
- setup.ts daemon-failure message uses the actual DAEMON_SOCKET_PATH.

Closes #7
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sun, 12 Jul 2026 22:03:43 +0530
f6bab02b
fix: remove browser footer status indicator (#10) (#11)

The footer status chip set during session_start could go stale: after a
successful /browser-setup, the setup command only called ctx.ui.notify()
and never updated ctx.ui.setStatus("browser", ...), so the footer kept
showing the red "Browser — run /browser-setup" nudge even though the
browser was connected.

Rather than syncing the status from the setup handler, remove the footer
status indicator entirely. Browser control is on-demand and the setup
command already reports its own outcome via notify(), so the persistent
footer chip added no value and only introduced a stale-state bug.

Closes #10
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sun, 12 Jul 2026 22:03:37 +0530
1c12b43a
feat: add deep-research skill, /deep-research command, fix web-search-researcher

deep-research skill fans out to isolated web-search-researcher subagents (now
using browser_web_search + browser_read_page instead of the non-existent
web_search/web_fetch), coverage-driven loop with a hard ceiling, synthesizes a
cited Markdown report. /deep-research command as explicit entry point. Docs updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Wed, 08 Jul 2026 21:41:47 +0530
3f65a30e
feat: add expand/collapse (Ctrl+O) rendering to web_search and read_page

Both tools dumped full output into the TUI. Add a shared renderExpandableText
helper (src/domains/render.ts) mirroring browser_execute_js: a compact preview
by default, full body on expand via keyHint("app.tools.expand"). Tools stash a
{summary, body, fullOutputPath} render payload in details; the full text still
goes to the LLM via ToolOk.text. Also surfaces engine + result count in the
web_search summary line (closes the S4 observability gap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Wed, 08 Jul 2026 21:31:37 +0530
68bf7fd2
feat: add browser_read_page reader-mode tool

URL (or owned targetId) → clean main-article text with nav/ads/footer
boilerplate stripped, via a dependency-free readability heuristic. DOM walk
lives in an in-page capture expression; scoring/selection is a pure, fixture-
tested function (link-density + boilerplate-ancestor filtering, body-text
fallback for structure-less pages). Reuses the isolated-attach lifecycle;
registered unserialized (safe concurrent read). 10 extractor unit scenarios.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Wed, 08 Jul 2026 21:24:32 +0530
c5c42df5
feat: add browser_web_search tool (real-Chrome SERP scrape, no API key)

Thin query→ranked-links primitive. Isolated-attach tab lifecycle mirroring
browser_open_urls (never touches the user's current tab); pure Google SERP
parser with semantic (a h3) extraction and redirect unwrapping; CAPTCHA and
no-results surfaced as invalid_state + details.reason. Fixture-based parser
tests (9 scenarios). Establishes the isolated-tab lifecycle and tsx+fixture
test convention that browser_read_page and deep-research build on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Wed, 08 Jul 2026 21:13:15 +0530
7826c950
release: v0.8.3

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmail.com
Wed, 08 Jul 2026 20:17:57 +0530
2ba58486
fix: stale socket detection prevents daemon spawn after crash

isDaemonRunning() only checked file existence of the Unix socket.
A dead daemon leaves a stale socket, causing ensureDaemon() to skip
spawning and client.start() to fail with 'Chrome not connected'.

Replace with a liveness probe: connect, register, verify ack, then
disconnect. On failure, clean up the stale socket.

Also makes bridge handleRequest async to poll for Chrome connection
for up to 15s before rejecting.
amankumarsingh77 amankumarsingh7702@gmail.com
Mon, 06 Jul 2026 23:15:08 +0530
26daf878
fix: make dispatch_key fire legacy keyCode handlers and tighten execute_js IIFE wrap (#9)

- browser_dispatch_key now sets keyCode/which on the synthesized KeyboardEvent
  (both ref and selector paths) so handlers checking e.keyCode === 13 fire.
- browser_execute_js IIFE auto-wrap triggers only when the source starts with a
  return statement, not on any substring "return " (which silently produced
  undefined for bare expressions).
- browser_fill guidelines point at dispatch_key for tag-input submission and the
  snapshot+click recipe for custom dropdowns.

Bump to 0.8.1.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sun, 05 Jul 2026 13:15:22 +0530
cf770d14
release: v0.8.0
amankumarsingh77 amankumarsingh7702@gmail.com
Fri, 03 Jul 2026 09:36:56 +0530
28e6390a
refactor: extract event queue from transport into dedicated module
amankumarsingh77 amankumarsingh7702@gmail.com
Fri, 03 Jul 2026 09:36:47 +0530
0b32e88f
feat: address elements by stable refs across re-renders (#8)

Interaction tools could only target elements by raw viewport coordinates
(browser_click) or model-guessed CSS selectors (browser_fill/focus/
select_option/upload). Neither survives a re-render: on React/SPA forms,
every save or edit reflows the page, so captured @(x,y) coordinates go
stale and guessed selectors miss — leaving the agent to fall back to
hand-rolled execute_js DOM walks and unnoticed failed saves.

Add a stable ref system, mirroring Playwright MCP / browser-use:

- snapshot assigns each interactive element a ref (e1, e2, …), shown as
  [eN] in the outline, and publishes a per-tab ref → backendNodeId map
- interaction tools accept an optional `ref` that re-resolves the element
  at call time (via DOM.resolveNode / getBoxModel), so it stays valid
  across re-renders; coordinates/selectors remain as fallbacks
- mutating tools append a compact diff of changed / newly-appeared (*) /
  removed interactive elements, so silent failed saves surface immediately
- stale refs return an actionable "re-snapshot" error instead of a miss

Ref map is replaced per snapshot (bounded memory); fill keeps the native
_valueTracker write path so React controlled inputs still update.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Mon, 29 Jun 2026 00:02:54 +0530
9f428cce
ci: use Node 24 for OIDC/npm 11 support
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 21 Jun 2026 00:55:38 +0530
0825b683
ci: add trusted-publishing workflow for npm
amankumarsingh77 amankumarsingh7702@gmail.com
Sun, 21 Jun 2026 00:51:33 +0530
da5d31af
release: v0.6.0 — per-tab isolation, Unix socket daemon, on-demand init (#6)

* 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
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Sun, 21 Jun 2026 00:46:44 +0530
8db684d4
docs: add per-tab isolation research, design, and implementation plan

- Research: multi-agent safety analysis with precedent findings
- Design: 4-slice per-tab isolation architecture (thoughts/shared/designs/)
- Plan: 3-phase implementation plan (thoughts/shared/plans/)
- Agent definitions (.pi/agents/) for codebase analysis workflow
amankumarsingh77 amankumarsingh7702@gmal.com
Fri, 08 May 2026 00:18:22 +0530
aa08e740
feat: add per-tab isolation for multi-agent safety

Replace singleton dialog, pageInfoDirty, networkBuffer, and consoleBuffer
with per-tab TabSession objects stored in a Map<string, TabSession>.

- Add TabSession type (sessionId, targetId, dialog, pageInfoDirty,
  networkBuffer, consoleBuffer) and tabs/sessionIdToTargetId Maps
- Add ev.sessionId filter guard in consumeEvents to prevent cross-tab
  event contamination
- Rework switchTo to create/reuse TabSession without clearing buffers,
  updating sessionId on re-attach
- Route all CDP events (dialog, network, console) through resolveTab
  to the correct TabSession
- takeDialog/drainPageInfoInvalidations/drainNetworkBuffer/
  drainConsoleBuffer read from current TabSession
- Target.targetDestroyed prunes dead TabSession entries
- attachFirstPage creates initial TabSession and prunes dead entries
- Per-tab pageCaches Map in client.ts replaces pageCache singleton
- listTabs reconciliation prunes dead pageCaches entries
- console-buffer.ts clear() resets nextSeq to 1 (per-tab seq space)
- Multi-agent warning in prompt.ts Parallel Execution section
amankumarsingh77 amankumarsingh7702@gmal.com
Fri, 08 May 2026 00:18:15 +0530
4052e6e6
release: v0.5.0 — browser_console tool for JS error/console diagnostics
amankumarsingh77 amankumarsingh7702@gmal.com
Thu, 07 May 2026 12:54:12 +0530
bd197399
docs: rewrite README for v0.4.0 — comparison table, snapshot-first patterns

Adds a "Why pi-browser-harness?" comparison table (vs Playwright MCP, Stagehand,
Puppeteer MCP) at the top, surfacing the differentiators a chooser actually
cares about: real Chrome, coordinate clicks through iframes/shadow DOM, inline
TUI rendering, AX tree with @(x,y) coords, parallel execution, scripts.

Tool list reorganized into "Page inspection (use these by default)" / "Visual
(last resort)" / Navigation / Interaction / Utility — adds browser_snapshot,
browser_network_requests, browser_close_tab. Patterns rewritten snapshot-first;
form filling and research no longer screenshot. New sections for parallel
execution and tab ownership. Temporary scripts example fixed to use the actual
daemon.session().call() binding. Three explicit anti-screenshot rules in
"What NOT to do".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Wed, 06 May 2026 00:20:20 +0530
589cd294
release: v0.4.0 — accessibility snapshot, network capture, snapshot-first 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>
amankumarsingh77 amankumarsingh7702@gmal.com
Wed, 06 May 2026 00:06:49 +0530
20ceaa1c
feat: tab ownership — isolate harness tabs from the user's browsing

Track which page targets this harness session opened so the agent can never
attach to, switch to, or close a tab the user opened themselves. New tabs
are spawned in a dedicated Chrome window (newWindow:true on first open,
openerId chaining after) so harness activity stays visually separate.

- new OwnershipRegistry (src/cdp/ownership.ts) tracks owned targetIds and
  the harness window seed; mutations notify a single listener that persists
  to BrowserState (ownedTargetIds, harnessWindowTargetId)
- attachFirstPage prefers an already-owned live target; otherwise opens a
  fresh harness-owned tab in its own window instead of grabbing whatever
  page the user is on
- session subscribes Target.setDiscoverTargets and reaps targetDestroyed
  events so the registry stays in sync with reality
- listTabs returns owned:boolean per tab and reconciles dead ids
- browser_list_tabs defaults to scope:"owned"; pass scope:"all" to inspect
  the user's other tabs (read-only — they cannot be switched to or closed)
- browser_switch_tab refuses non-owned tabs, with a clear remediation hint
- browser_new_tab opens inside the harness window via openerId, falling
  back to a fresh window when the seed has been closed
- new browser_close_tab tool — owned tabs only, supports unique hex prefix

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Tue, 05 May 2026 23:23:59 +0530
e42d9538
release: v0.3.2 — parallel tool execution, scroll fixes, TUI crash fix

- Add async mutex for auto-serialization of mutation tools
- Observation tools can run in parallel with mutations
- Fix scroll deltaY sign convention (positive=down per W3C)
- Add Page.bringToFront before scroll to prevent silent drops
- Fix screenshot TUI crash on long file paths (truncateToWidth)
amankumarsingh77 amankumarsingh7702@gmal.com
Tue, 05 May 2026 22:37:26 +0530
f73941cb
fix: prevent TUI crash when screenshot path overflows terminal width (#2)

The Image text-fallback render does not respect the rendered width,
so a long file path can blow past the terminal width and crash the
host TUI. Wrap the component to truncate each rendered line with
truncateToWidth so it fits the available width.

Co-authored-by: amankumarsingh77 <amankumarsingh7702@gmal.com>
Aman Kumar 145339995+amankumarsingh77@users.noreply.github.com
Tue, 05 May 2026 00:52:11 +0530
71da8e07
feat: parallel tool execution with automatic mutation serialization

- Add lightweight async mutex (src/util/mutex.ts) for FIFO serialization
- Add serialized?: boolean to BrowserToolDefinition
- Wire mutex acquisition into registerBrowserTool execute wrapper
- Expose mutationMutex() on BrowserClient
- Mark all mutation tools (click, type, scroll, navigate, switch_tab, etc.) as serialized
- Leave observation tools (screenshot, page_info, execute_js, http_get, etc.) unguarded
- Update prompt and skill docs to reflect parallel-safe behavior
- Fix scroll tool: add Page.bringToFront, explicit button:none/buttons:0/pointerType:mouse
- Update .gitignore to ignore scripts/ and data/ folders
amankumarsingh77 amankumarsingh7702@gmal.com
Mon, 04 May 2026 22:44:55 +0530
2a98be01
Revert "chore: add pre-parallel-execution latency baseline"

This reverts commit 361f4b31f7870f0a8bbd46793edc46c9967fdd69.
amankumarsingh77 amankumarsingh7702@gmal.com
Mon, 04 May 2026 21:18:34 +0530
361f4b31
chore: add pre-parallel-execution latency baseline
amankumarsingh77 amankumarsingh7702@gmal.com
Mon, 04 May 2026 21:17:21 +0530
7d70b2ba
docs: add speed-optimizations research; chore: improve browser detection in setup
amankumarsingh77 amankumarsingh7702@gmal.com
Mon, 04 May 2026 21:14:30 +0530
cb9a51eb
feat: render screenshots inline in TUI via Image component; add parallelization guidance to SKILL.md

- browser_screenshot now has renderResult that creates an Image component
  for inline display in Kitty/iTerm2/Ghostty/WezTerm terminals
- Falls back to dim text placeholder on terminals without image support
- Updated system prompt table description to mention TUI inline rendering
- Added Parallelization section to SKILL.md teaching the agent to batch
  independent operations (browser_open_urls, browser_http_get, etc.)
amankumarsingh77 amankumarsingh7702@gmal.com
Sun, 03 May 2026 00:43:43 +0530
ff485923
fix: replace @ts-expect-error with @ts-ignore for dynamic sharp import
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 22:15:41 +0530
167c50de
chore: update lock file for sharp optional dependency
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 22:01:18 +0530
a1df2d3e
release: v0.3.1 — audit fixes for list_tabs IDs, switch_tab prefix 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
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 21:59:30 +0530
c9d1605b
chore: address final-review minors — surface event-consumer crashes, document IIFE-wrap heuristic, log shutdown failures, fix CHANGELOG file-size claim

Five tweaks from the whole-implementation review:
- session.ts: log unexpected event-consumer rejections instead of silent swallow
- client.ts: comment the return-substring IIFE-wrap false-positive case
- transport.ts: comment that ws.onclose post-open settle is no-op-by-design
- index.ts: log client.stop() failures during shutdown
- CHANGELOG.md: correct largest-file claim (transport.ts and client.ts are ~220 each, not "client.ts at ~180")

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:53:34 +0530
500dd206
chore(release): v0.3.0 — internal rewrite
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:50:13 +0530
43b74f78
chore: final cleanup — drop unused keyCode export
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:48:19 +0530
bb691459
refactor: delete legacy daemon, tools, protocol, renderers; collapse to single tsconfig
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:46:12 +0530
c86b220d
refactor: drop legacy daemon usage from index/setup; trim state; move 4 files to strict tsconfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:44:52 +0530
0f717bc7
feat(domains): migrate execute_js and run_script with path allowlist + timeout + return validation
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:41:15 +0530
b7fd72a6
feat(domains): migrate http_get (timeout covers body) and get_network_log (deprecation note)
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:36:50 +0530
96934b77
feat(domains): migrate drag_and_drop
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:35:30 +0530
dc07a515
feat(domains): migrate viewport_resize
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:34:39 +0530
5664dc8f
feat(domains): migrate upload_file (safeJs), download (validated dir), print_to_pdf
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:32:07 +0530
e8650dc5
feat(domains): migrate tab tools (list, current, switch, new)
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:28:15 +0530
1d0873c7
feat(domains): migrate go_back, go_forward, reload
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:26:01 +0530
3dfc4f11
feat(domains): migrate navigate (NavOutcome) and open_urls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:24:23 +0530
6708da54
feat(domains): migrate screenshot, add typed sharp shim, restore click crosshair overlay
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:20:09 +0530
9ead6a05
feat(domains): migrate handle_dialog
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:16:24 +0530
1b11036e
feat(domains): migrate page_info, wait, wait_for_load with typed timeout
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:14:52 +0530
307f9e74
feat(domains): migrate scroll with W3C deltaY semantics and dialog guard
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:12:36 +0530
41e3b69b
feat(domains): migrate type, press_key, dispatch_key with safeJs and matched-count validation
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:08:50 +0530
0f962ef9
feat(domains): migrate browser_click to defineBrowserTool — vertical slice

Implements Task 9: adds src/domains/click.ts with the new architecture,
updates registry.ts to register clickTool, removes the legacy browser_click
block from tools.ts, and wires BrowserClient alongside BrowserDaemon in
index.ts so both systems coexist during migration.

Also fixes AnyBrowserToolDefinition variance: uses never-arg renderCall in
the erased type and a boundary cast in registerBrowserTool so concrete tool
definitions (with specific Static<S> args) are assignable to the registry
array without widening.
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:05:09 +0530
6a4bfa31
feat: add defineBrowserTool helper, truncation, schemas/common, empty registry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 16:00:58 +0530
d3d86f9d
fix(cdp): serialize event consumer; persist dialog until taken; validate page info shape
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:55:44 +0530
71228dea
feat: add CDP session and BrowserClient factory
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:52:10 +0530
52fd9523
fix(cdp): validate inbound CDP messages with a runtime type guard
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:48:28 +0530
22019a65
feat(cdp): add factory-based transport with Result and event AsyncIterable
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:46:05 +0530
73caf309
fix(cdp): clear socket timer on probe success; surface non-ENOENT readFile errors
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:43:52 +0530
962ae380
feat(cdp): port DevToolsActivePort discovery to Result
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:42:07 +0530
ede4b89e
feat(cdp): add types and error model for new transport layer
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:40:27 +0530
10f44002
build: enable strict tsconfig flags for new code, keep legacy lenient
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:39:06 +0530
60ddf56f
docs(util): clarify safeJs trust boundary and unsupported value types
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:38:07 +0530
31afcecd
feat(util): add pure keycodes and safeJs template helper
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:36:13 +0530
64cb4c87
fix(util): preserve AbortSignal.reason; validate namespace in temp paths
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:33:27 +0530
310d0a73
feat(util): add result, time, and paths primitives for v0.3 rewrite
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:31:01 +0530
6759b6fd
feat: v0.2.1 — feature parity with browser-use/browser-harness

Adds the missing tools called out in docs/browser-harness-comparison.md:
upload_file, dispatch_key, download (configure dir), viewport_resize,
drag_and_drop, print_to_pdf, get_network_log, plus the iframe-target
helper. Updates promptGuidelines and the system prompt's tools table.

Tracked in todo.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:29:23 +0530
5b76aa34
docs: add v0.3.0 harness rewrite implementation plan

27 tasks: foundations → CDP transport/session/client → defineTool helper
→ vertical slice (click) → 13 domain migrations → wire-up + delete legacy
→ smoke + release. Each task has concrete code, exact file paths, and a
typecheck checkpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:17:27 +0530
f6204b10
docs: add v0.3.0 internal-rewrite design spec

Per-domain module split, frozen tool names, normalized params, unified
Result+details contract. Captures the full audit (predictability bugs,
security gaps) and the structural fixes for each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:05:19 +0530
fa54f769
perf: v0.2.0 — event-based waitForLoad, fast ensureAlive, JPEG screenshots, page info caching

5 performance optimizations (14-36% faster end-to-end, output quality unchanged):

- fast ensureAlive(): WS state check instead of CDP roundtrip (96% faster per-call)
- event-based waitForLoad(): CDP load events instead of 300ms polling (99.5% faster for loaded tabs)
- JPEG screenshot support: 29-49% smaller files, faster CDP transfer
- page info caching: 1s TTL eliminates redundant evaluateJS calls
- parallel domain enables: Promise.all instead of sequential await in switchTab()

Benchmark: 3622ms vs 4243ms baseline on 3-tab navigation + screenshot task.
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 11:40:20 +0530
b77c5ec3
ci: suppress npm pack notice output
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 00:42:32 +0530
e865b26f
fix: commit package-lock.json for CI reproducibility

Removes package-lock.json from .gitignore so npm ci works in CI.
Without the lock file, actions/setup-node cache resolution fails
and npm ci has no dependency tree to install from.
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 00:40:51 +0530
5762eae6
docs: rewrite README — add hero image, condense tools, fix accuracy

- Add hero image banner at top
- Merge 4 tool tables into 1 compact reference table
- Fix tool syntax to match actual parameter schemas
- Add Core Patterns section (navigation, form fill, extraction, research)
- Add Dialogs, SPAs, Keyboard modifiers subsections
- Add What NOT to do section
- Add Architecture diagram
- Add Contributing link
- Remove verbose/duplicate usage examples
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 00:38:39 +0530
40c18b22
chore: update hero image
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 00:30:00 +0530
a122a32a
fix: normalize repository.url for npm
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 00:09:37 +0530
ae223796
chore: prepare v0.1.0 release

- Add hero image for pi package gallery
- Update CHANGELOG with release date
- Update README with npm install instructions
- Add assets/ to npm package files
- Add CONTRIBUTING.md and GitHub templates
- Add CI workflow for typecheck and pack validation
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 00:08:22 +0530
28578d1d
Replace dynamic tool registration with browser_run_script

Remove register_tool/remove_tool/list_dynamic_tools and the entire
dynamic-tools.ts module. Replace with a single browser_run_script tool
that executes temporary scripts from disk with daemon access.

Scripts are now written to disk before execution, making them auditable
and re-runnable. Same bindings (daemon, require, params, etc.) as before
via the same AsyncFunction mechanism.
amankumarsingh77 amankumarsingh7702@gmal.com
Thu, 30 Apr 2026 17:52:26 +0530
b6d75222
fix: stop Chrome remote-debugging consent popup storm

Previously a single consent-pending socket could spawn up to 5 stacked
'Allow remote debugging?' dialogs. Three causes:

- onopen reset _reconnectAttempts, but Chrome accepts the WS upgrade
  before the user clicks Allow, so a held socket looked healthy.
- ensureAlive's health probe used the full 15s CDP timeout; while the
  user was reading the dialog the probe would time out, trigger a
  stop()+start(), and a fresh popup would stack on top.
- onclose auto-reconnected unconditionally even when consent had never
  been granted on that connection.

Now a connection is only considered healthy once the first CDP
roundtrip completes (attachFirstPage). ensureAlive does a 2s short
probe before tearing down the socket. onclose bails out of auto-
reconnect when consent was never observed, so a denied dialog
surfaces a clean error instead of looping.
amankumarsingh77 amankumarsingh7702@gmal.com
Thu, 30 Apr 2026 13:09:27 +0530
63b16957
Replace socket daemon with direct CDP WebSocket connection

Drops the Unix socket subprocess in favor of talking CDP directly over
WebSocket from the extension. Removes the protocol's send/health helpers,
simplifies setup to just verifying Chrome's remote debugging port, and
tightens types in dynamic-tools. Bumps Node engine to >=22 and adds the
ws dependency.
amankumarsingh77 amankumarsingh7702@gmal.com
Wed, 29 Apr 2026 01:38:17 +0530
5317b329
Fix repo/bug URLs to point to amankumarsingh77/pi-browser-harness
amankumarsingh77 amankumarsingh7702@gmal.com
Wed, 29 Apr 2026 00:41:40 +0530
8fff9152
Initial release: pi-browser-harness

Browser control extension for pi — 20 browser_* tools for navigating,
clicking, typing, screenshotting, and extracting data from real Chrome
via CDP. Includes self-extending harness (register_tool/remove_tool),
guided /browser-setup, session persistence, and custom TUI renderers.
amankumarsingh77 amankumarsingh7702@gmal.com
Wed, 29 Apr 2026 00:40:36 +0530