Skip to content

Commit 6ed677e

Browse files
committed
refactor(extensions): decouple plugin capability publication
Move provider-neutral Agent, Tool, and Skill contribution contracts into product domains while keeping OpenCode parsing and lifecycle in its adapter and composition path. Scope publication generations by owner so future sibling adapters cannot overwrite each other.
1 parent af162a1 commit 6ed677e

25 files changed

Lines changed: 617 additions & 349 deletions

File tree

docs/architecture/extensions/opencode-extension-compatibility.md

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

359+
### 4.4 能力投影与多生态边界
360+
361+
OpenCode 的 Config Hook、contributor 归属和 Tool registration 仍由 OpenCode adapter 解释。adapter 只把已经验证的
362+
Agent、Tool 引用和 workspace Skill 根转换成 `product-domains` 的生态无关贡献;Core 的能力发布模块负责选择原生 Tool
363+
基线、生成 runtime/route identity、原子替换 Agent route,并按 `(workspace, publication owner)` 保存 Skill generation。
364+
因此一个生态更新或撤销时不会覆盖另一个生态的 Skill 贡献。
365+
366+
这条公共边界只覆盖当前已经存在的能力提交语义。DeepSeek Harness 当前仍是静态投影,不执行 Cordis 插件;后续增加
367+
可执行适配时,可以为已经验证过 BitFun owner 语义的能力输出同一贡献类型,但必须保留独立的 Cordis 来源解析、Host
368+
协议、执行句柄和生命周期,也不得进入 OpenCode 的 Config Hook、Hook dispatch 或 Plugin Host 组装路径。当前配置型
369+
Skill 根的扫描、优先级锚点和合并仍属于 OpenCode consumer;DeepSeek Harness 的 Skill 发布要等真实来源与优先级语义确定后
370+
再扩展该 owner。本边界不定义统一 Plugin Host、统一插件协议或跨生态配置模型。
371+
359372
## 5. 大类风险
360373

361374
| 大类 | 整体风险 | 主要控制点 |

docs/architecture/product-architecture.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,9 @@ flowchart LR
653653
但不能访问内部状态、绕过既有编排或复制业务规则。任何入口都不直接调用 Plugin Host。
654654
- 插件只进入扩展贡献接口,不直接写内核状态、工具结果、权限结果或审计事实。
655655
- 当前 package-plugin 路径由 Core 持有 workspace 逻辑实例和贡献代际,沿类型化 `HookFunctionRuntime` 调用
656-
OpenCode adapter;adapter 持有共享 Bun Plugin Host 的 wire、连接和物理生命周期,并复用 services 的进程树原语。
656+
OpenCode adapter;adapter 解释 OpenCode Config/Contributor/Tool 形态后输出生态无关的 Agent、Tool 引用和 Skill 根贡献,
657+
Core 的通用发布模块只把这些贡献提交给既有能力 owner。OpenCode 专属组装路径仍持有 Config 调用和 Tool registration
658+
转换;adapter 持有共享 Bun Plugin Host 的 wire、连接和物理生命周期,并复用 services 的进程树原语。
657659
legacy managed-package 请求可靠性仍由 `PluginRuntimeClient` 承担,standalone `.js` Tool worker 仍由
658660
`ScriptToolRuntime` 承担。三者不共享生命周期对象,也不能互相外推已交付能力。
659661
- 外部来源的 Command、Tool、Subagent、MCP 仍保留能力专属 DTO 和 owner,但它们的发现调度统一由
@@ -662,7 +664,8 @@ flowchart LR
662664
注入 management owner 后可以调用。通用 Server `/ws` 当前没有绑定可信工作区的 management owner,因此返回类型化 `unsupported`;只有注入 Host 持有的作用域化 owner 并通过 WebSocket round-trip 后,Server 才交付该共享边界。
663665
- 每个生态适配层独立保留该生态的外部格式、来源顺序和调用语义,并映射到 BitFun 归属模块;它本身不成为新的
664666
业务归属模块,也不能依赖或修改兄弟生态 adapter。通用目录、`ExternalSourceControlPlane` 和能力归属模块只依赖开放生态 ID、
665-
来源限定身份与能力专属 provider 契约,不按 OpenCode、Codex 或 Claude Code 分支行为。
667+
来源限定身份与能力专属 provider 契约,不按 OpenCode、Codex、Claude Code 或 DeepSeek Harness 分支行为。不同生态
668+
可以复用已存在的中立贡献 DTO 与发布动作,但不因此共享配置解析、Host 协议、执行句柄或生命周期。
666669
- 产品组装是组装根,只在组装期选择能力、服务实现、插件运行时绑定和降级策略。
667670
- 对外能力接口只提供现有归属模块的窄用例、只读状态、事件和明确错误;它不是第二个 Agent Runtime、通用服务
668671
定位器或插件 Host。外部产品扩展、外部 SDK 控制端和“使用外部 Runtime 组装新产品”是三种不同交付路径,

