Repository navigation
fix: 自注册 RPC 通道并迁移 sessionPersistence.stat,兼容 dsh 0.1.5-rc.2 - #4
Conversation
ce4b9b1 to
5c3029a
Compare
There was a problem hiding this comment.
事实核对
1. 背景前提成立
已对照已安装的 dsh 0.1.5-rc.1 中的 @deepseek-ai/dsh-client-connection@0.1.5-rc.2:
HostConnectionService.get rpc()取const owner = this.ctx,即 connection 服务自身的上下文;register()内执行owner.webServer.register(route)。- connection 插件声明
inject = ["credentials"],其自身上下文不含 webServer。 - 官方
apply自己也是走ctx.inject(["webServer"], (webCtx) => ...)再注册/api。 - cordis 的上下文代理在未 inject 时抛
cannot get property "<prop>" without inject。
因此「任何调用方走 ctx.connection.rpc.handle 都会在登记路由时抛 without inject,该 API 在 0.1.5 事实上不可用」属实,绕开它自注册路由是必要且合理的方向。
2. 替代方案与官方一致
webServer.register({ kind: 'prefix', path, handler })返回同步 disposer,prefix 语义为p与p/...,与插件用法一致。connection.requestRejection(req)实际执行 Host/Origin 校验加浏览器 cookie 鉴权,与官方/api路由同一条防线,复用正确。- endpoint 段正则与官方
ENDPOINT_SEGMENT_PATTERN完全相同。 sessionPersistence.stat(id, { signal })在 rc.2 存在,返回{ header: SessionHeader },SessionHeader.cwd为绝对工作目录;SessionStore.get(id)返回的Session.header.cwd也正确。冷读迁移方向正确。
3. 本地验证
pnpm typecheck、pnpm build、pnpm smoke 全绿,smoke 覆盖 describe 的 live/cold/未知/空 cwd/缺 id、listDir 过滤与目录优先、readFile、writeFile 及缺 content、未知端点、空端点 404、未授权 401。构建产物 lib/index.d.ts 自包含,未残留对 ./rpc-channel.ts 的引用。
修改要求
P1 必须修:超长请求体返回 ECONNRESET,413 分支不可达
src/rpc-channel.ts:159-162 在超限时先 req.destroy() 再 throw,serve 随后才 res.writeHead(413),此时 socket 已被销毁,异常响应永远发不出去。实测:
7MiB -> HTTP 200,落盘成功
8MiB -> fetch 抛错 SocketError: other side closed
8.5MiB -> fetch 抛错 read ECONNRESET
对照最小复现,官方顺序先回再销毁可以正常送达 413:
先 res.writeHead(413, {connection:'close'}); res.end(); req.destroy() -> HTTP 413
先 req.destroy(); res.writeHead(413); res.end() -> fetch 抛错
官方 bridge 的做法是:先按 content-length 快速拒绝,流式读取超限时 res.writeHead(413, { connection: 'close' })、res.end(),最后才 req.destroy()。请照此顺序修正,并保证客户端至少能收到 413 而不是连接复位。
P2 必须处理:8MiB 上限会静默废掉大文件保存
MAX_BODY_BYTES = 8 * 1024 * 1024,而请求体是整份文件内容加 JSON 封装,实测约 8MiB 的文件即可写入失败。readFile 的响应不限长,于是会出现能打开大文件但保存不了的不一致。官方 bridge 默认上限为 300MiB,本次迁移把有效上限从 300MiB 降到 8MiB。请二选一:把上限提到与官方接近并说明依据,或明确说明 8MiB 是编辑器定位下的有意取舍,同时修好 P1 让超限有清晰报错。
P3 建议修:取消信号是死参数
serve 用 new AbortController().signal 调用 handler,从未 abort;官方传的是 request.signal,客户端断开即取消。因此 describe 的 stat 与 listDir 的 fs.resolve 收到的 signal 永远不会触发,断线后后台工作继续。建议绑定 res.on('close') 触发 abort,或复用官方 bridge 等价逻辑。
P4 建议修:未校验 content-type
官方对非 application/json 返回 415,插件实测接受 text/plain 并正常处理。鉴权不受影响,但属于与官方信封契约的偏离,建议补齐 415。
P5 建议修:README 与实现已矛盾
README.md:73 仍写「Host 端经 ctx.connection.rpc.handle('/classic-coding', ...) 注册」「插件端点必须走 handle 独立通道」,与本次改动正好相反。请同步为自注册 webServer prefix 路由的描述。
P6 建议:补测试与 CI
smoke 未覆盖请求体限长与 content-type 两条路径,正好是本次新引入且唯一出问题的地方,建议补上超限 413 与 content-type 拒绝的用例。另 .github/workflows/ci.yml 只跑 typecheck、build 与产物校验,未跑 pnpm smoke,可一并加上。
P7 建议:清理小问题
src/rpc-channel.ts文件末尾缺换行。src/index.ts的RpcResult与src/rpc-channel.ts的RpcChannelResult结构重复,可只保留一处。- 插件绕开官方 connection API 自行注册 RPC 通道,是会波及其他插件的架构决策,建议按工作区规范补一份 ADR 并同步到
dsh-pause等同样走 RPC 的插件。
结论
核心修复方向正确,背景前提与 stat 迁移都经源码核实,构建与现有 smoke 全绿。但请求体限长这条新逻辑存在确定的实现缺陷:超限时连接被复位、413 永远发不出,且有效上限从官方 300MiB 降到 8MiB 会直接影响大文件保存。P1、P2 修好之前不建议合并。
P1 超限先回 413 再断连接,并按 content-length 快速拒绝;P2 上限提到 300MiB 对齐官方 bridge;P3 取消信号绑定 res close;P4 非 application/json 返回 415;P5 README 同步自注册 prefix 路由;P6 smoke 补超限 413 与 content-type 415 用例、CI 加冒烟;P7 去掉重复的 RpcResult 结构。
smoke 加入 CI 后在 ubuntu 暴露既有问题:ABS_RE 只认 Windows 盘符与 UNC,/tmp 下的路径全被拒。补上 POSIX 根路径分支。
There was a problem hiding this comment.
复审
事实核对
1. CI 已转绿
前一个 head bca0f34 曾双红,日志为 path 必须是绝对路径:收到 /tmp/cc-verify-...,根因是 ABS_RE = /^[A-Za-z]:[\\/]|^\\\\/ 只认 Windows 盘符与 UNC,冒烟测试进 CI 后在 ubuntu 把 /tmp 全判成相对路径。44fc74a 补上 POSIX 根路径分支后,远端 CI 两个 Node 版本均通过。
2. 前一轮 P1 到 P7 修复仍成立
- P1
tooLarge先回 413 再断连接,另有 content-length 快速拒绝;流式超限路径实测回 413。 - P2 上限对齐官方 bridge 的 300MiB,9MiB 写入实测 200 并正确落盘。
- P3
res.on('close')触发 abort,实测断开后 signal 变为 aborted。 - P4 非
application/json返回 415,解析方式与官方一致。 - P5 README 改为自注册 prefix 路由的描述。
- P6 smoke 新增 415 与 413 断言,CI 加入
pnpm smoke。 - P7 删除重复的
RpcResult,rpc-channel.ts 末尾补换行。
3. 本轮 44fc74a 改动核对
- 只改
src/index.ts两行,ABS_RE扩为/^[A-Za-z]:[\\/]|^\\\\|^\//,覆盖 Windows 盘符、UNC 与 POSIX 根路径。 - 本地
pnpm build、pnpm smoke全绿。 - 独立复现:
/tmp/cc-verify-xxx不再报绝对路径错误,relative/x仍被正确拒绝。 - 未引入其他行为变更。
修改要求
本轮无阻塞项,以下均为可选建议。
R1 建议:改用 path.isAbsolute
手写正则在所有平台同时接受两种路径形式,例如 Linux 上也会接受 C:\x,虽无害但不精确。建议改用 node:path 的 isAbsolute,由运行时按平台判定,语义更清晰也更不易漏分支。
R2 建议:加固 smoke 的落盘断言
smoke/verify.mjs 中 check('writeFile 落盘', await readFile(target)) 未做保护,writeFile 一旦失败,该读取抛出的 ENOENT 会让整个脚本崩溃并吞掉真正的 FAIL 摘要,上一轮 CI 日志里就出现过。建议包进 try/catch,失败记一条 FAIL 后继续执行后续断言。
R3 建议:同步 PR 描述
描述仍是首轮内容,未覆盖 415、413 顺序、300MiB、CI 冒烟、README 更新与本轮的 Linux 修复。
R4 建议:补 ADR
插件绕开官方 connection.rpc.handle 自注册 RPC 通道,是会波及其他走 RPC 插件的架构决策,前两轮均已提,仍建议按工作区规范落一份 ADR。
结论
P1 到 P7 已全部修复并复现通过,44fc74a 进一步修掉 Linux CI 失败,远端 CI 现为双绿。无阻塞项,其余均为非阻塞建议。建议合并。
R1 绝对路径校验改用 node:path 的 isAbsolute,按平台判定,删掉手写正则;R2 smoke 的 writeFile 落盘断言包进 try/catch,失败时记 FAIL 而不是让整个脚本崩溃。
dsh-classic-coding 兼容 dsh 0.1.5-rc.2
背景
dsh 0.1.5-rc.1 与 @deepseek-ai/dsh-client-connection 0.1.5-rc.2 升级后,HostConnectionService.register 在登记路由时于 connection 服务自身上下文解析 webServer,而该上下文只 inject 了 credentials,于是抛 cannot get property "webServer" without inject,插件树因此加载失败。官方 /api 走 ctx.inject(['webServer'], ...),从不经过 rpc.handle,说明该 API 在 0.1.5 事实上已不可用。
改动
验证
pnpm typecheck、pnpm build、pnpm smoke 全绿。