Skip to content

[FEATURE] Plugin System Architecture for CodeNomad (warning: LLM generated, brainstorming and discussion and opinions are welcomed) #193

@VooDisss

Description

@VooDisss

Summary

CodeNomad is currently a single-purpose coding assistant UI that runs OpenCode agents. This proposal requests a comprehensive plugin architecture that transforms CodeNomad into an extensible platform where developers can add custom UI panels, sidebars, visualizations, and custom AI agents without modifying CodeNomad's source code.

The core value proposition is simple: anyone should be able to create a plugin that adds new UI panels to CodeNomad - whether it's a research dashboard, data visualization panel, documentation viewer, or any other UI component - and share it with others.

This builds on existing infrastructure (SSE event channels, background processes, tool renderers) and introduces:

  • A formal plugin manifest and loading system
  • A UI extension API for custom panels, sidebars, and tool renderers
  • A server-side API for custom agents, tools, and HTTP routes
  • Plugin lifecycle management (install, enable, disable, uninstall)

This will enable use cases beyond coding: research assistants, data analysis tools, documentation writers, domain-specific AI helpers, and a thriving community ecosystem where users don't need to fork CodeNomad for niche needs.

User Goal

Enable the CodeNomad ecosystem to grow beyond coding by allowing developers to create plugins that:

UI Extensions (Primary Focus)

  • Add custom panels - New tabbed panels in the right sidebar (like Changes, Files, Git tabs) for displaying plugin-specific content
  • Add sidebar widgets - Expandable sections in the left sidebar for quick access to plugin features
  • Custom tool renderers - Rich, interactive displays for custom tool outputs beyond plain text
  • Custom visualizations - Charts, graphs, data tables, or any interactive UI for displaying agent results
  • Menu items and commands - Add new actions to the command palette
  • Keyboard shortcuts - Register custom hotkeys for plugin actions

Backend Extensions

  • Custom AI agents - Define new agent types with custom system prompts (e.g., research agent, data analyst)
  • Custom tools - Expose new capabilities to AI agents (web search, API calls, data processing)
  • HTTP routes - Add custom API endpoints per workspace for external integrations
  • Background processes - Run long-running tasks spawned by agents

Platform Features

  • Plugin storage - Persist plugin-specific configuration and state
  • Configuration UI - Built-in settings panel for configuring installed plugins
  • Event system - React to CodeNomad events (messages, sessions, agent actions)

This will allow users to avoid forking CodeNomad for niche needs, foster a community-driven ecosystem, and position CodeNomad as a general-purpose AI assistant platform.

Expected Behavior

Users should be able to:

  1. Browse plugins - See available plugins in a plugin browser UI
  2. Install plugins - Add plugins to their CodeNomad with one click
  3. Use custom panels - See new tabs in the right panel (like "Research", "Data", "Notes")
  4. Interact with sidebar widgets - Access plugin features from the left sidebar
  5. Configure plugins - Modify plugin settings in a unified settings UI
  6. Use custom agents - Select plugin-provided agents when creating sessions
  7. Disable/uninstall plugins - Remove plugins without breaking CodeNomad

Developers should be able to:

  1. Create UI panels - Add new tabbed panels to CodeNomad without touching source code
  2. Create sidebar widgets - Add expandable sections to the left sidebar
  3. Build custom tool renderers - Display tool outputs as rich interactive components
  4. Define agents - Create new AI agent types with custom prompts
  5. Register tools - Expose new capabilities to any agent
  6. Add HTTP routes - Create API endpoints for external integrations
  7. Persist data - Store plugin configuration and state

Technical Context

Click to expand - Existing Infrastructure

Server-Side Infrastructure (Already Exists)

Component Location Status
Plugin Event Channel packages/server/src/plugins/channel.ts ✅ Implemented
Plugin Event Handlers packages/server/src/plugins/handlers.ts ✅ Implemented
Plugin Routes packages/server/src/server/routes/plugin.ts ✅ Implemented
Background Processes packages/server/src/background-processes/ ✅ Implemented
Built-in Plugin packages/opencode-config/plugin/ ✅ Implemented
Event Bus packages/server/src/events/bus.ts ✅ Implemented

UI-Side Infrastructure (Already Exists)

Component Location Status
Tool Renderer Registry packages/ui/src/components/tool-call/renderers/index.ts ✅ Implemented
Keyboard Registry packages/ui/src/lib/keyboard-registry.ts ✅ Implemented
SSE Event Manager packages/ui/src/lib/sse-manager.ts ✅ Implemented
Message Store Hooks packages/ui/src/stores/message-v2/instance-store.ts ✅ Implemented
Message Store Bus packages/ui/src/stores/message-v2/bus.ts ✅ Implemented

Critical Gaps Identified

  1. No plugin manifest format - Plugins lack declarative metadata
  2. No plugin discovery/loading - Only one hardcoded built-in plugin exists
  3. No tool registration API - Plugins cannot expose new tools to AI agents
  4. No plugin lifecycle management - No install/unload/disable/enable functionality
  5. No plugin storage - No dedicated plugin configuration/state storage
  6. No UI extension API - No formal way to add custom panels, sidebars, or widgets
  7. No plugin-to-plugin communication - Isolated plugins cannot interact
Click to expand - Current UI Architecture + Plugin Integration Points

Current CodeNomad UI Architecture

Understanding the current structure is essential to see where custom panels would integrate:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                    CODE NOMAD UI                                        │
│  ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│  │                              TOP BAR (Title, Actions)                              │ │
│  └─────────────────────────────────────────────────────────────────────────────────┘ │
│  ┌──────────────────┬────────────────────────────────────────┬───────────────────────┐ │
│  │                  │                                        │                       │ │
│  │                  │                                        │                       │ │
│  │   LEFT SIDEBAR  │           MAIN CONTENT AREA            │    RIGHT PANEL       │ │
│  │                  │                                        │                       │ │
│  │  ┌────────────┐  │  ┌──────────────────────────────────┐  │  ┌─────────────────┐  │ │
│  │  │ Sessions   │  │  │                                  │  │  │ [Changes]       │  │ │
│  │  │ List       │  │  │                                  │  │  │ [Git Changes]  │  │ │
│  │  │            │  │  │     Agent Messages / Chat        │  │  │ [Files]         │  │ │
│  │  │ - Session1 │  │  │                                  │  │  │ [Status]        │  │ │
│  │  │ - Session2 │  │  │   (Tool calls, responses,       │  │  │                 │  │ │
│  │  │ - Session3 │  │  │    user inputs appear here)      │  │  │ ┌─────────────┐│  │ │
│  │  │            │  │  │                                  │  │  │ │             ││  │ │
│  │  └────────────┘  │  │                                  │  │  │ │  TAB CONTENT ││  │ │
│  │                   │  │                                  │  │  │ │             ││  │ │
│  │  ┌────────────┐  │  │                                  │  │  │ │             ││  │ │
│  │  │ Controls:  │  │  └──────────────────────────────────┘  │  │ │ └─────────────┘│  │ │
│  │  │ - Agent    │  │                                        │  │ │                 │  │ │
│  │  │ - Model    │  │  ┌──────────────────────────────────┐  │  │ │                 │  │ │
│  │  │ - Thinking │  │  │     PROMPT INPUT                 │  │  │ └─────────────────┘  │ │
│  │  └────────────┘  │  │  ┌────────────────────────────┐   │  │  │                 │  │
│  │                  │  │  │                            │   │  │  │                 │  │
│  │                  │  │  └────────────────────────────┘   │  │  │                 │  │
│  │                  │  │                                        │  │  │                 │  │
│  └──────────────────┴────────────────────────────────────────┴───────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────────┘

Current Right Panel Structure (Hardcoded Tabs)

The right panel currently has 4 hardcoded tabs - this is the primary integration point for custom panels:

┌─────────────────────────────────────────────────────┐
│  RIGHT PANEL                                         │
│  ┌───────────────────────────────────────────────┐  │
│  │ [Changes] [Git Changes] [Files] [Status]     │  │  ◄── Tab strip (hardcoded)
│  ├───────────────────────────────────────────────┤  │
│  │                                               │  │
│  │   ACTIVE TAB CONTENT                          │  │  ◄── Only one shows at a time
│  │                                               │  │
│  │   - ChangesTab                                │  │
│  │   - FilesTab                                  │  │
│  │   - GitChangesTab                             │  │
│  │   - StatusTab                                │  │
│  │                                               │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘

Current Code Structure (What Needs to Change)

The current implementation in RightPanel.tsx uses hardcoded strings and components:

// Types are hardcoded - this is the key blocker:
type RightPanelTab = "changes" | "git-changes" | "files" | "status"

// Tabs are hardcoded buttons in the JSX:
<button onClick={() => setRightPanelTab("changes")}>Changes</button>
<button onClick={() => setRightPanelTab("files")}>Files</button>
<button onClick={() => setRightPanelTab("git-changes")}>Git</button>
<button onClick={() => setRightPanelTab("status")}>Status</button>

// Tab content is hardcoded with Show components:
<Show when={rightPanelTab() === "changes"}><ChangesTab /></Show>
<Show when={rightPanelTab() === "files"}><FilesTab /></Show>
<Show when={rightPanelTab() === "git-changes"}><GitChangesTab /></Show>
<Show when={rightPanelTab() === "status"}><StatusTab /></Show>

Proposed Plugin Integration Points

After implementing plugin support, the structure would become:

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                    CODE NOMAD UI (WITH PLUGINS)                        │
│  ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│  │                              TOP BAR (Title, Actions)                              │ │
│  └─────────────────────────────────────────────────────────────────────────────────┘ │
│  ┌──────────────────┬────────────────────────────────────────┬───────────────────────┐ │
│  │                  │                                        │                       │ │
│  │   LEFT SIDEBAR  │           MAIN CONTENT AREA            │    RIGHT PANEL       │ │
│  │                  │                                        │                       │ │
│  │  ┌────────────┐  │  ┌──────────────────────────────────┐  │  ┌─────────────────┐  │ │
│  │  │ Sessions   │  │  │                                  │  │  │ [Changes]       │  │ │
│  │  │ List       │  │  │                                  │  │  │ [Git Changes]  │  │ │
│  │  │            │  │  │     Agent Messages / Chat        │  │  │ [Files]         │  │ │
│  │  │ - Session1 │  │  │                                  │  │  │ [Status]        │  │ │
│  │  │ - Session2 │  │  │   (Tool calls, responses,       │  │  │ ─────────────── │  │ │
│  │  │ - Session3 │  │  │    user inputs appear here)      │  │  │ [🔬 Research]  │  │ │
│  │  │            │  │  │                                  │  │  │ [📊 Data]       │  │ │
│  │  └────────────┘  │  │                                  │  │  │ [📝 Notes]      │  │ │
│  │                   │  │                                  │  │  │                 │  │ │
│  │  ┌────────────┐  │  └──────────────────────────────────┘  │  │ │ ┌─────────────┐│  │ │
│  │  │ Controls:  │  │                                        │  │ │ │             ││  │ │
│  │  │ - Agent    │  │  ┌──────────────────────────────────┐  │  │ │ │  RESEARCH  ││  │ │
│  │  │ - Model    │  │  │     PROMPT INPUT                 │  │  │ │ │   PANEL   ││  │ │
│  │  │ - Thinking │  │  └──────────────────────────────────┘  │  │ │ │             ││  │ │
│  │  └────────────┘  │                                        │  │ │ └─────────────┘│  │ │
│  │                   │                                        │  │ │                 │  │ │
│  │  ┌──────────────────────────────────────────┐              │  │ │                 │  │ │
│  │  │  PLUGINS COULD ADD WIDGETS HERE         │  │  ◄── NEW: Sidebar widgets   │ │
│  │  │  (e.g., Quick Search, Recent Files)    │  │              │                 │  │ │
│  │  └──────────────────────────────────────────┘  │              │                 │  │ │
│  │                  │                             │              │                 │  │ │
│  └──────────────────┴────────────────────────────┴──────────────┴─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────────────┘

