Repository navigation
feat(opencode): open OpenCode 2 in the chat view and list 2.x models before the first message - #28424
Conversation
2417b43 to
31e14cc
Compare
b242d86 to
e12da85
Compare
4a10da5 to
f9f8a97
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to
|
…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.
f9f8a97 to
0c618f4
Compare
…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.
0c618f4 to
5acb93d
Compare
brennanb2025
left a comment
There was a problem hiding this comment.
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!
ELI5
Orca has two launcher entries for OpenCode: "OpenCode" (the
opencodecommand) and "OpenCode 2" (theopencode2command). 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.
opencode2 --standalone, even with Chat UI on.opencodeon 2.x, the picker had no models until the first message: Orca ranopencode models --verbose, which every 2.x release rejects.opencodeis still 1.x, that chat showed the old messages, said it could not reopen the session, and started a new one.After.
opencode2is a stable 2.x from 2.0.14. Anything else (older 2.x, a pre-release, 1.x, noopencode2) keeps the terminal.opencode.jsonadds or removes models shows its own list./menu lists the skills OpenCode reads, and never lets a skill hide a command that OpenCode or Orca runs instead.Mechanism.
opencode2row runsopencode2 acp, accepts 2.x only, and shares OpenCode's account, history rows and conversation import.session/newin the asking chat's folder, reads its models with the code a running chat uses, and deletes it withsession/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 keepsopencode models --verbose, now run in the chat's folder. The startup and settings refreshes skip OpenCode, since they have no folder.modelsDifferByWorkspaceso the app's first-frame cache never shows one folder's list in another. Other agents keep one list per account.opencodeandopencode2open 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.opencode2rows: 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.~/.claude/skills,~/.agents/skills, and the project's.opencode/skills,.claude/skillsand.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".copilot).Why
opencode. Whereopencodeis 1.x, that is the wrong program for a 2.x conversation.--verbose, andopencode modelsandopencode api model.listanswer 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 --refreshno 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.opencode.jsoncan add or remove providers. bb keys OpenCode's list by folder for the same reason./tmp is filling upinto a skill request.opencodewhenopencode2is 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 atopencode.Linked Issue
Fixes #19581
Visual Proof
Before (main): New tab > OpenCode 2 opens a terminal.
After: it opens a chat tab; a tool call, its output and the model picker are shown.
A real OpenCode 2.0.29 chat with "Ask for approval" on, in a hidden dev build.
Permission card for a shell command:
After "Allow once": the command ran and OpenCode replied with its output.
After "Reject": OpenCode 2 ends the turn itself (
stopReason: "cancelled"), shown as Interrupted. 1.x instead fails the tool and finishes the turn.Stop during an approved
sleep 40: the turn is Interrupted and the command's process is gone.After an app restart: the chat reopens with its history shown once.
A follow-up in the reopened chat: OpenCode loaded the same conversation and recalls the first command's output.
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 --verbosewith no session. After a restart, no OpenCode 2 refresh ran until a chat asked.One list per project: with
opencode1.19.1 andopencode22.0.29 sharing one database, project A listed 10 models and project B 11, including the model itsopencode.jsonadds; 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 namedcompactandreview, the menu shows OpenCode's own commands with "Also a skill name". A picked skill,/tmp is filling upand/review the branchreach OpenCode as typed.Resume in chat with
opencode1.19.1 andopencode22.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/loadreplays 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
opencodehanded 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-fanoutand 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
Notes
opencode2is a.cmdfile), 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.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.XDG_CONFIG_HOMEorOPENCODE_CONFIG_DIR, and OpenCode's built-in skills, are not listed. Typing one still works.@opencode2orchestration group.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)