Skip to content
dsh.fish
Bundle

dsh-plugin-proxy-env

DeepSeek Harness plugin: inject proxy environment variables at startup and self-check the whole proxy chain

Source
looeton
License
MIT
Updated
Updated 8 days ago

Readme

# dsh-plugin-proxy-env

> DSH 插件:启动时自动注入代理环境变量,并自检整条代理链路。
> A DeepSeek Harness plugin that injects proxy environment variables at startup and self-checks the whole proxy chain.

[![CI](https://github.com/looeton/dsh-plugin-proxy-env/actions/workflows/ci.yml/badge.svg)](https://github.com/looeton/dsh-plugin-proxy-env/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-339933.svg)](#中文)
[![dsh plugin](https://img.shields.io/badge/dsh-plugin-8A2BE2.svg)](#中文)

[中文](#中文) · [English](#english)

---

## 中文

### 这是什么

一个 DSH 插件,做两件事:

1. **启动时注入代理变量**——把 `HTTP_PROXY` / `HTTPS_PROXY` / `NODE_USE_ENV_PROXY` / `NO_PROXY` 写进 harness 主进程的环境,之后**所有子进程**(shell、git、npm、python)自动继承,不用再 `setx` + 重启。
2. **自检**——从本地端口一路查到端到端请求,把结论写进日志和报告文件;同时注册一个 `proxy_env_check` 工具,随时能再查一次。

### 为什么这样能生效

DSH 在**派生子进程时**从 `process.env` 构造子环境(`dsh-subprocess` 的 `scrubbedParentEnv()`,它只剔除名字含 `KEY/PASSWORD/SECRET/TOKEN` 的和 `DSH_*` 的变量,**代理变量是刻意保留的**)。所以插件在加载阶段写一次 `process.env`,后面每一次 spawn 都带着它——比手工 `setx` 更即时,也不依赖启动方式。

### 注入哪些变量

| 变量 | 值 | 作用 |
|---|---|---|
| `HTTP_PROXY` / `HTTPS_PROXY` | 解析到的代理地址 | curl / git / python / Go 等自动读取 |
| `NODE_USE_ENV_PROXY` | `1` | **子** Node 进程启动时即让内置 `fetch` 读取上面的变量 |
| `NO_PROXY` | 默认 `localhost,127.0.0.1,::1,api.deepseek.com` | 回环必须排除,否则本地 GUI 请求会被塞进代理;模型 API 排除后,代理挂掉也不影响对话 |

> 已存在的同名变量默认**不覆盖**(`override: true` 可强制)。

### 自检做什么

按「由近到远」的顺序,先报最上游的断点:

1. 代理端口是否监听
2. 端口是否应答 `CONNECT`(手工建隧道,不依赖 undici)
3. DNS 是否返回真实地址(识别 `198.18.0.0/15` 与 `fdfe:dcba:9876::/64` 的 fake-ip)
4. 直连探针 + 走代理探针(TLS 隧道)
5. 汇总结论:`PASS` / `WARN` / `FAIL` + 一条修复建议

结果写入 `$DSH_HOME/proxy-env/report.json`,agent 可以直接读取,不必调用工具。

### 安装

**方式一:DSH Desktop 插件管理器**(推荐)

设置 → 插件 → 安装,填入 `dsh-plugin-proxy-env`(发布到 npm 后可用)。

**方式二:手动(从本仓库安装)**

```powershell
# 1) 进入当前 profile 目录
cd "$env:DSH_HOME\profiles\desktop"

# 2) 安装(本地路径、git 或 npm 均可)
pnpm add file:D:/path/to/dsh-plugin-proxy-env
#   或 pnpm add github:looeton/dsh-plugin-proxy-env

# 3) 把包名加进 package.json 的 dsh.profile.bundles
#    "dsh": { "profile": { "bundles": [ ..., "dsh-plugin-proxy-env" ] } }

# 4) 重启 DSH Desktop
```

**方式三:先本地验证,不装进 profile**

```powershell
node src/cli.mjs            # 独立自检,跟插件走同一套代码
node --test tests/*.test.mjs   # 52 个单元测试
```

### 配置项

通过 patch 行或 profile 的 `cordis.patch.yml` 传入:

```yaml
- id: proxy-env
  config:
    proxyUrl: http://127.0.0.1:7897   # 留空则自动探测
    noProxy: localhost,127.0.0.1,::1,api.deepseek.com
    selfCheck: true
    tool: true
```

| 键 | 默认 | 说明 |
|---|---|---|
| `enable` | `true` | 总开关 |
| `proxyUrl` | 自动 | 完整代理地址;留空则按 `port` → Windows 系统代理 → 已导出的环境变量顺序解析 |
| `host` | `127.0.0.1` | 本地代理主机 |
| `port` | `0` | 直接指定端口 |
| `noProxy` | 见上 | `NO_PROXY` 内容 |
| `override` | `false` | 是否覆盖已存在的同名变量 |
| `patchGlobalDispatcher` | `true` | 动态 import `undici`,把全局 dispatcher 换成 `EnvHttpProxyAgent`,让**运行时**的 Node 请求也走代理 |
| `selfCheck` | `true` | 启动时自检 |
| `writeReport` | `true` | 写 `$DSH_HOME/proxy-env/report.json` |
| `tool` | `true` | 注册 `proxy_env_check` 工具 |
| `probeUrl` | `https://www.google.com` | 只有走代理才通的探针 |
| `directProbeUrl` | `https://example.com` | 直连探针 |
| `dnsHost` | `example.com` | fake-ip 检测域名 |
| `timeoutMs` | `15000` | 单个探针超时 |

### 提供的工具

| 工具 | 参数 | 返回 |
|---|---|---|
| `proxy_env_check` | `probe_url`、`skip_network` | 一行结论 + 完整链路文本 |

### 局限

- **它管的是进程环境,不是 `web_fetch`**。DSH 的 `web_fetch` 用固定 IP 的自定义 undici `Agent`(防 SSRF / DNS rebinding),刻意绕过全局 dispatcher,所以即使代理正常它也不会走代理——这是设计,不是本插件的缺陷。被墙站点请用能走云端的抓取工具,或让 agent 用 shell 里的 `node` / `python` 抓。
- **注册表读取依赖 `reg.exe`**,且用文件重定向而非管道(受限令牌沙箱里管道会 EPERM)。读不到时自动回退到已导出的 `HTTPS_PROXY` / `HTTP_PROXY`。
- **非 Windows** 只走配置和环境变量,不读系统代理。
- **`patchGlobalDispatcher` 是运行时切换**:它需要能解析到 `undici`,且版本要有 `EnvHttpProxyAgent`(唯一会遵守 `NO_PROXY` 的实现);不满足时跳过并在日志里说明。

### 相关项目

- **[clash-verge-system-proxy](https://github.com/looeton/clash-verge-system-proxy)** —— 手工版完整指南(关 TUN、开系统代理、写变量、排错手册)
- **[proxy-doctor](https://github.com/looeton/proxy-doctor)** —— 独立的 PowerShell 诊断工具,不装插件也能用

---

## English

### What this is

A DSH plugin that does two things:

1. **Injects proxy variables at startup** — writes `HTTP_PROXY` / `HTTPS_PROXY` / `NODE_USE_ENV_PROXY` / `NO_PROXY` into the harness host process, so **every child process** (shell, git, npm, python) inherits them. No `setx`, no restart dance.
2. **Self-checks the chain** — walks from the local port to a real request, logs a verdict, writes a report file, and registers a `proxy_env_check` tool for on-demand re-runs.

### Why this works

DSH builds each child environment from `process.env` at spawn time (`scrubbedParentEnv()` in `dsh-subprocess` strips credential-shaped names and `DSH_*`, but **keeps proxy variables on purpose**). Writing `process.env` once during plugin load therefore covers every later spawn — immediately, and independent of how the app was launched.

### What it injects

| Variable | Value | Purpose |
|---|---|---|
| `HTTP_PROXY` / `HTTPS_PROXY` | resolved proxy URL | read by curl, git, python, Go, … |
| `NODE_USE_ENV_PROXY` | `1` | makes **child** Node processes honour the two above from bootstrap |
| `NO_PROXY` | `localhost,127.0.0.1,::1,api.deepseek.com` | loopback must be excluded or local GUI requests go through the proxy; the model API is excluded so a dead proxy cannot break chat |

> Existing values are left alone unless `override: true`.

### What the self-check does

Nearest-first, so the report always names the earliest broken link:

1. is the proxy port listening
2. does it answer `CONNECT` (hand-built tunnel, no undici needed)
3. does DNS return real addresses (detects `198.18.0.0/15` and `fdfe:dcba:9876::/64` fake-ip)
4. direct probe + through-proxy probe (TLS tunnel)
5. one verdict — `PASS` / `WARN` / `FAIL` — plus one fix

The report lands in `$DSH_HOME/proxy-env/report.json`, readable by the agent without a tool call.

### Install

**Option 1 — DSH Desktop plugin manager** (recommended): Settings → Plugins → Install, then `dsh-plugin-proxy-env` (available once published to npm).

**Option 2 — manual, from this repository**

```powershell
cd "$env:DSH_HOME\profiles\desktop"
pnpm add file:D:/path/to/dsh-plugin-proxy-env   # or github:looeton/dsh-plugin-proxy-env
# add "dsh-plugin-proxy-env" to dsh.profile.bundles in package.json
# restart DSH Desktop
```

**Option 3 — verify locally without installing**

```powershell
node src/cli.mjs            # standalone self-check, same code path
node --test tests/*.test.mjs   # 52 unit tests
```

### Configuration

```yaml
- id: proxy-env
  config:
    proxyUrl: http://127.0.0.1:7897   # empty = auto-detect
    noProxy: localhost,127.0.0.1,::1,api.deepseek.com
    selfCheck: true
    tool: true
```

| Key | Default | Purpose |
|---|---|---|
| `enable` | `true` | master switch |
| `proxyUrl` | auto | full proxy URL; when empty the order is `port` → Windows system proxy → exported env var |
| `host` | `127.0.0.1` | local proxy host |
| `port` | `0` | explicit port |
| `noProxy` | see above | `NO_PROXY` contents |
| `override` | `false` | overwrite existing variables |
| `patchGlobalDispatcher` | `true` | dynamically import `undici` and switch the global dispatcher to `EnvHttpProxyAgent`, so runtime Node requests use the proxy too |
| `selfCheck` | `true` | run the check at startup |
| `writeReport` | `true` | write `$DSH_HOME/proxy-env/report.json` |
| `tool` | `true` | register the `proxy_env_check` tool |
| `probeUrl` | `https://www.google.com` | only reachable through the proxy |
| `directProbeUrl` | `https://example.com` | reachable directly |
| `dnsHost` | `example.com` | host used for the fake-ip check |
| `timeoutMs` | `15000` | per-probe timeout |

### The tool it registers

| Tool | Arguments | Returns |
|---|---|---|
| `proxy_env_check` | `probe_url`, `skip_network` | one-line verdict plus the full chain report |

### Limitations

- **It covers process environment, not `web_fetch`.** DSH's `web_fetch` pins connections with a custom undici `Agent` (SSRF / DNS-rebinding defence) and bypasses the global dispatcher by design, so it will not use the proxy even when everything else does. For blocked sites use a cloud-based page reader, or have the agent fetch with `node` / `python` from a shell.
- **Registry reading shells out to `reg.exe`**, with stdout redirected to a temp file rather than a pipe (pipes fail with EPERM inside restricted-token sandboxes). When unreadable it falls back to an exported `HTTPS_PROXY` / `HTTP_PROXY`.
- **Non-Windows** hosts use configuration and environment variables only.
- **`patchGlobalDispatcher` is a runtime switch**: it needs `undici` to resolve and a version that ships `EnvHttpProxyAgent` (the only variant that honours `NO_PROXY`). Otherwise it is skipped and logged.

### Related projects

- **[clash-verge-system-proxy](https://github.com/looeton/clash-verge-system-proxy)** — the manual companion guide (TUN off, system proxy on, variables, troubleshooting)
- **[proxy-doctor](https://github.com/looeton/proxy-doctor)** — standalone PowerShell diagnosis, no plugin needed

---

## Development

```bash
node --check src/*.mjs      # or: npm run check
npm test                    # or: node --test tests/*.test.mjs
node src/cli.mjs            # self-check against the current machine
```

## License

[MIT](LICENSE)

Install

dsh plugin --profile web add github:looeton/dsh-plugin-proxy-env

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source