ARTICLE · 1271351
OpenCode 2.2.0:插件能改界面了,升级前有三处会断
2.2.0 是 10 月 27 日发的,更新日志一百多条,桌面端占了大半。值得看的变化只有一个:插件第一次能改你看到的东西——侧栏、设置页、输入框上方的胶囊。同一版里还有三个 breaking change,升级前得先看。

01以前的插件,只能在别人的界面里加东西
OpenCode 的插件一直分两层:Server 那层加工具、钩子和命令,TUI 那层加终端里的路由和面板。桌面端原本是另一套:你能装插件,但界面长什么样,插件说不上话。
2.2.0 加了一个 GUI part,入口是包里的 ./gui-experimental。装上以后,插件可以往里塞这些东西:
Panel | |
ComposerPill | |
SettingsPage | |
CommandSlot、TitlebarItem |
官方示例里有个挺实用的用法:把当前分支的 PR 状态做成一颗胶囊,检查挂了就变红,点开是 PR 列表。
意义不在“多了几个位置”。以前插件是给 agent 加能力,现在它能改工作流的入口——你不用切到浏览器看 CI,胶囊就在输入框上面。
这套 GUI API 现在标着 experimental,官方明确说版本之间可能变。想拿它做正式产品,得接受返工。
•••
02一个文件,两个进程
这是 2.2.0 里最容易被忽略、也最容易踩坑的设计。
一个包可以同时带 Server、TUI、GUI 三个部分,靠 package.json 的 exports 区分。GUI 插件的同一个文件会被读两次:setup 在每一个桌面窗口里跑,main 在 Electron 主进程里跑一次。两个进程各读整个文件,另一边专属的模块在此时是惰性占位符——窗口里 electron 和 Node 内置模块是空壳,主进程里 solid-js 是空壳。
绕的原因是权限不一样。窗口能画界面,但不能给主进程塞代码。文档原话是「A window cannot supply main-process code」。主进程手里有 Electron 和 Node,窗口只负责渲染,这条线不能破。
代价落在打包上。./gui-experimental 必须是一个提前打好的 CommonJS 文件,不能有第二个 chunk,因为应用不会再加载第二份代码。Vite 预设会把动态 import 全部内联,把 Electron 和 Node 内置模块留在外面,其余依赖打进包里。你要是 import 了不在白名单里的模块,构建直接失败,报错原文是 effect/Schema is not a module the OpenCode app provides to plugin GUIs。这条报错本身就说明了态度:宿主在收窄自己的信任边界。
还有个细节:插件 id 不能乱起。和内置插件重名、或者挂在 opencode 命名空间下,GUI 直接不加载,设置页里只留一行“未加载”。
•••
03改行为,不用重写 prompt
ctx.session.instructions.entry 这一组接口,比界面更值得看:put、list、remove。
它解决的是一个一直在花钱的问题。system prompt 在请求最前面,改一个字,它后面的整段缓存就作废,下一轮按未命中重新计费。所以过去插件想给 agent 加一句“这轮注意点 X”,要么动系统提示吃一次全量重算,要么等用户开新会话。
2.2.0 换了个做法:把改动做成一条 keyed 条目,在下一步请求时作为小增量追加进去。条目能活过上下文压缩和回滚,也不会额外触发一轮模型调用。插件可以随时 put、list、remove,改动只影响后面的步骤。
“改行为最贵的部分,从来不是那几句话,是把前面的上下文重算一遍。”
同一版还加了 prompt cache TTL,可以按 provider 和 model 分别设。这个配置看着琐碎,实际很值钱:会话闲置一段时间再回来,缓存可能已经过期,你以为还在命中,账单上按未命中算。把 TTL 调到接近你的使用节奏,比换便宜模型更直接。
结论对写插件的人最有价值的是这一条,不是 GUI。GUI 管插件长什么样,instruction entry 管它能不能便宜地改 agent 的行为。
•••
04升级前,三处会断
更新日志里写得清楚,但散在三个小节,容易漏。
@opencode/ai | ||
第一条长这样:
-import { AI } from "@opencode/ai/promise"+import { AI } from "@opencode/ai"/promise 这个入口没了,Effect API 从根路径挪到 @opencode/ai/effect,LLM 那一行同理。
前两条改完就没事:一个换 import,一个换调用方式。第三条会真的报错,而且只在装插件的时候报。
第三条的具体改法,文档给了三句话:renderBefore、renderAfter、onLifecyclePass 从类字段挪进构造函数;用反射加的钩子要调 refreshHooks();defineNativeIntegration() 换成静态的 nativeIntegration 声明。
•••
05顺手值得开的几个
就三个,都有明确理由:
- •
桌面端的待办工具:之前在 TUI 是实验开关,2.2.0 在桌面端按 opt-in 恢复了,任务多了能看见进度 - •
Focus / Pro 两套布局:Focus 收掉侧栏,Pro 留出多面板,写代码和读代码是两个场景 - •
Reduce motion:动画少了,长会话里风扇声也小一点
其余的(新的模型选择器、Mermaid 渲染加速、从任意消息回退或分叉)属于用到了会舒服,用不到也不影响。
•••
插件能改界面之后,一个问题会变得难回答:一个插件到底算“扩展”,还是算“另一个应用”?
你装过的插件里,有几个改过你的操作习惯,而不是只在菜单里多了一个按钮?















































































