定位并修复插件加载过程中的服务依赖缺失、循环依赖与配置反序列化错误。
上一课我们借助 Schemastery 定义了类型安全的 Config Schema,并用 ctx.effect() 注册带自动清理回调的副作用逻辑,从而在热重载下实现资源零泄漏与安全回滚。但声明式配置与可逆生命周期本身也会引入新的故障面:在 DeepSeek Harness (dsh) 中,整个运行时由 Cordis 内核驱动,所有能力均作为插件挂载在插件树上。当插件在加载过程中出现问题时,通常表现为进程挂起、加载超时、服务注入失败或启动崩溃。
根据社区稳定性审计与开发实践,约 80% 的启动故障源于未满足的 inject 依赖声明、Schemastery 配置模式类型冲突,或由于重复加载清单导致的插件条目冲突。掌握诊断命令与错误日志的排查路径,是保证 Agent 系统稳定扩展的前提。
遇到问题:插件中的 apply(ctx) 函数永远不执行,Harness 启动卡住无后续日志,或在等待指定服务就绪时超时退出。
可能原因
- 插件导出的
inject数组中声明了未在当前运行时中注册的服务 key(例如拼写错误或提供该服务的插件未启用)。 - 多个插件之间存在
inject强相互依赖,导致拓扑排序出现循环依赖(Circular Dependency)。 - 服务提供者插件异步初始化失败,未能成功向
Context挂载对应的服务实例。
排查步骤
- 检查插件导出的
export const inject = [...]数组,核对所需服务 key(如tools、database)的拼写与大小写。 - 检查当前配置文件(如
cordis.yml)中是否已声明并启用了提供该服务的底层插件。 - 将强依赖改为可选注入,在
apply内部通过条件判断if (ctx.myService)动态处理服务挂载逻辑,打破循环依赖。
遇到问题:执行启动命令时报错,抛出 Schemastery 配置解析异常或 TypeError: Schema validation failed,插件被跳过或应用直接崩溃。
可能原因
- 在配置文件中传入的参数类型与插件导出的
Config(Schemastery Schema)定义不兼容(例如端口号传递了字符串而非数值)。 - 配置文件缺少必填字段且在
Config中未定义.default()默认值兜底。 - 插件更新后修改了配置项字段结构,但本地配置文件未同步迁移。
排查步骤
- 比对配置文件中传入的 YAML/JSON 字段结构与插件中导出的
export const Config = ...模式定义。 - 在插件开发中为非必须配置项补充
.default(fallbackValue),提供安全的运行时默认值。 - 在终端中执行配置转储命令检查序列化后的实际配置树,修正不匹配的数据类型。
遇到问题:执行 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 重复引入且未设置覆盖优先级。
排查步骤
- 检查
package.json中的dsh.profile.bundles与本地cordis.patch.yml,排查是否有重名的插件id。 - 移除多余的手动插入项或从 bundle 配置中剔除冲突条目,确保每个插件标识符在加载树中仅注册一次。
- 重新运行安装与配置检查,确认插件树解析无重复项。
使用日志与 Creator 模式快速检查插件树挂载状态
- 1
使用开发模式启动命令,查看终端打印的 Loader 逐级挂载日志与服务注册流水。
bashpnpm dsh --dump-config - 2
在浏览器中进入 Creator 模式(Preset & Runtime Inspection),直观查看当前运行时的活跃插件树、已注入服务列表与就绪状态。
- 3
定位标记为未就绪(Unresolved/Pending)的插件节点,核对其依赖图谱,确认缺失的服务是由哪个上游插件提供。
你应该看到
能够清晰理清插件树的加载拓扑结构,准确定位阻塞加载的具体插件及其缺失的服务依赖。
DeepSeek Harness 内部没有特权核心,所有内置服务与外部扩展运行在平等的 Context 容器内。遇到挂载异常时,不要尝试修改底层执行循环,应优先检查插件四大导出(name、inject、Config、apply)的契约完整性,并利用单元测试与配置转储进行隔离验证。
自检:当你在插件中声明了 inject = ['database'] 但该服务从未挂载到 Context 时,Loader 会发生什么行为?
Loader 会根据依赖拓扑挂起该插件的生命周期,等待名为 database 的服务挂载到 Context 上。在此服务就绪前,该插件的 apply 函数不会被执行。