Skip to content

第 12 章 · 高级扩展与自动化

第 5 章讲的 Agents / Commands / Skills 解决「固化提示与姿态」,但有些需求它们做不到:定义全新的工具、从代码层面拦截事件、在 CI 里无人值守调用、让 Zed 等编辑器接入。本章讲 OpenCode 更底层的五类能力:Plugins、Policies、无头模式、References、ACP。

本章包含 5 个知识点:

  1. Plugins:用代码扩展 OpenCode
  2. Policies:只收紧、不放宽的集中策略
  3. 无头模式:opencode run 与自动化 / CI
  4. References:按名引用项目外目录
  5. ACP:让编辑器 / 图形客户端接入

知识点 1:Plugins:用代码扩展 OpenCode

① 定位

当 Markdown 定义的 Agent / Command / Skill 都不够——你需要运行真正的代码、注册自定义工具、拦截运行时事件——就轮到 Plugin。它是五层扩展机制中最底层、最强的一个。

② 是什么(What)

Plugin 是用 TypeScript / JavaScript 写的代码模块,在 opencode.jsoncplugins 数组中声明:

jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "plugins": [
    "opencode-acme-plugin",                 // 已发布包
    "opencode-acme-plugin@1.2.0",           // 指定版本
    "@acme/opencode-plugin",                // scoped 包
    "./plugins/local",                      // 本地目录(相对配置文件解析)
    "../shared/plugin.ts",                  // 直接引用 .ts/.js 文件
    {
      "package": "@acme/opencode-plugin",   // 带选项
      "options": { "agent": "reviewer", "strict": true }
    }
  ]
}

两种自动发现方式(无需写进配置):

  • .ts / .js 文件或插件包目录直接放进 .opencode/plugins/(项目级);
  • 全局插件放在 ~/.config/opencode/plugins/

注意:项目根的 plugins/(不在 .opencode/ 下)不会被自动发现,要么显式配置、要么挪进 .opencode/。多个配置文件的 plugins 数组按优先级叠加(不是互相替换)。

③ 怎么做(How)

  1. 先判断是否真需要 Plugin——能靠 Agent / Command / Skill 解决的,不必写代码;
  2. 简单需求先在 .opencode/plugins/ 放一个单文件 .ts 试水;
  3. 复杂 / 需分发的逻辑做成包,在 plugins 中声明并可用 options 传配置;
  4. 插件的具体 API(注册工具、事件钩子、RPC)以官方 Build / Plugins 文档为准;
  5. 改插件后按需重启服务使其生效。

④ 为什么这么做(Why)

  • Markdown 只能塑造提示,代码才能塑造能力。 Agent / Command 本质是预设文本,无法引入新的工具动作或改变运行时行为;Plugin 运行在 OpenCode 进程内,可以做到前三者做不到的事。分层是为了「用复杂度匹配需求」。
  • 以包的形式分发,让插件形成生态。 可以像普通 npm 包一样发布、版本锁定、scoped 管理;@1.2.0 锁版本避免上游突变。
  • 自动发现(.opencode/plugins)降低试验成本:放个文件即可,不必改配置;同时全局与项目目录分层,个人插件与团队插件互不干扰。
  • plugins 数组叠加而非替换,保证全局、项目、子包的插件都能共存,延续配置层的合并哲学(第 8 章)。
  • Plugin 是最后手段而非首选:它引入真正的代码依赖与维护成本,且运行权限高——官方的五层扩展是递进的,应当先用简单层。

⑤ 这样做的好处

  • 能力几乎无上限:自定义工具、事件拦截、集成内部系统。
  • 可版本化、可分发、可传选项
  • 试验成本低:单文件自动发现。
  • 多层插件叠加共存

⑥ 不这么做的问题 / 坏处

  • 把插件放进根目录 plugins/ 却不配置 → 不生效,困惑。
  • 不锁版本 → 上游更新导致行为突变。
  • 能用简单扩展却写 Plugin → 过度工程、维护负担。
  • 忽视插件的高权限 → 第三方插件可访问进程内资源,需像依赖一样审查。

⑦ 举一反三:三个例子

例 1(基础):单文件插件试水

📌 场景:你想在每次工具调用前后加一条自定义日志。

做法:在 .opencode/plugins/log.ts 放一个单文件插件实现该钩子,无需改配置即被发现,验证后再决定是否做成包。

