* feat(agents): implement category-based agent organization system - Organize agents into domain categories (core, development, content, data, product, learning) - Move 10 agents to category subdirectories with proper git rename tracking - Update 13 subagents with category and type metadata in frontmatter - Add category metadata files (0-category.json) documenting common patterns - Implement local registry fallback in install script for offline development - Add comprehensive validation suite with 15 automated tests (100% pass rate) - Enhance registry validation with duplicate ID and consistency checks - Update eval framework with intelligent path resolution (backward compatible) - Archive legacy eval structure to _archive/ for reference - Update all documentation to reflect category-based structure - Bump version to 0.5.0 with accurate CHANGELOG Technical Details: - 23 agents organized (10 category agents, 13 subagents) - 6 category directories created - Path resolution supports both agent IDs and category paths - Registry schema updated to v2.0.0 - 159 files changed, 872 insertions(+), 10738 deletions(-) BREAKING CHANGE: Agent file paths now use category structure. Update references from .opencode/agent/openagent.md to .opencode/agent/core/openagent.md. Eval framework maintains backward compatibility via path resolution. * refactor(evals): consolidate documentation and enhance test infrastructure - Remove temporary project tracking files (PHASE_5_COMPLETE, PROJECT_COMPLETE, etc.) - Consolidate evaluation framework docs into main README - Enhance test execution with improved logging and multi-prompt support - Move system-builder from core to meta category - Add comprehensive test suites for openagent with organized structure - Create evaluation test structure for all subagents - Clean up archived workflows and redundant documentation - Update registry to reflect new agent organization - Add shared test templates and golden test patterns * feat(ci): add manual trigger support for bot-created PRs in validate-registry workflow - Add pr_number input to workflow_dispatch for manually triggering validation - Fetch PR details dynamically when triggered manually - Support both automatic (PR event) and manual (workflow_dispatch) triggers - Enable validation of bot-created PRs like automated version bumps - Update branch detection and push logic to handle both trigger types - Add documentation explaining how to manually trigger for bot PRs * feat(evals): add explicit context file validation to test framework - Add expectedContextFiles field to test YAML schema for explicit context file specification - Enhance context-loading-evaluator to support both auto-detect and explicit validation modes - Update documentation with comprehensive guide and examples - Clean up archived legacy test structure (90+ old test files) - Add new example tests demonstrating explicit context validation - Backward compatible with existing tests * feat(evals): add multi-agent logging system and performance optimizations Implement comprehensive multi-agent logging and performance improvements for the eval framework. Task 01: Multi-Agent Logging System - Add complete hierarchical logging module (evals/framework/src/logging/) - SessionTracker: tracks parent-child delegation hierarchies - MultiAgentLogger: pretty-prints logs with visual indentation - Formatters: box characters and emoji formatting - 37 passing unit tests (session-tracker, logger, integration) - Integrate with SDK event stream handler - Hook into session.created, message.updated, message.part.updated events - Real-time child session detection via timestamp heuristics - Message deduplication for cleaner output - Enable in debug mode only (<1% performance overhead) - Add demo script and comprehensive documentation Task 02: Performance Optimizations - Reduce grace period from 5s to 2s (67% reduction, 10-20% faster tests) - Add PerformanceMetricsEvaluator for bottleneck identification - Collects tool latencies, inference time, idle time - Provides full performance visibility - Update smoke test to validate delegation (core feature) - Replace simple read test with multi-agent delegation test - Tests both parent and child agent functionality - Validates multi-agent logging system Results: - 37 unit tests passing - Smoke test passing (95/100 score) - Clean hierarchical logging output - 10-20% faster test execution - Full multi-agent visibility Files changed: 16 files, +2678 lines Time saved: ~6-8 days (completed in 6 hours vs 3-5 day estimate) * feat(evals): show child agent execution in non-debug mode - Enable MultiAgentLogger in both debug and non-debug modes - Add verbose parameter to control output level - Non-verbose mode shows concise child session lifecycle: - Child agent started message - Child agent completed with duration - Verbose mode (--debug) shows full delegation hierarchy - Update documentation to reflect new behavior This provides visibility into delegation without overwhelming output, giving confidence that child agents are actually running. * feat(evals): improve delegation testing and behavior validation Major improvements to eval framework: Test Suite: - Add 00-smoke-test.yaml (basic read operation) - Rename 01-smoke-test.yaml → 02-delegation-test.yaml (delegation test) - Add simple-responder test agent for delegation testing - Add debug test: simple-subagent-call.yaml Evaluators: - Add agent-model-evaluator.ts for agent/model validation - Enhance behavior-evaluator.ts with detailed task tool output - Improve delegation-evaluator.ts with better evidence tracking Schema & Execution: - Add expectedAgent and expectedModel to test schema - Improve test executor with better logging and error handling Documentation: - Update evals/README.md with new features and performance improvements - Remove outdated MULTI_AGENT_LOGGING_COMPLETE.md Registry: - Add simple-responder test agent to registry Fixes: - Fix dashboard.sh path resolution These changes provide better visibility into delegation, improved test coverage, and clearer validation of agent behavior. * chore: add GitHub Actions workflow and plugin documentation - Add .github/workflows/evals/run-evaluations.yml for automated eval testing - Add dev/ai-tools/opencode/plugins/Plugin-inspiration.md for plugin development reference
37 KiB
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
- OpenCode Plugin System Overview (#1-opencode-plugin-system-overview)
- The @opencode-ai/plugin Package (#2-the-opencode-aiplugin-package)
- oh-my-opencode Architecture (#3-oh-my-opencode-architecture)
- The Agent Orchestration System (#4-the-agent-orchestration-system)
- Background Agent System (#5-background-agent-system)
- The Hook Pipeline (#6-the-hook-pipeline)
- Custom Tools (#7-custom-tools)
- MCP Integrations (#8-mcp-integrations)
- Configuration System (#9-configuration-system)
- Plugin Loading & Updates (#10-plugin-loading--updates)
- Creating Your Own Plugin (#11-creating-your-own-plugin)
- Key Files Reference (#12-key-files-reference)
- 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:
- Receive a context object with SDK client, project info, and utilities
- 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
- 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<string, any> }) => 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
| 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 |
// 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<z.ZodObject>, context: ToolContext): Promise }) { return input } tool.schema = z // Zod schema builder Available Events (40+)
- 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) => { ... },
}; };
- 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. <Behavior_Instructions>
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. | |
| </Behavior_Instructions>` | |
| 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 }; }
-
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<string, BackgroundTask> private notifications: Map<string, BackgroundTask[]> 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...
- 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); },
- 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", // ... });
- 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 }; }
- Configuration System Config File Locations
- ~/.config/opencode/oh-my-opencode.json (user-level)
- .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 } }
-
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<string | null> { 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] }
- 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] }
- 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
- Multi-Model Orchestration: Uses the right model for each task (GPT-5.2 for reasoning, Gemini for UI, Grok for speed)
- True Parallel Execution: BackgroundManager creates separate sessions, allowing multiple agents to work simultaneously
- Automatic Notifications: Event system detects task completion and injects messages into the main conversation
- Defensive Hooks: 21 hooks ensure tasks complete, errors recover, context is managed
- Comprehensive System Prompt: OmO's 400+ line prompt teaches proper delegation and orchestration
- IDE-Quality Tools: LSP and AST-grep tools give agents the same capabilities as human developers
- Context Injection: Automatic README/AGENTS.md injection and conditional rules
- 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
- Researched the @opencode-ai/plugin API - Examined the Plugin type, Hooks interface, available events, and tool definitions from the sst/opencode repository
- 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/)
- 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)
- 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:
- Create a custom plugin based on what we learned
- Modify/extend oh-my-opencode
- Work on something else in the codebase
- Dive deeper into specific components Let me ask what direction they want to take.