@logictan/dsh-browser-agent
A DSH browser agent that drives your own Chrome over CDP. Each page
observation builds an indexed table of the visible controls; one TypeSafe
/v1/systemone request decides both the operation (CLICK / TYPE_TEXT /
SELECT / PRESS_KEY / SCROLL_UP / SCROLL_DOWN / WAIT / DONE / BLOCKED) and the
target element index; only TYPE_TEXT asks a second model for the field
value, and that model comes from DSH's own ctx.llm service rather than a
second key.
TYPE_TEXT inserts through the keyboard rather than assigning element.value:
React and Vue track the value property, so an assignment updates their tracker
and the input event they then see is discarded as a no-op — the field stays
empty however convincing the trace looks. PRESS_KEY is the keyboard companion:
a filled form still has to be submitted, and an open suggestion list still has
to be accepted, and neither is reachable by clicking.
A <select> is one target carrying its options as data, and the value to set
is a second question — "set Country to France" is one action with a parameter,
not one action per country. Emitting an action per option also made the option
count compete with every real control for the observer's action budget: two
160-option dropdowns consumed it entirely and left CLICK with zero targets, so
the Search button was not in the element table at all.
Why it connects instead of launching
Launching a browser cannot carry your login state. The browser-use provider's
launch mode hard-codes --isolated, whose documented meaning is keep the
browser profile in memory, do not save it to disk. Attaching to an
already-running Chrome is the only shape that uses a profile that persists.
macOS Chrome refuses --remote-debugging-port on the default profile
(DevTools remote debugging requires a non-default data directory), so the
supported shape is a dedicated --user-data-dir profile:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/.dsh/chrome-agent-profile"
Log in once in that window; the login survives restarts because the profile is
on disk. The profile does not travel with config sync — logging in is a
per-device, one-time step.
The plugin never closes that browser. It disconnects the CDP session when a run
ends, so your window and tabs are left exactly as they were.
A run also never drives one of your tabs. It opens a page of its own and closes
it when the run ends, because a run navigates the page it drives and there is no
reliable way to ask the browser which tab you are looking at: Playwright's
connection turns on focus emulation, which makes every tab report
document.visibilityState === 'visible' (measured on a real profile — three
tabs, one active, all three read visible).
Settings
Settings → Plugins → browser-agent → configuration:
| Field | Meaning |
|---|
| TypeSafe key | not a config field — a credential (BROWSER_AGENT_TYPESAFE_KEY); see below |
| TypeSafe endpoint | defaults to https://api.typesafe.ai/v1/systemone |
| TypeSafe model | defaults to jev-latest |
| CDP endpoint | defaults to http://127.0.0.1:9222 |
| Max steps | browser actions per run; defaults to 60 |
| Text provider / model / reasoning effort | route for the TYPE_TEXT field-value call, chosen from the live model catalog. All empty = the session's own current route; with no session route either, a TYPE_TEXT decision fails the run with a settings hint instead of being recorded as a failed step. |
The TypeSafe key is a credential, entered once per device in the same card
under TypeSafe key. It lives in $DSH_HOME/.credentials.yaml as
BROWSER_AGENT_TYPESAFE_KEY and is never read back into the card. Storing it as
a config field would defeat that: a config value is only redacted from settings
reads, while the plaintext still sits in cordis.patch.yml, which the config
sync exports verbatim.
Tool
browser_agent({ url, goal }) navigates to url, runs the decision loop until
DONE/BLOCKED or a cap is reached, and returns a structured trace.
Scope
v1 covers common HTML/ARIA controls, single-tab, single-frame pages. Iframes,
shadow roots, canvas, file uploads, nested scrolling, pop-ups and screenshots
are out of scope.