Sfoglia il codice sorgente

refactor(net): make the proxy a util library with six functions

Review asked why this is a plugin and why it exports so much. The design note
this branch shipped answered the second question itself — "a pure resolution
function plus an installation function" — and the code drifted to seventeen
exports and a Cordis plugin nobody approved or mounted.

The plugin is gone. Transport policy has one answer per process: nothing to
swap, and no scope narrower than the process to give one. Its `Config` was also
the only supplier of a configuration branch, so resolution now reads the
environment and nothing else — `mode`, the config-sourced fields, and the
`config` policy source were unreachable the moment the plugin left.

Four exports nothing outside the package used are internal again, and
`currentProxyPolicy` answers with the direct policy instead of `undefined`, so
`DIRECT_POLICY` no longer needs a public face. Nine functions remain, one per
way a caller can need the policy; two is not reachable with six consumer seams.

The package moves to `util/`. The note claimed a dependency on `undici`
disqualified it from that group; the charter governs harness dependencies, not
external ones, and the process note that says so predates this branch. The real
blocker was the harness dependency: resolution needed one method of
`LaunchEnvironmentSnapshot`, so it names a structural `EnvLookup` and the
launcher passes its snapshot unchanged. `net/` is dissolved.

Dropping the group's line from the repository layout also returns `AGENTS.md`
to its original ceiling, so the raise the merge needed is reverted.
Yichen Jiang 1 mese fa
parent
commit
c62d6f3a44
57 ha cambiato i file con 246 aggiunte e 655 eliminazioni
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml
  2. 7 3
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md
  3. 7 3
      .agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md
  4. 0 1
      AGENTS.md
  5. 2 2
      docs/config-catalog.i18n.yaml
  6. 1 33
      docs/config-catalog.md
  7. 1 33
      docs/config-catalog.zh.md
  8. 2 2
      docs/module-graph.i18n.yaml
  9. 26 29
      docs/module-graph.md
  10. 26 29
      docs/module-graph.zh.md
  11. 2 2
      packages/README.i18n.yaml
  12. 0 1
      packages/README.md
  13. 0 1
      packages/README.zh.md
  14. 1 1
      packages/e2b/e2b/tsconfig.json
  15. 1 1
      packages/llm/llm-deepseek/tsconfig.json
  16. 1 1
      packages/llm/llm-pi-ai/tsconfig.json
  17. 1 1
      packages/mcp/mcp-client/tsconfig.json
  18. 0 44
      packages/net/README.md
  19. 0 44
      packages/net/README.zh.md
  20. 0 6
      packages/net/http-proxy/README.i18n.yaml
  21. 0 88
      packages/net/http-proxy/src/index.ts
  22. 0 90
      packages/net/http-proxy/tests/plugin.spec.ts
  23. 1 1
      packages/session/session-telemetry-otel/tsconfig.json
  24. 1 1
      packages/subprocess/subprocess/tsconfig.json
  25. 1 1
      packages/test-support/loader-smoke/tsconfig.json
  26. 1 1
      packages/test-support/session-snapshot/tsconfig.json
  27. 2 2
      packages/util/README.i18n.yaml
  28. 2 1
      packages/util/README.md
  29. 2 1
      packages/util/README.zh.md
  30. 3 3
      packages/util/http-proxy/README.i18n.yaml
  31. 5 5
      packages/util/http-proxy/README.md
  32. 5 5
      packages/util/http-proxy/README.zh.md
  33. 3 6
      packages/util/http-proxy/package.json
  34. 37 0
      packages/util/http-proxy/src/index.ts
  35. 6 4
      packages/util/http-proxy/src/install.ts
  36. 0 0
      packages/util/http-proxy/src/invariant.ts
  37. 23 76
      packages/util/http-proxy/src/policy.ts
  38. 3 25
      packages/util/http-proxy/tests/install.spec.ts
  39. 18 0
      packages/util/http-proxy/tests/invariant.spec.ts
  40. 0 0
      packages/util/http-proxy/tests/matcher-parity.spec.ts
  41. 10 56
      packages/util/http-proxy/tests/policy.spec.ts
  42. 0 0
      packages/util/http-proxy/tsconfig.json
  43. 2 2
      packages/web/web-fetch-http/src/provider.ts
  44. 1 1
      packages/web/web-fetch-http/tsconfig.json
  45. 1 1
      packages/web/web-search-deepseek/tsconfig.json
  46. 1 1
      packages/web/web-search-exa/tsconfig.json
  47. 1 1
      packages/web/web-search-perplexity/tsconfig.json
  48. 1 1
      packages/workflow/workflow-worker-thread/tsconfig.json
  49. 28 34
      pnpm-lock.yaml
  50. 1 1
      scripts/doc-budgets.manifest.json
  51. 1 1
      scripts/test-proxy-environment.spec.ts
  52. 1 1
      scripts/test-proxy-environment.ts
  53. 1 1
      scripts/verify-no-bare-dispatcher.ts
  54. 1 1
      scripts/verify-package-readme-model-experience.ts
  55. 0 1
      scripts/verify-subsystem-pages.ts
  56. 2 2
      tsconfig.base.json
  57. 1 1
      tsconfig.host.json

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.i18n.yaml

@@ -2,5 +2,5 @@
 # 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/architecture/2026-08-27-outbound-proxy-policy.md
-2026-08-27-outbound-proxy-policy.md: 5c017d8c36321877981383f083b739f7b5622ccb
-2026-08-27-outbound-proxy-policy.zh.md: 7efe83a75c4c04695dc422cb87365226e249be1e
+2026-08-27-outbound-proxy-policy.md: c15c1d08537a5751ac57651fe7ef94e0088d0896
+2026-08-27-outbound-proxy-policy.zh.md: ee4b9b2768bd6a75565b2f09ef88c9bb334e4ed4

+ 7 - 3
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.md

@@ -14,11 +14,15 @@ That sentence could not have worked anyway, for three measured reasons. `NODE_US
 
 ## Decision
 
-**One policy, resolved once from the launch environment, installed as the global dispatcher.** `packages/net/http-proxy` resolves a `ProxyPolicy` and installs it in `runProfile` immediately after the environment snapshot is provided and before any entry mounts. Node's `fetch` resolves undici's global dispatcher, so every plain `fetch()` and every SDK that reaches `globalThis.fetch` is covered without touching its code — nine call sites at the time of writing, and every future one for free. `loadLayeredEnv` has exactly one caller and `apps/web` ships no bin, so this single site covers every profile including `sdk-minimal`, which does not layer over `base`.
+**One policy, resolved once from the launch environment, installed as the global dispatcher.** `packages/util/http-proxy` resolves a `ProxyPolicy` and installs it in `runProfile` immediately after the environment snapshot is provided and before any entry mounts. Node's `fetch` resolves undici's global dispatcher, so every plain `fetch()` and every SDK that reaches `globalThis.fetch` is covered without touching its code — nine call sites at the time of writing, and every future one for free. `loadLayeredEnv` has exactly one caller and `apps/web` ships no bin, so this single site covers every profile including `sdk-minimal`, which does not layer over `base`.
 
 Resolution reads the launcher's snapshot rather than `process.env`, which is what makes a proxy in a `.env` layer work — the capability the environment-variable approach cannot have.
 
-**A new `packages/net/` group.** The package must depend on `undici` (Node exposes no `node:undici`), so it cannot join the zero-dependency `util/` group; and `boot`, `web`, `subprocess`, and `workflow` all consume it, so joining any one of them would invert three dependencies. It is deliberately not a capability seam: transport policy has one implementation and one answer per process, so there is nothing to swap.
+**A library in `util/`, not a plugin.** Transport policy has one answer per process: nothing to swap, and no scope narrower than the process to give one. The package exports functions and mounts nothing — `boot`, `web`, `subprocess`, and `workflow` all consume it, and `util/` is the group every other group may depend on.
+
+An earlier revision put it in a new `net/` package group, reasoning that depending on `undici` disqualified it from a "zero-dependency" group. That reading was wrong: [dependencies over hand-rolling](../process/2026-07-26-dependencies-over-hand-rolling.md) records that the charter governs *harness* dependencies — util stays free of them so any group can depend on util — and does not ban external packages. What did need removing was the dependency on `dsh-launch-environment`: resolution needs one method from it, so it names a structural `EnvLookup` instead and the launcher passes its snapshot unchanged.
+
+The plugin that revision shipped is gone with it. It let a composition declare the policy in `cordis.yml`, but no shipped bundle mounted it, so the launcher's path was the only reachable one — and its `Config` was the sole supplier of a configuration branch nothing else could reach.
 
 **The installed dispatcher routes by the policy, not by an environment it re-parses.** `installGlobalProxy` builds an `Agent` whose per-origin `factory` asks `proxyForUrl` where that origin goes, and returns a `ProxyAgent` or undici's own default client for it. undici's `EnvHttpProxyAgent` was the first choice and is wrong for this policy: when no `HTTPS_PROXY` is present it sets its HTTPS agent to the HTTP one, so a scheme this package keeps direct after refusing a SOCKS or malformed URL would still be tunnelled while the diagnostic said otherwise. Routing through the one predicate removes that class of divergence by construction rather than by test. Publishing the policy into the environment remains, but now serves only the readers that have no policy object: Node's `proxyEnv` option and every spawned child.
 
@@ -72,7 +76,7 @@ The suite is hermetic against the developer's own environment: `plugin.spec.ts`
 
 ## Testing
 
-`packages/net/http-proxy` holds 64 tests at 100% per-file coverage. Resolution covers precedence, the `ALL_PROXY` fallback, blank-shadowing, the SOCKS and malformed diagnostics, and `mode: 'off'`; bypass matching covers suffixes, ports, both IPv6 spellings, and the CIDR entry that deliberately does not match. Installation drives a real loopback proxy and asserts the absolute-form request arrives, that a bypassed target does not, and that disposal restores the dispatcher, the policy, and the environment.
+`packages/util/http-proxy` holds 89 tests at 100% per-file coverage. Resolution covers precedence, the `ALL_PROXY` fallback, blank-shadowing, the SOCKS and malformed diagnostics, and the HTTPS-only environment that leaves `http:` direct; routing covers the whole loopback range structurally, and bypass matching covers suffixes, ports, both IPv6 spellings, and the CIDR entry that deliberately does not match. Installation drives a real loopback proxy and asserts the absolute-form request arrives, that a bypassed target does not, and that disposal restores the dispatcher, the policy, and the environment.
 
 `packages/web/web-fetch-http/tests/proxy.spec.ts` asserts the decision that matters most: under a proxy the public-address resolver is never called, while a bypassed hop still calls it exactly once, and the cross-origin redirect refusal survives on the proxied path.
 

+ 7 - 3
.agents/notes/implemented/architecture/2026-08-27-outbound-proxy-policy.zh.md

@@ -14,11 +14,15 @@ Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`。开发者运
 
 ## Decision
 
-**一份策略,从启动环境解析一次,装为全局 dispatcher。** `packages/net/http-proxy` 解析出 `ProxyPolicy`,并在 `runProfile` 中于环境快照提供之后、任何 entry 挂载之前完成安装。Node 的 `fetch` 解析的正是 undici 的全局 dispatcher,因此每一处普通 `fetch()` 以及每一个最终落到 `globalThis.fetch` 的 SDK 都无需改动即被覆盖——撰写时是九个调用点,未来新增的也自动覆盖。`loadLayeredEnv` 只有一个调用方,且 `apps/web` 不提供 bin,因此这一处即覆盖全部 profile,包括不叠加 `base` 的 `sdk-minimal`。
+**一份策略,从启动环境解析一次,装为全局 dispatcher。** `packages/util/http-proxy` 解析出 `ProxyPolicy`,并在 `runProfile` 中于环境快照提供之后、任何 entry 挂载之前完成安装。Node 的 `fetch` 解析的正是 undici 的全局 dispatcher,因此每一处普通 `fetch()` 以及每一个最终落到 `globalThis.fetch` 的 SDK 都无需改动即被覆盖——撰写时是九个调用点,未来新增的也自动覆盖。`loadLayeredEnv` 只有一个调用方,且 `apps/web` 不提供 bin,因此这一处即覆盖全部 profile,包括不叠加 `base` 的 `sdk-minimal`。
 
 解析读取的是启动器的快照而非 `process.env`,这正是让 `.env` 层中的代理生效的原因——也是环境变量方案不可能具备的能力。
 
