15 多插件冲突治理与优先级规范实战(让插件生态可控)
这篇讲“插件多了之后怎么不乱”。
核心源码入口
src/plugins/manifest-registry.tssrc/plugins/loader.tssrc/plugins/config-state.tssrc/plugins/registry.tssrc/plugins/hooks.tssrc/plugins/commands.tssrc/config/validation.tssrc/cli/plugins-cli.ts
总体裁决思路
来源优先级 -> 启用决策 -> 槽位排他 -> 注册冲突拦截 -> 诊断输出
模块一 来源优先级与重复 ID 裁决
manifest-registry.ts 里明确了来源排名:
configworkspaceglobalbundled
即:config > workspace > global > bundled
同 ID 冲突时做两步判断:
realpath相同:视为同一插件的路径别名,允许静默去重realpath不同:按来源优先级选赢家,并写 duplicate warning
这避免了“软链路径误报冲突”。
模块二 启用状态决策
normalizePluginsConfig(...) 统一解析配置:
enabledallow/denyload.pathsslots.memoryentries.<id>.enabled/config
resolveEnableState(...) 返回 {enabled, reason},典型 reason:
plugins disabledblocked by denylistnot in allowlistbundled (disabled by default)
模块三 memory slot 排他治理
resolveMemorySlotDecision(...) 保证 memory 插件单选:
slot = null:memory 能力整体禁用slot = <id>:只允许指定插件- 自动选择模式:第一个占位,后续 memory 插件被拒绝
loader.ts 还会做 memorySlotMatched 检查,slot 指向不存在插件时输出 warn。
模块四 注册阶段冲突拦截
registry.ts 在注册期硬拦截:
registerGatewayMethod(...)
- 与 core method 或已注册 method 冲突即拒绝
registerHttpRoute(...)
- path 重复即拒绝
registerCommand(...)
- 委托
registerPluginCommand(...)校验重名和非法命令
冲突不会“后者覆盖前者”,而是明确失败并留诊断。
模块五 Hook 冲突治理
plugins/hooks.ts 规则:
getHooksForName(...)按 priority 降序runModifyingHook(...)顺序执行 + mergerunVoidHook(...)并行执行
这样既保证安全插件优先,又不牺牲观察型 hook 吞吐。
模块六 诊断闭环
所有问题汇总到 registry.diagnostics:
- loader 错误
- manifest 冲突
- 注册冲突
- 配置 schema 错误
然后:
config/validation.ts映射为配置 issueplugins list/doctor输出可读结论和修复提示
最小复刻骨架
ts
type Origin = "config" | "workspace" | "global" | "bundled";
const RANK: Record<Origin, number> = { config: 0, workspace: 1, global: 2, bundled: 3 };
function pickByOrigin(a: Candidate, b: Candidate) {
return RANK[a.origin] <= RANK[b.origin] ? a : b;
}
function resolveEnabled(id: string, cfg: NormalizedCfg): { enabled: boolean; reason?: string } {
if (!cfg.enabled) return { enabled: false, reason: "plugins disabled" };
if (cfg.deny.includes(id)) return { enabled: false, reason: "blocked by denylist" };
if (cfg.allow.length && !cfg.allow.includes(id)) return { enabled: false, reason: "not in allowlist" };
return { enabled: true };
}
function assertUniqueMethod(name: string, core: Set<string>, exists: Set<string>) {
if (!name.trim()) throw new Error("missing method");
if (core.has(name) || exists.has(name)) throw new Error(`gateway method already registered: ${name}`);
}自检清单
- 同 ID 不同来源时是否按固定优先级裁决。
- 同一插件不同路径别名是否不会误报冲突。
- memory slot 是否严格单选。
- method/route/command 冲突是否明确报错。
- diagnostics 是否能在 CLI 一眼看出原因。
- Hook priority 是否能稳定压过低优先级插件。