Skip to content

feat: add OpenCode V2 plugin support (dual V1/V2 entry) - #20

Open
vheins wants to merge 5 commits into
MasuRii:masterfrom
vheins:feat/opencode-v2-support
Open

vheins wants to merge 5 commits into
MasuRii:masterfrom
vheins:feat/opencode-v2-support

Conversation

@vheins

@vheins vheins commented Sep 26, 2026 •

Copy link
Copy Markdown

Summary

Adds OpenCode V2 support to opencode-smart-voice-notify while keeping full V1 backward compatibility.

OpenCode v2.0.x loads plugins through the new @opencode/plugin API and requires the module's default export to be an object with id plus setup (or effect). The current 1.4.0 default export is a V1 async function, so V2 fails to load the plugin with:

PluginModule.LoadError: Plugin must export a default definition with an id and an effect or setup function.
(cause: SchemaError(Expected object at ["default"]))

This PR makes the plugin load and work on both V1 (1.18.29+) and V2 (2.0.x).

What changed

  • Dual entry point (src/index.ts): default export is now { id, setup, server } — V2 reads id/setup, V1 reads server. The original V1 implementation is preserved verbatim as a named export in src/plugin.ts.
  • V2 adapter (src/v2.ts):
    • Builds a V1-compatible $ shell runner on top of node:child_process (V2 has no shell runner) with .quiet()/.nothrow()/.timeout().
    • Provides a client shim (session.get → ctx.session.get; toast is a no-op since V2 exposes no plugin toast API).
    • Translates V2 events ({ type, data }) into the V1 { type, properties } shape so the battle-tested notification engine is reused unchanged.
    • Subscribes via ctx.event.subscribe() and disposes cleanly.
  • Event mapping for V2, including the important one: V2 defines session.idle in its schema but never emits it — the real completion signal is session.execution.succeeded. Mapping:
    • session.execution.succeeded → session.idle (agent finished)
    • session.execution.failed → session.error
    • permission.asked → permission.v2.asked
    • form.created / form.replied / form.cancelled → question.v2.asked / question.v2.replied / question.v2.rejected
    • session.inbox.enqueued (user item) → message.updated (user-activity signal)
  • Cross-instance correctness: V2 loads the plugin once per location but every instance shares one process-wide event stream. Two guards prevent duplicate/echoed audio:
    • Process-wide event-id de-duplication.
    • Location filtering, with a session→location registry so location-less events (e.g. session.execution.succeeded) are routed to the correct instance.
  • Install/packaging fixes for V2 git installs:
    • Root server.js re-export bridge + ./server export (V2 resolves an absolute plugin directory via its server entrypoint).
    • Rename build/build:types scripts → compile/compile:types so pacote's git-dependency preparation no longer triggers a failing npm install --force (the prepare-triggering build script was the cause).
    • Move msedge-tts to optionalDependencies (its preinstall: only-allow pnpm breaks npm-based git installs; it is only used as a lazy fallback for the edge engine).
    • dist/ stays git-ignored and is produced by the existing compile script as before.
  • Tests: new tests/unit/v2-adapter.test.ts covering entry shape, all event translations, the shell shim, the client shim, setup() wiring, de-duplication, and session→location routing. Existing tests updated to import the V1 function as a named export.

Compatibility

  • V1: unchanged behavior via the server field.
  • V2: full support. One caveat — V2 exposes no plugin-facing toast API, so the in-terminal toast is a no-op on V2; desktop notifications, sound, TTS, webhooks, and AI messages all work.

Testing

  • bun run typecheck — clean
  • bun run compile — clean
  • bun test — 765 pass / 17 skip / 1 fail. The single failure is pre-existing and unrelated (tests/unit/vscode-focus.test.ts → spyOn(target, prop) does not support accessor properties yet); it fails identically on the unmodified baseline.

Verified end-to-end on OpenCode v2.0.18: the plugin loads, completion notifications fire once (no echo), and permission/question/error paths work.

@vheins
vheins force-pushed the feat/opencode-v2-support branch from 45250d1 to 4b5390e Compare September 26, 2026 11:43
@vheins
vheins force-pushed the feat/opencode-v2-support branch from 4b5390e to a237941 Compare September 26, 2026 11:45
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.

1 participant