Repository navigation
feat: bring the Codex extension to feature parity with the Claude Desktop extension - #8
Conversation
…_URL_OVERRIDE
claw-server (the BrowserOS agent browser) writes its runtime state to
~/.browserclaw/, not ~/.browseros/. The old ~/.browseros/server.json path
this plugin read never existed against current builds, so discovery leaned
entirely on port-probing and failed on custom ports.
Point discovery at ~/.browserclaw/runtime.json (same { url } shape) and
rename the override env var to BROWSERCLAW_URL_OVERRIDE to match the Claude
Desktop extension. BROWSEROS_URL_OVERRIDE stays accepted as a legacy alias
so existing ~/.codex/config.toml entries keep working.
The richer on-disk sources (manifest, log), the loopback guard, and the
/system/health probe path follow in later commits.
…ealth probe Ports the multi-source on-disk discovery from the Claude Desktop extension. claw-server records its live base URL in three places on every successful bind; discovery now reads them in descending order of reliability: 1. ~/.browserclaw/runtime.json (atomic, no harness link needed) 2. ~/.browserclaw/mcp-manager/manifest.json (populated after a link) 3. ~/.browserclaw/claw-server.log (tail for the listen marker) 4. default http://127.0.0.1:9200 Each candidate is probe-tested against /system/health (the endpoint claw-server actually serves) before it is accepted, so a stale record for a dead port is skipped. This replaces the previous blind 9100/9200/9300 port sweep, which failed whenever BrowserOS ran on a custom port without a user override. Discovery still returns a plain URL string or null; the tagged outcome states and loopback guard land in the next commit.
…back discoverBaseUrl now returns a tagged discriminant instead of a bare URL: running / override-not-loopback / override-unreachable / not-installed / installed-not-running. The wrapper renders a distinct, actionable message per state instead of one generic "BrowserOS is not running" for every failure mode. The user-configurable override is now restricted to loopback hostnames (127.0.0.1, [::1], localhost). A misconfigured or hostile override can no longer make the wrapper forward Codex's MCP traffic to a public host; non-loopback URLs short-circuit to override-not-loopback before any network I/O. On-disk-discovered URLs come from claw-server itself and stay trusted. When discovery reports a healthy URL but the /mcp connect then fails, the wrapper re-tags the result as installed-not-running so the user gets the "is BrowserOS open / on the expected port" guidance rather than a stale success message. The single-flight reconnect, dedup'd discovery logging, and signal-handler teardown were already present from the initial port and are unchanged.
Ports the Claude Desktop extension's test suites and adds two cases specific
to this plugin's dual env-var handling:
- discovery.test.js: loopback guard (rejects public/LAN/IPv6, accepts
127.0.0.1 / localhost / [::1]) plus BROWSERCLAW_URL_OVERRIDE vs the legacy
BROWSEROS_URL_OVERRIDE precedence.
- on-disk-discovery.test.js: fixture-based coverage of readRuntimeUrl,
readManifestUrl (http/sse/stdio/tagged-object shapes), and readLogUrl
(last-match, malformed-line skipping, tail truncation).
The tail-truncation fixture pads past the real 1 MiB tail so the
partial-first-line discard branch is actually exercised (the ported original
used a 40 KiB filler that never triggered it).
Tests import only the pure discovery modules, so a new node-only Test
workflow runs them on PR with no dependency install. 28 pass / 0 fail.
Ports the Claude Desktop extension's smoke harness. It spawns the bundled
dist/wrapper.js (exactly what Codex spawns) and drives it over stdio through
three scenarios:
- sad path: dead loopback override -> tools/list empty, tools/call isError
with the "is unreachable" message.
- non-loopback path: public URL override -> tools/call surfaces the
loopback-rejection message.
- happy path: auto-discover, initialize, list tools, forward a navigate
call, assert a well-formed tool-result envelope.
Dev-only (the happy path needs a running BrowserOS), so it is not wired into
CI. Verified locally end-to-end against a live claw-server: all three
scenarios pass, happy path connected and listed 23 forwarded tools.
README gains Tests + Smoke test subsections under Local development.
Adds the release automation the Claude Desktop extension has but this repo
lacked. On a v* tag the job:
- verifies the tag equals the version in BOTH plugin.json and package.json
(a mismatch, or bumping only one file, fails the release);
- reinstalls with the frozen lockfile, rebuilds dist/, and fails if the
committed bundle drifted from source at that tag;
- creates a GitHub Release with generated notes, marked prerelease when the
tag carries a -suffix.
No archive is built: Codex installs the plugin straight from the git repo, so
the Release is purely a version pin + changelog. Actions pinned to SHAs.
A clean-machine-to-working-test walkthrough for Codex plugin reviewers, mirroring the Claude Desktop extension's REVIEWER.md. Covers what the plugin is (a zero-tool stdio proxy that forwards BrowserOS's tools verbatim), install via `codex plugin marketplace add`, the verify prompt, the forwarded tool surface, the [browseros] stderr log format and discovery `source` field, the loopback-restricted config.toml override, and uninstall.
Bumps plugin.json and package.json in lockstep for the feature-parity release: BrowserClaw-aligned multi-source discovery, loopback-guarded override, tagged discovery states, unit + smoke tests, and release automation. The wrapper reads the version at runtime, so dist/ is unchanged.
Greptile SummaryThis PR brings the BrowserOS Codex plugin to feature parity with the Claude Desktop extension by overhauling discovery, adding tagged error states, and wiring up CI, tests, and a release workflow.
Confidence Score: 3/5The discovery and error-message machinery is sound for every named failure mode, but a gap in the catch block of tryOpenInner means unexpected connection errors produce a blank user-facing message — a silent regression on a code path that previously always emitted the down-message. The blank-message regression in wrapper.js affects the else-branch of the inner-connect catch: any non-TransportConnectError leaves state.lastResult frozen at plugins/browseros/server/wrapper.js — the else-branch of the TransportConnectError catch in tryOpenInner needs state.lastResult updated to prevent the empty-message regression. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[discoverBaseUrl] --> B{BROWSERCLAW_URL_OVERRIDE\nor BROWSEROS_URL_OVERRIDE set?}
B -- yes --> C{isLoopback?}
C -- no --> D[override-not-loopback]
C -- yes --> E{probeHealth}
E -- ok --> F[running / override]
E -- fail --> G[override-unreachable]
B -- no --> H[readRuntimeUrl\n~/.browserclaw/runtime.json]
H --> I{normalizeUrl + probeHealth}
I -- ok --> J[running / runtime]
I -- fail --> K[readManifestUrl\nmcp-manager/manifest.json]
K --> L{normalizeUrl + probeHealth\nif different from runtimeUrl}
L -- ok --> M[running / manifest]
L -- fail --> N[readLogUrl\nclaw-server.log tail]
N --> O{normalizeUrl + probeHealth\nif different from previous}
O -- ok --> P[running / log]
O -- fail --> Q{any source == DEFAULT_URL?}
Q -- none match --> R{probeHealth DEFAULT_URL\n127.0.0.1:9200}
R -- ok --> S[running / default]
R -- fail --> T{isBrowserclawInstalled?\n~/.browserclaw exists}
T -- no --> U[not-installed]
T -- yes --> V[installed-not-running]
Q -- already tried --> T
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[discoverBaseUrl] --> B{BROWSERCLAW_URL_OVERRIDE\nor BROWSEROS_URL_OVERRIDE set?}
B -- yes --> C{isLoopback?}
C -- no --> D[override-not-loopback]
C -- yes --> E{probeHealth}
E -- ok --> F[running / override]
E -- fail --> G[override-unreachable]
B -- no --> H[readRuntimeUrl\n~/.browserclaw/runtime.json]
H --> I{normalizeUrl + probeHealth}
I -- ok --> J[running / runtime]
I -- fail --> K[readManifestUrl\nmcp-manager/manifest.json]
K --> L{normalizeUrl + probeHealth\nif different from runtimeUrl}
L -- ok --> M[running / manifest]
L -- fail --> N[readLogUrl\nclaw-server.log tail]
N --> O{normalizeUrl + probeHealth\nif different from previous}
O -- ok --> P[running / log]
O -- fail --> Q{any source == DEFAULT_URL?}
Q -- none match --> R{probeHealth DEFAULT_URL\n127.0.0.1:9200}
R -- ok --> S[running / default]
R -- fail --> T{isBrowserclawInstalled?\n~/.browserclaw exists}
T -- no --> U[not-installed]
T -- yes --> V[installed-not-running]
Q -- already tried --> T
|
… flow The code was updated to read ~/.browserclaw/ but the README and other user-facing text still called the app "BrowserOS" and never explained how to install the plugin correctly. Two fixes: Naming: the browser users install and run is BrowserClaw (BrowserOS's Chromium browser for agents, from browseros.com/agents). Every user-facing app reference now says BrowserClaw — README, REVIEWER.md, SKILL.md, the plugin/marketplace display name, and the wrapper's own down/error messages and connect log. The technical identifiers stay put: plugin slug `browseros`, tool prefixes `mcp__browseros__*`, the `[browseros]` log tag, the `browseros@browseros` install coordinate, and the `BROWSEROS_URL_OVERRIDE` legacy env alias. "BrowserOS" is retained only where it names the org (author, developerName, "BrowserOS's browser"). Install flow: corrected against the official Codex docs / CLI. Registering the marketplace is only step one; the plugin is installed with `codex plugin add browseros@browseros` (<plugin>@<marketplace>), then a new thread. Documented the `--ref` version pin and the `/plugins` UI path, and fixed Update to `marketplace upgrade` + re-add. No logic change; tests still 28/28, dist rebuilt, smoke sad/non-loopback green (asserted substrings unchanged).
The script quoted the glob (`node --test "test/*.test.js"`), which the shell does not expand and Node < 21 cannot expand itself, so CI failed with "Could not find .../test/*.test.js". Node's default test discovery finds `test/*.test.js` with no positional argument and works on every supported Node. Also bump the Test workflow's Node from the deprecated 20 to 22.
This is the BrowserClaw extension (the Codex counterpart to browserclaw-claude-desktop), so its identity is BrowserClaw, not browseros. Every plugin/tool/config identifier is renamed: - plugin + marketplace slug: browseros -> browserclaw (install is now `codex plugin add browserclaw@browserclaw`) - MCP server key in .mcp.json: browseros -> browserclaw (tools now surface as `mcp__browserclaw__*`; SKILL.md updated to match) - log tag: [browseros] -> [browserclaw] - config.toml env block: .mcp_servers.browserclaw.env - package + wrapper identity: browserclaw-codex, browserclaw-codex-wrapper - plugin/skill directories: plugins/browseros -> plugins/browserclaw, skills/browseros -> skills/browserclaw (CI path filters + working dirs, marketplace source path, README layout all follow) The BROWSEROS_URL_OVERRIDE legacy env alias is dropped entirely: the plugin was never published, so there are no existing config.toml entries to keep working. Discovery reads only BROWSERCLAW_URL_OVERRIDE; the two precedence tests for the alias are removed (26 unit tests, down from 28). "BrowserOS" survives only where it correctly names the org (LICENSE, author, developerName, "BrowserOS's Chromium browser") and the real npm package `@browseros/agent-mcp-manager`, plus the repo path browseros-ai/ browseros-codex-app. Verified: 26/26 tests, dist reproduces from source, smoke sad/non-loopback green with the [browserclaw] tag.
Summary
The Codex extension started as a fork of the Claude Desktop extension's wrapper/discovery code and then fell behind while the Claude extension hardened its discovery, testing, and release story. This PR closes that delta.
The headline fix: discovery now reads the same
~/.browserclaw/sources that claw-server (the BrowserOS agent browser) actually writes. The plugin previously read~/.browseros/server.json— a path current builds never write — so it leaned entirely on a blind 9100/9200/9300 port sweep and failed whenever BrowserOS ran on a custom port.What changed (as 8 successive commits)
~/.browserclaw/runtime.json+BROWSERCLAW_URL_OVERRIDE. Point discovery at the path claw-server writes; rename the override env var to match the Claude extension.BROWSEROS_URL_OVERRIDEstays accepted as a legacy alias so existing~/.codex/config.tomlentries keep working./system/health. Porton-disk-discovery.js: runtime.json → mcp-manager/manifest.json → claw-server.log tail → default:9200, each probe-tested against the endpoint claw-server actually serves. Replaces the port sweep.discoverBaseUrlreturnsrunning/override-not-loopback/override-unreachable/not-installed/installed-not-running; the wrapper renders a distinct, actionable message per state. The user override is restricted to loopback hostnames so a misconfigured value can't forward Codex's MCP traffic to a public host. (The single-flight reconnect, dedup'd logging, and signal-handler teardown were already present from the original port; the one delta — re-tagging a URL-found-but-connect-failed asinstalled-not-running— ships here since it's inseparable from the tagged-state handling.)test.yml.dist/wrapper.jsand drives it over stdio through the disconnect / loopback-rejection / happy paths.v*tag: verify the tag matches bothplugin.jsonandpackage.json, rebuild and checkdist/for drift, cut a GitHub Release with generated notes.Intentionally NOT changed
browseros), tool prefixes (mcp__browseros__*), marketplace identity, and the BrowserOS user-facing brand.dist/wrapper.jsinstall model (Codex has no install hook, so a committed single-file bundle stays correct — no.mcpb, no shipped node_modules).interfaceblock (Codex-specific, no Claude counterpart).Test plan
npm testinplugins/browseros→ 28 pass / 0 fail.bun run buildreproduces the committeddist/(build-check CI gate is green).