🔍 讲解:自动发现就是为这种轻量尝试设计的。先单文件验证想法,避免一上来就搭包。

例 2(进阶):团队共享一个带配置的插件

📌 场景:团队需要一个连接内部工单系统的插件,各项目配置不同。

做法:发布成 scoped 包,在项目配置用 { "package": ..., "options": {...} } 传入各项目参数,锁定主版本。

🔍 讲解:包 + options 让同一份插件代码适配不同项目;版本锁定保证团队行为一致。

例 3(挑战):判断该用 Plugin 还是 Skill

📌 场景:你需要「让 AI 遵循一套复杂的部署检查流程」。

判断:若流程本质是「知识 / 步骤说明」→ 用 Skill(渐进加载说明);若需要「真正调用一个新工具、拦截事件」→ 才用 Plugin。

🔍 讲解:五层扩展的选择看「需不需要运行新代码」。不要因为 Plugin 强就什么都用它——多数流程性需求,Skill / Command 已足够。


⑧ 练习题 ×2

练习 1(巩固型)

📝 题目:插件有哪两种引入方式?.opencode/plugins/ 与项目根 plugins/ 在自动发现上有何区别?

💡 思路提示:配置数组 vs 自动发现;根目录不被发现。

参考答案

text
① 在 opencode.jsonc 的 plugins 数组声明(包 / 路径 / 带选项对象);
② 放进 .opencode/plugins/(项目)或 ~/.config/opencode/plugins/
(全局)自动发现。
区别:.opencode/plugins 会被自动发现;项目根的 plugins/
不会,需显式配置或挪位置。

🔍 讲解:记住自动发现的目录限定,可避免最常见的「插件不生效」。

练习 2(迁移型)

📝 题目:迁移到浏览器扩展:浏览器为什么同时支持「装扩展(可运行代码、改浏览器行为)」和「用户脚本 / 样式(只改页面表现)」?这与 Plugin vs Skill 的分工有何共性?

参考答案(示例)

text
- 浏览器扩展能运行代码、改变浏览器本身行为(≈ Plugin);
  用户脚本 / 样式只影响页面表现(≈ Skill / 提示层);
- 能力越强、权限越大、越需要审查与版本管理;
共性:OpenCode 也按「改表现 → 改能力」分层,
按需选择、强能力最后用——这是最小复杂度原则。

知识点 2:Policies:只收紧、不放宽的集中策略

① 定位

Permissions(第 7 章)是「可以 allow / deny / ask 的、按 Agent 的工具规则」。但团队 / 企业还需要一种更高优先级、不可被下游绕过、且永不弹窗的硬约束——那就是 Policies。它也可由连接的 OpenCode Console 工作统一下发。

② 是什么(What)

Policies 写在 experimental.policies 下:

jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "experimental": {
    "policies": [
      { "action": "provider.use", "resource": "openai", "effect": "deny" }
    ]
  }
}

每条语句三字段:

字段取值含义
actionprovider.usepermission被控制的操作
resource字符串或通配模式作用对象
effectallow / deny匹配时的决定

Policies 与 Permissions 的本质区别(务必记住):

  • Policies 是二元的(allow/deny)、永不弹窗(没有 ask)
  • Policies 只会收紧 permissions / providers 本来允许的东西,绝不会放宽;
  • 被 deny 的供应商即使凭证有效,也会从模型选择列表中消失

匹配与顺序:* / ? 同 permissions;多条匹配时最后一条生效(无特异性规则),宽语句在前、例外在后;什么都没匹配时默认允许。未通过校验的语句会被丢弃并在服务日志告警,其余照常生效。

③ 怎么做(How)

  1. 用 Policies 表达「组织级红线」:如禁止某供应商、禁止某类权限;
  2. 宽 deny 在前,再用后置 allow 开精确例外(如「全禁供应商、只留 anthropic」);
  3. 编辑后查服务日志确认没有语句因校验失败被丢弃;
  4. 团队可由 Console 工作区统一下发,避免每台机器手改;
  5. 不要用 Policies 去做需要询问用户的场景(它不会 ask)。

