Skip to content

feat(opencode): open OpenCode 2 in the chat view and list 2.x models before the first message - #28424

Merged
AmoabaKelvin merged 20 commits into
stablyai:mainfrom
AmoabaKelvin:feat/opencode-2-chat-ui
Oct 18, 2026
Merged

AmoabaKelvin merged 20 commits into
stablyai:mainfrom
AmoabaKelvin:feat/opencode-2-chat-ui

Conversation

@AmoabaKelvin

@AmoabaKelvin AmoabaKelvin commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 36 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1524 $\color{#cf222e}{\Huge{\mathbf{−}}}$​177 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1347
Prod 44 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​861 $\color{#cf222e}{\Huge{\mathbf{−}}}$​372 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​489

ELI5

Orca has two launcher entries for OpenCode: "OpenCode" (the opencode command) and "OpenCode 2" (the opencode2 command). With Chat UI on, "OpenCode" opened Orca's chat view but "OpenCode 2" still opened a terminal, and on OpenCode 2.x the model picker stayed empty until the first message. Now OpenCode 2 opens as a chat, its models are listed before the first message, its skills show in the / menu, and a 2.x conversation from the terminal can be resumed in it.

What Changed

Before.

  • New tab > OpenCode 2 opened a terminal running opencode2 --standalone, even with Chat UI on.
  • With opencode on 2.x, the picker had no models until the first message: Orca ran opencode models --verbose, which every 2.x release rejects.
  • Resume in chat sent every 2.x conversation to the OpenCode chat. Where opencode is still 1.x, that chat showed the old messages, said it could not reopen the session, and started a new one.

After.

  • OpenCode 2 opens as a chat when opencode2 is a stable 2.x from 2.0.14. Anything else (older 2.x, a pre-release, 1.x, no opencode2) keeps the terminal.
  • Both OpenCode chats list their models before the first message, per project: a project whose opencode.json adds or removes models shows its own list.
  • A 2.x conversation resumes in the OpenCode 2 chat, or in the OpenCode chat when the host cannot run OpenCode 2.
  • The / menu lists the skills OpenCode reads, and never lets a skill hide a command that OpenCode or Orca runs instead.

Mechanism.

  • Chat registration. One OpenCode definition now serves both names. The opencode2 row runs opencode2 acp, accepts 2.x only, and shares OpenCode's account, history rows and conversation import.
  • Models before the first message. On 2.x the probe opens a session with session/new in the asking chat's folder, reads its models with the code a running chat uses, and deletes it with session/delete; an agent that cannot delete a session is not asked, a failed delete is logged, and a read with no folder stops before spawning anything. 1.x keeps opencode models --verbose, now run in the chat's folder. The startup and settings refreshes skip OpenCode, since they have no folder.
  • One model list per project. For agents that list in a chat's folder (both OpenCode chats), the folder is part of the catalog key, a running chat saves its lists under its own folder in the order it reported them, and answers carry modelsDifferByWorkspace so the app's first-frame cache never shows one folder's list in another. Other agents keep one list per account.
  • One chat per conversation. opencode and opencode2 open the same conversations. Both single-writer checks (when a resume is asked for, and when the record is saved) now treat them as one conversation store, read from the shared table of which chats take over a Session History row. The stored handle format is unchanged.
  • Resume in chat. That table now lists chats in order (opencode2 rows: OpenCode 2, then OpenCode). The resume asks the host to admit the first and falls back to the next only when the host refuses it; a host that cannot answer yet keeps the first, whose own launch reports it.
  • Copying a chat for the CLI. Resume in New CLI copies the conversation into this host's OpenCode store, so the CLI must not point at a server. The check now lives on the OpenCode launch spec, so it covers OpenCode 2 chats too, and the app checks every CLI whose chat could own the conversation before it asks for a copy.
  • Skills. OpenCode chats list skills from the six folders OpenCode reads: its config folder, ~/.claude/skills, ~/.agents/skills, and the project's .opencode/skills, .claude/skills and .agents/skills. Claude's plugins are not offered. The scanned list stays after the first message, because 2.x reports no skills. A picked skill is sent as typed.
  • / menu precedence. A command the chat's own send handles (/compact, /clear, /model, /effort) is never replaced by a same-named skill; this also fixes Claude's commands before its first report. An OpenCode command counts as the skill only when 1.x reported it under that skill's own description; any other clash shows the command with "Also a skill name".
  • Smaller tables. OpenCode 2 is added to the sign-in hint, the project config files that decide a default model, the orchestration skill check, the chat permission modes and the phone app's icon. Notification titles now use the shared agent names, which also fixes about 17 agents that showed their raw id (for example copilot).

Why

  • Why a separate OpenCode 2 chat, not the OpenCode one? The OpenCode chat runs opencode. Where opencode is 1.x, that is the wrong program for a 2.x conversation.
  • Why a throwaway session for the model list? 2.x's own listing commands are not reliable: every release rejects --verbose, and opencode models and opencode api model.list answer empty from a fresh start on 2.0.14 to 2.0.26 and missed a late provider on 2.0.28 (v2: opencode models --refresh no longer recognized (flag removed from models command) anomalyco/opencode#53682, #53724, #36117). A new session offers exactly what a chat offers, because it is how a chat starts. bb (get-bb/bb) does the same and also opens it in the chat's folder, since OpenCode 2 records a project for every folder a session opens in.
  • Why one list per folder? A project's opencode.json can add or remove providers. bb keys OpenCode's list by folder for the same reason.
  • Why list skills from folders instead of asking OpenCode? 2.x reports no skills over the chat protocol, and its skill list call answers empty on the first call after a start (server: skill.list is empty on startup anomalyco/opencode#51680, open). The folder rules are OpenCode's own; I checked them against 1.19 and 2.0.29.
  • Why send a picked skill as typed? bb rewrites it into an instruction, but only for mentions its composer marks as skills, and it keeps its own copy of what it sent. Orca has neither, and rewriting broke crash recovery (which matches OpenCode's stored copy by text) and turned /tmp is filling up into a skill request.
  • Why no fallback to opencode when opencode2 is missing at launch? Same rule as any agent whose command is gone: new launches open the terminal, and an existing chat says it could not restart. The OpenCode 2 Command setting can point at opencode.

Linked Issue

Fixes #19581

Visual Proof

Before (main): New tab > OpenCode 2 opens a terminal.

before-opencode2-terminal.png

After: it opens a chat tab; a tool call, its output and the model picker are shown.

after-opencode2-chat.png

A real OpenCode 2.0.29 chat with "Ask for approval" on, in a hidden dev build.

Permission card for a shell command:

02-permission-card.png

After "Allow once": the command ran and OpenCode replied with its output.

03-tool-allowed-done.png

After "Reject": OpenCode 2 ends the turn itself (stopReason: "cancelled"), shown as Interrupted. 1.x instead fails the tool and finishes the turn.

05-rejected-done.png

Stop during an approved sleep 40: the turn is Interrupted and the command's process is gone.

07-stopped.png

After an app restart: the chat reopens with its history shown once.

08-reopened.png

A follow-up in the reopened chat: OpenCode loaded the same conversation and recalls the first command's output.

09-reopened-follow-up.png

Testing

Real app, hidden dev builds over the debugging port, macOS arm64, fresh profiles, OpenCode folders isolated from my own. OpenCode 1.18.35, 1.19.0, 1.19.1, 2.0.13, 2.0.14, 2.0.28 and 2.0.29.

  • OpenCode 2 from the launcher with 2.0.28 and 2.0.14: chat tab. With 2.0.13: terminal.

  • In an OpenCode 2 chat: reply, tool call row, Stop ("Interrupted", child process gone), permission card (Allow once and Reject), question tool (plain-text fallback), and the picked model is the one OpenCode's store says ran.

  • Two OpenCode 2 chats at once: no messages crossed. Close and reopen from Session History, and app restart: same session, history shown once.

  • Folder workspace (not git): the chat runs in the folder. Paired host: a client built from main against this host opens the chat; this client against a host built from main gets the terminal, as today.

  • Models: 2.0.14 and 2.0.29 list 71 and 11 models before any message, with no session left in OpenCode's store and only the chat's folder in its project list. 1.19.0 lists through models --verbose with no session. After a restart, no OpenCode 2 refresh ran until a chat asked.

  • One list per project: with opencode 1.19.1 and opencode2 2.0.29 sharing one database, project A listed 10 models and project B 11, including the model its opencode.json adds; Orca's trace shows one listing per folder.

  • / menu: skills in each of the six folders are listed before and after the first message on 2.0.29, and once each on 1.19.0. With project skills named compact and review, the menu shows OpenCode's own commands with "Also a skill name". A picked skill, /tmp is filling up and /review the branch reach OpenCode as typed.

  • Resume in chat with opencode 1.19.1 and opencode2 2.0.29: a 2.x terminal conversation opened in an OpenCode 2 chat with its history, showed no reopen warning, and the follow-up landed in the same conversation in OpenCode's store, with the agent recalling its earlier reply.

  • Recorded OpenCode 2.0.29 traffic from a real chat in Orca, replayed through the chat code: an approved shell command, a rejected permission (2.x ends the turn as cancelled, unlike 1.x), Stop during a running command, and a reopen after restart whose session/load replays the history once before a follow-up.

  • New tests, each failing without its change: the model-list session and its cleanup, one list per folder (sequential reads, simultaneous reads and a running chat's update), the 1.x listing folder, the first-frame cache, both single-writer checks in both directions and for two racing resumes, resume order and fallback (only on a refusal), a running chat's reports saved in order, copy refusal for both OpenCode CLIs, a 2.x terminal tab started as opencode handed to the OpenCode 2 chat, the / menu collisions for OpenCode and Claude, skill folders, notification names, launch limits and row ownership.

  • pnpm lint, pnpm tc, the three audits, check:code-quality:changed, check:react-doctor:changed, check:zustand-selector-fanout and the mobile typecheck pass on the head. 300 dependent test files pass (3322 tests).

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Replaces fix(opencode): list OpenCode 2.x models before the first message #28423, which this PR now includes.
  • Not tested: Windows (there opencode2 is a .cmd file), Linux, and the mobile app, so the phone icon is unseen. Mobile reads chat tabs for any agent the host lists, and the mobile typecheck and its chat tests pass.
  • Mixed versions: one new optional field on the model list answer (modelsDifferByWorkspace); an older client ignores it, and an older host never sends it, so the first-frame cache behaves as today. No new stream message.
  • SSH and WSL workspaces: unchanged; they keep the terminal for every agent.
  • Best effort cleanup: a model-list session whose listing is stopped while OpenCode creates it can be left behind as one empty conversation, since there is no id to delete it by.
  • Overlap: fix(agents): stop detecting OpenCode v1 from an opencode v2 install #25008 would list only "OpenCode 2" where both names are 2.x; this should land first, or those users lose the chat view.
  • Left for follow-ups:
    • Question and form cards (OpenCode's question tool is off for now; 2.x then asks in plain text).
    • Using a picked skill by instruction, as bb does. That needs the composer to mark picked skills on the message and the chat's record to keep what was sent.
    • Skills in folders moved by XDG_CONFIG_HOME or OPENCODE_CONFIG_DIR, and OpenCode's built-in skills, are not listed. Typing one still works.
    • No @opencode2 orchestration group.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

@AmoabaKelvin
AmoabaKelvin force-pushed the feat/opencode-2-chat-ui branch 2 times, most recently from 2417b43 to 31e14cc Compare October 15, 2026 14:04
@AmoabaKelvin
AmoabaKelvin force-pushed the feat/opencode-2-chat-ui branch 4 times, most recently from b242d86 to e12da85 Compare October 17, 2026 16:57
@AmoabaKelvin AmoabaKelvin changed the title feat(opencode): open OpenCode 2 in the chat view feat(opencode): open OpenCode 2 in the chat view and list 2.x models before the first message Oct 17, 2026
@AmoabaKelvin
AmoabaKelvin force-pushed the feat/opencode-2-chat-ui branch 2 times, most recently from 4a10da5 to f9f8a97 Compare October 17, 2026 22:59
@AmoabaKelvin
AmoabaKelvin marked this pull request as ready for review October 17, 2026 23:02
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e7dd9416-3032-456f-8c6c-c0aed1c9c827
📥 Commits

Reviewing files that changed from the base of the PR and between 0c618f4 and 5acb93d.

⛔ Files ignored due to path filters: 1
  • src/main/runtime/__snapshots__/structured-agent-launch-parity.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing: 1
  • src/main/skills/skill-discovery-concurrency.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.

📝 Walkthrough

The change adds OpenCode 2.x as a Chat UI agent, with ACP launch support, session-based model discovery, and OpenCode-compatible conversation adoption. It adds workspace-scoped model catalogs and updates skill discovery, composer commands, resume routing, notifications, and mobile icon assets. ACP recordings and tests cover OpenCode 2.x tool approval, cancellation, and session reopening.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 5acb9

The reviewed change only adjusts the skill-discovery test for an additional absent OpenCode root. No actionable merge risk remains.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 51.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 45 functions across 51 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed The PR satisfies [#19581]. It adds an opencode2 Chat UI launch path for supported stable 2.x versions. It preserves terminal behavior for unsupported versions. It adds session resume, native ACP ren…
Out of Scope Changes check Passed The changes remain connected to [#19581]. OpenCode 1.x compatibility, shared conversation ownership, model catalog scoping, skills, command precedence, launch validation, notifications, permission mod…
Title check Passed The title clearly and concisely describes the primary change: adding OpenCode 2 chat support and pre-message model listing.
Description check Passed The description includes the required sections, linked issue, user-facing explanation, implementation details, visual proof, testing evidence, limitations, and checklist context. The AI Disclosure and…
Full details: Linked Issues check

Explanation

The PR satisfies [#19581]. It adds an opencode2 Chat UI launch path for supported stable 2.x versions. It preserves terminal behavior for unsupported versions. It adds session resume, native ACP rendering, permission handling, cancellation, history reload, model discovery, and related automated tests. The evidence also covers shared conversation ownership between opencode and opencode2.

Full details: Out of Scope Changes check

Explanation

The changes remain connected to [#19581]. OpenCode 1.x compatibility, shared conversation ownership, model catalog scoping, skills, command precedence, launch validation, notifications, permission modes, and mobile registration support the OpenCode 2 Chat UI integration. The tests and compatibility changes support this implementation. No unrelated change is demonstrated.

Full details: Description check

Explanation

The description includes the required sections, linked issue, user-facing explanation, implementation details, visual proof, testing evidence, limitations, and checklist context. The AI Disclosure and Review sections are empty, but the description is otherwise substantially complete.

  • Fix all pre-merge checks with AI
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…have

Every 2.x release rejects `opencode models --verbose`, and its own listings are empty before 2.0.27 and can omit providers that report after startup. The early listing now applies to the 1.x line only; on 2.x the chat's own list fills the picker.
…nd deletes

2.x has no listing command that matches its chats, but a new session reports exactly what a chat offers. The catalog probe now opens one, reads its models and deletes it with session/delete. A release that cannot delete a session is not asked, so no empty conversation is left; 1.x keeps its models --verbose listing.
OpenCode 2 records a project for every folder a session opens in, so the throwaway session in the home folder left a home-folder project in OpenCode's list. The session now opens in the folder of the chat that asked, as bb does; a startup or settings refresh has no chat, so it skips the listing without counting as a failure.
A picker opened during a startup refresh joined a listing that had no chat folder to open its session in, so it was answered with no models. Registrations now mark a listing that runs in the asking chat's folder, and the prewarm skips it, so the picker's own read lists in its folder. bb likewise keeps its folder-less prewarm apart from folder reads.
The listing session is deleted on a best-effort basis. A refused delete
was swallowed silently; it now logs. Tests pin both ends of the
contract: a refused delete still returns the models, and a listing
stopped while OpenCode creates its session closes without a delete.
A project's opencode.json can add or remove models, and OpenCode now
lists in the asking chat's folder, but the catalog kept one list per
account. Opening the picker in one project could then show another
project's models, through the host cache, a shared in-flight listing, a
running chat's update, and the renderer's first-frame cache.

For agents that list in a chat's folder, the folder is now part of the
catalog key, a running chat saves its list under its own folder, the
1.x listing command runs in that folder, and answers carry
modelsDifferByWorkspace so the renderer never shows one folder's list
for another. Other agents keep one catalog per account.
Register `opencode2` as a chat agent from the same definition as `opencode`, for stable 2.x from 2.0.14. Session History now checks a row against every chat agent that lists it, since both OpenCode agents share OpenCode's rows.
… name and phone icon

OpenCode 2.x reads OpenCode's skill folders, so its chat and the orchestration skill check now use them. Notifications say "OpenCode 2", and the phone app shows OpenCode's icon.
@AmoabaKelvin
AmoabaKelvin force-pushed the feat/opencode-2-chat-ui branch from f9f8a97 to 0c618f4 Compare October 18, 2026 00:25
…y name

OpenCode chats now list skills from every folder OpenCode reads (its own, the project's .opencode/skills, and Claude's skill folders but not Claude plugins), and keep that list after the chat starts, because 2.x reports no skill as a command. A picked skill OpenCode did not report as a command is sent to it as an instruction to use that skill; the chat still shows the message as typed.
…t message

The skill-instruction rewrite treated a command report not yet received as no commands, so a first message like /review reached OpenCode as an instruction to use a skill and its review never ran. It now waits for the report. The orchestration skill check reads the chat profile instead of repeating the OpenCode mapping, and two stale comments are fixed.
Rewriting an unreported opening /name into an instruction to use a skill guessed at what was a skill (`/tmp is filling up` became one) and changed the text Orca matches against OpenCode's store after a crash. bb rewrites only skills its composer marks as picked and keeps its own copy of what was sent; Orca's message carries neither, so a picked skill now goes as typed, as on main, the way Codex's /goal already reaches its model.
…p does

Notification titles read a local copy of the agent name table that had
fallen behind the shared one, so about 20 agents showed their raw id
(for example "copilot" instead of "GitHub Copilot").
…hat per conversation

Resume in chat sent every OpenCode 2.x conversation to the OpenCode chat,
which runs `opencode`. Where `opencode` is still 1.x, that chat showed the
old messages, said it could not reopen the session, and started a new
one. The OpenCode 2 chat now takes the conversation over when the host
admits it, and the OpenCode chat otherwise, since Homebrew's 2.x
installs only `opencode`.

Both single-writer checks compared the chat agent's name, so an
`opencode` chat and an `opencode2` chat could both hold one OpenCode
conversation. They now treat the two as one conversation store, read
from the same table that lists which chats take over a Session History
row. The stored handle format is unchanged.
…s name

OpenCode chats marked every reported command as possibly a skill, so a
scanned skill named `compact`, `review` or `init` replaced OpenCode's
command in the menu. Picking it still ran the command, and `/compact`
still ran Orca's own compaction. Claude's commands before its first
report had the same hole for `/clear` and `/compact`.

The picker now decides with the scanned skills in hand: a command the
chat's own send handles is never replaced, and an OpenCode command
counts as a skill only when OpenCode 1.x reported it under that
skill's own description, which is how it lists skills. Any other
collision shows the command with its "also a skill name" note.
Recordings of a real OpenCode 2.0.29 chat in Orca: an approved shell
command, a rejected permission, Stop during a running command, and a
reopen after restart whose session/load replays the history before a
follow-up. The OpenCode recordings so far covered only 1.18.31.

They pin a difference from 1.x: 2.x answers a rejected permission by
ending the turn as cancelled, which Orca shows as Interrupted.
…wered yet

Resume in chat moved from the OpenCode 2 chat to the OpenCode chat on any
answer but admitted, including a local host that took over three seconds
to answer. Where `opencode` is 1.x, that reopened a 2.x conversation in
the wrong program. It now moves only when the host refuses OpenCode 2.
Resume in New CLI copies the conversation into this host's OpenCode store,
so the CLI must not point at a server. Both checks knew only `opencode`:
the host skipped its check for OpenCode 2 chats, and the app read
`opencode`'s settings even when the copy reopens in `opencode2`. The
host now checks either chat's own arguments, and the app checks every CLI
whose chat could own the conversation before it asks for a copy.
Only the new OpenCode case belongs in this file's diff; running the
formatter had rewritten the existing tests too.
…nu code

- OpenCode's 2.x model list is a flag on the 1.x listing, not a nested
  listing whose fields nothing read; a folder-less listing is a stopped
  probe, not its own error class.
- One helper keys a record's catalog with its folder, used by reads and
  running chats alike; an OpenCode 2 read with no folder now stops before
  spawning `opencode2 --version`.
- Session History row owners are built once, and a row no chat owns
  skips the ownership scan.
- The resume runs its preparation and host check together, and its
  choice reads in one pass.
- The / menu helper does no work for agents whose report lists no
  skills, and the flag has one name and one description.
- The copy check lives on the OpenCode launch spec instead of naming the
  agents in the shared copy code.
A running OpenCode chat's report first looks up its folder, and that
lookup can finish out of order, so an older report could overwrite a
newer one. Reports from one chat are now saved in the order they were
sent, a failed save is logged instead of escaping, and a read waits for
any report still finding its folder.

The start-of-chat failure reset and the launch-model check now use the
chat's folder catalog too, and an `opencode` read with no folder stops
before spawning `opencode --version`, as `opencode2` already did. One
rule, acpListingNeedsChatFolder, decides both that and which agents
keep a catalog per folder.
@AmoabaKelvin
AmoabaKelvin force-pushed the feat/opencode-2-chat-ui branch from 0c618f4 to 5acb93d Compare October 18, 2026 00:36

@brennanb2025 brennanb2025 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a quick review pass on the current head (5acb93d) and didn't find any runtime correctness issues. This was a light pass over the production code; test files weren't reviewed in depth. Looks good to me, thanks!

@AmoabaKelvin
AmoabaKelvin merged commit e43f3e7 into stablyai:main Oct 18, 2026
55 of 57 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support OpenCode v2 in Chat UI

2 participants