dsh-request-doctor
Verifieddsh-request-doctor · v0.18.3 · MIT · Web UI
自动修复客户端引起的请求失败,并让版本不匹配的 DSH 插件能装上、能跑起来。
Install
dsh plugin add dsh-request-doctor Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
request-doctor · 请求急救员
一个 DSH 插件:自动修好客户端这边造成的请求失败,并让版本对不上的插件能装上、能跑。
它解决什么问题
请求莫名失败
网络抖一下、服务端超时、模型返回空、上下文太长——这些都不是你操作的问题,但默认会让一次对话直接断掉。
插件判断这属于「本机这边能挽回」的失败后自动重试,间隔逐次拉长,不会把对方打得更糟。
不重试:欠费、限流、服务端故障、内容审核拦截、图片超限。这些重试没有意义。
不重放:会改动东西的操作(执行命令、写文件、发消息)。只重放只读的(读文件、搜索、抓网页、看页面)。
版本对不上的插件
DSH 升级后,旧插件可能只认 0.2.1-rc.2,而你现在是 0.2.1-rc.3。默认结果是插件用不了,甚至装不上。
插件不会去关掉这个检查,而是给那个插件搭一个它期望的旧环境,跟 Windows 的「以兼容模式运行」是一个意思——不是忽略问题,是提供它需要的环境。
分两种情况:
当前实现还够用:在插件目录里放一层很薄的转发包,声明旧版本号,调用转到当前实现。不复制代码,开销极小。
当前实现缺了插件要的东西:把插件当初对着写的那份真实旧版本从仓库取回来装进去。跑的是它认识的那份代码,不是一个缺胳膊少腿的替身。
取回的版本缓存在 profile 下的 .request-doctor-cache/,同一个版本只下载一次。
旧版本里也没有:如实报告不兼容。不伪造、不放占位——放一个「调用时才报错」的假货,只是把崩溃推迟到运行期,那是骗自己。
装不上的插件
分两种情况,命令只有一个:
/doctor install <插件目录,或者包名>
本地目录:先把环境备好,再正常安装,管理器的版本检查就会按它自己的规则通过。
注册表里的包(dsh-ssh-ops、@scope/[email protected] 这类):管理器的安装路径是「pnpm 装完再检查」。远程包的字节在检查之前就落地了,没法提前改好 manifest,所以它对不兼容的包永远装不上。
这种情况由插件自己把 pnpm 该做的事做一遍:取包、取依赖、对齐声明、建兼容环境、登记为 bundle、重载。装完会打印位置、环境状态、依赖和重载结果。
覆盖范围
扫描的是所有不兼容的插件,不是一个。来源包括:Loader 拒绝的包、已启用但没被拒绝的、已禁用但装着的、安装时环境还欠着的,以及加载失败后不在插件清单里、只能靠扫目录发现的。
建完会核对,不是建完就算
环境建好之后,会逐条确认插件要的每个符号是否真能拿到,再决定算不算修好。
这一条是必要的:垫片少写一个导出,插件在加载那一刻就会崩,而修复流程会以为一切正常。核对通不过就记为未修复,不谎报成功。
检查脚本也接上了核对:垫片存在但内容不对的插件会被重新适配,不会当作已经修好放着。
路由冲突只报告
两个插件可能声明同一个 LLM provider id。插件会提示你,但不会替你停用任何一个——那是你的插件,由你决定。
运行开销
稳态下几乎不耗资源。
- 源码和 manifest 按文件签名缓存,没变就不重解析
- 后台扫描健康时逐步退避到 30 分钟一次
- 内容没变就不写盘,不触发文件监听
- 长会话里那些记账用的结构都有上限
- 定时器全部
unref,不拖住进程退出
安装
仓库根目录就是包本身:没有构建步骤,没有运行时依赖,npm pack 出来 33 个文件,装进去就能用。
从可视化界面安装
打开「插件」页,选安装,填本仓库地址或本地目录。装完立即生效,不用重启。
仓库里带 dsh.bundle.patch,DSH 会据此把插件挂进组合;版本检查对得上,不会出现在「不兼容」列表里。
用命令安装
/doctor install <仓库本地路径或包名>
本地目录和注册表里的包都走这一条。
手动放进 profile
- 把本仓库内容复制到
<profile>/node_modules/request-doctor/ - 在
<profile>/package.json的dependencies里加上"request-doctor": "link:<本目录>" - 在
<profile>/cordis.patch.yml里插入:
- insert:
- id: request-doctor
name: 'request-doctor'
- 重启 DSH
装完自检
/doctor
会打印已修复的数量、最近几次修复的记录,以及最近一轮版本审计的结论。任何一项为 0 都正常——没坏东西可修的时候它不动作。
配置
都放在 cordis.patch.yml 的 config 下,全部可选。
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
autoRepair |
boolean | true |
总开关 |
autoVersionRepair |
boolean | true |
是否自动处理版本不匹配 |
maxRetries |
number | 12 |
单步模型请求最多修几次 |
maxToolRetries |
number | 2 |
单次工具调用最多重放几次 |
maxVersionRepairs |
number | 64 |
单轮扫描最多适配几个插件,0 表示不限 |
maxAuthRetries |
number | 2 |
鉴权类失败最多重试几次,额度单独且更小 |
maxRepeatedToolCalls |
number | 3 |
同名同参调用超过几次后不再重放 |
initialDelayMs |
number | 400 |
首次退避基准 |
maxDelayMs |
number | 20000 |
退避上限 |
jitterRatio |
number | 0.25 |
退避抖动比例 |
retryableToolPattern |
string | 见下 | 允许重放的工具名正则 |
statusFile |
string | profile 下的 .request-doctor-status.json |
诊断快照路径,空串表示不写盘 |
recordRetryEvents |
boolean | true |
是否写入会话重试记录 |
默认只读工具白名单:
^(read|read_image|glob|grep|web_fetch|web_search|cordis_inspect_.*|browser_(list_tabs|snapshot|get_text|wait|scroll)|db_(query|preview|describe_table|list_tables))$
使用
/doctor
看各类修复计数和最近发生的事。
/doctor install <目录路径或包名>
本地插件先备好环境再让你正常安装;注册表里的包则直接装上。已经装着的包会就地修复,不会重装一遍。
/doctor allow <包名@版本>
只授权、不安装。给那些想继续在插件面板里点安装的人用:先放行这个版本,再在界面上装,装完声明对齐和兼容环境会自动补上。
插件面板里的「不兼容安装」
打开「插件」,标签栏里会多一个「不兼容安装」。里面列出当前被 DSH 版本检查拒掉的插件,每行一个按钮;顶上还有一个输入框,可以直接填 包名@版本。
点按钮做的事和 /doctor install 一样:先按管理器的规则授权这个版本,再让它自己装,装完声明对齐和兼容环境由后台扫描补上。点完不会立刻变绿,等几秒。
界面是随插件一起加的客户端模块。新装的插件要先刷新一次页面,浏览器才会加载它——插件热替换只覆盖已经载入过的客户端模块。
页面渲染出错时不会留一片空白,而是把那句错显示出来。看到「这个页面渲染出错」就是那条信息本身在告诉你原因。
还原
被适配过的插件目录里有 package.json.request-doctor-original,是改动前的备份。
要恢复原状:删掉插件目录下的 node_modules(如果只有本插件为它建过),用备份覆盖 package.json,再删掉 .request-doctor-compat.json。
兼容环境只包含标记里记下的那些包,删除时也只删那些——插件自带的依赖不会被误删。
已知边界
- 兼容环境只能保证「插件要用的东西还在」。插件内部若有别的假设已经变了,仍可能表现异常,需要作者更新。
- 第三方依赖不在检查范围,那是插件自己的事。
- 安装前预置只支持本地目录。注册表包的字节还没落盘,提前看一眼是不可能的,这种情况会明确告诉你。
License
MIT
维护者检查
仓库里自带三个脚本,改完东西依次跑:
npm test # 单元与集成测试(仅仓库)
npm run check:quality # 代码质量门禁(复杂度、重复率、注释密度、文件长度)
npm run check:docs # 文档与实现是否还对得上
check:docs 会核对:需求条目是否都有覆盖行、报告引用的测试是否存在、设计说明的模块清单是否与 lib/ 一致、六份文档版本号、质量报告数字、路由与命令是否写进文档。它随包发布,也可以在解压后的包里直接跑。