<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sz-chrome-remote-cdp.git/src, branch master</title>
<subtitle>pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search
</subtitle>
<id>https://git.soarez.net/sz/sz-chrome-remote-cdp.git/atom?h=master</id>
<link rel='self' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/'/>
<updated>2026-10-17T10:20:04+00:00</updated>
<entry>
<title>Start a local Chrome when the target is an explicit local and none answers</title>
<updated>2026-10-17T10:20:04+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-17T10:20:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=f61c43418d704cccb67132540ed7feceacef1d2a'/>
<id>urn:sha1:f61c43418d704cccb67132540ed7feceacef1d2a</id>
<content type='text'>
Dedicated profile (~/.chrome-cdp-profile), macOS only, never for the
castle default. BU_LOCAL_CHROME_LAUNCH=off disables it.
</content>
</entry>
<entry>
<title>fix(cdp): give the castle default 1.5s, not 5s, inside upstream's connect budget</title>
<updated>2026-10-15T21:25:41+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-15T21:25:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=08c0c3a8b4fe755f7fe65ea8d258d77971503882'/>
<id>urn:sha1:08c0c3a8b4fe755f7fe65ea8d258d77971503882</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(cdp): report a BU_CDP_WS pin in status, /browser-target and the prompt</title>
<updated>2026-10-15T06:07:58+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-10-15T06:07:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=3e761274fe7b0e6be4a162e880293150ad9d9fbe'/>
<id>urn:sha1:3e761274fe7b0e6be4a162e880293150ad9d9fbe</id>
<content type='text'>
Upstream 0.11.0 made BU_CDP_WS reach the daemon, and discoverEndpoint()
returns it before the fork's target is consulted. resolveCdpTarget() did
not know about it, so /browser-status and the system prompt named the
BU_CDP_HTTP/stored/default target while the daemon dialled the pinned URL,
and /browser-target claimed to switch browsers when it could not.

resolveCdpTarget() now puts BU_CDP_WS first, as a remote target with
source "ws". /browser-target warns that it takes precedence, as it already
did for BU_CDP_HTTP. Only host:port is ever shown: the path carries the
browser id and a proxied URL may carry a token. /browser-status does not
probe a pinned URL, since /json/version answering says nothing about
whether the pinned browser id is still current.
</content>
</entry>
<entry>
<title>feat(prompt): say which Chrome browser_* drives</title>
<updated>2026-10-14T11:19:26+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-09-13T17:30:51+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=792336dfef913291358f2312cd82c55944d18199'/>
<id>urn:sha1:792336dfef913291358f2312cd82c55944d18199</id>
<content type='text'>
The prompt told the model it drives "the user's running instance". With this
fork's default that is wrong: it is castle's shared Chrome, signed out of every
account. It now matters which is which, because pi-chrome's chrome_* tools
drive the user's signed-in Brave, and the model has to route between the two.

getBrowserSystemPrompt() takes the resolved target and describes it: castle's
shared browser for the built-in default, the user's own Chrome for local, the
endpoint otherwise.
</content>
</entry>
<entry>
<title>fix(profile): never prompt for a profile when driving a remote browser</title>
<updated>2026-10-14T11:19:26+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.me</email>
</author>
<published>2026-08-30T15:27:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=c8fd7b7078da841d5537aee9334291887445889f'/>
<id>urn:sha1:c8fd7b7078da841d5537aee9334291887445889f</id>
<content type='text'>
Profile selection works by launching the browser with --profile-directory and
letting ProcessSingleton delegate to the running process, which only reaches a
browser on this machine. discoverEndpoint() already withholds userDataDir for a
remote target, but resolveUserDataDir() fell through to the local candidate
dirs, so the picker offered this disk's Chromium profiles for a browser on
another host - on castle, Brave's profiles for a Chrome on :9223. Pinning one
launched a local browser and polled the remote for a sentinel that could never
appear, failing after the seed deadline; seedProfileWindow()'s mismatch guard
missed it because connectedDir is undefined on the remote path.

Setup now returns early instead of prompting - the question had no actionable
answer, and since cancelling stores nothing it returned every session.
/browser-profile and seedProfileWindow() refuse with the reason, the latter
catching a pin left over from a local session.
</content>
</entry>
<entry>
<title>fix(setup): skip local-browser precondition for every remote target</title>
<updated>2026-10-14T11:19:26+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-08-29T21:59:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=cfa0b6f854e7f15717db385df0e43f974a4b1e67'/>
<id>urn:sha1:cfa0b6f854e7f15717db385df0e43f974a4b1e67</id>
<content type='text'>
The pre-flight check only skipped isBrowserRunning() when BU_CDP_HTTP was
set, so a machine with no local browser failed /browser-setup with 'No
browser instance running' before discovery ever dialed the built-in
castle default. Resolve the target the same way discovery does and skip
the local-process check whenever it resolves remote — env var,
/browser-target choice, or default. A resolved local target still
requires a local browser, and an unreachable remote still fails loudly
in step 4.
</content>
</entry>
<entry>
<title>docs(readpage): flag SPA-shell results so models don't trust them as the page</title>
<updated>2026-10-14T11:19:26+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-08-05T12:42:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=681ea9d0b3b89bec1ad8c57ef79ae2d4d82ef49b'/>
<id>urn:sha1:681ea9d0b3b89bec1ad8c57ef79ae2d4d82ef49b</id>
<content type='text'>
</content>
</entry>
<entry>
<title>feat(cdp): /browser-target to switch browsers without leaving pi</title>
<updated>2026-10-14T11:19:26+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-07-29T09:38:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=08e558753e8ef9b2aa96114981a5a1dd37357bad'/>
<id>urn:sha1:08e558753e8ef9b2aa96114981a5a1dd37357bad</id>
<content type='text'>
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 &gt; stored &gt; 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.
</content>
</entry>
<entry>
<title>feat(cdp): default to the shared browser, and say so in /browser-status</title>
<updated>2026-10-14T11:17:59+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-07-28T21:08:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=95ff5e8eaefb35d79109f7ddfa1de5bf45a8daff'/>
<id>urn:sha1:95ff5e8eaefb35d79109f7ddfa1de5bf45a8daff</id>
<content type='text'>
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.
</content>
</entry>
<entry>
<title>feat(cdp): BU_CDP_HTTP to attach to a browser on another host</title>
<updated>2026-10-14T11:17:36+00:00</updated>
<author>
<name>Igor Soarez</name>
<email>igor@soarez.org</email>
</author>
<published>2026-07-28T20:46:00+00:00</published>
<link rel='alternate' type='text/html' href='https://git.soarez.net/sz/sz-chrome-remote-cdp.git/commit/?id=233f29aedf004699a97dbd9aee087e239f4a9e47'/>
<id>urn:sha1:233f29aedf004699a97dbd9aee087e239f4a9e47</id>
<content type='text'>
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.
</content>
</entry>
</feed>