④ 为什么这么做(Why)

  • 企业需要「不可被项目 / 个人绕过」的天花板。 permissions 可被各层配置叠加、还能 ask;Policies 站在更高处只收紧,保证组织红线(数据合规、供应商准入)不被下游配置意外打开。
  • 二元、不弹窗,适合无人值守与强制场景。 CI / 自动化中没有人回答 ask;Policies 给确定的 allow/deny,行为可预测。
  • 被 deny 的供应商从目录消失,是从源头消除选择,而非每次拦截——更彻底,也防止模型调用被禁渠道。
  • 「只收紧、不放宽」是安全语义的关键。 这保证 Policies 永远不可能成为削弱安全的通道;它的存在只让边界更严。
  • 默认允许 + 最后匹配生效,让规则既能精确开例外又保持简单,但也要求你把宽语句放前面(顺序错误会吞掉例外)。

⑤ 这样做的好处

  • 组织红线强制生效、不可绕过
  • 行为确定、适合自动化
  • 可集中(Console)下发、统一治理
  • 语义安全:只严不松

⑥ 不这么做的问题 / 坏处

  • 用 permissions 承担组织红线 → 可能被下游配置叠加 / ask 而失守。
  • 顺序写反 → 宽规则最后生效,例外失效。
  • 编辑后不查日志 → 某条策略校验失败被悄悄丢弃。
  • 指望 Policies 弹窗确认 → 它只会直接 allow/deny。

⑦ 举一反三:三个例子

例 1(基础):全禁供应商、只留一家

📌 场景:公司只允许数据发给 Anthropic。

策略:先 {provider.use, *, deny},再 {provider.use, anthropic, allow}——最后一条生效,只有 Anthropic 可用。

🔍 讲解:宽 deny + 后置精确 allow 是标准「白名单」写法,顺序不能反。

例 2(进阶):禁止一批公司自定义供应商

📌 场景:命名为 company-uscompany-eu 的内部渠道要停用。

策略{provider.use, company-*, deny}* 同时匹配两者及不带后缀的值。

🔍 讲解:通配结尾也匹配「无后缀」值,一条覆盖整组同源渠道。

例 3(挑战):发现某条策略没生效

📌 场景:配了 deny 但该供应商仍出现在列表。

排查:① 查服务日志,该语句是否校验失败被丢弃;② 检查顺序,是否后面有一条 allow 把它覆盖;③ 字段 / action 名是否写对。

🔍 讲解:Policies 失效通常是「校验丢弃 / 顺序被覆盖 / 拼写」三类,日志是第一现场。


⑧ 练习题 ×2

练习 1(巩固型)

📝 题目:说出 Policies 与 Permissions 的三个核心区别。多条策略匹配时如何裁决?

💡 思路提示:二元不弹窗、只收紧、供应商从目录消失;最后一条生效。

参考答案

text
区别:① Policies 只有 allow/deny、没有 ask(不弹窗);
② 只会收紧、绝不放宽 permissions/providers 的允许;
③ 被 deny 的供应商直接从模型选择目录消失。
裁决:多条匹配时最后一条生效(无特异性),宽语句在前。

🔍 讲解:理解「只收紧的硬天花板」这一角色,是区分两套机制的关键。

练习 2(迁移型)

📝 题目:迁移到公司财务制度:总部下发的「不可逾越的费用红线」与部门内「可分级审批」的规则,如何对应 Policies vs Permissions?为什么红线必须由更高层制定、且不可被部门放宽?

参考答案(示例)

text
- 总部红线:二元、强制、不可被部门放宽(≈ Policies);
- 部门审批:可按金额分级、可询问(≈ Permissions 的 ask);
- 红线必须高一层制定,否则各部门自行放宽会架空整体管控。
共性:Policies 就是 OpenCode 里由组织 / Console 下发、
只收紧不放宽的「红线层」。

知识点 3:无头模式:opencode run 与自动化 / CI

① 定位

前面所有操作都假设「人坐在 TUI 前」。但脚本、CI、定时任务里没有人操作界面——需要一种提交一句 Prompt、直接拿到结果的方式。这就是无头(headless)模式,是从「人机交互」迈向「自动化集成」的关键一跃。

② 是什么(What)

核心命令是 opencode run

bash
opencode run "Explain this repository"     # 不开界面,直接输出结果

常用参数(来自命令 help):

参数作用
-m, --model指定模型,provider/model#variant 格式
--agent指定 Agent
-c, --continue继续上一个会话
-s, --session继续指定会话 ID
--fork继续前先分叉(fork)该会话
-f, --file附加一个文件
--format输出格式:default / json(脚本解析用 json)
--title会话标题
--thinking显示思考块
--auto自动批准「未被显式 deny」的权限(CI 常用,但 deny 仍生效)
--standalone / --server用私有服务 / 连指定服务,而非共享后台服务

