Skip to content

docs: add OpenCode ext-host IPC bridge design - #1758

Open
Can2Nya wants to merge 2 commits into
GCWing:mainfrom
Can2Nya:codex/opencode-ext-host-ipc-design
Open

docs: add OpenCode ext-host IPC bridge design#1758
Can2Nya wants to merge 2 commits into
GCWing:mainfrom
Can2Nya:codex/opencode-ext-host-ipc-design

Conversation

@Can2Nya

@Can2Nya Can2Nya commented Jul 25, 2026

Copy link
Copy Markdown

Add comprehensive technical design for BitFun ↔ OpenCode extension host IPC integration, covering:

  • Architecture positioning and crate dependency direction
  • Lifecycle state machine (Idle → Starting → Connected → Ready)
  • IPC bridge layer design (framing, codec, transport, peer)
  • Module structure for new bitfun-ext-host-bridge crate
  • Key interfaces (ExtHostBinding port, instance management)
  • Error handling and recovery strategy
  • Security considerations (token auth, loopback isolation)
  • 4-phase implementation plan with effort estimates
  • Change impact analysis (~4,376 lines across 5 crates)

Summary

Fixes #

Type and Areas

Type:

Areas:

Motivation / Impact

Verification

Reviewer Notes

Checklist

  • This PR is focused and does not include secrets, temporary prompts, generated scratch files, or unrelated artifacts.
  • Relevant verification is recorded above, or skipped checks are explained.
  • User-facing strings, docs, and locales are updated where applicable.

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

基于当前 PR head 1d53f54a1 和最新 main@2848b58b7 复核后,这份设计目前还不适合合并。主要问题不是协议细节尚未补全,而是执行入口、运行时职责和安全边界与主线现有设计存在冲突:

  1. 插件可能绕过现有确认流程执行。 文档让 Session::create 从 workspace 配置直接启动 Host 并加载插件,但请求中没有已确认的来源、内容摘要和实际执行机器。插件又可以访问文件、网络和子进程。这样打开一个带配置的项目就可能执行尚未批准的代码,Remote workspace 还可能错误地回落到本机。应由现有来源与安全控制面先完成发现、确认和内容校验,Runtime 只接收已经批准的插件;远程执行不可用时应明确拒绝,不能本机回落。

  2. 新增 ExtHostBinding/bitfun-ext-host-bridge 形成了第二条 Plugin Runtime 主链。 最新主线已经明确:调用可靠性由 PluginRuntimeClient 负责,OpenCode 语义由 adapter 转换,进程、IPC 和物理健康由 services 负责,Tool/Hook/Permission 等现有模块负责最终判断和状态提交。当前方案又在 bridge 中管理启动、连接、超时、取消、背压、Hook、Tool 和 instance,会产生两套激活、恢复与贡献状态。请复用现有 PluginRuntimeClient → Adapter → Services → Plugin Host 链路,只在确有缺口的位置补窄接口。

  3. 协议无法在仓库内复核。 文档把作者机器上的 C:\Users\27931\...\PROTOCOL.md 称为唯一事实来源,但仓库中没有对应协议、schema 或 Rust/Bun 共用测试数据;plugin-runtime-host-design.md 等链接也已经失效。应提交版本化协议、schema 和跨语言 fixture,或引用固定的公开提交,并以当前 plugin-runtime-design.md 为上位约束。

  4. 多实例生命周期和双向调用还没有闭合。 同一 workspace instance 可以被多个 Session 共享,但没有使用关系或最后使用者关闭规则;状态图又把 Host 进程状态和单个 instance 状态混在一起。反向权限、认证和 HTTP 请求也缺少有限队列、独立读取/写入、过载处理和完整认证;timeout 后继续复用 instance,但取消和迟到结果处理被延期,存在死锁、跨 Session 串用和副作用失控风险。首个可执行版本必须先定义清楚 Host、instance、Session 三层生命周期,并具备期限、取消、背压、迟到结果拒绝和进程树回收。

  5. 长期架构文档混入了易失效的实施计划。 逐文件代码量、阶段 checklist、13–19 人天估算和约 4.4k 行预算应移到 issue 或实施计划。架构文档应集中说明当前/目标边界、职责关系、关键流程、安全约束和可验证的完成条件;同时修正“当前 adapter 仅静态预览”等已经不符合最新主线的描述。

本地检查也尚未达到文档 PR 的基本门槛:git diff --check 因尾随空格失败,pnpm run check:repo-hygiene 因本机绝对路径失败,Markdown 检查发现 3 个失效链接;GitHub 当前没有 checks,不能视为 CI 已通过。PR 还落后最新 main 28 个提交,其中包含新的 Plugin Runtime 与 worker 生命周期约束。

建议基于最新主线重新收敛设计:先确定一个经过批准的 package-plugin 最小端到端闭环,复用现有 Runtime 主链;协议和安全基础进入仓库后,再分别扩展 Hook、权限回调和其他能力。完成上述调整后再请求复审。

@Can2Nya
Can2Nya force-pushed the codex/opencode-ext-host-ipc-design branch from 1d53f54 to 230b68d Compare July 30, 2026 01:57
@Can2Nya
Can2Nya requested a review from limityan July 30, 2026 02:21

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

