Replies: 5 comments
|
这是生态里最经典的坑之一——社区叫它"cordis 双副本"(dsh-plugin-dev 技能文档的头条坑),你复现得很准。 机理:工具注册表和调度器各持一份 @deepseek-ai/dsh-tools(以及背后的 cordis)实例。插件声明的 peer 依赖与 profile 里已 hoist 的那份版本对不上时,pnpm 会给插件装第二份独立拷贝;插件把工具注册进"拷贝 B"的 registry,调度器却按"拷贝 A"找,于是 prepare 未定义。 用户侧临时解法(无需改插件):
插件作者侧的根修(我们发插件时踩过,已固化进脚手架):
参考模板:https://github.com/ciceroyang/dsh-plugin-starter (peer 写法 + 说明都备好了)。 给官方的建议:这类问题几乎必然反复出现——dsh plugin add 之后校验"关键 @deepseek-ai/* 包在 profile 树内唯一"并告警,能拦下绝大多数案例(dsh-doctor 也值得加这一项检查)。 |
|
Your peerDependencies/autoInstallPeers observation is key — dedup/relink approaches can't cover all cases. |
|
感谢指正,#2078 的 Symbol.for 方案确实比 dedupe 治本:模块级 Symbol 是"每副本一 key",全局符号注册表是"同 realm 一 key",一行改动覆盖最普遍的"同进程多副本"场景。 补一个边界供 #2078 参考:Symbol.for 的注册表是 per-realm 的。DSH 还有 worker-thread 边界(code-runtime worker),跨 realm 的副本仍然不共享注册表——所以 Symbol.for 修掉大头之后,loader 层的单副本保证(或重复告警)仍是值得保留的兜底,二者不是替代关系。 三方各就各位最稳:
生态协作最舒服的样子就是现在这样:报告者给复现,修复方给根因,检测方给护栏。 |
|
Confirm on
So profile plugin removal/reconciliation can leave the same dual-copy condition behind even after the manifest looks clean. A startup check that names both resolved paths, plus pruning stale package-map/lock entries when a bundle is removed, would make this recoverable instead of surfacing later as |
|
That is a significant sharpening of the repro — the dual-copy state can survive plugin REMOVAL, because stale profile package-map/lock entries keep a profile-local @deepseek-ai/dsh-tools materialized even after the manifest no longer declares the bundle. Same-version, identical lib hash, different module-local Symbols — so dedupe-by-version cannot see it, and the manifest looks clean. Two implications from our side:
Endorsing your two asks as the consolidated official action for this thread: (a) startup resolution check naming both paths, (b) prune-on-removal. The Symbol.for fix in #2078 remains the belt-and-braces layer for same-realm copies that none of the above can prevent. |
Uh oh!
There was an error while loading. Please reload this page.
Bug 1: Installing a plugin with
@deepseek-ai/dsh-toolspeer dependency breaks tool scheduling (Cannot read properties of undefined (reading 'prepare'))Environment
Steps to reproduce
npm install -g @deepseek-ai/dsh(0.1.0-rc.6)dsh plugin --profile web add dsh-better-sidebar(or any plugin declaring@deepseek-ai/dsh-toolsas a peer dependency)dsh web, open a session, ask the agent to do anything that invokes a tool (e.g. load a skill)Root cause
dsh-better-sidebar@0.10.3declares peer dependencies on@deepseek-ai/dsh-tools,@deepseek-ai/dsh-credentials,@deepseek-ai/dsh-settings. pnpm then installs a second, independent copy of@deepseek-ai/dsh-toolsunder the profile'snode_modules:C:\...\npm\node_modules\@deepseek-ai\dsh\node_modules\@deepseek-ai\dsh-toolsC:\...\.dsh\profiles\web\node_modules\@deepseek-ai\dsh-toolsThese are different physical files (verified different inode).
TOOL_RUNTIME_SCHEDULERis a module-levelSymbol("@deepseek-ai/dsh-tools.scheduler")(seedsh-tools/lib/index.js:2409). Because the two copies are separate module instances, their Symbols differ.dsh-agent-loop/lib/index.js:193does:ctx.toolswas registered by the profile's dsh-tools copy (different Symbol), soctx.tools[<global Symbol>]isundefined→.preparethrows.Workaround (confirmed working)
Replace the profile's independent copies with junctions pointing at the global dsh install so there is a single module instance:
After this, tool scheduling (including skill loading) works normally.
Suggested fix
The plugin loader should resolve peer dependencies like
@deepseek-ai/dsh-toolsagainst the same instance used by the host dsh runtime (single-instance hoisting), rather than creating per-profile copies.All reactions