Repository navigation
Conversation
OpenCode 2 decodes the module default export against `{ id, setup }` (or
`{ id, effect }`) and ignores `server()`, so the generated status plugin
never loaded and OpenCode pane status stayed dark. V2 also emits
`session.*` and `form.*` events with a `data` envelope instead of the V1
events the engine consumes.
- Default export now carries both `server` (1.x) and `setup` (2.x).
- New V2 compat source maps the V2 stream onto the V1 events: execution
boundaries to busy/idle, text events to previews, inbox enqueue to the
user prompt, permission/form events to attention, plus status/idle
passthroughs.
- The V2 context is exposed as the SDK client shape the lineage walk
expects so child sessions still roll up to the root pane.
Verified live against OpenCode 2.0.1: a full turn posts SessionStart,
MessagePart(user), SessionBusy, MessagePart(assistant), SessionIdle, and
an auto-rejected permission ask posts PermissionRequest.
Fixes stablyai#20407
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThe generated OpenCode plugin now exports both Priority: ⬆️ High Severity of issue fixed: High Merge Risk: ⚪ Minimal · up to The compatibility change has no identified current-head merge-blocking risk. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
There was a problem hiding this comment.
Important
The V2 adapter is written against a plugin/event surface that does not match the published @opencode-ai/plugin V2 API, and its form.* resolution can leave a question stuck in waiting. Please confirm the first against the exact OpenCode 2.0.1 surface you tested before merging — details inline.
Reviewed changes
- Adds
setup()to the plugin default export (status-plugin-factory-source.ts) so OpenCode 2.x's{ id, setup }/{ id, effect }loader accepts the module while keepingserver()for 1.x. - New V2→V1 event adapter (
status-plugin-v2-compat-source.ts): mapssession.status/session.idle/session.execution.*/session.text.*/session.inbox.enqueued/permission.*/form.*onto the V1propertiesshapes the engine consumes, and exposes asession.get/session.listfacade for the lineage walk. - Wires the adapter into the generated plugin (
hook-service.ts) and bumps the source hash pin inhook-service.test.ts. - New tests (
hook-plugin-opencode-v2.test.ts, 9 cases) plus asetup()assertion inhook-plugin-module-contract.test.ts.
ℹ️ Nitpicks
- The form→question mapper emits Orca's output in OpenCode's own
Questionshape (multiple/custom), but Orca'sparseQuestionsShapereadsmultiSelect(src/shared/native-chat-ask.ts:42), so a V2 multi-select form still renders and validates as single-select. If card parity is intended, map tomultiSelect(and dropcustom, which nothing reads). session.createdis reconstructed from a flatdata.parentID, while the engine keys child detection oninfo.parentID; only a rootsession.createdis covered. A child event that nests or omits the parent would post a spuriousSessionStartand poisonrootSessionById(status-plugin-session-lineage-source.ts:10), defeating child suppression. A child-session test would pin this.- The two new tests disagree on
setup's return type (Promise<unknown>in the module-contract test vs a disposer here); if the loader ignores the return, the returned disposer is dead code and the subscription is never aborted.
openrouter/deepseek/deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
- Map multiselect form fields to `multiSelect`, the key Orca's question parser reads, instead of the SDK's `multiple`. - Mirror the permission requestID hedge on form.replied/form.cancelled so a blocker still clears when the id arrives as requestID. - Warn once instead of silently no-oping when the V2 event API is absent, and guard the session facade when the domain is unavailable. - Split the compat source into event-mapping and setup fragments to stay under the 300-line max; generated bytes are unchanged. - Add child session.created, form.replied and form.cancelled tests.
|
Pushed
Final state: 126 tests pass across |
|
@coderabbitai review |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Mapped V2 form questions to
multiSelectinstatus-plugin-v2-event-source.ts, dropping the unreadmultiple/customkeys so multi-select forms render and validate correctly (parseQuestionsShapereadsmultiSelect). - Hedged form resolution IDs —
form.replied/form.cancellednow usedata.requestID || data.id, mirroringpermission.replied, so a blocker whose id arrives asrequestIDstill clears. - Split the V2 compat source into
status-plugin-v2-event-source.ts(V2→V1 event mapping) andstatus-plugin-v2-setup-source.ts(client facade + subscription pump), wiring both intogetOpenCodeFamilyPluginSource. - Hardened
setup(ctx)with aconsole.warnwhenctx.eventis absent and a null-safecreateOrcaV2Clientsession facade, so a shape mismatch is visible rather than a silent no-op. - Added tests for child
session.createdsuppression,form.replied, and therequestIDresolution hedge; the V2 suite now has 12 cases.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
✅ Action performedReview finished.
|
|
Thanks for the PR! OpenCode 2 support is on main through #21418 (ee354a3) and its follow-ups. The generated plugin's default export carries setup (setupOpenCode2Status) next to server(), and the factory source normalizes the V2 session.next.* / permission.v2 / question.v2 events. #20407 is closed. Closing — please reopen if there's a remaining case main doesn't cover. |

Summary
Fixes #20407.
OpenCode 2's external plugin loader decodes the module default export against
{ id, setup }(or{ id, effect }) and ignoresserver(), so the generatedorca-opencode-status.jsfailed to load with:Satisfying the loader alone is not enough: OpenCode 2 emits
session.*/form.*events with adataenvelope, while the engine consumes the V1 events (message.part.updated,session.status,permission.asked,question.asked, …) with apropertiesenvelope. So this change also maps the V2 stream onto the existing engine.What the change does:
server()for OpenCode 1.x.setup(ctx)to the default export for OpenCode 2.x.status-plugin-v2-compat-source.ts, which maps the V2 stream onto V1 events:session.execution.started→ busy;.succeeded/.interrupted/.failed→ idle (live V2 runs do not emitsession.statusfor ordinary turn boundaries; both forms are still passed through when present)session.status/session.idlepassthroughsession.text.started/.delta/.ended→ role cache, busy recovery, and assistant previewssession.inbox.enqueued→ user prompt previewpermission.asked→PermissionRequestwith the V1 field names the listener reads (permission,patterns,metadata,always,tool)permission.replied/form.created/form.replied/form.cancelled→ attention resolution andAskUserQuestionLive evidence (OpenCode 2.0.1, Orca hooks enabled)
Full successful turn captured at the hook endpoint:
An auto-rejected tool ask produced one
PermissionRequestwithpermission=shell,patterns=["echo hello-from-probe"], and the mappedmetadata/toolfields. The plugin loads with nofailed to load pluginentry.Testing
pnpm exec vitest run --config config/vitest.config.ts src/main/opencode/— 9 files, 123 tests pass (8 new V2 behavior tests)pnpm run typecheck:nodepnpm exec oxlint <changed files>— 0 warnings/errorspnpm build— not run (heavy desktop build)Notes
setupno-ops whenctx.event.subscribeis absent and it does not translate the V2 event names/payloads. It also does not replace fix(opencode): export plugin factory instead of object default (#16245) #16270, which targets the OpenCode 1.1.x function-callable loader shape.@opencode/client@2.0.2types and a live OpenCode 2.0.1 server.hook-service.test.tsis updated accordingly.