Why: The base must stay unmodified by downstream products, so the roadmap, compatibility window, and plugin contract need a written proposal before further forks. Evidence: Three proposals under docs/proposals and a short pointer in the plugin development guide. Impact: Downstream work follows extension-point requests and a published base version. Tag commands in the compat proposal are not executed. Refs: .task/feature-base-extension-roadmap.md Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
4.0 KiB
Markdown
80 lines
4.0 KiB
Markdown
# 插件契约 v2
|
||
|
||
状态:**实施中(2026-10-01)**;owner:weichen;主任务:Dev 仓 `feature/base-extension-roadmap`。
|
||
撰写日期:2026-10-01。配套:[路线图](2026-10-01-base-extension-roadmap.md)、[兼容规则](2026-10-01-extension-compat-policy.md)。
|
||
|
||
## 1. 四种形态
|
||
|
||
下游按需要选一种或组合。基座对四种形态使用同一份 manifest 与同一套 fail-closed:任一资产解析失败则整包拒绝,保留上一状态。
|
||
|
||
| 形态 | 下游交付 | 基座职责 |
|
||
|---|---|---|
|
||
| 声明 | `plugin.json` + 资产目录 | 注册 tools/skills/mcp/knowledge/parsers/agents/channels/pages/commands/… |
|
||
| 进程内编译 | 下游 `main` import `pkg/extension` 并 `RegisterCompiled` | `link` + builder 模式产出**下游自己的**单二进制 |
|
||
| sidecar | 原生进程,manifest `backend.mode=sidecar` | 拉起、健康(进程存活)、停止;环境变量注入 Host API |
|
||
| 前端 ESM | `pages/*.json` 指向模块 | UI SDK 只暴露命令、SSE 与 `@ui` 组件;页面不得另起设计令牌 |
|
||
|
||
进程内编译适合要事务一致性的领域逻辑。sidecar 适合异构语言或强隔离。声明式 HTTP 工具仍然只描述调用,不携带任意代码。
|
||
|
||
## 2. manifest 增补
|
||
|
||
`apiVersion` 用 `2.0-experimental` 才强制网络白名单。字段见 `modules/core/plugin_v2.go`。
|
||
|
||
- `provides.skills`:`skills/<name>.md`,宿主写入 `<data>/skills/plugin-<id>/`。
|
||
- `provides.mcpServers`:并入小队 MCP 配置(与 `tools.mcp.servers` 合并)。
|
||
- `provides.knowledge|parsers|agents|channels|pages`:声明登记,供清单 API 与前端加载。
|
||
- `scope.tenants/projects`:空 = 全部可见。
|
||
- `backend.command`:sidecar 可执行文件(相对插件目录或 PATH)。`schemaVersion` 记入 `plugin_schema`。
|
||
|
||
密钥查找顺序:`secrets.<pluginId>.<authRef>`(按工具执行时的 `OwnerID`),再回落同名全局设置。不再在绑定期固定读 `OwnerDefault`。
|
||
|
||
## 3. Host API(sidecar 与外部进程)
|
||
|
||
监听由基座在本机拉起,令牌随进程环境变量 `AGENT_HOST_API`、`AGENT_HOST_TOKEN` 下发。
|
||
|
||
| 方法 | 路径 | 作用 |
|
||
|---|---|---|
|
||
| PUT/GET | `/host/v1/kv/{key}` | 插件隔离 KV(带 owner 与 plugin id) |
|
||
| POST | `/host/v1/audit` | 写通用审计 |
|
||
| POST | `/host/v1/commands/{id}` | 走命令总线 |
|
||
| POST | `/host/v1/llm` | 代理聊天补全;未配置模型返回 503,不静默伪造 |
|
||
| GET | `/host/v1/knowledge/search` | 混合检索(引擎在基座) |
|
||
|
||
插件故障不拖垮基座:sidecar 退出只记日志与审计,不退出主进程。
|
||
|
||
## 4. 进程内注册
|
||
|
||
```go
|
||
func init() {
|
||
extension.RegisterCompiled(extension.InProc{
|
||
ID: "rdm",
|
||
Register: func(reg extension.Registry) error {
|
||
return reg.AddSkill("rdm-note", "# …")
|
||
},
|
||
})
|
||
}
|
||
```
|
||
|
||
下游 main:
|
||
|
||
```go
|
||
import _ "agent-system-backend/pkg/extension/link"
|
||
func main() { extension.Main(extension.Options{Addr: "127.0.0.1:8787"}) }
|
||
```
|
||
|
||
## 5. 前端
|
||
|
||
`@ui` 导出 `createPluginBridge`:`execCommand` 与 `subscribeEvents`。插件页面模块默认导出 `mount(el, bridge)` / `unmount()`。宿主按 `pages[].module` 动态 `import()`。插件 TSX 与基座一样受 design-guard 约束(令牌、无私人 keyframes、无行内字号)。
|
||
|
||
## 6. 生命周期
|
||
|
||
- 启用:`POST /api/plugins/{id}/enabled`,按租户记 `plugin_grants`。
|
||
- 升级:`backend.schemaVersion` 大于已记录版本时调用宿主迁移钩子(KV 命名空间不清空;钩子由进程内扩展提供)。版本只升不降,降级拒绝装载。
|
||
- 管理面:`GET /api/plugins` 返回 manifest(含 v2 provides)以及技能/页面/渠道声明。既有设置页「插件」分组继续承担热重载。
|
||
|
||
## 7. 开发体验
|
||
|
||
- `make plugin-new NAME=<id>` 在 `examples/plugins/<id>` 生成声明式骨架。
|
||
- `examples/plugins/decl-demo`、`examples/downstream-rdm`、`examples/sidecar-demo`、`examples/page-demo` 各覆盖一种形态。
|
||
- 验收:`examples/downstream-rdm` 的测试不导入 `internal`,并由 `make test-boundaries` 卡住。
|