OpenWriterControle/dev/ai-tools/opencode/plugins/Plugin-inspiration.md
Darren Hinde c8f7103cb6
refactor(evals): consolidate documentation and enhance test infrastructure (#56)
* 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
2025-12-18 09:52:11 +00:00

1007 lines
No EOL
37 KiB
Markdown

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<typeof createOpencodeClient> // 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<Hooks>
---
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<void>
// Configuration hook - modify agents, MCPs, commands
config?: (input: Config) => Promise<void>
// Custom tools registration
tool?: {
[key: string]: ToolDefinition
}
// Authentication provider
auth?: AuthHook
// Chat lifecycle hooks
"chat.message"?: (input, output: { message: UserMessage; parts: Part[] }) => Promise<void>
"chat.params"?: (input, output: { temperature: number; topP: number; topK: number; options: Record<string, any> }) => Promise<void>
// Permission hooks
"permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
// Tool execution hooks
"tool.execute.before"?: (input: { tool: string; sessionID: string; callID: string }, output: { args: any }) => Promise<void>
"tool.execute.after"?: (input: { tool: string; sessionID: string; callID: string }, output: { title: string; output: string; metadata: any }) => Promise<void>
// Experimental hooks
"experimental.chat.messages.transform"?: (input, output: { messages: { info: Message; parts: Part[] }[] }) => Promise<void>
"experimental.chat.system.transform"?: (input, output: { system: string[] }) => Promise<void>
"experimental.text.complete"?: (input: { sessionID: string; messageID: string; partID: string }, output: { text: string }) => Promise<void>
}
Tool Definition
export type ToolContext = {
sessionID: string
messageID: string
agent: string
abort: AbortSignal
}
export function tool<Args extends z.ZodRawShape>(input: {
description: string
args: Args
execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<string>
}) {
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 = `<Role>
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.
</Role>
<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
};
}
---
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<string, BackgroundTask>
private notifications: Map<string, BackgroundTask[]>
private client: OpencodeClient
private pollingInterval?: Timer
async launch(input: LaunchInput): Promise<BackgroundTask> {
// 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<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] }
---
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.