DeepSeek Harness 共享包契约回归:deepEqualJson 与 marketVersion 排查

故障排查发布于 2026-10-20作者: DeepSeek Plugin 插件市场
DeepSeek Harness共享包deepEqualJsonmarketVersion契约回归
DeepSeek Harness 的 deepEqualJson 认原型链、marketVersion 只做正则校验,共享包行为与文档契约对不上。本文给合入补丁分支、逐包打 patch、调用侧防御三条路。

这五个问题都不是「用错 API」,而是共享包的行为与它自己声明的契约对不上,所以升级版本不会自动消失。 有用户在 687f282e86 上逐条只读复核,结论是 5/5 都是真实缺陷;补丁与回归测试在 fork 分支上,尚未合并进官方仓库(Discussion #10065)。

DeepSeek Harness 共享包五个缺陷分别错在哪、影响谁

共同点是「校验或比较的实现比文档窄」,每一处都能用一段最小输入打出来。 逐条对照:

  1. values 的 deepEqualJson 用 in 加成员读取比较记录。预期判据:{"__proto__":{},"b":2} 与 {"a":1,"b":2} 被判相等,因为继承成员满足了左侧的 a。修法是只比较自有属性。
  2. plugin-market-index 的 marketVersion 只用 SemVer 语法正则。预期判据:超长版本号能通过校验,但列表排序走 semver.rcompare 时抛裸 TypeError,而不是 MarketIndexError。
  3. values 的 PartialArguments#invalid 读取时不经过 remember。预期判据:读完之后才发现的无效状态不会触发 refresh(),与 complete() 的行为不一致。
  4. llm 的 normalizeLlmFailure 渲染非 Error 抛出。预期判据:抛 {message, code} 时错误信息变成 [object Object],原始 message 丢失。
  5. llm 的 boundContextSummary 按码元截断。预期判据:切点落在代理对内部时,摘要里留下孤立的高位代理项,并随消息进入持久化日志。

补丁分支同时纠正了两处文档:code-language 的 JSDoc 把 hcl 写成映射到 tf,而实际表里 hcl 仍映射 hcl;plugin-market-index 的 README 声称无运行时依赖,实际校验器依赖 semver 与包清单包。据讨论,复核者认为后一条的描述比代码多算了一个包。

五处缺陷的触发面完全不同,按输入特征分流比一次性全改更稳。 四步:

  1. 检查你的 JSON 输入里是否可能出现 __proto__、constructor 这类键。预期结果:只有这类输入会踩 deepEqualJson,普通对象比较不受影响。
  2. 检查市场索引里是否存在超长或格式边缘的版本号。预期结果:正常 SemVer 不会触发 marketVersion 的排序异常。
  3. 检查你是否依赖 PartialArguments#invalid 变化后的回调。预期结果:不读这个字段的调用方完全碰不到第三处。
  4. 在日志里搜 [object Object] 与替换字符。预期结果:出现前者说明踩了非 Error 抛出,出现后者要看摘要截断。

分流的价值在于:多数应用只会踩一到两处,把五处全改反而扩大了回归面。

DSH插件路径一:合入补丁分支

适合能改宿主源码、并且要一次性解决全部五处的人。 做法:

  1. 拉取 fork 分支 fix/shared-contract-regressions-20261017。预期结果:能看到五个独立提交,分别对应上面的五个缺陷。
  2. 对照提交说明挑出你需要的部分,逐个 cherry-pick 到自己的基线。预期结果:每个提交只动一个包,冲突范围可控。
  3. 跑对应包的测试与受影响消费者的测试。预期结果:新增回归用例在未修源码上失败、在修后通过。
  4. 记录基线 commit 与 cherry-pick 顺序。预期结果:下次升级宿主时能判断这些补丁是否已被官方吸收。

五个提交分别是 fc74097e0e(deepEqualJson)、038c710d33(marketVersion)、b49f975945(invalid 观察)、d451d26f9b(非 Error 抛出)、61ee3f668e(代理对截断),据讨论均带回归测试。

DeepSeek Harness 路径二:逐个包打局部补丁

适合不想整体替换宿主、只想修自己实际踩到的那一两个包的人。 步骤:

  1. 先确定哪个包真的影响了你的功能,不要五个一起改。预期结果:列出一到两个包名与触发场景。
  2. 在本地用你现有的 patch 机制覆盖该包的构建产物,只改比较或校验那一小段。预期结果:包版本号不变,行为按预期变化。
  3. 为每处改动补一个最小用例,输入就用上面判据里的那段 JSON 或版本号。预期结果:不打补丁时用例失败,打上后通过。
  4. 在升级宿主后重新应用并重跑用例。预期结果:补丁未失效,或已被上游修复从而可以删除。

局部补丁的代价是升级时要重新核对偏移,尤其是压缩产物里的行号会变。用行为断言而不是行号定位,能省掉很多返工。

DeepSeek Harness 路径三:在调用侧加防御

适合完全不能改依赖、只需要让自己的插件不出错的人;在 DSH Plugin Hub 发布插件前,也建议先确认自己不会被这些共享包行为拖垮。 三处最常见的防御:

  1. 比较 JSON 前先做一次自有属性归一,比如只取 Object.keys 能枚举到的键再比较。预期结果:原型链上的成员不再影响相等判断。
  2. 使用 marketVersion 的返回值前,自己再跑一次 semver.valid 并限制长度。预期结果:超长版本号在进入排序前就被拒绝,不会抛裸 TypeError。
  3. 捕获非 Error 抛出时,先判断对象上有没有自有的 message 字符串,再回退到 String(value)。预期结果:日志里不再出现 [object Object]。

调用侧防御不会修好共享包,别的调用方仍然会踩。它的价值是把故障限制在你自己的边界内,给你时间等上游合并。

DeepSeek Harness 用回归测试确认补丁真的生效

只看代码 diff 不够,五处都需要一个能独立失败的最小输入。 按顺序建测试:

  1. 为 deepEqualJson 写一条含 __proto__ 键的相等断言。预期结果:未修时断言通过(错误地相等),修后断言失败(正确地不等)。
  2. 为 marketVersion 写一条超长版本号用例,断言抛出的是 MarketIndexError。预期结果:未修时收到 TypeError。
  3. 为 PartialArguments#invalid 写「先读 invalid、再改内容、检查 refresh 次数」的用例。预期结果:未修时 refresh 不触发。
  4. 为非 Error 抛出与代理对截断各写一条字符串断言。预期结果:前者不再包含 [object Object],后者不再出现孤立代理项。

这组用例的写法是「在当前源码上必须失败」,否则它没有守住任何东西。

DeepSeek Harness 处理这类契约缺陷的注意事项

先分清「文档写错」与「实现写错」,两者的修法不同。 四点:

  1. 文档强于代码时,优先修文档还是修代码取决于调用方是否已经依赖文档语义。已经有人按文档写调用时,应修代码。
  2. 共享包的修复要连带跑消费者的测试,values 与 llm 的改动面比看起来大。
  3. 外部 PR 不被接受时,fork 分支加讨论帖是目前可行的提交方式,别把补丁只留在本地。
  4. 升级宿主后重新核对这些提交是否已被吸收,避免同一段逻辑被改两遍。

据讨论,这五处目前都还没有进官方版本,属于「补丁可用、待合入」的状态。把它们当成已知边界写进你的兼容说明,比等一个不确定的修复时间更实际。

三条路的适用范围可以这样分:

  1. 合入补丁分支:一次改五个包,升级时要重解冲突,适合能改宿主源码的人。
  2. 逐包打局部补丁:只改踩到的一两个包,代价是每次升级都要重新核对产物偏移。
  3. 调用侧防御:完全不动依赖,代价是防御逻辑得自己维护,别处调用方仍然会踩。

三条不互斥:先做调用侧防御止血,再挑需要的提交 cherry-pick,是常见顺序。 如果团队同时维护多个宿主分支,给每个分支记录一份「已合入哪几条」的清单,避免升级时把已验证的补丁又解一遍。

来源:Discussion #10065、fix/shared-contract-regressions-20261017。更多插件侧排查入口见 DSH 故障排查。

常见问题

DeepSeek Harness 的 deepEqualJson 为什么把两个不同的 JSON 对象判成相等?

DeepSeek Harness 的旧实现用 in 加成员读取来比较记录,继承自 Object.prototype 的成员也能满足左侧键,于是 {"__proto__":{},"b":2} 会与 {"a":1,"b":2} 相等。修法是只比较自有属性,补丁分支里的提交是 fc74097e0e。

DeepSeek Harness 的 marketVersion 只用正则校验会有什么后果?

DeepSeek Harness 会放过超长版本号,并让排序在遇到它时抛出裸 TypeError,而不是文档承诺的 MarketIndexError。原因是正则只管语法,真正排序走 semver.rcompare,后者还会限制总长度与每个数字标识符,补丁改为用 node-semver 一起校验。

DeepSeek Harness 里抛出非 Error 对象时,为什么错误信息变成 [object Object]?

DeepSeek Harness 的 normalizeLlmFailure 把抛出的 {message, code} 当普通对象渲染成了字符串。补丁改为读取自有且 data-backed 的 message 字段、不触发访问器,同时把 code 保持为 UNKNOWN,因为第三方错误码不属于 harness 的分类体系。

DeepSeek Harness 的 boundContextSummary 截断代理对会造成什么可见影响?

DeepSeek Harness 会在进入持久化日志的摘要里留下一个孤立的高位代理项,显示为乱码或替换字符,后续按码点处理的逻辑也可能错位。修法是在切点落在代理对内部时丢掉未配对的那一半。

相关术语

deepEqualJson
deepEqualJson 是 values 包里按 JSON 语义比较两个值的函数,正确实现只应比较对象的自有属性,不应让原型链上的成员参与相等判断。— https://github.com/deepseek-ai/deepseek-harness/discussions/10065
marketVersion
marketVersion 是 plugin-market-index 用来校验市场索引版本号的函数,它需要同时满足 SemVer 语法与排序实现的可接受范围。— https://github.com/deepseek-ai/deepseek-harness/discussions/10065
PartialArguments#invalid
PartialArguments#invalid 是 values 包里表示流式参数是否已判定无效的只读答案,读取它应当像 complete() 一样登记观察,否则后续的 invalid 变化不会触发 refresh。— https://github.com/deepseek-ai/deepseek-harness/discussions/10065
surrogate pair(代理对)
surrogate pair 是 UTF-16 中用两个 16 位码元表示一个补充平面字符的编码方式,按码元下标截断时必须避免只保留其中一半。— https://github.com/deepseek-ai/deepseek-harness/discussions/10065

来源