插件挂载异常与依赖未满足诊断

约 25 分钟

定位并修复插件加载过程中的服务依赖缺失、循环依赖与配置反序列化错误。

上一课我们借助 Schemastery 定义了类型安全的 Config Schema,并用 ctx.effect() 注册带自动清理回调的副作用逻辑,从而在热重载下实现资源零泄漏与安全回滚。但声明式配置与可逆生命周期本身也会引入新的故障面:在 DeepSeek Harness (dsh) 中,整个运行时由 Cordis 内核驱动,所有能力均作为插件挂载在插件树上。当插件在加载过程中出现问题时,通常表现为进程挂起、加载超时、服务注入失败或启动崩溃。

根据社区稳定性审计与开发实践,约 80% 的启动故障源于未满足的 inject 依赖声明、Schemastery 配置模式类型冲突,或由于重复加载清单导致的插件条目冲突。掌握诊断命令与错误日志的排查路径,是保证 Agent 系统稳定扩展的前提。

遇到问题:插件中的 apply(ctx) 函数永远不执行,Harness 启动卡住无后续日志,或在等待指定服务就绪时超时退出。

可能原因

  • 插件导出的 inject 数组中声明了未在当前运行时中注册的服务 key(例如拼写错误或提供该服务的插件未启用)。
  • 多个插件之间存在 inject 强相互依赖,导致拓扑排序出现循环依赖(Circular Dependency)。
  • 服务提供者插件异步初始化失败,未能成功向 Context 挂载对应的服务实例。

排查步骤

  1. 检查插件导出的 export const inject = [...] 数组,核对所需服务 key(如 tools、database)的拼写与大小写。
  2. 检查当前配置文件(如 cordis.yml)中是否已声明并启用了提供该服务的底层插件。
  3. 将强依赖改为可选注入,在 apply 内部通过条件判断 if (ctx.myService) 动态处理服务挂载逻辑,打破循环依赖。

遇到问题:执行启动命令时报错,抛出 Schemastery 配置解析异常或 TypeError: Schema validation failed,插件被跳过或应用直接崩溃。

可能原因

  • 在配置文件中传入的参数类型与插件导出的 Config(Schemastery Schema)定义不兼容(例如端口号传递了字符串而非数值)。
  • 配置文件缺少必填字段且在 Config 中未定义 .default() 默认值兜底。
  • 插件更新后修改了配置项字段结构,但本地配置文件未同步迁移。

排查步骤

  1. 比对配置文件中传入的 YAML/JSON 字段结构与插件中导出的 export const Config = ... 模式定义。
  2. 在插件开发中为非必须配置项补充 .default(fallbackValue),提供安全的运行时默认值。
  3. 在终端中执行配置转储命令检查序列化后的实际配置树,修正不匹配的数据类型。

遇到问题:执行 dsh web 启动后端服务时,控制台抛出 Error: dsh: plugin tree failed to load: failed to apply loader entry include (cordis:include): duplicate loader entry id: <plugin_id>,服务无法进入监听状态。

可能原因

  • 在 cordis.patch.yml 中已手动声明了插件条目,同时又通过命令或 bundle 引入了相同的插件包,导致 loader 收集到重复的 id。
  • 同一插件被多层 bundle 重复引入且未设置覆盖优先级。

排查步骤

  1. 检查 package.json 中的 dsh.profile.bundles 与本地 cordis.patch.yml,排查是否有重名的插件 id。
  2. 移除多余的手动插入项或从 bundle 配置中剔除冲突条目,确保每个插件标识符在加载树中仅注册一次。
  3. 重新运行安装与配置检查,确认插件树解析无重复项。

使用日志与 Creator 模式快速检查插件树挂载状态

  1. 1

    使用开发模式启动命令,查看终端打印的 Loader 逐级挂载日志与服务注册流水。

    bash
    pnpm dsh --dump-config
  2. 2

    在浏览器中进入 Creator 模式(Preset & Runtime Inspection),直观查看当前运行时的活跃插件树、已注入服务列表与就绪状态。

  3. 3

    定位标记为未就绪(Unresolved/Pending)的插件节点,核对其依赖图谱,确认缺失的服务是由哪个上游插件提供。

你应该看到

能够清晰理清插件树的加载拓扑结构,准确定位阻塞加载的具体插件及其缺失的服务依赖。

自检:当你在插件中声明了 inject = ['database'] 但该服务从未挂载到 Context 时,Loader 会发生什么行为?

Loader 会根据依赖拓扑挂起该插件的生命周期,等待名为 database 的服务挂载到 Context 上。在此服务就绪前,该插件的 apply 函数不会被执行。