scripts/check-core-boundaries.test.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ test('contract and AI adapter tests keep reviewed feature and failure-domain top
849849
'tests/external_source_contracts/external_hook_catalog_contracts.rs',
850850
'tests/external_source_contracts/external_hook_contribution_contracts.rs',
851851
'tests/external_source_contracts/external_source_contracts.rs',
852+
'tests/external_source_contracts/plugin_capability_contracts.rs',
852853
'tests/external_source_contracts/workspace_reference_contracts.rs',
853854
],
854855
requiredFeatures: ['external-sources'],

scripts/core-boundaries/explicit-test-topology.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ export const productDomainsIntegrationTestTargets = [
186186
'tests/external_source_contracts/external_hook_catalog_contracts.rs',
187187
'tests/external_source_contracts/external_hook_contribution_contracts.rs',
188188
'tests/external_source_contracts/external_source_contracts.rs',
189+
'tests/external_source_contracts/plugin_capability_contracts.rs',
189190
'tests/external_source_contracts/workspace_reference_contracts.rs',
190191
],
191192
requiredFeatures: ['external-sources'],

scripts/core-boundaries/rules/source/forbidden-rules.mjs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,19 @@ const agentRuntimeRootUnexpectedLine = new RegExp(
88
);
99

1010
export const forbiddenContentRules = [
11+
{
12+
path: 'src/crates/assembly/core/src/plugin_capability_publication.rs',
13+
reason:
14+
'generic plugin capability publication consumes provider-neutral product contracts and must not absorb ecosystem config, Host wire, or adapter lifecycle',
15+
patterns: [
16+
{
17+
regex:
18+
/\b(?:OpenCode|DeepSeek|HookFunction|serde_json|bitfun_[a-z0-9_]+_adapter)\b/,
19+
message:
20+
'plugin capability publication must not depend on ecosystem adapters, raw config, or Host runtime contracts',
21+
},
22+
],
23+
},
1124
{
1225
path: 'Cargo.toml',
1326
reason:
@@ -4150,7 +4163,7 @@ export const forbiddenContentUnderRules = [
41504163
'src/crates/assembly/core/src/external_sources.rs',
41514164
'src/crates/assembly/core/src/external_hooks.rs',
41524165
'src/crates/assembly/core/src/instruction_sources.rs',
4153-
'src/crates/assembly/core/src/plugin_config_publication.rs',
4166+
'src/crates/assembly/core/src/plugin_host.rs',
41544167
],
41554168
message:
41564169
'only a reviewed product composition root may import bitfun-opencode-adapter through a capability-specific provider boundary',

scripts/core-boundaries/rules/source/public-api-rules.mjs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,12 +296,8 @@ export const opencodeAdapterPublicApiEntries = [
296296
),
297297
...[
298298
'project_plugin_config',
299-
'OpenCodePluginAgentProjection',
300-
'OpenCodePluginConfigProjection',
299+
'project_plugin_tool_ref',
301300
'OpenCodePluginConfigProjectionError',
302-
'OpenCodePluginContributor',
303-
'OpenCodePluginSkillRootProjection',
304-
'OpenCodePluginToolRef',
305301
].map(opencodePluginConfigProjectionEntry),
306302
opencodeAdapterEntry(
307303
'OpenCodeCommandProvider',
@@ -608,6 +604,27 @@ export const externalHookContractPublicApiEntries = [
608604
),
609605
);
610606

607+
export const pluginCapabilityProjectionPublicApiEntries = [
608+
'PluginContributorIdentity',
609+
'PluginToolRef',
610+
'PluginAgentProjection',
611+
'PluginSkillRootContribution',
612+
'PluginCapabilityProjection',
613+
].map((symbol) => ({
614+
symbol,
615+
owner: 'product-domains plugin capability projection contract owner',
616+
consumer: 'ecosystem plugin adapters and bitfun-core capability publication',
617+
verification:
618+
'product-domain projection contract tests, OpenCode projection tests, bitfun-core publication tests, and core-boundary checks',
619+
p0: 'provider-neutral Agent, Tool, and Skill contributions from executable plugin adapters',
620+
contractSlice: contractSlices.bitfunPluginExtension,
621+
wireImpact: false,
622+
rationale:
623+
'sibling executable plugin adapters need one typed contribution shape without sharing source formats, Host protocols, or lifecycle',
624+
exit:
625+
'remove only through a reviewed capability-publication migration with equivalent owner isolation and adapter-boundary tests',
626+
}));
627+
611628
export const externalHookCatalogPublicApiEntries = [
612629
'EXTERNAL_HOOK_CATALOG_SCHEMA_V1',
613630
'ExternalHookSourceKind',
@@ -1419,6 +1436,12 @@ export const publicApiAllowlistRules = [
14191436
'external source contracts must stay capability-specific, ecosystem-neutral, and explicitly consumer-backed',
14201437
allowedSymbolEntries: externalSourceContractPublicApiEntries,
14211438
},
1439+
{
1440+
path: 'src/crates/contracts/product-domains/src/plugin_capabilities.rs',
1441+
reason:
1442+
'executable plugin capability projections must stay ecosystem-neutral, behavior-light, and explicitly consumer-backed',
1443+
allowedSymbolEntries: pluginCapabilityProjectionPublicApiEntries,
1444+
},
14221445
{
14231446
path: 'src/crates/contracts/product-domains/src/external_subagents.rs',
14241447
reason:

scripts/core-boundaries/self-test.mjs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,22 @@ export function runManifestParserSelfTest({
14301430
throw new Error(`plugin runtime boundary rule must forbid: ${forbiddenContract}`);
14311431
}
14321432
}
1433+
const pluginCapabilityPublicationRuleText = forbiddenRuleTextForPath(
1434+
'src/crates/assembly/core/src/plugin_capability_publication.rs',
1435+
);
1436+
for (const forbiddenDependency of [
1437+
'OpenCode',
1438+
'DeepSeek',
1439+
'HookFunction',
1440+
'serde_json',
1441+
'bitfun_[a-z0-9_]+_adapter',
1442+
]) {
1443+
if (!pluginCapabilityPublicationRuleText.includes(forbiddenDependency)) {
1444+
throw new Error(
1445+
`plugin capability publication boundary must forbid: ${forbiddenDependency}`,
1446+
);
1447+
}
1448+
}
14331449
const pluginPublicApiRule = publicApiAllowlistRules.find(
14341450
(rule) => rule.path === 'src/crates/contracts/runtime-ports/src/plugin.rs',
14351451
);
@@ -1457,6 +1473,9 @@ export function runManifestParserSelfTest({
14571473
const externalSubagentPublicApiRule = publicApiAllowlistRules.find(
14581474
(rule) => rule.path === 'src/crates/contracts/product-domains/src/external_subagents.rs',
14591475
);
1476+
const pluginCapabilityProjectionPublicApiRule = publicApiAllowlistRules.find(
1477+
(rule) => rule.path === 'src/crates/contracts/product-domains/src/plugin_capabilities.rs',
1478+
);
14601479
const externalHookPublicApiRule = publicApiAllowlistRules.find(
14611480
(rule) => rule.path === 'src/crates/contracts/product-domains/src/external_hook_contributions.rs',
14621481
);
@@ -1604,12 +1623,26 @@ export function runManifestParserSelfTest({
16041623
).map((entry) => entry.symbol);
16051624
if (
16061625
opencodeAdapterPublicApiSymbols.join(',') !==
1607-
'load_opencode_package_adapter,load_opencode_config_snapshot,OpenCodeConfigSnapshot,OpenCodeConfigSnapshotError,project_plugin_config,OpenCodePluginAgentProjection,OpenCodePluginConfigProjection,OpenCodePluginConfigProjectionError,OpenCodePluginContributor,OpenCodePluginSkillRootProjection,OpenCodePluginToolRef,OpenCodeCommandProvider,OpenCodeCommandProviderOptions,OpenCodeConfiguredSkillRoot,OpenCodeSkillRootProvider,OpenCodeSkillRootProviderOptions,OpenCodeToolProvider,OpenCodeToolProviderOptions,OpenCodeSubagentProvider,OpenCodeSubagentProviderOptions,OpenCodeMcpProvider,OpenCodeMcpProviderOptions,OpenCodeHookProvider,OpenCodeHookProviderOptions,OpenCodeWorkspaceReferenceProvider,OpenCodeWorkspaceReferenceProviderOptions,load_opencode_user_instructions,OpenCodeInstructionSourceOptions'
1626+
'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'
16081627
) {
16091628
throw new Error(
16101629
'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',
16111630
);
16121631
}
1632+
if (!pluginCapabilityProjectionPublicApiRule) {
1633+
throw new Error('plugin capability projection must have a public API budget rule');
1634+
}
1635+
const pluginCapabilityProjectionSymbols = (
1636+
pluginCapabilityProjectionPublicApiRule.allowedSymbolEntries || []
1637+
).map((entry) => entry.symbol);
1638+
if (
1639+
pluginCapabilityProjectionSymbols.join(',') !==
1640+
'PluginContributorIdentity,PluginToolRef,PluginAgentProjection,PluginSkillRootContribution,PluginCapabilityProjection'
1641+
) {
1642+
throw new Error(
1643+
'plugin capability projection public API must stay limited to the reviewed provider-neutral contribution DTOs',
1644+
);
1645+
}
16131646
const opencodeInstructionSymbols = new Set([
16141647
'load_opencode_user_instructions',
16151648
'OpenCodeInstructionSourceOptions',

src/apps/cli/src/dispatch/runner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,6 @@ mod tests {
501501
let mut leader = command.spawn().expect("spawn process-group leader");
502502
let process_group = i32::try_from(leader.id()).expect("safe pid");
503503
let _guard = ProcessGroupGuard(process_group);
504-
assert!(worker_process_alive(process_group as u32, job_id));
505504
for _ in 0..100 {
506505
if ready_path.is_file() {
507506
break;
@@ -512,6 +511,7 @@ mod tests {
512511
ready_path.is_file(),
513512
"TERM-resistant child must be ready before cancellation"
514513
);
514+
assert!(worker_process_alive(process_group as u32, job_id));
515515
let reaper = std::thread::spawn(move || leader.wait());
516516

517517
let error = terminate_worker(process_group as u32, job_id)

src/crates/adapters/dsh-adapter/AGENTS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ It does not execute Cordis plugins, install npm packages, or depend on a
1111
user-local `dsh` CLI. Execution of dsh bundles belongs to future Plugin Host /
1212
external-ACP work, not this adapter boundary.
1313

14+
If executable dsh support is added, the dsh adapter may project capabilities
15+
whose BitFun owner semantics have been verified through the provider-neutral
16+
plugin capability contract. Cordis source parsing, execution handles, Host
17+
protocol, and lifecycle remain dsh-owned and must not reuse the OpenCode Config
18+
Hook or OpenCode Plugin Host composition path. The current configured Skill-root
19+
merge and precedence behavior remains OpenCode-owned; dsh Skill publication
20+
requires its own consumer evidence before that path is shared.
21+
1422
## Boundary Rules
1523

1624
- Depend on stable contracts (`bitfun-runtime-ports`, `bitfun-product-domains`)

src/crates/adapters/opencode-adapter/AGENTS-CN.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ Agent/权限/Plugin Tool/Skill 投影,以及 Command、standalone Tool 和 Sub
3838
- 依赖 `bitfun-runtime-ports` 等稳定接口和 `PluginRuntimeAdapter` 边界 trait,不依赖
3939
`bitfun-core`、app crate、Tauri API、产品界面或具体服务管理器。
4040
- OpenCode 配置 JSON、来源顺序、加载器兼容、参数展开和受管 Plugin Host Config 贡献归属保留在本 crate 内。
41-
跨 crate 输出使用类型化来源快照、Agent/权限/Plugin Tool/Skill 投影、adapter 绑定和 `PluginRuntimeClient` DTO,
41+
跨 crate 输出使用类型化来源快照、生态无关的 Agent/权限/Plugin Tool/Skill 贡献、adapter 绑定和 `PluginRuntimeClient` DTO,
4242
不得把 OpenCode 原始 JSON 或源码语法暴露为产品接口。Config 投影不得生成 Agent runtime key、选择原生 Tool
43-
基线、修改产品 registry 或扩成通用 Config 平台。
43+
基线、修改产品 registry 或扩成通用 Config 平台;OpenCode contributor 解析和 Tool registration 转换仍留在本 crate,
44+
不得让 OpenCode 专属 DTO 进入通用发布模块。
4445
- 当前源码探测只识别测试覆盖的声明式语法子集,不是通用 JS/TS 解析器;没有可识别入口的包和已识别但不支持的 hook
4546
必须返回诊断,其他语法不属于当前兼容范围。
4647
- 未支持的 OpenCode 能力必须显式返回类型化诊断或不支持状态,不得静默忽略。
@@ -49,7 +50,7 @@ Agent/权限/Plugin Tool/Skill 投影,以及 Command、standalone Tool 和 Sub
4950
- 经评审的产品组装根只选择并构造已编译的 OpenCode adapter/provider,再注入 `PluginRuntimeClient`;它不发现
5051
动态来源、不准备依赖,也不 import 插件模块。
5152
- Product Assembly 只允许从经过评审的组装模块(如 `bitfun-core/plugin_runtime`
52-
`bitfun-core/external_sources``bitfun-core/plugin_config_publication`)消费本 crate;增加其他消费方时必须同步
53+
`bitfun-core/external_sources`OpenCode 专属的 `bitfun-core/plugin_host` 组装路径)消费本 crate;增加其他消费方时必须同步
5354
边界脚本和聚焦组装路径测试。
5455
- 本 crate 不得依赖 Codex、Claude Code 或其他生态 adapter。新生态是由 Product Assembly 注册的同级 adapter,
5556
不是本 adapter 的模式。

0 commit comments

Comments
 (0)