系列导航:本篇是 v2.2.0 的深读之二。总览在OpenCode v2 更新日志 03:v2.2.0 一篇抵十篇——桌面端重写、插件能画界面了
本文目录
- 先说结论:这版真正的变化是「桌面端可编程」
- 一个包,三种形态
- 我从零写了一个速记栏
- 四个坑,日志里一个字都没写
- 怎么确认界面到底渲染了没有
- 它的边界在哪
- 辅助线:两个新开的实验开关
- 想自己动手,从这三步开始
一、先说结论:这版真正的变化是「桌面端可编程」
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 里的插件列表分两层——能手动开关的配置插件,和默认隐藏、只在加载失败时才会现身的内置插件。第三方插件用的不是"二等公民接口",而是内置功能同款。
二、一个包,三种形态
三份代码写进同一个 package.json 的 exports,依赖都只有 @opencode/plugin 一个:
|
|
|
|
|---|---|---|
"." |
|
|
"./tui" |
|
|
"./gui-experimental" |
|
|
需要注意两个硬约束:
./gui-experimental必须是一个预编译好的单 CJS 文件,官方 Vite preset 会把所有动态import()内联掉,因为应用不加载第二个 chunk。- server 入口挂掉,GUI 也不会加载。 文档原话。所以别想着"我只做界面,服务端部分随便写"。
三、我从零写了一个速记栏
既然是"能画界面",光看图没有说服力。我把本机桌面端升到 v2.2.0,真写了一个插件装进去——窗口底部常驻一条速记栏:输入、保存、翻历史、清空,外加两个命令面板入口。
图里那两条笔记是真录进去的:11:45 去吃午饭、8:00 开会。左下是历史面板展开后的样子,按记录时间倒序排,右侧就是那条常驻的输入栏。
最小骨架就两个文件:
构建配置一行导入:
整个插件打出来 4.24 kB(gzip 2.0 kB),代码就三段:一个 Slot、两个 Command、一个 ctx.storage 存笔记。
Slot 一共只有四个位置:
|
|
|
|
|---|---|---|
window.bottom |
|
|
session.header |
|
|
session.location.menu |
|
|
session.panel.end |
|
|
我选 window.bottom,因为它是唯一一条横跨整个窗口、又不抢输入框位置的区域。
除了 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:
报错是 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)满宽横条:
然后扫截图里的洋红像素——有就是渲染通了,没有就是没渲染。配合 renderer.log 里的 [plugin] 报错,一秒钟就能分清"没加载"和"加载了但渲染抛错"。
还有一条反直觉的:渲染函数里抛错不会崩应用,只会什么都不画。所以别指望界面会给你任何报错提示。
六、它的边界在哪
用过之后,我的判断是:现在可以做日常小工具,不适合做要长期维护的产品。
ctx.storage有持久化版本,但要传 effect 的 Schema;而 GUI bundle 禁止引入effect/*的子路径(构建阶段就报错,官方明说"打包副本不会共享应用实例")。所以实际可用的只有memory—— 扛得住插件重载,扛不住窗口刷新。- 整套 GUI API 明确标着 experimental,
-experimental这个导出名就是官方在提醒你会变。 - 桌面端只渲染自己服务器加载的插件的界面。其他服务器、web 来源的插件只列名字,不给界面,也不渲染远端内容。
七、辅助线:两个新开的实验开关
除了插件,这一版还在 设置 → 实验 里留了两个开关。它们不是大头,但和上面这条主线指向同一件事——桌面端的外观开始变得可配置、可替换。
New icons:settings.general.newIcons。42 个新图形替换掉 70 个旧图标名,不是"重画一遍",是把 close / close-small / xmark-small / outline-xmark 四套变体收敛成一个 cross;方向图标只留 chevron-up,其余靠旋转 90/180/270 度。还有个手感细节:旧图标 small 会缩到 14px,新图标画在 16 网格所以保持 16——打开后小图标显得更实,不是错觉。
New model picker:settings.general.newModelPicker。不是从老下拉框升级,而是在 v2.2.0 已经内置的可搜索对话框之上再设计一层:28px 行高、文本左缘一条线、搜索行去掉图标和底色、表头 sticky、以及一个 160px 的 variant 飞出层(带 ⌘F 收藏和用量统计)。
两个开关都是纯渲染层,默认关、即时生效、不碰数据,放心试。细节我单独写过一篇,见系列导航里的《设置里那两个实验开关》。
八、想自己动手,从这三步开始
- 确认版本:桌面端必须是 v2.2.0 以上(本机
OpenCode.exe的 ProductVersion 是 2.2.0.0)。 - 抄我的骨架:
package.json双入口 +@opencode/plugin/gui/vite的gui()preset,bun add -d vite vite-plugin-solid solid-js,然后bunx vite build。 - 注册到全局配置:
~/.config/opencode/opencode.json里plugins: ["你的插件目录绝对路径"],目录用英文路径。改完先opencode-cli service stop,再重启桌面端。
想在设置里看状态,去 设置 → 扩展 → 插件:每个插件带一个开关(关掉它的 GUI)、一个 Reload(重试加载)、以及加载失败时的第一条错误和 Show details。
我的项目留在工作区 oc-gui-plugin/,想接着改可以直接用。
互动问题:你希望 OpenCode 桌面端的哪个位置先被插件改造——底部状态条、会话标题行的上下文占用量,还是侧栏加一个真正的笔记面板?另外,如果官方下个版本把这套 GUI API 改到不兼容,你会先跟版本还是先停在能用的那一版?






