feat: add searxng web-search provider, openrouter cost balance UI, offline scripts; update source-launch and docs
Some checks failed
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / windows node 24 / wine blocking (push) Has been skipped
CI / wine apt cache (push) Successful in 58s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 2m46s
Deploy documentation / deploy (push) Has been skipped
E2E (real DeepSeek API) / e2e (push) Failing after 1m18s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m18s
Landlock Run / Matrix (push) Successful in 13s
Release (vendor) / Pack npm tarballs (push) Failing after 3m43s
Release (dsh) / Pack npm tarballs (push) Failing after 1m53s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m51s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled

This commit is contained in:
2026-08-20 13:01:40 +07:00
parent 99f6f02fec
commit ed152416d5
111 changed files with 5038 additions and 43 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-19-offline-pnpm-prefetch-scripts.md
2026-08-19-offline-pnpm-prefetch-scripts.md: c00eda84455b582f1ee006617d3031473b14f9e4
2026-08-19-offline-pnpm-prefetch-scripts.zh.md: 6314726a7a30cfa79b188d2a98eefa3f2525437a

View File

@@ -0,0 +1,37 @@
# Agent Note: Offline pnpm prefetch scripts
Status: implemented
English | [中文](2026-08-19-offline-pnpm-prefetch-scripts.zh.md)
## Problem
A source checkout cannot boot `dsh web` until a supported Node runtime, `pnpm install`, and `pnpm run build` have materialized `node_modules`, package `lib/` output, and `apps/web/dist`. Install reaches the npm registry (and Corepack reaches it for `pnpm@11.7.0`); the Node distro comes from `nodejs.org/dist`. Node's fetch client cannot use `socks5h://` proxies, so a machine whose only ambient proxy is SOCKS fails `pnpm install` even while curl through the same proxy succeeds. An air-gapped follow-up install therefore needs a same-OS prefetch of the official Node archive, the lockfile store, and a Corepack home that does not call the registry.
## Decision
[`scripts/offline/download-deps`](../../../../scripts/offline/download-deps.ps1) (POSIX twin [`.sh`](../../../../scripts/offline/download-deps.sh)) runs while `nodejs.org` and the registry are reachable. It downloads the official Node zip/tar for the current OS and CPU from `DSH_NODE_DIST_BASE` (default `https://nodejs.org/dist`), verifies `SHASUMS256.txt`, unpacks into ignored [`.offline-cache/node/runtime`](../../../../.gitignore), and writes `.offline-cache/node/runtime.json`. The Node version is `DSH_NODE_VERSION` when set, otherwise the running Node when it satisfies `engines.node` (`^22.19.0 || >=24.0.0`), otherwise the newest `v24.x` from `index.json` that publishes this platform's archive. It then sets `COREPACK_HOME` to `.offline-cache/corepack`, `corepack prepare`s the root `package.json` `packageManager` pin using that Node, and `pnpm fetch --frozen-lockfile --store-dir .offline-store`. [`scripts/offline/install-deps`](../../../../scripts/offline/install-deps.ps1) (POSIX twin [`.sh`](../../../../scripts/offline/install-deps.sh)) unpacks the cached Node if needed, prepends it to `PATH`, sets `COREPACK_ENABLE_NETWORK=0`, and runs `pnpm install --offline --frozen-lockfile` against that store. Optional `-Build` / `--build` runs `npm run build:lib` then `pnpm --dir apps/web run build`, avoiding the nested `pnpm --filter` invocation inside `npm run build:web` that can pick a different Corepack pnpm than `packageManager`.
`DSH_NPM_HTTP_PROXY`, when set, is an HTTP overlay applied only around `corepack prepare` and `pnpm fetch`, then the process proxy variables are restored so a caller SOCKS session is unchanged. curl uses the inherited `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` (SOCKS included) and falls back to `DSH_NPM_HTTP_PROXY` only when none of those are set. A `socks5` or `socks5h` value for Node/pnpm (from `DSH_NPM_HTTP_PROXY`, or from the inherited proxies when no HTTP overlay is set) fails before Corepack runs, because that scheme is not a supported pnpm transport.
The scripts prefetch Node and the lockfile for the host OS and CPU they run on. They do not download LLM weights, Playwright browsers, or a local inference server. Copy the checkout including `.offline-store` and `.offline-cache` to the offline machine.
## Alternatives considered
**Tell contributors to copy a finished `node_modules` tree and skip a second install.** A copied isolated pnpm layout is host-specific and breaks when the store path or package content-address links do not match; `pnpm install --offline` against a fetched store is the documented pnpm air-gap path.
**Check `supportedArchitectures` into the download script so one store serves win32 and linux.** Rejected for the default: optional native packages (esbuild, koffi, node-pty) must match the install host, and a mixed store still needs a same-OS install to unpack the right binaries. Cross-OS prefetch remains an explicit local `pnpm fetch` config, not the script default.
**Add root `package.json` scripts that call these files.** Nested `pnpm` under `npm run` is what already mismatches Corepack versions on `build:web`; the wrappers stay invoked as files so they control `COREPACK_HOME` and `--store-dir`.
**Overwrite the caller's `HTTP_PROXY` for the whole download script.** Rejected: `.\download-deps.ps1` runs in the current PowerShell session, so a session-wide overwrite would replace a working SOCKS proxy for later commands. curl can use SOCKS; only Node/pnpm need the HTTP overlay, and only for those child processes.
**Ship the Node MSI or pkg installer.** Rejected: those installers need administrator rights and a global install; the official zip/tar is relocatable, and install only unpacks it and prepends `PATH`.
**Hardcode one Node version in the script.** Rejected: a pinned patch goes stale against `engines.node` and CI (`22.19`, `24`, `26`). Matching the running Node keeps optional native packages aligned with the prefetch host; `index.json` is the fallback when Node is not on `PATH`.
**Vendor the pnpm store in git.** The store is hundreds of megabytes of registry tarballs; `.gitignore` keeps `.offline-store/` and `.offline-cache/` untracked.
## Consequences
Air-gap preparation is a two-command, same-OS pair and does not change the online `pnpm install` path. The offline machine does not need a preinstalled Node: install unpacks the cached official distro. `dsh plugin … add` of a registry or git spec still needs a network on the machine that runs it. Local chat still needs a separately installed OpenAI-compatible server; these scripts do not substitute for that.