另有 opencode mini:极简交互界面(非全屏),适合轻量场景。

③ 怎么做(How)

  1. 脚本里用 opencode run + --format json,把结果交给程序解析;
  2. CI 中需要跑工具时加 --auto(自动批准非 deny 操作),但关键动作仍可用 Policies / deny 硬性拦住;
  3. 想复用已有上下文 → --continue--session <id>;要避免污染原会话 → --fork
  4. 需要模型读文件 → --file <路径> 或在消息里用路径;
  5. 凭证用环境变量注入(第 6、8 章),CI 中不要依赖交互登录。

④ 为什么这么做(Why)

  • 同一个引擎,两种用法。 交互式 TUI 服务于「人主导的探索」,run 服务于「程序主导的调用」;底层会话、配置、工具、权限完全一致,所以你在 TUI 里调好的规则在自动化里同样生效。
  • --format json 让输出可被机器消费。 脚本可以稳定解析结果文本、usage 等字段,而不必去解析 TUI 的屏幕字符。
  • --continue / --session / --fork 复用上下文。 自动化任务可以接着已有会话做,--fork 则在不污染主线的前提下尝试——与会话分叉语义一致(第 2 章)。
  • --auto + Policies 的组合兼顾效率与安全。 CI 里无人应答 ask,--auto 放行未被 deny 的操作,而组织红线仍由 Policies / 显式 deny 硬性兜底,不会失控。

⑤ 这样做的好处

  • 可嵌入脚本、CI、定时任务、Git hooks。
  • 结果可解析(json)、退出码可判断成败。
  • 复用既有会话与全部配置。
  • --fork 安全试验、不污染主线。

⑥ 不这么做的问题 / 坏处

  • 在 CI 里硬开 TUI → 没有 TTY 直接挂起或失败。
  • 不加 --auto 又无人值守 → 遇到 ask 永远卡住。
  • 不用 json 而解析人类可读输出 → 格式一变脚本就崩。
  • 凭证靠交互登录 → CI 环境无法登录,任务直接鉴权失败。

⑦ 举一反三:三个例子

例 1(基础):命令行快速提问

📌 场景:你想在终端直接问一句「这个仓库是干什么的」,不进界面。

答案

bash
opencode run "用三句话说明这个仓库的用途"

🔍 讲解run 提交一句 Prompt 后把结果打到标准输出,适合一次性、可管道化的调用。

例 2(进阶):CI 中让 AI 审查改动

📌 场景:PR 流水线里想自动审查本次 diff,并以 JSON 供后续步骤判断。

答案

bash
opencode run --auto --format json \
  "审查本次 git diff 中的潜在 bug,只报告高置信问题"

🔍 讲解--auto 让读文件 / 跑只读命令不被 ask 卡住,--format json 让流水线稳定解析;危险写操作仍可被 deny 拦住。

例 3(挑战):基于已有会话做一次不落地的尝试

📌 场景:你想让自动化接着上个会话分析,但绝不能改动原会话。

答案

bash
opencode run --continue --fork "在分叉里尝试另一种重构思路,只做分析"

🔍 讲解--continue 复用上下文、--fork 先分叉,原会话保持干净——把「安全试验」从交互场景延伸到自动化。


⑧ 练习题 ×2

练习 1(巩固型)

📝 题目:要在无人值守的 CI 中调用 OpenCode,至少需要哪两个参数 / 做法?为什么?

💡 思路提示:让非 deny 权限不弹窗 + 让输出可解析。

参考答案

text
① --auto:自动批准未被显式 deny 的权限,避免卡在 ask;
② --format json:输出结构化结果,供脚本稳定解析;
另外凭证应通过环境变量注入,不能依赖交互登录。

🔍 讲解:无人场景的两个核心痛点是「没人点确认」和「程序要读结果」,分别由 --auto--format json 解决。

练习 2(迁移型)

📝 题目:迁移到数据库 CLI:像 psql 既有交互式 REPL、又有 psql -c "SQL" 的非交互模式,这与 TUI / opencode run 的双形态有何共性?什么场景该选哪种?

参考答案(示例)

text
- REPL / TUI:适合人探索、多轮迭代、即时反馈;
- -c / run:适合脚本、批量、自动化、可被其它程序调用;
共性:同一引擎提供「人用」与「机器用」两个入口,
能力一致、按交互对象选择;
需要反复探索用人,需要编排进流程用 run。

