Skip to content

feat: first-class OpenCode integration via a local plugin#156

Merged
arzafran merged 1 commit into
mainfrom
feat/139-opencode
Jul 20, 2026
Merged

feat: first-class OpenCode integration via a local plugin#156
arzafran merged 1 commit into
mainfrom
feat/139-opencode

Conversation

@arzafran

Copy link
Copy Markdown
Member

What this does

Completes #139: OpenCode gets the same first-class treatment as Claude Code and Codex. OpenCode sessions now register with Programa, show Running and "Needs input" status in the sidebar, send completion and attention notifications, and clean up properly when the session ends. Installed the same way as the others: "Install OpenCode Integration…" in the File menu (or programa opencode install-integration), which shows exactly what will be written and asks first.

Closes #139.

Summary

  • OpenCode has no shell-hook config — its extension surface is a JS plugin API. The integration is a small local plugin (embedded in the CLI, ~30 lines) written to ~/.config/opencode/plugins/programa.js (respects OPENCODE_CONFIG_DIR). Local plugin files auto-load: no opencode.json edit, no npm install — simpler than the existing omo machinery. Design validated against opencode.ai docs and sst/opencode source; event mapping: plugin boot → session-start, chat.message → prompt-submit, session.idle → stop, permission.asked → needs-input, dispose → session-end.
  • Every plugin shell-out is .quiet().nothrow() — a programa CLI failure can never crash the user's OpenCode session.
  • programa opencode-hook mirrors the codex handler: opencode/opencode.<sessionId> PID keys, statuses, notifications, guaranteed session-end cleanup. It deliberately mirrors codex-hook's quiet-on-missing-socket guard; verification showed claude-hook lacks that guard and exits 1, so codex's is the pattern that matches the plugin's fire-and-forget contract.
  • Uninstall has one extra safety rule: it refuses to delete the plugin file if the managed-by marker comment is missing — a file the user replaced with their own content is never destroyed.
  • Menubar item + en/ja localization, mirroring the Claude/Codex entries.
  • Known quirk documented in the handler: OpenCode's session.idle and permission.asked can fire close together; status application is idempotent like the other agents' handlers, so no dedupe machinery was added.

Test Plan

  • tests_v2/test_opencode_install_integration.py 6/6 against the built CLI, no app required (fresh install, idempotent reinstall, modified-file diff + restore, uninstall delete, marker-refusal with file intact, decline-untouched)
  • Debug app build succeeds
  • Live end-to-end with a real OpenCode session (needs opencode installed; the hook handler's socket calls are the same exercised paths as codex-hook)
  • CI green

OpenCode has no shell-hook config; its extension surface is a JS plugin.
A small local plugin (embedded in the CLI, installed to
OPENCODE_CONFIG_DIR/plugins/programa.js, else
~/.config/opencode/plugins/) shells out to programa opencode-hook at
five lifecycle points: plugin boot (session-start), chat.message
(prompt-submit), session.idle (stop), permission.asked (notification),
dispose (session-end). Every call is quiet().nothrow() so a CLI hiccup
can never take down the user's OpenCode session. No opencode.json edit,
no npm install.

- opencode-hook handler mirrors codex-hook: PID keys
  opencode/opencode.<sessionId>, Running/Needs-input statuses,
  completion + attention notifications, guaranteed session-end cleanup.
  Mirrors codex-hook's quiet-on-missing-socket guard (claude-hook,
  verified, has no such guard and exits 1 there).
- programa opencode install-integration / uninstall-integration with
  the house diff+confirm pattern. Uninstall refuses to delete the file
  if the managed-by marker is missing (a user-customized file is never
  destroyed).
- Install OpenCode Integration... menubar item, en+ja strings.
- tests_v2/test_opencode_install_integration.py: 6 tests including the
  marker-refusal path and modified-file restore.

Verified: build green; all 6 CLI tests pass against the built binary.
@arzafran
arzafran merged commit 2a30cc1 into main Jul 20, 2026
4 of 7 checks passed
@arzafran
arzafran deleted the feat/139-opencode branch July 20, 2026 20:44
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.

Codex and OpenCode integrations

1 participant