基于当前 PR head 230b68dc4 和最新 main@a1b5f283e 重新审查后,结论仍是 Request changes

这次更新已经实质修复了旧版的一些问题:补充了来源审批与 activation authority、Remote fail-closed、三层生命周期、迟到结果处理、ProcessTreeChild 进程树回收,并把协议来源固定到公开提交。因此下面不再重复旧 review,而只列当前版本仍会阻塞实现或导致安全/架构回归的问题。

1. 批准摘要没有绑定最终执行的 npm 代码

问题

文档前面要求把入口、锁文件、完整依赖图和物化摘要纳入用户确认,并禁止无锁漂移;但 §4.2/§4.3 又规定 Bun Host 在可写 cacheDirectory 中安装 npm 插件,host.instance.open 示例仍传入裸 { "spec": "opencode-plugin-foo" }

冻结的 ext-host 实现会把裸包名解析为 @latest,随后执行 bun add --ignore-scripts --exact <spec>。也就是说,用户确认的声明/manifest 摘要与最终下载、import 的包及传递依赖不是同一个经过验证的对象。

风险

包版本或传递依赖可以在确认后变化,未被批准的代码仍会在当前用户权限下获得文件、网络和子进程能力;这会直接破坏本文声称的“固定内容 + 当前 authority”安全边界。

建议方案

依赖准备服务应先解析、安装并冻结完整依赖闭包,计算最终物化摘要并让用户确认;确认后 instance.open 只能收到只读、内容寻址的本地入口,不能再让 Host 根据裸 npm spec 联网解析。如果必须由 Host 安装,则协议需要增加 prepare/attest 两阶段流程,让 Rust 在 import 前校验 Host 返回的完整依赖清单和摘要。

2. Host 的进程键和生命周期与最新主线冲突

问题

§3 把 workspace + plugin target 作为 Host/Instance 键,并由首个/末个 Session holder 启停进程。最新 plugin-runtime-design.md 已明确:workspace、session 和插件数量都不是默认物理进程键;同一 RuntimeServices 中兼容的插件和多个 workspace 默认共享 Plugin Host,session 只用于调用身份、取消和权限上下文。

风险

该模型会把进程数量放大为 workspace × target,并在 Session 归档时错误回收仍被事件订阅、后台任务或其他 Client 使用的 Host;同时会破坏共享 Host 的加载顺序、模块缓存、统一重启预算和故障传播语义。

建议方案

以 RuntimeServices 的真实插件使用、停用和进程退出控制 Host 生命周期;Session 关闭只取消该 Session 的在途调用。若确实需要按插件隔离进程,必须作为对上位设计的显式变更,给出安全收益、启动/内存数据、兼容性差异和行为等价测试,不能在 P0 落地文档中隐式改写默认模型。

3. ExtHostRuntime 与现有静态预览组合点形成第二套 owner

问题

§5.1 把 Session quiesce/resume、holder token、Host 关闭和物理清理状态全部放进 runtime-ports::ExtHostRuntime;§5.4 又让 assembly/core/plugin_runtime.rs 中一个并不存在的 Session::create 启动 Host、消费 registrations 并直接注册 Tool/Hook。

但最新代码中该 assembly 模块明确只负责选择 adapter/client 和投影候选,不注册工具、不执行插件代码;opencode-adapter/AGENTS.md 也明确要求当前 load_opencode_package_adapter 保持 static-preview only,不能直接扩展成新的受管 OpenCode 执行路径。

风险

稳定 contract 会同时承担产品 Session 生命周期和物理进程生命周期,静态预览入口也会升级成执行 authority,最终形成第二套激活、generation、恢复、贡献注册和清理状态;现有来源 owner、能力 owner、PluginRuntimeClient 与 services 进程 owner 无法保持单一权威。

建议方案

保持来源/激活事实由 source owner 管理,Tool/Hook/Permission 由各能力 owner 提交,调用可靠性由 PluginRuntimeClient 管理,进程与 IPC 由 services 管理。只新增 provider-neutral、真实 consumer 驱动的窄执行控制端口,并由 Assembly 注入;不要让 runtime-ports 或 legacy static-preview 路径拥有 Session/Host 生命周期。

4. 一个异常 Host 可以永久阻塞或直接击穿 Session 创建

问题

§5.3 的 spawn_ext_host()listener.accept() 和 handshake 没有 startup deadline,也没有同时监听 child exit、cancel 或 shutdown;§5.4 的 Session::create 又同步等待所有 target,并用 ? 传播任一启动、握手或注册失败。

风险

Bun 启动后不连接即可让创建流程永久挂起;Bun 缺失或单个插件损坏则会让整个无关 Session 创建失败。这与上位设计要求的后台准备、非阻塞产品入口和单插件失败隔离相冲突。

建议方案

由 workspace/source runtime coordinator 在后台维护 target 可用性,Session 只读取轻量状态而不等待所有插件启动。每个 target 独立降级;startup 使用统一 deadline,并通过 select 同时处理 accept、handshake、child exit、取消和应用 shutdown。

5. timeout/cancel 只停止 Rust 等待,没有取消插件执行

问题

§3.6 的 tokio::select! 在 timeout 或 cancellation token 触发后直接返回,随后只从 pending map 移除 request id。协议已经提供 host.tool.cancel { instanceID, executionID },但这里没有发送;“丢弃迟到响应”也不能撤销插件已经开始的副作用。

风险

用户界面显示已取消后,插件仍可能继续写文件、访问网络或创建子进程;如果继续复用该 target,后续调用还可能观察到不可控的残留状态。

建议方案

请求模型需要携带调用类别和 executionID。Tool 超时/取消时先发送 host.tool.cancel 并有界等待确认;Host 不响应时将 target 标记为 poisoned 并回收/重启。没有 cancel RPC 的 Hook 按 generation fence 丢弃结果,并按本文其他章节已经描述的规则 recycle target。

合并前状态

  • PR 当前落后 main 97 个提交,其中包含 refactor(plugins): clarify runtime process ownership;需要先变基再校对整篇设计。
  • 最新主线已把 plugin-runtime-host-design.md 替换为 plugin-runtime-design.md。当前提交应用到最新主线后会留下 2 个失效 Markdown 链接和 2 处过时文本引用。
  • git diff --checkpnpm run check:repo-hygienenode scripts/check-core-boundaries.mjs 本地通过,但这些检查不会验证架构文档中的 owner 与安全流程。
  • GitHub 当前仍是 no checks reported,不能视为 CI 已通过。
  • 如果要逐字复制 ext-host 协议/schema 或随产品分发 Host,还需要先明确外部仓库许可证、可复现构建、签名、升级和跨平台交付方式。

建议先基于最新主线收敛一个最小闭环:已批准的不可变 package target → 唯一 Runtime/Services 主链 → 一个真实 Tool 或稳定 Hook consumer → 可取消、可回收、可验证的 Host 调用。该闭环和固定跨语言 fixture 完成后,再扩展完整 Client、Auth、Provider 和其他 Hook。

@Can2Nya
Can2Nya force-pushed the codex/opencode-ext-host-ipc-design branch from 230b68d to 87ab106 Compare July 30, 2026 07:08
@Can2Nya
Can2Nya requested a review from limityan July 30, 2026 07:08

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

基于最新 PR head 87ab106464810d531991d472f64b52b391a4b131 和当前 main@8f9d494558803afcfc9bd0cbea6aa09c06bce634 重新审查后,结论仍是 Request changes

这版已经实质修复了上轮指出的主要问题:明确了 Host 外 prepared target、共享 Host 生命周期、唯一 Runtime/Services owner、后台有界启动、Tool cancel/poison 回收、协议快照和许可证前置。因此下面只列当前版本仍未闭合的安全与可靠性问题。

1. Per-instance HTTP gateway 没有调用方认证

问题

§4.6 只要求 backend.http.request 按 instance authority 和网络策略复核,但冻结提交 e084c921src/gateway.ts:18-51127.0.0.1:0 启动每实例 HTTP server 后,没有 token、Authorization header 或随机路径,任何请求都会被自动附上闭包中的固定 instanceID 并转发。主 RPC 的一次性 handshake token 只认证 Bun child 与 Rust 的主连接,不覆盖该 gateway。

风险

本机其他进程,尤其共享 Host 中的其他插件,可以枚举端口并冒用目标 instance 的 authority,访问 BitFun 内部兼容 API或制造请求/stream 压力。Rust 再检查 instanceID 也无效,因为未经认证的 caller 已经继承了合法 instance 身份。

建议方案

升级协议时加入与 instanceID + connection_generation 绑定的高熵 capability token,在读取或注册请求体之前完成认证;补充无 token、错误 token、跨 instance 和旧 generation 重放 fixture。更稳妥的方案是让兼容 Client 直接走已认证 RPC/私有 Pipe,而不是裸 loopback HTTP。§6.1 的协议升级前置也需要明确包含 gateway authentication。

2. “OS 强制只读 prepared target”没有三平台可实现机制

问题

§2.1 把只读、内容寻址 target 作为 approval 到 import 的核心不变量,同时又明确插件按当前 execution domain 的实际 OS 用户权限运行。如果 target 由同一用户的 Rust/services 创建,Windows 文件 owner 可以重新修改 DACL,Unix 文件 owner 也可以重新 chmod;普通 read-only 位或同用户 ACL 不是不可变边界。

风险

插件可以恢复写权限、rename/link 文件,或替换后续动态 import 的依赖,使用户批准的 materialization_digest 与实际执行字节再次脱钩。文档虽规定“无法实现则不支持”,但没有给出 Windows、macOS、Linux 任一平台的可实施机制,因此 P0 安全闭环和三平台退出条件目前不可执行。

建议方案

明确选择并验证独立 principal/restricted token、不同 owner 的 target store、只读 mount/image,或等价的受约束 loader;真实测试必须覆盖插件内部 chmod、DACL 修改、write、rename、hardlink、symlink/reparse point 和延迟 import 竞态。若没有可落地的强制机制,只能把该存储描述为 tamper-evident,不能称为 immutable。

3. cancel acknowledgment 仍可能被误当成执行已经终止

