Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 18 additions & 3 deletions docs/architecture/extensions/opencode-extension-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ Tool 执行与取消、dispose,以及插件反向调用的 metadata/ask。Open
失败会停止当前有序 Hook 链;after 失败只把已执行结果标成错误并反馈给模型,不进入 Tool 重试。

Config Hook 的输入复用 adapter 现有本地来源计划,按 user global、显式文件、project、配置目录和 inline 的顺序
合并完整 JSON/JSONC 对象;`$schema` 不是必填项,未知字段原样保留。Config Hook、插件 Agent/权限/Skill 投影、Tool
注册和模型可见 output 已接入现有归属模块。插件 Tool 的真实 `title`、`output`、`metadata` 会进入完整 after Hook 链,
合并完整 JSON/JSONC 对象;`$schema` 不是必填项,未知字段原样保留。OpenCode adapter 负责 Config contributor 归属及
Agent/权限/Plugin Tool/Skill 字段的类型化投影;Core 只负责现有产品 owner 对接、runtime key 绑定和 generation 原子提交。
Config Hook、插件 Agent/权限/Skill 投影、Tool 注册和模型可见 output 已接入现有归属模块。插件 Tool 的真实
`title`、`output`、`metadata` 会进入完整 after Hook 链,
原始结构化结果继续保留这些字段;after Hook 对 title/metadata 的变换尚无稳定 UI/持久化消费方,当前只把变换后的
output 作为模型展示结果,后续有真实消费方时再扩展小型展示契约。

Expand Down Expand Up @@ -354,6 +356,19 @@ Node 进程永久累积;这不是 package-plugin 的 workspace-scoped runtime
执行版本记录不是源码备份。软件包或文件的完整旧版本副本仍在且摘要匹配时可以重建;本地原位源码已变化、
旧 worker 又丢失时不能从当前来源重建后仍称为旧版本。此时只允许准备当前来源或等待用户恢复源码。

### 4.4 能力投影与多生态边界

OpenCode 的 Config Hook、contributor 归属和 Tool registration 仍由 OpenCode adapter 解释。adapter 只把已经验证的
Agent、Tool 引用和 workspace Skill 根转换成 `product-domains` 的生态无关贡献;Core 的能力发布模块负责选择原生 Tool
基线、生成 runtime/route identity、原子替换 Agent route,并按 `(workspace, publication owner)` 保存 Skill generation。
因此一个生态更新或撤销时不会覆盖另一个生态的 Skill 贡献。

这条公共边界只覆盖当前已经存在的能力提交语义。DeepSeek Harness 当前仍是静态投影,不执行 Cordis 插件;后续增加
可执行适配时,可以为已经验证过 BitFun owner 语义的能力输出同一贡献类型,但必须保留独立的 Cordis 来源解析、Host
协议、执行句柄和生命周期,也不得进入 OpenCode 的 Config Hook、Hook dispatch 或 Plugin Host 组装路径。当前配置型
Skill 根的扫描、优先级锚点和合并仍属于 OpenCode consumer;DeepSeek Harness 的 Skill 发布要等真实来源与优先级语义确定后
再扩展该 owner。本边界不定义统一 Plugin Host、统一插件协议或跨生态配置模型。

## 5. 大类风险

