@yanglaofish/dsh-proxy-pro 1.0.0
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 +153 -0
- package/cordis.patch.yml +14 -0
- package/docs/ACCEPTANCE.md +61 -0
- package/docs/LESSONS.md +551 -0
- package/docs/SPEC.md +381 -0
- package/lib/client.js +457 -0
- package/lib/index.js +693 -0
- package/lib/proxy-core.js +268 -0
- package/lib/tool-schemas.js +103 -0
- package/package.json +62 -0
package/docs/SPEC.md
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
# dsh-proxy-pro 设计规格(DESIGN SPEC)
|
|
2
|
+
|
|
3
|
+
> 版本:0.1(2026-09-16,2026-09-17 修订部署形态为发布/软件包制)
|
|
4
|
+
> 状态:v0.1 代码完成(2026-09-17),待安装验收 A1-A10
|
|
5
|
+
> 读者:接手开发的模式(拥有更高权限与技能)。请把本文与 `docs/LESSONS.md`
|
|
6
|
+
> 一起读:LESSONS 记录**已验证的事实与踩坑教训**(不可违背),本文记录
|
|
7
|
+
> **要做什么、怎么验收**。两者冲突时以 LESSONS.md 为准。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. 背景与目标
|
|
12
|
+
|
|
13
|
+
### 1.1 背景
|
|
14
|
+
|
|
15
|
+
用户环境的 `web_fetch` 曾全线失败,根因链路见 LESSONS §2-§3。修复方案(给已装
|
|
16
|
+
的 `dsh-plugin-proxy` 打补丁调用 `installProxyFromEnvironment`)已验证有效,但
|
|
17
|
+
原插件存在体验问题:
|
|
18
|
+
|
|
19
|
+
- 前端藏在 Settings → Plugins 深处(旧式 `settings.plugin.item` 卡片)+侧边栏
|
|
20
|
+
底部小开关,状态全靠推算,看不到运行时事实。
|
|
21
|
+
- 代码在 `node_modules` 里(pnpm HardLink),改完一次 `pnpm install` 就被洗掉,
|
|
22
|
+
升级也覆盖 —— 用户明确不满。
|
|
23
|
+
|
|
24
|
+
### 1.2 目标(用户原话提炼)
|
|
25
|
+
|
|
26
|
+
1. 做一个我们自己的代理插件,参考 `LucienLL/dsh-plugin-proxy` 的机制但重写体验。
|
|
27
|
+
2. **设置里独立一页「代理管理」**(用户已确认,推荐项)。
|
|
28
|
+
3. **对话页面上一个可点击开关的代理按钮**(用户已确认;"如果能拖动更好"——
|
|
29
|
+
见 §5.3 的处理决定)。
|
|
30
|
+
4. 修复并保持:web_fetch 也走代理(这是本插件的第一价值)。
|
|
31
|
+
5. 无缝过渡:**不破坏现在已配通的路由**,回滚容易,配置零迁移。
|
|
32
|
+
|
|
33
|
+
### 1.3 非目标(明确不做)
|
|
34
|
+
|
|
35
|
+
- ✗ NTLM keepalive 常驻机制(用户已同意:不常见,只做可选诊断,见 §4.5)。
|
|
36
|
+
- ✗ 多网站 keepalive(代理是单点,无意义)。
|
|
37
|
+
- ✗ 拖拽式浮动按钮(平台 slots 不支持;见 §5.3 决策)。
|
|
38
|
+
- ✗ PAC 文件解析、代理测速排行、多代理切换 UI 等超需求功能。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 2. 需求清单
|
|
43
|
+
|
|
44
|
+
### 2.1 功能需求
|
|
45
|
+
|
|
46
|
+
| ID | 需求 | 验收要点 |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| FR-1 | Settings 独立页「代理管理」 | 从 Settings 侧栏可直达;显示运行时状态 + 可编辑配置 |
|
|
49
|
+
| FR-2 | 对话头部代理按钮 | 每会话头部右侧可见;一个点击完成开关;显示当前状态(ON/OFF) |
|
|
50
|
+
| FR-3 | 状态实时可见 | 头部按钮/设置页展示**运行时事实**(active/url/noProxy/reason),非设置推算 |
|
|
51
|
+
| FR-4 | 工具:proxy_status | 同现有语义,读实时快照 |
|
|
52
|
+
| FR-5 | 工具:proxy_set | 开关 + 持久化 + 立即生效(三通道:env/dispatcher/policy) |
|
|
53
|
+
| FR-6 | 工具:proxy_test | 单 URL 路由诊断:PROXIED/DIRECT 判定 + 实际连通探测 + NTLM 提示 |
|
|
54
|
+
| FR-6b | 工具:proxy_config | 读/改配置(enabled / mode / customUrl / noProxy);参数全可选,无参 = 只读;写入持久化并立即生效 |
|
|
55
|
+
| FR-7 | web_fetch 修复保持 | installProxyFromEnvironment 纳入 sync 主流程 |
|
|
56
|
+
| FR-8 | 系统代理跟随 | mode=system 时周期读注册表,变更才重应用 |
|
|
57
|
+
| FR-9 | 干净卸载 | teardown 恢复 env/dispatcher/policy,无人为残留 |
|
|
58
|
+
|
|
59
|
+
### 2.2 非功能需求
|
|
60
|
+
|
|
61
|
+
| ID | 需求 | 约束 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| NFR-1 | 配置零迁移 | 复用 `proxy` settings namespace,settings.yaml 原样生效 |
|
|
64
|
+
| NFR-2 | 独立部署 | 插件目录在 `~/.dsh/plugins/dsh-proxy-pro/`,不经 node_modules/pnpm |
|
|
65
|
+
| NFR-3 | 可回滚 | 切回原插件只需恢复 cordis.patch.yml 一行 + 保留 `.bak` |
|
|
66
|
+
| NFR-4 | 单一 dispatcher 主人 | 与 dsh-plugin-proxy 二选一启用,不同时 |
|
|
67
|
+
| NFR-5 | 无新增第三方依赖 | 纯 ESM + 现有 DSH 包;逻辑层(proxy-core.js)零依赖可单测 |
|
|
68
|
+
| NFR-6 | 双 profile 部署 | web + obsidian-web 都要装 |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 3. 总体架构
|
|
73
|
+
|
|
74
|
+
### 3.1 部署形态(2026-09-17 修订:file:// 方案作废,改为发布形态)
|
|
75
|
+
|
|
76
|
+
插件以标准 npm 插件包发布,经 `dsh plugin --profile <name> add @yanglaofish/dsh-proxy-pro`
|
|
77
|
+
装入各 profile(= 写 dependencies + 追加 `dsh.profile.bundles` + pnpm 安装)。
|
|
78
|
+
启动时 dsh-app-boot 依次读每个 bundle 包的 `dsh.bundle.patch`(cordis.patch.yml)
|
|
79
|
+
把插件行 insert 进入口树。参照 dsh-skill-manager(已发布 4.3.3,机制见
|
|
80
|
+
LESSONS §12)。
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
开发源(~/.dsh/plugins/dsh-proxy-pro/,git 仓库):
|
|
84
|
+
package.json # name/exports["/client","/cordis.patch.yml"]/dsh.client(platform:web)
|
|
85
|
+
cordis.patch.yml # bundle patch:insert 一行 name: '@yanglaofish/dsh-proxy-pro'(dsh.bundle.patch 指向)
|
|
86
|
+
lib/
|
|
87
|
+
index.js # host 半(Cordis 插件)
|
|
88
|
+
client.js # 浏览器半(__ModuleLoader__.load)
|
|
89
|
+
proxy-core.js # 纯逻辑(系统代理读取/NO_PROXY/状态解析/probe 分类)
|
|
90
|
+
test/proxy-core.test.mjs + tool-schema.test.mjs # node --test 单测(26 例)
|
|
91
|
+
docs/ README.md LICENSE
|
|
92
|
+
|
|
93
|
+
profile 内安装(pnpm nodeLinker: hoisted;本地=file: 软链,发布=registry 包):
|
|
94
|
+
"dependencies": { "@yanglaofish/dsh-proxy-pro": "file:../../plugins/dsh-proxy-pro" }
|
|
95
|
+
"dsh": { "profile": { "bundles": [ "...", "@yanglaofish/dsh-proxy-pro" ] } }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- **host 加载**:bundle patch 以 `name: '@yanglaofish/dsh-proxy-pro'`(包名)insert → profile
|
|
99
|
+
resolver 从 profile node_modules(没有则兜底 app 安装副本)解析,无需 file://。
|
|
100
|
+
- **client 发现**:client-modules 按已装包的 `dsh.client.platform: web` +
|
|
101
|
+
`exports["./client"]` 扫描打包(skill-manager 同款)。
|
|
102
|
+
- **两个 profile 共用一份源码**:file: 软链指向同一目录,改一处两处生效。
|
|
103
|
+
|
|
104
|
+
### 3.2 运行时交互图
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
[浏览器 client.js] [host index.js (NodeService 链)]
|
|
108
|
+
│ inject settings.section │ installSettingsSection(proxy NS)
|
|
109
|
+
│ inject header.utilities │ sync(): resolve → applyEffective(env+dispatcher)
|
|
110
|
+
│ fetch /dsh-proxy-pro/api/* ◄──────┤ → installProxyFromEnvironment(policy)
|
|
111
|
+
│ │ ↓ 让 web_fetch 的 proxyRouteFor 判 PROXIED
|
|
112
|
+
│ │ tools: proxy_status/set/test
|
|
113
|
+
│ │ systemPrompt.section(proxy:status)
|
|
114
|
+
└─ settingsScope (bind proxy NS) ──► │ (用户改设置 → onChange → requestSync)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3.3 状态模型
|
|
118
|
+
|
|
119
|
+
`snapshot = { config, effective, systemProxy, at }`;对外统一用
|
|
120
|
+
`summarize(snapshot)`(proxy-core.js):
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{ "active": true, "enabled": true, "mode": "system",
|
|
124
|
+
"url": "http://proxyhk.huawei.com:8080", "noProxy": "localhost,127.0.0.1,::1,*.huawei.com",
|
|
125
|
+
"reason": "system", "source": "system", "at": "2026-09-16T…" }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 4. Host 详细设计(lib/index.js)
|
|
131
|
+
|
|
132
|
+
### 4.1 生命周期
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
const name = 'dsh-proxy-pro'
|
|
136
|
+
const inject = ['tools', 'systemPrompt', 'settings'] // webServer 改可选(ctx.get)
|
|
137
|
+
function apply(ctx, config) { … } // 导出 { Config, apply, inject, name }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **没有 savedEnv / EnvHttpProxyAgent / 手写 dispatcher**:传输完全委托
|
|
141
|
+
`installProxyFromEnvironment`(env + 全局 dispatcher + 模块级策略一体,
|
|
142
|
+
LESSONS §12.4:单一通道,杜绝 undici 错版)。
|
|
143
|
+
- `installSettingsSection(ctx, PROXY_NS, Config, config, { setSource, onChange })`,
|
|
144
|
+
onChange → `requestSync()`(合并去重,`syncing` 单飞)。
|
|
145
|
+
- teardown(`ctx.effect`):清 pollTimer → `await httpProxyPolicy?.()`(卸载=恢复
|
|
146
|
+
env/dispatcher/策略)。
|
|
147
|
+
|
|
148
|
+
### 4.2 sync() 主流程(顺序不可乱)
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
0. haveApplied 守卫:从未启用过且当前不 active → 只记快照,不碰传输层
|
|
152
|
+
(与旧 dsh-plugin-proxy 并存启动时互不抢 dispatcher,NFR-4)
|
|
153
|
+
1. resolved = source()
|
|
154
|
+
2. if mode=system && enabled: systemProxy = await systemProxyReader()(reg.exe)
|
|
155
|
+
3. effective = resolveProxyState(resolved, systemProxy)
|
|
156
|
+
4. active 判定:active 时 haveApplied = true
|
|
157
|
+
5. ★ 唯一传输通道:await httpProxyPolicy?.()(先卸载旧 policy)
|
|
158
|
+
→ httpProxyPolicy = await installProxyFromEnvironment(makeEnvLike(effective), log)
|
|
159
|
+
6. snapshot = { ... }; ctx.emit('dsh-proxy-pro/status', summarize(snapshot))
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`makeEnvLike(effective)`:active 时输出 8 个名字(HTTP_PROXY/HTTPS_PROXY/ALL_PROXY/
|
|
163
|
+
NO_PROXY 大小写双写)的 `{ value }` 快照,inactive 时全空;必须带 `.get()`,
|
|
164
|
+
且**重新 install 前先 await 旧 policy**(LESSONS §4)。OFF 分支由
|
|
165
|
+
installProxyFromEnvironment 的 none-branch 完成:恢复首次安装前的 env + 装直连 Agent。
|
|
166
|
+
|
|
167
|
+
### 4.3 为什么没有 applyEffective(2026-09-17 重构)
|
|
168
|
+
|
|
169
|
+
- 旧设计先 `applyEffective`(EnvHttpProxyAgent + 手写 env + savedEnv),随后
|
|
170
|
+
`installProxyFromEnvironment` 又覆盖 dispatcher:后者永远最后赢,前者白建白关,
|
|
171
|
+
还引入两个 undici 实例(profile 7.29.0 vs app 8.10.0)的错版风险(LESSONS §3)。
|
|
172
|
+
- dsh-http-proxy 的 `applyPolicyEnv` 本来就写 env(含 ALL_PROXY 兜底 + loopback
|
|
173
|
+
合并),`installGlobalProxy` 装 per-origin Agent dispatcher——三件事一次调用完成。
|
|
174
|
+
- 结论:**删除 undici 依赖 + EnvHttpProxyAgent + savedEnv + applyEffective**,
|
|
175
|
+
插件对 undici 零依赖,UND_ERR_INVALID_ARG 这类问题在源头上不存在。
|
|
176
|
+
|
|
177
|
+
### 4.4 系统代理跟随
|
|
178
|
+
|
|
179
|
+
- `makeSystemProxyReader(execFileAsync)`(proxy-core.js)——reg.exe 三查
|
|
180
|
+
ProxyEnable / ProxyServer / ProxyOverride(HKCU Internet Settings)。
|
|
181
|
+
- `systemPollMs`(默认 30000)轮询;`systemFactsEqual` 判变,没变不重应用。
|
|
182
|
+
|
|
183
|
+
### 4.5 NTLM 可选诊断(非核心)
|
|
184
|
+
|
|
185
|
+
- `keepaliveHint(message)`:响应文本命中 `407|NTLM|proxy authentication` 时返回
|
|
186
|
+
一句提示("先 curl 通一次建 NAT 窗口"),否则返回空串。
|
|
187
|
+
- 只在 `proxy_test` / `/api/route` 中使用,不进 sync 主流程,无常驻。
|
|
188
|
+
|
|
189
|
+
### 4.6 工具契约(与 dsh-plugin-proxy 相同语义 + 新增)
|
|
190
|
+
|
|
191
|
+
| 工具 | 参数 | 返回(render 摘要) |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| proxy_status | – | summarize(snapshot);active 显示 ON — url (system/custom) |
|
|
194
|
+
| proxy_set | enabled: bool | 更新 settings(PROXY_NS) → requestSync → summarize |
|
|
195
|
+
| proxy_test | url: string | { url, route: PROXIED url/DIRECT, bypassed: bool, probe: {ok,status/short}, hint? } |
|
|
196
|
+
| proxy_config | enabled?/mode?/customUrl?/noProxy?(全可选) | 无参 = 读;有参 = settings.update(PROXY_NS, patch) → requestSync → summarize。customUrl 经 normalizeProxyUrl 校验(裸 host:port 自动补 http://),非法即抛错;空串 = 清除该字段 |
|
|
197
|
+
|
|
198
|
+
`proxy_test` 内部:`proxyRouteFor(parsed)` 判路由名(try/catch,不可用时回退快照
|
|
199
|
+
推导)→ NO_PROXY 列表命中判断 → `fetch(url, {method:'HEAD', redirect:'manual'})`
|
|
200
|
+
实测 6s 超时(AbortController)→ `classifyTargetFailure` 归类
|
|
201
|
+
(EACCES/ENETUNREACH/EHOSTUNREACH → "needs a proxy route";407/NTLM → auth)。
|
|
202
|
+
|
|
203
|
+
### 4.7 系统提示段落
|
|
204
|
+
|
|
205
|
+
`ctx.systemPrompt.section({ name: 'dsh-proxy-pro:status', order: 55, text: () => renderStatusText(snapshot) })`
|
|
206
|
+
——文本与 dsh-plugin-proxy 相同结构(模型必须知道代理是否在力)。
|
|
207
|
+
|
|
208
|
+
### 4.8 状态 HTTP API(客户端轮询用)
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
GET /dsh-proxy-pro/api/status → { ok, status: summarize }
|
|
212
|
+
GET /dsh-proxy-pro/api/route?url=… → { ok, route, bypassed, probe, hint }
|
|
213
|
+
POST /dsh-proxy-pro/api/toggle {enabled} → 更新 settings + sync → { ok, status }
|
|
214
|
+
404 其它 / 500 内部错
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**安全铁律**:handler 首行 `denyIfUntrusted(req, res)`(browser-trust fence:
|
|
218
|
+
Host 必须 loopback;`sec-fetch-site: cross-site` 拒绝;origin 异源拒绝;403)。
|
|
219
|
+
实现细节抄 skill-manager L131-162(LESSONS §7 有完整清单)。
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 5. Client 详细设计(lib/client.js)
|
|
224
|
+
|
|
225
|
+
### 5.1 骨架(铁律)
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
window.__ModuleLoader__.load({
|
|
229
|
+
id: "dsh-proxy-pro",
|
|
230
|
+
factory: (require) => {
|
|
231
|
+
const react = require("react");
|
|
232
|
+
const { useSyncExternalStore } = react; // 或 useState/useEffect 轮询
|
|
233
|
+
const inject = ["slots", "settingsScope"]; // 服务名,不是包名!
|
|
234
|
+
function apply(ctx) { … }
|
|
235
|
+
return { apply, inject };
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
⚠️ 不要引入 jsx runtime 构建依赖:用 `react.createElement`(dsh-plugin-proxy 的
|
|
241
|
+
client.js 就是这么写的,worked)。样式用注入 `<style data-plugin="dsh-proxy-pro">`
|
|
242
|
+
或内联 CSS 字符串,别引 UI 组件库。
|
|
243
|
+
|
|
244
|
+
### 5.2 设置独立页(FR-1)
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
248
|
+
name: "settings.section",
|
|
249
|
+
id: "dsh-proxy-pro",
|
|
250
|
+
order: 20, // 排在「技能管理」(30) 之前
|
|
251
|
+
label: "代理管理",
|
|
252
|
+
}, ProxyManagerPanel));
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`ProxyManagerPanel` 结构(自上而下):
|
|
256
|
+
|
|
257
|
+
| 区块 | 内容 |
|
|
258
|
+
|---|---|
|
|
259
|
+
| 状态卡 | 大开关(enabled)+ 运行时事实:active ✓/✗、url、mode 标签、reason(红色显示 OFF 原因)、source、最近同步 at |
|
|
260
|
+
| 模式 | radio:system(Windows 系统代理)/ custom(自定义地址)/ none(禁用) |
|
|
261
|
+
| custom 地址 | input(mode=custom 时启用)placeholder `http://127.0.0.1:7890` |
|
|
262
|
+
| NO_PROXY | input(blur 提交)placeholder `localhost,127.0.0.1,::1`,说明行 |
|
|
263
|
+
| 诊断 | 输入 URL + 「测试路由」按钮 → 显示 route / bypassed / probe / hint |
|
|
264
|
+
| 状态轮询 | 每 3~5s `fetch('/dsh-proxy-pro/api/status')` 刷新状态卡(不刷新表单编辑值) |
|
|
265
|
+
|
|
266
|
+
- 表单写回:`ctx.settingsScope.bind({ namespace: "proxy" })` →
|
|
267
|
+
`scope.set(field, value)`(与原插件同法);写回后状态卡等轮询自然刷新。
|
|
268
|
+
- 可复用 dsh-plugin-proxy client.js 的 `useProxySnapshot`(useSyncExternalStore +
|
|
269
|
+
scope.subscribe/getSnapshot)。
|
|
270
|
+
|
|
271
|
+
### 5.3 对话头部按钮(FR-2/FR-3)
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
ctx.slots.inject("conversation.session.header", () => ctx.slots.register({
|
|
275
|
+
name: "conversation.session.header.utilities", // list, scope session
|
|
276
|
+
id: "dsh-proxy-pro",
|
|
277
|
+
order: 10,
|
|
278
|
+
label: "代理",
|
|
279
|
+
}, ProxyHeaderButton));
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**关于拖动(用户问过)**:`conversation.session.header.*` 是 slots 布局注入点,
|
|
283
|
+
不提供拖拽 API。**决定**:做成「状态胶囊按钮」——圆形状态点(绿=ON 红=OFF)+
|
|
284
|
+
标签「代理 · ON/system」;点击切换 enabled(调 `/api/toggle` 或
|
|
285
|
+
`scope.set("enabled", !v)`);title 提示完整 url/status。若要"拖",留作
|
|
286
|
+
README 的延伸议题(可评估在 composer 区域做实验性悬浮,但不承诺进 v0.1)。
|
|
287
|
+
|
|
288
|
+
### 5.4 状态来源约定
|
|
289
|
+
|
|
290
|
+
- 按钮与状态卡:**优先用 `/api/status` 轮询的运行时事实**,表单值只是配置编辑层。
|
|
291
|
+
- 轮询节流:同一页面两处(设置页/按钮)共用一次 poll 或按钮用长轮询+事件,
|
|
292
|
+
v0.1 允许各自 3~5s 轮询(DSH 是本地回环,开销可忽略)。
|
|
293
|
+
- 写路径优先走 settingsScope(声明式、可持久化),`/api/toggle` 仅在需要立即
|
|
294
|
+
拿到最新全文快照时用(二选一即可,别双写)。
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 6. 设置 Schema 与过渡
|
|
299
|
+
|
|
300
|
+
### 6.1 Config(= settings.yaml proxy 段,与现有一致)
|
|
301
|
+
|
|
302
|
+
```yaml
|
|
303
|
+
enabled: true
|
|
304
|
+
mode: system # system | custom | none
|
|
305
|
+
customUrl: "http://127.0.0.1:7890"
|
|
306
|
+
noProxy: "localhost,127.0.0.1,::1"
|
|
307
|
+
systemPollMs: 30000
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
- schema(schemastery):enabled bool / mode union / customUrl string /
|
|
311
|
+
noProxy string / systemPollMs number min 0。
|
|
312
|
+
|
|
313
|
+
### 6.2 过渡步骤(NFR-3,2026-09-17 改为 bundle 形态)
|
|
314
|
+
|
|
315
|
+
1. 本地装入 web profile:
|
|
316
|
+
`dsh plugin --profile web add file:../../plugins/dsh-proxy-pro`
|
|
317
|
+
(发布后即 `add @yanglaofish/dsh-proxy-pro`;等价包级操作见 §3.1)。
|
|
318
|
+
2. profile 的 cordis.patch.yml **同时**把原 `dsh-plugin-proxy` 行改为
|
|
319
|
+
`- id: proxy, disabled: true`(bundle 层 insert 的行可按 id patch),
|
|
320
|
+
**不要动 settings.yaml 任何内容**。
|
|
321
|
+
3. 重启 DSH → `proxy_status` 确认 active=true、url=proxyhk → 手动 web_fetch
|
|
322
|
+
一个被墙 URL(如 github)确认 200。
|
|
323
|
+
4. 验证 proxy_test github=PROXIED / deepseek=DIRECT。
|
|
324
|
+
5. 再对 obsidian-web profile 做同样操作,重复验证。
|
|
325
|
+
6. 回滚 = 恢复原插件行 enabled(settings.yaml 没动过,天然安全),或
|
|
326
|
+
`dsh plugin --profile web remove @yanglaofish/dsh-proxy-pro`。
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 7. 验收清单(开发完成逐项打勾)
|
|
331
|
+
|
|
332
|
+
- [ ] A1 设置页出现「代理管理」,显示 status 真实事实(active/url/noProxy/reason)
|
|
333
|
+
- [ ] A2 对话头部有代理按钮,绿点/红点随开关联动,点击切换生效且持久(重启仍在)
|
|
334
|
+
- [ ] A3 `proxy_status` 返回与 UI 一致
|
|
335
|
+
- [ ] A4 `proxy_set enabled=false` 后 web_fetch 到 github 报 EACCES;true 后 200
|
|
336
|
+
- [ ] A5 `proxy_test https://github.com` → route=PROXIED proxyhk…
|
|
337
|
+
- [ ] A6 `proxy_test https://www.deepseek.com` → route=DIRECT(NO_PROXY 命中)
|
|
338
|
+
- [ ] A7 mode=none 时三通道全关,process.env 无代理变量,web_fetch 直连
|
|
339
|
+
- [ ] A8 修改 customUrl(如 127.0.0.1:9999 无效端口)→ status 显示 OFF +
|
|
340
|
+
reason=invalid-custom-url
|
|
341
|
+
- [ ] A9 teardown(禁用插件/退出)后 process.env 恢复原状、dispatcher 恢复默认
|
|
342
|
+
- [ ] A10 两 profile 均通过 A1-A9
|
|
343
|
+
- [ ] A11 拖拽 → 明确降级为胶囊按钮(无拖拽,符合 spec 决定)
|
|
344
|
+
- [ ] A12 `proxy_config` 无参返回当前配置;写入 mode/customUrl/noProxy 后 status 与
|
|
345
|
+
settings.yaml 同步且立即生效;非法地址(如 `"not a url"`)抛清晰错误而非静默写入
|
|
346
|
+
|
|
347
|
+
**观察通道约定**(2026-09-17 补充,重启后逐项按通道确认):
|
|
348
|
+
- **GUI**:设置页「代理管理」(A1/A8)、对话头部胶囊(A2/A11)——唯一需要
|
|
349
|
+
人工肉眼确认的通道;本地 HTTP 探测会被 app 安全门 403,浏览器内不影响。
|
|
350
|
+
- **会话内工具**:`proxy_status` / `proxy_set` / `proxy_test` / `proxy_config`(A3/A5/A6/A7/A12)。
|
|
351
|
+
- **实测网页请求**:web_fetch 被墙域(A4/A7 的 200/失败行为)。
|
|
352
|
+
- **进程事实**:Get-Process 启动时间 / env(A9 卸载后恢复)。
|
|
353
|
+
|
|
354
|
+
_运行时快照(2026-09-17,⚠️ 尚未重启):`/dsh-proxy-pro/api/status` 与
|
|
355
|
+
不存在路径均返回 403 `forbidden`——这是 DSH 对未认证 HTTP 请求的统一安全门,
|
|
356
|
+
**不能作为插件挂载证据**。插件未加载前路由不注册,待用户重启后按上表验证。_
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 8. 已知风险与开放问题
|
|
361
|
+
|
|
362
|
+
1. `proxyRouteFor` 不可用(旧版 dsh-http-proxy / 组合里未安装)→ proxy_test 的
|
|
363
|
+
route 降级为快照推导,工具仍可用(try/catch 已兜底)。
|
|
364
|
+
2. 两个 profile 同时起两个插件实例 → 各自进程独立,无冲突;同 profile 内与原
|
|
365
|
+
插件二选一(NFR-4)。
|
|
366
|
+
3. settings 写回与 `/api/toggle` 双通道可能竞态 → 选一主一辅(§5.4)。
|
|
367
|
+
4. 无 jsx 编译 → client 代码保持 createElement 风格,别引非打包可用依赖。
|
|
368
|
+
5. NTLM 环境首次启用后若 407,提示文案足够(keepaliveHint),不自动重试。
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 9. 开发状态(2026-09-17 更新:v0.1 代码完成并提交)
|
|
373
|
+
|
|
374
|
+
1. ✅ `lib/proxy-core.js`(16 例单测全过)
|
|
375
|
+
2. ✅ `lib/index.js`(host:settings → sync(单一通道)→ 工具 → fence API → poll → teardown)
|
|
376
|
+
3. ✅ `lib/client.js`(设置页 → 头部按钮 → 轮询/诊断)
|
|
377
|
+
4. ✅ `cordis.patch.yml` / `package.json`(发布形态)+ git init(commit 04eb709)
|
|
378
|
+
5. ✅ 已装入两 profile + `--dump-config` 预检通过(两个组合树都含 dsh-proxy-pro 行);
|
|
379
|
+
⏳ 待用户重启(当前**尚未重启**,403 为应用安全门、非插件响应)
|
|
380
|
+
6. ⏳ GUI 确认 A1/A2(头部胶囊 + 设置页「代理管理」)→ 运行时验收 A3-A9
|
|
381
|
+
→ obsidian-web A10 → 发布收尾
|