问题

§4.4 在“成功确认”后直接以 Cancelled/TimedOut 结算并继续复用 Host。冻结实现的 src/host.ts:404-408 只是执行 AbortController.abort() 后立即返回 { cancelled: true },并没有等待 Tool handler 结束;插件也可以忽略 AbortSignal

风险

界面和审计显示调用已取消后,插件仍可能继续写文件、联网或创建子进程;共享 Host 继续接收后续调用时还会观察到残留状态。

建议方案

新协议必须区分 signal_delivered、invocation promise 已结束和“可以证明执行已终止”。只有终态证明成立时才能返回普通 Cancelled;否则必须 poison/recycle 整个 Host generation,并返回 OutcomeUnknown。对应 fixture 应包含忽略 AbortSignal、永不 settle 和取消后继续产生副作用的 Tool。

4. 缺少 post-import contribution 扩权闸门

问题

§2.1 在 materialization 批准后直接 import,并让能力 owner 校验、发布动态贡献。但真实 Tool/Hook/Auth/Provider 集合只有 import 后才完整可知;src/crates/adapters/opencode-adapter/AGENTS.md:23-27 已明确要求 pre-import 权限扩大和 post-import contribution expansion 使用两个独立 gate。

风险

如果 import 后发现的贡献超出已批准 capability envelope,仅拒绝注册贡献并不能停止已经执行的模块、timer、后台任务或直接副作用,用户会在代码已经持续运行后才看到扩权差异。

建议方案

补充明确分支:超出 envelope 时不发布任何新增贡献,停止并确认共享 Host 进程树退出,进入 AwaitingApproval 展示差异;批准后从同一 immutable target 重启,拒绝时只恢复仍合规的插件组。该流程也应进入真实纵向 fixture。

5. 声称处理 event-loop stall,但没有检测机制

问题

§4.7 把 event-loop stall 列为 poison 条件,但 §5.3 的 services 实现清单、§6 的协议升级要求和 §7 的验证项都没有 heartbeat/liveness protocol;冻结 ext-host 也没有 ping/heartbeat。上位 plugin-runtime-design.md:140-142 明确要求 Rust supervisor 的健康检查不能依赖可能被同步插件代码阻塞的业务消息队列。

风险

插件在后台进入同步死循环且当前没有业务调用时,进程仍存活、socket 仍连接,系统会无限保持错误的 Ready 状态,无法触发文档承诺的 poison/recovery。

建议方案

定义独立于业务队列的 supervisor heartbeat/liveness channel、deadline、generation fencing 和回收条件,并加入“无在途请求时后台同步死循环”的真实进程测试。

合并前状态

  • 文档第 429 行把 @opencode-ai/plugin@1.17.18 称为“当前兼容目标”,但兼容总览已固定 OpenCode v1.18.9。应把 1.17.18 标为冻结 ext-host 的旧审计依赖,并将升级当前稳定 fixture 设为退出条件。
  • PR 实际落后当前 main 11 个提交;merge-tree 无冲突,但仍应先变基再校对。
  • PR 描述仍保留“4 阶段、约 4,376 行、5 crates”等旧内容,Verification 和 Checklist 未更新,与当前单个 540 行文档不符。
  • git diff --checkpnpm run check:repo-hygienenode scripts/check-core-boundaries.mjs 和内部 Markdown 链接检查本地通过。
  • GitHub 当前仍是 no checks reported,不能视为 CI 已通过。

建议先把上述 5 个安全/可靠性缺口写成可执行的协议与三平台约束,再请求复审。当前 owner 链和生命周期方向已经基本收敛,不需要重新引入 bridge、Session holder 或 workspace Host owner。

@Can2Nya
Can2Nya force-pushed the codex/opencode-ext-host-ipc-design branch from 87ab106 to 307f4be Compare July 30, 2026 09:20

@limityan limityan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

基于最新 PR head 307f4bea36baedd294cda10075493562e03935b4 和当前 main@1426c6b164b7a3eee43d53d8430dc89c1c410132 重新审查后,结论仍是 Request changes

这次更新正确补充了 RuntimeServices backend -> Shared Plugin Host 默认 1:1 模型,并进一步明确 Session、Workspace、插件停用和后端退出的生命周期边界。此前关于 per-workspace/per-plugin Host、Session holder 和第二套 runtime owner 的问题已经收敛,不需要重新调整这个方向。

不过,新增的 .omo/drafts/opencode-ext-host-security-followup.md 只是记录了下一步准备采用的方案;下面 6 个安全、可靠性和生命周期要求尚未落实到正式设计和协议退出条件中。

1. Per-instance HTTP gateway 仍没有调用方认证

问题

§4.6 只要求 Rust 按 instance authority 和网络策略复核请求。冻结提交 e084c921src/gateway.ts:16-51 会在 127.0.0.1:0 无条件创建 gateway,但没有 token、Authorization header 或不可预测路径;所有请求都会被自动附上闭包中的固定 instanceID。主 RPC handshake token 只认证 Host 与 Rust 的主连接,不认证 gateway caller。

风险