| 大类 | 整体风险 | 主要控制点 |
Expand Down Expand Up @@ -386,7 +401,7 @@ Node 进程永久累积;这不是 package-plugin 的 workspace-scoped runtime
| 代际替换与长时 backend 写请求精确并发 | 后续可靠性 PR | 当前会停止新 Hook/Tool、取消 instance stream 并 dispose,但尚无 instance-scoped backend RPC admission/drain;极端并发下旧代已接收的写请求可能晚到完成 | 在 backend bridge 增加 instance-scoped 拒绝新请求和有界 drain;超时标记 OutcomeUnknown,且不得确认 replacement 完成。 |
| 多 workspace 同名 Tool 激活与退役精确并发 | 后续可靠性 PR | 当前 mux 路由与全局注册表分别受锁保护;极端的最后一条旧路由退役和新路由激活交错时,可能短暂撤下仍有新路由的 mux | 统一两层状态的锁序或保留空 mux,并增加同名 Tool 激活/退役并发测试;常规顺序切换和 workspace 隔离已由本 PR 覆盖。 |
| workspace 在扫描前已被删除或移动 | 后续清理 PR | workspace 路径无法规范化时不会猜测等价身份;已激活实例可能保留到显式停用或应用退出 | 保存已确认的 canonical identity,并在来源撤销通知中按该 identity 退役;补删除、移动和符号链接变化样例。 |
| Core 内遗留的 OpenCode Config 投影细节 | 后续边界收敛 PR | 当前 Core 正确拥有 generation 原子提交及 Agent/权限/Skill owner 对接,但仍直接解释 `agent.mode/hidden/temperature/prompt/permission` 与 `skills.paths`,形成明确的临时模块边界债务 | 由 OpenCode adapter 输出最小的类型化 Agent、权限约束和 workspace Skill root 投影;Core 只做现有 owner 校验、runtime key 绑定与原子提交。不得新增通用 Config 平台或第二套 Runtime。 |
| Core 内遗留的 OpenCode Client wire 投影 | 后续边界收敛 PR | 当前 loopback adapter 已拥有认证、framing、route/method 匹配和传输错误,但受限的 `client.*` bridge 仍在 Core 解析部分 query/body 并生成 wire JSON;普通插件链路已闭环,协议演进仍可能触及 Product Assembly | 按真实插件消费到的 route 分批把 wire DTO、解析和响应投影移入 `opencode-plugin-host`,向 Core 暴露最小 typed operation/result 并增加 boundary gate;不引入通用 HTTP transport、第二套产品协议或一次性重写。 |
| existing-session 激活失败的诊断归属 | 后续诊断 PR | 当前 create-session 失败按 workspace 记录;已有 Session 的 ensure 失败缺少已解析 execution root,可能显示为全局诊断,但不会改变原生 Session/Turn 结果 | 让 ensure 返回 workspace 与错误的组合,并补 workspace A 失败不污染 workspace B 的状态测试;不改变插件激活或执行语义。 |
| existing-session 每次 ensure 的 prepare 成本 | 后续性能 PR | 当前每个恢复触发点都会重新 prepare,再按稳定摘要复用实例;结果正确,但大本地源码树会增加文件扫描和一次 Host RPC | 在来源 watcher/config revision 已有事实之上增加健康 generation 快路径;失去健康或版本事实时仍执行完整 prepare,不使用固定 TTL 猜测。 |

