feat: integrate managed opencode plugin host - #2221
Conversation
09a3376 to
3607d5e
Compare
limityan
left a comment
There was a problem hiding this comment.
Review(Request Changes)
对 PR #2221 的独立对抗性复审意见。整体上,传输层工程质量不错(监听先行绑定、首帧强制握手、帧长先校验后分配、进程树监督、npm --ignore-scripts、符号链接逃逸防护等均已核实),但存在四类阻断性问题(架构归属、信任/审批门、成熟度阶段、副作用路由权限),另有若干中等级别的具体缺陷。以下问题、风险与建议均基于实际 diff(merge-base 9d4db799c)逐条核实。
A. 阻断性问题
A1. 架构分层违规:OpenCode 兼容语义与 Plugin Host 进程对象都落在 bitfun-core(Assembly 层)
src/crates/assembly/core/src/plugin_host_http_routes.rs+plugin_host_http_routes_impl.rs(约 1850 行)把 30+ 条 OpenCode Client 路由(session/PTY/file/mcp/lsp/provider/tool)及其 wire 语义实现为 core 内部逻辑;plugin_host.rs在 core 中持有PluginHost进程对象与实例/PTY 静态注册表;product_runtime.rs新增fork_session_for_plugin。- 冲突条款:根
AGENTS.md分层表规定 Assembly「must not implement concrete adapter, OS, or service details」;docs/architecture/extensions/plugin-runtime-design.md职责表规定物理进程(进程树、资源预算、IPC、强制回收)应沿ScriptToolRuntime/services 边界承担,「不得把 Rust 侧实现命名为 Host」「Rust 主应用…没有 Plugin Host 对象」;生态格式/参数/错误语义应保留在适配器,而opencode-plugin-host适配器自身的 AGENTS.md 声明不拥有配置选择与信任决策——实际上这些恰恰都被放进了 core。 - 建议:进程监督下沉 services(或明确由适配器持有进程、core 只做组装);OpenCode 路由与序列化移到
bitfun-opencode-plugin-host;core 只保留 owner 端口调用。
A2. 信任/审批门缺失:config.plugin 非空即启动并执行第三方代码
plugin_host.rs:84-197只要launch_policy != Disabled且config.has_configured_plugins()就 spawn Bun 进程并对全部声明做prepare_plugins(含 npm 依赖安装与import);ensure_configured_plugin_instance无任何确认直接open_instance。- 裸 npm spec 在
extension-host/src/loader.ts:217-233被解析为name@latest,每次 prepare 都可能静默安装最新版本,无候选版本展示、无更新确认。 - 冲突条款:
docs/architecture/product-architecture.md(首次激活/import 前须由既有归属模块重新确认执行许可,确认前不得 import module/产生直接脚本副作用);opencode-extension-compatibility.md§4.2(来源限定身份+插件身份+入口类型+执行域+更新策略确认)。 - 全 PR 未接入既有
plugin_source/trust 文件/来源 owner,也没有任何 UI/CLI 首次启用确认。 - 建议:合并前至少补齐一个最小确认门(CLI 交互确认或持久化 trust 记录),并把 npm 安装固定到可验证版本;若「config 中存在 plugin」本身即构成用户审批,请在 PR/文档中明确写出该流程与证据。
A3. 成熟度阶段被一次性跳过,且无对应设计/计划文档
- 本 PR 一次落地 package 依赖安装 + 全套 Hook(chat.message/permission.ask/tool.execute.before/shell.env 等)+ 回环 Server 路由 + auth/provider/workspace adapter,而设计文档明确这些应分阶段(OC-E0→E3)交付:
opencode-extension-compatibility.md:87-90、docs/plans/opencode-extension-compatibility-plan.md:355-363(暂停条件:为完整兼容一次性引入 package manager、Hook、renderer、Server 和权限系统时应停止扩面先修架构)、plugin-runtime-design.md:276-278及 §8 的固定 fixture 六项验证要求。 - 本 PR 文件清单中没有任何
docs/变更,未更新兼容矩阵「当前状态」,也未提供阶段设计/退出标准。 - 建议:补充本实现对应的分阶段设计文档与固定版本真实插件夹具验证证据,或先在文档中关闭/降级相应阶段。
A4. 副作用路由缺少权限归属模块的调用时检查
- 第三方插件可经回环路由执行
session_create/delete/fork/abort/update、pty_create(允许插件提供任意 env)、file_read、全局mcp_status/lsp_status(routes_impl.rs:111-142、173-184、244-264、676-709、945-1008)。入口只有 instance 目录作用域校验与 30s 超时,没有任何 Permission owner / tool-permission 策略检查。 - 冲突条款:
product-architecture.md(经 BitFun 接口发起的调用仍执行调用时权限判断);opencode-plugin-runtime-adapter-design.md(在 Rust 归属模块检查来源、凭据、文件范围、工具覆盖等)。 - 建议:副作用路由接入 Permission owner 调用时检查;不支持能力返回稳定
unsupported错误而不是统一 400。
B. 具体缺陷(Medium)
B1. 握手读帧无超时(可用性/本地 DoS)
adapters/opencode-plugin-host/src/lib.rs:201-206:STARTUP_TIMEOUT 只覆盖 listener.accept(),complete_handshake → read_frame → read_exact 无超时。本机任意进程抢连后不发 payload(或 Bun 子进程握手前卡死)即可让 CLI 启动路径无限挂起。建议把 accept+handshake 放进同一 timeout。
B2. 单条流取消失败会中断实例 dispose
extension-host/src/host.ts:841 的 await this.#streams.cancelAll(...) 位于 disposer 循环(847-863)与实例/目录清理(865-868)之前;main.ts:54-62 的 cancelAll 未吞 rejection,streams.ts:85-99 的 reader.cancel() 抛错会提前退出 dispose:disposer 不执行、目录永久占用,后续 open 永久报 directory_exists。违反 README「invokes every registered disposer once」。
B3. 出站超限帧拖垮整个 Host
extension-host/src/rpc.ts:257-275:所有出站消息(工具结果、auth loader options、hook 输出)强制 ≤ 协商帧长;插件返回一个序列化后 >16 MiB 的结果即触发 #fail → terminate(),整条连接关闭、全部实例失败。与「按插件隔离失败」的设计意图冲突,建议超限响应降级为 JSON-RPC error 而非断开连接。
B4. 帧协商上限恒 16 MiB,64 MiB 为死代码;且无下限 clamp
adapters/opencode-plugin-host/src/lib.rs:445-448:.min(DEFAULT_MAX_FRAME_BYTES).min(MAX_FRAME_BYTES) 恒 ≤ 16 MiB,MAX_FRAME_BYTES=64 MiB 永不生效,与 PROTOCOL/README「可达 64 MiB」表述不符(方向安全)。另外协商值无下限:子进程可协商到 1 字节,之后后端所有出站响应都会超限断连。
B5. opencode-plugin-host feature 闭包过宽
assembly/core/Cargo.toml:opencode-plugin-host = ["plugin-runtime", "remote-connect", "git", "lsp", ...]。remote-connect 的唯一消费点是 get_ai_model_catalog(lib.rs:82-85 门控;routes.rs:349 仅为 provider 投影),会把 phone-relay/rustls/tungstenite 等整块 owner 拖进插件宿主闭包。建议改用窄的 model-catalog 能力口,避免为读模型目录引入 remote-connect。
B6. Remote workspace 静默跳过
src/apps/cli/src/plugin_host_activation.rs:30-36:远程绑定只打 tracing::debug! 后返回 Ok,用户配置了插件在远程工作区运行会得到静默 no-op,没有任何「不支持」可见状态。违反仓库「Remote compatibility」规则(gate 或显示明确 unsupported 状态)与「禁止静默回落本机」的设计要求。
B7. 兼容基线不一致:pin 1.17.18 vs 文档固定 v1.18.9,且 Bun 被固定为唯一后端
extension-host/src/loader.ts:8 与 package.json 固定 @opencode-ai/plugin@1.17.18,而 opencode-extension-compatibility.md:24,33 明确「稳定兼容只固定 v1.18.9」;plugin_host.rs:53-62 把 Bun 硬编码为唯一运行时,文档要求「确有需要的裁决」。需要二选一并在文档中说明。
B8. 投影数据硬编码,向插件返回不实状态
plugin_host_http_routes.rs:501 所有 agent 恒定 "permission": {"edit":"ask","bash":{},"webfetch":"ask"};:376-388 把所有 enabled provider 都标为 connected;:92 无条件 "vcs":"git"。违反设计「不伪造成功」,插件会基于错误状态做决策。
B9. Bun 运行时未随包安装、无预检
launch spec 仅从 PATH 解析 bun(plugin_host.rs:53-62),安装/自更新只捆绑扩展宿主入口文件;用户配置了插件但机器没有 bun 时,初始化错误被吞(仅 error 日志),直到首次会话操作才报「Configured plugin host is not running」硬错误,难以定位。建议安装/自更新增加 bun 检测与明确指引,或随包捆绑。
C. 次要问题(Low)
extension-host/README.md:47引用了不存在的docs/architecture/extensions/plugin-host-rpc-example.zh-CN.md(悬空引用)。- 握手前 host 侧已注册部分方法(
main.ts:72-79早于握手83-101),host.stream.read/cancel不等待握手即可被处理,与协议「握手前不允许 host.* 调用」不一致(纵深防御缺口)。 - 握手 token 比较非常数时间(lib.rs:438);token 用
thread_rng而非显式 CSPRNG(lib.rs:178-182)(令牌空间足够大,实际风险低)。 #pending(rpc.ts)与#preparations(host.ts:445)无超时/无逐出,长期运行可累积。- 环回地址校验
startsWith("127.")过宽(main.ts:191-195)。 - 流读取出错时 owner 映射条目残留(main.ts:149-155 + streams.ts:66-71);
read_host_stream中途 RPC 失败不 cancel 对端流(http.rs:392-436)。 - host 侧 handler 无超时,32 槽信号量可被占死(peer_runtime.rs:104-116)。
- shutdown
Notify存在错过唤醒竞态,且SHUTDOWN_STARTED永不复位(plugin_host.rs:514-522)。 bun add对以-开头的 spec 存在 CLI flag 注入面(bun-loader.ts:51,可信来源,低危)。- 测试缺口:握手负路径、协商边界、握手超时、read_host_stream 失败路径、handler 超限拒绝等均无覆盖。
- 与本特性无关的纯 rustfmt 格式化改动混入 PR(glob_search.rs、web_readable.rs、hook_import.rs、client_info.rs、page_upload.rs、transcript.rs、remote_ssh/transport.rs 等),建议剥离以保持 PR focused。
D. 风险汇总
- 安全:启动即 import/执行第三方代码、无确认门;副作用路由(会话删除/fork、PTY、文件读取)无权限闸门;
@latest静默换包。 - 可用性:握手无超时可使 CLI 启动挂死;单插件超限结果可拖垮整个 Host 与全部实例;bun 缺失时的错误难以诊断;远程场景静默 no-op 误导用户。
- 治理一致性:与 product-architecture.md / plugin-runtime-design.md / opencode-extension-compatibility.md 三份文档直接冲突(分层、信任、阶段、版本基线),若不先修文档或实现,后续架构审计会持续失败。
E. 已核实正确的部分
- 监听先绑定、后 spawn;首帧强制
backend.handshake并逐项校验(method/jsonrpc/id/token/protocolVersion)。 - 帧长先校验后分配;进程树监督(Unix process group / Windows Job Object)与失败清理属实。
- 远程 CLI 跳过本地激活与桌面 Disabled 符合 PR 声明;npm 安装
--ignore-scripts有测试;符号链接逃逸防护(realpath);自更新回滚完整;新 crate 的边界规则为收紧方向。
F. 建议的处理顺序
- 信任门(合并前):接入来源/信任 owner 确认或明确 config 写入即审批;npm 安装固定可验证版本、去
@latest。 - 架构归位:进程监督下沉 services/适配器,路由与序列化移到适配器,core 只做组装。
- 权限:副作用路由接入 Permission owner 调用时检查,不支持能力返回稳定 unsupported。
- 文档与阶段:更新兼容矩阵「当前状态」与 plugin-runtime-design.md §7;补分阶段设计与固定 fixture 验证;统一版本 pin。
- 必修 bug:B1(握手超时)、B2(dispose 中断)、B3(超限帧降级)。
- 次要:remote 显式不支持状态、bun 预检、feature 闭包解耦、清理格式化 churn 与悬空引用。
Summary
Fixes #
Type and Areas
Type:
Feature
Areas:
Motivation / Impact
BitFun needs to run established OpenCode Server plugins without embedding or depending on an OpenCode server process. This change introduces a separately supervised Bun compatibility host while keeping application state, persistence, HTTP behavior, security-sensitive lifecycle decisions, and process supervision in Rust.
The integration enables configured OpenCode-compatible plugins to contribute hooks, tools, authentication flows, providers, and workspace behavior to local CLI sessions.
Current scope and impact:
1.17.18.Verification
Passed:
cargo check -p bitfun-cligit diff --checkorigin/mainBun Extension Host test result on Windows:
/tmppaths, POSIX path formatting, and theprintfcommand.Remote scenario coverage:
Reviewer Notes
Please pay particular attention to:
1.17.18.The Extension Host is a compatibility process, not an OpenCode server. Rust remains the owner of product state and lifecycle policy; Bun owns JavaScript module loading and function-valued plugin capabilities that cannot cross the JSON boundary.
Checklist