本机其他进程或共享 Host 中的其他插件可以枚举端口,并借用目标 instance 的 authority 访问 backend 或制造 request/stream 压力。Rust 再检查 instanceID 无法识别真实调用方,因为未经认证的请求已经继承了合法实例身份。

建议方案

把与 instanceID + connection_generation 绑定的高熵 capability token 纳入公开 Zod/schema;必须在读取请求体或分配 stream 前认证。补充无 token、错误 token、跨 instance 和旧 generation 重放 fixture。若 gateway 不属于首个 Tool/Hook 闭环,应明确禁用,而不是保留未认证的兼容入口。

2. “不可变/OS 强制只读 target”缺少三平台可实施机制

问题

§2.1 要求 Host 只能读取不可变或 OS 强制只读的 prepared target,同时又明确第三方代码按当前执行域的真实 OS 用户权限运行。如果 target 与 Host 属于同一用户,普通 Unix read-only/chmod 或同 owner 的 Windows ACL 不能阻止其恢复写权限、rename/link 文件或替换延迟 import 的依赖。仓库 ProcessTreeChild 也明确只提供生命周期 containment,不是文件系统 sandbox。

风险

用户批准的 materialization_digest 仍可能与随后实际执行的字节脱钩,而当前文档没有给出 Windows、macOS、Linux 任一平台的可执行方案,因此 P0 安全边界无法据此实现和验收。

建议方案

明确 restricted token、独立 principal/owner、只读 mount/image 或受约束 loader 等真实机制,并覆盖 write、chmod/DACL、rename、hardlink、symlink/reparse point 和 delayed import 测试。若首期只能做到 canonical attestation、原子 staging 和 pre-import revalidation,应明确称为 tamper-evident,并披露 import 后变化风险,不能继续称为 immutable。

3. Cancel acknowledgement 仍被误当成 invocation 已经终止

问题

§4.4 在收到“取消确认”后直接以 Cancelled/TimedOut 结算并继续复用 Host。冻结 src/host.ts:404-408 只是调用 AbortController.abort() 后立即返回 { cancelled: true },没有等待 Tool handler 结束;插件可以忽略 AbortSignal

风险

界面和审计已经显示取消后,插件仍可能写文件、联网、创建子进程或影响后续调用。此时继续复用共享 Host 会把未终止的副作用带入新的请求。

建议方案

新协议必须区分 signal_deliveredinvocation_terminalnot_foundconnection_lost/unknown。只有终态事实成立时才能返回普通 Cancelled;否则应 poison/recycle 整个 Host generation,并返回 OutcomeUnknown。fixture 需要覆盖忽略 AbortSignal、永不 settle 和取消后继续产生副作用的 Tool。

4. 缺少独立的 post-import contribution expansion gate

问题

§2.1/§4.2 在 materialization 获批并 import 后,直接把候选交给能力 owner 校验和发布;但真实 Tool、Hook、Auth、Provider 集合只有 import 后才完整可知。当前流程没有定义“贡献超出已批准 capability envelope”时的状态转换,而 opencode-adapter/AGENTS.md 和上位 adapter 设计明确要求 pre-import 权限扩大与 post-import contribution expansion 使用两个独立 gate。

风险

仅拒绝注册新增贡献不能停止已经执行的模块、timer、后台任务或直接副作用,用户会在未批准代码持续运行后才看到扩权差异。

建议方案

超出 envelope 时不得发布新增贡献;应停止并确认共享 Host 进程树退出,进入 AwaitingApproval 展示真实差异。批准后从同一 prepared target 重启;拒绝时只恢复仍合规的插件组,并将该分支加入纵向 fixture。

5. Event-loop stall 只有错误分类,没有检测路径

问题

§4.7 把 event-loop stall 列为 poison 条件,但 §5.3 的 services 清单、§6 的协议升级条件和 §7 的 fixture 都没有 watchdog、progress generation 或独立 control channel。冻结协议也没有 heartbeat/ping。上位 plugin-runtime-design.md 明确要求健康检查不能依赖可能被同步插件代码阻塞的业务队列。

风险

插件在后台进入同步死循环且没有业务请求时,进程仍存活、socket 仍连接,系统会无限保持错误的 Ready 状态,无法触发本文承诺的 poison/recovery。

建议方案

定义独立于业务队列的 supervisor liveness channel、deadline、generation fencing 和回收条件,并加入“无在途请求时后台同步死循环”的真实进程测试。

6. Runtime 生命周期方向正确,但恢复和持久化合同没有闭合

问题

§3.1 已明确同一 RuntimeServices backend 默认复用一个 Shared Plugin Host,多 workspace/plugin/session 只是逻辑实例和调用上下文;§3.3—§3.6 也覆盖了后台启动、正常退出、安全重启和预算内崩溃恢复。但仍有四处关键空白:

  1. “按真实插件使用延迟启动”没有锁定触发点。Host 不能等到首次 Tool call 才启动,因为 Hook、事件订阅、Auth/Provider 需要先 import;应明确由首个仍获批准且需要执行的 target activation 触发后台 single-flight acquire。
  2. Workspace 关闭只写了移除 workspace-specific logical instance,却没有要求在无法证明 module 已卸载时安全重启共享 Host。instance.close 只是 best-effort,已 import 的 timer、后台任务和副作用可能继续运行。
  3. 崩溃恢复只说“重载整组插件”,没有定义从哪个 owner 取得当前获批 target graph、固定加载顺序、每个 logical instance 的 project/workspace context 和 capability envelope,也没有定义订阅/后台 callback 重建完成后才进入 Ready。
  4. Rust 侧对象基数和持久化边界没有写死:executable adapter、PluginRuntimeClient、services supervisor 应是每个 RuntimeServices composition 一份,而不是每 target/workspace 一份;authority handle、connection/contribution generation、idempotency result、fault、restart budget 和 target state checkpoint 哪些跨 Host generation 或应用重启保留也未形成矩阵。

风险

Workspace 已关闭后插件代码仍可能继续执行;崩溃后可能按错误顺序、旧上下文或不完整 target 集合恢复;若每个 target 各建 client/adapter,会形成多套队列、fault 和 recovery owner;若成功 idempotency 结果或恢复预算被 Host 重启清空,还可能重放有副作用调用或形成重启风暴。

建议方案

补充一份可执行的 lifecycle/rehydration contract:

  • 启动:首个有效 activation 触发后台 single-flight Host acquire;Session 创建/恢复不触发也不等待 Host。
  • 共享:一个 RuntimeServices composition 对应一个 executable adapter binding、一个 PluginRuntimeClient reliability owner 和一个 services supervisor;仅 process key 不兼容时增加 Host。
  • 关闭:Session 关闭只取消自身调用;Workspace/target 的最后实例关闭若无法证明卸载,则停止整个旧 Host、确认进程树退出,再加载剩余 target。
  • 恢复:source owner 提供当前获批 target graph;逐项复核 materialization/authority;按固定生态顺序 import;能力 owner 用新 contribution generation 原子重发;订阅重建完成后才 Ready。旧 pending call 只结算,不恢复或自动重放。
  • 持久化:明确区分产品持久事实、跨 Host generation 的 Rust 状态和纯易失状态。Host 内 module、closure、timer、background task、socket、stream 和 pending queue 必须易失;source choice、approval、target attestation 和期望启用图由既有 owner 保存;idempotency/fault/restart-budget 的保留和清理条件必须写明。

合并前还需处理

  • §6.1 的 @opencode-ai/plugin@1.17.18 应标为冻结 ext-host 的旧审计依赖;当前稳定兼容基线是 v1.18.9,升级后的协议与 fixture 应对齐该版本。
  • §7.4 当前要求 Remote fixture 完成完整 Host 流程,但权威兼容矩阵明确规定 full plugin Remote 在 OC-R5 前保持 unsupported。P0 应验证明确拒绝且不本机 fallback,真实远端纵向执行留到对应阶段。
  • 新增 .omo/drafts/opencode-ext-host-security-followup.md 仍标记为 drafting/awaiting-approval,而且其中列出的修复尚未进入正式设计。该工具内部草稿不应作为架构交付物合入;应先把已确定的结论落实到正式文档,再移除草稿。
  • PR 当前相对 main 为 18 behind / 1 ahead。merge-tree 无冲突,但仍应变基后重新校对;PR 描述中的“4 阶段、约 4,376 行、5 crates”以及未填写的 Verification/Checklist 也需要同步更新。
  • 本地 git diff --checkpnpm run check:repo-hygienenode scripts/check-core-boundaries.mjs 和 Markdown 本地链接检查均通过;GitHub 对精确 head 没有任何 workflow run,属于 no checks reported,不能视为 CI 通过。

建议直接把新增 draft 已经记录的安全决策落实到正式设计,并同时补齐 lifecycle/rehydration contract。完成后再请求复审。

@Can2Nya

Can2Nya commented Jul 30, 2026

Copy link
Copy Markdown
Author

1的场景是用在插件调用http的时候,2是多平台读取插件文件可能会遇到的问题,3、6主要是针对host进程异常场景恢复能力,4是权限范围问题,5应该是考虑了插件在读取过程中遇到阻塞异常等需要恢复的情况。目前这个文档主要是落地运行时,后续具体场景,再做针对性开发,所以对最后这个评论暂时不做方案上的整改

@limityan

Copy link
Copy Markdown
Collaborator

感谢说明。我理解这 6 点分别涉及 HTTP gateway、跨平台 target、取消/故障恢复、权限范围和阻塞恢复。但本轮 review 的核心不是它们分别属于哪个“具体场景”,而是:当前正式文档已经把这些行为写成运行时安全、可靠性与 P0 退出条件的一部分。既然本文定位是指导运行时落地,就必须让这些合同在实现前达到可执行、可验收的程度;否则实现者会按照一份内部不闭合、甚至无法兑现的设计继续开发。

这里并不是要求现在一次性实现所有 OpenCode 场景。如果某项能力确实要延期,可以在正式设计中明确标记为 disabled/unsupported,删除当前无法兑现的保证,并用协议和 fixture 证明它在首期不会被意外启用。但不能一边保留“已认证、不可变、可取消、可恢复”等架构承诺,一边把保证这些承诺成立的机制留到以后。