-**新增 `packages/net/` 分组。** 本包必须依赖 `undici`(Node 不暴露 `node:undici`),因此无法加入零依赖的 `util/` 组;而 `boot`、`web`、`subprocess` 与 `workflow` 都消费它,放进其中任何一组都会让另外三条依赖反向。它刻意不是能力接缝:传输策略每个进程只有一种实现、一个答案,没有可替换的对象。
+**放在 `util/` 的库,而非插件。** 传输策略每个进程只有一个答案:没有可替换的实现,也没有比进程更窄的作用域可赋予。因此本包只导出函数、不挂载任何东西——`boot`、`web`、`subprocess` 与 `workflow` 都消费它,而 `util/` 正是其他所有组都可以依赖的那一组。
+
+早先的修订把它放进新建的 `net/` 包组,理由是依赖 `undici` 使它不符合“零依赖”组。那个理解是错的:[优先使用依赖而非手写](../process/2026-07-26-dependencies-over-hand-rolling.zh.md) 记录了该章程约束的是 *harness* 依赖——util 不依赖它们,任何组才都能依赖 util——并不禁止外部包。真正需要去掉的是对 `dsh-launch-environment` 的依赖:解析只用到它的一个方法,于是改为声明结构化的 `EnvLookup`,启动器原样传入自己的快照即可。
+
+那次修订一并引入的插件也随之删除。它让某个组合可以把策略写进 `cordis.yml`,但没有任何随附 bundle 挂载它,因此启动器那条路径是唯一可达的——而它的 `Config` 是那条配置分支唯一的供给方,别处无从到达。
 
 **已安装的 dispatcher 按策略路由,而不是重新解析一遍环境。** `installGlobalProxy` 构造一个 `Agent`,其按 origin 调用的 `factory` 会询问 `proxyForUrl` 该 origin 的去向,并据此返回 `ProxyAgent` 或 undici 自带的默认客户端。undici 的 `EnvHttpProxyAgent` 曾是首选,但对这套策略是错的:没有 `HTTPS_PROXY` 时它会把 HTTPS agent 设为 HTTP agent,于是本包在拒绝某个 SOCKS 或畸形 URL 后本应保持直连的 scheme 仍会被隧道转发,而诊断却声称直连。让路由走同一个谓词,从构造上而非靠测试消除了这一类分歧。把策略发布到环境中的做法保留下来,但如今只服务那些拿不到策略对象的读者:Node 的 `proxyEnv` 选项,以及每个派生的子进程。
 
@@ -72,7 +76,7 @@ userland undici 能触及 Node 内置的 `fetch`,依赖于两者都会写入 l
 
 ## Testing
 
-`packages/net/http-proxy` 有 64 个测试,per-file 覆盖率 100%。解析覆盖优先级、`ALL_PROXY` 兜底、空值遮蔽、SOCKS 与畸形值诊断,以及 `mode: 'off'`;绕过匹配覆盖后缀、端口、两种 IPv6 写法,以及刻意不匹配的 CIDR 条目。安装驱动一个真实的 loopback 代理,断言绝对形式的请求确实抵达、被绕过的目标不抵达,且 dispose 会还原 dispatcher、策略与环境。
+`packages/util/http-proxy` 有 89 个测试,per-file 覆盖率 100%。解析覆盖优先级、`ALL_PROXY` 兜底、空值遮蔽、SOCKS 与畸形值诊断,以及只设 https 变量时 `http:` 保持直连;路由以结构化方式覆盖整个 loopback 网段,绕过匹配覆盖后缀、端口、两种 IPv6 写法,以及刻意不匹配的 CIDR 条目。安装驱动一个真实的 loopback 代理,断言绝对形式的请求确实抵达、被绕过的目标不抵达,且 dispose 会还原 dispatcher、策略与环境。
 
 `packages/web/web-fetch-http/tests/proxy.spec.ts` 断言了最关键的那个决定:经由代理时公网地址解析器完全不被调用,而被绕过的一跳仍恰好调用一次,且跨域重定向拒绝在代理路径上依然成立。
 

+ 0 - 1
AGENTS.md

@@ -25,7 +25,6 @@ packages/    @deepseek-ai/dsh-<pkg> workspaces at packages/<group>/<pkg>/
   lsp/         language-server capability
   skill/       skill provider registry + local impl + catalog/loader tool
   web/         web capability: Service Definition + search/fetch providers + tool Consumer
-  net/         outbound HTTP proxy policy
   compaction/     compaction capability + basic provider
   context/     request-context plugins
   subagent/    subagent capability: Service Definition + providers + delegation Consumers

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -2,5 +2,5 @@
 # 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 docs/config-catalog.md
-config-catalog.md: ff9d14558ea616fd51c9ece71750066f713d57d2
-config-catalog.zh.md: 8237fc7fef5316d6d6e40b396665b067605d7cbb
+config-catalog.md: d7644a1c7cd2e5c54d1609c0e86bf9f424e0db77
+config-catalog.zh.md: dc38567717f63ff82d32b5c5cba9962f260c5d82

+ 1 - 33
docs/config-catalog.md

@@ -924,39 +924,6 @@ export interface Config {
 
 Source: [`packages/host/webserver/src/index.ts:59`](../packages/host/webserver/src/index.ts)
 
-<a id="deepseek-aidsh-http-proxy"></a>
-
-## `@deepseek-ai/dsh-http-proxy`
-
-```ts config-catalog
-/** Composition-declared proxy settings; every field is optional and the environment outranks them. */
-export interface Config extends ProxyConfig {}
-
-/**
- * Proxy settings a composition may declare in `cordis.yml`. Real environment variables win over every
- * field here except `mode`, which governs whether the environment is consulted at all.
- */
-export interface ProxyConfig {
-  /**
-   * `env` (default) resolves from the environment and lets the fields below fill the gaps; `custom`
-   * does the same but is the honest label for a composition that supplies its own proxy; `off`
-   * ignores every source and keeps the harness's own requests direct.
-   *
-   * `off` governs requests this process issues. It does not strip proxy variables from the
-   * environment child tools inherit, because those belong to the user, not to the harness.
-   */
-  mode?: 'env' | 'custom' | 'off'
-  /** Proxy for `http:` origins when the environment supplies none. */
-  httpProxy?: string
-  /** Proxy for `https:` origins when the environment supplies none. */
-  httpsProxy?: string
-  /** Bypass list when the environment supplies none. {@link LOOPBACK_NO_PROXY} is merged in regardless. */
-  noProxy?: string
-}
-```
-
-Source: [`packages/net/http-proxy/src/index.ts:51`](../packages/net/http-proxy/src/index.ts)
-
 <a id="deepseek-aidsh-invariants"></a>
 
 ## `@deepseek-ai/dsh-invariants`
@@ -3564,6 +3531,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
 - `@deepseek-ai/dsh-experimental-webworker-runtime` ([`packages/experimental/webworker-runtime/src/index.ts`](../packages/experimental/webworker-runtime/src/index.ts))
 - `@deepseek-ai/dsh-home-paths` ([`packages/util/home-paths/src/index.ts`](../packages/util/home-paths/src/index.ts))
 - `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts))
+- `@deepseek-ai/dsh-http-proxy` ([`packages/util/http-proxy/src/index.ts`](../packages/util/http-proxy/src/index.ts))
 - `@deepseek-ai/dsh-launch-environment` ([`packages/util/launch-environment/src/index.ts`](../packages/util/launch-environment/src/index.ts))
 - `@deepseek-ai/dsh-llm-mock-server` ([`packages/test-support/llm-mock-server/src/index.ts`](../packages/test-support/llm-mock-server/src/index.ts))
 - `@deepseek-ai/dsh-loader-smoke` ([`packages/test-support/loader-smoke/src/index.ts`](../packages/test-support/loader-smoke/src/index.ts))

+ 1 - 33
docs/config-catalog.zh.md

@@ -926,39 +926,6 @@ export interface Config {
 
 来源:[`packages/host/webserver/src/index.ts:59`](../packages/host/webserver/src/index.ts)
 
-<a id="deepseek-aidsh-http-proxy"></a>
-
-## `@deepseek-ai/dsh-http-proxy`
-
-```ts config-catalog
-/** Composition-declared proxy settings; every field is optional and the environment outranks them. */
-export interface Config extends ProxyConfig {}
-
-/**
- * Proxy settings a composition may declare in `cordis.yml`. Real environment variables win over every
- * field here except `mode`, which governs whether the environment is consulted at all.
- */
-export interface ProxyConfig {
-  /**
-   * `env` (default) resolves from the environment and lets the fields below fill the gaps; `custom`
-   * does the same but is the honest label for a composition that supplies its own proxy; `off`
-   * ignores every source and keeps the harness's own requests direct.
-   *
-   * `off` governs requests this process issues. It does not strip proxy variables from the
-   * environment child tools inherit, because those belong to the user, not to the harness.
-   */
-  mode?: 'env' | 'custom' | 'off'
-  /** Proxy for `http:` origins when the environment supplies none. */
-  httpProxy?: string
-  /** Proxy for `https:` origins when the environment supplies none. */
-  httpsProxy?: string
-  /** Bypass list when the environment supplies none. {@link LOOPBACK_NO_PROXY} is merged in regardless. */
-  noProxy?: string
-}
-```
-
-来源:[`packages/net/http-proxy/src/index.ts:51`](../packages/net/http-proxy/src/index.ts)
-
 <a id="deepseek-aidsh-invariants"></a>
 
 ## `@deepseek-ai/dsh-invariants`
@@ -3565,6 +3532,7 @@ export interface Config {
 - `@deepseek-ai/dsh-experimental-webworker-runtime`([`packages/experimental/webworker-runtime/src/index.ts`](../packages/experimental/webworker-runtime/src/index.ts))
 - `@deepseek-ai/dsh-home-paths`([`packages/util/home-paths/src/index.ts`](../packages/util/home-paths/src/index.ts))
 - `@deepseek-ai/dsh-hook-protocol`([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts))
+- `@deepseek-ai/dsh-http-proxy`([`packages/util/http-proxy/src/index.ts`](../packages/util/http-proxy/src/index.ts))
 - `@deepseek-ai/dsh-launch-environment`([`packages/util/launch-environment/src/index.ts`](../packages/util/launch-environment/src/index.ts))
 - `@deepseek-ai/dsh-llm-mock-server`([`packages/test-support/llm-mock-server/src/index.ts`](../packages/test-support/llm-mock-server/src/index.ts))
 - `@deepseek-ai/dsh-loader-smoke`([`packages/test-support/loader-smoke/src/index.ts`](../packages/test-support/loader-smoke/src/index.ts))

+ 2 - 2
docs/module-graph.i18n.yaml

@@ -2,5 +2,5 @@
 # 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 docs/module-graph.md
-module-graph.md: 3ceb954a1fd97ba8ff1182c284295f00c7c78f1b
-module-graph.zh.md: faa95926b1d858cd7f8423d38b1d7dc89a7ef073
+module-graph.md: 049a2614195cd3b26f483a21093828912983ab10
+module-graph.zh.md: 50308aeed9cce61d2e89799cb924b985cd4ba2f4

+ 26 - 29
docs/module-graph.md

@@ -12,6 +12,7 @@ flowchart TD
     pkg_brand["brand"]
     pkg_deque["deque"]
     pkg_home_paths["home-paths"]
+    pkg_http_proxy["http-proxy"]
     pkg_launch_environment["launch-environment"]
     pkg_native_command["native-command"]
     pkg_output_retention["output-retention"]
@@ -264,9 +265,6 @@ flowchart TD
   subgraph group_mcp["packages/mcp"]
     pkg_mcp_client["mcp-client"]
   end
-  subgraph group_net["packages/net"]
-    pkg_http_proxy["http-proxy"]
-  end
   subgraph group_preset["packages/preset"]
     pkg_agent_presets["agent-presets"]
     pkg_persona["persona"]
@@ -367,6 +365,7 @@ flowchart TD
   pkg_brand --> pkg_invariants
   pkg_deque --> pkg_invariants
   pkg_home_paths --> pkg_invariants
+  pkg_http_proxy --> pkg_invariants
   pkg_launch_environment --> pkg_invariants
   pkg_native_command --> pkg_invariants
   pkg_output_retention --> pkg_invariants
@@ -409,6 +408,10 @@ flowchart TD
   pkg_skill --> pkg_invariants
   pkg_skill --> pkg_llm
   pkg_skill --> pkg_scope
+  pkg_web_fetch_http --> pkg_http_proxy
+  pkg_web_fetch_http --> pkg_invariants
+  pkg_web_fetch_http --> pkg_timeout
+  pkg_web_fetch_http --> pkg_web
   pkg_web_search_exa --> pkg_invariants
   pkg_web_search_exa --> pkg_launch_environment
   pkg_web_search_exa --> pkg_web
@@ -426,6 +429,8 @@ flowchart TD
   pkg_credentials_local --> pkg_home_paths
   pkg_credentials_local --> pkg_invariants
   pkg_credentials_local --> pkg_launch_environment
+  pkg_e2b --> pkg_http_proxy
+  pkg_e2b --> pkg_invariants
   pkg_experimental_inspector --> pkg_client_modules
   pkg_experimental_inspector --> pkg_host_webserver
   pkg_experimental_inspector --> pkg_invariants
@@ -448,20 +453,16 @@ flowchart TD
   pkg_lsp --> pkg_brand
   pkg_lsp --> pkg_invariants
   pkg_lsp --> pkg_llm
-  pkg_http_proxy --> pkg_invariants
-  pkg_http_proxy --> pkg_launch_environment
   pkg_storage_domain --> pkg_invariants
   pkg_storage_domain --> pkg_storage
   pkg_storage_json --> pkg_invariants
   pkg_storage_json --> pkg_storage
   pkg_storage_sqlite --> pkg_invariants
   pkg_storage_sqlite --> pkg_storage
+  pkg_subprocess --> pkg_http_proxy
+  pkg_subprocess --> pkg_invariants
   pkg_skill_badge --> pkg_invariants
   pkg_skill_badge --> pkg_skill
-  pkg_web_fetch_http --> pkg_http_proxy
-  pkg_web_fetch_http --> pkg_invariants
-  pkg_web_fetch_http --> pkg_timeout
-  pkg_web_fetch_http --> pkg_web
   pkg_spill --> pkg_brand
   pkg_spill --> pkg_invariants
   pkg_spill --> pkg_llm
@@ -477,8 +478,10 @@ flowchart TD
   pkg_code_runtime_worker_thread --> pkg_invariants
   pkg_code_runtime_worker_thread --> pkg_session
   pkg_code_runtime_worker_thread --> pkg_timeout
-  pkg_e2b --> pkg_http_proxy
-  pkg_e2b --> pkg_invariants
+  pkg_subprocess_e2b --> pkg_e2b
+  pkg_subprocess_e2b --> pkg_invariants
+  pkg_subprocess_e2b --> pkg_subprocess
+  pkg_subprocess_e2b --> pkg_timeout
   pkg_persona --> pkg_invariants
   pkg_persona --> pkg_system_prompt
   pkg_sandbox --> pkg_invariants
@@ -496,8 +499,9 @@ flowchart TD
   pkg_settings --> pkg_brand
   pkg_settings --> pkg_invariants
   pkg_settings --> pkg_session
-  pkg_subprocess --> pkg_http_proxy
-  pkg_subprocess --> pkg_invariants
+  pkg_subprocess_local --> pkg_invariants
+  pkg_subprocess_local --> pkg_subprocess
+  pkg_subprocess_local --> pkg_timeout
   pkg_session_snapshot --> pkg_http_proxy
   pkg_session_snapshot --> pkg_invariants
   pkg_session_snapshot --> pkg_session
@@ -514,10 +518,6 @@ flowchart TD
   pkg_fs --> pkg_sandbox
   pkg_spill_local --> pkg_invariants
   pkg_spill_local --> pkg_spill
-  pkg_subprocess_e2b --> pkg_e2b
-  pkg_subprocess_e2b --> pkg_invariants
-  pkg_subprocess_e2b --> pkg_subprocess
-  pkg_subprocess_e2b --> pkg_timeout
   pkg_message_feedback --> pkg_brand
   pkg_message_feedback --> pkg_invariants
   pkg_message_feedback --> pkg_llm
@@ -552,9 +552,6 @@ flowchart TD
   pkg_shell --> pkg_sandbox
   pkg_shell --> pkg_settings
   pkg_shell --> pkg_subprocess
-  pkg_subprocess_local --> pkg_invariants
-  pkg_subprocess_local --> pkg_subprocess
-  pkg_subprocess_local --> pkg_timeout
   pkg_workspace --> pkg_invariants
   pkg_workspace --> pkg_session
   pkg_workspace --> pkg_session_persistence
@@ -1396,6 +1393,7 @@ flowchart TD
 | [`brand`](../packages/util/brand) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`deque`](../packages/util/deque) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`home-paths`](../packages/util/home-paths) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`http-proxy`](../packages/util/http-proxy) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`launch-environment`](../packages/util/launch-environment) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`native-command`](../packages/util/native-command) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`output-retention`](../packages/util/output-retention) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1432,41 +1430,41 @@ flowchart TD
 | [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) |
 | [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
 | [`skill`](../packages/skill/skill) | `skill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
+| [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
 | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
 | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
 | [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) |
 | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
 | [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment) |
+| [`e2b`](../packages/e2b/e2b) | `e2b` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
-| [`http-proxy`](../packages/net/http-proxy) | `net` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment) |
 | [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) |
 | [`storage-json`](../packages/storage/storage-json) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) |
 | [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) |
+| [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
-| [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
 | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) |
 | [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
-| [`e2b`](../packages/e2b/e2b) | `e2b` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`persona`](../packages/preset/persona) | `preset` | [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt) |
 | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
 | [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
-| [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
+| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
 | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) |
-| [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
 | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
 | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
@@ -1475,7 +1473,6 @@ flowchart TD
 | [`session-stats`](../packages/session/session-stats) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
 | [`settings-file`](../packages/settings/settings-file) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
 | [`shell`](../packages/shell/shell) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`settings`](../packages/settings/settings), [`subprocess`](../packages/subprocess/subprocess) |
-| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`workspace`](../packages/workspace/workspace) | `workspace` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
 | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`credentials`](../packages/credentials/credentials), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
 | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
@@ -1503,7 +1500,7 @@ flowchart TD
 | [`bash-local`](../packages/shell/bash-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) |
 | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
@@ -1544,7 +1541,7 @@ flowchart TD
 | [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`agent`](../packages/core/agent), [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
 | [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
-| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
+| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
 | [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
 | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
 | [`shell-env`](../packages/shell/shell-env) | `shell` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-persistence`](../packages/session/session-persistence), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) |

+ 26 - 29
docs/module-graph.zh.md

@@ -14,6 +14,7 @@ flowchart TD
     pkg_brand["brand"]
     pkg_deque["deque"]
     pkg_home_paths["home-paths"]
+    pkg_http_proxy["http-proxy"]
     pkg_launch_environment["launch-environment"]
     pkg_native_command["native-command"]
     pkg_output_retention["output-retention"]
@@ -266,9 +267,6 @@ flowchart TD
   subgraph group_mcp["packages/mcp"]
     pkg_mcp_client["mcp-client"]
   end
-  subgraph group_net["packages/net"]
-    pkg_http_proxy["http-proxy"]
-  end
   subgraph group_preset["packages/preset"]
     pkg_agent_presets["agent-presets"]
     pkg_persona["persona"]
@@ -369,6 +367,7 @@ flowchart TD
   pkg_brand --> pkg_invariants
   pkg_deque --> pkg_invariants
   pkg_home_paths --> pkg_invariants
+  pkg_http_proxy --> pkg_invariants
   pkg_launch_environment --> pkg_invariants
   pkg_native_command --> pkg_invariants
   pkg_output_retention --> pkg_invariants
@@ -411,6 +410,10 @@ flowchart TD
   pkg_skill --> pkg_invariants
   pkg_skill --> pkg_llm
   pkg_skill --> pkg_scope
+  pkg_web_fetch_http --> pkg_http_proxy
+  pkg_web_fetch_http --> pkg_invariants
+  pkg_web_fetch_http --> pkg_timeout
+  pkg_web_fetch_http --> pkg_web
   pkg_web_search_exa --> pkg_invariants
   pkg_web_search_exa --> pkg_launch_environment
   pkg_web_search_exa --> pkg_web
@@ -428,6 +431,8 @@ flowchart TD
   pkg_credentials_local --> pkg_home_paths
   pkg_credentials_local --> pkg_invariants
   pkg_credentials_local --> pkg_launch_environment
+  pkg_e2b --> pkg_http_proxy
+  pkg_e2b --> pkg_invariants
   pkg_experimental_inspector --> pkg_client_modules
   pkg_experimental_inspector --> pkg_host_webserver
   pkg_experimental_inspector --> pkg_invariants
@@ -450,20 +455,16 @@ flowchart TD
   pkg_lsp --> pkg_brand
   pkg_lsp --> pkg_invariants
   pkg_lsp --> pkg_llm
-  pkg_http_proxy --> pkg_invariants
-  pkg_http_proxy --> pkg_launch_environment
   pkg_storage_domain --> pkg_invariants
   pkg_storage_domain --> pkg_storage
   pkg_storage_json --> pkg_invariants
   pkg_storage_json --> pkg_storage
   pkg_storage_sqlite --> pkg_invariants
   pkg_storage_sqlite --> pkg_storage
+  pkg_subprocess --> pkg_http_proxy
+  pkg_subprocess --> pkg_invariants
   pkg_skill_badge --> pkg_invariants
   pkg_skill_badge --> pkg_skill
-  pkg_web_fetch_http --> pkg_http_proxy
-  pkg_web_fetch_http --> pkg_invariants
-  pkg_web_fetch_http --> pkg_timeout
-  pkg_web_fetch_http --> pkg_web
   pkg_spill --> pkg_brand
   pkg_spill --> pkg_invariants
   pkg_spill --> pkg_llm
@@ -479,8 +480,10 @@ flowchart TD
   pkg_code_runtime_worker_thread --> pkg_invariants
   pkg_code_runtime_worker_thread --> pkg_session
   pkg_code_runtime_worker_thread --> pkg_timeout
-  pkg_e2b --> pkg_http_proxy
-  pkg_e2b --> pkg_invariants
+  pkg_subprocess_e2b --> pkg_e2b
+  pkg_subprocess_e2b --> pkg_invariants
+  pkg_subprocess_e2b --> pkg_subprocess
+  pkg_subprocess_e2b --> pkg_timeout
   pkg_persona --> pkg_invariants
   pkg_persona --> pkg_system_prompt
   pkg_sandbox --> pkg_invariants
@@ -498,8 +501,9 @@ flowchart TD
   pkg_settings --> pkg_brand
   pkg_settings --> pkg_invariants
   pkg_settings --> pkg_session
-  pkg_subprocess --> pkg_http_proxy
-  pkg_subprocess --> pkg_invariants
+  pkg_subprocess_local --> pkg_invariants
+  pkg_subprocess_local --> pkg_subprocess
+  pkg_subprocess_local --> pkg_timeout
   pkg_session_snapshot --> pkg_http_proxy
   pkg_session_snapshot --> pkg_invariants
   pkg_session_snapshot --> pkg_session
@@ -516,10 +520,6 @@ flowchart TD
   pkg_fs --> pkg_sandbox
   pkg_spill_local --> pkg_invariants
   pkg_spill_local --> pkg_spill
-  pkg_subprocess_e2b --> pkg_e2b
-  pkg_subprocess_e2b --> pkg_invariants
-  pkg_subprocess_e2b --> pkg_subprocess
-  pkg_subprocess_e2b --> pkg_timeout
   pkg_message_feedback --> pkg_brand
   pkg_message_feedback --> pkg_invariants
   pkg_message_feedback --> pkg_llm
@@ -554,9 +554,6 @@ flowchart TD
   pkg_shell --> pkg_sandbox
   pkg_shell --> pkg_settings
   pkg_shell --> pkg_subprocess
-  pkg_subprocess_local --> pkg_invariants
-  pkg_subprocess_local --> pkg_subprocess
-  pkg_subprocess_local --> pkg_timeout
   pkg_workspace --> pkg_invariants
   pkg_workspace --> pkg_session
   pkg_workspace --> pkg_session_persistence
@@ -1398,6 +1395,7 @@ flowchart TD
 | [`brand`](../packages/util/brand) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`deque`](../packages/util/deque) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`home-paths`](../packages/util/home-paths) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`http-proxy`](../packages/util/http-proxy) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`launch-environment`](../packages/util/launch-environment) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`native-command`](../packages/util/native-command) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`output-retention`](../packages/util/output-retention) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1434,41 +1432,41 @@ flowchart TD
 | [`session`](../packages/core/session) | `core` | [`scope`](../packages/core/scope) |
 | [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
 | [`skill`](../packages/skill/skill) | `skill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
+| [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
 | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
 | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
 | [`api-remotes`](../packages/api/remotes) | `api` | [`scope`](../packages/core/scope) |
 | [`attachment`](../packages/attachment/attachment) | `attachment` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
 | [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment) |
+| [`e2b`](../packages/e2b/e2b) | `e2b` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`experimental-inspector`](../packages/experimental/inspector) | `experimental` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`experimental-webworker-runtime`](../packages/experimental/webworker-runtime) | `experimental` | [`client-connection`](../packages/client/connection), [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`client-ui-directory-picker-browse`](../packages/client/ui-directory-picker-browse), [`client-ui-directory-picker-native`](../packages/client/ui-directory-picker-native), [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`host-frontend-static`](../packages/host/frontend-static) | `host` | [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`anonymous-user-id`](../packages/identity/anonymous-user-id) | `identity` | [`brand`](../packages/util/brand), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
-| [`http-proxy`](../packages/net/http-proxy) | `net` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment) |
 | [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) |
 | [`storage-json`](../packages/storage/storage-json) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) |
 | [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`invariants`](../packages/runtime-diagnostics/invariants), [`storage`](../packages/storage/storage) |
