The Browy backend is a small Node.js process registered with Chrome /
Edge / Brave as a native messaging host under the application id
com.browy.host. It runs on your machine.
The extension speaks to the host over a single bidirectional
length-prefixed JSON channel (Chrome’s native messaging protocol).
The agent’s tools run in the host. When a tool needs the page, the
host sends the extension a cdp.send message (or tab.new,
tab.activate, tab.list, tab.getActive); the extension’s service
worker runs it through chrome.debugger or chrome.tabs and posts a
tool.result back.
Process entry. Chrome starts one for each connectNative port.
It opens the log file, starts the Copilot SDK and attaches one
Runner to the stdio transport. It also holds the Copilot sign-in
check and the helper that opens a terminal to sign in.
`src/agent/loop.ts`
The agent loop. Calls into the Copilot SDK
(createSession / resumeSession), feeds it the prompt + tool
inventory, streams assistant text and tool-call events back to
the extension through the Runner, and routes tool results back
into the SDK.
`src/agent/tools/browser.ts`
The tool registry: every Browy tool the LLM can call
(inspect_page, click_index, type_index,
get_network_requests, …). Each one is a register({ def, run })
call: a JSON-schema definition and a run(args, ctx) function
that does the work in the host, driving the tab through ctx.cdp.
`src/agent/runner.ts`
Session routing. One Runner per host process keeps a session
for each connected panel, keyed by the panel’s session id, and
passes messages between the panels and the one Agent. It runs
one chat turn at a time across all panels; a message sent from
another panel meanwhile waits in line and starts as soon as the
current turn ends. Each session’s pending
CDP calls live in its ExtensionContext
(src/agent/extension-context.ts).
`src/transports/`
Stdio framing (native-messaging.ts): a 4-byte little-endian
length, then UTF-8 JSON. The Runner only sees the Transport
interface from src/protocol.ts, where the host’s message types
are defined.
`src/cli-bin.ts`
Standalone entry: the browy command (dist/cli-bin.js). The
installers run its install-host subcommand to register the
native host with Chrome, Edge and Brave, and uninstall-host
removes it. It also has run, a terminal agent that needs no
browser, plus repl and chat.
The side panel posts {type: 'chat.send', sessionId, text} to
the service worker, which forwards it over the native port.
Host’s Runner binds the agent to that session id and calls
agent.chat(text). If no Copilot session is open for the id, the
agent calls the SDK’s resumeSession(id, ...) (or
createSession({sessionId: id, ...}) the first time) with the
filtered tool list, then sends your text with the current tab’s
context and a fresh page snapshot attached.
The SDK streams events back. Host forwards text chunks as
{type: 'chat.delta'}. When the SDK calls a tool, the host posts
{type: 'chat.tool_call'} to the panel, runs the tool, then posts
{type: 'chat.tool_result'}.
Each CDP command the tool needs goes to the extension as
{type: 'cdp.send', sessionId, callId, method, params}
(inspect_page, for example, is one Runtime.evaluate). The
service worker runs it with chrome.debugger.sendCommand and
posts {type: 'tool.result', sessionId, callId, ok, result}.
Host hands the tool’s result back to the SDK; the loop continues
until the SDK reports session.idle.
Host posts {type: 'chat.done', sessionId, text}.
The whole exchange is synchronous from the model’s perspective; from
the user’s perspective it streams in real time.
Browy delegates session storage to the GitHub Copilot CLI’s session
machinery (in ~/.copilot/). The host uses the id the extension
mints for a chat (sp-… in the side panel, dt-… in the DevTools
panel) as the Copilot session id. The extension keeps the current one
in chrome.storage.local, so reopening a panel resumes the same
Copilot session.
The agent also has its own scratch surfaces:
~/.browy/data/files/: sandboxed file scratch the save_file /
read_file agent tools operate against
~/.browy/data/notes.json: persistent key-value memory across chats
~/.browseragent/native-host.log: single rotating 5 MB log file
Tool definitions live in src/agent/tools/browser.ts. Each one
declares:
def: name, description, JSON-schema parameters (consumed by the SDK)
run(args, ctx): runs in the host. Browser tools call
ctx.cdp.send(method, params); src/agent/extension-context.ts
sends each call to the extension as a cdp.send message and awaits
the matching tool.result reply
This means the tool logic runs in the host, and only the CDP commands
run in the extension, where the page lives. Tools like save_file,
note_set and run_script run entirely in the host. The host is the
orchestrator and the bridge to the LLM.
src/agent/loop.ts wraps each tool with the SDK’s defineTool and
builds the tool list when it creates or resumes a Copilot session.
toolPolicy() in src/agent/tool-policy.ts filters it against the
user’s Settings → Tools
toggles, which the extension sends with every session.start. A tool
you switch off is dropped from the definitions and from the SDK’s
availableTools allowlist, and the opt-in host tools stay out until
you switch them on.
src/native-host.ts rebinds console.* so it writes to
~/.browseragent/native-host.log as well as stderr. When the host
starts and the file is over ~5 MB, it is renamed to
native-host.log.1 and a fresh one begins. Only one old copy is
kept; the next rotation overwrites it.
This is the first place to look when a session misbehaves. It contains
the host’s diagnostic output, not transcripts.