Expand Down
7 changes: 5 additions & 2 deletions docs/architecture/product-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,9 @@ flowchart LR
但不能访问内部状态、绕过既有编排或复制业务规则。任何入口都不直接调用 Plugin Host。
- 插件只进入扩展贡献接口,不直接写内核状态、工具结果、权限结果或审计事实。
- 当前 package-plugin 路径由 Core 持有 workspace 逻辑实例和贡献代际,沿类型化 `HookFunctionRuntime` 调用
OpenCode adapter;adapter 持有共享 Bun Plugin Host 的 wire、连接和物理生命周期,并复用 services 的进程树原语。
OpenCode adapter;adapter 解释 OpenCode Config/Contributor/Tool 形态后输出生态无关的 Agent、Tool 引用和 Skill 根贡献,
Core 的通用发布模块只把这些贡献提交给既有能力 owner。OpenCode 专属组装路径仍持有 Config 调用和 Tool registration
转换;adapter 持有共享 Bun Plugin Host 的 wire、连接和物理生命周期,并复用 services 的进程树原语。
legacy managed-package 请求可靠性仍由 `PluginRuntimeClient` 承担,standalone `.js` Tool worker 仍由
`ScriptToolRuntime` 承担。三者不共享生命周期对象,也不能互相外推已交付能力。
- 外部来源的 Command、Tool、Subagent、MCP 仍保留能力专属 DTO 和 owner,但它们的发现调度统一由
Expand All @@ -662,7 +664,8 @@ flowchart LR
注入 management owner 后可以调用。通用 Server `/ws` 当前没有绑定可信工作区的 management owner,因此返回类型化 `unsupported`;只有注入 Host 持有的作用域化 owner 并通过 WebSocket round-trip 后,Server 才交付该共享边界。
- 每个生态适配层独立保留该生态的外部格式、来源顺序和调用语义,并映射到 BitFun 归属模块;它本身不成为新的
业务归属模块,也不能依赖或修改兄弟生态 adapter。通用目录、`ExternalSourceControlPlane` 和能力归属模块只依赖开放生态 ID、
来源限定身份与能力专属 provider 契约,不按 OpenCode、Codex 或 Claude Code 分支行为。
来源限定身份与能力专属 provider 契约,不按 OpenCode、Codex、Claude Code 或 DeepSeek Harness 分支行为。不同生态
可以复用已存在的中立贡献 DTO 与发布动作,但不因此共享配置解析、Host 协议、执行句柄或生命周期。
- 产品组装是组装根,只在组装期选择能力、服务实现、插件运行时绑定和降级策略。
- 对外能力接口只提供现有归属模块的窄用例、只读状态、事件和明确错误;它不是第二个 Agent Runtime、通用服务
定位器或插件 Host。外部产品扩展、外部 SDK 控制端和“使用外部 Runtime 组装新产品”是三种不同交付路径,
Expand Down
1 change: 1 addition & 0 deletions scripts/check-core-boundaries.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ test('contract and AI adapter tests keep reviewed feature and failure-domain top
'tests/external_source_contracts/external_hook_catalog_contracts.rs',
'tests/external_source_contracts/external_hook_contribution_contracts.rs',
'tests/external_source_contracts/external_source_contracts.rs',
'tests/external_source_contracts/plugin_capability_contracts.rs',
'tests/external_source_contracts/workspace_reference_contracts.rs',
],
requiredFeatures: ['external-sources'],
Expand Down
1 change: 1 addition & 0 deletions scripts/core-boundaries/explicit-test-topology.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export const productDomainsIntegrationTestTargets = [
'tests/external_source_contracts/external_hook_catalog_contracts.rs',
'tests/external_source_contracts/external_hook_contribution_contracts.rs',
'tests/external_source_contracts/external_source_contracts.rs',
'tests/external_source_contracts/plugin_capability_contracts.rs',
'tests/external_source_contracts/workspace_reference_contracts.rs',
],
requiredFeatures: ['external-sources'],
Expand Down
2 changes: 1 addition & 1 deletion scripts/core-boundaries/rules/feature-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ export const capabilityContractDependencyRules = [
capabilityEdge(['agent-api', 'permission', 'workspace-ports']),
])],
['bitfun-opencode-adapter', capabilityConsumer([
capabilityEdge(['plugin-runtime']),
capabilityEdge(['hook-function-runtime', 'plugin-runtime']),
capabilityEdge(['script-tool-runtime'], { kind: 'dev' }),
])],
['bitfun-opencode-plugin-host', capabilityConsumer([
Expand Down
14 changes: 14 additions & 0 deletions scripts/core-boundaries/rules/source/forbidden-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ const agentRuntimeRootUnexpectedLine = new RegExp(
);

export const forbiddenContentRules = [
{
path: 'src/crates/assembly/core/src/plugin_capability_publication.rs',
reason:
'generic plugin capability publication consumes provider-neutral product contracts and must not absorb ecosystem config, Host wire, or adapter lifecycle',
patterns: [
{
regex:
/\b(?:OpenCode|DeepSeek|HookFunction|serde_json|bitfun_[a-z0-9_]+_adapter)\b/,
message:
'plugin capability publication must not depend on ecosystem adapters, raw config, or Host runtime contracts',
},
],
},
{
path: 'Cargo.toml',
reason:
Expand Down Expand Up @@ -4150,6 +4163,7 @@ export const forbiddenContentUnderRules = [
'src/crates/assembly/core/src/external_sources.rs',
'src/crates/assembly/core/src/external_hooks.rs',
'src/crates/assembly/core/src/instruction_sources.rs',
'src/crates/assembly/core/src/plugin_host.rs',
],
message:
'only a reviewed product composition root may import bitfun-opencode-adapter through a capability-specific provider boundary',
Expand Down
51 changes: 50 additions & 1 deletion scripts/core-boundaries/rules/source/public-api-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,23 @@ function opencodeHookAdapterEntry(symbol, consumer) {
};
}

function opencodePluginConfigProjectionEntry(symbol) {
return {
symbol,
owner: 'opencode-adapter managed Plugin Host Config projection owner',
consumer: 'bitfun-core plugin Config publication composition root',
verification:
'OpenCode plugin Config projection tests, bitfun-core publication tests, and core-boundary public API budget checks',
p0: 'typed OpenCode Config Hook projection for the managed Plugin Host runtime slice',
contractSlice: contractSlices.opencodeAdapterBoundary,
wireImpact: false,
rationale:
'the live Plugin Host path needs OpenCode field interpretation and contributor attribution outside Product Assembly while Core retains product publication',
exit:
'remove only if the managed Plugin Host Config projection moves behind another reviewed OpenCode adapter boundary with equivalent attribution and fail-closed tests',
};
}

function opencodeReferenceAdapterEntry(symbol, consumer) {
return {
symbol,
Expand Down Expand Up @@ -277,6 +294,11 @@ export const opencodeAdapterPublicApiEntries = [
'OpenCodeConfigSnapshotError',
'bitfun-core live Plugin Host config validation and OpenCode config snapshot contract tests',
),
...[
'project_plugin_config',
'project_plugin_tool_ref',
'OpenCodePluginConfigProjectionError',
].map(opencodePluginConfigProjectionEntry),
opencodeAdapterEntry(
'OpenCodeCommandProvider',
'bitfun-core external source composition root and OpenCode command adapter tests',
Expand Down Expand Up @@ -582,6 +604,27 @@ export const externalHookContractPublicApiEntries = [
),
);

export const pluginCapabilityProjectionPublicApiEntries = [
'PluginContributorIdentity',
'PluginToolRef',
'PluginAgentProjection',
'PluginSkillRootContribution',
'PluginCapabilityProjection',
].map((symbol) => ({
symbol,
owner: 'product-domains plugin capability projection contract owner',
consumer: 'ecosystem plugin adapters and bitfun-core capability publication',
verification:
'product-domain projection contract tests, OpenCode projection tests, bitfun-core publication tests, and core-boundary checks',
p0: 'provider-neutral Agent, Tool, and Skill contributions from executable plugin adapters',
contractSlice: contractSlices.bitfunPluginExtension,
wireImpact: false,
rationale:
'sibling executable plugin adapters need one typed contribution shape without sharing source formats, Host protocols, or lifecycle',
exit:
'remove only through a reviewed capability-publication migration with equivalent owner isolation and adapter-boundary tests',
}));

export const externalHookCatalogPublicApiEntries = [
'EXTERNAL_HOOK_CATALOG_SCHEMA_V1',
'ExternalHookSourceKind',
Expand Down Expand Up @@ -1339,7 +1382,7 @@ export const publicApiAllowlistRules = [
{
path: 'src/crates/adapters/opencode-adapter/src/lib.rs',
reason:
'OpenCode adapter public API must stay limited to reviewed runtime-free sources and the PluginRuntimeClient adapter boundary',
'OpenCode adapter public API must stay limited to reviewed sources, managed Plugin Host Config projection, and the PluginRuntimeClient adapter boundary',
allowedSymbolEntries: opencodeAdapterPublicApiEntries,
},
{
Expand Down Expand Up @@ -1393,6 +1436,12 @@ export const publicApiAllowlistRules = [
'external source contracts must stay capability-specific, ecosystem-neutral, and explicitly consumer-backed',
allowedSymbolEntries: externalSourceContractPublicApiEntries,
},
{
path: 'src/crates/contracts/product-domains/src/plugin_capabilities.rs',
reason:
'executable plugin capability projections must stay ecosystem-neutral, behavior-light, and explicitly consumer-backed',
allowedSymbolEntries: pluginCapabilityProjectionPublicApiEntries,
},
{
path: 'src/crates/contracts/product-domains/src/external_subagents.rs',
reason:
Expand Down
37 changes: 35 additions & 2 deletions scripts/core-boundaries/self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,22 @@ export function runManifestParserSelfTest({
throw new Error(`plugin runtime boundary rule must forbid: ${forbiddenContract}`);
}
}
const pluginCapabilityPublicationRuleText = forbiddenRuleTextForPath(
'src/crates/assembly/core/src/plugin_capability_publication.rs',
);
for (const forbiddenDependency of [
'OpenCode',
'DeepSeek',
'HookFunction',
'serde_json',
'bitfun_[a-z0-9_]+_adapter',
]) {
if (!pluginCapabilityPublicationRuleText.includes(forbiddenDependency)) {
throw new Error(
`plugin capability publication boundary must forbid: ${forbiddenDependency}`,
);
}
}
const pluginPublicApiRule = publicApiAllowlistRules.find(
(rule) => rule.path === 'src/crates/contracts/runtime-ports/src/plugin.rs',
);
Expand Down Expand Up @@ -1457,6 +1473,9 @@ export function runManifestParserSelfTest({
const externalSubagentPublicApiRule = publicApiAllowlistRules.find(
(rule) => rule.path === 'src/crates/contracts/product-domains/src/external_subagents.rs',
);
const pluginCapabilityProjectionPublicApiRule = publicApiAllowlistRules.find(
(rule) => rule.path === 'src/crates/contracts/product-domains/src/plugin_capabilities.rs',
);
const externalHookPublicApiRule = publicApiAllowlistRules.find(
(rule) => rule.path === 'src/crates/contracts/product-domains/src/external_hook_contributions.rs',
);
Expand Down Expand Up @@ -1604,10 +1623,24 @@ export function runManifestParserSelfTest({
).map((entry) => entry.symbol);
if (
opencodeAdapterPublicApiSymbols.join(',') !==
'load_opencode_package_adapter,load_opencode_config_snapshot,OpenCodeConfigSnapshot,OpenCodeConfigSnapshotError,OpenCodeCommandProvider,OpenCodeCommandProviderOptions,OpenCodeConfiguredSkillRoot,OpenCodeSkillRootProvider,OpenCodeSkillRootProviderOptions,OpenCodeToolProvider,OpenCodeToolProviderOptions,OpenCodeSubagentProvider,OpenCodeSubagentProviderOptions,OpenCodeMcpProvider,OpenCodeMcpProviderOptions,OpenCodeHookProvider,OpenCodeHookProviderOptions,OpenCodeWorkspaceReferenceProvider,OpenCodeWorkspaceReferenceProviderOptions,load_opencode_user_instructions,OpenCodeInstructionSourceOptions'
'load_opencode_package_adapter,load_opencode_config_snapshot,OpenCodeConfigSnapshot,OpenCodeConfigSnapshotError,project_plugin_config,project_plugin_tool_ref,OpenCodePluginConfigProjectionError,OpenCodeCommandProvider,OpenCodeCommandProviderOptions,OpenCodeConfiguredSkillRoot,OpenCodeSkillRootProvider,OpenCodeSkillRootProviderOptions,OpenCodeToolProvider,OpenCodeToolProviderOptions,OpenCodeSubagentProvider,OpenCodeSubagentProviderOptions,OpenCodeMcpProvider,OpenCodeMcpProviderOptions,OpenCodeHookProvider,OpenCodeHookProviderOptions,OpenCodeWorkspaceReferenceProvider,OpenCodeWorkspaceReferenceProviderOptions,load_opencode_user_instructions,OpenCodeInstructionSourceOptions'
) {
throw new Error(
'OpenCode adapter public API budget must stay limited to the reviewed package factory, managed Plugin Host Config projection, and capability-specific command, configured Skill root, tool, subagent, MCP, static Hook, workspace Reference, and user Instruction providers',
);
}
if (!pluginCapabilityProjectionPublicApiRule) {
throw new Error('plugin capability projection must have a public API budget rule');
}
const pluginCapabilityProjectionSymbols = (
pluginCapabilityProjectionPublicApiRule.allowedSymbolEntries || []
).map((entry) => entry.symbol);
if (
pluginCapabilityProjectionSymbols.join(',') !==
'PluginContributorIdentity,PluginToolRef,PluginAgentProjection,PluginSkillRootContribution,PluginCapabilityProjection'
) {
throw new Error(
'OpenCode adapter public API budget must stay limited to the reviewed package factory and capability-specific command, configured Skill root, tool, subagent, MCP, static Hook, workspace Reference, and user Instruction providers',
'plugin capability projection public API must stay limited to the reviewed provider-neutral contribution DTOs',
);
}
const opencodeInstructionSymbols = new Set([
Expand Down
Loading
Loading