Plugin Panel Integration Flow

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           PLUGIN PANEL INTEGRATION FLOW                             │
└─────────────────────────────────────────────────────────────────────────────────────┘

1. PLUGIN DEFINES PANEL IN MANIFEST
   ┌────────────────────────────────────────────────────────────────┐
   │ research-plugin/plugin.json                                    │
   │ {                                                              │
   │   "name": "research-assistant",                                │
   │   "contributes": {                                             │
   │     "panels": [                                                │
   │       {                                                        │
   │         "id": "research-dashboard",                           │
   │         "title": "Research",                                  │
   │         "icon": "🔬"                                         │
   │       }                                                        │
   │     ]                                                          │
   │   }                                                            │
   │ }                                                              │
   └────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
2. PLUGIN REGISTERS PANEL COMPONENT AT RUNTIME
   ┌────────────────────────────────────────────────────────────────┐
   │ research-plugin/src/ui/panel.tsx                              │
   │                                                                 │
   │ import { UIPanel } from '@codenomad/ui-plugin-sdk'           │
   │                                                                 │
   │ export const researchPanel: UIPanel = {                       │
   │   id: 'research-dashboard',                                   │
   │   title: 'Research',                                          │
   │   icon: '🔬',                                                 │
   │   render: () => <ResearchDashboard />,                        │
   │ }                                                             │
   │                                                                 │
   │ codenomad.ui.panels.register(researchPanel)                  │
   └────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
3. RIGHT PANEL DYNAMICALLY LOADS FROM REGISTRY
   ┌────────────────────────────────────────────────────────────────┐
   │ RightPanel.tsx (MODIFIED)                                     │
   │                                                                 │
   │ // Dynamic tab loading from registry                           │
   │ const tabs = usePanelRegistry()  // Returns all registered    │
   │                                                                    │
   │ // Tab buttons rendered dynamically                             │
   │ <For each={tabs()}>                                           │
   │   {(tab) => <TabButton tab={tab} />}                          │
   │ </For>                                                        │
   │                                                                    │
   │ // Active content resolved from registry                       │
   │ {tabs().find(t => t.id === activeTab())?.render()}           │
   └────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
4. USER SEES NEW TAB IN RIGHT PANEL
   ┌────────────────────────────────────────────────────────────────┐
   │                         RIGHT PANEL                            │
   │  ┌──────────────────────────────────────────────────────────┐  │
   │  │ [Changes] [Files] [Git] [Status] [🔬 Research] [📊 Data]│ │
   │  ├──────────────────────────────────────────────────────────┤  │
   │  │                                                          │  │
   │  │            Research Panel Content                         │  │
   │  │                                                          │  │
   │  │  ┌────────────────────────────────────────────────────┐  │  │
   │  │  │  🔍 Search: "neural architecture search"          │  │  │
   │  │  ├────────────────────────────────────────────────────┤  │  │
   │  │  │  📄 Paper 1: "Neural Architecture Search..."     │  │  │
   │  │  │       Authors: Smith et al. | Year: 2024         │  │  │
   │  │  │  ┌────────────────────────────────────────────┐  │  │  │
   │  │  │  │ [Save] [Fetch] [Cite]                    │  │  │  │
   │  │  │  └────────────────────────────────────────────┘  │  │  │
   │  │  ├────────────────────────────────────────────────────┤  │  │
   │  │  │  📄 Paper 2: "Efficient Neural..."              │  │  │
   │  │  │       Authors: Chen et al. | Year: 2023         │  │  │
   │  │  └────────────────────────────────────────────────────┘  │  │
   │  └──────────────────────────────────────────────────────────┘  │
   └────────────────────────────────────────────────────────────────┘

Sidebar Widget Integration (Future Enhancement)

┌─────────────────────────────────────────────────────────────────┐
│                    LEFT SIDEBAR WITH WIDGETS                    │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ CodeNomad                                      [+] [📌]        │
├─────────────────────────────────────────────────────────────────┤
│                                                                     │
│ ┌─────────────────────────────────────────────────────────────┐  │
│ │ 🔍 Quick Search                           [▼]                │  │  ◄── Plugin widget
│ │ ┌─────────────────────────────────────────────────────────┐ │  │
│ │ │ Search papers...                                      │ │  │
│ │ └─────────────────────────────────────────────────────────┘ │  │
│ └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│ SESSIONS                              [+ New] [Search]            │
│ ┌─────────────────────────────────────────────────────────────┐  │
│ │ 💬 Session 1                                              │  │
│ │ 💬 Session 2                                              │  │
│ │ 💬 Session 3                                              │  │
│ └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│ ACTIVE SESSION                                                   │
│ Agent:    [Claude 3.5] ▼                                        │
│ Model:    [Sonnet] ▼                                            │
│ Thinking: [Enabled] ▼                                           │
│                                                                     │
│ ┌─────────────────────────────────────────────────────────────┐  │
│ │ 🔬 Recent Papers                        [▶]                   │  │  ◄── Plugin widget
│ │   • Neural Architecture Search (2024)                      │  │
│ │   • Efficient Transformers (2023)                          │  │
│ └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────┘

Technical Changes Required

Location Current (Hardcoded) After (Dynamic)
types.ts type RightPanelTab = "changes" | "files" | ... type RightPanelTab = string or discriminated union
RightPanel.tsx <Show when={tab() === "changes"}> tabRegistry.get(activeTab())?.render()
Tab buttons Hardcoded <button>Changes</button> <For each={tabs()}> loop
Tab state Single rightPanelTab signal Per-panel state managed in registry
Click to expand - Server-Side API

Server-Side Plugin API

Plugin Manifest (plugin.json)

