Skip to content

OpenCode (awaiting for better plan capability) #2

Closed

Description

@backnotprop

Opencode requires them to actually implement more robust capabilities around plan mode. Right now, it just seems like a dummy chat.

  The Complete Flow (What Actually Happens)

  1. User switches to "plan" agent (Tab key or agent selector)
     └─ File: opencode/packages/opencode/src/cli/cmd/tui/app.tsx (agent_cycle command)

  2. When sending a message with plan agent selected:
     └─ File: opencode/packages/opencode/src/session/prompt.ts:1009
     └─ insertReminders() injects PROMPT_PLAN text into user message

  3. LLM receives message with READ-ONLY constraints
     └─ Responds with analysis/planning as regular chat text
     └─ If it tries to edit → Permission.ask() with edit:"deny" → Rejected

  4. Plans are just text in the chat (no storage, no approval)

  5. User manually switches to "build" agent when ready
     └─ BUILD_SWITCH prompt injected telling LLM it can now make changes

Activity

  1. backnotprop commented on Dec 31, 2025

    @backnotprop
    OwnerAuthor

    Nevermind building this

    OpenCode Plannotator Plugin

    Overview

    Adapt Plannotator (existing Claude Code plugin at /Users/ramos/cupcake/cupcake-rego/planning-hook) to work with OpenCode. The goal is to give OpenCode the same interactive plan review experience with visual annotation.

    Existing Assets (Plannotator):

    • Bun server: apps/hook/server/index.ts (122 lines)
    • React UI: packages/editor/App.tsx (554 lines)
    • Annotation system: delete, insert, replace, comment
    • Diff export: packages/ui/utils/parser.ts:exportDiff()
    • Single-file HTML build via vite-plugin-singlefile

    What We Build: OpenCode plugin that bridges to Plannotator's server/UI.


    Core Differences: Claude Code vs OpenCode

    Aspect Claude Code OpenCode
    Hook trigger PermissionRequest on ExitPlanMode Plugin tool submit_plan
    Data input stdin JSON from hook Tool args in execute()
    Decision output stdout JSON Tool return value
    Mode switch Automatic (allow = proceed) client.tui.executeCommand

    Core UX Flow

    User activates "plan" agent in OpenCode
             ↓
    Plugin injects planning workflow prompts
             ↓
    Agent explores, plans, calls `submit_plan` tool
             ↓
    Plugin starts Plannotator server with plan markdown
             ↓
    Browser opens → Plannotator UI
             ↓
    User annotates, then clicks Approve or Provide Feedback
             ↓
        ┌────────┴────────┐
        ↓                 ↓
     APPROVED           DENIED
        ↓                 ↓
     Tool returns      Tool returns
     success msg       diff feedback
        ↓                 ↓
     Plugin switches   Agent revises
     to "build" agent  and resubmits
        ↓
     Agent begins implementation
    

    Directory Structure

    /Users/ramos/cupcake/cupcake-rego/planning-hook/
    ├── apps/
    │   ├── hook/                    # Claude Code plugin (existing)
    │   │   └── server/index.ts      # Bun server (adapt for reuse)
    │   └── opencode-plugin/         # NEW: OpenCode plugin
    │       ├── index.ts             # Plugin entry point
    │       ├── server.ts            # Server adapter
    │       └── package.json
    ├── packages/
    │   ├── editor/                  # Shared - App.tsx (no changes)
    │   └── ui/                      # Shared - components (no changes)
    └── package.json                 # Add opencode-plugin to workspaces
    

    Implementation Plan

    Step 1: Create Plugin Scaffold

    Files to create:

    • apps/opencode-plugin/package.json
    • apps/opencode-plugin/index.ts
    // apps/opencode-plugin/index.ts
    import { type Plugin, tool } from "@opencode-ai/plugin"
    
    export const PlannotatorPlugin: Plugin = async (ctx) => {
      return {
        tool: {
          submit_plan: tool({
            description: "Submit your completed plan for interactive user review",
            args: {
              plan: tool.schema.string().describe("Complete plan in markdown"),
              summary: tool.schema.string().describe("Brief summary"),
            },
            async execute(args, context) {
              // Step 4 implementation
            },
          }),
        },
      }
    }

    Step 2: Adapt Server for Function-Based Invocation

    File to create: apps/opencode-plugin/server.ts

    Current server reads from stdin:

    // apps/hook/server/index.ts:10-12
    const eventJson = await Bun.stdin.text()
    const event = JSON.parse(eventJson)
    let planContent = event.tool_input?.plan || ""

    New server accepts plan as parameter:

    // apps/opencode-plugin/server.ts
    export async function startPlannotatorServer(planContent: string): Promise<{
      port: number
      waitForDecision: () => Promise<{ approved: boolean; feedback?: string }>
    }> {
      let resolveDecision: (result: { approved: boolean; feedback?: string }) => void
      const decisionPromise = new Promise<{ approved: boolean; feedback?: string }>(
        (resolve) => { resolveDecision = resolve }
      )
    
      const server = Bun.serve({
        port: 0,  // Random port
        async fetch(req) {
          const url = new URL(req.url)
    
          if (url.pathname === "/api/plan") {
            return Response.json({ plan: planContent })
          }
    
          if (url.pathname === "/api/approve" && req.method === "POST") {
            resolveDecision({ approved: true })
            return Response.json({ success: true })
          }
    
          if (url.pathname === "/api/deny" && req.method === "POST") {
            const body = await req.json()
            resolveDecision({ approved: false, feedback: body.feedback })
            return Response.json({ success: true })
          }
    
          // Serve embedded HTML
          return new Response(indexHtml, {
            headers: { "Content-Type": "text/html" }
          })
        }
      })
    
      return {
        port: server.port,
        waitForDecision: () => decisionPromise
      }
    }

    Step 3: Wire Up Browser Opening

    async function openBrowser(url: string) {
      const { platform } = process
      const cmd = platform === "darwin" ? "open"
                : platform === "win32" ? "cmd /c start"
                : "xdg-open"
      await Bun.spawn(cmd.split(" ").concat(url))
    }

    Step 4: Complete Tool Execute Function

    async execute(args, context) {
      // Start server
      const { port, waitForDecision } = await startPlannotatorServer(args.plan)
    
      // Open browser
      await openBrowser(`http://localhost:${port}`)
    
      // Wait for user decision (blocks until approve/deny)
      const decision = await waitForDecision()
    
      if (decision.approved) {
        // Switch to build mode
        await ctx.client.tui.executeCommand({
          body: { command: "agent_cycle" }
        })
        return "Plan approved! Switching to build mode. Proceed with implementation."
      } else {
        return `Plan needs revision. User feedback:\n\n${decision.feedback}\n\nRevise and call submit_plan again.`
      }
    }

    Step 5: Add System Prompt Injection (Optional Enhancement)

    "experimental.chat.system.transform": async (input, output) => {
      output.system.push(`
    When in plan mode, create a comprehensive plan then call submit_plan.
    The user will review your plan visually and can annotate with feedback.
    If rejected, revise based on feedback and resubmit.
      `)
    }

    Step 6: Build Configuration

    Add to package.json workspaces, create Vite config to build single-file HTML:

    // apps/opencode-plugin/vite.config.ts
    // Similar to apps/hook/vite.config.ts
    // Outputs dist/index.html with embedded JS/CSS

    Verification Checklist

    # Test Command/Action Expected
    1 Plugin loads Start OpenCode with plugin No errors, tool registered
    2 Tool available Agent lists tools submit_plan appears
    3 Server starts Call submit_plan Browser opens Plannotator
    4 Plan displays View UI Markdown rendered correctly
    5 Annotations work Select text, add comment Annotation appears in sidebar
    6 Approve flow Click Approve Tool returns success, mode switches
    7 Deny flow Annotate + Provide Feedback Tool returns diff feedback
    8 Revision loop Agent receives feedback Agent revises and resubmits

    Files to Create

    File Lines (est) Purpose
    apps/opencode-plugin/package.json 15 Package config
    apps/opencode-plugin/index.ts 60 Plugin entry + submit_plan tool
    apps/opencode-plugin/server.ts 80 Adapted Bun server
    apps/opencode-plugin/vite.config.ts 30 Build config

    Files to Reference (Read-Only)

    File Purpose
    apps/hook/server/index.ts Server logic to adapt
    apps/hook/vite.config.ts Build config template
    packages/editor/App.tsx UI entry (reuse as-is)
    packages/ui/utils/parser.ts exportDiff() for feedback

    Decisions Made

    1. Shared build: OpenCode plugin will import the built HTML from apps/hook/dist/index.html - single source of truth, no duplication.

    2. Detecting plan agent: Will investigate during implementation - may need to check context or use event hook.


    Implementation Order

    1. Create apps/opencode-plugin/ directory structure
    2. Create package.json with @opencode-ai/plugin dependency
    3. Create basic index.ts with empty plugin
    4. Create server.ts adapted from hook server
    5. Implement submit_plan tool with server integration
    6. Add browser opening logic
    7. Test approve/deny flow manually
    8. Add agent switching on approval
    9. Add optional prompt injection
    10. End-to-end test in OpenCode
  2. backnotprop commented on Dec 31, 2025

    @backnotprop
    OwnerAuthor

    merging in #3

  3. backnotprop commented on Dec 31, 2025

    @backnotprop
    OwnerAuthor

    merged in #3

  4. atreus44 commented on Aug 14, 2026

    @atreus44

    Wow, this is where it started

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions