[FEATURE]: Window System as Foundation for UI Plugin Ecosystem #4028

Open
opened 2026-02-16 17:42:20 -05:00 by yindo · 2 comments
Owner

Originally created by @vtemian on GitHub (Dec 31, 2025).

Originally assigned to: @thdxr on GitHub.

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

The current OpenCode TUI architecture limits plugins to backend functionality (tools, commands).

Plugins cannot render persistent UI elements like file browsers, git panels, or search results. This document proposes a window system that decouples view rendering from routing, enabling a rich UI plugin ecosystem.


Current Plugin Capabilities

  • Register tools (AI-callable functions)
  • Register commands (slash commands)
  • Emit events
  • Render persistent UI panels
  • Create sidebars or split views
  • Display interactive trees, lists, forms

Plugins can offer IDE-like extensibility

  • NERDTree-style file browser - navigate project, open files in context
  • Git panel - stage, diff, commit without leaving OpenCode
  • Search results pane - persistent results, click to jump
  • MCP tool inspector - monitor tool calls in real-time

None of these is possible with single-view routing.


Current Architecture

flowchart TD
    RP[RouteProvider] --> SW{route.type?}
    SW -->|home| H[Home Component]
    SW -->|session| S[Session Component]

Limitations:

  1. One view at a time
  2. Route owns the entire screen
  3. No mechanism for plugins to inject views
  4. Navigation replaces everything

Proposed Architecture

flowchart TD
    subgraph Plugins
        P1[File Tree Plugin]
        P2[Git Plugin]
    end
    
    subgraph ViewRegistry
        V1["home → Home"]
        V2["session:* → Session"]
        V3["plugin:filetree → FileTree"]
        V4["plugin:git → GitPanel"]
    end
    
    subgraph LayoutProvider
        LT[Layout Tree]
    end
    
    P1 -->|register| V3
    P2 -->|register| V4
    
    LT --> LR[LayoutRenderer]
    LR -->|resolve viewID| ViewRegistry
    LR --> W1[Window: plugin:filetree]
    LR --> W2[Window: session:abc]
    LR --> W3[Window: plugin:git]

Layout Tree Structure

graph TD
    Root[Split: horizontal]
    Root --> Left[Window<br/>viewID: plugin:filetree]
    Root --> Right[Split: vertical]
    Right --> Top[Window<br/>viewID: session:abc]
    Right --> Bottom[Window<br/>viewID: plugin:git]

Plugin View API

Registering a View

// In plugin initialization
import { ViewRegistry, View } from "@opencode/plugin"

// Tree view (file browser, symbol outline)
ViewRegistry.register("my-plugin:files", View.Tree.create({
  id: "my-plugin:files",
  title: "Project Files",
  nodes: [
    { id: "src", label: "src/", icon: "📁", children: [...] },
    { id: "readme", label: "README.md", icon: "📄", children: [] }
  ]
}))
// List view (search results, command palette)
ViewRegistry.register("my-plugin:search", View.List.create({
  id: "my-plugin:search", 
  title: "Search Results",
  items: [...]
}))

View Types

Type Use Case Features
Tree File browsers, symbol outlines Hierarchical, expandable nodes
List Search results, fuzzy finders Flat, searchable, selectable
Text Logs, previews, docs Styled, scrollable, read-only
Form Settings, input dialogs Fields, validation, submit

Opening a Plugin View

// Plugin requests to open its view
opencode.layout.open("my-plugin:files", { 
  position: "left",   // left, right, top, bottom
  size: 0.25          // 25% of screen
})
// Or user opens via command
/open my-plugin:files

Data Flow

sequenceDiagram
    participant U as User
    participant K as Keybind Handler
    participant L as LayoutProvider
    participant R as LayoutRenderer
    participant V as ViewRegistry
    participant C as Component
    U->>K: Ctrl+X |
    K->>L: splitVertical("home")
    L->>L: Update tree state
    L->>R: Re-render
    R->>V: resolve("home")
    V->>R: Home component
    R->>C: Render in new pane

User Experience

Keybindings (Vim-style)

Action Keybind
Split vertical Ctrl+X |
Split horizontal Ctrl+X -
Navigate Ctrl+X h/j/k/l
Close pane Ctrl+X q
Focus plugin view Ctrl+X 1/2/3...

Example Layout

┌─────────────┬─────────────────────────────┐
│ File Tree   │ Session: "Fix auth bug"     │
│ (plugin)    │                             │
│             │ > Review src/auth.ts        │
│ 📁 src/      │                             │
│   📄 auth.ts │ [AI response...]            │
│   📄 user.ts │                             │
│ 📁 tests/    │                             │
├─────────────┼─────────────────────────────┤
│ Git Status (plugin)                       │
│ M src/auth.ts  [Stage] [Diff] [Revert]    │
└───────────────────────────────────────────┘

Why Not Alternatives?

Approach Problem
Modals/dialogs Block interaction, not persistent, can't reference while typing
Fixed sidebars Not flexible, each sidebar = special case, plugins can't add their own
Plugins render anywhere No consistency, layout conflicts, focus management nightmare
Window system Consistent primitives, user controls layout, plugins just provide content

Conclusion
The window system transforms OpenCode from a chat application into an extensible platform.

By decoupling views from routing and providing typed view primitives, we enable a plugin ecosystem comparable to Vim, VS Code, or Emacs.

Originally created by @vtemian on GitHub (Dec 31, 2025). Originally assigned to: @thdxr on GitHub. ### Feature hasn't been suggested before. - [x] I have verified this feature I'm about to request hasn't been suggested before. ### Describe the enhancement you want to request The current OpenCode TUI architecture limits plugins to backend functionality (tools, commands). Plugins cannot render persistent UI elements like file browsers, git panels, or search results. This document proposes a window system that decouples view rendering from routing, enabling a rich UI plugin ecosystem. --- #### Current Plugin Capabilities - ✅ Register tools (AI-callable functions) - ✅ Register commands (slash commands) - ✅ Emit events - ❌ Render persistent UI panels - ❌ Create sidebars or split views - ❌ Display interactive trees, lists, forms #### Plugins can offer IDE-like extensibility - NERDTree-style file browser - navigate project, open files in context - Git panel - stage, diff, commit without leaving OpenCode - Search results pane - persistent results, click to jump - MCP tool inspector - monitor tool calls in real-time None of these is possible with single-view routing. --- Current Architecture ```mermaid flowchart TD RP[RouteProvider] --> SW{route.type?} SW -->|home| H[Home Component] SW -->|session| S[Session Component] ``` Limitations: 1. One view at a time 2. Route owns the entire screen 3. No mechanism for plugins to inject views 4. Navigation replaces everything --- Proposed Architecture ```mermaid flowchart TD subgraph Plugins P1[File Tree Plugin] P2[Git Plugin] end subgraph ViewRegistry V1["home → Home"] V2["session:* → Session"] V3["plugin:filetree → FileTree"] V4["plugin:git → GitPanel"] end subgraph LayoutProvider LT[Layout Tree] end P1 -->|register| V3 P2 -->|register| V4 LT --> LR[LayoutRenderer] LR -->|resolve viewID| ViewRegistry LR --> W1[Window: plugin:filetree] LR --> W2[Window: session:abc] LR --> W3[Window: plugin:git] ``` --- Layout Tree Structure ```mermaid graph TD Root[Split: horizontal] Root --> Left[Window<br/>viewID: plugin:filetree] Root --> Right[Split: vertical] Right --> Top[Window<br/>viewID: session:abc] Right --> Bottom[Window<br/>viewID: plugin:git] ``` --- Plugin View API Registering a View ```typescript // In plugin initialization import { ViewRegistry, View } from "@opencode/plugin" // Tree view (file browser, symbol outline) ViewRegistry.register("my-plugin:files", View.Tree.create({ id: "my-plugin:files", title: "Project Files", nodes: [ { id: "src", label: "src/", icon: "📁", children: [...] }, { id: "readme", label: "README.md", icon: "📄", children: [] } ] })) // List view (search results, command palette) ViewRegistry.register("my-plugin:search", View.List.create({ id: "my-plugin:search", title: "Search Results", items: [...] })) ``` View Types | Type | Use Case | Features | |------|----------|----------| | Tree | File browsers, symbol outlines | Hierarchical, expandable nodes | | List | Search results, fuzzy finders | Flat, searchable, selectable | | Text | Logs, previews, docs | Styled, scrollable, read-only | | Form | Settings, input dialogs | Fields, validation, submit | Opening a Plugin View ```typescript // Plugin requests to open its view opencode.layout.open("my-plugin:files", { position: "left", // left, right, top, bottom size: 0.25 // 25% of screen }) ``` ```bash // Or user opens via command /open my-plugin:files ``` --- Data Flow ```mermaid sequenceDiagram participant U as User participant K as Keybind Handler participant L as LayoutProvider participant R as LayoutRenderer participant V as ViewRegistry participant C as Component U->>K: Ctrl+X | K->>L: splitVertical("home") L->>L: Update tree state L->>R: Re-render R->>V: resolve("home") V->>R: Home component R->>C: Render in new pane ``` --- User Experience Keybindings (Vim-style) | Action | Keybind | |--------|---------| | Split vertical | Ctrl+X \| | | Split horizontal | Ctrl+X - | | Navigate | Ctrl+X h/j/k/l | | Close pane | Ctrl+X q | | Focus plugin view | Ctrl+X 1/2/3... | Example Layout ```bash ┌─────────────┬─────────────────────────────┐ │ File Tree │ Session: "Fix auth bug" │ │ (plugin) │ │ │ │ > Review src/auth.ts │ │ 📁 src/ │ │ │ 📄 auth.ts │ [AI response...] │ │ 📄 user.ts │ │ │ 📁 tests/ │ │ ├─────────────┼─────────────────────────────┤ │ Git Status (plugin) │ │ M src/auth.ts [Stage] [Diff] [Revert] │ └───────────────────────────────────────────┘ ``` --- Why Not Alternatives? | Approach | Problem | |----------|---------| | Modals/dialogs | Block interaction, not persistent, can't reference while typing | | Fixed sidebars | Not flexible, each sidebar = special case, plugins can't add their own | | Plugins render anywhere | No consistency, layout conflicts, focus management nightmare | | Window system | Consistent primitives, user controls layout, plugins just provide content | --- Conclusion The window system transforms OpenCode from a chat application into an extensible platform. By decoupling views from routing and providing typed view primitives, we enable a plugin ecosystem comparable to Vim, VS Code, or Emacs.
yindo added the opentuidiscussion labels 2026-02-16 17:42:20 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Dec 31, 2025):

This issue might be a duplicate of existing issues. Please check:

  • #6330: Generic UI Intent Channel for cross-client plugin-driven UX - proposes a declarative UI framework for plugins
  • #6032: Nerdtree like session manager - requests a tree-based UI component for managing sessions
  • #6335: Improve Agent Navigation: Tree View, Alphabetical Sorting, and Active/Inactive Filtering - requests tree view navigation for agents

All three issues are related to improving plugin UI capabilities and creating persistent, interactive UI panels in OpenCode. While this issue proposes a comprehensive window system architecture, the related issues address specific use cases that would benefit from (or complement) this proposal.

Feel free to ignore if this is intentionally a more fundamental/architectural proposal.

@github-actions[bot] commented on GitHub (Dec 31, 2025): This issue might be a duplicate of existing issues. Please check: - #6330: Generic UI Intent Channel for cross-client plugin-driven UX - proposes a declarative UI framework for plugins - #6032: Nerdtree like session manager - requests a tree-based UI component for managing sessions - #6335: Improve Agent Navigation: Tree View, Alphabetical Sorting, and Active/Inactive Filtering - requests tree view navigation for agents All three issues are related to improving plugin UI capabilities and creating persistent, interactive UI panels in OpenCode. While this issue proposes a comprehensive window system architecture, the related issues address specific use cases that would benefit from (or complement) this proposal. Feel free to ignore if this is intentionally a more fundamental/architectural proposal.
Author
Owner

@ai-llt commented on GitHub (Jan 19, 2026):

OpenCode 插件 UI 功能请求 - 实际用例 / OpenCode Plugin UI Feature Request - Real Use Cases

我:整个过程是我和LLM共同测试的,我想保留LLM的想法,只做一些小幅度的修改。我手写的部分会特别标注一下。
Me: The entire process was tested by me and the LLM together. I want to preserve the LLM's ideas and only make minor modifications. The parts I wrote by hand will be specially marked.

背景

我们进行了详细的测试,确认了 Issue #6521 中描述的插件 UI 限制。我们开发了一个 edit_replica 插件来扩展原生 edit 工具的功能。

我:我最初的想法是每次修改LLM能够看到修改的内容,而不用再去read一次文件提升速度,我不知道你们有没有遇见过edit经常会将代码块代码结构给改坏,这时候单从LSP去提示已经不足以让LLM意识到问题在哪,于是我想:如果LLM能直接看到代码的diff不就能知道问题在哪了吗?也是从这里我意识到显示在TUI面板上的diff结果LLM是看不到的。
Me: My initial idea was that each time the LLM makes modifications, it could see the changes directly without having to read the file again, thereby improving speed. I don't know if you've encountered situations where edits often mess up the structure of code blocks—in such cases, relying solely on LSP hints isn't enough for the LLM to realize where the problem lies. That's when I thought: if the LLM could directly see the code diff, wouldn't it immediately understand the issue? It was from this realization that I understood the diff results displayed on the TUI panel are not visible to the LLM.

我: 于是我想通过插件的方式增强Edit的功能于是发现了一下需求。
Me: So I wanted to enhance Edit's functionality through plugins, and I discovered the following requirements.

测试结果

测试 1: metadata() 方法

// 插件中调用 metadata()
context.metadata({
  title: "Diff: filename",
  metadata: { diff: "..." }
})

结果:

  • metadata() 方法存在且可调用
  • UI 没有显示任何变化
  • ℹ️ 只会打印日志

测试 2: TUI Toast

// 尝试通过 SDK 触发 toast
await client.tui.showToast({
  body: { message: "文件已修改", variant: "success" }
})

结果:

  • 插件上下文中无法访问 client.tui
  • ⚠️ 插件无法主动触发 TUI 事件

结论

确认的限制:

  • 插件无法渲染 persistent UI panels
  • 插件无法显示 diff 信息
  • 插件无法创建 sidebars 或 split views
  • ℹ️ 插件只能返回字符串给 LLM

实际用例:edit_replica 插件

我们开发了一个插件来测试diff display的功能:
功能:

  • 完整复制原生 edit 工具的 9 种匹配策略
  • 返回完整的 unified diff 给 LLM
  • 提供格式化的变更统计
  • 机器可读元数据
    代码示例:
edit_replica({
  filePath: "src/auth.ts",
  oldString: `function login(): void {
  }`,
  newString: `function login(): void {
    const token = validateToken();
    if (!token) throw new Error('Invalid');
  }`
})

当前输出:

✅ 文件编辑成功 | src/auth.ts
📊 变更统计:
   +3 行增加
   -1 行删除
🔍 代码差异:
```diff
@@ -10,3 +10,5 @@
   }
   
+  const token = validateToken();
+  if (!token) throw new Error('Invalid');
   return true;

问题:

  • LLM 可以看到完整的 diff
  • 用户 UI 中不显示 diff panel
  • ℹ️ 用户需要阅读 LLM 的文本来了解变更

需求

迫切需要: 插件能够显示 UI diff panel
使用场景:

  1. 开发者想可视化查看代码变更
  2. 代码 review 时需要清晰的 diff 显示
  3. 与原生 edit 工具保持一致的用户体验

建议的解决方案

基于 Issue #6521 的 Window System 设计,我们建议:

优先级 1:插件 Diff Panel

允许插件注册并显示 diff panel:

// 插件中
ViewRegistry.register("edit-replica:diff", View.Diff.create({
  id: "edit-replica:diff",
  title: "Diff Preview",
  diff: unifiedDiff,  // 传入 diff 字符串
}))
// 显示 panel
opencode.layout.open("edit-replica:diff", {
  position: "right",
  size: 0.4
})

优先级 2:简化版 diff display

提供简化的 API 专门用于显示 diff:

// 插件中
ctx.showDiff({
  title: "File Modified",
  filepath: "src/auth.ts",
  diff: unifiedDiff,
  position: "right"
})

建议

支持 Issue #6521 的 Window System 提案,并提供简化的 API 专门用于显示 diff:
ctx.showDiff({
title: "File Modified",
filepath: "src/auth.ts",
diff: unifiedDiff,
position: "right"
})
这将大大提升插件生态系统的实用价值。
感谢官方团队的努力!期待这个功能的实现。

影响

如果实现:

  • 插件可以提供与原生工具相同的用户体验
  • 提升插件生态系统的实用性
  • 吸引更多开发者创建插件
    如果不实现:
  • ⚠️ 插件功能受限,只能作为后端工具
  • ⚠️ 限制了插件的实用价值
  • ⚠️ 开发者可能选择 fork OpenCode 而不是写插件

参考

  • Issue #6521: Window System as Foundation for UI Plugin Ecosystem
  • Issue #8219: ToolContext type missing metadata() method (已修复)
  • Issue #4454: Proposal: OSC 9 terminal notifications for TUI toasts

我们完全支持 Issue #6521 的 Window System 提案,并提供了这个实际用例来说明为什么插件需要 UI 能力。
感谢官方团队的努力!期待这个功能的实现。

@ai-llt commented on GitHub (Jan 19, 2026): # OpenCode 插件 UI 功能请求 - 实际用例 / OpenCode Plugin UI Feature Request - Real Use Cases 我:整个过程是我和LLM共同测试的,我想保留LLM的想法,只做一些小幅度的修改。我手写的部分会特别标注一下。 Me: The entire process was tested by me and the LLM together. I want to preserve the LLM's ideas and only make minor modifications. The parts I wrote by hand will be specially marked. ## 背景 我们进行了详细的测试,确认了 Issue #6521 中描述的插件 UI 限制。我们开发了一个 `edit_replica` 插件来扩展原生 `edit` 工具的功能。 我:我最初的想法是每次修改LLM能够看到修改的内容,而不用再去read一次文件提升速度,我不知道你们有没有遇见过edit经常会将代码块代码结构给改坏,这时候单从LSP去提示已经不足以让LLM意识到问题在哪,于是我想:如果LLM能直接看到代码的diff不就能知道问题在哪了吗?也是从这里我意识到显示在TUI面板上的diff结果LLM是看不到的。 Me: My initial idea was that each time the LLM makes modifications, it could see the changes directly without having to read the file again, thereby improving speed. I don't know if you've encountered situations where edits often mess up the structure of code blocks—in such cases, relying solely on LSP hints isn't enough for the LLM to realize where the problem lies. That's when I thought: if the LLM could directly see the code diff, wouldn't it immediately understand the issue? It was from this realization that I understood the diff results displayed on the TUI panel are not visible to the LLM. 我: 于是我想通过插件的方式增强Edit的功能于是发现了一下需求。 Me: So I wanted to enhance Edit's functionality through plugins, and I discovered the following requirements. ## 测试结果 ### 测试 1: metadata() 方法 ```typescript // 插件中调用 metadata() context.metadata({ title: "Diff: filename", metadata: { diff: "..." } }) ``` **结果**: - ✅ `metadata()` 方法存在且可调用 - ❌ **UI 没有显示任何变化** - ℹ️ 只会打印日志 ### 测试 2: TUI Toast ```typescript // 尝试通过 SDK 触发 toast await client.tui.showToast({ body: { message: "文件已修改", variant: "success" } }) ``` **结果**: - ❌ 插件上下文中无法访问 `client.tui` - ⚠️ 插件无法主动触发 TUI 事件 ### 结论 **确认的限制**: - ❌ 插件无法渲染 persistent UI panels - ❌ 插件无法显示 diff 信息 - ❌ 插件无法创建 sidebars 或 split views - ℹ️ 插件只能返回字符串给 LLM ## 实际用例:edit_replica 插件 我们开发了一个插件来测试diff display的功能: **功能**: - ✅ 完整复制原生 edit 工具的 9 种匹配策略 - ✅ 返回完整的 unified diff 给 LLM - ✅ 提供格式化的变更统计 - ✅ 机器可读元数据 **代码示例**: ```typescript edit_replica({ filePath: "src/auth.ts", oldString: `function login(): void { }`, newString: `function login(): void { const token = validateToken(); if (!token) throw new Error('Invalid'); }` }) ``` **当前输出**: ``` ✅ 文件编辑成功 | src/auth.ts 📊 变更统计: +3 行增加 -1 行删除 🔍 代码差异: ```diff @@ -10,3 +10,5 @@ } + const token = validateToken(); + if (!token) throw new Error('Invalid'); return true; ``` **问题**: - ✅ LLM 可以看到完整的 diff - ❌ **用户 UI 中不显示 diff panel** - ℹ️ 用户需要阅读 LLM 的文本来了解变更 ## 需求 **迫切需要**: 插件能够显示 UI diff panel **使用场景**: 1. 开发者想可视化查看代码变更 2. 代码 review 时需要清晰的 diff 显示 3. 与原生 edit 工具保持一致的用户体验 ## 建议的解决方案 基于 Issue #6521 的 Window System 设计,我们建议: ### 优先级 1:插件 Diff Panel 允许插件注册并显示 diff panel: ```typescript // 插件中 ViewRegistry.register("edit-replica:diff", View.Diff.create({ id: "edit-replica:diff", title: "Diff Preview", diff: unifiedDiff, // 传入 diff 字符串 })) // 显示 panel opencode.layout.open("edit-replica:diff", { position: "right", size: 0.4 }) ``` ### 优先级 2:简化版 diff display 提供简化的 API 专门用于显示 diff: ```typescript // 插件中 ctx.showDiff({ title: "File Modified", filepath: "src/auth.ts", diff: unifiedDiff, position: "right" }) ``` ## 建议 支持 Issue #6521 的 Window System 提案,并提供简化的 API 专门用于显示 diff: ctx.showDiff({ title: "File Modified", filepath: "src/auth.ts", diff: unifiedDiff, position: "right" }) 这将大大提升插件生态系统的实用价值。 感谢官方团队的努力!期待这个功能的实现。 ## 影响 **如果实现**: - ✅ 插件可以提供与原生工具相同的用户体验 - ✅ 提升插件生态系统的实用性 - ✅ 吸引更多开发者创建插件 **如果不实现**: - ⚠️ 插件功能受限,只能作为后端工具 - ⚠️ 限制了插件的实用价值 - ⚠️ 开发者可能选择 fork OpenCode 而不是写插件 ## 参考 - Issue #6521: Window System as Foundation for UI Plugin Ecosystem - Issue #8219: ToolContext type missing metadata() method (已修复) - Issue #4454: Proposal: OSC 9 terminal notifications for TUI toasts --- 我们完全支持 Issue #6521 的 Window System 提案,并提供了这个实际用例来说明为什么插件需要 UI 能力。 感谢官方团队的努力!期待这个功能的实现。
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#4028