View File

@@ -0,0 +1,37 @@
# Agent Note: Offline pnpm prefetch scripts
Status: implemented
[English](2026-08-19-offline-pnpm-prefetch-scripts.md) | 中文
## Problem
源码检出在受支持的 Node 运行时、`pnpm install` 与 `pnpm run build` 物化 `node_modules`、各包 `lib/` 以及 `apps/web/dist` 之前无法启动 `dsh web`。安装会访问 npm registry(Corepack 为 `pnpm@11.7.0` 也会访问);Node 发行包来自 `nodejs.org/dist`。Node 的 fetch 客户端不能使用 `socks5h://` 代理,因此环境里只有 SOCKS 代理的机器即使 curl 经同一代理成功,`pnpm install` 仍会失败。后续在隔离网络中安装,需要按相同 OS 预取官方 Node 归档、lockfile store,以及一个不再访问 registry 的 Corepack home。
## Decision
[`scripts/offline/download-deps`](../../../../scripts/offline/download-deps.ps1)(POSIX 对侧 [`.sh`](../../../../scripts/offline/download-deps.sh))在能访问 `nodejs.org` 与 registry 时运行。它从 `DSH_NODE_DIST_BASE`(默认 `https://nodejs.org/dist`)下载当前 OS 与 CPU 的官方 Node zip/tar,校验 `SHASUMS256.txt`,解压到已忽略的 [`.offline-cache/node/runtime`](../../../../.gitignore),并写入 `.offline-cache/node/runtime.json`。Node 版本在设置了 `DSH_NODE_VERSION` 时用它,否则在运行中的 Node 满足 `engines.node`(`^22.19.0 || >=24.0.0`)时用该版本,否则用 `index.json` 中发布了当前平台归档的最新 `v24.x`。随后把 `COREPACK_HOME` 设为 `.offline-cache/corepack`,用该 Node 按根目录 `package.json` 的 `packageManager` 引脚执行 `corepack prepare`,并运行 `pnpm fetch --frozen-lockfile --store-dir .offline-store`。[`scripts/offline/install-deps`](../../../../scripts/offline/install-deps.ps1)(POSIX 对侧 [`.sh`](../../../../scripts/offline/install-deps.sh))在需要时解压缓存的 Node,把它前置到 `PATH`,设置 `COREPACK_ENABLE_NETWORK=0`,并对该 store 执行 `pnpm install --offline --frozen-lockfile`。可选的 `-Build` / `--build` 先运行 `npm run build:lib`,再运行 `pnpm --dir apps/web run build`,从而避开 `npm run build:web` 内部嵌套的 `pnpm --filter`——它可能选出与 `packageManager` 不同的 Corepack pnpm。
若设置了 `DSH_NPM_HTTP_PROXY`,它只作为 HTTP 覆盖层套在 `corepack prepare` 与 `pnpm fetch` 周围,随后恢复进程内的代理变量,因此调用方会话里的 SOCKS 代理保持不变。curl 使用继承的 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`(包括 SOCKS),仅在这些变量都未设置时才回退到 `DSH_NPM_HTTP_PROXY`。供 Node/pnpm 使用的值(来自 `DSH_NPM_HTTP_PROXY`,或在未设置 HTTP 覆盖层时来自继承的代理变量)若为 `socks5` 或 `socks5h`,会在 Corepack 运行前失败,因为该 scheme 不是 pnpm 支持的传输。
脚本只为运行时所在主机的 OS 与 CPU 预取 Node 与 lockfile。它们不下载 LLM 权重、Playwright 浏览器或本地推理服务器。把包含 `.offline-store` 与 `.offline-cache` 的检出复制到离线机器。
## Alternatives considered
**让贡献者复制已完成的 `node_modules` 树并跳过第二次安装。** 复制出的 isolated pnpm 布局与主机相关,在 store 路径或包 content-address 链接不匹配时会损坏;针对已 fetch 的 store 运行 `pnpm install --offline` 才是 pnpm 文档中的隔离网络路径。
**把 `supportedArchitectures` 写进 download 脚本,使一份 store 同时服务 win32 与 linux。** 拒绝作为默认:可选原生包(esbuild、koffi、node-pty)必须匹配安装主机,混合 store 仍需在相同 OS 上安装才能解出正确二进制。跨 OS 预取仍是显式的本地 `pnpm fetch` 配置,不是脚本默认。
**在根 `package.json` 增加调用这些文件的 scripts。** `npm run` 下嵌套 `pnpm` 正是 `build:web` 上 Corepack 版本错配的原因;包装器保持按文件调用,以便自行控制 `COREPACK_HOME` 与 `--store-dir`。
**在整个 download 脚本期间覆盖调用方的 `HTTP_PROXY`。** 拒绝:`.\download-deps.ps1` 在当前 PowerShell 会话中运行,会话级覆盖会把后续命令仍可用的 SOCKS 代理换掉。curl 可以使用 SOCKS;只有 Node/pnpm 需要 HTTP 覆盖层,且仅针对这些子进程。
**随脚本分发 Node 的 MSI 或 pkg 安装包。** 拒绝:那些安装器需要管理员权限和全局安装;官方 zip/tar 可重定位,install 只需解压并前置 `PATH`。
**在脚本中写死某一个 Node 版本。** 拒绝:钉死的 patch 会相对 `engines.node` 与 CI(`22.19`、`24`、`26`)过时。与正在运行的 Node 对齐,可使可选原生包与预取主机一致;`PATH` 上没有 Node 时回退到 `index.json`。
**把 pnpm store 纳入 git。** store 是数百 MB 的 registry tarball;`.gitignore` 使 `.offline-store/` 与 `.offline-cache/` 保持未跟踪。
## Consequences
隔离网络准备是一对相同 OS 上的两条命令,不改变在线 `pnpm install` 路径。离线机器不需要预先安装 Node:install 会解压缓存的官方发行包。在运行 `dsh plugin … add` 安装 registry 或 git spec 的机器上仍然需要网络。本地对话仍需要另行安装的 OpenAI-compatible 服务器;这些脚本不能替代它。

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-19-source-dsh-http-proxy-overlay.md
2026-08-19-source-dsh-http-proxy-overlay.md: 814633aeda22d1afb306e9f42c26a0701e62b746
2026-08-19-source-dsh-http-proxy-overlay.zh.md: 878a6024027bbc6081cc08b5891d7da6dc243895

View File

@@ -0,0 +1,35 @@
# Agent Note: Source dsh HTTP proxy overlay
Status: implemented
English | [中文](2026-08-19-source-dsh-http-proxy-overlay.zh.md)
## Problem
Machines whose ambient proxy is SOCKS can reach OpenRouter with curl and still fail `pnpm dsh` HTTPS fetches: Node, undici, and pi-ai accept `http://` / `https://` proxy URLs and reject `socks5` / `socks5h`. Setting `HTTPS_PROXY` in the calling PowerShell or bash session also routes later shell commands through that HTTP proxy. Product `.env` files cannot supply `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, or `NO_PROXY` because those names are bootstrap-only. Assigning those names on `process.env` after Node has started does not rebind `fetch`: undici reads `NODE_USE_ENV_PROXY` and `HTTPS_PROXY` at process start. Node `--env-file-if-exists` also leaves inherited names in place, so a user-level `HTTPS_PROXY=socks5h://…` still wins.
## Decision
The root `dsh` script runs [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts) as `node --import tsx/esm scripts/run-source-dsh.ts`. [`applySourceDshHttpProxy`](../../../../scripts/apply-source-dsh-http-proxy.ts) writes HTTP proxy variables onto a copy of `process.env`. When gitignored [`.dsh-http-proxy.env`](../../../../.gitignore) or `DSH_HTTP_PROXY` supplies an overlay and `DSH_SOURCE_HTTP_PROXY_APPLIED` is unset, the wrapper respawns the same Node argv with that environment so `NODE_USE_ENV_PROXY=1` and the HTTP proxy URLs exist before `fetch` initializes. The marker prevents a second respawn. The calling shell is unchanged.
`DSH_HTTP_PROXY`, when set to a non-empty HTTP(S) URL, overwrites `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` (both cases) and sets `NODE_USE_ENV_PROXY=1`. When `NO_PROXY` and `no_proxy` are both unset, it sets them to `localhost,127.0.0.1,::1`. Otherwise the overlay file's parsed assignments are applied, then any remaining SOCKS value on those six names is replaced by the file's HTTP URL so an omitted `ALL_PROXY` cannot keep `socks5h://`. A `socks5` or `socks5h` winning overlay fails before the CLI boots. Absent both sources, the inherited environment is unchanged and there is no respawn. The wrapper prints the applied HTTP URL once to stderr before respawn.
The installed `apps/cli/lib/bin.js` path is unchanged: it has no checkout overlay file.
## Alternatives considered
**Tell the user to export `HTTPS_PROXY` in the shell before `pnpm dsh`.** That reaches OpenRouter, but the same variables remain on later commands in that session, including tools that should keep a SOCKS proxy.
**Put proxy names in the invoking `.env` or `$DSH_HOME/.env`.** Rejected by the [configuration-source-ownership decision](../architecture/2026-08-04-configuration-source-ownership.md): bootstrap network variables may come only from the inherited process environment.
**Assign `NODE_USE_ENV_PROXY` only inside the wrapper after Node has started.** OpenRouter traffic uses `fetch`; Node binds the env-proxy dispatcher at process start, so a later `process.env` write does not change it.
**Use Node `--env-file-if-exists` without respawning.** That file does not replace names the parent already set, so an inherited `socks5h://` `HTTPS_PROXY` still reaches `fetch`.
## Consequences
Source `pnpm dsh` can use an HTTP overlay without mutating the calling shell. An inherited SOCKS `HTTPS_PROXY` is replaced in the respawned Node process, not in the shell. Contributors without `DSH_HTTP_PROXY` or `.dsh-http-proxy.env` see the previous inheritance behavior and no extra process. A SOCKS-only overlay fails loud instead of timing out against OpenRouter. The published bin still requires the caller to pass HTTP proxy variables when the host needs a proxy. `--use-env-proxy` is not on the command: Node 22.19 in CI rejects the flag.
## Testing
`scripts/apply-source-dsh-http-proxy.spec.ts` covers no-overlay, `DSH_HTTP_PROXY` overwrite of SOCKS, file parse, file-versus-`DSH_HTTP_PROXY` precedence, SOCKS leftover on omitted names, SOCKS rejection, and the one-shot respawn marker. `apps/cli/tests/source-launch.compat.spec.ts` pins the root `dsh` command to `scripts/run-source-dsh.ts` and smokes that vector.

