@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-proxy-pro 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 ADDED
@@ -0,0 +1,153 @@
1
+ # dsh-proxy-pro
2
+
3
+ 一个 DeepSeek Harness(DSH)插件的**代理管理器**:把整个运行时(模型请求、web_search / web_fetch、spawned 工具)统一路由到 **Windows 系统代理**或**自定义地址**,并提供随时可见、一键切换、可诊断的体验。
4
+
5
+ 区别于原 dsh-plugin-proxy 的核心差异:
6
+
7
+ - **web_fetch / web_search 真正走代理**。原插件只换 undici 全局 dispatcher + env,而 `dsh-web-fetch-http` 的 `proxyRouteFor()` 只读 `@deepseek-ai/dsh-http-proxy` 的模块级策略——本插件通过 `installProxyFromEnvironment()` 纳入这条通道,web_fetch 的网络故障(`fetch failed`)被根治。
8
+ - **对话头部一键开关**:每会话头部胶囊按钮(绿 = ON / 红 = OFF),点击即切换;「关→开」兼作手动重检——触发 host 现读注册表并重新应用,代理出问题时直接在头部拨一下即可。
9
+ - **设置页「代理配置」**:显示运行时事实(生效地址 / 原因 / 直连名单)而非设置推断;模式仅「系统代理(Windows)/ 自定义地址」两个选项;内置按 URL 的路由诊断(PROXIED / DIRECT 判定 + 强制走代理 / 强制直连双通道探测 + NTLM 提示)。
10
+ - **单一传输通道**:env + dispatcher + 策略由 dsh-http-proxy 一体管理,插件不直接触碰 undici,杜绝双实例错版(`UND_ERR_INVALID_ARG`)。
11
+ - **幂等内核**:`effKey` 相同只刷快照、绝不重装 dispatcher——读型 API 与轮询永不触发重组(修复过"每次拉都重装导致桌面卡顿"的根因);「拉时现读」让页面看到的永远是新鲜事实而无需任何定时器。
12
+ - **干净卸载**:卸除时恢复 env / dispatcher / 策略,fire-and-forget 后台排空 keep-alive。
13
+
14
+ > 全部踩坑与设计决策见 [`docs/LESSONS.md`](docs/LESSONS.md)(必读),规格与验收清单见 [`docs/SPEC.md`](docs/SPEC.md)。
15
+
16
+ ## 安装
17
+
18
+ 两种方式任选其一(npm 包已发布,拉取即用、免构建授权):
19
+
20
+ **方式 A:npm 安装(推荐)**
21
+
22
+ ```sh
23
+ dsh plugin --profile web add @yanglaofish/dsh-proxy-pro
24
+ dsh plugin --profile obsidian-web add @yanglaofish/dsh-proxy-pro
25
+ ```
26
+
27
+ **方式 B:GitHub 源安装**
28
+
29
+ ```sh
30
+ dsh plugin --profile web add github:yanglaofish/dsh-proxy-pro
31
+ ```
32
+
33
+ 安装完成后直接启动:
34
+
35
+ ```sh
36
+ dsh web
37
+ ```
38
+
39
+ ## 使用
40
+
41
+ **对话头部胶囊按钮** —— 每会话头部右侧的「代理 · ON system / OFF」:绿点 = 代理生效、红点 = 关闭;一点切换总开关。头部按钮为配置驱动(打开即绿、零轮询),「关→开」操作触发 host 现读系统注册表并重新应用——代理状态异常时,直接在头部拨一下即可重检修复。
42
+
43
+ **设置 → 代理配置** ——
44
+
45
+ - **状态行(置顶)**:生效地址 + 原因说明;右侧短小启用开关,切换立即生效。
46
+ - **代理地址来源**:仅「系统代理(Windows)」与「自定义地址」两个选项。「不使用代理」模式已从界面与 `proxy_config` 工具移除(scheme 保留 `none` 仅解析旧配置残留)。
47
+ - **自定义地址**:`http://host:port` 或裸 `host:port`。
48
+ - **直连名单(NO_PROXY)**:编辑框下方显示**当前生效名单**;`system` 模式自动并入 Windows 系统 ProxyOverride(标注「含并入」),`custom` 模式只保留你自己的名单(不并入系统白名单)。
49
+ - **路由诊断**:输入 URL,勾选「使用代理」= 强制走代理探测 / 不勾 = 强制直连探测;对比两次结果即可判断该域名该不该加进直连名单。
50
+
51
+ **模型侧** —— 系统提示实时标注代理状态;四个工具:
52
+
53
+ - `proxy_status` — 查询当前状态:是否走代理、生效地址、NO_PROXY 名单、来源。
54
+ - `proxy_set` — 一键开/关整个运行时(改配置持久化并立即生效)。
55
+ - `proxy_test` — 单 URL 路由诊断:PROXIED / DIRECT 判定、NO_PROXY 命中、连通探测;HTTP 407 归类为 NTLM 认证失败并给出 keepalive 提示。
56
+ - `proxy_config` — 读/改配置:开关、模式(system / custom)、自定义地址、NO_PROXY;可只传任意子集。
57
+
58
+ ## 配置(`settings.yaml` `proxy:` 段,零迁移)
59
+
60
+ ```yaml
61
+ proxy:
62
+ enabled: false
63
+ mode: system # system | custom(none 仅解析旧配置残留)
64
+ customUrl: http://127.0.0.1:7890
65
+ noProxy: localhost,127.0.0.1,::1
66
+ systemPollMs: 30000
67
+ ```
68
+
69
+ ## 卸载
70
+
71
+ ```sh
72
+ dsh plugin --profile web remove @yanglaofish/dsh-proxy-pro
73
+ dsh plugin --profile obsidian-web remove @yanglaofish/dsh-proxy-pro
74
+ ```
75
+
76
+ 卸载即恢复原始路由行为,不留残留。
77
+
78
+ ## 技术方案
79
+
80
+ ### 整体架构
81
+
82
+ 插件由「宿主侧」(Node,随 DSH 主进程运行)与「客户端侧」(浏览器 bundle,随 Web UI 运行)两部分组成,通过 `/dsh-proxy-pro/api/*` 自注册 HTTP 接口衔接(浏览器信任围栏保护)。宿主侧为原生 ESM(无编译步骤),客户端侧为手写 `react.createElement` 的原生 JS bundle。
83
+
84
+ ```
85
+ dsh-proxy-pro
86
+ ├── lib/
87
+ │ ├── index.js 宿主侧(原生 ESM,无需编译)
88
+ │ │ ├── apply() settings 钩子 → 幂等 sync → 4 个工具 →
89
+ │ │ │ systemPrompt 段 → webServer API → 30s poll → teardown
90
+ │ │ └── 模块级函数 probeTarget / forceChannelProbe(双通道探测)、
91
+ │ │ browser-trust fence(loopback + sec-fetch-site + origin)
92
+ │ ├── client.js 客户端 bundle(__ModuleLoader__ 包装)
93
+ │ │ ├── ProxyHeaderButton 对话头部胶囊:配置驱动、点击切换、关→开重检
94
+ │ │ └── ProxyManagerPanel 设置页:状态行+开关、模式、自定义地址、直连名单、路由诊断
95
+ │ ├── proxy-core.js 纯逻辑层(零 DSH 依赖,可独立单测)
96
+ │ └── tool-schemas.js dsh-tools 值 schema(编译前置校验)
97
+ ├── cordis.patch.yml bundle patch:挂载宿主侧插件行
98
+ ├── test/
99
+ │ ├── proxy-core.test.mjs 逻辑层单测(26 条)
100
+ │ └── tool-schema.test.mjs 工具 schema 编译前置校验(真实编译器)
101
+ ├── README.md
102
+ └── package.json bundle 清单:exports + dsh.client 声明(v1.0.0)
103
+ ```
104
+
105
+ **核心设计原则**:单一传输通道 + 幂等内核。所有路由决策经 `installProxyFromEnvironment(envLike)` 一个通道落地(写 env → 装 per-origin dispatcher → 模块级策略供 `proxyRouteFor()` 读取,web_fetch 因此也走代理);`sync()` 以 effKey 幂等——有效状态未变时只刷新快照、绝不重装 dispatcher。「拉时现读」:client 每次拉 `/status` 都触发一次幂等 requestSync(同状态零重装),页面看到的永远是新鲜事实而无需任何定时器;host 30s poll 独立承担无人值守跟随——设置页关闭时系统代理变化仍会被发现并重新应用。
106
+
107
+ ### 关键模块
108
+
109
+ | 模块 | 职责 |
110
+ | --- | --- |
111
+ | `resolveProxyState / composeNoProxy` | 由配置 +(system 模式)实时注册表事实推导生效代理;系统 ProxyOverride **仅在 system 模式**并入 NO_PROXY,custom 模式保留用户自己的名单 |
112
+ | `makeSystemProxyReader` | 三次 `reg.exe` 查询(ProxyEnable / ProxyServer / ProxyOverride)解析为事实集 |
113
+ | `sync / requestSync / effKey` | 幂等应用:写 env + dispatcher + 策略只发生在有效状态真正变化时;合并并发触发 |
114
+ | `probeTarget / forceChannelProbe` | 按策略 / 强制代理 / 强制直连三通道探测;407 归类为 NTLM 认证失败并给出 keepalive 提示 |
115
+ | `isTrustedPanelRequest` | 镜像 dsh 官方 `/api` 围栏:Host loopback(防 DNS rebinding)+ sec-fetch-site 同源 + Origin 校验 |
116
+ | `useStatusOnce / useProxySnapshot` | client 侧:配置驱动显示 + 拉时现读;快照经 settingsScope 订阅 |
117
+
118
+ ### 数据流
119
+
120
+ **拉取(/status)**:client 拉取 → host `await requestSync()`(读注册表 → effKey 比较 → 未变仅刷快照,变了才重装)→ 返回最新 summarize 快照。无定时轮询。
121
+
122
+ **切换(/toggle、头部按钮、proxy_set)**:settings 更新 → host 钩子 requestSync → 现读注册表 + 重应用 → 快照更新;幂等保证同状态重复调用零开销。
123
+
124
+ **跟随(30s poll)**:仅 system 模式且启用时读注册表,事实变化才 requestSync(`systemFactsEqual` 比较含 http/https 单边变化)。
125
+
126
+ **排查(工具链路)**:`proxy_status` / `proxy_test` / `proxy_config` / `proxy_set` 的 execute 均先 `requestSync()`——模型排查时看到的永远是最新注册表事实与生效状态。
127
+
128
+ ### 关键设计细节
129
+
130
+ - **幂等 sync(LESSONS §17)**:effKey 含 active / url / http / https / noProxy / reason——https 单边变化也会触发重装,`HTTPS_PROXY` 真正切换;读型 API 与轮询永不触发无条件重装(旧版"每次拉都重装"是桌面卡顿根因)。
131
+ - **卸载不卡**:disposer 的 `agent.close()` 排空 keep-alive 可能阻塞数秒,卸除改为 fire-and-forget,让连接池后台排空。
132
+ - **浏览器信任围栏**:面板 API 与 dsh 官方 `/api` 一致的三重防线(loopback Host / sec-fetch-site / Origin),纯头判定、零额外依赖。
133
+ - **工具 schema 前置校验**:`test/tool-schema.test.mjs` 用真实 dsh-tools 编译器在应用启动前编译全部工具 schema——boot-time 崩溃类问题不再出现(LESSONS §15)。
134
+ - **依赖取舍**:undici 零直接依赖(避免实例错版);peerDependencies 对齐运行时 `@deepseek-ai/*` 版本(解析由 DSH profile resolver 兜底到 app 副本)。
135
+
136
+ ## 开发
137
+
138
+ ```sh
139
+ # 语法检查
140
+ node --check lib/index.js lib/client.js lib/proxy-core.js
141
+
142
+ # 运行 26+ 条单测(node --test,零运行时依赖)
143
+ npm test
144
+ ```
145
+
146
+ - 纯逻辑层 `lib/proxy-core.js` 零 DSH 依赖,可独立单测。
147
+ - **GitHub 安装模式**:改代码需 `git push` 后 `pnpm update @yanglaofish/dsh-proxy-pro` 再重启 `dsh web` 生效。
148
+ - **npm 安装模式**:bump 版本 → `npm publish --registry=https://registry.npmjs.org` 后,profile 内 `pnpm add @yanglaofish/dsh-proxy-pro@最新版` 再重启 `dsh web` 生效。
149
+ - **本地开发模式**(改代码重启即生效):`dsh plugin --profile web add file:../../plugins/dsh-proxy-pro`。
150
+
151
+ ## 许可
152
+
153
+ MIT
@@ -0,0 +1,14 @@
1
+ # Bundle patch layer for dsh-proxy-pro: inserts the proxy plugin row into
2
+ # the launcher composition (host half = lib/index.js, browser half = ./client
3
+ # because the package declares dsh.client). The values below are the
4
+ # composition `base` layer — the user can override every field live from the
5
+ # settings UI (Settings → 代理管理), which then takes effect immediately.
6
+ - insert:
7
+ - id: dsh-proxy-pro
8
+ name: '@yanglaofish/dsh-proxy-pro'
9
+ config:
10
+ enabled: false
11
+ mode: system
12
+ customUrl: 'http://127.0.0.1:7890'
13
+ noProxy: 'localhost,127.0.0.1,::1'
14
+ systemPollMs: 30000
@@ -0,0 +1,61 @@
1
+ # dsh-proxy-pro v0.1 验收清单(自验版)
2
+
3
+ > 用途:重启 DSH 后逐项自验插件是否生效。每项给出「看哪里」与「预期结果」;
4
+ > 标 🔵 的项需要会话内的模型(我)代跑,标 👁 的项你自己在界面上看。
5
+ > 全部通过 = FR-1..FR-9(SPEC §7 A1-A11)达成,可以进入「接管旧插件 → 发布」。
6
+
7
+ ## 0. 前置(已就绪,无需操作)
8
+
9
+ - [x] 单测 16/16 通过、语法 OK、git 干净(head = afb96b4)
10
+ - [x] web / obsidian-web 两 profile 已装入 `dsh-proxy-pro`(node_modules 为 Junction 软链)
11
+ - [x] `dsh --dump-config` 预检:两 profile 组合树均含 `- id: dsh-proxy-pro` 行
12
+ - [x] settings.yaml 的 `proxy.enabled: true` → 重启后插件**启动即接管**系统代理(预期行为)
13
+
14
+ ## 1. 重启(唯一的前置动作)
15
+
16
+ 关闭 DSH 桌面应用再重新打开(bundle 行只在启动时编排,无法热加载)。
17
+
18
+ ## 2. 挂载确认(重启后第一步)
19
+
20
+ | # | 通道 | 看哪里 | 预期 |
21
+ |---|---|---|---|
22
+ | 👁 A1 | GUI | 设置 → 「代理管理」页存在,显示真实事实 | 状态行「代理生效中 · <系统代理地址>」,开关为开 |
23
+ | 👁 A2 | GUI | 对话头部右侧 | 「代理 · ON system」胶囊(绿点) |
24
+ | 🔵 A3 | 工具 | 让我跑 `proxy_status` | `active:true, mode:system, url:<系统代理>, source:system` |
25
+
26
+ > 本地 curl 打 `/dsh-proxy-pro/api/status` 会 403——这是应用对未认证请求的安全门,
27
+ > **不是故障**,别用它判断。浏览器内(GUI 页面)同源请求不受影响。
28
+
29
+ ## 3. 功能验收
30
+
31
+ | # | 通道 | 操作 | 预期 |
32
+ |---|---|---|---|
33
+ | 🔵 A5 | 工具 | `proxy_test https://github.com` | `route: PROXIED <系统代理>`、probe ok |
34
+ | 🔵 A6 | 工具 | `proxy_test https://www.deepseek.com` | `route: DIRECT`(NO_PROXY 命中)|
35
+ | 🔵 A4 | 实测 | `web_fetch` 一个被墙 URL(如 github 页面) | 成功返回(此前会 `fetch failed`)= FR-7 修复达成 |
36
+ | 🔵 A7 | 工具 | `proxy_set enabled=false` → `web_fetch` 再试 | 走直连(被墙域可能失败);再 `proxy_set enabled=true` 恢复 |
37
+ | 👁 A8 | GUI | 设置页把「代理地址来源」切「自定义地址」填 `http://127.0.0.1:9999` | 状态行变「未生效 · 自定义地址无效」,恢复为系统代理 |
38
+ | 🔵 A9 | 观察 | 卸载/退出后 `Get-Process` + env | 代理环境变量与 dispatcher 恢复(不进脚本,靠代码审查保证)|
39
+ | 🔵 A12 | 工具 | `proxy_config` 无参读 → 改 `noProxy`/`mode` | 无参返回当前配置;改后 settings.yaml 同步且状态立即变化;非法地址(`"not a url"`)报清晰错误 |
40
+
41
+ ## 4. 异常排查
42
+
43
+ | 现象 | 含义 | 处理 |
44
+ |---|---|---|
45
+ | 重启后头部无胶囊、设置无「代理管理」 | client 半未加载 | 把启动日志贴给我(`%APPDATA%\DSH Desktop\logs\dsh-<日期>.log`)|
46
+ | GUI 在、但设置页显示「状态 API 不可用」 | host API 未注册 | 同上,查日志里 dsh-proxy-pro 相关行 |
47
+ | `proxy_status` 报错/工具不存在 | host 半未 mount | 同上 |
48
+ | 拨开关后 web_fetch 仍失败 | 代理策略未接管 | 先 `proxy_test` 看 route,再贴日志 |
49
+
50
+ ## 5. 接管旧插件(可选,验收全绿后)
51
+
52
+ `<profile>/cordis.patch.yml` 追加一行 `- id: proxy, disabled: true`(禁旧
53
+ dsh-plugin-proxy)→ 重启 → 复测 A1-A6。回滚 = 删该行,settings.yaml 全程不动。
54
+
55
+ ## 6. obsidian-web 复用
56
+
57
+ 用 `dsh --profile obsidian-web` 启动 → 重复 §2-§3(A10)。
58
+
59
+ ---
60
+
61
+ _维护:本清单与 SPEC §7 验收清单一一对应;观察通道约定见 SPEC §7 附录。_