4.6 KiB
4.6 KiB
L8 插件依赖与服务互调
插件不是孤岛:A 依赖 B/C 时,声明、激活顺序、可用性判定、调用规范由宿主统一保证。 契约 0.3.0 起;全部机制在
dependencies/optionalDependencies+api.service/api.requirePlugin四个名字里。
1. 声明依赖(manifest.json)
{
"id": "wiki-assistant",
"version": "1.2.0",
"minContract": "0.3.0",
"dependencies": {
"fetch-tools": "^1.0.0",
"browser": "*"
},
"optionalDependencies": {
"embed-store": "~2.1.0"
}
}
- 值是 semver 范围,支持五种写法:
*(任意)、^1.2.0(同主版本)、~1.2.0(同次版本)、>=1.2.0(下限)、1.2.0(精确)。 dependencies是必需依赖:缺失 / 已停用 / 版本不符 / 自身装载失败 → 本插件激活失败。optionalDependencies是可选依赖:不就绪只降级,api.requirePlugin(id)返回undefined。- 依赖声明即访问授权:
requirePlugin只对声明过的 id 放行,未声明直接抛错。
2. 运行时判定(软绑定,无强行绑定)
宿主在每次激活轮(启停/重载/重建运行时)做三步:
- 静态预检:逐项检查必需依赖——
| 状态 | 判定 | 插件行展示的错误 |
|---|---|---|
| missing | resolved 表里没有该 id | 缺少依赖插件 fetch-tools(^1.0.0),请安装后重载 |
| disabled | 在禁用列表里 | 依赖插件 browser 已停用,请先启用 |
| version | 不满足范围 | 依赖插件 fetch-tools 需要 ^1.0.0,当前 v0.9.0 |
| error | 依赖自身装载失败 | 依赖插件 fetch-tools 装载失败,无法激活 |
- 环检测:必需依赖图存在环 → 整环不激活,每个成员报
循环依赖: a → b → a。 - 拓扑波次激活:依赖先激活、依赖者后激活,无依赖关系的插件同波并行(异步工厂限时 10s,挂死不拖垮后续波次)。依赖激活失败时,依赖方报
依赖插件 x 未能激活。
行为语义(有意为之):
- A 声明依赖 B:禁用 B → A 激活失败(错误栏写明原因);重新启用 B → A 自动恢复(重建运行时天然重放)。
- 禁用 A 不影响 B——依赖不级联、不偷改用户的选择。
- 不自动安装:缺什么报什么,装不装由用户决定(错误文案就是行动指引)。
3. 服务互调(api.service / api.requirePlugin)
所有插件的 main 工厂运行在同一 pi 上下文,互调是同进程直调(无 IPC、无序列化),宿主只提供注册表与门控:
// fetch-tools 的 main.mjs —— 暴露服务
export default definePlugin((api) => {
api.service('fetch', async (url: string, init?: RequestInit) => {
const res = await fetch(url, init)
return res.text()
})
})
// wiki-assistant 的 main.mjs —— 消费服务
export default definePlugin(async (api) => {
const fetchTools = api.requirePlugin('fetch-tools') as
| { fetch: (url: string, init?: RequestInit) => Promise<string> }
| undefined
if (!fetchTools) {
// 可选依赖未就绪:降级路径(必需依赖不会走到这里——激活前置检已拦)
}
api.pi.registerTool({
name: 'wiki_lookup',
// ...
execute: async (args) => {
// 服务方法约定 async;调用方自行限时(与宿主性能护栏同构)
const body = await withTimeout(fetchTools!.fetch(args.url), 8_000, 'wiki_lookup')
// ...
}
})
})
规则:
api.service(name, impl):按名键控;重载/停用时随注册表清空,重激活即重新注册。api.requirePlugin(id):返回{ serviceName: impl };未声明 → 抛错;必需依赖未就绪 → 抛错;可选未就绪 →undefined。- 服务方法约定全 async,调用方用
withTimeout限时、必要时withRetry(见 L7)——被调插件慢不能变成调用方卡死。 - 依赖可以指向内置插件(如
browser),内置与外置同一注册表。
4. UI 展示
- 管理页插件行名称旁出现链接图标,悬停 Tooltip 列出每个依赖:id / 范围 / 必需或可选 / 实时状态(可用·绿 / 未安装·橙 / 已停用·橙 / 版本不符·橙 / 装载失败·红)。
- 依赖问题的详细错误在该插件行红字与二级页展示;「去启用」到对应插件的行上操作。
5. 检验器
pnpm plugin:check 会校验:依赖 id 合法、范围语法、不能依赖自身、不能同时出现在必需与可选、声明了依赖但 minContract < 0.3.0(旧宿主会静默忽略声明)、调用了 requirePlugin 却未声明依赖。