View File

@@ -0,0 +1,35 @@
# Agent Note: Source dsh HTTP proxy overlay
Status: implemented
[English](2026-08-19-source-dsh-http-proxy-overlay.md) | 中文
## 问题
环境中只有 SOCKS 代理的机器可以用 curl 访问 OpenRouter,但 `pnpm dsh` 的 HTTPS fetch 仍会失败:Node、undici 和 pi-ai 接受 `http://` / `https://` 代理 URL,并拒绝 `socks5` / `socks5h`。在调用方的 PowerShell 或 bash 会话里设置 `HTTPS_PROXY` 也会让该会话中后续命令走这个 HTTP 代理。产品 `.env` 文件不能提供 `HTTP_PROXY`、`HTTPS_PROXY`、`ALL_PROXY` 或 `NO_PROXY`,因为这些名称只能来自进程启动环境。在 Node 启动之后再往 `process.env` 写入这些名称不会重新绑定 `fetch`:undici 在进程启动时读取 `NODE_USE_ENV_PROXY` 和 `HTTPS_PROXY`。Node `--env-file-if-exists` 同样不会替换已继承的名称,因此用户级的 `HTTPS_PROXY=socks5h://…` 仍然生效。
## 决策
根目录的 `dsh` 脚本以 `node --import tsx/esm scripts/run-source-dsh.ts` 运行 [`scripts/run-source-dsh.ts`](../../../../scripts/run-source-dsh.ts)。[`applySourceDshHttpProxy`](../../../../scripts/apply-source-dsh-http-proxy.ts) 把 HTTP 代理变量写入 `process.env` 的副本。当被 gitignore 的 [`.dsh-http-proxy.env`](../../../../.gitignore) 或 `DSH_HTTP_PROXY` 提供覆盖层且未设置 `DSH_SOURCE_HTTP_PROXY_APPLIED` 时,包装层用该环境按相同的 Node argv 再拉起一次进程,以便在 `fetch` 初始化之前就存在 `NODE_USE_ENV_PROXY=1` 和 HTTP 代理 URL。该标记防止第二次再拉起。调用方 shell 不变。
当 `DSH_HTTP_PROXY` 设为非空的 HTTP(S) URL 时,它会覆盖 `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`(两种大小写)并设置 `NODE_USE_ENV_PROXY=1`。当 `NO_PROXY` 与 `no_proxy` 都未设置时,它将它们设为 `localhost,127.0.0.1,::1`。否则先应用覆盖文件中解析出的赋值,再把这六个名称上残留的 SOCKS 值换成文件中的 HTTP URL,以免漏写的 `ALL_PROXY` 仍指向 `socks5h://`。最终生效的覆盖层若为 `socks5` 或 `socks5h`,会在 CLI 启动前失败。两种来源都不存在时,继承的环境保持不变,也不会再拉起进程。包装层在再拉起之前会把生效的 HTTP URL 打印到 stderr 一次。
已安装的 `apps/cli/lib/bin.js` 路径不变:它没有检出级覆盖文件。
## 考虑过的备选方案
**让用户在运行 `pnpm dsh` 之前于 shell 中导出 `HTTPS_PROXY`。**这样可以访问 OpenRouter,但这些变量会留在该会话的后续命令上,包括本应继续使用 SOCKS 代理的工具。
**把代理名称放进调用目录的 `.env` 或 `$DSH_HOME/.env`。**被[配置来源归属决策](../architecture/2026-08-04-configuration-source-ownership.md)拒绝:网络启动变量只能来自继承的进程环境。
**仅在包装层内、Node 启动之后赋值 `NODE_USE_ENV_PROXY`。** OpenRouter 流量走 `fetch`;Node 在进程启动时绑定 env-proxy dispatcher,随后再写 `process.env` 不会改变它。
**使用 Node `--env-file-if-exists` 且不再拉起进程。**该文件不会替换父进程已经设置的名称,因此继承来的 `socks5h://` `HTTPS_PROXY` 仍会到达 `fetch`。
## 影响
源码路径的 `pnpm dsh` 可以使用 HTTP 覆盖层,而不修改调用方 shell。继承来的 SOCKS `HTTPS_PROXY` 会在再拉起的 Node 进程中被替换,而不是在 shell 中。没有 `DSH_HTTP_PROXY` 或 `.dsh-http-proxy.env` 的贡献者仍看到原来的继承行为,也不会多一个进程。仅有 SOCKS 的覆盖层会立即失败,而不是在访问 OpenRouter 时超时。已发布的 bin 在宿主需要代理时,仍要求调用方传入 HTTP 代理变量。命令行不加 `--use-env-proxy`:CI 中的 Node 22.19 会拒绝该 flag。
## 测试
`scripts/apply-source-dsh-http-proxy.spec.ts` 覆盖无覆盖层、`DSH_HTTP_PROXY` 覆盖 SOCKS、文件解析、文件与 `DSH_HTTP_PROXY` 的优先级、文件未写出的名称上残留的 SOCKS、SOCKS 拒绝,以及一次性再拉起标记。`apps/cli/tests/source-launch.compat.spec.ts` 将根目录 `dsh` 命令钉在 `scripts/run-source-dsh.ts` 上,并对该启动向量做冒烟测试。