Skip to content

[兼容性] DSH 0.1.5-alpha.1 起宿主启动失败:cannot get property "webServer" without inject(根因在 dsh-client-connection,非本项目) #115

Description

@lolkda

TL;DR — 装上 dsh-pocket 后 dsh web 直接启动失败。已定位根因不在 dsh-pocket,而在 @deepseek-ai/dsh-client-connection:它在 0.1.5-alpha.1 的重构中从顶层 inject 移除了 webServer,却漏改了一处仍在使用 owner.webServer 的调用点。所有通过 connection.rpc.handle() 注册通道的第三方插件都会中招。本 issue 附逐版本证据、最小复现,以及建议本项目做的一处防御性改动(可选)。

环境

项 值
dsh-pocket 2.10.3(当前 npm latest)
DSH 0.1.5-rc.1(npx,latest)
@deepseek-ai/cordis 4.0.2
Node v24.18.0
OS Windows 10.0.19045
profile $DSH_HOME/profiles/web

现象

dsh plugin --profile web add dsh-pocket@2.10.3 安装成功,随后 dsh web 启动即崩:

Error: dsh: plugin tree failed to load: failed to apply loader entry dsh-pocket (dsh-pocket): cannot get property "webServer" without inject
Error: cannot get property "webServer" without inject
    at Fiber.<anonymous> (.../@deepseek-ai/dsh-client-connection/lib/index.js:618:35)
    at Proxy.register  (.../@deepseek-ai/dsh-client-connection/lib/index.js:618:16)
    at Object.handle   (.../@deepseek-ai/dsh-client-connection/lib/index.js:543:39)
    at installPocketRpc (.../dsh-pocket/lib/web-rpc.js:39:29)
    at new apply        (.../dsh-pocket/lib/index.js:349:22)
    at Fiber.execute    (.../@deepseek-ai/cordis/lib/index.js:1067:24)
    at boot             (.../@deepseek-ai/dsh-app-boot/lib/index.js:1545:9)

dshmarket 的热挂载路径同样失败,只是降级为“需重启”而不是中断启动:

{"event":"hot-mount","detail":"dsh-pocket: fell back to restart — failed to apply loader entry mkt-dsh-pocket (dsh-pocket): cannot get property \"webServer\" without inject"}

根因

dsh-client-connection 的 register():

// dsh-client-connection/lib/index.js
543:  handle: (channel, handler) => this.register(owner, channel, handler)   // owner = this.ctx
618:  return owner.effect(() => owner.webServer.register(route), `client-connection: ${channel} rpc channel`);

owner 是该服务自己的 ctx,而该包的顶层 inject 在 0.1.5-alpha.1 起不再声明 webServer。cordis 4.x 对未声明 inject 的服务做属性读取会抛错,异常沿 fiber 冒到 boot(),导致整棵插件树加载失败。

逐版本核对 lib/index.js:

dsh-client-connection 顶层 inject owner.webServer 使用 子 ctx 注入 webServer
0.1.2-rc.1 ["webServer","credentials"] 1 处 无
0.1.3-alpha.2 ["webServer","credentials"] 1 处 无
0.1.5-alpha.1 ["credentials"] 1 处 有
0.1.5-alpha.2 ["credentials"] 1 处 有
0.1.5-rc.1 ["credentials"] 1 处 有

即 0.1.5-alpha.1 把 /api 路由的注册包进了子 ctx:

ctx.inject(["webServer"], (webCtx) => { ... webCtx.effect(() => webCtx.webServer.register(route), ...) });

于是从顶层 inject 移除了 webServer,但 register()(第三方通道路径)仍在同一个 ctx 上读 owner.webServer —— 重构漏改了一个调用点。

已排除 cordis 作为变量:@deepseek-ai/dsh 的 0.1.2-rc.1、0.1.3-alpha.2、0.1.5-rc.1 三个版本都钉 @deepseek-ai/cordis: ^4.0.2;且 cordis 4.0.1 与 4.0.2 的 lib/index.js 字节数完全一致(60228),严格服务访问检查两边都存在。横跨这段窗口变化的是 DSH 自己的 inject 列表。

影响范围