{
  "name": "research-assistant",
  "version": "1.0.0",
  "description": "AI-powered research assistant for academic literature review",
  "author": {
    "name": "CodeNomad Community",
    "url": "https://github.com/codenomad"
  },
  "engines": {
    "codenomad": ">=1.0.0 <2.0.0"
  },
  "contributes": {
    "agents": [
      {
        "id": "researcher",
        "name": "Research Assistant",
        "description": "Helps find, analyze, and synthesize academic literature",
        "systemPrompt": "You are a research assistant..."
      }
    ],
    "tools": [
      {
        "name": "web_search",
        "description": "Search the web for relevant information",
        "inputSchema": {
          "type": "object",
          "properties": {
            "query": { "type": "string" },
            "numResults": { "type": "number", "default": 10 }
          },
          "required": ["query"]
        }
      }
    ],
    "routes": [
      {
        "method": "GET",
        "path": "/research/:paperId",
        "handler": "handlers/getPaper"
      }
    ]
  },
  "permissions": [
    "storage:read",
    "storage:write",
    "network:http",
    "process:spawn"
  ]
}

Plugin Interface

// Core plugin interface
interface CodeNomadPlugin {
  // Metadata
  name: string
  version: string
  description?: string
  
  // Contributions
  agents?: AgentDefinition[]
  tools?: ToolDefinition[]
  routes?: RouteDefinition[]
  
  // Lifecycle hooks
  onLoad?(context: PluginContext): Promise<void>
  onUnload?(): Promise<void>
  onEnable?(): Promise<void>
  onDisable?(): Promise<void>
}

// Context provided to plugins
interface PluginContext {
  // Workspace identification
  workspaceId: string
  workspacePath: string
  
  // Storage API
  storage: PluginStorage
  
  // Event API
  events: PluginEventBus
  
  // Background processes
  processes: BackgroundProcessManager
  
  // Configuration
  config: PluginConfigManager
  
  // Logging
  logger: PluginLogger
  
  // HTTP client for external APIs
  http: HttpClient
}

// Storage API
interface PluginStorage {
  get<T>(key: string): Promise<T | undefined>
  set<T>(key: string, value: T): Promise<void>
  delete(key: string): Promise<void>
  list(prefix?: string): Promise<string[]>
}

// Event API for inter-plugin communication
interface PluginEventBus {
  emit(event: string, data: unknown): void
  on(event: string, handler: (data: unknown) => void): () => void
  off(event: string, handler: (data: unknown) => void): void
}

Agent Definition

interface AgentDefinition {
  id: string
  name: string
  description: string
  systemPrompt: string
  capabilities?: string[]
  defaultModel?: string
  temperature?: number
  maxTokens?: number
}

Tool Definition

interface ToolDefinition {
  name: string
  description: string
  inputSchema: JSONSchema
  handler: ToolHandler
}

type ToolHandler = (input: unknown, context: ToolContext) => Promise<ToolResult>

interface ToolContext {
  workspaceId: string
  sessionId: string
  userId?: string
  logger: PluginLogger
}

Route Definition

interface RouteDefinition {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
  path: string
  handler: string // Reference to exported handler function
  options?: {
    auth?: boolean
    schema?: FastifySchema
  }
}
Click to expand - UI Extension API

UI Extension API

The primary focus of this plugin system is enabling developers to add custom UI to CodeNomad without modifying its source code. This section details exactly what UI extensions plugins can provide.

Custom Panels (Right Sidebar)

The right sidebar in CodeNomad currently has tabs like "Changes", "Files", "Git Changes", "Status". Plugins should be able to add new tabs to this panel.

// Panel definition - appears as new tab in right sidebar
interface UIPanel {
  // Unique identifier for the panel
  id: string
  
  // Display title shown in tab
  title: string
  
  // Icon shown in tab (emoji or icon name)
  icon?: string
  
  // Optional badge number (like notification count)
  badge?: () => number | undefined
  
  // Render function - SolidJS component
  render: () => JSX.Element
  
  // Panel toolbar actions
  actions?: UIPanelAction[]
  
  // Called when panel becomes visible
  onMount?: () => void
  
  // Called when panel is hidden
  onUnmount?: () => void
  
  // Called when panel receives new data from agent
  onData?: (data: unknown) => void
}

interface UIPanelAction {
  id: string
  label: string
  icon?: string
  onClick: () => void
}

// Registration API
codenomad.ui.panels.register(panel: UIPanel): void
codenomad.ui.panels.unregister(panelId: string): void

// Example: Research panel would register like this
codenomad.ui.panels.register({
  id: 'research-dashboard',
  title: 'Research',
  icon: '🔍',
  render: () => <ResearchDashboard />,
  actions: [
    { id: 'new-search', label: 'New Search', icon: '🔎', onClick: () => startNewSearch() }
  ]
})

Example Use Cases:

  • Research plugin: Papers list, citation manager, notes panel
  • Data analysis plugin: Query results table, visualization charts
  • Documentation plugin: Generated docs viewer, API reference
  • Notes plugin: Quick notes panel, markdown editor

Sidebar Widgets (Left Sidebar)

The left sidebar (session list) should accept widget plugins that appear as expandable sections.

// Sidebar widget - appears in left sidebar
interface SidebarWidget {
  id: string
  title: string
  icon?: string
  
  // Position in sidebar
  position: 'top' | 'bottom'
  
  // Sort order within position
  priority?: number
  
  // Initial expanded state
  defaultExpanded?: boolean
  
  // Render the widget content
  render: () => JSX.Element
  
  // Optional: actions in widget header
  headerActions?: WidgetAction[]
}

interface WidgetAction {
  id: string
  icon: string
  tooltip: string
  onClick: () => void
}

// Registration
codenomad.ui.sidebar.register(widget: SidebarWidget): void
codenomad.ui.sidebar.unregister(widgetId: string): void
codenomad.ui.sidebar.setExpanded(widgetId: string, expanded: boolean): void

Example Use Cases:

  • Quick search widget for research plugin
  • Recent files widget
  • Active background processes widget
  • Plugin status/indicator widget

Custom Tool Renderers

When an AI agent uses a custom tool, plugins can provide rich renderers for the output (beyond plain text).