+| [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
-| [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
 | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`attachment-local`](../packages/attachment/attachment-local) | `attachment` | [`attachment`](../packages/attachment/attachment), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`app-boot`](../packages/boot/app-boot) | `boot` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`system-prompt`](../packages/core/system-prompt) |
 | [`code-runtime-worker-thread`](../packages/code-runtime/code-runtime-worker-thread) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
-| [`e2b`](../packages/e2b/e2b) | `e2b` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`persona`](../packages/preset/persona) | `preset` | [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt) |
 | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`session-log-deepseek`](../packages/session/session-log-deepseek) | `session` | [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
 | [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
-| [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
+| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
 | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) |
-| [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
 | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
 | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
@@ -1477,7 +1475,6 @@ flowchart TD
 | [`session-stats`](../packages/session/session-stats) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
 | [`settings-file`](../packages/settings/settings-file) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
 | [`shell`](../packages/shell/shell) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`settings`](../packages/settings/settings), [`subprocess`](../packages/subprocess/subprocess) |
-| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/runtime-diagnostics/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`workspace`](../packages/workspace/workspace) | `workspace` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
 | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`credentials`](../packages/credentials/credentials), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
 | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
@@ -1505,7 +1502,7 @@ flowchart TD
 | [`bash-local`](../packages/shell/bash-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) |
 | [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
@@ -1546,7 +1543,7 @@ flowchart TD
 | [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`agent`](../packages/core/agent), [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
 | [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
-| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`http-proxy`](../packages/net/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
+| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`http-proxy`](../packages/util/http-proxy), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
 | [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
 | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
 | [`shell-env`](../packages/shell/shell-env) | `shell` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-persistence`](../packages/session/session-persistence), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) |

+ 2 - 2
packages/README.i18n.yaml

@@ -2,5 +2,5 @@
 # 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 packages/README.md
-README.md: 032c18f301846b6e80c89b5f208e344d954b336b
-README.zh.md: 86c3ce9b19812bcb21c6bb7440ee96f5075922f4
+README.md: e1a729a6c80d8f8125e0d4c4b038dc631edd7a26
+README.zh.md: 754ad0fc44677afb243c3a32a0281f58ec3b3af2

+ 0 - 1
packages/README.md

@@ -53,7 +53,6 @@ Every package lives in exactly one group; new packages join existing groups, and
 | [`workflow/`](workflow/README.md) | Workflow seam, worker-thread engine, and model-facing `workflow`/`ralph` tools |
 | [`webhook/`](webhook/README.md) | Verified external events, trusted rules, and fire-and-forget Workspace Sessions |
 | [`web/`](web/README.md) | Web capability family: seam, search/fetch providers, model-facing web tools |
-| [`net/`](net/README.md) | Process-wide outbound transport policy: the HTTP proxy every request inherits |
 | [`attachment/`](attachment/README.md) | Durable attachment identity, validation, local content-addressed storage |
 | [`spill/`](spill/README.md) | Spill capability family: storage seam, local impl, tool-result spill policy |
 | [`todo/`](todo/README.md) | The model-facing `todo_write` tool |

+ 0 - 1
packages/README.zh.md

@@ -53,7 +53,6 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会
 | [`workflow/`](workflow/README.zh.md) | 工作流 seam、worker 线程引擎、面向模型的 `workflow`/`ralph` 工具 |
 | [`webhook/`](webhook/README.zh.md) | 已验证外部事件、受信规则与即发即弃 Workspace Session |
 | [`web/`](web/README.zh.md) | Web 能力系列:seam、搜索/获取提供方、面向模型的 Web 工具 |
-| [`net/`](net/README.zh.md) | 进程级出站传输策略:每个请求都会继承的 HTTP 代理 |
 | [`attachment/`](attachment/README.zh.md) | 持久附件标识、校验、本地内容寻址存储 |
 | [`spill/`](spill/README.zh.md) | spill 能力系列:存储 seam、本地实现、工具结果 spill 策略 |
 | [`todo/`](todo/README.zh.md) | 面向模型的 `todo_write` 工具 |

+ 1 - 1
packages/e2b/e2b/tsconfig.json

@@ -24,7 +24,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     },
     {
       "path": "../../util/launch-environment"

+ 1 - 1
packages/llm/llm-deepseek/tsconfig.json

@@ -57,7 +57,7 @@
       "path": "../../identity/anonymous-user-id"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/llm/llm-pi-ai/tsconfig.json

@@ -48,7 +48,7 @@
       "path": "../../util/timeout"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/mcp/mcp-client/tsconfig.json

@@ -36,7 +36,7 @@
       "path": "../../util/timeout"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 0 - 44
packages/net/README.md

@@ -1,44 +0,0 @@
----
-description: "Package map for the network group: process-wide outbound transport policy that applies to every request the harness makes."
-kind: "package-group"
----
-
-# net/ — outbound network transport
-
-English | [中文](README.zh.md)
-
-## Summary
-
-The `net/` group owns transport-level decisions that apply to every outbound request the harness makes, regardless of which capability makes it. Today that is one decision — whether a request goes through an HTTP proxy — and one package that owns it. The group exists because such a decision belongs to no single capability: an LLM adapter, a web-search backend, an MCP transport, and a telemetry exporter all inherit it without knowing about each other, and putting it inside any of their groups would make the other three depend backwards. These packages are not capability seams: transport policy has one implementation and one answer per process, so there is nothing to swap.
-
-## Table of Contents
-
-- [Packages](#packages)
-- [Related documentation](#related-documentation)
-- [Dev Note](#dev-note)
-
------
-
-<a id="packages"></a>
-## Packages
-
-| Package | Role | ctx key |
-|---|---|---|
-| [`http-proxy/`](http-proxy/README.md) | Resolves one outbound proxy policy and installs it as the process's global dispatcher | none — installed by the launcher |
-
------
-
-<a id="related-documentation"></a>
-## Related documentation
-
-- [Network proxy guide](../../docs/user/guide/network-proxy.md) — the user-facing page: what to export, and why a browser is proxied when a terminal is not.
-
-<a id="dev-note"></a>
-## Dev Note
-
-<details>
-<summary>Working context for maintainers — click to expand</summary>
-
-None.
-
-</details>

+ 0 - 44
packages/net/README.zh.md

@@ -1,44 +0,0 @@
----
-description: "network 组的包地图:适用于 Harness 发出的每一个请求的进程级出站传输策略。"
-kind: "package-group"
----
-
-# net/ — 出站网络传输
-
-[English](README.md) | 中文
-
-## 概述
-
-`net/` 组负责传输层面的决策——它们适用于 Harness 发出的每一个出站请求,与由哪个能力发起无关。目前这样的决策只有一个:请求是否经由 HTTP 代理;对应的包也只有一个。该组之所以存在,是因为这类决策不属于任何单一能力:LLM(大语言模型)适配器、web 搜索后端、MCP 传输与遥测导出器都在彼此无感的情况下继承它,而把它放进其中任何一组,都会让另外三组产生反向依赖。这些包不是能力接缝:传输策略每个进程只有一种实现、一个答案,没有可替换的对象。
-
-## 目录
-
-- [包](#packages)
-- [相关文档](#related-documentation)
-- [开发备注](#dev-note)
-
------
-
-<a id="packages"></a>
-## 包
-
-| 包 | 角色 | ctx key |
-|---|---|---|
-| [`http-proxy/`](http-proxy/README.zh.md) | 解析一份出站代理策略,并将其装为进程的全局 dispatcher | 无——由启动器安装 |
-
------
-
-<a id="related-documentation"></a>
-## 相关文档
-
-- [网络代理指南](../../docs/user/guide/network-proxy.zh.md)——面向用户的页面:需要导出什么,以及为什么浏览器走代理而终端不走。
-
-<a id="dev-note"></a>
-## 开发备注
-
-<details>
-<summary>面向维护者的工作上下文——点击展开</summary>
-
-无。
-
-</details>

+ 0 - 6
packages/net/http-proxy/README.i18n.yaml

@@ -1,6 +0,0 @@
-# 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 packages/net/http-proxy/README.md
-README.md: d07ca2fceced2adfa4d788462f8e04894d87c99a
-README.zh.md: 74d256a9d9f2b7d00ea01b5bf50a9b84279511fd

+ 0 - 88
packages/net/http-proxy/src/index.ts

@@ -1,88 +0,0 @@
-/**
- * Outbound HTTP proxy support for DeepSeek Harness.
- *
- * Node's built-in `fetch` ignores `HTTP_PROXY` and friends, so every harness request would connect
- * directly no matter what the user exported. This package resolves one policy from the launch
- * environment and installs it as undici's global dispatcher, which is what `fetch` resolves — so
- * LLM adapters, web search, MCP over HTTP, telemetry, and sandbox SDKs are all covered without
- * touching their code.
- *
- * The launcher installs the environment-derived policy for every profile. This plugin exists for a
- * composition that wants the policy in `cordis.yml` instead: it replaces the launcher's dispatcher
- * for as long as it is mounted, and restores it on disposal. It is not part of any shipped bundle,
- * so the default path installs exactly once.
- * @module @deepseek-ai/dsh-http-proxy
- */
-
-import type { Context } from '@deepseek-ai/cordis'
-import z from '@deepseek-ai/schemastery'
-import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
-import { installGlobalProxy } from './install.ts'
-import { describeProxyPolicy, resolveProxyPolicy, type ProxyConfig } from './policy.ts'
-
-export {
-  bypassesProxy,
-  isLoopbackHost,
-  describeProxyPolicy,
-  proxyForUrl,
-  resolveProxyPolicy,
-  DIRECT_POLICY,
-  LOOPBACK_NO_PROXY,
-  PROXY_ENV_NAMES,
-  type ProxyConfig,
-  type ProxyDiagnostic,
-  type ProxyPolicy,
-  type ProxyResolution,
-} from './policy.ts'
-
-export {
-  childProxyEnv,
-  createDispatcher,
-  createNodeHttpAgent,
-  currentProxyPolicy,
-  installGlobalProxy,
-  proxyUrlFor,
-} from './install.ts'
-
-/** Cordis plugin name. */
-export const name = 'http-proxy'
-
-/** Composition-declared proxy settings; every field is optional and the environment outranks them. */
-export interface Config extends ProxyConfig {}
-
-/** Schema for {@link Config}; a malformed proxy URL here fails the load rather than warning. */
-export const Config: z<Config> = z.object({
-  mode: z.union([z.const('env'), z.const('custom'), z.const('off')]).description(
-    'Whether to resolve from the environment (`env`, the default), do the same for a composition that supplies its own proxy (`custom`), or keep this process direct (`off`).',
-  ),
-  httpProxy: z.string().description('Proxy for `http:` origins when the environment supplies none.'),
-  httpsProxy: z.string().description('Proxy for `https:` origins when the environment supplies none.'),
-  noProxy: z.string().description('Bypass list when the environment supplies none; loopback is always added.'),
-})
-
-/**
- * Install the composition's proxy policy for as long as this plugin is mounted.
- *
- * A value this plugin's own `Config` supplied and that failed validation throws: it is the harness's
- * configuration surface, where a typo must be loud. A rejected *environment* value only warns,
- * because the same variable may have been exported for other tools and must not stop the agent from
- * starting.
- *
- * Installing IS this plugin's lifetime, so the disposer is returned as the startup effect rather than
- * registered through `ctx.effect()`: a caller awaiting the mount must observe an installed dispatcher,
- * which a separately-scheduled async effect would not guarantee.
- *
- * @param ctx - the mounting context, read for the launch environment snapshot and the logger.
- * @param config - composition-declared settings.
- * @returns the disposer restoring the previous dispatcher, policy, and environment.
- */
-export async function apply(ctx: Context, config: Config): Promise<() => Promise<void>> {
-  const { policy, diagnostics } = resolveProxyPolicy(launchEnvironmentOf(ctx), config)
-  const fatal = diagnostics.filter(diagnostic => diagnostic.origin.startsWith('config.'))
-  if (fatal.length > 0) {
-    throw new Error(`http-proxy: ${fatal.map(diagnostic => diagnostic.message).join('; ')}`)
-  }
-  for (const diagnostic of diagnostics) ctx.logger.warn('http-proxy: %s', diagnostic.message)
-  if (policy.source !== 'none') ctx.logger.debug('http-proxy: %s', describeProxyPolicy(policy))
-  return await installGlobalProxy(policy)
-}

+ 0 - 90
packages/net/http-proxy/tests/plugin.spec.ts

@@ -1,90 +0,0 @@
-import { afterEach, describe, expect, it } from 'vitest'
-import { Context } from '@deepseek-ai/cordis'
-import InvariantRegistry from '@deepseek-ai/dsh-invariants'
-import { getGlobalDispatcher } from 'undici'
-import { PROXY_ENV_NAMES } from '../src/policy.ts'
-import * as HttpProxy from '../src/index.ts'
-import * as HttpProxyInvariant from '../src/invariant.ts'
-
-const PROXY = 'http://127.0.0.1:7897'
-
-// `scripts/test-proxy-environment.ts` clears the machine's proxy variables before any suite runs,
-// so each test starts from nothing and only has to undo what `withEnv` set.
-afterEach(() => {
-  for (const name of PROXY_ENV_NAMES) Reflect.deleteProperty(process.env, name)
-})
-
-/** The launcher normally provides a snapshot; without one the plugin reads the process environment. */
-function withEnv(values: Record<string, string>): void {
-  for (const [name, value] of Object.entries(values)) process.env[name] = value
-}
-
-describe('http-proxy plugin', () => {
-  it('installs the configured policy and restores the dispatcher on disposal', async () => {
-    const before = getGlobalDispatcher()
-    const ctx = new Context()
-    const fiber = await ctx.plugin(HttpProxy, { httpProxy: PROXY })
-
-    expect(HttpProxy.currentProxyPolicy()?.httpProxy).toBe(PROXY)
-    expect(getGlobalDispatcher()).not.toBe(before)
-
-    await fiber.dispose()
-    expect(HttpProxy.currentProxyPolicy()).toBeUndefined()
-    expect(getGlobalDispatcher()).toBe(before)
-  })
-
-  it('lets a real environment variable outrank the configured proxy', async () => {
-    withEnv({ HTTP_PROXY: PROXY })
-    const ctx = new Context()
-    const fiber = await ctx.plugin(HttpProxy, { httpProxy: 'http://127.0.0.1:9' })
-    try {
-      expect(HttpProxy.currentProxyPolicy()?.httpProxy).toBe(PROXY)
-    } finally {
-      await fiber.dispose()
-    }
-  })
-
-  it('installs nothing under mode off, even with a proxy in the environment', async () => {
-    withEnv({ HTTP_PROXY: PROXY })
-    const before = getGlobalDispatcher()
-    const ctx = new Context()
-    const fiber = await ctx.plugin(HttpProxy, { mode: 'off' })
-    try {
-      expect(HttpProxy.currentProxyPolicy()?.source).toBe('none')
-      expect(getGlobalDispatcher()).toBe(before)
-    } finally {
-      await fiber.dispose()
-    }
-  })
-
-  it('reports an unusable environment value and connects directly instead of failing the load', async () => {
-    withEnv({ HTTP_PROXY: 'socks5://127.0.0.1:1080' })
-    const before = getGlobalDispatcher()
-    const ctx = new Context()
-    const fiber = await ctx.plugin(HttpProxy, {})
-    try {
-      expect(HttpProxy.currentProxyPolicy()?.source).toBe('none')
-      expect(getGlobalDispatcher()).toBe(before)
-    } finally {
-      await fiber.dispose()
-    }
-  })
-
-  it('fails the load when the composition itself declares an unusable proxy', async () => {
-    const ctx = new Context()
-    await expect(ctx.plugin(HttpProxy, { httpsProxy: 'socks5://127.0.0.1:1080' }))
-      .rejects.toThrow(/SOCKS proxy/)
-  })
-})
-
-describe('http-proxy invariant companion', () => {
-  it('reserves the package name against duplicate registration', async () => {
-    const ctx = new Context()
-    await ctx.plugin(InvariantRegistry)
-    await ctx.plugin(HttpProxyInvariant)
-
-    expect(() => {
-      ctx.invariants.register('@deepseek-ai/dsh-http-proxy', () => {})
-    }).toThrow(/already registered/)
-  })
-})

+ 1 - 1
packages/session/session-telemetry-otel/tsconfig.json

@@ -36,7 +36,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/subprocess/subprocess/tsconfig.json

@@ -18,7 +18,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     },
     {
       "path": "../../util/launch-environment"

+ 1 - 1
packages/test-support/loader-smoke/tsconfig.json

@@ -21,7 +21,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/test-support/session-snapshot/tsconfig.json

@@ -21,7 +21,7 @@
       "path": "../../core/session"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 2 - 2
packages/util/README.i18n.yaml

@@ -2,5 +2,5 @@
 # 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 packages/util/README.md
-README.md: f3ce5f7ad65e2faa75b6e1141b73c537c9fab601
-README.zh.md: 308f887f0a26b489f5796ff953127b364be956b9
+README.md: ab201cfbc33a51e1713804532f9c6d0a5003c19c
+README.zh.md: 751370c822a05da1b050d96bdcf9afbee1d0ea39

+ 2 - 1
packages/util/README.md

@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
 
 ## Summary
 
-The `util/` group gives capability packages shared mechanical primitives instead of duplicate implementations. It covers atomic writes, branded ids, deques, lossless JSON values, UUIDs, Harness-home paths, launch environments, native commands, output retention, time-zone canonicalization, and timeout handling. Every root entry here is a library: it registers no product service or event, and the consuming capability retains the business semantics.
+The `util/` group gives capability packages shared mechanical primitives instead of duplicate implementations. It covers atomic writes, branded ids, deques, lossless JSON values, UUIDs, Harness-home paths, launch environments, outbound proxy policy, native commands, output retention, time-zone canonicalization, and timeout handling. Every root entry here is a library: it registers no product service or event, and the consuming capability retains the business semantics.
 
 ## Table of Contents
 
@@ -31,6 +31,7 @@ Each package provides one primitive; open a package page for how to use it.
 | [`deque/`](deque/README.md) | Provides amortized constant-time queue operations with bounded vacant storage |
 | [`values/`](values/README.md) | Validates, snapshots, compares, and freezes lossless JSON-compatible values |
 | [`home-paths/`](home-paths/README.md) | Resolves the single Harness home and joins shared user-data paths |
+| [`http-proxy/`](http-proxy/README.md) | Resolves one outbound proxy policy and installs it for `fetch`, SDK agents, and spawned children |
 | [`launch-environment/`](launch-environment/README.md) | Frozen launch environment that remembers which layer supplied each value |
 | [`atomic-write/`](atomic-write/README.md) | Atomic file replacement and cross-process writer locking |
 | [`native-command/`](native-command/README.md) | Runs host-native commands directly, never through a shell string |

+ 2 - 1
packages/util/README.zh.md

@@ -9,7 +9,7 @@ kind: "package-group"
 
 ## 概述
 
-`util/` 组为能力包提供共享的机制原语,避免重复实现。它涵盖原子写入、品牌化 id、双端队列、无损 JSON 值、UUID、Harness home 路径、启动环境、原生命令、输出保留、时区规范化和超时处理。这里的每个根入口都是库:它不注册产品服务或事件,业务语义仍由消费它的能力负责。
+`util/` 组为能力包提供共享的机制原语,避免重复实现。它涵盖原子写入、品牌化 id、双端队列、无损 JSON 值、UUID、Harness home 路径、启动环境、出站代理策略、原生命令、输出保留、时区规范化和超时处理。这里的每个根入口都是库:它不注册产品服务或事件,业务语义仍由消费它的能力负责。
 
 ## 目录
 
@@ -31,6 +31,7 @@ kind: "package-group"
 | [`deque/`](deque/README.zh.md) | 提供摊销常数时间的队列操作和有界空闲存储 |
 | [`values/`](values/README.zh.md) | 校验、创建快照、比较和冻结无损 JSON 兼容值 |
 | [`home-paths/`](home-paths/README.zh.md) | 解析统一的 Harness 主目录并拼接共享的用户数据路径 |
+| [`http-proxy/`](http-proxy/README.zh.md) | 解析出唯一的出站代理策略,并为 `fetch`、SDK agent 与派生子进程安装它 |
 | [`launch-environment/`](launch-environment/README.zh.md) | 冻结的启动环境,记住每个值来自哪一层 |
 | [`atomic-write/`](atomic-write/README.zh.md) | 原子文件替换与跨进程写锁 |
 | [`native-command/`](native-command/README.zh.md) | 直接运行宿主原生命令,绝不拼 shell 字符串 |

+ 3 - 3
packages/net/README.i18n.yaml → packages/util/http-proxy/README.i18n.yaml

@@ -1,6 +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 packages/net/README.md
-README.md: 21aa924afd94e3232b67a3648ca236fd8396b437
-README.zh.md: 9dec251f6e5b546d6fc8308eb8c13754a65c562b
+#   pnpm run verify-translation-pairing --write packages/util/http-proxy/README.md
+README.md: d8eb4383c44deededef9d2ed7286789463417fc8
+README.zh.md: c5f82fe55656338b8581742c63e8c4078e42f8d0

+ 5 - 5
packages/net/http-proxy/README.md → packages/util/http-proxy/README.md

@@ -25,7 +25,7 @@ Node's built-in `fetch` ignores `HTTP_PROXY` and `HTTPS_PROXY`, so a harness beh
 <a id="use-this-package"></a>
 ## Use this package
 
-Nothing to mount. The `dsh` launcher resolves and installs the policy for every profile before the first plugin loads, so a user who exports `HTTPS_PROXY` is proxied everywhere. Mount the plugin only when a composition wants the policy declared in `cordis.yml` instead of the environment.
+Nothing to mount, and nothing to configure. The `dsh` launcher resolves and installs the policy for every profile before the first plugin loads, so a user who exports `HTTPS_PROXY` is proxied everywhere. This is a library rather than a plugin because transport policy has one answer per process: there is no second implementation to swap and no scope narrower than the process to give one.
 
 ### Writing a new outbound call
 
@@ -50,7 +50,7 @@ Loopback is always bypassed — `localhost`, the whole `127.0.0.0/8` range, `::1
 
 ### Failures
 
-A proxy value the package cannot use — a SOCKS or PAC URL, an unparseable string, an unsupported scheme — is reported and skipped, and the process connects directly. That variable may have been exported for other tools, so it must not stop the agent from starting. The same value supplied through this plugin's `Config` throws at load instead: that is the harness's own configuration surface, where a typo has to be loud.
+A proxy value the package cannot use — a SOCKS or PAC URL, an unparseable string, an unsupported scheme — is reported and skipped, and that scheme connects directly. The variable may have been exported for other tools, so it must not stop the agent from starting.
 
 -----
 
@@ -61,7 +61,7 @@ A proxy value the package cannot use — a SOCKS or PAC URL, an unparseable stri
 
 **One resolution, one matcher.** `proxyForUrl()` and the installed dispatcher must never disagree about a URL, or `dsh-web-fetch-http` would pin a connection the dispatcher meant to tunnel. The dispatcher is therefore an `Agent` whose per-origin `factory` calls `proxyForUrl()` itself, so there is no second parser to drift from the first. undici's `EnvHttpProxyAgent` cannot serve here: with no `HTTPS_PROXY` present it reuses the HTTP proxy for `https:`, which would tunnel a scheme this package keeps direct after refusing the URL the user named for it.
 
-**A child inherits the user's own values, and the resolved policy for what they left unset.** A scheme the user named in either casing reaches a child exactly as they wrote it, so a SOCKS proxy `curl` uses is never replaced by an HTTP one named for another scheme. A scheme they named in neither casing carries the resolved value instead, because otherwise the child's routing diverges from its parent's: Node's `NODE_USE_ENV_PROXY` reads neither `ALL_PROXY` nor a proxy that came from `cordis.yml`. The bypass list is always the resolved one — it only ever adds the loopback entries, so nothing the user wrote is lost. The cost of one routing answer for parent and child alike is that `curl` also sees the `https:` proxy this package derives from the HTTP one.
+**A child inherits the user's own values, and the resolved policy for what they left unset.** A scheme the user named in either casing reaches a child exactly as they wrote it, so a SOCKS proxy `curl` uses is never replaced by an HTTP one named for another scheme. A scheme they named in neither casing carries the resolved value instead, because otherwise the child's routing diverges from its parent's: Node's `NODE_USE_ENV_PROXY` does not read `ALL_PROXY`. The bypass list is always the resolved one — it only ever adds the loopback entries, so nothing the user wrote is lost. The cost of one routing answer for parent and child alike is that `curl` also sees the `https:` proxy this package derives from the HTTP one.
 
 ### Source map
 
@@ -69,7 +69,7 @@ A proxy value the package cannot use — a SOCKS or PAC URL, an unparseable stri
 |---|---|
 | `src/policy.ts` | Resolution, bypass matching, and redaction. Imports no transport, so it stays loadable where undici is absent. |
 | `src/install.ts` | The global dispatcher, the active-policy record, `createDispatcher`, and `childProxyEnv`. Imports undici dynamically. |
-| `src/index.ts` | Re-exports both halves and the optional Cordis plugin. |
+| `src/index.ts` | The package face: six functions and the types they use. |
 
 ### Bypass matching
 
@@ -101,7 +101,7 @@ No direct invalidation: the package contributes no request tokens and never muta
 
 These limits define when the package is a poor fit. They are current package constraints.
 
-- **No SOCKS, PAC, or operating-system proxy detection** — only `http(s)://` proxy URLs from the environment or configuration. A macOS or Windows system-proxy setting is not read, so a user who only toggled it in a proxy application must still export the variables; a SOCKS URL is reported and that scheme stays direct rather than borrowing another scheme's proxy.
+- **No SOCKS, PAC, or operating-system proxy detection** — only `http(s)://` proxy URLs from the environment. A macOS or Windows system-proxy setting is not read, so a user who only toggled it in a proxy application must still export the variables; a SOCKS URL is reported and that scheme stays direct rather than borrowing another scheme's proxy.
 - **No custom certificate authority** — a TLS-intercepting corporate proxy needs `NODE_EXTRA_CA_CERTS` set on the process before launch, which this package neither sets nor validates.
 - **A separate Node context honors the policy only on a new enough runtime** — a spawned child reads it through Node's `NODE_USE_ENV_PROXY` (22.21+, 24+), and the OTLP exporter's agent through Node's `proxyEnv` option (22.21+, **24.5+**). The engines range admits 22.19, 22.20, and 24.0–24.4, where those two paths stay direct. Such a context also matches bypass entries with Node's own `NO_PROXY` rules, which differ from this package's in their separators and IPv4-range support.
 - **A worker that executes model-authored code gets no proxy at all** — neither the `code-runtime` worker nor the `workflow` worker receives proxy configuration, so their own requests go direct. A proxy URL may carry `user:password`, and both run scripts the model wrote.

+ 5 - 5
packages/net/http-proxy/README.zh.md → packages/util/http-proxy/README.zh.md

@@ -25,7 +25,7 @@ Node 内置的 `fetch` 会忽略 `HTTP_PROXY` 与 `HTTPS_PROXY`,因此在代
 <a id="use-this-package"></a>
 ## 使用本包
 
-无需挂载。`dsh` 启动器会在第一个插件加载之前,为每个 profile 解析并安装策略,因此导出了 `HTTPS_PROXY` 的用户在所有位置都会走代理。只有当某个组合希望把策略写在 `cordis.yml` 而非环境中时,才需要挂载本插件。
+无需挂载,也无需配置。`dsh` 启动器会在第一个插件加载之前,为每个 profile 解析并安装策略,因此导出了 `HTTPS_PROXY` 的用户在所有位置都会走代理。本包是库而非插件,因为传输策略每个进程只有一个答案:没有第二个实现可替换,也没有比进程更窄的作用域可赋予。
 
 ### 编写新的出站调用
 
@@ -50,7 +50,7 @@ loopback 始终被绕过——`localhost`、整个 `127.0.0.0/8` 段、`::1`、`
 
 ### 失败处理
 
-本包无法使用的代理值——SOCKS 或 PAC URL、无法解析的字符串、不受支持的协议——会被报告并跳过,进程转为直连。该变量可能是用户为其他工具导出的,不应因此阻止 agent 启动。同样的值若通过本插件的 `Config` 提供,则在加载期抛出:那是 Harness 自己的配置面,笔误必须立刻响。
+本包无法使用的代理值——SOCKS 或 PAC URL、无法解析的字符串、不受支持的协议——会被报告并跳过,该 scheme 转为直连。该变量可能是用户为其他工具导出的,不应因此阻止 agent 启动。
 
 -----
 
@@ -61,7 +61,7 @@ loopback 始终被绕过——`localhost`、整个 `127.0.0.0/8` 段、`::1`、`
 
 **一次解析,一个匹配器。** `proxyForUrl()` 与已安装的 dispatcher 绝不能对同一个 URL 给出不同答案,否则 `dsh-web-fetch-http` 会把 dispatcher 本打算隧道转发的连接固定到某个地址上。因此该 dispatcher 是一个 `Agent`,其按 origin 调用的 `factory` 自身调用 `proxyForUrl()`,不存在可能与第一个解析器产生漂移的第二个解析器。undici 的 `EnvHttpProxyAgent` 在此无法胜任:没有 `HTTPS_PROXY` 时它让 `https:` 复用 HTTP 代理,于是本包在拒绝用户为该 scheme 指定的 URL 后本应保持直连的 scheme 仍会被隧道转发。
 
-**子进程继承用户自己的值,以及用户未设置部分的解析结果。** 用户以任一大小写指定过的 scheme,会以他们书写的形式原样传给子进程,因此用户为 `curl` 设置的 SOCKS 代理绝不会被替换成为其他 scheme 指定的 HTTP 代理。两种大小写都未指定的 scheme 则携带解析值,否则子进程的路由会与父进程分歧:Node 的 `NODE_USE_ENV_PROXY` 既不读 `ALL_PROXY`,也不读来自 `cordis.yml` 的代理。绕过列表始终采用解析结果——它只会追加 loopback 条目,用户写下的内容不会丢失。让父子进程只有一个路由答案的代价是:`curl` 也会看到本包由 HTTP 代理推导出的 `https:` 代理。
+**子进程继承用户自己的值,以及用户未设置部分的解析结果。** 用户以任一大小写指定过的 scheme,会以他们书写的形式原样传给子进程,因此用户为 `curl` 设置的 SOCKS 代理绝不会被替换成为其他 scheme 指定的 HTTP 代理。两种大小写都未指定的 scheme 则携带解析值,否则子进程的路由会与父进程分歧:Node 的 `NODE_USE_ENV_PROXY` 不读 `ALL_PROXY`。绕过列表始终采用解析结果——它只会追加 loopback 条目,用户写下的内容不会丢失。让父子进程只有一个路由答案的代价是:`curl` 也会看到本包由 HTTP 代理推导出的 `https:` 代理。
 
 ### 源码地图
 
@@ -69,7 +69,7 @@ loopback 始终被绕过——`localhost`、整个 `127.0.0.0/8` 段、`::1`、`
 |---|---|
 | `src/policy.ts` | 解析、绕过匹配与脱敏。不引入任何传输实现,因此在没有 undici 的环境中仍可加载。 |
 | `src/install.ts` | 全局 dispatcher、生效策略记录、`createDispatcher` 与 `childProxyEnv`。动态引入 undici。 |
-| `src/index.ts` | 重导出两半,以及可选的 Cordis 插件。 |
+| `src/index.ts` | 本包的对外面:六个函数及其使用的类型。 |
 
 ### 绕过匹配
 
@@ -101,7 +101,7 @@ loopback 始终被绕过——`localhost`、整个 `127.0.0.0/8` 段、`::1`、`
 
 这些限制界定了本包不适用的场景,属于当前的包级约束。
 
-- **不支持 SOCKS、PAC 或操作系统代理探测**——只接受来自环境或配置的 `http(s)://` 代理 URL。不会读取 macOS 或 Windows 的系统代理设置,因此仅在代理软件里拨了开关的用户仍须导出环境变量;SOCKS URL 会被报告,且该协议保持直连,不会借用另一协议的代理。
+- **不支持 SOCKS、PAC 或操作系统代理探测**——只接受来自环境的 `http(s)://` 代理 URL。不会读取 macOS 或 Windows 的系统代理设置,因此仅在代理软件里拨了开关的用户仍须导出环境变量;SOCKS URL 会被报告,且该协议保持直连,不会借用另一协议的代理。
 - **不支持自定义证书颁发机构**——做 TLS 拦截的企业代理需要在启动前为进程设置 `NODE_EXTRA_CA_CERTS`,本包既不设置也不校验它。
 - **独立的 Node 上下文只在足够新的运行时上遵循策略**——派生的子进程通过 Node 的 `NODE_USE_ENV_PROXY` 读取(22.21+、24+),OTLP 导出器的 agent 则通过 Node 的 `proxyEnv` 选项(22.21+、**24.5+**)。engines 范围允许 22.19、22.20 与 24.0–24.4,在这些版本上这两条路径保持直连。此类上下文还会按 Node 自己的 `NO_PROXY` 规则匹配绕过条目,其分隔符与 IPv4 区间处理与本包不同。
 - **执行模型编写代码的 worker 完全不获得代理**——`code-runtime` worker 与 `workflow` worker 都不接收代理配置,它们自身的请求直连。代理 URL 可能携带 `user:password`,而两者运行的都是模型写的脚本。

+ 3 - 6
packages/net/http-proxy/package.json → packages/util/http-proxy/package.json

@@ -8,7 +8,7 @@
   "repository": {
     "type": "git",
     "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
-    "directory": "packages/net/http-proxy"
+    "directory": "packages/util/http-proxy"
   },
   "type": "module",
   "main": "lib/index.js",
@@ -33,16 +33,13 @@
   "license": "MIT",
   "peerDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/dsh-launch-environment": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^"
   },
   "dependencies": {
-    "@deepseek-ai/schemastery": "workspace:^",
     "undici": "^8.10.0"
   },
   "devDependencies": {
     "@deepseek-ai/cordis": "workspace:^",
-    "@deepseek-ai/dsh-invariants": "workspace:^",
-    "@deepseek-ai/dsh-launch-environment": "workspace:^"
+    "@deepseek-ai/dsh-invariants": "workspace:^"
   }
 }

+ 37 - 0
packages/util/http-proxy/src/index.ts

@@ -0,0 +1,37 @@
+/**
+ * Outbound HTTP proxy support for DeepSeek Harness.
+ *
+ * Node's built-in `fetch` ignores `HTTP_PROXY` and friends, so every harness request would connect
+ * directly no matter what the user exported. This library resolves one policy from the launch
+ * environment and installs it as undici's global dispatcher, which is what `fetch` resolves — so
+ * LLM adapters, web search, MCP over HTTP, telemetry, and sandbox SDKs are all covered without
+ * touching their code.
+ *
+ * The launcher resolves and installs once, before the first plugin mounts. This is a library, not a
+ * plugin: transport policy has one answer per process, so there is nothing for a composition to
+ * mount, swap, or scope.
+ *
+ * Six exports, one per way a caller can need the policy: resolve it, install it, get a dispatcher
+ * for a request, get a `node:http` agent for an SDK that takes one, get a proxy URL for an SDK that
+ * takes that, and get the environment a spawned child needs.
+ * @module @deepseek-ai/dsh-http-proxy
+ */
+
+export {
+  proxyForUrl,
+  resolveProxyPolicy,
+  PROXY_ENV_NAMES,
+  type EnvLookup,
+  type ProxyDiagnostic,
+  type ProxyPolicy,
+  type ProxyResolution,
+} from './policy.ts'
+
+export {
+  childProxyEnv,
+  createDispatcher,
+  createNodeHttpAgent,
+  currentProxyPolicy,
+  installGlobalProxy,
+  proxyUrlFor,
+} from './install.ts'

+ 6 - 4
packages/net/http-proxy/src/install.ts → packages/util/http-proxy/src/install.ts

@@ -30,11 +30,13 @@ let inheritedProxyEnv: Readonly<Record<string, string | undefined>> | undefined
 /**
  * The policy governing this process's outbound requests.
  *
- * @returns the installed policy, or `undefined` when {@link installGlobalProxy} has not run. A caller
- *   that only needs to route a URL can treat `undefined` as {@link DIRECT_POLICY}.
+ * A caller that branches on the answer must hold this value and pass it back to
+ * {@link createDispatcher}: reading it twice lets an install or disposal land between the two reads.
+ *
+ * @returns the installed policy, or the direct one when {@link installGlobalProxy} has not run.
  */
-export function currentProxyPolicy(): ProxyPolicy | undefined {
-  return active
+export function currentProxyPolicy(): ProxyPolicy {
+  return active ?? DIRECT_POLICY
 }
 
 /**

+ 0 - 0
packages/net/http-proxy/src/invariant.ts → packages/util/http-proxy/src/invariant.ts


+ 23 - 76
packages/net/http-proxy/src/policy.ts → packages/util/http-proxy/src/policy.ts

@@ -8,7 +8,19 @@
  * @module @deepseek-ai/dsh-http-proxy/policy
  */
 
-import type { LaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
+/**
+ * The one thing resolution needs from an environment: a name in, the winning value out. The
+ * launcher's snapshot satisfies this structurally and is passed unchanged, so this module names no
+ * package to describe its input — and a test builds one from an object literal.
+ */
+export interface EnvLookup {
+  /**
+   * Resolve one variable name.
+   * @param name - the variable name.
+   * @returns the winning entry, or `undefined` when nothing supplies it.
+   */
+  get(name: string): { readonly value: string } | undefined
+}
 
 /**
  * Loopback entries merged into every policy's `noProxy`. A proxy that also serves the harness's own
@@ -60,7 +72,7 @@ export interface ProxyPolicy {
   /** The bypass list, already merged with {@link LOOPBACK_NO_PROXY}. Empty when nothing is bypassed. */
   readonly noProxy: string
   /** Which layer supplied the winning proxy URL; `env` when either field came from the environment. */
-  readonly source: 'env' | 'config' | 'none'
+  readonly source: 'env' | 'none'
 }
 
 /** A policy that proxies nothing. Callers that have not installed a policy resolve URLs against this. */
@@ -76,27 +88,6 @@ export interface ProxyDiagnostic {
   readonly message: string
 }
 
-/**
- * Proxy settings a composition may declare in `cordis.yml`. Real environment variables win over every
- * field here except `mode`, which governs whether the environment is consulted at all.
- */
-export interface ProxyConfig {
-  /**
-   * `env` (default) resolves from the environment and lets the fields below fill the gaps; `custom`
-   * does the same but is the honest label for a composition that supplies its own proxy; `off`
-   * ignores every source and keeps the harness's own requests direct.
-   *
-   * `off` governs requests this process issues. It does not strip proxy variables from the
-   * environment child tools inherit, because those belong to the user, not to the harness.
-   */
-  mode?: 'env' | 'custom' | 'off'
-  /** Proxy for `http:` origins when the environment supplies none. */
-  httpProxy?: string
-  /** Proxy for `https:` origins when the environment supplies none. */
-  httpsProxy?: string
-  /** Bypass list when the environment supplies none. {@link LOOPBACK_NO_PROXY} is merged in regardless. */
-  noProxy?: string
-}
 
 /** A resolved policy plus every candidate value that was rejected on the way to it. */
 export interface ProxyResolution {
@@ -116,7 +107,7 @@ export interface ProxyResolution {
  * @returns the trimmed value and the name that supplied it, or `undefined` when neither is set.
  */
 function readEnv(
-  env: LaunchEnvironmentSnapshot,
+  env: EnvLookup,
   lower: string,
 ): { value: string; name: string } | undefined {
   for (const name of [lower, lower.toUpperCase()]) {
@@ -292,50 +283,29 @@ export function bypassesProxy(noProxy: string, url: URL): boolean {
 /**
  * Resolve the outbound proxy policy for this process.
  *
- * Precedence is environment first, configuration second: a value the user exported wins over one a
- * composition declares, and `ALL_PROXY` backs both schemes. HTTPS falls back to the HTTP proxy last,
- * matching undici, so this function and the installed dispatcher never disagree about one URL.
+ * A scheme's own variable wins, then `ALL_PROXY`, then — for HTTPS only — the HTTP proxy, matching
+ * undici so this function and the installed dispatcher never disagree about one URL.
  *
- * @param env - the launch environment snapshot, whose own layering already prefers real variables over `.env` files.
- * @param config - optional composition-declared settings.
+ * @param env - the launch environment, whose own layering already prefers real variables over `.env` files.
  * @returns the policy to install plus every rejected candidate.
  */
-export function resolveProxyPolicy(
-  env: LaunchEnvironmentSnapshot,
-  config: ProxyConfig = {},
-): ProxyResolution {
+export function resolveProxyPolicy(env: EnvLookup): ProxyResolution {
   const diagnostics: ProxyDiagnostic[] = []
-  if (config.mode === 'off') return { policy: DIRECT_POLICY, diagnostics }
-
   const all = acceptProxyUrl(readEnv(env, 'all_proxy'), diagnostics)
   const allValue = all.kind === 'accepted' ? all.value : undefined
-  const configHttp = acceptProxyUrl(
-    config.httpProxy === undefined ? undefined : { value: config.httpProxy, name: 'config.httpProxy' },
-    diagnostics,
-  )
-  const configHttps = acceptProxyUrl(
-    config.httpsProxy === undefined ? undefined : { value: config.httpsProxy, name: 'config.httpsProxy' },
-    diagnostics,
-  )
-  const configHttpValue = configHttp.kind === 'accepted' ? configHttp.value : undefined
-  const configHttpsValue = configHttps.kind === 'accepted' ? configHttps.value : undefined
-
   const envHttp = acceptProxyUrl(readEnv(env, 'http_proxy'), diagnostics)
   const envHttps = acceptProxyUrl(readEnv(env, 'https_proxy'), diagnostics)
-  const httpProxy = resolveScheme(envHttp, allValue, configHttpValue)
+  const httpProxy = resolveScheme(envHttp, allValue)
   // HTTPS falls back to the HTTP proxy last, matching undici — but never past a value the user named
   // for HTTPS and this package refused.
-  const httpsProxy = resolveScheme(envHttps, allValue, configHttpsValue, httpProxy)
+  const httpsProxy = resolveScheme(envHttps, allValue, httpProxy)
   if (httpProxy === undefined && httpsProxy === undefined) return { policy: DIRECT_POLICY, diagnostics }
-
-  const noProxy = withLoopback(readEnv(env, 'no_proxy')?.value ?? config.noProxy)
-  const fromEnv = envHttp.kind === 'accepted' || envHttps.kind === 'accepted' || all.kind === 'accepted'
   return {
     policy: {
       ...httpProxy === undefined ? {} : { httpProxy },
       ...httpsProxy === undefined ? {} : { httpsProxy },
-      noProxy,
-      source: fromEnv ? 'env' : 'config',
+      noProxy: withLoopback(readEnv(env, 'no_proxy')?.value),
+      source: 'env',
     },
     diagnostics,
   }
@@ -357,26 +327,3 @@ export function proxyForUrl(policy: ProxyPolicy, url: URL): string | undefined {
   if (isLoopbackHost(url.hostname)) return undefined
   return bypassesProxy(policy.noProxy, url) ? undefined : proxy
 }
-
-/**
- * Render a policy for an operator, with any proxy password replaced. The username survives because it
- * identifies the account without granting it, which is what makes the line useful in a bug report.
- *
- * @param policy - a policy whose URLs {@link resolveProxyPolicy} already validated.
- * @returns one line naming the effective proxies and bypass list.
- */
-export function describeProxyPolicy(policy: ProxyPolicy): string {
-  if (policy.source === 'none') return 'no proxy (direct)'
-  const redact = (value: string): string => {
-    const url = new URL(value)
-    if (url.password !== '') url.password = '***'
-    return url.toString()
-  }
-  const parts = [
-    `http=${policy.httpProxy === undefined ? 'direct' : redact(policy.httpProxy)}`,
-    `https=${policy.httpsProxy === undefined ? 'direct' : redact(policy.httpsProxy)}`,
-    `no_proxy=${policy.noProxy}`,
-    `from=${policy.source}`,
-  ]
-  return parts.join(' ')
-}

+ 3 - 25
packages/net/http-proxy/tests/install.spec.ts → packages/util/http-proxy/tests/install.spec.ts

@@ -141,7 +141,9 @@ describe('installGlobalProxy', () => {
       await dispose()
       delete process.env.HTTP_PROXY
     }
-    expect(currentProxyPolicy()).toBeUndefined()
+    // With nothing installed the accessor still answers, so a caller never has to spell the direct
+    // case itself — that spelling is what let two reads disagree about one request.
+    expect(currentProxyPolicy()).toBe(DIRECT_POLICY)
   })
   it('keeps a scheme direct when the policy refused the proxy the user named for it', async () => {
     // What `HTTPS_PROXY=socks5://…` plus `HTTP_PROXY=http://p` resolves to: http proxied, https
@@ -287,30 +289,6 @@ describe('childProxyEnv', () => {
     }
   })
 
-  it('propagates a proxy that only a composition declared', async () => {
-    const saved = Object.fromEntries(PROXY_ENV_NAMES.map(name => [name, process.env[name]]))
-    for (const name of PROXY_ENV_NAMES) Reflect.deleteProperty(process.env, name)
-    const dispose = await installGlobalProxy({ ...proxyAll('example.com'), source: 'config' })
-    try {
-      // Nothing was exported, so every name carries the configured policy rather than being removed.
-      expect(childProxyEnv()).toEqual({
-        NODE_USE_ENV_PROXY: '1',
-        http_proxy: proxyUrl,
-        HTTP_PROXY: proxyUrl,
-        https_proxy: proxyUrl,
-        HTTPS_PROXY: proxyUrl,
-        no_proxy: 'example.com',
-        NO_PROXY: 'example.com',
-      })
-    } finally {
-      await dispose()
-      for (const [name, value] of Object.entries(saved)) {
-        if (value === undefined) Reflect.deleteProperty(process.env, name)
-        else process.env[name] = value
-      }
-    }
-  })
-
   it('keeps the outermost install\'s record of what the user exported across a nested one', async () => {
     const saved = Object.fromEntries(PROXY_ENV_NAMES.map(name => [name, process.env[name]]))
     for (const name of PROXY_ENV_NAMES) Reflect.deleteProperty(process.env, name)

+ 18 - 0
packages/util/http-proxy/tests/invariant.spec.ts

@@ -0,0 +1,18 @@
+import { describe, expect, it } from 'vitest'
+import { Context } from '@deepseek-ai/cordis'
+import InvariantRegistry from '@deepseek-ai/dsh-invariants'
+import * as HttpProxyInvariant from '../src/invariant.ts'
+
+describe('http-proxy invariant companion', () => {
+  it('registers its explained empty runtime invariant', async () => {
+    const ctx = new Context()
+    await ctx.plugin(InvariantRegistry)
+    const fiber = await ctx.plugin(HttpProxyInvariant)
+
+    expect(() => {
+      ctx.invariants.register('@deepseek-ai/dsh-http-proxy', () => {})
+    }).toThrow(/already registered/)
+    await fiber.dispose()
+    await ctx.fiber.dispose()
+  })
+})

+ 0 - 0
packages/net/http-proxy/tests/matcher-parity.spec.ts → packages/util/http-proxy/tests/matcher-parity.spec.ts


+ 10 - 56
packages/net/http-proxy/tests/policy.spec.ts → packages/util/http-proxy/tests/policy.spec.ts

@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
 import { createLaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
 import {
   bypassesProxy,
-  describeProxyPolicy,
   isLoopbackHost,
   proxyForUrl,
   resolveProxyPolicy,
@@ -82,6 +81,16 @@ describe('resolveProxyPolicy', () => {
     expect(policy.httpProxy).toBe(PROXY)
   })
 
+  it('leaves http direct when only the https variable is set', () => {
+    // The reverse of the HTTP-only case: the fallback runs one way, so naming only HTTPS proxies
+    // that scheme alone and every `http:` request stays direct.
+    const { policy } = resolveProxyPolicy(env({ HTTPS_PROXY: PROXY }))
+    expect(policy.httpProxy).toBeUndefined()
+    expect(policy.httpsProxy).toBe(PROXY)
+    expect(proxyForUrl(policy, new URL('http://example.com/'))).toBeUndefined()
+    expect(proxyForUrl(policy, new URL('https://example.com/'))).toBe(PROXY)
+  })
+
   it('backs both schemes with ALL_PROXY, which neither Node nor undici reads', () => {
     const { policy } = resolveProxyPolicy(env({ ALL_PROXY: PROXY }))
     expect(policy.httpProxy).toBe(PROXY)
@@ -147,36 +156,6 @@ describe('resolveProxyPolicy', () => {
     expect(diagnostics[0]?.message).toMatch(/unsupported ftp:\/\/ scheme/)
   })
 
-  it('lets configuration fill a gap the environment leaves', () => {
-    const { policy } = resolveProxyPolicy(env({}), { httpProxy: PROXY, noProxy: 'internal.example' })
-    expect(policy.httpProxy).toBe(PROXY)
-    expect(policy.httpsProxy).toBe(PROXY)
-    expect(policy.noProxy).toBe('internal.example,localhost,127.0.0.1,::1,[::1]')
-    expect(policy.source).toBe('config')
-  })
-
-  it('takes each scheme from its own configured field', () => {
-    const { policy } = resolveProxyPolicy(env({}), { httpProxy: PROXY, httpsProxy: OTHER })
-    expect(policy.httpProxy).toBe(PROXY)
-    expect(policy.httpsProxy).toBe(OTHER)
-    expect(policy.source).toBe('config')
-  })
-
-  it('lets the environment outrank configuration', () => {
-    const { policy } = resolveProxyPolicy(env({ HTTP_PROXY: PROXY }), { httpProxy: OTHER })
-    expect(policy.httpProxy).toBe(PROXY)
-    expect(policy.source).toBe('env')
-  })
-
-  it('names configuration as the origin of a rejected configured value', () => {
-    const { diagnostics } = resolveProxyPolicy(env({}), { httpsProxy: 'socks5://127.0.0.1:1080' })
-    expect(diagnostics[0]?.origin).toBe('config.httpsProxy')
-  })
-
-  it('ignores every source under mode off', () => {
-    const { policy } = resolveProxyPolicy(env({ HTTP_PROXY: PROXY }), { mode: 'off' })
-    expect(policy).toEqual(DIRECT_POLICY)
-  })
 })
 
 describe('bypassesProxy', () => {
@@ -249,28 +228,3 @@ describe('proxyForUrl', () => {
     expect(proxyForUrl(DIRECT_POLICY, new URL('https://example.com/'))).toBeUndefined()
   })
 })
-
-describe('describeProxyPolicy', () => {
-  it('names a direct policy', () => {
-    expect(describeProxyPolicy(DIRECT_POLICY)).toBe('no proxy (direct)')
-  })
-
-  it('replaces the password and keeps the username', () => {
-    const { policy } = resolveProxyPolicy(env({ HTTP_PROXY: 'http://alice:s3cret@proxy.example:8080' }))
-    const described = describeProxyPolicy(policy)
-    expect(described).toContain('alice')
-    expect(described).toContain('***')
-    expect(described).not.toContain('s3cret')
-  })
-
-  it('reports a scheme left direct', () => {
-    expect(describeProxyPolicy({ httpsProxy: PROXY, noProxy: '', source: 'env' })).toContain('http=direct')
-    expect(describeProxyPolicy({ httpProxy: PROXY, noProxy: '', source: 'env' })).toContain('https=direct')
-  })
-
-  it('resolves an https-only environment without an http proxy', () => {
-    const { policy } = resolveProxyPolicy(env({ HTTPS_PROXY: PROXY }))
-    expect(policy.httpProxy).toBeUndefined()
-    expect(policy.httpsProxy).toBe(PROXY)
-  })
-})

+ 0 - 0
packages/net/http-proxy/tsconfig.json → packages/util/http-proxy/tsconfig.json


+ 2 - 2
packages/web/web-fetch-http/src/provider.ts

@@ -10,7 +10,7 @@ import { WebError } from '@deepseek-ai/dsh-web'
 import type { WebFetchBody, WebFetchProvider, WebFetchRequest, WebFetchResult } from '@deepseek-ai/dsh-web'
 import { deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
 import type { Response } from 'undici'
-import { currentProxyPolicy, proxyForUrl, DIRECT_POLICY } from '@deepseek-ai/dsh-http-proxy'
+import { currentProxyPolicy, proxyForUrl } from '@deepseek-ai/dsh-http-proxy'
 import { isNonPublicIpLiteral, publicHttpNetwork } from './network.ts'
 import type { PublicAddress } from './network.ts'
 import { classifyContentType, decoderForCharset, isSameOrigin, parseCharset, validateFetchUrl } from './policy.ts'
@@ -132,7 +132,7 @@ export class HttpFetchProvider implements WebFetchProvider {
       // An IP literal the address checks would refuse never takes it. The proxy would resolve
       // nothing — the address is already stated — so the shortcut would spend the checks for
       // nothing and let a proxy on this machine reach the very service they keep out of reach.
-      const policy = currentProxyPolicy() ?? DIRECT_POLICY
+      const policy = currentProxyPolicy()
       if (proxyForUrl(policy, url) !== undefined && !isNonPublicIpLiteral(url.hostname)) {
         return await publicHttpNetwork.requestProxied(url, headers, signal, policy)
       }

+ 1 - 1
packages/web/web-fetch-http/tsconfig.json

@@ -27,7 +27,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/web/web-search-deepseek/tsconfig.json

@@ -39,7 +39,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/web/web-search-exa/tsconfig.json

@@ -27,7 +27,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/web/web-search-perplexity/tsconfig.json

@@ -27,7 +27,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 1 - 1
packages/workflow/workflow-worker-thread/tsconfig.json

@@ -42,7 +42,7 @@
       "path": "../../runtime-diagnostics/invariants"
     },
     {
-      "path": "../../net/http-proxy"
+      "path": "../../util/http-proxy"
     }
   ]
 }

+ 28 - 34
pnpm-lock.yaml

@@ -200,7 +200,7 @@ importers:
         version: link:../../packages/hooks/hooks-codex
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../packages/net/http-proxy
+        version: link:../../packages/util/http-proxy
       '@deepseek-ai/dsh-jobs-local':
         specifier: workspace:^
         version: link:../../packages/jobs/jobs-local
@@ -4667,7 +4667,7 @@ importers:
         version: link:../fs-e2b
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -6542,7 +6542,7 @@ importers:
         version: link:../../util/home-paths
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -6600,7 +6600,7 @@ importers:
         version: link:../../fs/fs
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -6891,7 +6891,7 @@ importers:
         version: link:../../attachment/attachment-local
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -6917,25 +6917,6 @@ importers:
         specifier: ^2026.7.4
         version: 2026.7.10(zod@4.4.3)
 
-  packages/net/http-proxy:
-    dependencies:
-      '@deepseek-ai/schemastery':
-        specifier: link:../../../vendor/schemastery
-        version: link:../../../vendor/schemastery
-      undici:
-        specifier: ^8.10.0
-        version: 8.10.0
-    devDependencies:
-      '@deepseek-ai/cordis':
-        specifier: workspace:^
-        version: link:../../../vendor/cordis
-      '@deepseek-ai/dsh-invariants':
-        specifier: workspace:^
-        version: link:../../runtime-diagnostics/invariants
-      '@deepseek-ai/dsh-launch-environment':
-        specifier: workspace:^
-        version: link:../../util/launch-environment
-
   packages/plan/plan-mode:
     dependencies:
       zod:
@@ -7802,7 +7783,7 @@ importers:
         version: link:../../feedback/command-feedback
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -9379,7 +9360,7 @@ importers:
         version: link:../../../vendor/cordis
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -9704,7 +9685,7 @@ importers:
         version: link:../../boot/app-boot
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -9747,7 +9728,7 @@ importers:
         version: link:../../compaction/compaction
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -9942,6 +9923,19 @@ importers:
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
 
+  packages/util/http-proxy:
+    dependencies:
+      undici:
+        specifier: ^8.10.0
+        version: 8.10.0
+    devDependencies:
+      '@deepseek-ai/cordis':
+        specifier: workspace:^
+        version: link:../../../vendor/cordis
+      '@deepseek-ai/dsh-invariants':
+        specifier: workspace:^
+        version: link:../../runtime-diagnostics/invariants
+
   packages/util/launch-environment:
     devDependencies:
       '@deepseek-ai/cordis':
@@ -10096,7 +10090,7 @@ importers:
         version: link:../../../vendor/cordis
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -10127,7 +10121,7 @@ importers:
         version: link:../../credentials/credentials-local
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -10155,7 +10149,7 @@ importers:
         version: link:../../../vendor/cordis
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -10177,7 +10171,7 @@ importers:
         version: link:../../../vendor/cordis
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -10413,7 +10407,7 @@ importers:
         version: link:../../test-support/agent-loop-testkit
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../net/http-proxy
+        version: link:../../util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../runtime-diagnostics/invariants
@@ -10603,7 +10597,7 @@ importers:
         version: link:../../packages/hooks/hooks-codex
       '@deepseek-ai/dsh-http-proxy':
         specifier: workspace:^
-        version: link:../../packages/net/http-proxy
+        version: link:../../packages/util/http-proxy
       '@deepseek-ai/dsh-invariants':
         specifier: workspace:^
         version: link:../../packages/runtime-diagnostics/invariants

+ 1 - 1
scripts/doc-budgets.manifest.json

@@ -1,5 +1,5 @@
 {
-  "AGENTS.md": 1960,
+  "AGENTS.md": 1950,
   "docs/AGENTS.md": 1320,
   "docs/architecture.md": 2400,
   "docs/cordis-primer.md": 600,

+ 1 - 1
scripts/test-proxy-environment.spec.ts

@@ -1,6 +1,6 @@
 import { readFileSync } from 'node:fs'
 import { describe, expect, it } from 'vitest'
-import { PROXY_ENV_NAMES } from '../packages/net/http-proxy/src/policy.ts'
+import { PROXY_ENV_NAMES } from '../packages/util/http-proxy/src/policy.ts'
 import { clearAmbientProxyEnv, TEST_PROXY_SETUP_FILE, vitestConfigFiles } from './test-proxy-environment.ts'
 
 describe('ambient proxy environment', () => {

+ 1 - 1
scripts/test-proxy-environment.ts

@@ -25,7 +25,7 @@
 
 import { globSync } from 'node:fs'
 import { resolve } from 'node:path'
-import { PROXY_ENV_NAMES } from '../packages/net/http-proxy/src/policy.ts'
+import { PROXY_ENV_NAMES } from '../packages/util/http-proxy/src/policy.ts'
 
 /** The flag a Node process reads before honoring the names above; ambient in the same way. */
 const NODE_PROXY_FLAG = 'NODE_USE_ENV_PROXY'

+ 1 - 1
scripts/verify-no-bare-dispatcher.ts

@@ -23,7 +23,7 @@ import ts from 'typescript'
 const root = resolve(import.meta.dirname, '..')
 
 /** The package that owns dispatcher construction; its own agents are the implementation. */
-export const DISPATCHER_OWNER = 'packages/net/http-proxy/'
+export const DISPATCHER_OWNER = 'packages/util/http-proxy/'
 
 /**
  * A comment carrying this marker states why the construction or option is exempt. It counts on the

+ 1 - 1
scripts/verify-package-readme-model-experience.ts

@@ -64,7 +64,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
   'packages/typert/registry': { kind: 'none', reason: 'Runtime type registry; consumers (cordis_inspect, wire faces, gates) own any model-visible projection of registry contents.' },
   'packages/typert/loader': { kind: 'none', reason: 'Loader integration only registers generated artifacts; consumers own any model-visible projection.' },
   'packages/e2b/e2b': { kind: 'none', reason: 'The shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects.' },
-  'packages/net/http-proxy': { kind: 'none', reason: 'Transport policy only: it changes how bytes reach the network and registers no prompt, schema, or result text.' },
+  'packages/util/http-proxy': { kind: 'none', reason: 'Transport policy only: it changes how bytes reach the network and registers no prompt, schema, or result text.' },
   'packages/client/hmr': { kind: 'none', reason: 'Browser-side UI plugin layer; registers nothing model-facing.' },
   'packages/client/modules': { kind: 'none', reason: 'Browser-side module-loading kernel machinery; registers nothing model-facing.' },
   'packages/test-support/client-runtime': { kind: 'none', reason: 'Browser-side test infrastructure (jsdom bench); registers nothing model-facing.' },

+ 0 - 1
scripts/verify-subsystem-pages.ts

@@ -20,7 +20,6 @@ export const GROUPS_WITHOUT_SUBSYSTEM_PAGE: Readonly<Record<string, string>> = {
   bundle: 'Composition patch carriers whose mounted packages own all runtime contracts.',
   examples: 'Non-product demonstration compositions whose mounted packages own all runtime contracts.',
   hooks: 'External hook-protocol bridges over existing interception points, not a new Harness service.',
-  net: 'Process-wide transport policy with no service, no seam, and no runtime vocabulary of its own; the user guide and the one package README own it.',
   sdk: 'Out-of-process protocol and client packages whose package READMEs own the SDK contracts.',
   util: 'Low-level primitives whose business semantics remain with their consuming subsystems.',
 }

+ 2 - 2
tsconfig.base.json

@@ -342,8 +342,8 @@
       "@deepseek-ai/dsh-hooks-claude-code/invariant": ["./packages/hooks/hooks-claude-code/src/invariant.ts"],
       "@deepseek-ai/dsh-hooks-codex": ["./packages/hooks/hooks-codex/src"],
       "@deepseek-ai/dsh-hooks-codex/invariant": ["./packages/hooks/hooks-codex/src/invariant.ts"],
-      "@deepseek-ai/dsh-http-proxy": ["./packages/net/http-proxy/src"],
-      "@deepseek-ai/dsh-http-proxy/invariant": ["./packages/net/http-proxy/src/invariant.ts"],
+      "@deepseek-ai/dsh-http-proxy": ["./packages/util/http-proxy/src"],
+      "@deepseek-ai/dsh-http-proxy/invariant": ["./packages/util/http-proxy/src/invariant.ts"],
       "@deepseek-ai/dsh-invariants/invariant": ["./packages/runtime-diagnostics/invariants/src/invariant.ts"],
       "@deepseek-ai/dsh-jobs": ["./packages/jobs/jobs/src"],
       "@deepseek-ai/dsh-jobs/invariant": ["./packages/jobs/jobs/src/invariant.ts"],

+ 1 - 1
tsconfig.host.json

@@ -128,7 +128,7 @@
     { "path": "./vendor/hmr" },
     { "path": "./vendor/logger-console" },
     { "path": "./packages/util/brand" },
-    { "path": "./packages/net/http-proxy" },
+    { "path": "./packages/util/http-proxy" },
     { "path": "./packages/util/launch-environment" },
     { "path": "./packages/util/native-command" },
     { "path": "./packages/util/home-paths" },