1. Per-instance HTTP gateway 不是普通 HTTP 功能细节,而是 authority 边界

当前冻结 Host 在 loopback 随机端口创建 gateway,没有 token、Authorization header 或不可预测的 capability path;请求进入后会自动继承闭包中的合法 instanceID。因此 Rust 后续再按 instanceID 检查 authority,也无法识别真实 caller。

风险是本机其他进程或共享 Host 中的其他插件能够枚举端口,借用目标 instance 的身份访问 backend;还可以在认证前发送大 body、创建 stream 或制造队列压力。这样不仅可能越权访问网络或内部兼容接口,也会形成跨 instance 的资源消耗攻击。

如果 gateway 不属于首个 Tool/Hook 闭环,可以延期启用,但正式协议必须明确禁用该入口;不能把未认证 gateway 留作兼容路径。若保留,则需要在读取 body、创建 stream 之前验证与 instanceID + connection_generation 绑定的高熵 capability,并覆盖无 token、错误 token、跨 instance、旧 generation 重放等 fixture。

2. prepared target 的问题不是“多平台读取文件”,而是批准内容与执行字节能否保持一致

正文要求确认后的 target “不可变/OS 强制只读”,同时插件和 target 又运行在同一真实 OS 用户权限下。普通 Unix read-only/chmod 或同 owner 的 Windows ACL 并不能阻止该用户重新取得写权限、替换文件、rename/link,或者修改延迟 import 的依赖;ProcessTreeChild 只负责进程树回收,不是文件系统 sandbox。

风险是用户批准的 materialization_digest 与最终执行的代码再次脱钩:确认后被替换的插件或传递依赖仍能以当前用户权限访问文件、网络和子进程,整个供应链确认边界会失效,而且当前设计无法在 Windows、macOS、Linux 上按统一退出条件验收。

P0 如果暂时没有独立 principal、受限 token、只读 mount/image 或受约束 loader,可以采用 canonical attestation、原子 staging、pre-import revalidation 和 generation fencing,但必须如实把保证降级为 tamper-evident,明确 import 后变化与 delayed import 的残余风险,不能继续称为 immutable 或 OS-enforced read-only。

3. Cancel acknowledgement 不等于插件执行已经终止

冻结实现的 host.tool.cancel 只是调用 AbortController.abort() 后立即返回 { cancelled: true }。AbortSignal 是合作式信号,插件完全可以忽略它,handler 也可能永不 settle;因此这个响应最多证明 signal 已送达,不能证明 invocation 已结束。

风险是 UI 和审计已经显示 Cancelled/TimedOut,但插件仍在写文件、联网、创建子进程或修改共享内存状态。若继续复用同一 Shared Host,后续调用会观察到这些残留副作用;若把它记录成普通 Cancelled,还会形成错误的审计事实,并掩盖真实的 OutcomeUnknown。

这不是仅在 Host 崩溃时才发生,而是正常的用户取消和 deadline 路径。首个真实 Tool 闭环已经包含取消,因此不能延期。协议需要区分 signal_delivered、invocation_terminal、not_found 和 connection_lost/unknown;只有终态可证明时才返回普通 Cancelled,否则应 poison/recycle 整个 Host generation,并返回 OutcomeUnknown。

4. post-import contribution expansion 是独立安全闸门,不只是一般权限校验

真实 Tool、Hook、Auth、Provider 集合只有插件 import 后才能完整知道;但 import 本身已经可能执行顶层代码、创建 timer、后台任务、网络连接或子进程。仅让能力 owner “拒绝注册超出范围的贡献”并不能停止已经运行的代码。

风险是用户在未确认扩大的 capability envelope 前,第三方代码已经持续运行并产生不可撤销副作用;表面上新增 Tool/Hook 没有发布,实际上执行权限已经扩大。这也违反当前 adapter 上位约束中 pre-import 权限扩大与 post-import contribution expansion 必须分成两个 gate 的要求。

正确状态转换应是:发现贡献超出 envelope 后不发布新增贡献,停止并确认整个 Shared Host 进程树退出,进入 AwaitingApproval 展示实际差异;批准后从同一 prepared target 重启,拒绝时只恢复仍合规的插件组。这个分支需要进入真实纵向 fixture。

5. event-loop stall 不能只有错误名称而没有检测路径

同步死循环或长期阻塞可能发生在没有在途业务请求时。此时 Bun 进程仍存活、socket 仍连接,但处理业务 RPC 的 event loop 已经失去进展。如果健康检查仍走同一个业务队列,它也会一起被阻塞。

风险是系统会无限保持错误的 Ready 状态:既不会触发文档承诺的 poison/recovery,也无法撤下已经暴露给用户或模型的贡献;共享 Host 中其他正常插件也会一起永久不可用。

如果 P0 声称能够识别并恢复 event-loop stall,就需要独立于插件主事件队列的 supervisor liveness/control channel、deadline、progress generation 和回收条件,并覆盖“无业务请求时后台同步死循环”的真实进程测试。如果暂不实现,则应删除当前恢复保证并明确 unsupported/degraded 行为,不能只在错误枚举中写一个无法产生的状态。

6. lifecycle/rehydration 是运行时设计本身,不是可留到后续的异常场景

