Commit Graph
18634 Commits
Author SHA1 Message Date
Ryan Vogel fd06f8f785 fix(browser-extension): switch servers from a conversation, and lay out server rows like directories
The server section was only in the directory menu, which a conversation's
header replaces with its title menu, so servers could only be switched from a
new conversation. It is now a shared ServerMenu at the end of both menus
(switching starts over on the other server, since conversations belong to it).
Server rows used the default one-line item height for two lines of text; they
now use the directory rows' 44px two-line layout. The lists above shrink to
leave room for it; the History icon's menu, without a server section, keeps
its height.

Verified: typecheck, lint, build; the extension reloaded into the new build
in the user's Helium (relay reports the new build id).
2026-10-06 16:44:03 -04:00
Ryan Vogel 27c09b4d3a feat(browser-extension): sidebar as a plain client, browse tools, and pairing setup
The side panel no longer brings its own browser tools into opencode. It is an
opencode client like the desktop app and TUI; agents drive the browser through
the `browse` MCP server (renamed from opencode-browser), and setup pairs the
extension with opencode instead of using a native messaging host.

Removed / replaced:
- The per-session browser attachment (session-browser, page, cdp, files,
  profiling, analysis, diagnostics, preview): the panel no longer attaches
  `browser.*` tools or writes instruction entries. Instead, each prompt sent
  from the panel is preceded by a synthetic "Browser context" message
  (resume: false) naming the page; a tab shared with agents comes with its
  Chrome tab id. Sent only when the page or its sharing changed.
- The opencode plugin (site_scripts, browsing, browser.tabs.request) and its
  relay-link: these are browse MCP tools now (site_scripts_*, browsing_*,
  tabs_request), sent through the relay as `extension.request`
  (POST /extension/request, 10-minute timeout for the user's answer).
  tabs_request adopts the approved tab into the session.
- The native messaging host and service discovery: the extension keeps a list
  of paired servers (background/servers.ts, chrome.storage.local). connect.html
  redeems a one-time code from opencode's own pairing (POST /api/pair,
  GET /auth/connect/:code -> 30-day token used as the password) and renews the
  token a week before expiry. Loopback links pair right away; others ask.
  Servers can also be added by hand with a code or the service password; the
  directory menu switches, adds, and forgets servers.
- Permissions nativeMessaging, downloads, webNavigation.

Setup (cli/src/setup.ts):
- `install` writes the `browse` MCP entry (renaming the old one), removes the
  old host and plugin, then runs `connect`.
- `connect` finds browsers/profiles with the extension (default browser
  first; macOS-protected profiles are reported as unknown and asked about),
  opens connect.html#server=&code= there via the browser's executable and
  --profile-directory, opens the Web Store listing when missing, and prints
  the link and code when headless (no TTY, SSH, no display, --no-open).
- The relay reports the extension's build in /extension/status; install
  checks the build changed after asking for a reload, and says so (and skips
  connect) when it didn't.

Fixed: the extension's runtime.reload closed its socket with code 1001,
which browsers reject (only 1000 or 3000-4999), so the reload threw and never
ran; every install since reported "Reloaded" while Helium kept the old
build. It closes with 1000 now.

Also: session_adopt / `session adopt --tab-id` adopt a tab by Chrome tab id;
"Share tab" above the composer lets agents use the current tab; agent guide
renamed to skills/browse; store listing and privacy policy updated.

Verified in the user's Helium: `connect --yes` detected Helium/Ryan (default),
Chrome unreadable (macOS privacy), Arc without the extension, opened the
connect page, which showed "Connected · This computer · 4102". Through the
browse MCP server: site_scripts_list, tabs_request (returned the tab id and
adopted it), session_adopt({ tabId }) (success, exclusivity error, unknown-tab
error). The synthetic message with metadata was accepted and stayed queued
(resume: false). A clean headless Chromium loads the build and the connect
page answers. Install now reports a stale reload instead of claiming success.
Typecheck (extension, cli), lint. Upstream relay suite: 871/888; the new
failures are the 9 MCP tests asserting the old server name.
Not yet verified: the panel UI (server menu, Share tab, composer flow).
2026-10-06 16:39:48 -04:00
Ryan Vogel a73b58c031 feat(browser-extension): drive pages with Patchright so they can't detect the debugger
Bot checks (deviceandbrowserinfo.com's isAutomatedWithCDP and its worker
variant) override Error.prepareStackTrace and console.log an Error: with
CDP's Runtime domain enabled, Chrome serializes console arguments for the
debugger and calls the override. Playwright enables Runtime on every page and
worker. Patchright (patchright-core, a Playwright fork pinned to the same
1.63.0) never does.

- playwright-core is replaced by patchright-core (same API; two runtime
  imports, the rest types).
- Without Runtime, a page's console messages and uncaught errors don't reach
  execute. pageConsole.start()/stop() (new page-console.ts) enables Runtime
  on that one page through its own CDP session, drops what Chrome replays
  from before, and feeds the same execute logs. Documented in the skill and
  the MCP execute description.
- patchright-tuning.ts: Patchright finds elements by walking the DOM over CDP
  (for closed shadow roots), and pulled the whole DOM tree before every
  count()/evaluateAll()/all(). Through the extension, getByRole("link").count()
  on a 4,000-element Wikipedia page took ~650-1,900 ms vs Playwright's ~9-29
  ms. count() and querySelectorAll() go back to Playwright's lookup (injected
  script in the isolated world, which pages can't see) and the shadow-root
  scan is off; Playwright never reached into closed shadow roots either. The
  internals are reached through the first page's main frame; if an update
  renames them, lookups stay correct but slower and relay.log records
  patchright.tuning.

Verified in the user's Helium:
- deviceandbrowserinfo.com: isAutomatedWithCDP and isAutomatedWithCDPInWebWorker
  gone; only hasInconsistentWorkerValues remains (Helium reports
  hardwareConcurrency 16 on pages, 18 in workers; neither engine touches it).
  Sannysoft 31/31, BrowserScan 18/18 normal, reCAPTCHA v3 0.9, CreepJS 0%
  headless/stealth, Fingerprint scraping demo not blocked: unchanged.
- Upstream test suite: 880/888, the same 8 intentional failures as before.
- Benchmark page (form, iframe, 200-item list, snapshot, screenshot), 3 runs
  each: medians within noise, identical form results and snapshot hashes.
- Wikipedia: count/all/evaluateAll/textContent/isVisible within a few ms of
  Playwright with the same values; click, hover, fill, waitForSelector same.
