Files
inkreach-agent/docs/plugins/10-dependencies.md
T

98 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# L8 插件依赖与服务互调
> 插件不是孤岛:A 依赖 B/C 时,声明、激活顺序、可用性判定、调用规范由宿主统一保证。
> 契约 0.3.0 起;全部机制在 `dependencies` / `optionalDependencies` + `api.service` / `api.requirePlugin` 四个名字里。
## 1. 声明依赖(manifest.json)
```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. 运行时判定(软绑定,无强行绑定)
宿主在每次激活轮(启停/重载/重建运行时)做三步:
1. **静态预检**:逐项检查必需依赖——
| 状态 | 判定 | 插件行展示的错误 |
|---|---|---|
| missing | resolved 表里没有该 id | `缺少依赖插件 fetch-tools(^1.0.0),请安装后重载` |
| disabled | 在禁用列表里 | `依赖插件 browser 已停用,请先启用` |
| version | 不满足范围 | `依赖插件 fetch-tools 需要 ^1.0.0,当前 v0.9.0` |
| error | 依赖自身装载失败 | `依赖插件 fetch-tools 装载失败,无法激活` |
2. **环检测**:必需依赖图存在环 → 整环不激活,每个成员报 `循环依赖: a → b → a`。
3. **拓扑波次激活**:依赖先激活、依赖者后激活,无依赖关系的插件**同波并行**(异步工厂限时 10s,挂死不拖垮后续波次)。依赖激活失败时,依赖方报 `依赖插件 x 未能激活`。
行为语义(有意为之):
- A 声明依赖 B:**禁用 B → A 激活失败**(错误栏写明原因);**重新启用 B → A 自动恢复**(重建运行时天然重放)。
- **禁用 A 不影响 B**——依赖不级联、不偷改用户的选择。
- **不自动安装**:缺什么报什么,装不装由用户决定(错误文案就是行动指引)。
## 3. 服务互调(api.service / api.requirePlugin)
所有插件的 main 工厂运行在同一 pi 上下文,互调是**同进程直调**(无 IPC、无序列化),宿主只提供注册表与门控:
```ts
// 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` 却未声明依赖。