[修复方案] 源码启动 prepare 崩溃:profile 解析锚点未规范化导致 tsx 跳过 paths(fork 分支 + 回归测试 + 端到端验证) #7048
Replies: 2 comments
|
独立复现确认 + 补丁验证通过。我们在看到这个帖子之前,从症状出发独立排查到了同一处,结论与你一致;这里补一组来自另一台机器的数据,供维护者参考。 环境:dsh 0.1.6-alpha.2( 一、独立复现的现场数据除了 const viaBare = await import('@deepseek-ai/dsh-tools') // tsx + paths -> src/index.ts
const viaEntry = await import(pathToFileURL(resolve('packages/core/tools', entryFromPackageJson)).href) // -> lib/index.js
const live = Object.getOwnPropertySymbols(ctx.tools)
.find(s => s.description === '@deepseek-ai/dsh-tools.scheduler')
// live === viaBare.TOOL_RUNTIME_SCHEDULER ? 'src 副本' : 'lib 副本'修复前: 这与你说的"插件侧走包 你提到的几条判据我们也在本地逐一核对过,均属实: 二、补丁单独验证在干净 master 上只应用 撤销该行后立即回到 相邻面: 另外确认你提到的临时缓解有效:改用构建入口启动不出现双实例。 三、一个附带的数据点我们最初尝试过把 四、补充:这条路径上的非工具型报错顺带一提, 需要的话我可以提供那份不依赖 API key、浏览器和会话的复现脚本(启动 profile 后直接判定 |
|
补一条防复发建议(结论不重复,仅供维护者取舍):
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
概要
源码启动(
pnpm dsh)在存在构建产物时,第一次工具调用即整轮失败:Cannot read properties of undefined (reading 'prepare')。我们在本地定位到完整根因,给出最小修复(4 行)与回归测试,并做了端到端验证。由于仓库当前不接受外部 PR(见 CONTRIBUTING.md),修复放在 fork 分支供维护者取用。
本问题与以下报告同源: #6974、#7011、#7030、#7035、#7039。
根因
同一进程加载了两份
@deepseek-ai/dsh-tools:apps/cli/src/bin.ts的 ESM 图)经node --import tsx/esm+ tsconfigpaths→packages/core/tools/src/index.tspackages/boot/app-boot/src/profile-resolution/的原生解析 →packages/core/tools/lib/index.jsTOOL_RUNTIME_SCHEDULER定义为Symbol('@deepseek-ai/dsh-tools.scheduler')(packages/core/tools/src/index.ts:463,不是Symbol.for),两份模块各持有一个 symbol,因此packages/core/agent-loop/src/tool-calls.ts:170取ctx.tools[TOOL_RUNTIME_SCHEDULER]得到undefined,.prepare(...)抛错 —— 与 #7039 的现场一致(ctx.tools[TOOL_RUNTIME_SCHEDULER] === undefined、两个 Symbol 描述相同但!==)。触发条件:工作树存在
lib/构建产物。没有构建产物时同一路径直接ERR_MODULE_NOT_FOUND(apps/cli/node_modules/@deepseek-ai/dsh-agent-instructions/node_modules/@deepseek-ai/dsh-tools/lib/index.js)—— 也就是源码启动在这两种状态下都不健康。引入点:
9ddef327a4 feat: resolution mode link to runtime把非打包 CLI 的默认 resolution mode 从link翻到runtime(随 #4471 合入 master);此后源码启动会同时挂载 src 与 lib 两个平面 —— 与 #7030 的分析一致。链路细节:profile 的 ESM 后备解析把 pnpm 符号链接下的
node_modules/.../package.json当作 declaring 锚点交给 Node/tsx;tsx 对parentURL含/node_modules/的请求不应用 tsconfigpaths(tsx@4.22.4的!parentURL.includes("/node_modules/")判据),于是插件走包exports的lib/,而 CLI 走paths的src/。修复
packages/boot/app-boot/src/profile-resolution/resolver.ts的 ESM fallback 路由,把 anchor 规范化为真实路径后再交给已有 loader hooks:pnpm 的
node_modules/@deepseek-ai/dsh-tools是符号链接;真实锚点不再包含/node_modules/,tsx 的paths投影因此对 workspace 插件生效,profile 与 CLI 落到同一份src单例(Symbol自然同一)。约束:不新增平面开关、不复制 tsconfig 解析器、不改包
exports、不动 vendor;普通 Node 仍走包exports。worker 线程解析(worker-bootstrap.ts)复用同一installProfileResolution,自动获得同一修复。验证
pnpm run build退出 0 →pnpm dsh --profile headless "…"退出 0,tool/call→ 成功tool/result→turn/end: completed。pnpm run clean(删除 308 项,确认 tools/agent-loop/app-boot/CLI 的lib/不存在)后同一命令退出 0,输出当天日期。sameInstance: false且 URL 落到lib/;无产物ERR_MODULE_NOT_FOUND);修复后68 passed,resolver.tsstatements/branches/functions/lines 均 100%。pnpm run typecheck、pnpm run lint、pnpm run test packages/boot/app-boot apps/cli scripts/run-gates.spec.ts(460 passed / 1 skipped)、test:e2e apps/cli/tests/built-bin.e2e.ts(3 passed)、test:snapshot snapshots/session/headless.snapshot.ts -t bash-tool-turn(1 passed)、test:docs(20 checks)、doc-sync(41 checks)全部通过。tool/call全部成功,tool-workflow/run-start+ 5 ×tool-workflow/agent-start、5 个子会话目录落盘,turn/end错误计数 0。临时缓解(在修复合入前):用构建入口启动(#7039 提到的
pnpm exec node apps/cli/lib/bin.js web)可避免双实例。已知范围外残留:
pnpm run clean后typert-loader会对 6 个未生成的lib/typert.host.js输出非致命警告;该 loader 直接拼接生成产物路径,不经过本次修复的 bare-package ESM 后备解析,本修复未涉及。代码位置(fork 分支)
由于仓库暂不接受外部 PR,修复放在 fork 分支:
packages/boot/app-boot/src/profile-resolution/resolver.ts(+3/−1)、新增packages/boot/app-boot/tests/profile-resolution-source.spec.ts(+107)、scripts/run-gates.ts(+1,接入 Node compatibility smoke gate)、packages/boot/app-boot/README.md/README.zh.md/README.i18n.yaml。如果维护者愿意接收,我可以按仓库要求调整形态(分支名、commit 拆分、文档位置)或补任何缺失的验证。
All reactions