当前方向——一个 RuntimeServices composition 默认对应一个 Shared Plugin Host——已经正确;问题是恢复合同仍没有写到实现者可以唯一执行的程度。至少还需要明确:

  • 首个仍获批准且需要执行的 target activation 触发后台 single-flight Host acquire;Session 创建/恢复既不触发也不等待 Host。
  • Session 关闭只取消自身调用;Workspace/target 最后实例关闭时,如果无法证明 module 已卸载,必须安全重启整个共享 Host。
  • 崩溃后由哪个 source owner 提供当前获批 target graph、固定加载顺序、每个 logical instance 的 project/workspace context 与 capability envelope。
  • 只有 contribution、订阅和后台 callback 在新 generation 上重建完成后才进入 Ready。
  • executable adapter、PluginRuntimeClient 和 services supervisor 的对象基数,以及 authority、generation、idempotency result、fault、restart budget、target checkpoint 哪些跨 Host generation 或应用重启保留。

如果这些合同缺失,风险包括:Workspace 已关闭后插件代码仍继续运行;崩溃后按旧内容、错误顺序或错误 workspace context 恢复;每个 target 各自创建 client/supervisor,形成多套队列、fault 和 recovery owner;Host 重启清空成功的 idempotency 结果后重放有副作用调用;每个插件分别消耗重启预算,最终形成重启风暴。

当前解除阻塞所需的最小整改

  1. 把 .omo/drafts/opencode-ext-host-security-followup.md 中已经形成的 gateway auth、tamper-evident、cancel terminal、post-import gate 和 liveness 决定落实到正式设计、协议退出条件与 fixture;然后移除内部 drafting/awaiting-approval 草稿。
  2. 补齐 lifecycle/rehydration contract 和持久化矩阵。
  3. 对确实延期的能力,在正式文档和协议中明确 disabled/unsupported,并删除相应的当前保证。
  4. 将 @opencode-ai/plugin@1.17.18 标为冻结 ext-host 的旧审计依赖,当前稳定兼容 fixture 对齐 v1.18.9。
  5. P0 Remote 验证应是明确拒绝且不回退本机;full plugin Remote 在对应阶段完成前不应要求真实远端纵向执行。
  6. 移除或更新 PR 描述中已经过时的阶段、代码量和 crate 估算,补全 Verification/Checklist,并基于最新 main 重新校对。

当前 head 307f4be 没有把上述决定落实到正式设计,因此本轮仍维持 Request changes。完成这些设计级整改后可以再请求复审;不要求在这个文档 PR 中直接提交完整 Rust/TypeScript 实现。

Add comprehensive technical design for BitFun ↔ OpenCode extension host
IPC integration, covering:

- Architecture positioning and crate dependency direction
- Lifecycle state machine (Idle → Starting → Connected → Ready)
- IPC bridge layer design (framing, codec, transport, peer)
- Module structure for new bitfun-ext-host-bridge crate
- Key interfaces (ExtHostBinding port, instance management)
- Error handling and recovery strategy
- Security considerations (token auth, loopback isolation)
- 4-phase implementation plan with effort estimates
- Change impact analysis (~4,376 lines across 5 crates)
@Can2Nya
Can2Nya force-pushed the codex/opencode-ext-host-ipc-design branch from 7a7020f to 87a035d Compare July 31, 2026 03:44
Apply the six-point ext-host security review to the formal design doc
and remove the internal draft that captured the decisions.

- Point 1 (gateway auth): new §4.7a makes per-instance HTTP gateway an
  authority boundary; disabled/unsupported in protocol until capability
  auth (instanceID + connection_generation, high-entropy) is fixed.
- Point 2 (tamper-evident target): downgrade immutable/OS-enforced
  read-only claims to tamper-evident (§2, §2.1, new §2.5); state
  canonical attestation, atomic staging, pre-import revalidation,
  generation fencing, and explicit residual risks.
- Point 3 (cancel terminal): rewrite §4.4 to distinguish
  signal_delivered / invocation_terminal / not_found / connection_lost;
  only terminal states return ordinary Cancelled, else poison/recycle
  and OutcomeUnknown.
- Point 4 (post-import gate): insert stop/approve/restart gate into §4.2
  so contribution expansion beyond the envelope stops the shared Host
  before publication.
- Point 5 (liveness): new §4.8 requires an out-of-band supervisor
  liveness/control channel, or the stall recovery guarantee is deleted.
- Point 6 (lifecycle/rehydration): §3.2 activation-driven single-flight
  Host acquire; §3.1 logical-unload safe restart; new §3.7 rehydration
  contract + three-tier persistence matrix.
- Version: mark @opencode-ai/plugin@1.17.18 as old audit dep; align
  stable fixture to v1.18.9.
- Remote: P0 verifies fail-closed only; real Remote fixture deferred to
  OC-R5 (§2.4, §7.4, §9).
- §9 exit conditions expanded to cover all six decisions.
- Remove .omo/drafts/opencode-ext-host-security-followup.md.
@Can2Nya
Can2Nya force-pushed the codex/opencode-ext-host-ipc-design branch from 87a035d to 13e445d Compare July 31, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants