Просмотр исходного кода

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
darrenhinde 7 месяцев назад
Родитель
Сommit
dd0a907c80

+ 137 - 0
.github/workflows/evals/run-evaluations.yml

@@ -0,0 +1,137 @@
+name: Run Evaluations
+
+on:
+  workflow_dispatch:
+    inputs:
+      agent:
+        description: 'Agent to test'
+        required: false
+        default: 'openagent'
+        type: choice
+        options:
+          - openagent
+          - opencoder
+          - system-builder
+      pattern:
+        description: 'Test pattern (glob)'
+        required: false
+        default: '**/golden/*.yaml'
+        type: string
+      model:
+        description: 'Model to use (provider/model)'
+        required: false
+        default: 'opencode/grok-code'
+        type: string
+      seed:
+        description: 'Seed for reproducible randomness'
+        required: false
+        default: 'ci-evaluation-seed'
+        type: string
+      timeout:
+        description: 'Test timeout in milliseconds'
+        required: false
+        default: '120000'
+        type: string
+  schedule:
+    # Run daily at 2 AM UTC
+    - cron: '0 2 * * *'
+  push:
+    branches: [main]
+    paths:
+      - 'evals/framework/**'
+      - 'evals/agents/**'
+      - '.github/workflows/evals/**'
+
+jobs:
+  run-evaluations:
+    runs-on: ubuntu-latest
+    
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v4
+      
+    - name: Setup Node.js
+      uses: actions/setup-node@v4
+      with:
+        node-version: '20'
+        cache: 'npm'
+        cache-dependency-path: evals/framework/package-lock.json
+        
+    - name: Install dependencies
+      working-directory: evals/framework
+      run: npm ci
+      
+    - name: Build framework
+      working-directory: evals/framework
+      run: npm run build
+      
+    - name: Install OpenCode CLI
+      run: |
+        # Install OpenCode CLI if available
+        # This step may need to be adjusted based on OpenCode installation method
+        echo "OpenCode CLI installation would go here"
+        
+    - name: Run evaluations
+      working-directory: evals/framework
+      env:
+        OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
+        OPENCODE_SESSION_STORAGE: /tmp/opencode-sessions
+      run: |
+        # Create isolated session directory
+        mkdir -p /tmp/opencode-sessions
+        
+        # Run evaluations with isolation and seeding
+        npm run eval:sdk \
+          -- --agent=${{ github.event.inputs.agent || 'openagent' }} \
+          --pattern="${{ github.event.inputs.pattern || '**/golden/*.yaml' }}" \
+          --model=${{ github.event.inputs.model || 'opencode/grok-code' }} \
+          --seed=${{ github.event.inputs.seed || 'ci-evaluation-seed' }} \
+          --timeout=${{ github.event.inputs.timeout || '120000' }} \
+          --isolate-environment \
+          --debug
+          
+    - name: Upload results
+      uses: actions/upload-artifact@v4
+      if: always()
+      with:
+        name: evaluation-results
+        path: |
+          evals/results/
+          evals/test_tmp/
+        retention-days: 30
+        
+    - name: Generate summary
+      if: always()
+      run: |
+        if [ -f "evals/results/latest.json" ]; then
+          echo "## Evaluation Results" >> $GITHUB_STEP_SUMMARY
+          echo "" >> $GITHUB_STEP_SUMMARY
+          
+          # Extract summary from latest results
+          node -e "
+            const results = require('./evals/results/latest.json');
+            const passed = results.filter(r => r.passed).length;
+            const total = results.length;
+            const failed = total - passed;
+            
+            console.log(\`| ✅ ${passed}/${total} tests passed\`);
+            console.log(\`| ❌ ${failed} failures\`);
+            console.log('');
+            
+            if (failed > 0) {
+              console.log('### Failed Tests');
+              results.filter(r => !r.passed).forEach(r => {
+                console.log(\`- ${r.testCase.id}: ${r.errors.join(', ')}\`);
+              });
+            }
+          " >> $GITHUB_STEP_SUMMARY
+        else
+          echo "## No Results Generated" >> $GITHUB_STEP_SUMMARY
+          echo "Evaluation run may have failed to complete." >> $GITHUB_STEP_SUMMARY
+        fi
+        
+    - name: Cleanup
+      if: always()
+      run: |
+        # Clean up temporary session storage
+        rm -rf /tmp/opencode-sessions

+ 1007 - 0
dev/ai-tools/opencode/plugins/Plugin-inspiration.md

@@ -0,0 +1,1007 @@
+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.