- pageConsole: off by default (no page logs, page can't detect a debugger);
  start() captures log/warn/error/pageerror across executes and reloads;
  stop() makes the page undetectable again.
- Mouse/typing/scroll humanization unchanged.
2026-10-06 12:14:58 -04:00
Ryan Vogel bf81e11848 feat(browser-extension): scroll smoothly instead of jumping
Agent scrolling stuttered: Playwright's mouse.wheel is one wheel event (the
page jumps by the whole delta in one frame), and before acting on an
off-screen element Playwright calls DOM.scrollIntoViewIfNeeded, which snaps
the page there instantly.

- Each wheel event is now split into an eased ~60 Hz burst (quick start,
  slowing to a stop) with the original event carrying the last frame.
- DOM.scrollIntoViewIfNeeded on the top frame first scrolls toward the
  element with wheel flicks of 450-900 px from where the pointer rests,
  re-measuring after each and stopping when the element stops moving; the
  original command then only fine-tunes. Logged as input.scroll.
- relay.ts imports human-input by name instead of a star import (repo style).

Verified in the user's Helium: wheel(0, 800) now moves over 28 frames, at most
80 px per frame (was 800 px in one frame); clicking a button 2,600 px down
scrolls over 105 frames, at most 83 px per frame, in 3.1 s. cli typecheck, lint.
2026-10-06 11:54:56 -04:00
Ryan Vogel d494106add feat(browser-extension): make agent mouse and keyboard input look human
Playwright teleports the pointer: one mouseMoved straight to the exact center
of the target, then a press and release ~50 ms apart. The events are trusted,
but behavioral bot detection scores exactly that shape.

The relay now paces Input.* commands per tab (cli/src/human-input.ts):
- each pointer move is preceded by a curved trajectory from the last position
  (cubic Bezier with a sideways bow, minimum-jerk speed, Fitts'-law duration,
  fading tremor, occasional overshoot and correction), sent at ~60 Hz with the
  round trip counted toward each gap so samples don't arrive in bursts;
- clicks land 1-3 px off center (same offset for move, press, release), after
  a 30-130 ms settle, with a 60-140 ms hold;
- key presses are spaced 30-110 ms apart;
- one tab's input runs in order, since Playwright doesn't always wait for the
  move before pressing (that interleaved two paths in the first attempt).
Each movement is logged to relay.log as input.move. OPENCODE_BROWSER_HUMAN_INPUT=0
turns it off. The skill tells agents to use pressSequentially where typing is
watched.

Verified in the user's Helium: a 690 px click went from 1 mousemove in 0 ms
to ~45 moves over ~650 ms (path ratio ~1.03, bow 40-80 px, 15-20 ms gaps),
clicks hit 1-3 px off center, typing 40-110 ms per key; a 4-click httpbin
form still submits correctly (1.7 s -> 3.7 s); bot.sannysoft.com 0 failed,
BrowserScan 18/18 normal, reCAPTCHA v3 score 0.9. cli typecheck, lint.
2026-10-06 11:49:04 -04:00
Ryan Vogel 0532ba301a fix(browser-extension): close agent tabs with their group, and keep sessions through a reload
Two ways agent tabs ended up loose in the user's tab list:

- When the relay was done with a tab (session deleted or detached), the
  extension only ungrouped it. A tab an agent opened now closes with its
  group; a tab the user lent an agent still just leaves the group.
- Reloading the extension (now also done by install) detached every tab, and
  the dying worker reported those detaches to the relay, which dropped its
  sessions' tabs and later treated them as the user's. On runtime.reload the
  extension now stops reporting detaches and freezes its stored attachments;
  those (tab id + URL) live in chrome.storage.local, and the new worker
  re-attaches the same pages, so the relay reclaims each session's tab on
  the next hello. The URL check keeps a browser restart from re-attaching a
  stranger that reused a tab id.

Verified: extension and cli typecheck, lint. In the user's Helium: a "news"
session's lobste.rs tab survived POST /extension/reload (the session still
answered with its page), and `session delete news` closed that tab.
2026-10-06 11:39:21 -04:00
Ryan Vogel 4633044ef0 fix(browser-extension): don't leave loose agent tabs after a reload, and reload on install
Reloading the extension (or an extension update) emptied its memory and
session storage, so on reconnect it ungrouped every relay group's tabs it no
longer recognized: the groups vanished and the agents' tabs stayed open as
ordinary tabs.

- Which tabs agents opened (tab cleanup) and which groups are agents'
  (collapse/placement) now live in chrome.storage.local, cleared on browser
  startup since tab and group ids don't survive a restart.
- After a reconnect, tabs an agent opened stay in their group (the relay
  reclaims named sessions' tabs, idle cleanup closes the rest); only tabs the
  user lent an agent go back to ordinary tabs.
- install reloads a connected extension through the relay (new POST
  /extension/reload, sending runtime.reload) after replacing the unpacked
  files, waiting up to 5s for a just-restarted relay to see it, so updates
  apply without a trip to chrome://extensions.
- The extension's hello carries its build time and the relay logs it, so a
  browser running an old build shows in relay.log.

Verified: extension and cli typecheck, lint. In the user's Helium,
POST /extension/reload reconnected the extension with the new build within a
second (relay.log). The reload-without-ungrouping path couldn't be checked
headless (Chrome for Testing blocks a command-line extension after
runtime.reload).
2026-10-06 11:35:39 -04:00
Ryan Vogel a8f4716311 feat(browser-extension): close idle agent tabs after 10 minutes
Agent tabs unused for 10 minutes (was 30) now close, checked every minute (was 5), so a tab goes within about 11 minutes. The agent guidance, skill, MCP tool, and CLI flag descriptions say 10.

Verified: extension and cli typecheck, cli build.
2026-10-06 11:31:26 -04:00
Ryan Vogel e7279fc152 fix(browser-extension): keep agent tab groups at the start of the strip
With vertical tabs the far right is the bottom of the list. Agent groups now sit at the start of the strip, right after pinned tabs (the top of a vertical strip), keeping their order; a tab opened or moved in before them puts them back.

Verified: extension typecheck and lint. Headless Chrome against an isolated relay with a pinned tab: [research] and [Agent · example.net] sat right after the pinned tab and stayed there after the user opened another tab.
2026-10-06 11:29:45 -04:00
Ryan Vogel 5abc156104 feat(browser-extension): keep agent tab groups at the far right
Agent groups landed wherever Chrome put the new tab. Each agent group now moves to the end of its window's tab strip when it gains a tab, and after the user opens a tab or moves one into the window (debounced 150ms), keeping the groups' order among themselves. A user dragging a group elsewhere isn't fought until the next new tab.

Verified: extension typecheck and lint. Headless Chrome against an isolated relay: with user tabs open, two agent sessions' groups ([research], [Agent · example.net]) sat at the far right, and stayed there after the user opened another tab.
2026-10-06 11:06:27 -04:00
Ryan Vogel 3ca1cc981b fix(browser-extension): make updates take effect in running relays and MCP servers
After reinstalling, the running relay and every MCP server opencode had
started kept the old code, and the relay's build check then refused them
("relay build ... is newer than CLI build"; restart the client). Install now:
- writes the runtime's fingerprint (sha256 of dist/cli.mjs) into the
  opencode-browser MCP entry's environment, so the entry changes whenever the
  runtime does and opencode restarts the server with the new code;
- restarts a running relay whose build no longer matches (detected with
  `status --json`'s stale flag), keeping tabs open.

Verified: cli typecheck and build; reinstalling on this machine updated the
MCP entry, restarted the stale relay, opencode reloaded the MCP server (26
tools), and an MCP execute in the user's browser returned HN's newest stories.
2026-10-06 10:59:06 -04:00
Ryan Vogel 7ef1a25eae feat(browser-extension): keep agent tab groups collapsed until needed
Agent tab groups stayed expanded and took over the tab strip. A new module,
background/agent-groups.ts, collapses every agent group (conversations'
groups and agent sessions' groups) when it's created, and again whenever the
user's active tab in that window moves outside it. Chrome expands a group by
itself when one of its tabs becomes active (the agent focuses it or the user
picks it), and collapsing a group that holds the active tab would switch the
user away, so that case is left open. A handoff ("Your turn") expands the
tab's group and keeps it open while it waits; it folds back up once the
handoff resolves. Known groups survive worker restarts in session storage.

Verified: extension typecheck and lint. Headless Chrome against an isolated
relay: the "checkout" session's group was collapsed after its tab opened,
expanded while a handoff waited, and collapsed again after Continue.
2026-10-06 10:51:05 -04:00
Ryan Vogel fe369ae72d feat(browser-extension): name agent tab groups and close idle agent tabs
Agent tab groups had opaque names (every conversation's group was
"opencode"; MCP/CLI sessions showed ids like mcp-48092553), and tabs agents
opened stayed open forever.

Names:
- A conversation's group is named after the conversation title (cut to 28
  characters; "opencode" until it has one), refreshed when it groups a tab.
- An agent session's group uses the session's chosen name ("github"); ids
  the relay or MCP made up (mcp-<hex>, adjective-animal-###) become
  "Agent · <site>", following the tab's navigation. Each session keeps one
  group by id, so renames no longer split its tabs.

Cleanup (background/tab-cleanup.ts):
- Tracks only tabs agents created (browser.tabs.open, relay tabs.create);
  an agent command or the user switching to the tab counts as use. Tabs the
  user shared are never tracked; pinning a tab or dragging it out of the
  group hands it to the user.
- Every 5 minutes, closes tracked tabs unused for 30 minutes
  (chrome.storage.local tabCleanupIdleMinutes; 0 turns it off), skipping
  the active tab of the focused window, tabs playing audio, recording, or
  waiting for a handoff. State survives worker restarts.
- On demand: "Close agent tabs" in the panel's agents menu, the relay
  command tabs.cleanup, POST /tabs/cleanup, the MCP tool tabs_cleanup, and
  `opencode-browser tabs cleanup [--idle-minutes N]`.
- The skill and the in-session guidance tell agents to name sessions,
  close their tabs, and that idle tabs close by themselves.

Verified: extension and cli typecheck, lint. Headless Chrome against an
isolated relay: a "github" session's tab was grouped as "github", an
unnamed session's as "Agent · iana.org", and `tabs cleanup --idle-minutes 0`
closed both and their groups. The conversation group name and the 30-minute
alarm were not exercised end to end.
2026-10-06 10:48:47 -04:00
Ryan Vogel b679f7bb4b fix(browser-extension): install a packed build with node, not the source runner
With OPENCODE_BROWSER_PACKAGE set from a source checkout, install reported "(from source)" and wrote a bun host wrapper for the installed runtime. A package spec now always means a real runtime install run with node.

Verified: cli typecheck; installing a packed 0.0.2 on this machine wrote a node host wrapper and MCP entry pointing at the runtime.
2026-10-06 10:37:50 -04:00
Ryan Vogel 0bd901e7bb feat(browser-extension): connect the extension to its own relay as "agents"
The extension's relay link now talks to OpenCode Browser's relay
(ws://127.0.0.1:19988, protocol 2) and nothing in the extension refers to
Browser Control:

- Renamed browser-control.ts / src/browser-control / browser-control.tsx to
  agent-relay.ts / src/agent-relay / agents.tsx; panel, welcome tab, page
  pill and card, badges, and the toolbar menu ("Let agents use this tab")
  speak of agents. Storage keys are agentRelayPort/agentRelayProfile.
- Synced with upstream's extension at 8c80ac2: removes other extensions'
  frames and password-manager overlays (1Password closed shadow hosts,
  LastPass) while attached or running, re-attaches after a target_closed
  detach, retries CDP calls that hit an extension frame or a lost debugger,
  falls back to tabs.update for Page.navigate, names tab groups after the
  agent session, and ports the offscreen recording cleanup. profile.rename
  is gone (the relay no longer sends it).
- The "rejected" status is removed (our relay accepts our IDs);
  "incompatible" now says to run `npx opencode-browser-cli install`.
- Reliability: reconnects back off exponentially (0.5s to 15s, +/-25%
  jitter) with a 10s connect timeout, listeners register once per worker,
  and per-connection timers are cleared on close.
- Logging: a 500-entry relay event log (connects, close codes, backoff,
  slow or failed commands) in memory and chrome.storage.session, with Copy
  diagnostics in the agents menu, the problem notice, and the welcome tab.
- Agent guidance points at browser.* for the user's tabs and describes the
  opencode-browser MCP server as part of the same product.

Verified: typecheck, build, lint, and no "browser control"/19989 matches.
Headless Chrome for Testing against an isolated relay: status connected,
execute returned example.com's title, a handoff completed from the page's
Continue, a default-mode recording wrote a 2.6 MB webm, "Let agents use this
tab" attached a user tab, password-manager overlays were removed (synthetic
elements), and the link reconnected about 3s after a relay restart. Light
and dark screenshots checked. The target_closed re-attach path was not
exercised.
2026-10-06 10:18:48 -04:00
Ryan Vogel 476f398510 feat(browser-extension): bring Browser Control into OpenCode Browser
OpenCode Browser no longer depends on the separate Browser Control product.
Its relay, CLI, MCP server, and agent guide (anomalyco/browser-control
8c80ac2, MIT, Anomaly) now live in packages/browser-extension/cli and ship
as the opencode-browser-cli npm package:

- Renamed throughout: `opencode-browser` command and MCP server,
  OPENCODE_BROWSER_* environment, opencode-browser skill, injected page ids.
- Its own port, 19988, so a Browser Control relay on 19989 can't hold it,
  and it accepts only OpenCode Browser's store and unpacked extension IDs
  (the path-hashed bundled-extension origin is gone).
- All files under one data root (src/paths.ts: ~/.local/share/
  opencode-browser, %LOCALAPPDATA% on Windows, OPENCODE_BROWSER_HOME):
  runtime, logs, relay catalogs and lifecycle, sessions, secrets.
- One entry (src/main.ts, bundled to dist/cli.mjs with npm dependencies
  external): setup commands (install, extension, uninstall, host) go to
  setup.ts, everything else to the relay CLI; `status` prints setup state
  first. The managed relay respawns the same entry.
- install now installs this exact version into <data root>/runtime with
  npm (OPENCODE_BROWSER_PACKAGE overrides the spec), points the native host
  and a new "opencode-browser" MCP entry at it, and warns about leftover
  browser-control MCP entries instead of configuring them. uninstall also
  removes the MCP entry and the data root.
- Logging: relay.log is always on (relay ready, extension connected/
  rejected/closed with codes, every HTTP request with status and ms except
  status/version polls, faults) and rotates to relay.log.1 instead of
  truncating; host.log records each native messaging request with timing.
- cli/ has its own package.json, bun.lock, and tsconfig so its pinned
  dependencies (effect 4.0.1, playwright-core 1.63.0, ...) stay out of the
  monorepo lockfile. The release workflow installs, typechecks, builds, and
  smoke tests it.

Verified: cli typecheck; browser-control's own test suite run against the
port outside the repo: 880/888 pass, the 8 failures assert the removed
layout (dist/mcp.js, path-hashed origins, extension/dist). From an npm pack
tarball in a temp HOME: install (5.7s), the native host answered service and
plugin requests, the MCP server listed 25 tools over stdio, and `execute`
started a managed relay, drove headless Chrome with the extension to
example.com, and returned the title; status and relay.log looked right.
2026-10-06 10:18:42 -04:00
Ryan Vogel 8ee61be65b ci: release OpenCode Browser with publish-browser-extension
cli/package.json is now the one release version: the store zip and the
extension bundled in the npm helper are stamped with it. Bumped to 0.0.2
(0.0.1 is on npm and in the store draft).

.github/workflows/publish-browser-extension.yml runs on a
browser-extension-v<version> tag (checked against cli/package.json) or by
hand: typecheck, build the helper and the store zip, smoke test the helper,
upload the zip as an artifact, then publish opencode-browser-cli to npm with
provenance (trusted publishing, or OPENCODE_BROWSER_NPM_TOKEN; skipped when the
version exists) and attach the zip to a GitHub release. Manual runs only
build unless `publish` is checked.

Verified: actionlint (only the repo's custom runner label is unknown to it);
locally, cli build + `bun run package` produced version 0.0.2 in both the zip
(no key) and the bundled extension, the built helper ran, and `npm pack
--dry-run` packs 0.0.2.
2026-10-03 19:25:25 -04:00
Ryan Vogel 5d7ddb898b refactor: drop opencode browser from the CLI in favor of the npx helper
The opencode-browser-cli npm package (packages/browser-extension/cli) does
everything `opencode browser install|status|uninstall|host` did and works with
any opencode release, so the CLI no longer needs to ship it. Removed the
command, its handlers, and services/browser-extension.ts; commands.ts and
index.ts match the branch base again, so this PR no longer changes
packages/cli. The README, plugin header, and host comment describe the npx
helper instead.

Verified: cli and extension typecheck, root lint; `git diff <merge-base> --
packages/cli` is empty.
2026-10-03 19:25:25 -04:00
Ryan Vogel 6dabba2867 fix(browser-extension): send the current page only with a panel prompt
Every switch of the active tab in the panel's window rewrote the session's
"opencode-browser.page" context (1s debounce), so a session showed a stream
of "Instructions updated" rows while the user browsed, including while the
agent was working or the user chatted from the TUI.

The background no longer pushes page context. The composer sends it with
`prepare` right before a prompt from the side panel, and only when it
changed since the last one it sent to that session. A tab included with a
new conversation is described as shared.

Verified: typecheck, build, lint; headless panel against the live service:
the entry was written with the first prompt, unchanged after switching tabs,
and updated to the new page with the next prompt. The test session was
deleted.
2026-10-03 19:19:23 -04:00
Ryan Vogel f963d124c1 feat(browser-extension): attach images in the side panel composer
The composer only sent text. Images can now be pasted, dropped, or picked with a + button; they show as removable thumbnails above the text and are sent inline as data URLs (like the desktop composer), with or without text. PNG, JPEG, GIF, and WebP up to 20 MB; attachments are kept per draft and restored if sending fails.

Verified: typecheck, build, lint; headless panel against the live service pasted an image, sent it with a prompt to a new conversation (the model answered, the timeline showed the thumbnail), then the test conversation was deleted.
2026-10-03 16:23:08 -04:00
Ryan Vogel 8f38d16246 fix(session-ui): show pasted images that have an inline mention
Clients that name a pasted image in the text (the TUI's "[Image 1]") send it as a file with a mention. Mentioned files were excluded from the attachment row, so the image never appeared: only the highlighted "[Image 1]" text. Inline or data: images with a mention now also show as thumbnails; the text highlight is unchanged, and non-image mentions are still left out.

Verified: session-ui typecheck; in the side panel, a TUI message with [Image 1] now shows its thumbnail.
2026-10-03 16:23:08 -04:00
Ryan Vogel 376e950e74 feat(browser-extension): let agents ask to see a tab (browser.tabs.request)
Agents could only use tabs they opened or the user shared, so "can you see
this tab?" ended with instructions to click Share tab. The plugin adds
browser.tabs.request({ query?, reason? }) next to the built-in browser.tabs
tools: it finds the user's current tab (or the most recently used open tab
whose title or URL matches the query), shows a "Share the tab you're on?"
dock in the panel with the tab's title, site, and the agent's reason, and on
Share gives the session that tab (same path as the Share tab button) and
returns { tabID, title, url }. Tabs the session already has return without
asking; browser/extension pages and a missing panel fail with guidance.
Session guidance now points agents at browser.tabs.request instead of asking
the user to click Share tab.

Verified: typecheck, build, lint; light/dark screenshots of the dock with an
injected request. The relayed tool call itself was not exercised end to end
(needs the plugin loaded in the live service).
2026-10-03 15:55:29 -04:00
Ryan Vogel b1445cf912 feat(browser-extension): switch conversations from the session title
While a conversation is open, its title in the header is a menu of recent conversations across every directory, like the directory picker on a new conversation. The directory line under the title is gone. History is global too and only shows when no conversation is open (the picker replaces it), so the list is fetched once.

Verified: typecheck, build, lint; headless panel against the live service opened a conversation from History, the title menu listed sessions from several directories, and picking one switched to it.
2026-10-03 15:00:36 -04:00
Ryan Vogel 6e6fadae5e feat(browser-extension): add opencode-browser-cli npm helper
`opencode browser install` only exists on this branch, so nobody on a
released opencode can connect the extension. packages/browser-extension/cli
is a standalone npm package (opencode-browser-cli) that does the same setup
without changing opencode:

- install: copies itself to ~/.local/share/opencode-browser (npx caches can
  be cleared), registers the ai.opencode.browser host for every installed
  Chromium browser (macOS/Linux manifests, Windows registry), adds the
  Browser Control MCP to opencode's global config (jsonc edits keep comments),
  starts the service, and copies the bundled unpacked extension to a
  permanent folder for Load unpacked.
- host: answers the extension with the URL and password from the user's
  installed `opencode service start` / `service get password`, and installs
  the extension's plugin.
- status, extension (opens the folder), uninstall.

The bundle includes jsonc-parser and the extension build (manifest key kept
so the unpacked ID matches the helper). The panel, welcome tab, store copy,
and privacy policy now point at `npx opencode-browser-cli install`. The
opencode CLI command is unchanged.

Verified: lint, extension typecheck; `npm pack` then npx from the tarball in
a temp HOME/XDG registered Helium, kept a commented opencode.json and added
the MCP entry, started the service; the wrapper answered service (URL +
password) and plugin (written) messages over native messaging; status and
uninstall worked.
2026-10-02 20:38:40 -04:00
Ryan Vogel 9c34279187 chore(browser-extension): capitalize OpenCode in the store summary
The store shows the manifest description as the listing summary. Verified: bun run package.
2026-10-02 14:37:45 -04:00
Ryan Vogel 355f60ee89 feat(cli): accept the Chrome Web Store build of OpenCode Browser
The store assigned mfnicocicmmlkpjnaffgihfjhdgjkdjg. The helper manifest and Browser Control's allowed origins now include it next to the unpacked ID. STORE_URL stays unset until the listing is published, so install keeps pointing at the unpacked build.

Verified: cli typecheck.
2026-10-02 14:34:52 -04:00
Ryan Vogel 0fcf9fb641 feat(browser-extension): package for the Chrome Web Store
- `bun run package` builds and writes release/opencode-browser-<version>.zip
  without the manifest `key` (the store rejects it and assigns its own ID) and
  without source maps. release/ is ignored.
- history, bookmarks, topSites, and sessions are now optional permissions,
  requested from the panel's Allow click on an agent's browsing-data request,
  so install shows fewer warnings and review sees them as user-initiated. A
  session grant only counts while the browser permissions are held; revoking
  them in browser settings makes the panel ask again.
- store/listing.md (listing copy, single purpose, permission justifications,
  data disclosures) and store/privacy-policy.md (to host for the listing).

Verified: typecheck, lint, and `bun run package` produced a zip whose manifest
has no key and lists the four optional permissions. The Chrome permission
prompt on Allow was not exercised (headless can't answer it).
2026-10-02 14:33:28 -04:00
Ryan Vogel b3de11a73a chore: name the product OpenCode Browser
Product name only: manifest, panel, welcome tab, page pill, CLI copy, agent-facing text, and the relay profile name. Identifiers (opencode-browser, ai.opencode.browser, `opencode browser`) are unchanged.

Verified: extension and cli typecheck, extension build, lint.
2026-10-02 14:13:35 -04:00
Ryan Vogel 439aaa61a1 fix(browser-extension): don't say "You're set" while Browser Control needs attention
The welcome footer only checked the opencode connection. Verified: typecheck and build.
2026-10-02 14:06:21 -04:00
Ryan Vogel 52a8a1992b feat(browser-extension): onboarding tab, setup polish, and Browser Control UI
- welcome.html: first-run checklist with live status for the opencode
  connection (copyable `opencode browser install`, auto re-check), site
  scripts, Browser Control (offline reads as idle; conflict/rejected/
  incompatible get fixes), and opening the side panel (button, shortcut, pin
  hint). It uses its own watch-only port, so it never answers approvals.
- Panel setup screens share the welcome page's copy and components
  (onboarding.tsx); manual server entry is a disclosure.
- Panel Browser Control: header menu while connected or holding tabs
  (working/waiting dot, tab list, "Let Browser Control use this tab"), a
  "Your turn" dock with Show tab and Continue, and a dismissible fix-it strip.
- Page pill and handoff card restyled for light/dark with system fonts only
  (pages' own "Inter" made them render in italics).
- Relay retries no longer flip the status to "connecting" each attempt, which
  made the conflict/rejected notices flicker; it still shows after a live
  connection drops.

Verified: typecheck, build, lint; 62 light/dark screenshots of the welcome
page, panel setup and Browser Control states, and page pill/card, including a
real relay on port 19990 where Continue in the panel completed a handoff and
"Let Browser Control use this tab" attached a user tab.
2026-10-02 13:28:54 -04:00
Ryan Vogel 5d32de98d3 fix(cli): let an existing Browser Control MCP entry accept opencode Browser
Install skipped a browser-control MCP server that was already configured, so
relays it starts still refused opencode Browser. It now adds
BROWSER_CONTROL_EXTENSION_ORIGINS to that entry (comments and other settings
kept via jsonc edits) and reports "updated"; entries that already have it are
left alone.

Verified: cli typecheck; in a temp HOME/XDG with a commented config holding a
bare browser-control entry, install added the env var and kept the comment, and
a second run reported it already configured.
2026-10-02 13:16:20 -04:00
Ryan Vogel 2db2908440 feat(browser-extension): take over Browser Control's extension role
opencode Browser now connects to the local Browser Control relay
(ws://127.0.0.1:19989/extension, protocol 2) and runs its commands, so the
relay's CLI, MCP server, and Playwright execute drive tabs through this
extension instead of the separate Browser Control extension.

- debugger-hub.ts: one chrome.debugger attachment per tab shared by owners
  (each opencode page and the relay); the tab detaches with its last owner.
  cdp.ts now attaches through it instead of calling chrome.debugger directly.
- browser-control.ts: relay link ported from browser-control's background
  (hello with a stored profile id, debugger/tab/group/badge/page-status/profile
  and recording commands, events only for relay-owned tabs, reconnect with
  backoff, /version probe to tell "not running" from "refused").
- Recording: offscreen document + tabCapture ported verbatim; frames encoded
  with the relay's BCRD format.
- content.js (separate IIFE build): in-page status pill and "Your turn"
  handoff card in opencode's style; restyles the relay's ghost cursor.
- Badge shows ON/RUN/WAIT for relay tabs, else the site-script count. The
  toolbar still opens the panel; "Let Browser Control use this tab" moved to
  the icon's context menu. First install opens welcome.html (placeholder).
- Manifest adds offscreen, tabCapture, activeTab, contextMenus and the
  content script.

Verified: typecheck, build, and lint pass. Headless Chrome for Testing with an
isolated relay on port 19990: `browser-control status` reports the extension
connected (protocol 2, profile "opencode Browser"); `browser-control execute`
opened example.com, hovered a link, returned the title; the page showed the
status pill and the panel received the relay tab state.
2026-10-02 13:13:57 -04:00
Ryan Vogel 2ba1f4e05c feat: rename Open Extension to opencode Browser and set up Browser Control in install
The extension becomes "opencode Browser", the product that will also take over
Browser Control's extension, and the CLI command follows it.

Renamed:
- packages/open-extension -> packages/browser-extension (@opencode/browser-extension),
  manifest name, panel copy, README, and agent-facing text.
- `opencode sidepanel` -> `opencode browser` (install/status/uninstall/host);
  services/sidepanel.ts -> services/browser-extension.ts.
- Native host ai.opencode.sidepanel -> ai.opencode.browser; plugin id, file
  (plugins/opencode-browser.ts), relay RPC (opencode-browser.relay), panel port,
  session context keys, and cursor element id use "opencode-browser".

Install now:
- adds the Browser Control MCP server to opencode's global config (browser-control
  on PATH, else npx @opencode-ai/browser-control), with
  BROWSER_CONTROL_EXTENSION_ORIGINS so existing relays accept this extension;
  skips it when a browser-control server is already configured;
- uses clack steps and, in a terminal, waits up to 3 minutes for the extension's
  first connection; status also reports the Browser Control MCP server.

Verified: cli and extension typecheck, extension build, and root lint pass; in a
temp HOME/XDG, `opencode browser install` registered Helium, wrote the MCP entry
to opencode.json, started the service, and `status` reported all three.
2026-10-02 13:08:23 -04:00
Ryan Vogel b4d1189696 feat(cli): register the side panel host for the same browsers as ChatGPT
Matches the browser and OS coverage of ChatGPT's extension host, keeping our
existing Helium, Arc, and Chrome Beta/Canary support.

- macOS: adds Opera (com.operasoftware.Opera) and Chrome for Testing (both
  "Google/Chrome for Testing" and "Google/ChromeForTesting" manifest dirs).
- Linux: adds Opera, Chrome Unstable, and Chrome for Testing; Chrome-family
  builds honor CHROME_CONFIG_HOME before XDG_CONFIG_HOME.
- Windows (new): writes the manifest next to a host.bat wrapper and registers
  it under HKCU\Software\Google\Chrome (read by Chrome and the other Chromium
  browsers) and HKCU\Software\Microsoft\Edge NativeMessagingHosts keys;
  status reads and uninstall deletes those keys; the store page opens with
  `start`.
- status now uses a shared registered() check instead of per-file lookups.

Verified: cli typecheck and root lint pass; on macOS with HOME/XDG in a temp
directory, install registered Chrome for Testing (both dirs), Opera, and Helium,
status listed them, and uninstall removed every manifest; the Linux and Windows
browser tables were checked by evaluating browsers() with process.platform
overridden. Windows registry writes were not run (no Windows machine).
2026-10-02 12:45:29 -04:00
Ryan Vogel e18a61b92c refactor(open-extension): use the opencode CLI as the native host
Replaces the package's own bun native host and host:install script with
`opencode sidepanel install`, so store users only need opencode.

- The extension talks to ai.opencode.sidepanel and, once per worker, sends its
  bundled plugin source (plugin/open-extension.ts?raw) for the host to install;
  failures are retried on the next worker start.
- The plugin is now self-contained (type-only imports) and owns the relay RPC
  definition; src/shared/relay-rpc.ts re-exports it.
- Setup screen and README point to `opencode sidepanel install`.

Removed: host/host.ts and host/install.ts (and the host:install script), now
provided by the CLI.

Verified: package typecheck and build pass; the bundled background contains
the relay ID and plugin source; the CLI host accepted the plugin message.
2026-10-02 12:39:56 -04:00
Ryan Vogel 3ba3bed4a1 feat(cli): add sidepanel command for the browser side panel extension
The side panel extension (packages/open-extension) finds the local background
service through a Chrome native messaging host. Until now that host was a bun
script installed from the repository, which store users would not have.

`opencode sidepanel` makes the CLI the host:
- install: registers ai.opencode.sidepanel for installed Chromium browsers
  (Chrome, Brave, Edge, Arc, Vivaldi, Helium, Chromium; macOS and Linux) with a
  small wrapper that runs `opencode sidepanel host`, ensures the service is
  running, and opens the Chrome Web Store page in the default browser once
  STORE_URL is set.
- host: answers {type:"service"} with the service URL (0.0.0.0 rewritten to
  127.0.0.1) and password via Service.ensure, and writes the extension's
  opencode plugin to plugins/sidepanel.ts when {type:"plugin"} differs, so the
  plugin always matches the installed extension. Records the connection time.
- status / uninstall: show and remove the registration.

The change is additive: new files plus one command spec and one handler map
entry. No new dependencies; the CLI does not import the extension package.

Verified: cli typecheck and root lint pass; with HOME/XDG pointed at a temp
directory, install wrote the Helium manifest and wrapper, the host answered
service (URL + password), plugin (changed, then unchanged), and an unknown
request over native messaging framing with no stray stdout, status reported
the connection, and uninstall removed everything.
2026-10-02 12:39:56 -04:00
Ryan Vogel 05f5563672 chore(open-extension): align package metadata with the workspace
Use the shared workspace version (2.0.22) like the other packages and drop the
test script: the package has no test files, and CI runs `turbo test
--affected`, where `bun test` with no tests would fail the job.

Verified: bun install leaves the lockfile consistent; typecheck and build pass.
2026-10-02 12:23:38 -04:00
Ryan Vogel ff7b91a281 docs(open-extension): add package README
Explains what Open Extension does, how to build, load, and install the native
host and plugin, how the pieces connect (panel, background, opencode browser
plugin, relay plugin), what is ported from gui-extensions, and what an
extension cannot do (heap snapshots, Lighthouse).

Verified: commands and paths match package.json scripts and the manifest key.
2026-10-02 12:22:29 -04:00
Ryan Vogel 16acdd091d fix(open-extension): hide scrollbars on full-height panel areas
The timeline's scrollbar took a visible column at the right edge of the narrow
side panel. The timeline, site scripts manager, file preview, and setup screen
now use the ui package's no-scrollbar utility; they still scroll by trackpad,
wheel, and keyboard. Menus and code previews keep their scrollbars.

Verified: build passes and the bundled CSS contains the no-scrollbar rules
(scrollbar-width:none and ::-webkit-scrollbar display:none).
2026-10-02 12:21:55 -04:00
Ryan Vogel 3fd079abe7 feat(open-extension): add browsing access prompt, file preview, and page scripts UI
- Browsing access dock (after any script approval, in every view): which
  conversation asks and what it asked for first, Don't allow / Allow for this
  conversation, "1 of N" when queued.
- File preview for browser.preview: a sheet over the transcript with the file
  name and path; images, Markdown (session-ui Markdown), and text/code
  (session-ui File) render; PDF/audio/video/binary or text over 2 MB show
  "Can't preview this file here"; read errors show the server message.
  Absolute paths are read from their parent directory like the desktop app.
- Site scripts on this page: the header's Site scripts button shows the
  number of enabled scripts on the active tab (matching the toolbar badge);
  the manager lists "On this page" above "All scripts"; each script has a
  Tweak action that prefills the composer with
  `Change the site script "<name>" (id <id>): ` without discarding a draft.

Verified: typecheck and build pass; in Chrome for Testing against the live
service, the access dock and preview were rendered from injected messages
(text, image, Markdown, unsupported, missing), and the page-scripts chip,
manager sections, and Tweak prefill were checked in dark and light at 420px.
2026-10-02 12:21:55 -04:00
Ryan Vogel 0956864218 feat(open-extension): add capture tools, downloads, browsing data, and script badges
Browser tools that answered "not available yet" now work, and agents can read
browsing data. Backend only; panel UI for access prompts, previews, and
per-page scripts follows separately.

- Files: a per-tab in-memory store (ported from gui-extensions files.ts) for
  screenshots, captures, and downloads; files.list / files.get work.
  Downloads the agent triggers (matched by referrer or the tab it used in the
  last 15s) are tracked through chrome.downloads; their bytes are re-fetched
  from the URL with the site's cookies, or read from the page for blob: URLs.
- Traces: trace.start/stop/analyze, ported from gui-extensions profiling.ts,
  filtered to the tab's renderer process via TracingStartedInBrowser.
- CPU profiles: chrome.debugger does not expose Profiler/HeapProfiler to
  extensions ('Profiler.enable' wasn't found), so cpu.start/stop records the
  v8 sampling profiler into a trace and rebuilds a .cpuprofile from
  Profile/ProfileChunk events; cpu.analyze works on it. The timeline
  categories are required for the renderer-process record.
- heap.* and lighthouse report a clear "not available from an extension".
- browser.preview forwards the path to panels showing the session.
- New browsing tools in the opencode plugin (history, bookmarks, top_sites,
  recently_closed). Each session must be allowed once in the side panel
  (access / access.reply messages); the grant is remembered.
- The toolbar badge shows how many enabled site scripts run on each tab.
- The relay RPC is renamed open-extension.relay (scripts-rpc -> relay-rpc,
  scripts-link -> relay-link) now that it carries more than scripts.
- Manifest adds history, bookmarks, topSites, sessions, and downloads.

Verified: typecheck and build pass; Chrome for Testing against the live
service: browsing history/bookmarks/top_sites/recently_closed returned data
after one access prompt for four calls; a panel agent got a screenshot, a
trace and trace analysis, a CPU profile (294 ms, top self-time function
reported), files.list, a downloaded file saved server-side via files.get,
and preview acknowledged; heap.snapshot returned the explanation. Test
sessions were deleted.
2026-10-02 12:21:54 -04:00
Ryan Vogel b1214d188c feat(open-extension): apply site script changes to open tabs immediately
Toggling or installing a site script only took effect on the next page load,
so the user had to reload matching tabs by hand.

- Installing a new script or turning one on injects it into matching open
  tabs right away with chrome.userScripts.execute (Chrome 135+), in the same
  world it is registered in; tabs where injection fails, or browsers without
  execute, are reloaded instead.
- Turning a script off, deleting it, or replacing an installed script reloads
  its open tabs (old and new matches), because what a script already did to a
  page cannot be undone in place.
- Toasts and tool results now say what happened ("Running now in 1 open
  tab." / "Reloaded 1 open tab."), and agents are told not to reload tabs that
  were already updated.

Verified: typecheck and build pass; in Chrome for Testing with user scripts
allowed, installing a marker script on an open example.com tab ran it without
a reload (a page variable survived), turning it off reloaded the tab and the
marker was gone, and turning it on again injected it live; the three toasts
read as above.
2026-10-02 12:21:54 -04:00
Ryan Vogel e05a15c8d3 feat(open-extension): show the agent's cursor and steer agents to click
In panel sessions agents moved around sites with browser.navigate (full page
loads) and browser.evaluate instead of clicking, so watching them looked like
the page kept reloading, and CDP input was invisible when they did click.

- Pointer actions (click, hover, drag, fill, select, check, scroll) first
  glide a visible "opencode" cursor to the target in the page and pulse on
  press. It is drawn in a closed shadow root on a pointer-events:none layer
  in the top frame, enters from the side-panel edge, fades after 6s idle,
  and a drawing failure never fails the action.
- Session guidance now says the user watches the tabs: find controls with
  browser.snapshot/find and use click/fill/press; navigate only to open a new
  site or an exact URL; evaluate only to read data.
- Snapshots join adjacent StaticText runs into one line. example.com puts
  each letter in its own element, so the 500-line budget ran out before the
  "Learn more" link got a ref, and the agent fell back to Tab+Enter.

Verified: typecheck and build pass; in Chrome for Testing a panel agent asked
to follow example.com's "Learn more" now used browser.find + browser.click
(landing on iana.org) and the cursor with its press pulse was captured on the
page mid-action; earlier run without the snapshot fix fell back to keyboard.
Test sessions were deleted.
2026-10-02 12:21:54 -04:00
Ryan Vogel 20d900b6d5 feat(open-extension): tell panel sessions where they run and allow page-world scripts
Agents in panel sessions did not know they were inside the browser: asked to
highlight tweets, one reached for the browser-control CLI to inspect x.com and
planned a CSP-nonce hack to read page data from the isolated world.

- When a session's browser attaches, the background adds an
  "open-extension" session context entry: the browser.* tools control the
  user's real browser (opened and shared tabs), prefer them over other
  browser automation, ask the user to share the current page or open it
  yourself, and install persistent changes with site_scripts.
- An "open-extension.page" entry names the page the user is looking at and
  whether it is shared (with its tabID). It updates one second after the
  active tab settles in a window showing that session, only when it changes.
- Site scripts gain world: "page" (header: // @inject-into page), registered
  in Chrome's MAIN world for wrapping fetch/XHR or reading app state. The
  approval shows a warning that page-world scripts and the site can see each
  other. Tool and namespace descriptions explain when to use it and to
  inspect the site with browser.* first.

Verified: typecheck and build pass; showing a fresh session in the panel in
Chrome for Testing wrote both context entries to the live server (guidance,
and "looking at Example Domain (https://example.org/) ... not shared"); the
test session was deleted.
2026-10-02 12:21:54 -04:00
Ryan Vogel fb30443fb7 feat(open-extension): add site script approvals, install cards, and manager
Side panel UI for site scripts:
- Approval dock (permission-dock look) for agent site_scripts.install
  requests in every view: name, sites, description, warnings, collapsible
  code, Deny / Install (or Update when replacing), "1 of N" when queued.
- Install card in a session for the newest userscript code block in the
  agent's replies: Install, Update, or Installed; dismissable.
- Site scripts manager behind a header button: enable switch, delete with
  confirm, empty state, and a "turned off" notice with steps, a shortcut to
  the extension's details page, and Check again when Allow user scripts is off.
- notice messages show as success toasts.

The tool description and decline message now name the panel's actual
buttons (Install/Update, Deny) so agents stop telling users to "Approve".

Verified: typecheck and build pass; in Chrome for Testing against the live
service, a real agent install request showed the dock ("Update site script?",
replaces "Hi title") and Deny was returned to the agent; install card,
manager states, and toasts checked in dark and light at 420px. The test
session was deleted.
2026-10-02 12:21:54 -04:00
Ryan Vogel 9390e9a3b8 feat(open-extension): add site scripts managed by the extension
Agents (and the user) can now extend websites with scripts that Open Extension
injects itself through chrome.userScripts, the API Tampermonkey uses, instead
of asking the user to install a userscript manager.

- Background stores scripts in chrome.storage.local, registers them in the
  isolated USER_SCRIPT world, and reconciles registrations on startup. Drafts
  may carry a // ==UserScript== header (@name, @description, @match,
  @exclude-match, @run-at); unsupported keys become warnings.
- New opencode plugin (plugin/open-extension.ts) adds site_scripts.install /
  list / get / set_enabled / remove tools. It relays each call over a new
  open-extension.scripts RPC: the plugin emits a control event, the extension
  claims the command once (so two browser profiles never both ask), runs it,
  and returns the result. Without an open side panel the tools fail fast
  after 8 seconds with instructions.
- Agent installs wait for the user's approval in any open side panel
  (approvals message / approval.reply); a cancelled tool call withdraws it.
- Manifest gains the userScripts permission and <all_urls> host access,
  which userScripts needs to inject into matched sites.
- host:install also bundles the plugin into ~/.config/opencode/plugins.

Verified: typecheck and build pass; the live server loaded the plugin and
exposed the site_scripts namespace; with no panel, list failed after 8s with
the instruction; in Chrome for Testing with user scripts allowed, an agent
install was approved, registered, and ran on example.com (data attribute set).
Side panel UI for approvals and script management follows separately.
2026-10-02 12:21:54 -04:00
Ryan Vogel 4609624af7 fix(open-extension): load lazy chunk styles and drop the timeline focus ring
The timeline chunk is lazy-loaded, and with modulePreload disabled Vite never
linked its stylesheet, so TextShimmer rendered both of its text layers
("ThinkingThinking", "Working...Working..."). Build one stylesheet instead
(cssCodeSplit: false); the panel loads locally, so splitting buys nothing.

The timeline scroller is focusable (tabIndex -1) for keyboard scrolling and
showed the browser's blue focus outline after clicks; it now has none.

Verified: build emits a single style-*.css linked from sidepanel.html that
contains the text-shimmer rules; typecheck passes.
2026-10-02 12:21:54 -04:00
Ryan Vogel d51a5e40ec feat(open-extension): open on a new conversation in the home directory
The panel opened on a list of recent sessions for a remembered project. It now
always opens on an empty new conversation (wordmark, one-line hint, composer),
like a browser side chat, in the opencode service's default directory (the
user's home), read from the server instead of hard-coded.

- Removed the home session list and the remembered-project default. Past
  conversations are behind a History button in the header (up to 30 for the
  current directory, busy indicator); New conversation returns to the empty
  state.
- Rebuilt the directory picker: two-line rows with avatar, name, and ~-relative
  path (the old rows overlapped), duplicates merged by path, temporary
  directories hidden, Home first, then most recently active, scrolling list.

Verified: bun typecheck and build pass; headless Chrome for Testing with the
extension and native host against the live service rendered the new
conversation, picker, and history menu in dark and light at 420px; opening a
session from History and switching directory work. No prompts were sent.
2026-10-02 12:21:54 -04:00
Ryan Vogel 981b96320e fix(open-extension): drop InlineTextBox nodes from snapshots
Chrome's full accessibility tree gives every StaticText an InlineTextBox child
with the same text, so snapshots listed each text run twice and hit the
500-line cap early. On example.com (one span per letter) the agent's snapshot
was truncated before useful content. Skipping InlineTextBox keeps every name
via its StaticText parent.

Verified: bun typecheck and build pass; CDP getFullAXTree on example.com shows
773 InlineTextBox nodes duplicating 785 StaticText nodes.
2026-10-02 12:21:54 -04:00
Ryan Vogel 96be289244 feat(open-extension): add the side panel UI
Replaces the placeholder side panel with an opencode UI sized for a browser
side panel, reusing the desktop's real pieces: the session-ui SessionTimeline,
@opencode/ui primitives and tokens, and the client's createData store.

- Setup screen for a missing native host or unreachable server, with a manual
  URL/password form and automatic discovery as the default.
- Header with a remembered project picker; home lists recent sessions.
- Session view with the live timeline, stick-to-bottom while streaming, and
  older history on scroll.
- Browser strip: attachment status, "Use here" takeover, the agent's tab chips
  (focus, unshare), and "Share tab" for the active tab.
- Composer with agent/model pickers, steer while busy, Stop, per-session
  drafts, and "Include tab" when starting a session; permission and question
  docks forked from the desktop.
- The panel port reconnects after Chrome restarts the service worker and
  re-announces its window and shown session.

Background: failed panel requests now return an `error` message shown as a
toast, and showing a session re-sends the window's active tab so share state
is correct immediately. host/host.ts gains `export {}` so top-level await
type-checks.

Verified: bun typecheck and vite build pass; built dist loaded unpacked in
headless Chrome against the live service (manual connection): home, menus,
existing session, send from home with streaming and Stop, question dock
dismiss, and a forced service-worker restart that reconnected with state
kept, at 360-400px in light and dark. Test sessions were deleted.
2026-10-02 12:21:54 -04:00