大数跨境

OpenCode v2.2.0 实战:插件能给桌面端画界面了——我写了个速记栏跑通

OpenCode v2.2.0 实战:插件能给桌面端画界面了——我写了个速记栏跑通 创见AI实验室
2026-10-28
2
导读:v2.2.0 里最有用的不是那 256 个 PR,而是插件能给桌面端画界面了。我从零写了个 4.2 kB 的速记栏装进本机跑通,四个坑日志里一个字没写。
oc22gui-01-cover.png

系列导航:本篇是 v2.2.0 的深读之二。总览在OpenCode v2 更新日志 03:v2.2.0 一篇抵十篇——桌面端重写、插件能画界面了

本文目录

  1. 先说结论:这版真正的变化是「桌面端可编程」
  2. 一个包,三种形态
  3. 我从零写了一个速记栏
  4. 四个坑,日志里一个字都没写
  5. 怎么确认界面到底渲染了没有
  6. 它的边界在哪
  7. 辅助线:两个新开的实验开关
  8. 想自己动手,从这三步开始

一、先说结论:这版真正的变化是「桌面端可编程」

v2.2.0 是 v2 至今最大的一版:223 条变更、256 个 PR、661 个 commit,桌面端独占 126 条(57%)。但这些数字容易让人看偏——真正会改变使用方式的是埋在 Highlights 第三条里的那句话:插件可以给桌面端画界面了。

以前 OpenCode 的插件只有两条路:挂服务端逻辑(工具、钩子、权限),或者在终端界面里占个槽位。想给桌面端加一栏、加个面板、加个按钮,没有官方路子。

v2.2.0 起,一个 npm 包可以再导出一个 ./gui-experimental,把自己的 React(准确说是 Solid)界面塞进 OpenCode Desktop 的任何窗口。

一个细节能说明官方的态度:桌面端自己的界面,跑在同一套 @opencode/plugin/gui SDK 上。Settings 里的插件列表分两层——能手动开关的配置插件,和默认隐藏、只在加载失败时才会现身的内置插件。第三方插件用的不是"二等公民接口",而是内置功能同款。

二、一个包,三种形态

oc22gui-02-shapes.png

三份代码写进同一个 package.json 的 exports,依赖都只有 @opencode/plugin 一个:

入口
跑在哪
干什么
"."
后台 server
工具、钩子、权限
"./tui"
终端界面
TUI 槽位
"./gui-experimental"
桌面端窗口 + 主进程
真界面

需要注意两个硬约束:

  • ./gui-experimental必须是一个预编译好的单 CJS 文件,官方 Vite preset 会把所有动态 import() 内联掉,因为应用不加载第二个 chunk。
  • server 入口挂掉,GUI 也不会加载。 文档原话。所以别想着"我只做界面,服务端部分随便写"。

三、我从零写了一个速记栏

既然是"能画界面",光看图没有说服力。我把本机桌面端升到 v2.2.0,真写了一个插件装进去——窗口底部常驻一条速记栏:输入、保存、翻历史、清空,外加两个命令面板入口。

图里那两条笔记是真录进去的:11:45 去吃午饭、8:00 开会。左下是历史面板展开后的样子,按记录时间倒序排,右侧就是那条常驻的输入栏。

最小骨架就两个文件:

{  "name": "opencode-scratchpad",  "type": "module",  "exports": {    ".": "./src/server.ts",    "./gui-experimental": "./dist/gui-experimental.cjs"  }}

构建配置一行导入:

import { defineConfig } from "vite"import { gui } from "@opencode/plugin/gui/vite" export default defineConfig({ plugins: [gui()] })

整个插件打出来 4.24 kB(gzip 2.0 kB),代码就三段:一个 Slot、两个 Command、一个 ctx.storage 存笔记。

Slot 一共只有四个位置:

槽位
位置
适合做
window.bottom
窗口内容下方通栏,toast 之上
常驻工具条、状态条
session.header
会话时间线标题行
用量、上下文占满度
session.location.menu
项目菜单项
项目级操作入口
session.panel.end
侧栏标签条末端
面板入口

我选 window.bottom,因为它是唯一一条横跨整个窗口、又不抢输入框位置的区域。

oc22gui-05-bar.png

除了 Slot,ctx 上还挂着这些:toast(角落提示)、dialogs(confirm / prompt / select / alert)、attention(系统通知)、ui(应用自有组件:Icon、Button、IconButton、Tooltip)、tabs(管 shell 标签页)、composer(读写输入框)、commands(注册命令)、client.rpc(回调自己插件的服务端方法)。

顺带一条省时间的事实:vite build 一跑,所有窗口和主进程会自动重载 GUI,不用重启应用。但 server 入口不会重启。

四、四个坑,日志里一个字都没写

这四个坑,官方 changelog 一个字没提,但它们吃掉了我这一晚上大部分时间。

① 窗口 setup 里没有 ctx.log。

日志 API 只存在于 main(ctx)(主进程)上下文。我第一版在 setup 里写了 ctx.log.write("info", ...),插件直接变成 Failed:

oc22gui-06-failed.png

报错是 TypeError: Cannot read properties of undefined (reading 'write')——它不会告诉你"这个 API 在这个上下文不存在",只会告诉你读 write 的时候炸了。

更麻烦的是第二层:setup 是整体失败。同一个函数里写在后面的 ctx.add(Slot, ...) 一次都没执行,所以界面上什么都不出现。你在外部看到的只是"插件没反应"。

② 配置字段是 plugins,不是 plugin。

v2 用复数。元素可以是包名字符串,也可以是 { package, options }。旧的单数数组不会报错,只会静默地不生效——不报错这点最坑。

③ 共享 server 不跟着配置热重载。

桌面端背后有一个常驻 server,插件是它加载的。改完 opencode.json,得先 opencode-cli service stop 再重启应用,否则一直跑旧的那一份。判断方法:日志里的 run id 从头到尾没变过,就说明没重载。

有意思的是两侧行为相反:GUI 随 vite build 自动重载,server 侧却咬死不动。

④ 插件目录不能带中文路径。

中文目录下,构建正常、vite build 也正常,但服务端就是不加载它——日志里连一条失败记录都没有,纯静默跳过。换成 ASCII 路径立刻好。

五、怎么确认界面到底渲染了没有

这一步花的时间最多,因为外部看"界面没出现"和"代码没跑"是一模一样的。窗口 setup 里能用的诊断手段只有两个:ctx.toast(界面可见)和 console.log(会落到桌面端自己的 renderer.log)。

想做像素级验证,可以临时铺一条纯洋红(#FF00FF)满宽横条:

oc22gui-07-magenta.png

然后扫截图里的洋红像素——有就是渲染通了,没有就是没渲染。配合 renderer.log 里的 [plugin] 报错,一秒钟就能分清"没加载"和"加载了但渲染抛错"。

还有一条反直觉的:渲染函数里抛错不会崩应用,只会什么都不画。所以别指望界面会给你任何报错提示。

六、它的边界在哪

用过之后,我的判断是:现在可以做日常小工具,不适合做要长期维护的产品。

  • ctx.storage 有持久化版本,但要传 effect 的 Schema;而 GUI bundle 禁止引入 effect/* 的子路径(构建阶段就报错,官方明说"打包副本不会共享应用实例")。所以实际可用的只有 memory —— 扛得住插件重载,扛不住窗口刷新。
  • 整套 GUI API 明确标着 experimental,-experimental 这个导出名就是官方在提醒你会变。
  • 桌面端只渲染自己服务器加载的插件的界面。其他服务器、web 来源的插件只列名字,不给界面,也不渲染远端内容。

七、辅助线:两个新开的实验开关

除了插件,这一版还在 设置 → 实验 里留了两个开关。它们不是大头,但和上面这条主线指向同一件事——桌面端的外观开始变得可配置、可替换。

oc22gui-08-icons.png

New icons:settings.general.newIcons。42 个新图形替换掉 70 个旧图标名,不是"重画一遍",是把 close / close-small / xmark-small / outline-xmark 四套变体收敛成一个 cross;方向图标只留 chevron-up,其余靠旋转 90/180/270 度。还有个手感细节:旧图标 small 会缩到 14px,新图标画在 16 网格所以保持 16——打开后小图标显得更实,不是错觉。

oc22gui-09-picker.png

New model picker:settings.general.newModelPicker。不是从老下拉框升级,而是在 v2.2.0 已经内置的可搜索对话框之上再设计一层:28px 行高、文本左缘一条线、搜索行去掉图标和底色、表头 sticky、以及一个 160px 的 variant 飞出层(带 ⌘F 收藏和用量统计)。

两个开关都是纯渲染层,默认关、即时生效、不碰数据,放心试。细节我单独写过一篇,见系列导航里的《设置里那两个实验开关》。

八、想自己动手,从这三步开始

  1. 确认版本:桌面端必须是 v2.2.0 以上(本机 OpenCode.exe 的 ProductVersion 是 2.2.0.0)。
  2. 抄我的骨架:package.json 双入口 + @opencode/plugin/gui/vite 的 gui() preset,bun add -d vite vite-plugin-solid solid-js,然后 bunx vite build。
  3. 注册到全局配置:~/.config/opencode/opencode.json 里 plugins: ["你的插件目录绝对路径"],目录用英文路径。改完先 opencode-cli service stop,再重启桌面端。

想在设置里看状态,去 设置 → 扩展 → 插件:每个插件带一个开关(关掉它的 GUI)、一个 Reload(重试加载)、以及加载失败时的第一条错误和 Show details。

我的项目留在工作区 oc-gui-plugin/,想接着改可以直接用。


互动问题:你希望 OpenCode 桌面端的哪个位置先被插件改造——底部状态条、会话标题行的上下文占用量,还是侧栏加一个真正的笔记面板?另外,如果官方下个版本把这套 GUI API 改到不兼容,你会先跟版本还是先停在能用的那一版?

精选系列





【声明】内容源于网络
0
0
创见AI实验室
创见AI实验室,我们不只是介绍工具,我们共同创造工作方式的未来。
内容 231
粉丝 0
创见AI实验室 创见AI实验室,我们不只是介绍工具,我们共同创造工作方式的未来。
总阅读22.6k
粉丝0
内容231