OpenCode Plugin Architecture & oh-my-opencode Deep Dive > A comprehensive guide to understanding OpenCode's plugin system and how oh-my-opencode transforms a single-agent assistant into a multi-model orchestration powerhouse. --- Table of Contents 1. OpenCode Plugin System Overview (#1-opencode-plugin-system-overview) 2. The @opencode-ai/plugin Package (#2-the-opencode-aiplugin-package) 3. oh-my-opencode Architecture (#3-oh-my-opencode-architecture) 4. The Agent Orchestration System (#4-the-agent-orchestration-system) 5. Background Agent System (#5-background-agent-system) 6. The Hook Pipeline (#6-the-hook-pipeline) 7. Custom Tools (#7-custom-tools) 8. MCP Integrations (#8-mcp-integrations) 9. Configuration System (#9-configuration-system) 10. Plugin Loading & Updates (#10-plugin-loading--updates) 11. Creating Your Own Plugin (#11-creating-your-own-plugin) 12. Key Files Reference (#12-key-files-reference) --- 1. OpenCode Plugin System Overview What is OpenCode? OpenCode is a terminal-based AI coding assistant (similar to Claude Code) created by SST. It's highly extensible through a plugin system. Repository: https://github.com/sst/opencode How Plugins Work Plugins are JavaScript/TypeScript modules that: 1. Receive a context object with SDK client, project info, and utilities 2. Return a hooks object to extend OpenCode's behavior Plugin locations: - .opencode/plugin/ (project-level) - ~/.config/opencode/plugin/ (global) - npm packages via opencode.json The Plugin Interface // From @opencode-ai/plugin export type PluginInput = { client: ReturnType // SDK client for API calls project: Project // Current project info directory: string // Working directory path worktree: string // Git worktree path $: BunShell // Bun's shell API for commands } export type Plugin = (input: PluginInput) => Promise --- 2. The @opencode-ai/plugin Package Core Types Source: packages/plugin/src/index.ts in sst/opencode export interface Hooks { // Event subscription - react to 40+ event types event?: (input: { event: Event }) => Promise // Configuration hook - modify agents, MCPs, commands config?: (input: Config) => Promise // Custom tools registration tool?: { [key: string]: ToolDefinition } // Authentication provider auth?: AuthHook // Chat lifecycle hooks "chat.message"?: (input, output: { message: UserMessage; parts: Part[] }) => Promise "chat.params"?: (input, output: { temperature: number; topP: number; topK: number; options: Record }) => Promise // Permission hooks "permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise // Tool execution hooks "tool.execute.before"?: (input: { tool: string; sessionID: string; callID: string }, output: { args: any }) => Promise "tool.execute.after"?: (input: { tool: string; sessionID: string; callID: string }, output: { title: string; output: string; metadata: any }) => Promise // Experimental hooks "experimental.chat.messages.transform"?: (input, output: { messages: { info: Message; parts: Part[] }[] }) => Promise "experimental.chat.system.transform"?: (input, output: { system: string[] }) => Promise "experimental.text.complete"?: (input: { sessionID: string; messageID: string; partID: string }, output: { text: string }) => Promise } Tool Definition export type ToolContext = { sessionID: string messageID: string agent: string abort: AbortSignal } export function tool(input: { description: string args: Args execute(args: z.infer>, context: ToolContext): Promise }) { return input } tool.schema = z // Zod schema builder Available Events (40+) | Category | Events | |----------|--------| | Command | command.executed | | File | file.edited, file.watcher.updated | | Installation | installation.updated | | LSP | lsp.client.diagnostics, lsp.updated | | Message | message.part.removed, message.part.updated, message.removed, message.updated | | Permission | permission.replied, permission.updated | | Server | server.connected | | Session | session.created, session.compacted, session.deleted, session.diff, session.error, session.idle, session.status, session.updated | | Todo | todo.updated | | Tool | tool.execute.after, tool.execute.before | | TUI | tui.prompt.append, tui.command.execute, tui.toast.show | --- 3. oh-my-opencode Architecture What is oh-my-opencode? Repository: https://github.com/code-yeongyu/oh-my-opencode oh-my-opencode is a comprehensive OpenCode plugin that transforms the single-agent experience into a multi-model, multi-agent orchestration system. It's described as "oh-my-zsh for OpenCode." Project Structure oh-my-opencode/ ├── src/ │ ├── agents/ # AI agents (OmO, oracle, librarian, explore, etc.) │ │ ├── omo.ts │ │ ├── oracle.ts │ │ ├── librarian.ts │ │ ├── explore.ts │ │ ├── frontend-ui-ux-engineer.ts │ │ ├── document-writer.ts │ │ ├── multimodal-looker.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── hooks/ # 21 lifecycle hooks │ │ ├── anthropic-auto-compact/ │ │ ├── auto-update-checker/ │ │ ├── background-notification/ │ │ ├── claude-code-hooks/ │ │ ├── comment-checker/ │ │ ├── directory-readme-injector/ │ │ ├── interactive-bash-session/ │ │ ├── keyword-detector/ │ │ ├── non-interactive-env/ │ │ ├── rules-injector/ │ │ ├── session-recovery/ │ │ ├── think-mode/ │ │ ├── context-window-monitor.ts │ │ ├── empty-task-response-detector.ts │ │ ├── grep-output-truncator.ts │ │ ├── session-notification.ts │ │ ├── todo-continuation-enforcer.ts │ │ ├── tool-output-truncator.ts │ │ └── index.ts │ ├── tools/ # Custom tools (LSP, AST-Grep, background tasks) │ │ ├── lsp/ │ │ ├── ast-grep/ │ │ ├── background-task/ │ │ ├── call-omo-agent/ │ │ ├── look-at/ │ │ ├── grep/ │ │ ├── glob/ │ │ ├── interactive-bash/ │ │ ├── skill/ │ │ ├── slashcommand/ │ │ └── index.ts │ ├── mcp/ # MCP server configurations │ │ ├── context7.ts │ │ ├── grep-app.ts │ │ ├── websearch-exa.ts │ │ └── index.ts │ ├── features/ # Core features │ │ ├── background-agent/ │ │ ├── claude-code-*-loader/ │ │ ├── hook-message-injector/ │ │ └── terminal/ │ ├── auth/ # Google Antigravity OAuth │ ├── shared/ # Utilities │ ├── config/ # Zod schema, types │ └── index.ts # Main plugin entry ├── package.json └── tsconfig.json Main Entry Point (src/index.ts) The plugin implements ALL available hooks: const OhMyOpenCodePlugin: Plugin = async (ctx) => { // 1. Load configuration const pluginConfig = loadPluginConfig(ctx.directory); // 2. Initialize 20+ hooks with enable/disable support const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null; const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null; // ... 18 more hooks // 3. Initialize background agent manager const backgroundManager = new BackgroundManager(ctx); // 4. Create tools const backgroundTools = createBackgroundTools(backgroundManager, ctx.client); const callOmoAgent = createCallOmoAgent(ctx, backgroundManager); const lookAt = createLookAt(ctx); return { // Authentication auth: googleAuthHooks?.auth, // Custom tools tool: { ...builtinTools, // LSP, AST-grep, grep, glob ...backgroundTools, // background_task, background_output, background_cancel call_omo_agent, look_at, interactive_bash, }, // Config modification config: async (config) => { // Inject custom agents config.agent = { OmO: ..., oracle: ..., ... }; // Inject MCPs config.mcp = { context7: ..., websearch_exa: ..., grep_app: ... }; // Load Claude Code commands, skills, agents }, // Event handling event: async (input) => { // Process through all 20+ hooks }, // Tool interception "tool.execute.before": async (input, output) => { ... }, "tool.execute.after": async (input, output) => { ... }, // Chat hooks "chat.message": async (input, output) => { ... }, "experimental.chat.messages.transform": async (input, output) => { ... }, }; }; --- 4. The Agent Orchestration System The Agent Team oh-my-opencode creates a team of specialized agents, each using the optimal model for their task: | Agent | Model | Purpose | Mode | |-------|-------|---------|------| | OmO | anthropic/claude-opus-4-5 | Primary orchestrator, delegates work | primary | | oracle | openai/gpt-5.2 | Architecture, debugging, deep reasoning | subagent | | librarian | anthropic/claude-sonnet-4-5 | External docs, GitHub examples | subagent | | explore | opencode/grok-code | Fast codebase search | subagent | | frontend-ui-ux-engineer | google/gemini-3-pro-preview | UI generation | subagent | | document-writer | google/gemini-3-pro-preview | Documentation | subagent | | multimodal-looker | google/gemini-2.5-flash | PDF/image analysis | subagent | OmO Agent System Prompt (Key Excerpts) Source: src/agents/omo.ts The OmO agent has a 400+ line system prompt that teaches it to: const OMO_SYSTEM_PROMPT = ` You are OmO, the orchestrator agent for OpenCode. **Identity**: Elite software engineer working at SF, Bay Area. You work, delegate, verify, deliver. **Operating Mode**: You NEVER work alone when specialists are available. Frontend work → delegate. Deep research → parallel background agents. Complex architecture → consult Oracle. ## Phase 0 - Intent Gate (EVERY message) ### Step 1: Classify Request Type | Type | Signal | Action | |------|--------|--------| | **Trivial** | Single file, known location | Direct tools only, no agents | | **Explicit** | Specific file/line, clear command | Execute directly | | **Exploratory** | "How does X work?" | Assess scope, then search | | **Open-ended** | "Improve", "Refactor" | Assess codebase first | ## Phase 2A - Exploration & Research ### Tool Selection: | Tool | Cost | When to Use | |------|------|-------------| | grep, glob, lsp_*, ast_grep | FREE | Always try first | | explore agent | CHEAP | Multiple search angles | | librarian agent | CHEAP | External docs, GitHub examples | | oracle agent | EXPENSIVE | Architecture, review, debugging | ### Parallel Execution (DEFAULT behavior) // CORRECT: Always background, always parallel background_task(agent="explore", prompt="Find auth implementations...") background_task(agent="librarian", prompt="Find JWT best practices...") // Continue working immediately. Collect with background_output when needed. ## Phase 2B - Implementation ### GATE: Frontend Files (HARD BLOCK) | Extension | Action | |-----------|--------| | .tsx, .jsx | DELEGATE | | .vue, .svelte | DELEGATE | | .css, .scss | DELEGATE | ALL frontend = DELEGATE to frontend-ui-ux-engineer. Period. ` export const omoAgent: AgentConfig = { description: "Powerful AI orchestrator for OpenCode...", mode: "primary", model: "anthropic/claude-opus-4-5", thinking: { type: "enabled", budgetTokens: 32000, // Extended thinking enabled }, maxTokens: 64000, prompt: OMO_SYSTEM_PROMPT, color: "#00CED1", } Oracle Agent (Strategic Advisor) Source: src/agents/oracle.ts export const oracleAgent: AgentConfig = { description: "Expert technical advisor with deep reasoning...", mode: "subagent", model: "openai/gpt-5.2", temperature: 0.1, reasoningEffort: "medium", textVerbosity: "high", tools: { write: false, edit: false, task: false, background_task: false }, prompt: `You are a strategic technical advisor... ## Decision Framework **Bias toward simplicity**: The right solution is typically the least complex one. **Leverage what exists**: Favor modifications to current code over new components. **One clear path**: Present a single primary recommendation. **Signal the investment**: Tag recommendations with Quick(<1h), Short(1-4h), Medium(1-2d), Large(3d+). `, } How Agents Get Injected In the config hook: config: async (config) => { const builtinAgents = createBuiltinAgents(...); // OmO becomes primary, original agents become subagents config.agent = { OmO: builtinAgents.OmO, // NEW primary "OmO-Plan": omoPlanConfig, // NEW plan variant ...builtinAgents, // oracle, librarian, etc. ...config.agent, // User's original agents build: { ...config.agent?.build, mode: "subagent" }, // DEMOTED plan: { ...config.agent?.plan, mode: "subagent" }, // DEMOTED }; } --- 5. Background Agent System The BackgroundManager Class Source: src/features/background-agent/manager.ts This is the key innovation that enables parallel agent execution: export class BackgroundManager { private tasks: Map private notifications: Map private client: OpencodeClient private pollingInterval?: Timer async launch(input: LaunchInput): Promise { // 1. Create a NEW child session const createResult = await this.client.session.create({ body: { parentID: input.parentSessionID, title: `Background: ${input.description}` } }); const sessionID = createResult.data.id; // 2. Track the task const task: BackgroundTask = { id: `bg_${crypto.randomUUID().slice(0, 8)}`, sessionID, parentSessionID: input.parentSessionID, description: input.description, status: "running", startedAt: new Date(), }; this.tasks.set(task.id, task); // 3. Fire async prompt (NON-BLOCKING!) this.client.session.promptAsync({ path: { id: sessionID }, body: { agent: input.agent, tools: { task: false, background_task: false }, // Prevent recursion parts: [{ type: "text", text: input.prompt }] } }); // 4. Start polling for completion this.startPolling(); return task; } // Event handler for completion detection handleEvent(event: Event): void { if (event.type === "session.idle") { const task = this.findBySession(sessionID); if (task?.status === "running") { task.status = "completed"; this.markForNotification(task); this.notifyParentSession(task); // Send message back! } } } // Notify the main session when background task completes private notifyParentSession(task: BackgroundTask): void { // Show toast notification this.client.tui.showToast({ body: { title: "Background Task Completed", message: `Task "${task.description}" finished.`, variant: "success", } }); // Send a message to the parent session this.client.session.prompt({ path: { id: task.parentSessionID }, body: { parts: [{ type: "text", text: `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished. Use background_output with task_id="${task.id}" to get results.` }] } }); } } Background Task Tools Source: src/tools/background-task/tools.ts // background_task - Launch an agent in background export function createBackgroundTask(manager: BackgroundManager) { return tool({ description: "Launch a background agent task...", args: { description: tool.schema.string(), prompt: tool.schema.string(), agent: tool.schema.string(), }, async execute(args, toolContext) { const task = await manager.launch({ description: args.description, prompt: args.prompt, agent: args.agent, parentSessionID: toolContext.sessionID, parentMessageID: toolContext.messageID, }); return `Background task launched. Task ID: ${task.id}`; }, }); } // background_output - Get results from background task export function createBackgroundOutput(manager: BackgroundManager, client: OpencodeClient) { return tool({ description: "Get output from a background task...", args: { task_id: tool.schema.string(), block: tool.schema.boolean().optional(), // Wait for completion? }, async execute(args) { const task = manager.getTask(args.task_id); if (task.status === "completed") { // Fetch messages from the task's session const messages = await client.session.messages({ path: { id: task.sessionID } }); return formatTaskResult(task, messages); } return formatTaskStatus(task); }, }); } // background_cancel - Cancel running tasks export function createBackgroundCancel(manager: BackgroundManager, client: OpencodeClient) { return tool({ description: "Cancel background tasks...", args: { taskId: tool.schema.string().optional(), all: tool.schema.boolean().optional(), // Cancel all? }, async execute(args, toolContext) { if (args.all) { const tasks = manager.getTasksByParentSession(toolContext.sessionID); for (const task of tasks.filter(t => t.status === "running")) { client.session.abort({ path: { id: task.sessionID } }); task.status = "cancelled"; } } // ... }, }); } The Parallel Execution Flow You: "Add authentication to my app" │ ▼ ┌─────────────────────────────────────────┐ │ OmO (Claude Opus 4.5) │ │ "I'll orchestrate this..." │ └─────────────────────────────────────────┘ │ │ 1. Creates todo list │ 2. Launches background agents: │ ├──▶ background_task(agent="librarian", "Find JWT best practices...") │ └── Creates child session, runs async │ ├──▶ background_task(agent="explore", "Find existing auth patterns...") │ └── Creates child session, runs async │ │ 3. Starts implementing immediately (doesn't wait!) │ ▼ ┌─────────────────────────────────────────┐ │ [MEANWHILE: Background agents working] │ │ librarian → searching docs │ │ explore → scanning codebase │ └─────────────────────────────────────────┘ │ │ 4. BackgroundManager detects completion │ 5. Sends notification to OmO's session │ ▼ ┌─────────────────────────────────────────┐ │ [BACKGROUND TASK COMPLETED] │ │ Task "Find JWT best practices" done │ │ Use background_output to get results │ └─────────────────────────────────────────┘ │ ▼ OmO: background_output(task_id="bg_abc123") │ ▼ Integrates findings, continues work... --- 6. The Hook Pipeline Hook Categories oh-my-opencode implements 21 hooks across several categories: Session Management - session-recovery: Auto-recovers from API errors - session-notification: OS notifications when session goes idle - anthropic-auto-compact: Auto-summarizes when hitting token limits Task Management - todo-continuation-enforcer: Forces agent to complete all todos - empty-task-response-detector: Warns about empty task responses Context Management - context-window-monitor: Implements "Context Window Anxiety Management" - directory-readme-injector: Injects README.md context - directory-agents-injector: Injects AGENTS.md context - rules-injector: Conditional rules from .claude/rules/ Output Processing - tool-output-truncator: Truncates verbose tool output - grep-output-truncator: Specifically handles grep output Quality Control - comment-checker: Prevents AI-style excessive comments - keyword-detector: Detects ultrawork, search, analyze keywords - think-mode: Auto-enables extended thinking for complex tasks Background System - background-notification: Routes events to BackgroundManager Compatibility - claude-code-hooks: Pre/PostToolUse, UserPromptSubmit, Stop hooks - non-interactive-env: Handles non-interactive environments - interactive-bash-session: Tmux integration Event Handler Pipeline event: async (input) => { // Process through ALL hooks in sequence await autoUpdateChecker?.event(input); await claudeCodeHooks.event(input); await backgroundNotificationHook?.event(input); // Background tracking! await sessionNotification?.(input); await todoContinuationEnforcer?.handler(input); await contextWindowMonitor?.event(input); await directoryAgentsInjector?.event(input); await directoryReadmeInjector?.event(input); await rulesInjector?.event(input); await thinkMode?.event(input); await anthropicAutoCompact?.event(input); await keywordDetector?.event(input); await agentUsageReminder?.event(input); await interactiveBashSession?.event(input); // Handle session lifecycle if (event.type === "session.created") { ... } if (event.type === "session.idle") { ... } if (event.type === "session.error") { ... } } Tool Execution Hooks "tool.execute.before": async (input, output) => { // Claude Code hooks (PreToolUse) await claudeCodeHooks["tool.execute.before"](input, output); // Non-interactive environment handling await nonInteractiveEnv?.["tool.execute.before"](input, output); // Comment checking await commentChecker?.["tool.execute.before"](input, output); // Disable dangerous tools for subagents if (input.tool === "task") { output.args.tools = { ...output.args.tools, background_task: false, // Prevent nested background tasks }; } }, "tool.execute.after": async (input, output) => { // Claude Code hooks (PostToolUse) await claudeCodeHooks["tool.execute.after"](input, output); // Truncate verbose output await toolOutputTruncator?.["tool.execute.after"](input, output); // Track context usage await contextWindowMonitor?.["tool.execute.after"](input, output); // Inject directory context await directoryAgentsInjector?.["tool.execute.after"](input, output); await directoryReadmeInjector?.["tool.execute.after"](input, output); // Inject conditional rules await rulesInjector?.["tool.execute.after"](input, output); }, --- 7. Custom Tools LSP Tools (11 tools) Source: src/tools/lsp/ export const builtinTools = { // Information lsp_hover, // Type info, docs at position lsp_goto_definition, // Jump to definition lsp_find_references, // Find all usages lsp_document_symbols, // File symbol outline lsp_workspace_symbols, // Search symbols by name lsp_diagnostics, // Get errors/warnings lsp_servers, // List available LSP servers // Refactoring lsp_prepare_rename, // Validate rename lsp_rename, // Rename symbol across workspace lsp_code_actions, // Get quick fixes/refactorings lsp_code_action_resolve, // Apply code action } AST-Grep Tools Source: src/tools/ast-grep/ export const astGrepTools = { ast_grep_search, // AST-aware pattern search (25 languages) ast_grep_replace, // AST-aware code replacement } Search Tools (Improved) export const searchTools = { grep, // With timeout protection (original hangs forever) glob, // With timeout protection } Special Tools // look_at - Multimodal analysis via subagent export function createLookAt(ctx: PluginInput) { return tool({ description: "Analyze files using multimodal-looker agent", args: { file: tool.schema.string() }, async execute(args, toolContext) { // Delegates to multimodal-looker agent internally // Saves context in main session }, }); } // call_omo_agent - Specialized agent caller export function createCallOmoAgent(ctx: PluginInput, manager: BackgroundManager) { return tool({ description: "Call explore or librarian agents", args: { subagent_type: tool.schema.enum(["explore", "librarian"]), prompt: tool.schema.string(), run_in_background: tool.schema.boolean(), }, async execute(args, toolContext) { if (args.run_in_background) { return await manager.launch(...); } return await executeSynchronously(...); }, }); } // interactive_bash - Tmux integration export const interactive_bash = tool({ description: "Execute commands in interactive tmux session", // ... }); --- 8. MCP Integrations Built-in MCPs Source: src/mcp/ // context7.ts - Official documentation lookup export const context7 = { type: "remote", url: "https://mcp.context7.io/sse", enabled: true, } // websearch_exa.ts - Real-time web search export const websearch_exa = { type: "remote", url: "https://mcp.exa.ai/sse", enabled: true, } // grep_app.ts - GitHub code search export const grep_app = { type: "remote", url: "https://mcp.grep.app/sse", enabled: true, } MCP Injection config: async (config) => { config.mcp = { ...config.mcp, ...createBuiltinMcps(pluginConfig.disabled_mcps), // context7, websearch_exa, grep_app ...loadMcpConfigs(), // Claude Code .mcp.json files }; } --- 9. Configuration System Config File Locations 1. ~/.config/opencode/oh-my-opencode.json (user-level) 2. .opencode/oh-my-opencode.json (project-level, overrides user) Config Schema Source: src/config/schema.ts export const OhMyOpenCodeConfigSchema = z.object({ // Enable Google OAuth google_auth: z.boolean().optional(), // Agent overrides agents: z.record(z.object({ model: z.string().optional(), temperature: z.number().optional(), prompt: z.string().optional(), tools: z.record(z.boolean()).optional(), disable: z.boolean().optional(), description: z.string().optional(), mode: z.enum(["primary", "subagent"]).optional(), color: z.string().optional(), permission: z.object({ edit: z.enum(["ask", "allow", "deny"]).optional(), bash: z.union([ z.enum(["ask", "allow", "deny"]), z.record(z.enum(["ask", "allow", "deny"])) ]).optional(), }).optional(), })).optional(), // Disable specific agents disabled_agents: z.array(z.string()).optional(), // Disable specific MCPs disabled_mcps: z.array(z.enum(["context7", "websearch_exa", "grep_app"])).optional(), // Disable specific hooks disabled_hooks: z.array(z.string()).optional(), // Claude Code compatibility toggles claude_code: z.object({ mcp: z.boolean().optional(), commands: z.boolean().optional(), skills: z.boolean().optional(), agents: z.boolean().optional(), hooks: z.boolean().optional(), }).optional(), // OmO agent settings omo_agent: z.object({ disabled: z.boolean().optional(), }).optional(), }); Example Configuration { $schema: https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json, google_auth: true, agents: { OmO: { model: anthropic/claude-sonnet-4 }, oracle: { temperature: 0.3 }, frontend-ui-ux-engineer: { disable: true } }, disabled_hooks: [comment-checker], disabled_mcps: [grep_app], claude_code: { hooks: true, commands: true, mcp: false } } --- 10. Plugin Loading & Updates How OpenCode Loads Plugins Source: packages/opencode/src/plugin/index.ts (in sst/opencode) for (let plugin of plugins) { if (!plugin.startsWith("file://")) { // Parse version: "oh-my-opencode@2.1.6" const lastAtIndex = plugin.lastIndexOf("@") const pkg = lastAtIndex > 0 ? plugin.substring(0, lastAtIndex) : plugin const version = lastAtIndex > 0 ? plugin.substring(lastAtIndex + 1) : "latest" // Install via Bun from npm registry plugin = await BunProc.install(pkg, version) } const mod = await import(plugin) for (const [_name, fn] of Object.entries(mod)) { const init = await fn(input) hooks.push(init) } } Plugin Cache Location ~/.cache/opencode/ ├── node_modules/ │ └── oh-my-opencode/ ← THE ACTUAL PLUGIN CODE │ ├── dist/ │ │ └── index.js │ └── package.json ├── package.json └── bun.lockb Auto-Update Checker Source: src/hooks/auto-update-checker/ export function createAutoUpdateCheckerHook(ctx: PluginInput) { return { event: async ({ event }) => { if (event.type !== "session.created") return; // Check npm registry for latest version const result = await checkForUpdate(ctx.directory); if (result.needsUpdate) { // Show notification await ctx.client.tui.showToast({ body: { title: `OhMyOpenCode ${result.latestVersion}`, message: `v${result.latestVersion} available. Restart OpenCode to apply.`, } }); // Invalidate cache (delete cached package) invalidatePackage("oh-my-opencode"); // Next restart will download new version } }, }; } async function getLatestVersion(): Promise { const response = await fetch("https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags"); const data = await response.json(); return data.latest; } Version Pinning // Latest (auto-updates on restart) { plugin: [oh-my-opencode] } // Pinned (never auto-updates) { plugin: [oh-my-opencode@2.1.6] } // Local development { plugin: [file:///path/to/oh-my-opencode] } --- 11. Creating Your Own Plugin Minimal package.json { name: my-opencode-plugin, version: 1.0.0, main: dist/index.js, types: dist/index.d.ts, type: module, files: [dist], exports: { .: { types: ./dist/index.d.ts, import: ./dist/index.js } }, scripts: { build: bun build src/index.ts --outdir dist --target bun --format esm && tsc --emitDeclarationOnly, prepublishOnly: bun run build }, dependencies: { @opencode-ai/plugin: ^1.0.162 }, devDependencies: { bun-types: latest, typescript: ^5.7.3 }, peerDependencies: { bun: >=1.0.0 } } Minimal tsconfig.json { compilerOptions: { target: ESNext, module: ESNext, moduleResolution: bundler, declaration: true, declarationDir: dist, outDir: dist, rootDir: src, strict: true, esModuleInterop: true, skipLibCheck: true, lib: [ESNext], types: [bun-types] }, include: [src/**/*], exclude: [node_modules, dist] } Minimal Plugin (src/index.ts) import type { Plugin } from "@opencode-ai/plugin" import { tool } from "@opencode-ai/plugin" const MyPlugin: Plugin = async ({ client, directory }) => { return { // Add custom tools tool: { my_tool: tool({ description: "Does something useful", args: { input: tool.schema.string(), }, async execute(args) { return `Processed: ${args.input}`; }, }), }, // Modify config config: async (config) => { config.agent = { ...config.agent, "my-agent": { description: "My custom agent", model: "anthropic/claude-sonnet-4", prompt: "You are helpful...", }, }; }, // React to events event: async ({ event }) => { if (event.type === "session.created") { await client.tui.showToast({ body: { title: "Plugin Loaded", message: "My plugin is active!", variant: "success", } }).catch(() => {}); } }, // Intercept tool calls "tool.execute.before": async (input, output) => { console.log(`Tool ${input.tool} called`); }, }; }; export default MyPlugin; Publish to npm npm login npm publish Users Install { plugin: [my-opencode-plugin] } --- 12. Key Files Reference Core oh-my-opencode Files | File | Purpose | |------|---------| | src/index.ts | Main plugin entry, orchestrates everything | | src/agents/*.ts | Agent definitions (OmO, oracle, etc.) | | src/features/background-agent/manager.ts | Background task management | | src/tools/background-task/tools.ts | background_task, background_output, background_cancel | | src/tools/lsp/*.ts | LSP tool implementations | | src/hooks/*.ts | All 21 hooks | | src/mcp/*.ts | MCP server configurations | | src/config/schema.ts | Zod config schema | OpenCode Core Files (sst/opencode) | File | Purpose | |------|---------| | packages/plugin/src/index.ts | Plugin interface types | | packages/plugin/src/tool.ts | Tool definition helper | | packages/opencode/src/plugin/index.ts | Plugin loading logic | --- Summary: Why oh-my-opencode Works So Well 1. Multi-Model Orchestration: Uses the right model for each task (GPT-5.2 for reasoning, Gemini for UI, Grok for speed) 2. True Parallel Execution: BackgroundManager creates separate sessions, allowing multiple agents to work simultaneously 3. Automatic Notifications: Event system detects task completion and injects messages into the main conversation 4. Defensive Hooks: 21 hooks ensure tasks complete, errors recover, context is managed 5. Comprehensive System Prompt: OmO's 400+ line prompt teaches proper delegation and orchestration 6. IDE-Quality Tools: LSP and AST-grep tools give agents the same capabilities as human developers 7. Context Injection: Automatic README/AGENTS.md injection and conditional rules 8. Claude Code Compatibility: Existing Claude Code configs just work The plugin transforms a single-agent assistant into a dev team where you're the manager and AI models are your specialized engineers. --- Session Summary What We Covered 1. Researched the @opencode-ai/plugin API - Examined the Plugin type, Hooks interface, available events, and tool definitions from the sst/opencode repository 2. Deep-dived into oh-my-opencode architecture - Read and analyzed: - Main entry point (src/index.ts) - 566 lines - Agent definitions (src/agents/) - OmO, oracle, librarian, explore, etc. - BackgroundManager (src/features/background-agent/manager.ts) - 430 lines - Background tools (src/tools/background-task/tools.ts) - 332 lines - Auto-update checker (src/hooks/auto-update-checker/) 3. Explained key concepts: - How plugins are loaded from npm (not GitHub) - How the config hook injects custom agents - How BackgroundManager enables parallel execution - How notifications work (toast + message injection) - The difference between schema URL (IDE autocomplete) vs actual code (npm) 4. Created publishing guide - How to create, build, and publish your own OpenCode plugin Key Files Examined - /Users/darrenhinde/Documents/GitHub/test-larvel/oh-my-opencode/src/index.ts - /Users/darrenhinde/Documents/GitHub/test-larvel/oh-my-opencode/src/agents/*.ts - /Users/darrenhinde/Documents/GitHub/test-larvel/oh-my-opencode/src/features/background-agent/manager.ts - /Users/darrenhinde/Documents/GitHub/test-larvel/oh-my-opencode/src/tools/background-task/tools.ts - /Users/darrenhinde/Documents/GitHub/test-larvel/oh-my-opencode/src/hooks/auto-update-checker/*.ts - /Users/darrenhinde/Documents/GitHub/test-larvel/oh-my-opencode/package.json - /tmp/opencode-repo/packages/plugin/src/index.ts (from sst/opencode) ▣ Compaction · claude-opus-4-5 · 3m 0s Thinking: The user is asking me to continue if there are next steps. Let me check what the current state is and if there's anything pending. Looking at the comprehensive summary I provided, it seems like we've completed a thorough documentation of the OpenCode plugin system and oh-my-opencode architecture. Let me check if there's a todo list or any pending tasks by reading the todo list. ⚙ todoread Thinking: The todo list is empty, which means we've completed the documentation work. The user asked "What did we do so far?" and I provided a comprehensive summary. Now they're asking to continue if there are next steps. Since there's no active todo list and the documentation seems complete, I should ask what they'd like to do next. Possible directions: 1. Create a custom plugin based on what we learned 2. Modify/extend oh-my-opencode 3. Work on something else in the codebase 4. Dive deeper into specific components Let me ask what direction they want to take.