知识点 4:References:按名引用项目外目录

① 定位

@文件 只能引用当前项目内的东西。但你常需要让 AI 看隔壁仓库的文档、共享的组件库、另一个 repo 的源码。References 给这些项目外目录起一个别名(alias),之后按名挂载、按需让 Agent 查阅,而不必把外部代码复制进项目。

② 是什么(What)

opencode.jsoncreferences 中声明「别名 → 位置」:

jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "references": {
    "docs": {
      "path": "../product-docs",
      "description": "产品行为与术语,需要时查阅"
    },
    "shared": "~/work/shared"
  }
}

支持两类位置:

1. 本地目录(path

  • 相对路径相对配置文件解析;支持绝对路径与 ~/ 开头;
  • 字符串简写只有以 ./~ 开头时才被当作本地路径——用 ./docs裸写 docs 会被当成 Git 仓库

2. 远程 Git 仓库(repository

jsonc
{
  "references": {
    "effect": { "repository": "Effect-TS/effect", "branch": "main" },
    "internal-sdk": { "repository": "git@gitlab.example.com:platform/sdk.git" }
  }
}
  • 支持 GitHub owner/repo 简写、Git URL、SCP 式 remote;不支持本地 file: 仓库;
  • 不指定 branch 时跟随远程默认分支;checkout 缓存在全局数据目录,如 ~/.local/share/opencode/repos/github.com/Effect-TS/effect

另外几个关键字段:

字段作用
description告诉 Agent「这个引用何时相关」,有描述才会被自动推荐,无描述只能手动挂载
hidden: true从交互式选择器中隐藏,但仍可按名使用

远程引用的刷新是异步、后台、约 24 小时一次:缺失时后台克隆,存在时后台 fetch + reset;不要手动改缓存目录里的 checkout,刷新会把它重置。

③ 怎么做(How)

  1. 在配置里给外部资料起别名,尽量写清 description
  2. 需要时在客户端把该别名作为引用挂载,再让 Agent 读取其中具体文件;
  3. 本地共享资料用 path(注意 ./ 前缀),跨仓库 / 第三方源码用 repository
  4. 需要固定版本就显式指定 branch,否则接受默认分支的后台刷新;
  5. 仅想程序化使用、不出现在选择器里时设 hidden: true

④ 为什么这么做(Why)

  • 保持项目边界,又能跨界取材。 外部资料不必复制 / 软链进仓库,避免污染源码与 diff;通过别名「按需挂载」,项目保持干净。
  • description 让引用「可被发现」而非「死配置」。 Agent 只有在知道「这个别名何时相关」时才会主动想到它,否则你声明了也白声明。
  • Git 引用统一缓存 + 后台刷新,兼顾新鲜与零等待。 每个 remote/branch 只存一份、多位置共享;刷新是异步的,Prompt 不会被克隆阻塞(代价是可能先读到稍旧内容)。
  • 24 小时节流避免频繁打扰远程。 时间戳跨重启持久、失败也计入节流,防止反复打远程。
  • 显式 branch 锁定可复现,默认 branch 追求最新,两种诉求都被满足。

⑤ 这样做的好处

  • 外部文档 / 共享库 / 其它仓库可按名查阅,不污染项目。
  • 缓存一份、多处共享、后台自动更新。
  • 有描述即可被 Agent 主动利用。
  • 支持本地与远程两种来源、可锁定分支。

⑥ 不这么做的问题 / 坏处

  • 把外部代码复制进项目 → 很快过期、产生同步负担与脏 diff。
  • 裸写 docs(不带 ./ → 被误判成 Git 仓库,行为与预期完全不同。
  • 不写 description → 引用不会被自动推荐,容易被遗忘。
  • 手改缓存 checkout → 下次后台刷新直接重置,改动丢失。
  • 假设引用实时最新 → 异步刷新可能让你先读到旧内容,需留意。

⑦ 举一反三:三个例子

例 1(基础):引用隔壁的产品文档

📌 场景:写代码时常要查放在另一个目录的产品文档。

答案

jsonc
{ "references": { "docs": { "path": "../product-docs",
  "description": "查产品行为和术语时使用" } } }

挂载 docs 后让 Agent 读取其中具体文件。

🔍 讲解:本地 path + 清晰 description 是最常用形态,文档留在原处、版本单一。

例 2(进阶):参考一个开源库的官方源码

📌 场景:你想让 AI 参照 Effect 的真实用法来写代码。

答案

jsonc
{ "references": { "effect": { "repository": "Effect-TS/effect", "branch": "main" } } }

🔍 讲解:远程引用会被克隆到全局缓存并后台刷新;显式 main 让跟随的分支明确。

例 3(挑战):发现 Agent 从不主动使用某个引用

📌 场景:你声明了引用,但 AI 从来想不起用它。

排查:检查是否漏了 description——没有描述的引用不会被自动推荐,只能手动挂载。补上「何时该用它」的说明。

🔍 讲解:引用「存在」不等于「会被用」。description 是把它接进 Agent 认知的开关。


⑧ 练习题 ×2

练习 1(巩固型)

📝 题目"docs": "../docs""docs": "docs" 的解析结果有何不同?远程引用多久刷新一次、是否阻塞 Prompt?

💡 思路提示:路径前缀决定本地 / Git;24h、异步。

参考答案

text
"../docs" 以 . 开头 → 本地目录(相对配置文件解析);
"docs" 裸写 → 被解释为 Git 仓库。
远程引用约 24 小时后台刷新一次(缺失时异步克隆),
不阻塞 Prompt,因此可能先读到稍旧内容。

🔍 讲解:前缀规则和异步刷新是 References 最容易踩的两个点,记住可避免误判与「内容怎么是旧的」的困惑。

练习 2(迁移型)

📝 题目:迁移到包管理器:npm 依赖装在项目外的全局 / 缓存目录、项目只按「名字 + 版本」引用,而不复制源码进来。这与 References 的思路有何共性?为什么「引用而非复制」通常更好?

参考答案(示例)

text
- 包管理器:源码在外部缓存,项目只声明名称 / 版本(≈ 别名 + branch);
- References:外部资料在原处 / 全局缓存,项目只声明别名;
共性:单一副本、集中维护、按需取用、不污染项目;
好处:避免复制导致的过期与不一致,升级只需改一处。

知识点 5:ACP:让编辑器 / 图形客户端接入

① 定位

TUI 是 OpenCode 自带的终端界面,但你可能更想在 Zed 等编辑器 / 图形客户端里使用同一个 OpenCode 引擎。ACP(Agent Client Protocol)就是让这些外部客户端把 OpenCode 当作「agent 后端」接入的标准协议。

② 是什么(What)

客户端通过启动 opencode acp 子进程来接入。例如在 Zed 的 ~/.config/zed/settings.json 中:

json
{
  "agent_servers": {
    "OpenCode": { "command": "opencode", "args": ["acp"] }
  }
}

传输与进程模型(关键事实):

  • 客户端把 opencode acp 作为子进程启动,通过 stdin / stdout 交换换行分隔的 JSON-RPC,使用 ACP 协议第 1 版;
  • 该命令为 ACP 进程启动私有 OpenCode 服务——不连共享后台服务、也不暴露网络端口
  • 一个进程可同时服务多个 ACP 会话,直到客户端关闭 stdin 才退出。

会话与能力:

  • 客户端创建会话时提供工作目录,OpenCode 会先加载该目录的配置、插件、模型、Agent、命令、技能、指令与 MCP;
  • 支持会话的创建 / 列出 / 载入 / 恢复 / 分叉 / 关闭 / 删除,以及取消正在执行的 Prompt、流式输出文本 / 推理 / 工具调用 / 权限请求 / usage;
  • 已存在会话里记录的目录是权威的,载入 / 恢复 / 分叉都以它为准。

③ 怎么做(How)

  1. 在支持 ACP 的客户端里配置启动命令 opencode acp
  2. 若图形客户端找不到 opencode,把 command 改成 which opencode 给出的绝对路径;
  3. 通过客户端在目标项目目录创建会话,沿用该目录已有的全部配置;
  4. 需要并行试验时使用「分叉」,需要终止但保留记录时「关闭」而非「删除」;
  5. 界面差异以各客户端为准,但底层调用的是同一个引擎。

④ 为什么这么做(Why)

  • 引擎与界面解耦。 OpenCode 专注做「agent 引擎」,通过开放协议让任何客户端接入;你在 TUI 里沉淀的配置、规则、插件在 Zed 等客户端里原样生效,不必为不同工具重配。
  • 子进程 + stdio,安全且免端口。 不监听网络端口,攻击面小;随客户端启停、客户端关 stdin 即退出,生命周期干净、无残留进程。
  • 私有服务隔离 ACP 流量。 与共享后台服务分开,避免图形客户端与终端客户端互相干扰,行为更可预测。
  • 一进程多会话 + 分叉,复用进程开销的同时保留「安全试验 / 恢复 / 删除」等完整生命周期。
  • 以会话存储目录为权威,避免客户端传入不同目录导致「同一会话看到不同项目」的错乱。

⑤ 这样做的好处

  • 在熟悉的编辑器里用上同一个 OpenCode,配置全复用。
  • 无网络端口、随客户端生命周期、安全干净。
  • 多会话复用单进程,支持分叉 / 恢复 / 删除。
  • 协议开放,生态客户端可广泛接入。

⑥ 不这么做的问题 / 坏处

  • 客户端 PATH 中找不到 opencode → 启动失败,需写绝对路径。
  • 以为 ACP 走共享后台服务 → 排障方向错误(它用的是私有服务)。
  • 误删会话 → 与「关闭」不同,删除会真正移除存储记录。
  • 期待各客户端界面完全一致 → 界面能力取决于客户端,引擎一致但表现不同。

⑦ 举一反三:三个例子

例 1(基础):把 OpenCode 接进 Zed

📌 场景:你想在 Zed 里直接使用 OpenCode。

答案:在 Zed settings.jsonagent_servers 中配置 command: "opencode", args: ["acp"]

🔍 讲解:所有 ACP 客户端都是同一个套路:启动 opencode acp,区别只在配置文件与字段名。

例 2(进阶):图形客户端报「找不到 opencode」

📌 场景:GUI 应用的 PATH 与终端不同,启动失败。

答案:运行 which opencode 取绝对路径,填到 command 字段。

🔍 讲解:图形程序常常不加载 shell 的 PATH 配置,绝对路径是最稳妥的解决方式。

例 3(挑战):在编辑器里安全尝试一个冒险改动

📌 场景:你想试激进重构,但保留当前稳定状态。

答案:对当前会话使用「分叉」,在分叉里尝试;不满意就回到原会话(关闭分叉而非删除原记录)。

🔍 讲解:ACP 完整暴露了会话分叉 / 关闭 / 删除语义,安全试验能力与 TUI 一致。


⑧ 练习题 ×2

练习 1(巩固型)

📝 题目opencode acp 使用什么传输、连的是共享后台服务还是私有服务?客户端如何终止它?

💡 思路提示:stdio JSON-RPC;私有;关 stdin。

参考答案

text
通过子进程的 stdin/stdout 交换换行分隔的 JSON-RPC(ACP v1);
启动的是私有 OpenCode 服务,不连共享后台、不监听网络端口;
客户端关闭 stdin 后进程退出并停止其私有服务。

🔍 讲解:记住「子进程 + stdio + 私有服务」这个模型,接入与排障都会顺。

练习 2(迁移型)

📝 题目:迁移到 LSP:语言服务器也是「编辑器以子进程启动、通过标准输入输出收发协议消息、不自己开界面」。这与 ACP 的架构有何共性?这种「编辑器做界面、独立进程做能力」的分离带来什么好处?

参考答案(示例)

text
- LSP / ACP:编辑器是前端,独立语言 / agent 进程提供能力,
  经 stdio 用标准协议通信;
好处:① 引擎与界面解耦,可独立升级、多客户端复用;
② 无网络端口、随编辑器启停,安全且生命周期清晰;
③ 能力只需实现一次,任何兼容客户端都能接入。

🔍 讲解:ACP 正是「agent 版的 LSP」。理解这套成熟的「协议 + 子进程」架构,你就掌握了 OpenCode 融入各类工具的方式。


本章小结

能力一句话定位何时使用
Plugins用代码扩展、注册工具 / 钩子Markdown 扩展不够、需运行新代码
Policies只收紧、不弹窗的组织红线企业治理、Console 统一下发
无头模式opencode run 提交即返回脚本、CI、自动化
References按名引用项目外目录 / 仓库查外部文档、共享库、其它 repo
ACP让编辑器 / GUI 接入同一引擎想在 Zed 等客户端里使用

这五类能力共同把 OpenCode 从「一个 TUI 工具」扩展成「可编排、可治理、可嵌入、可互联」的 agent 平台。掌握它们的关键不是记全细节,而是清楚各自解决哪一类问题、以及选择顺序(能用简单层就不动用强机制)。