// Tool renderer - custom display for tool outputs
interface ToolRenderer {
  // Tools this renderer handles (e.g., ["web_search", "url_fetch"])
  tools: string[]
  
  // Optional: dynamic title based on tool input
  getTitle?: (context: ToolRendererContext) => string | undefined
  
  // Optional: action button (e.g., "View Details")
  getAction?: (context: ToolRendererContext) => string | undefined
  
  // Render the main tool output body
  renderBody: (context: ToolRendererContext) => JSX.Element | null
  
  // Optional: compact summary version
  renderSummary?: (context: ToolRendererContext) => JSX.Element | null
  
  // Optional: full-screen or expanded view
  renderExpanded?: (context: ToolRendererContext) => JSX.Element | null
}

interface ToolRendererContext {
  tool: Tool
  toolName: string
  input: unknown
  output: unknown
  message: Message
  instanceId: string
  sessionId: string
}

// Registration
codenomad.ui.tools.registerRenderer(renderer: ToolRenderer): void
codenomad.ui.tools.unregisterRenderer(toolName: string): void

// Example: Research plugin would render search results
codenomad.ui.tools.registerRenderer({
  tools: ['research_web_search'],
  getTitle: (ctx) => `🔍 Search: ${(ctx.input as any).query}`,
  renderBody: (ctx) => {
    const results = (ctx.output as SearchResult[]);
    return <SearchResultsList results={results} />
  }
})

Example Use Cases:

  • Search results as clickable cards
  • Data tables with sorting/filtering
  • Image galleries for vision tool outputs
  • Code syntax highlighting
  • Interactive charts/graphs

Command Palette Commands

Plugins can add commands that appear in the command palette.

// Command for command palette
interface PluginCommand {
  id: string
  label: string
  category?: string  // e.g., "Research", "Files"
  icon?: string
  keybinding?: string  // e.g., "ctrl+shift+r"
  
  // Enable/disable based on context
  enabled?: () => boolean
  
  // Execute the command
  execute: () => void | Promise<void>
}

// Registration
codenomad.ui.commands.register(command: PluginCommand): void
codenomad.ui.commands.unregister(commandId: string): void

// Example
codenomad.ui.commands.register({
  id: 'research.new-search',
  label: 'Research: New Search',
  category: 'Research',
  keybinding: 'ctrl+shift+r',
  execute: () => openSearchPanel()
})

Keyboard Shortcuts

interface KeyboardShortcut {
  id: string
  key: string
  modifiers?: ('ctrl' | 'shift' | 'alt' | 'meta')[]
  context?: 'global' | 'input' | 'messages'
  description?: string
  action: () => void
  condition?: () => boolean
}

codenomad.ui.shortcuts.register(shortcut: KeyboardShortcut): void
codenomad.ui.shortcuts.unregister(shortcutId: string): void

Reacting to Agent Events

Plugins should be able to react when agents send messages, request permissions, etc.

// Available events in UI
interface UIEventMap {
  // Message events
  'message:send': (message: string) => void
  'message:receive': (message: Message) => void
  'message:part': (part: MessagePart) => void
  'tool:call': (tool: ToolCall) => void
  'tool:result': (toolName: string, result: unknown) => void
  
  // Session events
  'session:create': (session: Session) => void
  'session:switch': (sessionId: string) => void
  'session:complete': (session: Session) => void
  
  // Agent events
  'agent:thinking': (thinking: string) => void
  'agent:error': (error: Error) => void
  
  // UI events
  'panel:open': (panelId: string) => void
  'panel:close': (panelId: string) => void
  'panel:data': (panelId: string, data: unknown) => void
  
  // System events
  'theme:change': (theme: 'light' | 'dark') => void
  'instance:connect': (instanceId: string) => void
  'instance:disconnect': (instanceId: string) => void
}

// Subscribe to events
codenomad.ui.events.on<K extends keyof UIEventMap>(
  event: K, 
  handler: UIEventMap[K]
): () => void  // Returns unsubscribe function

Sending Data to UI Panels

Plugins should be able to send data to their UI panels.

// Send data from server-side plugin to UI panel
codenomad.ui.panels.sendData(panelId: string, data: unknown): void

// Example: Research agent sends search results to Research panel
// Server-side:
agent.on('search_complete', (results) => {
  codenomad.ui.panels.sendData('research-dashboard', results)
})

// UI side:
codenomad.ui.panels.register({
  id: 'research-dashboard',
  // ...
  onData: (data) => {
    // Update panel state with new data
    setResults(data as SearchResult[])
  }
})

Configuration UI

Plugins can define settings that appear in CodeNomad's settings panel.

interface PluginSetting {
  key: string
  type: 'string' | 'number' | 'boolean' | 'select' | 'array' | 'object'
  label: string
  description?: string
  default?: unknown
  
  // For select type
  options?: { label: string; value: unknown }[]
  
  // For object type - nested schema
  schema?: JSONSchema
  
  // Validation
  validate?: (value: unknown) => string | null  // Error message or null if valid
}

codenomad.ui.settings.register(pluginId: string, settings: PluginSetting[]): void
codenomad.ui.settings.get<T>(key: string): T
codenomad.ui.settings.set<T>(key: string, value: T): void

Theme Integration

Plugins should integrate with CodeNomad's theme system.

// Access current theme
codenomad.ui.theme.get(): 'light' | 'dark'

// Subscribe to theme changes
codenomad.ui.theme.onChange((theme) => {
  // Update plugin UI accordingly
})

// Use theme colors in plugin UI
// CodeNomad provides CSS custom properties:
// - var(--cn-bg-primary)
// - var(--cn-bg-secondary)
// - var(--cn-text-primary)
// - var(--cn-border-color)
// etc.

Dialogs and Modals

// Show dialog
codenomad.ui.dialogs.show(options: {
  title: string
  content: JSX.Element | string
  confirmLabel?: string
  cancelLabel?: string
  type?: 'info' | 'warning' | 'error'
}): Promise<boolean>

// Show notification/toast
codenomad.ui.notifications.show(options: {
  message: string
  type?: 'info' | 'success' | 'warning' | 'error'
  duration?: number  // ms
})

Communication with Server-Side Plugin

// UI plugin can communicate with its server-side counterpart
codenomad.plugin.invoke<T>(method: string, args?: unknown): Promise<T>

// Example: Research panel calls server-side to fetch paper details
const paper = await codenomad.plugin.invoke<Paper>('getPaper', { id: paperId })

// Server-side handler:
plugin.onInvoke('getPaper', async (args) => {
  return await fetchPaperDetails(args.id)
})
Click to expand - Research Use Case

Research Use Case - Concrete Example

To validate the plugin architecture, this section outlines a Research Assistant Plugin - this is the primary use case driving this feature request. The goal is to enable research workflows within CodeNomad without forking the codebase.

What Researchers Need

Capability UI Component Description
Search the web Custom tool Agent can search for papers, articles
Fetch URLs Custom tool Extract content from web pages
View papers Custom panel List of saved papers with metadata
Take notes Custom panel Notes attached to each paper
Citations Custom panel Generate citation strings
Analysis Custom tool Summarize, extract key findings

Plugin UI Components

Research Panel (Right Sidebar)

The main panel researchers would interact with:

// research-plugin/src/ui/ResearchPanel.tsx
import { UIPanel } from '@codenomad/ui-plugin-sdk'

export const researchPanel: UIPanel = {
  id: 'research-dashboard',
  title: 'Research',
  icon: '🔬',
  badge: () => unreadPapersCount(),
  
  render: () => (
    <div class="research-panel">
      <div class="search-bar">
        <Input 
          value={query()} 
          onInput={setQuery}
          placeholder="Search for papers..."
          onEnter={() => agent.search(query())}
        />
        <Button onClick={() => agent.search(query())}>Search</Button>
      </div>
      
      <Tabs 
        tabs={[
          { id: 'papers', label: 'Papers', content: <PapersList /> },
          { id: 'notes', label: 'Notes', content: <NotesPanel /> },
          { id: 'citations', label: 'Citations', content: <CitationsPanel /> }
        ]}
      />
    </div>
  ),
  
  actions: [
    { id: 'new-search', label: 'New Search', icon: '🔎', onClick: () => startSearch() },
    { id: 'export', label: 'Export All', icon: '📤', onClick: () => exportAll() }
  ],
  
  onMount: () => {
    // Load saved papers when panel opens
    loadPapers()
  }
}

Search Results Tool Renderer

When the research agent performs a search, results appear as interactive cards:

// research-plugin/src/ui/SearchResultsRenderer.tsx
import { ToolRenderer } from '@codenomad/ui-plugin-sdk'

export const searchResultsRenderer: ToolRenderer = {
  tools: ['research_web_search'],
  
  getTitle: (ctx) => `🔍 Search: "${(ctx.input as SearchInput).query}"`,
  
  getAction: (ctx) => 'View All',
  
  renderBody: (ctx) => {
    const results = ctx.output as SearchResult[]
    return (
      <div class="search-results">
        <For each={results}>
          {(result) => (
            <PaperCard 
              title={result.title}
              authors={result.authors}
              year={result.year}
              abstract={result.snippet}
              onSave={() => savePaper(result)}
              onFetch={() => fetchFullPaper(result.url)}
            />
          )}
        </For>
      </div>
    )
  },
  
  renderSummary: (ctx) => {
    const count = (ctx.output as SearchResult[]).length
    return <span>{count} results found</span>
  }
}

Sidebar Widget

Quick access from the left sidebar:

// research-plugin/src/ui/ResearchWidget.tsx
import { SidebarWidget } from '@codenomad/ui-plugin-sdk'

export const researchWidget: SidebarWidget = {
  id: 'research-quick',
  title: 'Research',
  icon: '🔬',
  position: 'top',
  priority: 10,
  defaultExpanded: false,
  
  render: () => (
    <div class="research-widget">
      <div class="quick-search">
        <Input 
          placeholder="Quick search..." 
          onEnter={(q) => quickSearch(q)}
        />
      </div>
      <div class="recent-papers">
        <span>Recent:</span>
        <For each={recentPapers()}>
          {(paper) => (
            <Button 
              variant="ghost" 
              onClick={() => selectPaper(paper.id)}
            >
              {paper.title}
            </Button>
          )}
        </For>
      </div>
    </div>
  )
}

User Flow

  1. Install: User runs codenomad plugin install research or clicks "Install" in plugin browser
  2. Configure: User sets API keys for search services in plugin settings
  3. Panel appears: "Research" tab appears in right panel alongside "Changes", "Files"
  4. Research: User asks "Find recent papers on neural architecture search"
  5. Agent uses tools: Research agent calls research_web_search, results appear as cards
  6. Results in UI: Papers appear in Research panel, can save, take notes
  7. Citations: Generate citation for saved papers

Server-Side Components

// research-plugin/src/index.ts
import { CodeNomadPlugin } from '@codenomad/server-plugin-sdk'

export const researchPlugin: CodeNomadPlugin = {
  name: 'research-assistant',
  version: '1.0.0',
  
  // Tools exposed to the agent
  tools: [
    webSearchTool,
    urlFetchTool,
    analyzePaperTool,
    generateCitationTool
  ],
  
  // Custom research agent
  agents: [
    {
      id: 'researcher',
      name: 'Research Assistant',
      description: 'Helps find, analyze, and synthesize academic literature',
      systemPrompt: `You are a research assistant...`
    }
  ],
  
  // HTTP routes for external integrations
  routes: [
    { method: 'GET', path: '/papers', handler: 'handlers/listPapers' },
    { method: 'POST', path: '/papers', handler: 'handlers/savePaper' }
  ],
  
  async onLoad(ctx) {
    // Initialize storage, connect to external APIs
  }
}
Click to expand - Implementation Plan

Implementation Plan

The primary focus of this plugin system is UI extensibility - allowing developers to add custom panels without modifying CodeNomad source code. The backend support is secondary but enables powerful agent capabilities.

Phase 1: Foundation (Server + Plugin Infrastructure)

Goal: Establish core plugin infrastructure

Task Description Files to Modify
Define plugin manifest schema Create JSON schema for plugin.json New: packages/server/src/plugins/manifest.ts
Create plugin loader Scan and load plugins from ./plugins folder New: packages/server/src/plugins/loader.ts
Implement plugin registry Track loaded plugins and their capabilities New: packages/server/src/plugins/registry.ts
Add plugin lifecycle Load/unload/enable/disable New: packages/server/src/plugins/lifecycle.ts
Add plugin storage Dedicated key-value storage per plugin New: packages/server/src/plugins/storage.ts

Estimated Effort: 1-2 weeks


Phase 2: UI Extension Infrastructure (PRIMARY FOCUS)

Goal: Enable UI panels, sidebars, and tool renderers

Task Description Files to Modify
Create UI plugin SDK Expose UI APIs to plugins New: packages/ui/src/lib/plugin-sdk.ts
Implement panel registry Allow adding custom panels to right sidebar New: packages/ui/src/stores/panels.ts
Extend right panel Support dynamic tab types (not hardcoded) Modify: packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx
Implement sidebar widgets Allow adding sidebar content Modify: packages/ui/src/components/instance/shell/SessionSidebar.tsx
Tool renderer registry Formalize existing tool renderer system Modify: packages/ui/src/components/tool-call/renderers/index.ts
Command palette Allow plugins to register commands Modify: packages/ui/src/stores/commands.ts
Plugin settings UI Configure installed plugins New: Settings panel for plugins

Estimated Effort: 2-3 weeks

This is the key phase that enables the main value proposition: adding UI panels without source code.


Phase 3: Server-Side Tools & Agents

Goal: Enable custom tools and agents via plugins

Task Description Files to Modify
Define tool registration API Allow plugins to register tools Modify: packages/server/src/plugins/api.ts
Define agent interface Agent definition structure New: packages/server/src/plugins/agent.ts
Agent spawner Create agent instances from plugins Modify: Agent creation flow
Agent registry Track available agents New: packages/server/src/plugins/agent-registry.ts
Update UI agent picker Show plugin agents in UI Modify: packages/ui/src/components/session/
Background process integration Plugins can spawn long-running tasks Modify: packages/server/src/background-processes/manager.ts

Estimated Effort: 1-2 weeks


Phase 4: Developer Experience

Goal: Make it easy to create plugins

Task Description Files to Modify
Create plugin template codenomad plugin init command New: CLI command
Plugin CLI tools Install, list, enable, disable New: CLI commands
Documentation Plugin development guide New: Docs
VS Code extension Snippets for plugin development Future

Estimated Effort: 1 week


Phase 5: Research Plugin (Reference Implementation)

Goal: Build the first comprehensive plugin as validation

Task Description
Implement web search tool Using external search API
Implement URL fetch tool Content extraction
Create research agent Specialized system prompt
Build research panel Papers list, notes, citations in right sidebar
Build sidebar widget Quick search in left sidebar
Create search result renderer Rich card display for search results
Test end-to-end Full user flow

Estimated Effort: 2-3 weeks


Total Estimated Timeline

Phase Duration Focus
Phase 1: Foundation 1-2 weeks Server infrastructure
Phase 2: UI Extensions 2-3 weeks PRIMARY FOCUS
Phase 3: Tools & Agents 1-2 weeks Server agents
Phase 4: Developer Experience 1 week DX
Phase 5: Research Plugin 2-3 weeks Validation
Total 7-11 weeks
Click to expand - Alternatives Considered

Alternatives Considered

1. MCP (Model Context Protocol) Integration

Description: Use Anthropic's MCP as the plugin protocol instead of custom JSON-RPC

Pros:

  • Industry standard gaining traction
  • Built-in tool definitions and schema
  • Ecosystem of existing MCP servers

Cons:

  • Designed for Claude Desktop, not general-purpose
  • Less flexible for UI extensions
  • Would require significant abstraction layer

Decision: Rejected - MCP is too specific to Claude's tool calling model. CodeNomad should define its own protocol that works with any LLM/agent.


2. VS Code Extension-like Architecture

Description: Model plugins after VS Code extensions with extension host process

Pros:

  • Proven architecture with large ecosystem
  • Mature APIs for UI and backend
  • Well-documented patterns

Cons:

  • Heavy complexity for initial implementation
  • Requires significant infrastructure (extension host, API surface)
  • Overkill for CodeNomad's scale

Decision: Deferred - Consider in v2. Start with simpler process-based plugins first.


3. npm-Based Distribution Only

Description: Only support npm packages for plugin distribution

Pros:

  • Familiar developer experience
  • Built-in versioning and dependency management
  • npm registry infrastructure

Cons:

  • Barriers to entry (npm account, publishing)
  • No local development workflow
  • Security concerns with npm packages

Decision: Rejected - Support local folder plugins for development, add npm support later.


4. WebView-Only UI Extensions

Description: Only allow UI extensions via WebViews (no direct component API)

Pros:

  • Complete isolation (security)
  • Flexibility for plugin developers
  • No coupling to CodeNomad internals

Cons:

  • Limited integration (can't add to existing panels easily)
  • Harder to build cohesive experience
  • Communication overhead

Decision: Rejected - Offer both WebView panels AND direct component API for different use cases.


5. No Plugin System - Feature Requests Instead

Description: Handle all requests via GitHub issues and core team implementation

Pros:

  • Simpler initial development
  • Full control over quality
  • No maintenance burden

Cons:

  • Slow feature velocity
  • Limits ecosystem growth
  • Forces forking for niche needs
  • Community cannot contribute

Decision: Rejected - Does not align with goal of building an extensible platform.

Click to expand - Open Questions

Open Questions

1. Plugin Distribution

Q: Should plugins be distributed as npm packages, local folders, or both?

Options:

  • Local folders only - Simplest, good for development, no registry needed
  • npm packages - Better for distribution, requires publishing workflow
  • Both - Local for development, npm for distribution

Recommendation: Start with local folders, add npm support in Phase 4.


2. Version Compatibility

Q: Should plugins declare compatible CodeNomad versions?

Options:

  • No version checking - Risk of broken plugins
  • Semver range in manifest - Like VS Code's engines.vscode
  • Runtime capability checks - More flexible but complex

Recommendation: Use semver ranges like VS Code (engines.codenomad: ">=1.0.0 <2.0.0").


3. Security/Sandboxing

Q: Should plugins run in isolated processes from day 1?

Options:

  • Same process - Simpler, faster, but risky
  • Separate processes - More secure, more complex
  • Tiered - Built-in plugins same process, external isolated

Recommendation: Start simple (same process) with clear documentation, add isolation in v2.


4. Research Agent Specifics

Q: What specific research capabilities should the reference plugin include?

Options:

  • Basic - Web search, URL fetch, notes
  • Comprehensive - + citation management, PDF parsing, literature synthesis
  • Minimal MVP - Just agent + one tool, UI later

Recommendation: Build comprehensive to validate full plugin capabilities.


5. Plugin-to-Plugin Communication

Q: Should plugins be able to communicate with each other?

Options:

  • No isolation - Each plugin independent
  • Event bus only - Loose coupling via events
  • Direct API - Plugins can call each other's exposed APIs

Recommendation: Start with event bus only, add direct API if needed.


6. Marketplace

Q: Should CodeNomad have an official plugin marketplace?

Options:

  • No - Just local/folder distribution
  • Yes - Official marketplace with review process
  • Third-party - Let community build marketplace

Recommendation: Defer to v2. Start with documentation and GitHub-based distribution.

Click to expand - File References

File References

UI Files (PRIMARY FOCUS - Custom Panels)

These files need modification to enable custom panels without source code changes:

File Reason
packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx KEY - Currently hardcoded tabs; needs dynamic tab registry
packages/ui/src/components/instance/shell/SessionSidebar.tsx KEY - Needs widget registry for sidebar
packages/ui/src/components/tool-call/renderers/index.ts Already has renderer registry; formalize as plugin API
packages/ui/src/stores/commands.ts Needs command registry for plugin commands
packages/ui/src/stores/preferences.tsx Add plugin settings section
packages/ui/src/lib/keyboard-registry.ts Already exists; formalize as plugin API

Server Files (Backend Support)

File Reason
packages/server/src/plugins/channel.ts Already implements plugin event channel
packages/server/src/plugins/handlers.ts Already implements plugin event handlers
packages/server/src/server/routes/plugin.ts Plugin route definitions
packages/server/src/server/http-server.ts Plugin route registration
packages/server/src/background-processes/manager.ts Background process management
packages/server/src/events/bus.ts Event bus for inter-plugin communication

New Files to Create

File Purpose
packages/server/src/plugins/manifest.ts Plugin manifest schema
packages/server/src/plugins/loader.ts Plugin discovery and loading
packages/server/src/plugins/registry.ts Plugin registry
packages/server/src/plugins/api.ts Plugin API surface
packages/server/src/plugins/storage.ts Plugin storage
packages/server/src/plugins/agent.ts Agent definitions
packages/ui/src/lib/plugin-sdk.ts KEY - UI plugin SDK
packages/ui/src/stores/panels.ts KEY - Panel registry
packages/opencode-config/plugin/ (new) Research plugin

Key Technical Changes Required

  1. RightPanel.tsx - Replace hardcoded tab list with dynamic registry:
// BEFORE (hardcoded)
const tabs = ['changes', 'files', 'git', 'status']

// AFTER (dynamic)
const tabs = usePanelRegistry() // Returns registered panels
  1. SessionSidebar.tsx - Add widget section:
// Add widget area
<div class="sidebar-widgets">
  <For each={useWidgetRegistry()}>
    {(widget) => <WidgetPanel widget={widget} />}
  </For>
</div>
  1. New panels.ts - Panel registry store:
// New file
const [panels, setPanels] = createSignal<Map<string, UIPanel>>(new Map())

export function registerPanel(panel: UIPanel) { ... }
export function unregisterPanel(id: string) { ... }
export function usePanels() { return panels() }

Suggested Solutions

Near-term (Phase 1-3)

  1. Start with local folder plugins only, no npm distribution initially
  2. Use semver ranges for version compatibility
  3. Run plugins in same process initially (document security implications)
  4. Build comprehensive research plugin to validate architecture
  5. Add plugin-to-plugin events via existing EventBus

Future (v2+)

  1. Add npm package support for plugins
  2. Implement process isolation for untrusted plugins
  3. Create official marketplace with review process
  4. Consider VS Code-like extension host for better isolation
  5. Add plugin signatures and verification

Appendix A: Example Plugin Structure

my-plugin/
├── plugin.json           # Manifest
├── package.json          # Node package (optional)
├── src/
│   ├── index.ts          # Plugin entry
│   ├── agents/           # Custom agents
│   ├── tools/            # Custom tools
│   ├── routes/           # HTTP handlers
│   └── ui/               # UI components
├── dist/                 # Compiled JS
└── README.md             # Documentation

Appendix B: SDK Packages

// Server-side plugin SDK
@codenomad/server-plugin-sdk
├── types/manifest.ts     // Manifest types
├── types/plugin.ts      // Plugin interface
├── types/agent.ts       // Agent types
├── types/tool.ts        // Tool types
├── api/
   ├── storage.ts       // Storage API
   ├── events.ts        // Event API
   ├── processes.ts     // Process API
   └── http.ts          // HTTP client

// UI-side plugin SDK  
@codenomad/ui-plugin-sdk
├── types/panel.ts       // Panel types
├── types/widget.ts      // Widget types
├── api/
   ├── ui.ts            // UI API
   ├── events.ts        // Event API
   ├── shortcuts.ts     // Shortcut API
   └── settings.ts      // Settings API

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions