# Phase 19 — OpenCode Integration Plan ## Goal Add full OpenPets support for OpenCode with parity to the current Claude Code integration, without over-splitting implementation work. Target parity means: - OpenPets MCP tools available in OpenCode. - OpenPets instructions installed into OpenCode instructions. - OpenCode activity drives pet reactions and short safe speech. - CLI project setup can route a selected pet to a project. - Desktop Integrations can detect/configure/remove/doctor OpenCode setup. ## Current Claude integration to mirror Claude support has four layers: 1. **MCP tools** - Public tools stay exactly `openpets_status`, `openpets_say`, `openpets_react`. - MCP server: `packages/mcp/src/server.ts`. - CLI wrapper: `packages/cli/src/index.ts` → `openpets mcp --pet `. - Claude command/config helpers: `packages/claude/src/claude-code.ts`. - Desktop setup: `apps/desktop/src/agent-setup.ts`. 2. **Instructions/memory** - Claude writes `~/.claude/openpets.md` and imports it from `~/.claude/CLAUDE.md`. - Implementation: `apps/desktop/src/claude-memory.ts`. - Safety pattern: managed markers, preserve user content, private file modes, no symlink writes, max file size. 3. **Hooks/events** - Claude hooks write managed command entries to `~/.claude/settings.json` or project-local `.claude/settings.local.json`. - Implementation: `packages/claude/src/hook-settings.ts`, `packages/claude/src/hooks.ts`, `packages/claude/src/hook-messages.ts`. - Current mapping: - `UserPromptSubmit` → `thinking` + throttled speech. - `PreToolUse Edit/Write/MultiEdit` → `editing`. - `PreToolUse Bash` test command → `testing`, otherwise `running`. - other `PreToolUse` → `working`. - `PermissionRequest` → `waiting` + forced short speech. - `Notification` → `waving`. - `Stop` → `success` + throttled speech. - `StopFailure` → `error` + throttled speech. 4. **CLI project setup** - `openpets configure --agent claude --pet --cwd ` configures project-local Claude MCP and hooks. - Explicit `--pet` works without desktop app; omitted `--pet` uses local IPC `pets.list`. ## OpenCode source findings OpenCode has enough native extension surface for parity, but OpenCode support should use config + plugin, not Claude-style hooks. - **MCP config** - OpenCode config has `mcp` at `v1/opencode/packages/opencode/src/config/config.ts` lines 220-229. - Local MCP shape is in `v1/opencode/packages/opencode/src/config/mcp.ts` lines 5-19: - `{ type: "local", command: string[], environment?, enabled?, timeout? }`. - `opencode mcp add` is interactive, so automated OpenPets setup should directly edit JSON/JSONC config with the same shape. - **Config locations** - Global config uses OpenCode global config dir, with `OPENCODE_CONFIG_DIR` override support. - Project configs include `opencode.json`, `opencode.jsonc`, `.opencode/opencode.json`, `.opencode/opencode.jsonc`. - OpenCode's own MCP add path resolution checks `opencode.json` before `opencode.jsonc`, then `.opencode/*`: `v1/opencode/packages/opencode/src/cli/cmd/mcp.ts` lines 399-415. - OpenCode writes config with `jsonc-parser`: `v1/opencode/packages/opencode/src/cli/cmd/mcp.ts` lines 417-431. - **Instructions** - OpenCode config supports `instructions: string[]`: `v1/opencode/packages/opencode/src/config/config.ts` lines 238-240. - OpenCode config merge concatenates instruction arrays without duplicates: lines 54-59. - **Plugins/events** - OpenCode plugin hook surface: `v1/opencode/packages/plugin/src/index.ts` lines 222-333. - Useful hooks include `event`, `chat.message`, `tool.execute.before`, `tool.execute.after`, `command.execute.before`. - Do **not** rely on `permission.ask`; permissions should be handled through plugin `event` for bus event `permission.asked`. - OpenCode awaits plugin hooks directly, so plugin hooks must return immediately and run OpenPets calls fire-and-forget. ## Non-goals - Do not fork or modify `v1/opencode/`. - Do not add new public MCP tools. - Do not expose pet install/remove/default controls through MCP or OpenCode plugin tools. - Do not add a network listener or external SSE watcher unless plugin-based events prove insufficient. - Do not send prompts, raw commands, code, logs, tool output, URLs, secrets, or file paths to pet speech. - Do not write project-local absolute local/bundled paths by default. ## Recommended subphase sequence This should be **4 subphases**, not 6. That keeps risk separated without making the project feel fragmented. ### Phase 19A — OpenCode Foundation **Goal:** Add OpenCode config helpers, previews, safe write primitives, and shared speech/event safety. No plugin runtime or UI yet. **Scope:** - Add `packages/opencode` or equivalent module. - Build helpers for: - best-effort OpenCode detection; - global/project config path discovery; - JSON/JSONC read/write; - MCP/instructions/plugin previews; - installed/missing/stale/error status classification. - Extract shared speech categories, message picking, speech validation, and throttling from Claude into a neutral reusable module. - Keep Claude behavior unchanged. - Define exact config target behavior: - follow OpenCode's own existing file order: `opencode.json`, `opencode.jsonc`, `.opencode/opencode.json`, `.opencode/opencode.jsonc`; - if no project config exists, create `.opencode/opencode.jsonc`; - published mode is default for project config; - local/bundled absolute paths are explicit dev/global-only, never default project config. - Add safety primitives: - max config size; - reject symlinked file and parent dirs; - no writes on invalid JSON/JSONC; - backup before update/replace/remove; - temp-file + rename atomic writes. **Acceptance criteria:** - OpenCode MCP preview shape is: ```json { "mcp": { "openpets": { "type": "local", "command": ["npx", "-y", "@open-pets/cli@0.0.0", "mcp", "--pet", "fixer"], "enabled": true } } } ``` - Tests cover JSON/JSONC parsing, candidate ordering, idempotency, stale entries, invalid config, backups, max size, symlink rejection, and speech safety. - Claude checks still pass unchanged. **Checks:** - `pnpm --filter @open-pets/opencode check` - `pnpm --filter @open-pets/claude check` ### Phase 19B — OpenCode Plugin Runtime **Goal:** Add the OpenCode plugin that reacts to OpenCode activity. No desktop UI yet. **Scope:** - Implement a valid OpenCode server plugin package/entry. - Package contract must satisfy OpenCode loader expectations: - npm package export/main compatible with OpenCode; - default export exposes the OpenCode plugin shape; - file/local plugin has a stable `id`; - local/bundled dynamic import smoke test exists. - Plugin hooks must return immediately. - OpenPets calls run fire-and-forget with internal `.catch()` and debug-only logging. - Use short client timeouts, but do not await them in OpenCode hooks. - Lease strategy: - acquire a lease when configured with `pet`; - pass `leaseId` to `say`/`react`; - prefer cached lease with safe fallback, or per-event acquisition if simpler and non-blocking. - Event mapping: - `chat.message` → `thinking` + throttled speech. - `tool.execute.before` edit/write/patch tool names → `editing`. - `tool.execute.before` shell/bash tool with test-like category → `testing`. - `tool.execute.before` shell/bash tool otherwise → `running`. - other tools → `working`. - plugin `event` with `permission.asked` → `waiting` + approval-needed speech. - stable session completion/error events, if verified during implementation → `success` / `error`. - avoid raw prompt/command/output text in speech. **Acceptance criteria:** - Plugin can be imported from built output. - Plugin hook functions return without awaiting OpenPets IPC. - Unit tests cover event classification and fire-and-forget failure swallowing. - Manual OpenCode config can load the plugin and trigger reactions. **Checks:** - `pnpm --filter @open-pets/opencode check` - Manual plugin load smoke test. ### Phase 19C — CLI Project Setup for OpenCode **Goal:** Extend `openpets configure` to support project-local OpenCode setup. **Scope:** - Add `--agent opencode` alongside existing `--agent claude`. - Keep Claude CLI behavior unchanged. - With explicit `--pet`, configuration can run without desktop app. - Without `--pet`, use local IPC pet picker as Claude does. - Write project config entries for: - `mcp.openpets`; - OpenPets instructions file, e.g. `.opencode/openpets.md`; - OpenPets plugin spec/options. - Print exact files changed and restart guidance. - Warn that `.opencode/opencode.jsonc` can be committed and may contain the selected pet id. - Do not require `opencode` binary on PATH to write project config; warn if not found. **Acceptance criteria:** - `openpets configure --agent opencode --pet fixer --local-dev --cwd ` writes expected project config offline. - Re-running is idempotent. - `--force` replaces only OpenPets-managed entries. - Unknown agents still fail clearly. - Claude project setup tests still pass. **Checks:** - `pnpm --filter @open-pets/cli check` - `pnpm --filter @open-pets/opencode check` - `pnpm --filter @open-pets/claude check` ### Phase 19D — Desktop Integration, Packaging, Docs, Hardening **Goal:** Add OpenCode to Desktop Integrations and close out docs/packaging/cross-platform hardening. **Scope:** - Add OpenCode card next to Claude Code. - Desktop OpenCode setup target is explicitly **global OpenCode config**, not project config. - If project setup is desired from desktop, defer to a later phase with a project directory selector. - Global setup can: - detect OpenCode best-effort; - install/replace/remove global MCP entry; - install/update global OpenPets instructions; - install/update/remove global OpenPets plugin; - show previews and copy manual snippets; - show clear status and backups. - Preserve Claude UI/actions. - Package bundled OpenCode plugin/CLI resources safely: - no true `app.asar` paths; - no symlinked bundled entry; - dynamic-import smoke test for bundled plugin path. - Update README and mapping docs. - Manual verification covers: - invalid config; - custom `OPENCODE_CONFIG_DIR`; - OpenCode absent; - removal safety; - packaged bundled plugin; - project/global precedence; - macOS/Windows/Linux path differences where available. **Acceptance criteria:** - Desktop UI clearly says OpenCode desktop setup is global. - No global config write occurs without explicit user action. - Remove only removes OpenPets-managed entries. - OpenCode absent on PATH does not prevent showing config status/previews. - Packaged app can locate bundled OpenCode plugin and CLI resources. - Docs explain exact files touched and CLI project setup vs desktop global setup. - `pnpm check` passes. **Checks:** - `pnpm --filter @open-pets/desktop check` - `pnpm --filter @open-pets/opencode check` - `pnpm check` ## Security/privacy requirements for every subphase - Never include prompt text, command text, file paths, tool output, code, logs, URLs, or secrets in pet speech. - Classify tools using tool names and coarse categories only. - Validate pet ids with the same strict regex as Claude. - Reject symlinked config/instruction/plugin write targets. - Keep writes inside the selected project or OpenCode config dir. - Back up before update/replace/remove once writes are implemented. - No new TCP/HTTP surface. - Plugin failures must never break or delay OpenCode; hooks must return immediately. ## Oracle plan review Oracle reviewed the first all-in-one Phase 19 plan and found the architecture viable but too broad and not implementation-ready. ### Blockers found - Desktop target scope was ambiguous: global vs project setup. - `permission.ask` assumption was wrong; use `event` hook for `permission.asked`. - Plugin hooks are awaited by OpenCode; OpenPets work must be fire-and-forget, not merely timeout-bounded. - Plugin package/path contract was underspecified. - Config precedence and write targets needed tightening. - Data-loss protections needed hard requirements. - `.opencode` is not private; project config can be committed. - Manual verification was too happy-path-only. ## Oracle feedback disposition - **Fixed:** Split one large phase into 4 subphases, not 6. - **Fixed:** Desktop setup is global-only unless a later project picker is added. - **Fixed:** Permission mapping uses `event`/`permission.asked`, not `permission.ask`. - **Fixed:** Plugin hooks must return immediately and run OpenPets calls fire-and-forget. - **Fixed:** Added plugin package/path contract and bundled import smoke requirement. - **Fixed:** Aligned existing config candidate order with OpenCode's own `mcp add` order. - **Fixed:** Added invalid JSONC, max size, symlink, backup, and atomic write requirements. - **Fixed:** Removed “private-ish” wording for `.opencode` and added commit warning. - **Fixed:** Combined desktop, packaging, docs, and hardening into one closeout phase to avoid oversplitting. ## Implementation closeout Phase 19A–19D are implemented in the v2 workspace: - `packages/opencode` provides OpenCode config helpers, strict managed-entry classification, safe global/project writes, and the OpenCode plugin runtime. - `packages/cli` supports `openpets configure --agent opencode` for project-local setup. - Desktop Integrations supports global OpenCode setup/removal/preview/copy, with packaged CLI resource checks and published plugin configuration. - README and mapping docs explain OpenCode project-local vs desktop-global setup and safe speech constraints. Validation completed with `pnpm check` after @oracle implementation review.