@luolingp/request-doctor 0.0.0-stage → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +184 -3
- package/client.js +174 -0
- package/cordis.patch.yml +16 -0
- package/docs/00-/346/226/207/346/241/243/347/264/242/345/274/225.md +26 -0
- package/docs/01-/350/275/257/344/273/266/351/234/200/346/261/202/350/247/204/346/240/274/350/257/264/346/230/216.md +192 -0
- package/docs/02-/350/275/257/344/273/266/350/256/276/350/256/241/350/257/264/346/230/216.md +391 -0
- package/docs/03-/350/275/257/344/273/266/346/265/213/350/257/225/346/212/245/345/221/212.md +214 -0
- package/docs/04-/345/256/211/350/243/205/351/203/250/347/275/262/350/257/264/346/230/216.md +176 -0
- package/docs/05-/347/224/250/346/210/267/346/211/213/345/206/214.md +197 -0
- package/docs/06-/350/275/257/344/273/266/350/264/250/351/207/217/345/272/246/351/207/217/346/212/245/345/221/212.md +109 -0
- package/docs/quality-report.json +27 -0
- package/icon.svg +12 -0
- package/index.js +471 -0
- package/lib/arbitrate.js +45 -0
- package/lib/cache-store.js +79 -0
- package/lib/classify.js +389 -0
- package/lib/compat-env.js +490 -0
- package/lib/compat-repair.js +196 -0
- package/lib/compat.js +332 -0
- package/lib/diagnostics.js +77 -0
- package/lib/env-verify.js +160 -0
- package/lib/install-bundle.js +199 -0
- package/lib/install-rescue.js +161 -0
- package/lib/install-support.js +206 -0
- package/lib/model-repair.js +190 -0
- package/lib/package-fetch.js +181 -0
- package/lib/plugin-discovery.js +232 -0
- package/lib/repair-listeners.js +126 -0
- package/lib/restart-notice.js +21 -0
- package/lib/retry-ledger.js +67 -0
- package/lib/runtime-probe.js +171 -0
- package/lib/semver.js +159 -0
- package/lib/sleep.js +18 -0
- package/lib/stat-cache.js +67 -0
- package/lib/symbol-alias.js +44 -0
- package/lib/timers.js +39 -0
- package/lib/version-repair.js +499 -0
- package/lib/version-search.js +44 -0
- package/locale/en.json +17 -0
- package/locale/zh.json +17 -0
- package/package.json +51 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-request-doctor contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,184 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# request-doctor · 请求急救员
|
|
2
|
+
|
|
3
|
+
一个 DSH 插件:自动修好客户端这边造成的请求失败,并让版本对不上的插件能装上、能跑。
|
|
4
|
+
|
|
5
|
+
## 它解决什么问题
|
|
6
|
+
|
|
7
|
+
### 请求莫名失败
|
|
8
|
+
|
|
9
|
+
网络抖一下、服务端超时、模型返回空、上下文太长——这些都不是你操作的问题,但默认会让一次对话直接断掉。
|
|
10
|
+
|
|
11
|
+
插件判断这属于「本机这边能挽回」的失败后自动重试,间隔逐次拉长,不会把对方打得更糟。
|
|
12
|
+
|
|
13
|
+
**不重试**:欠费、限流、服务端故障、内容审核拦截、图片超限。这些重试没有意义。
|
|
14
|
+
|
|
15
|
+
**不重放**:会改动东西的操作(执行命令、写文件、发消息)。只重放只读的(读文件、搜索、抓网页、看页面)。
|
|
16
|
+
|
|
17
|
+
### 版本对不上的插件
|
|
18
|
+
|
|
19
|
+
DSH 升级后,旧插件可能只认 `0.2.1-rc.2`,而你现在是 `0.2.1-rc.3`。默认结果是插件用不了,甚至装不上。
|
|
20
|
+
|
|
21
|
+
插件不会去关掉这个检查,而是**给那个插件搭一个它期望的旧环境**,跟 Windows 的「以兼容模式运行」是一个意思——不是忽略问题,是提供它需要的环境。
|
|
22
|
+
|
|
23
|
+
分两种情况:
|
|
24
|
+
|
|
25
|
+
**当前实现还够用**:在插件目录里放一层很薄的转发包,声明旧版本号,调用转到当前实现。不复制代码,开销极小。
|
|
26
|
+
|
|
27
|
+
**当前实现缺了插件要的东西**:把插件当初对着写的那份**真实旧版本**从仓库取回来装进去。跑的是它认识的那份代码,不是一个缺胳膊少腿的替身。
|
|
28
|
+
|
|
29
|
+
取回的版本缓存在 profile 下的 `.request-doctor-cache/`,同一个版本只下载一次。
|
|
30
|
+
|
|
31
|
+
**旧版本里也没有**:如实报告不兼容。不伪造、不放占位——放一个「调用时才报错」的假货,只是把崩溃推迟到运行期,那是骗自己。
|
|
32
|
+
|
|
33
|
+
### 装不上的插件
|
|
34
|
+
|
|
35
|
+
分两种情况,命令只有一个:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
/doctor install <插件目录,或者包名>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**本地目录**:先把环境备好,再正常安装,管理器的版本检查就会按它自己的规则通过。
|
|
42
|
+
|
|
43
|
+
**注册表里的包**(`dsh-ssh-ops`、`@scope/name@1.2.3` 这类):管理器的安装路径是「pnpm 装完再检查」。远程包的字节在检查之前就落地了,没法提前改好 manifest,所以它对不兼容的包永远装不上。
|
|
44
|
+
|
|
45
|
+
这种情况由插件自己把 pnpm 该做的事做一遍:取包、取依赖、对齐声明、建兼容环境、登记为 bundle、重载。装完会打印位置、环境状态、依赖和重载结果。
|
|
46
|
+
|
|
47
|
+
## 覆盖范围
|
|
48
|
+
|
|
49
|
+
扫描的是**所有**不兼容的插件,不是一个。来源包括:Loader 拒绝的包、已启用但没被拒绝的、已禁用但装着的、安装时环境还欠着的,以及加载失败后不在插件清单里、只能靠扫目录发现的。
|
|
50
|
+
|
|
51
|
+
## 建完会核对,不是建完就算
|
|
52
|
+
|
|
53
|
+
环境建好之后,会逐条确认插件要的每个符号是否真能拿到,再决定算不算修好。
|
|
54
|
+
|
|
55
|
+
这一条是必要的:垫片少写一个导出,插件在加载那一刻就会崩,而修复流程会以为一切正常。核对通不过就记为未修复,不谎报成功。
|
|
56
|
+
|
|
57
|
+
检查脚本也接上了核对:垫片存在但内容不对的插件会被重新适配,不会当作已经修好放着。
|
|
58
|
+
|
|
59
|
+
## 路由冲突只报告
|
|
60
|
+
|
|
61
|
+
两个插件可能声明同一个 LLM provider id。插件会提示你,但**不会**替你停用任何一个——那是你的插件,由你决定。
|
|
62
|
+
|
|
63
|
+
## 运行开销
|
|
64
|
+
|
|
65
|
+
稳态下几乎不耗资源。
|
|
66
|
+
|
|
67
|
+
- 源码和 manifest 按文件签名缓存,没变就不重解析
|
|
68
|
+
- 后台扫描健康时逐步退避到 30 分钟一次
|
|
69
|
+
- 内容没变就不写盘,不触发文件监听
|
|
70
|
+
- 长会话里那些记账用的结构都有上限
|
|
71
|
+
- 定时器全部 `unref`,不拖住进程退出
|
|
72
|
+
|
|
73
|
+
## 安装
|
|
74
|
+
|
|
75
|
+
仓库根目录就是包本身:没有构建步骤,没有运行时依赖,`npm pack` 出来 33 个文件,装进去就能用。
|
|
76
|
+
|
|
77
|
+
### 从可视化界面安装
|
|
78
|
+
|
|
79
|
+
打开「插件」页,选安装,填本仓库地址或本地目录。装完立即生效,不用重启。
|
|
80
|
+
|
|
81
|
+
仓库里带 `dsh.bundle.patch`,DSH 会据此把插件挂进组合;版本检查对得上,不会出现在「不兼容」列表里。
|
|
82
|
+
|
|
83
|
+
### 用命令安装
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
/doctor install <仓库本地路径或包名>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
本地目录和注册表里的包都走这一条。
|
|
90
|
+
|
|
91
|
+
### 手动放进 profile
|
|
92
|
+
|
|
93
|
+
1. 把本仓库内容复制到 `<profile>/node_modules/request-doctor/`
|
|
94
|
+
2. 在 `<profile>/package.json` 的 `dependencies` 里加上 `"request-doctor": "link:<本目录>"`
|
|
95
|
+
3. 在 `<profile>/cordis.patch.yml` 里插入:
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
- insert:
|
|
99
|
+
- id: request-doctor
|
|
100
|
+
name: 'request-doctor'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
1. 重启 DSH
|
|
104
|
+
|
|
105
|
+
### 装完自检
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
/doctor
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
会打印已修复的数量、最近几次修复的记录,以及最近一轮版本审计的结论。任何一项为 0 都正常——没坏东西可修的时候它不动作。
|
|
112
|
+
|
|
113
|
+
## 配置
|
|
114
|
+
|
|
115
|
+
都放在 `cordis.patch.yml` 的 `config` 下,全部可选。
|
|
116
|
+
|
|
117
|
+
| 配置项 | 类型 | 默认 | 说明 |
|
|
118
|
+
| --- | --- | --- | --- |
|
|
119
|
+
| `autoRepair` | boolean | `true` | 总开关 |
|
|
120
|
+
| `autoVersionRepair` | boolean | `true` | 是否自动处理版本不匹配 |
|
|
121
|
+
| `maxRetries` | number | `12` | 单步模型请求最多修几次 |
|
|
122
|
+
| `maxToolRetries` | number | `2` | 单次工具调用最多重放几次 |
|
|
123
|
+
| `maxVersionRepairs` | number | `64` | 单轮扫描最多适配几个插件,`0` 表示不限 |
|
|
124
|
+
| `maxAuthRetries` | number | `2` | 鉴权类失败最多重试几次,额度单独且更小 |
|
|
125
|
+
| `maxRepeatedToolCalls` | number | `3` | 同名同参调用超过几次后不再重放 |
|
|
126
|
+
| `initialDelayMs` | number | `400` | 首次退避基准 |
|
|
127
|
+
| `maxDelayMs` | number | `20000` | 退避上限 |
|
|
128
|
+
| `jitterRatio` | number | `0.25` | 退避抖动比例 |
|
|
129
|
+
| `retryableToolPattern` | string | 见下 | 允许重放的工具名正则 |
|
|
130
|
+
| `recordRetryEvents` | boolean | `true` | 是否写入会话重试记录 |
|
|
131
|
+
|
|
132
|
+
默认只读工具白名单:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
^(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))$
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 使用
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
/doctor
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
看各类修复计数和最近发生的事。
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
/doctor install <目录路径或包名>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
本地插件先备好环境再让你正常安装;注册表里的包则直接装上。已经装着的包会就地修复,不会重装一遍。
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
/doctor allow <包名@版本>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
只授权、不安装。给那些想继续在插件面板里点安装的人用:先放行这个版本,再在界面上装,装完声明对齐和兼容环境会自动补上。
|
|
157
|
+
|
|
158
|
+
## 插件面板里的「不兼容安装」
|
|
159
|
+
|
|
160
|
+
打开「插件」,标签栏里会多一个「不兼容安装」。里面列出当前被 DSH 版本检查拒掉的插件,每行一个按钮;顶上还有一个输入框,可以直接填 `包名@版本`。
|
|
161
|
+
|
|
162
|
+
点按钮做的事和 `/doctor install` 一样:先按管理器的规则授权这个版本,再让它自己装,装完声明对齐和兼容环境由后台扫描补上。点完不会立刻变绿,等几秒。
|
|
163
|
+
|
|
164
|
+
界面是随插件一起加的客户端模块。**新装的插件要先刷新一次页面**,浏览器才会加载它——插件热替换只覆盖已经载入过的客户端模块。
|
|
165
|
+
|
|
166
|
+
页面渲染出错时不会留一片空白,而是把那句错显示出来。看到「这个页面渲染出错」就是那条信息本身在告诉你原因。
|
|
167
|
+
|
|
168
|
+
## 还原
|
|
169
|
+
|
|
170
|
+
被适配过的插件目录里有 `package.json.request-doctor-original`,是改动前的备份。
|
|
171
|
+
|
|
172
|
+
要恢复原状:删掉插件目录下的 `node_modules`(如果只有本插件为它建过),用备份覆盖 `package.json`,再删掉 `.request-doctor-compat.json`。
|
|
173
|
+
|
|
174
|
+
兼容环境只包含标记里记下的那些包,删除时也只删那些——插件自带的依赖不会被误删。
|
|
175
|
+
|
|
176
|
+
## 已知边界
|
|
177
|
+
|
|
178
|
+
- 兼容环境只能保证「插件要用的东西还在」。插件内部若有别的假设已经变了,仍可能表现异常,需要作者更新。
|
|
179
|
+
- 第三方依赖不在检查范围,那是插件自己的事。
|
|
180
|
+
- 安装前预置只支持本地目录。注册表包的字节还没落盘,提前看一眼是不可能的,这种情况会明确告诉你。
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
package/client.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// 插件面板里的「不兼容安装」页。
|
|
2
|
+
//
|
|
3
|
+
// 管理器把版本检查放在安装流程里,对不上的包直接拒掉,然后原样回滚。它自己留了版本
|
|
4
|
+
// 豁免这个出口,但只有命令行或它自己的弹窗能走。这里把它做成一个页面:列出当前被拒的
|
|
5
|
+
// 插件,一键授权并安装,装完交给后台扫描去对齐声明、建兼容环境。
|
|
6
|
+
//
|
|
7
|
+
// 界面和 Host 之间走同源 HTTP,不碰 typert 的 remote 门面。动态加载的客户端模块拿不到
|
|
8
|
+
// 那个服务(它的上下文里没有),而 fetch 只要同源就行。Host 侧对应
|
|
9
|
+
// /doctor/incompatible 与 /doctor/install 两条路由。
|
|
10
|
+
window.__ModuleLoader__.load({
|
|
11
|
+
id: '@luolingp/request-doctor',
|
|
12
|
+
factory: (require) => {
|
|
13
|
+
const React = require('react')
|
|
14
|
+
const h = React.createElement
|
|
15
|
+
const inject = ['slots']
|
|
16
|
+
|
|
17
|
+
// 界面文案。语言服务不再参与:动态模块拿不到它,标签还曾经因此显示成键名。
|
|
18
|
+
const TEXT = {
|
|
19
|
+
tabLabel: '不兼容安装',
|
|
20
|
+
intro: '下面是被当前 DSH 版本拒装的插件。装它们只需要一步:先按管理器的规则授权这个版本,再让它自己装。装完之后,声明对齐和兼容环境会自动补上。',
|
|
21
|
+
placeholder: '包名@版本,例如 dsh-ssh-ops@0.4.0',
|
|
22
|
+
install: '授权并安装',
|
|
23
|
+
requires: '要求运行在',
|
|
24
|
+
none: '当前没有被拒装的插件。可以在上面直接填包名安装,或点「重新扫描」重算一遍。',
|
|
25
|
+
working: '正在处理',
|
|
26
|
+
done: '已装好,兼容环境会在后台补上',
|
|
27
|
+
failed: '没能装上',
|
|
28
|
+
rescan: '重新扫描',
|
|
29
|
+
offline: '连不上 Host 侧的安装路由,可以改用命令:',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const style = {
|
|
33
|
+
column: { display: 'flex', flexDirection: 'column', gap: 14, width: '100%', minWidth: 0 },
|
|
34
|
+
title: { margin: 0, fontSize: '0.95em', fontWeight: 600 },
|
|
35
|
+
text: { margin: 0, fontSize: '0.9em', lineHeight: 1.6, opacity: 0.8 },
|
|
36
|
+
hush: { margin: 0, fontSize: '0.9em', opacity: 0.7 },
|
|
37
|
+
mono: { margin: 0, fontSize: '0.85em', opacity: 0.75, fontFamily: 'monospace' },
|
|
38
|
+
line: { display: 'flex', gap: 8, alignItems: 'center' },
|
|
39
|
+
field: {
|
|
40
|
+
flex: 1, padding: '6px 10px', borderRadius: 8, font: 'inherit',
|
|
41
|
+
border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4))',
|
|
42
|
+
background: 'var(--dsw-alias-bg-l1, transparent)', color: 'inherit',
|
|
43
|
+
},
|
|
44
|
+
press: {
|
|
45
|
+
padding: '6px 14px', borderRadius: 8, cursor: 'pointer', font: 'inherit',
|
|
46
|
+
border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.4))',
|
|
47
|
+
background: 'var(--dsw-alias-bg-l2, transparent)', color: 'inherit',
|
|
48
|
+
},
|
|
49
|
+
card: {
|
|
50
|
+
display: 'flex', flexDirection: 'column', gap: 8, padding: '10px 12px', borderRadius: 10,
|
|
51
|
+
border: '1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))',
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 统一走同源路由:取回 JSON,失败就把原因抛出去,由调用方决定怎么显示。
|
|
56
|
+
async function requestJson(path, init) {
|
|
57
|
+
const response = await fetch(path, init)
|
|
58
|
+
return response.json()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 读一次被拒插件清单。Host 侧已经把不可用的行筛掉了。
|
|
62
|
+
async function loadRows() {
|
|
63
|
+
const data = await requestJson('/doctor/incompatible', { headers: { accept: 'application/json' } })
|
|
64
|
+
if (data.ok !== true) throw new Error(data.reason || 'host declined')
|
|
65
|
+
return data.rows || []
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 装一个包。授权、重试、声明对齐都在 Host 侧完成,这里只拿结果。
|
|
69
|
+
async function postInstall(spec) {
|
|
70
|
+
const data = await requestJson('/doctor/install', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'content-type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({ spec }),
|
|
74
|
+
})
|
|
75
|
+
return { ok: data.ok === true, reason: data.text || '' }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 让 Host 立刻重算一轮审计,不用等下一次定时扫描。
|
|
79
|
+
async function postRescan() {
|
|
80
|
+
const data = await requestJson('/doctor/rescan')
|
|
81
|
+
return { ok: data.ok === true, reason: data.reason || '' }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function IncompatiblePanel() {
|
|
85
|
+
const [rows, setRows] = React.useState([])
|
|
86
|
+
const [spec, setSpec] = React.useState('')
|
|
87
|
+
const [busy, setBusy] = React.useState(false)
|
|
88
|
+
const [note, setNote] = React.useState('')
|
|
89
|
+
|
|
90
|
+
const refresh = React.useCallback(async () => {
|
|
91
|
+
try {
|
|
92
|
+
setRows(await loadRows())
|
|
93
|
+
} catch (error) {
|
|
94
|
+
// 路由不通就把可用的替代办法说出来,不留空白。
|
|
95
|
+
setNote(`${TEXT.offline} /doctor install <包名>(${String((error && error.message) || error)})`)
|
|
96
|
+
}
|
|
97
|
+
}, [])
|
|
98
|
+
|
|
99
|
+
React.useEffect(() => { refresh() }, [refresh])
|
|
100
|
+
|
|
101
|
+
// 两个入口共用同一段收尾:跑、报结果、刷新列表。
|
|
102
|
+
async function run(label, task) {
|
|
103
|
+
setBusy(true)
|
|
104
|
+
setNote(`${TEXT.working} ${label}`)
|
|
105
|
+
try {
|
|
106
|
+
const outcome = await task()
|
|
107
|
+
setNote(outcome.ok ? `${TEXT.done}:${label}` : `${TEXT.failed}:${outcome.reason}`)
|
|
108
|
+
await refresh()
|
|
109
|
+
} catch (error) {
|
|
110
|
+
setNote(`${TEXT.failed}:${String(error)}`)
|
|
111
|
+
} finally {
|
|
112
|
+
setBusy(false)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const submit = () => {
|
|
117
|
+
const trimmed = spec.trim()
|
|
118
|
+
if (trimmed === '') return
|
|
119
|
+
run(trimmed, () => postInstall(trimmed))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const children = []
|
|
123
|
+
children.push(h('p', { key: 'title', style: style.title }, TEXT.tabLabel))
|
|
124
|
+
children.push(h('p', { key: 'intro', style: style.text }, TEXT.intro))
|
|
125
|
+
children.push(h('div', { key: 'entry', style: style.line },
|
|
126
|
+
h('input', {
|
|
127
|
+
value: spec, style: style.field, placeholder: TEXT.placeholder,
|
|
128
|
+
onChange: (event) => setSpec(event.target.value),
|
|
129
|
+
}),
|
|
130
|
+
h('button', {
|
|
131
|
+
type: 'button', style: style.press, disabled: busy, onClick: submit,
|
|
132
|
+
}, TEXT.install),
|
|
133
|
+
h('button', {
|
|
134
|
+
type: 'button', style: style.press, disabled: busy,
|
|
135
|
+
onClick: () => run(TEXT.rescan, postRescan),
|
|
136
|
+
style: { ...style.press, opacity: 0.8 },
|
|
137
|
+
}, TEXT.rescan)))
|
|
138
|
+
|
|
139
|
+
if (rows.length === 0) {
|
|
140
|
+
children.push(h('p', { key: 'none', style: style.hush }, TEXT.none))
|
|
141
|
+
} else {
|
|
142
|
+
children.push(h('div', { key: 'rows', style: style.column }, rows.map((row) => h('div', {
|
|
143
|
+
key: `${row.name}@${row.version}`, style: style.card,
|
|
144
|
+
},
|
|
145
|
+
h('div', { style: style.title }, `${row.name}@${row.version}`),
|
|
146
|
+
h('div', { style: style.hush }, `${TEXT.requires} ${row.runtimeVersion || '?'}`),
|
|
147
|
+
h('button', {
|
|
148
|
+
type: 'button', style: style.press, disabled: busy,
|
|
149
|
+
onClick: () => run(`${row.name}@${row.version}`, () => postInstall(`${row.name}@${row.version}`)),
|
|
150
|
+
}, TEXT.install)))))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Host 的返回是多行文本(安装结果、位置、兼容环境),按原样保留换行。
|
|
154
|
+
if (note !== '') children.push(h('p', { key: 'note', style: { ...style.hush, whiteSpace: 'pre-wrap' } }, note))
|
|
155
|
+
return h('div', { style: style.column }, ...children)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function apply(ctx) {
|
|
159
|
+
if (typeof ctx.slots?.inject !== 'function') return
|
|
160
|
+
const cells = [
|
|
161
|
+
['plugins.tab', 'requestDoctorIncompatible', 5, TEXT.tabLabel, IncompatiblePanel],
|
|
162
|
+
['settings.plugins.tab', 'requestDoctorSettingsTab', 20, TEXT.tabLabel, IncompatiblePanel],
|
|
163
|
+
]
|
|
164
|
+
for (const [slot, id, order, label, Component] of cells) {
|
|
165
|
+
ctx.slots.inject(slot, () => ctx.slots.register({ name: slot, id, order, label }, Component))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const module = { exports: {} }
|
|
170
|
+
module.exports.apply = apply
|
|
171
|
+
module.exports.inject = inject
|
|
172
|
+
return module.exports
|
|
173
|
+
},
|
|
174
|
+
})
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
- insert:
|
|
2
|
+
- id: @luolingp/request-doctor
|
|
3
|
+
name: '@luolingp/request-doctor'
|
|
4
|
+
config:
|
|
5
|
+
autoRepair: true
|
|
6
|
+
autoVersionRepair: true
|
|
7
|
+
recordRetryEvents: true
|
|
8
|
+
maxRetries: 12
|
|
9
|
+
maxToolRetries: 2
|
|
10
|
+
maxVersionRepairs: 64
|
|
11
|
+
maxAuthRetries: 2
|
|
12
|
+
maxRepeatedToolCalls: 3
|
|
13
|
+
initialDelayMs: 400
|
|
14
|
+
maxDelayMs: 20000
|
|
15
|
+
jitterRatio: 0.25
|
|
16
|
+
retryableToolPattern: '^(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))$'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 文档索引
|
|
2
|
+
|
|
3
|
+
| 编号 | 文档 | 对应标准条款 | 读者 |
|
|
4
|
+
| --- | --- | --- | --- |
|
|
5
|
+
| 01 | [软件需求规格说明](01-软件需求规格说明.md) | 6.1 | 开发者、验收方 |
|
|
6
|
+
| 02 | [软件设计说明](02-软件设计说明.md) | 6.2 | 开发者、维护者 |
|
|
7
|
+
| 03 | [软件测试报告](03-软件测试报告.md) | 6.6 | 验收方、维护者 |
|
|
8
|
+
| 04 | [安装部署说明](04-安装部署说明.md) | 6.7 | 使用者、运维 |
|
|
9
|
+
| 05 | [用户手册](05-用户手册.md) | 6.5 | 使用者 |
|
|
10
|
+
| 06 | [软件质量度量报告](06-软件质量度量报告.md) | GB/T 45717-2025 | 验收方 |
|
|
11
|
+
| — | [质量度量机器可读结果](quality-report.json) | — | 自动化门禁 |
|
|
12
|
+
|
|
13
|
+
顶层文件:
|
|
14
|
+
|
|
15
|
+
| 文件 | 说明 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `README.md` | 产品概述与快速上手 |
|
|
18
|
+
| `LICENSE` | 许可协议 |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 文档维护规则
|
|
23
|
+
|
|
24
|
+
1. 需求变更先改《软件需求规格说明》,再改设计与测试报告。
|
|
25
|
+
2. 任何度量项变化后重新生成《软件质量度量报告》与 `quality-report.json`。
|
|
26
|
+
3. 新增模块必须在《软件设计说明》第 2.2 节登记。
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# 软件需求规格说明
|
|
2
|
+
|
|
3
|
+
**产品名称**:request-doctor(请求急救员)
|
|
4
|
+
**产品标识**:request-doctor
|
|
5
|
+
**版本**:0.11.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1 引言
|
|
10
|
+
|
|
11
|
+
### 1.1 标识
|
|
12
|
+
|
|
13
|
+
本文档适用于 request-doctor 插件。该插件以 npm 包形式发布,模块名 `request-doctor`,运行于 DeepSeek Harness(以下简称 DSH)宿主进程内,属于 Host 端插件,无客户端一半。
|
|
14
|
+
|
|
15
|
+
### 1.2 系统概述
|
|
16
|
+
|
|
17
|
+
DSH 是一个以 Cordis 插件框架组织的智能体运行时。插件通过组合包(bundle)安装进某个 profile,由 Loader 在启动时组合。系统存在两类对用户可见的失败:
|
|
18
|
+
|
|
19
|
+
1. **请求失败**:模型请求、工具调用、浏览器载体请求在传输层、承载层或超时层失败,本身可重试,但运行时默认不重试或重试策略不足以覆盖。
|
|
20
|
+
2. **版本不匹配**:已安装插件声明的 DSH 依赖范围与当前运行时版本不符,导致插件被 Loader 拒绝组合,或安装时被管理器拒绝。
|
|
21
|
+
|
|
22
|
+
本插件对这两类失败自动处置,无需人工介入。
|
|
23
|
+
|
|
24
|
+
### 1.3 文档概述
|
|
25
|
+
|
|
26
|
+
本文档规定功能需求、外部接口需求、性能需求、设计约束、软件质量特性与验收准则。安全与隐私需求见第 5 章。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2 引用文件
|
|
31
|
+
|
|
32
|
+
| 编号 | 名称 | 说明 |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| GB/T 8567-2006 | 计算机软件文档编制规范 | 本文档及配套文档的编制依据 |
|
|
35
|
+
| GB/T 45717-2025 | 信息技术 软件测量 软件质量测量 自动化的源代码质量测度 | 源代码质量度量依据 |
|
|
36
|
+
| GB/T 8566-2007 | 信息技术 软件生存周期过程 | 验证与确认过程依据 |
|
|
37
|
+
| GB/T 25000.51-2016 | 系统与软件质量要求和评价(SQuaRE) | 质量特性分类参考 |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 3 需求
|
|
42
|
+
|
|
43
|
+
### 3.1 所需状态和方式
|
|
44
|
+
|
|
45
|
+
插件在 DSH 宿主进程内以单例方式运行,生命周期由 Cordis 上下文管理。插件被禁用或卸载时,所有定时器、进行中的重试、监听器必须一并释放,不得残留。
|
|
46
|
+
|
|
47
|
+
### 3.2 功能需求
|
|
48
|
+
|
|
49
|
+
#### FR-1 模型请求自动修复
|
|
50
|
+
|
|
51
|
+
当 `agent/request-error` 事件触发时:
|
|
52
|
+
|
|
53
|
+
- FR-1.1 必须在**所有下游监听器放弃重试之后**才接管,避免与 provider 自有重试策略形成双重重试链。
|
|
54
|
+
- FR-1.2 必须区分"客户端原因"与"服务端原因",仅修复前者。
|
|
55
|
+
- FR-1.3 客户端原因必须覆盖下列形态,并归入对应的处置策略:
|
|
56
|
+
|
|
57
|
+
| 形态 | 归类 | 处置 |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| 传输中断、流式断连、socket 重置 | `transport` | 退避重试 |
|
|
60
|
+
| SSE 长连接超时、事件流被截断 | `stream` | 退避重试 |
|
|
61
|
+
| 超时 | `timeout` | 退避重试 |
|
|
62
|
+
| 空响应 | `empty` | 退避重试 |
|
|
63
|
+
| 上下文累加超限、裁剪策略失效 | `overflow` | 先压缩再重试 |
|
|
64
|
+
| 压缩后仍超限、压缩自身打转 | `compaction-loop` | 先修历史,再重试 |
|
|
65
|
+
| 会话历史损坏、规则文件解析失败、配置 JSON 解析失败 | `history` | 先修历史,再重试 |
|
|
66
|
+
| Wire API 版本不匹配 | `protocol` | 有限重试 |
|
|
67
|
+
| 角色字段不被接受、客户端追加独有参数、tool_call 格式不符 | `protocol` | 有限重试 |
|
|
68
|
+
| 模型 ID 写死、模型能力判断错、模型列表缓存过期 | `model` | 先刷新模型目录,再重试 |
|
|
69
|
+
| Header 构造错误、OAuth 会话过期、本地缓存 token 失效、不自动刷新 | `auth` | 先刷新凭据,再重试 |
|
|
70
|
+
| 多 Key 轮换挑到失效项、Provider 侧缓存了旧凭据 | `auth` | 先刷新凭据,再重试 |
|
|
71
|
+
| MCP 子服务独立鉴权失败 | `auth` | 先刷新凭据,再重试 |
|
|
72
|
+
| MCP 会话关闭、握手失败、子服务未起 | `mcp` | 退避重试 |
|
|
73
|
+
| `PI_AI_ERROR` | `provider-error` | 退避重试 |
|
|
74
|
+
| 端口占用、句柄耗尽、内存不足、子进程崩溃、本地中转服务崩溃 | `local-env` | 退避重试 |
|
|
75
|
+
| 权限不足、路径不存在、磁盘写满、沙箱拦截 | `local-blocked` | **不重试** |
|
|
76
|
+
| TLS 证书校验失败、自签名、证书过期 | `tls` | **不重试** |
|
|
77
|
+
| 配额耗尽、欠费、限流、内容审核、图片超限 | `quota` / `policy` / `image` | **不处置** |
|
|
78
|
+
|
|
79
|
+
- FR-1.4 服务端原因不得处置,包括:配额耗尽、账号欠费、限流、上游 5xx、内容审核拦截、图片超限。
|
|
80
|
+
- FR-1.5 重试必须采用有界指数退避,带抖动,且受单步预算约束。
|
|
81
|
+
- FR-1.6 上下文超限时,必须先尝试压缩与工具结果裁剪,再重试。
|
|
82
|
+
- FR-1.7 适配器缺失时,必须先触发版本修复,修复无效则不重试。
|
|
83
|
+
- FR-1.7a 凭据失效**不得**走版本修复:重装插件不会让过期 token 变新。应先请求本机凭据层刷新,再重试。
|
|
84
|
+
- FR-1.8 重试过程必须以既有的 `llm/retry` / `llm/retry-started` 会话事件记录,**不得新增会话事件类型**。
|
|
85
|
+
- FR-1.9 同一处失败(按轮次、步骤、provider、类别定位)同时只允许一个重试在飞,避免并发重试叠加。
|
|
86
|
+
- FR-1.10 压缩或修历史连续两轮未见进展时,必须停止重试并留下记录,不得无限循环。
|
|
87
|
+
- FR-1.11 鉴权类失败的重试额度必须独立于普通重试额度,取值更小。
|
|
88
|
+
- FR-1.12 不可重试类(证书、权限、路径、磁盘)必须直接放弃,不得进入退避。
|
|
89
|
+
|
|
90
|
+
#### FR-2 工具调用自动修复
|
|
91
|
+
|
|
92
|
+
当 `tools/execute` 事件触发且结果为瞬时失败时:
|
|
93
|
+
|
|
94
|
+
- FR-2.1 仅允许重放**只读类**工具,白名单可配置。
|
|
95
|
+
- FR-2.2 有副作用的工具(命令执行、写文件、发送消息等)一律不得重放。
|
|
96
|
+
- FR-2.3 重放次数受 `maxToolRetries` 约束。
|
|
97
|
+
- FR-2.4 同名同参的调用连续出现次数受 `maxRepeatedToolCalls` 约束;超过后**不得再重放**,避免把宿主侧失效的循环检测放大成重放风暴。
|
|
98
|
+
|
|
99
|
+
#### FR-3 浏览器载体自动修复
|
|
100
|
+
|
|
101
|
+
当 `connection/request` 事件在响应头发出前失败,且属于瞬时传输错误时:
|
|
102
|
+
|
|
103
|
+
- FR-3.1 必须替换为 `503` 并附 `retry-after`,使前端自身恢复逻辑可以重发。
|
|
104
|
+
- FR-3.2 响应头已发出或响应体已结束时,必须原样抛出原错误,不得写出残缺响应。
|
|
105
|
+
|
|
106
|
+
#### FR-4 版本不匹配自动修复
|
|
107
|
+
|
|
108
|
+
- FR-4.1 必须发现**全部**不兼容插件,而非只处理首个。
|
|
109
|
+
- FR-4.2 发现来源必须覆盖:Loader 拒绝的包、已启用但未被拒绝的包、已禁用但已安装的包、安装时被延迟处理的包。
|
|
110
|
+
- FR-4.3 修复前必须审计:该插件所导入的每个 `@deepseek-ai/*` 符号在现行运行时中是否仍然存在。
|
|
111
|
+
- FR-4.4 审计通过者才可修复;审计未通过者只报告,不修改。
|
|
112
|
+
- FR-4.5 修复方式为**兼容环境**:在插件目录内建立嵌套 `node_modules`,其中每个被重定位的包是一个声明旧版本号的垫片,转发到现行实现。
|
|
113
|
+
- FR-4.6 修复必须同时对齐插件自身 manifest 中的 DSH 依赖声明,因为 Loader 在组合前读取该文件。
|
|
114
|
+
- FR-4.7 **不得绕过版本检查**:修复后的声明必须真实满足当前运行时。
|
|
115
|
+
- FR-4.8 现行运行时不存在的符号**不得伪造**。必须为插件安装它期望的那份真实旧版本;旧版本也没有时如实报缺。
|
|
116
|
+
- FR-4.13 兼容环境装入真实旧版本后重载失败、且失败原因是缺少导出时,必须判定为「需要重启」,向用户给出明确提示,**不得**记为环境失败。
|
|
117
|
+
- FR-4.12 **不得使用占位实现**。缺符号只有两种出路:装真实旧版本,或如实拒绝。
|
|
118
|
+
- FR-4.9 环境建成后必须**逐条核对**插件所需符号是否真能提供;核对不通过不得计为已修复。
|
|
119
|
+
- FR-4.10 核对无法进行时(目标文件不可读)不得判为失败,应单独记录为未核对。
|
|
120
|
+
- FR-4.11 扫描须识别「垫片存在但内容不对」的情况并重新适配。
|
|
121
|
+
|
|
122
|
+
#### FR-5 不兼容插件安装支持
|
|
123
|
+
|
|
124
|
+
- FR-5.1 必须提供在安装前预置兼容环境的手段,使管理器的版本检查按其自身规则通过。
|
|
125
|
+
- FR-5.2 安装失败或被取消时,必须完整回滚到候选目录原状。
|
|
126
|
+
- FR-5.3 仅支持本地目录;注册表包字节尚未落盘,必须明确报告为不支持,不得静默失败。
|
|
127
|
+
|
|
128
|
+
#### FR-6 状态与诊断
|
|
129
|
+
|
|
130
|
+
- FR-6.1 必须提供 `/doctor` 命令,输出各类修复计数与最近事件。
|
|
131
|
+
- FR-6.2 `/doctor install <路径>` 执行安装前预置。
|
|
132
|
+
- FR-6.3 诊断快照可选写入文件,写入不得阻塞事件循环。
|
|
133
|
+
|
|
134
|
+
### 3.3 性能需求
|
|
135
|
+
|
|
136
|
+
- PR-1 稳态下后台扫描的 CPU 与 I/O 开销必须可忽略,采用退避策略。
|
|
137
|
+
- PR-2 已适配插件必须从后续扫描的审计集合中自然退出,不得反复审计。
|
|
138
|
+
- PR-3 任何长期存活的数据结构必须有界。
|
|
139
|
+
- PR-4 定时器必须不阻止进程退出。
|
|
140
|
+
|
|
141
|
+
### 3.4 设计约束
|
|
142
|
+
|
|
143
|
+
- DR-1 不得依赖 `node_modules` 之外的第三方包;插件须在无额外依赖的 profile 中运行。
|
|
144
|
+
- DR-2 不得调用 `setBundleEnabled` / `removeBundle` 操作本插件扫描范围之外的插件。
|
|
145
|
+
- DR-3 路由冲突只报告,不处置——归属用户决定。
|
|
146
|
+
- DR-4 不得新增会话事件类型,避免未知事件被读取方拒绝。
|
|
147
|
+
- DR-5 源码质量须满足 GB/T 45717-2025 度量限值,见《软件质量度量报告》。
|
|
148
|
+
|
|
149
|
+
### 3.5 属性
|
|
150
|
+
|
|
151
|
+
- AR-1 平台:Windows / macOS / Linux,Node.js ≥ 20。
|
|
152
|
+
- AR-2 运行时:DSH 0.2.x。
|
|
153
|
+
- AR-3 并发:同一 profile 内单实例;修复过程以互斥标志防止重入。
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 4 合格性规定
|
|
158
|
+
|
|
159
|
+
| 需求 | 验证方法 | 证据 |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| FR-1.1 ~ FR-1.8 | 自动化测试 | `test/plugin.test.mjs`、`test/classify.test.mjs` |
|
|
162
|
+
| FR-2.1 ~ FR-2.3 | 自动化测试 | `test/plugin.test.mjs` |
|
|
163
|
+
| FR-3.1 ~ FR-3.2 | 自动化测试 | `test/plugin.test.mjs` |
|
|
164
|
+
| FR-4.1 ~ FR-4.8 | 自动化测试 + 实机验证 | `test/compat*.test.mjs`、`RELEASE_CHECKLIST.md` |
|
|
165
|
+
| FR-4.9 ~ FR-4.11 | 自动化测试 | `test/env-verify.test.mjs`、`test/verify-roundtrip.test.mjs` |
|
|
166
|
+
| FR-4.12 不用占位 | 自动化测试 | `test/compat-env.test.mjs`(装真实旧版本 / 旧版本也没有则拒绝)、`test/dry-run.test.mjs` |
|
|
167
|
+
| FR-5.1 ~ FR-5.3 | 自动化测试 + 实机验证 | `test/install-support.test.mjs` |
|
|
168
|
+
| FR-6.1 ~ FR-6.3 | 代码审查 + 实机验证 | `index.js`、`lib/diagnostics.js` |
|
|
169
|
+
| PR-1 ~ PR-4 | 代码审查 + 度量 | `lib/stat-cache.js`、`lib/timers.js`、《软件质量度量报告》 |
|
|
170
|
+
| DR-1 ~ DR-5 | 代码审查 | 依赖清单、事件清单 |
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 5 安全与隐私
|
|
175
|
+
|
|
176
|
+
- SR-1 插件不读取、不传输任何凭据内容;仅判断凭据相关错误码。
|
|
177
|
+
- SR-2 插件不外发任何数据;全部处理在本地进程内完成。
|
|
178
|
+
- SR-3 修改插件目录前必须备份原 manifest,备份文件名为 `package.json.request-doctor-original`。
|
|
179
|
+
- SR-4 删除兼容环境时只删除本插件创建的包,不得删除插件自带的依赖。
|
|
180
|
+
- SR-5 诊断快照默认不写文件;启用后内容限于计数、事件类别与配置项,不含请求体。
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 6 注释
|
|
185
|
+
|
|
186
|
+
- 6.1 术语
|
|
187
|
+
- **兼容环境**:插件目录内嵌套的 `node_modules`,含声明旧版本号的垫片包。
|
|
188
|
+
- **垫片(shim)**:只做 `export *` 转发的最小包。
|
|
189
|
+
- **审计**:判定插件所需符号在现行运行时是否存在的过程。
|
|
190
|
+
- **退避**:重试间隔按指数增长并加抖动。
|
|
191
|
+
- 6.2 待定事项
|
|
192
|
+
- 安装前预置目前需显式调用 `/doctor install`;拦截 `install_bundle` 工具调用可进一步自动化,但会改变既有工具行为,需单独确认。
|