不只 dsh-pocket —— 任何通过 connection.rpc.handle() 注册 HTTP 通道的第三方插件都受影响(同类报告见 dsh-ssh、dsh-mnemon)。DSH 自家插件走 ctx.inject(['webServer'], ...) 拿子 ctx,因此不受影响。

两种表现形态:

  • 插件作为 boot 路径条目加载时 → 异常冒到顶层,整个 DSH 起不来(本 issue 的情形)
  • 插件在 ctx.inject 回调里注册时 → 异常被 fiber 吞掉,无任何控制台报错,通道静默缺失,任意 POST /<自定义通道> 返回 405

已实测的本地绕过(供使用者参考)

在 profile 的 cordis.patch.yml 给 connection 条目补回注入即可,无需改动任何已安装包:

- id: connection
  inject:
    - webRuntime
    - webServer

验证(隔离 DSH_HOME,profile 内装 dsh-pocket@2.10.3):

  • 打补丁前:复现上述堆栈,进程退出
  • 打补丁后:正常启动;POST /dsh-pocket → 401(通道已挂载,被认证栅栏拦下),对照组 POST /dsh-nonexistent-abc → 405
  • 且修复后 dshmarket 热挂载可直接成功,不再要求重启

注意:改 inject 会让 loader 走完整 dispose + re-init,所以建议停掉 DSH 再改,然后重启,不要在运行中改。

对本项目的建议(可选)

本项目自身代码没有问题,2.10.3 在 0.1.3-alpha.2 及以前运行正常。但当前这个异常会让宿主完全启动不了,对使用者来说是最重的一档故障,而它发生在插件的 apply() 同步路径上。

lib/web-rpc.js:35 已有同思路的降级分支:

if (!ctx?.connection?.rpc?.handle) {
  log.warn?.('dsh-pocket: DSH Host Connection RPC unavailable — settings tab disabled | 无 Connection RPC,设置页不可用');
  return () => {};
}

建议把紧随其后的 handle() 调用也纳入同样的降级,让宿主兼容性问题表现为“设置页不可用”而非“DSH 起不来”:

try {
  return ctx.connection.rpc.handle(POCKET_RPC_CHANNEL, handler, { authority: 'loopback' });
} catch (err) {
  log.warn?.('dsh-pocket: RPC channel registration failed, settings tab disabled | RPC 通道注册失败,设置页不可用:%s', err?.message ?? err);
  return () => {};
}

这只是在既有降级设计上多包一层,不影响正常环境。是否采纳由作者判断。

附:另一处 DSH 侧回归(与本项目相关,供参考)

0.1.5-rc.1 的 handle 签名是 handle: (channel, handler) => ...(lib/index.js:543),第三个参数被静默丢弃:

// dsh-pocket/lib/web-rpc.js:222
}, { authority: 'loopback' });

也就是说本项目请求的 loopback-only 限制实际没有生效,通道拿到的是通用栅栏(trustedHosts + 浏览器认证)。对局域网/公网扫码访问这一主场景影响不大,但如果项目里任何设计假设了“管理类端点仅本机可达”(例如 POCKET_ENDPOINTS.fileRead、pocketReset),这个假设在当前 DSH 版本下不成立。0.1.2-rc.1 / 0.1.3-alpha.2 上该参数正常生效。

上游线索

英文摘要 / English summary

After installing dsh-pocket 2.10.3 on DSH 0.1.5-rc.1, dsh web aborts during plugin-tree boot with cannot get property "webServer" without inject. The root cause is not in this plugin: @deepseek-ai/dsh-client-connection dropped webServer from its top-level inject in 0.1.5-alpha.1 while register() (line 618) still reads owner.webServer — a refactor that missed one call site. Cordis is ruled out (all three DSH versions pin ^4.0.2; cordis 4.0.1 and 4.0.2 are byte-identical in size and both contain the strict check). Every third-party plugin registering a channel via connection.rpc.handle() is affected. A profile-level inject patch on the connection entry is verified to fix it; a defensive try/catch around the handle() call here is suggested so this class of host incompatibility degrades to "settings tab disabled" instead of "DSH will not start".

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions