@karoc/dsh-proxy 0.1.4 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,10 +12,12 @@ plus a **Settings page** dedicated to model-provider proxying.
12
12
 
13
13
  It runs a loopback forward proxy inside the dsh host process, points the
14
14
  process's egress traffic at it (`HTTP(S)_PROXY` + `NODE_USE_ENV_PROXY`), and
15
- routes **only the hosts you select** through an optional upstream proxy
15
+ routes the hosts you select through an optional upstream proxy (or, with one
16
+ click, every host **except** the ones you select)
16
17
  (HTTP / HTTPS / SOCKS5, with optional Basic auth). Everything else stays
17
- direct. Model-provider hosts are derived from your dsh `settings.yaml`, and
18
- hosts observed in traffic are collected for you — both appear as checkboxes.
18
+ direct. Model-provider hosts are read LIVE from your running dsh
19
+ configuration, and hosts observed in traffic are collected for you — both
20
+ appear as checkboxes.
19
21
 
20
22
  Why an external plugin: the built-in settings pages don't cover outbound
21
23
  proxying, and adding one to a built-in package would be overwritten by the next
@@ -38,9 +40,16 @@ external **Model reasoning** page. It shows:
38
40
  button (a SOCKS5 handshake for SOCKS5, a `CONNECT` probe for HTTP; an HTTPS
39
41
  upstream is only checked for TCP reachability — its TLS handshake is not
40
42
  verified);
41
- - a **model providers** list — hosts read from your dsh `settings.yaml`
42
- (`llm-deepseek.baseURL`, `llm-pi-ai.providers.<n>.baseURL`, any `llm-*`
43
- namespace), labeled with the friendly display name where available;
43
+ - a **routing rule** with two readings of the same checkbox list — *only checked
44
+ hosts* (default) or *everything except checked*, for setups whose local proxy
45
+ already does its own routing;
46
+ - a **model providers** list — hosts read LIVE from your running dsh
47
+ configuration (`llm-deepseek.baseURL`, `llm-pi-ai.providers.<n>.baseURL`, any
48
+ `llm-*` namespace), labeled with the friendly display name where available.
49
+ The values come from the harness's own settings document, so the list follows
50
+ the running configuration wherever dsh keeps it; a harness that still has the
51
+ pre-0.2 `<DSH_HOME>/settings.yaml` is read as the fallback. Loopback
52
+ endpoints are omitted — they can never be proxied;
44
53
  - an **other observed hosts** list — hosts the proxy has seen in traffic
45
54
  (including `registry.npmjs.org` install/update traffic), persisted into
46
55
  `proxy.json` so they survive restarts;
@@ -69,13 +78,35 @@ dsh host process
69
78
  upstream proxy (HTTP/HTTPS/SOCKS5, optional Basic auth)
70
79
  ```
71
80
 
81
+ The checkboxes have two readings (**Routing rule** at the top of the list):
82
+
83
+ | Rule | Checked hosts | Everything else | Pick it when |
84
+ |---|---|---|---|
85
+ | *Only checked hosts* (default) | go upstream | connect directly | a few providers should use the upstream |
86
+ | *Everything except checked* | connect directly | go upstream | your local proxy already does the filtering |
87
+
88
+ ```
89
+ # a few providers through the upstream
90
+ rule: Only checked hosts checked: new-api.example, opencode.ai
91
+
92
+ # everything through the local proxy, except what must stay direct
93
+ rule: Everything except checked checked: api.internal.example (i.e. the direct list)
94
+ ```
95
+
96
+ Either way the list title follows the rule ("hosts going through the proxy" ↔
97
+ "hosts staying direct"), and the per-row status shows the RESULT — a host reads
98
+ *direct* while the upstream is off, whatever the checkboxes say.
99
+
72
100
  Safety rules (hard-coded, not configurable):
73
101
 
74
102
  - **loopback targets are always direct** — never sent to an upstream proxy;
75
103
  - an upstream pointing back at this very proxy is treated as **disabled**
76
104
  (self-loop guard);
77
- - the proxy itself uses raw `net`/`http`, so it can never route its own
78
- outbound connections back through itself.
105
+ - the proxy itself uses raw `net`/`http` through an explicit `Agent` that
106
+ ignores the environment proxy — even when the launch environment sets
107
+ `NODE_USE_ENV_PROXY=1` for dsh's own fetch, its outbound connections can never
108
+ be routed back through itself; a mismatched `Host` is replaced with the
109
+ request-target's authority (RFC 9112 §3.2.2).
79
110
 
80
111
  ### Config file
81
112
 
@@ -87,12 +118,17 @@ Safety rules (hard-coded, not configurable):
87
118
  {
88
119
  "upstream": { "enabled": false, "protocol": "http", "host": "", "port": 0, "username": "", "password": "" },
89
120
  "proxiedHosts": ["api.deepseek.com"],
90
- "knownHosts": ["api.deepseek.com", "registry.npmjs.org"]
121
+ "knownHosts": ["api.deepseek.com", "registry.npmjs.org"],
122
+ "listenPort": 45999
91
123
  }
92
124
  ```
93
125
 
94
- `knownHosts` is written by the plugin (observed traffic); `upstream` and
95
- `proxiedHosts` are written from the settings page. You can also hand-edit the
126
+ `knownHosts` is written by the plugin (observed traffic); `upstream`,
127
+ `proxiedHosts` and `listenPort` are written from the settings page
128
+ (`listenPort` takes effect after a restart; `0` means an ephemeral port).
129
+ `listenPort` is the address this plugin LISTENS on — it is not the upstream
130
+ address, which belongs in `upstream`; setting it to the upstream's own port
131
+ fails to bind and falls back to an ephemeral port. You can also hand-edit the
96
132
  file while dsh runs — it is re-read live.
97
133
 
98
134
  ## Install
@@ -128,6 +164,47 @@ dsh plugin --profile web update @karoc/dsh-proxy
128
164
 
129
165
  Then **restart `dsh web`** so the new client bundle loads.
130
166
 
167
+ ### dsh's own requests (model calls) ride this proxy automatically
168
+
169
+ The proxy has two kinds of clients, and they are wired differently:
170
+
171
+ - **Child processes** (bash, npm, pnpm, git, curl…) inherit the `*PROXY`
172
+ variables this plugin publishes — no setup.
173
+ - **dsh's own requests** (model calls, web fetch/search) are routed by the
174
+ harness's egress policy, which dsh resolves from the environment it was
175
+ LAUNCHED with, before any plugin loads. When that environment declared no
176
+ proxy, this plugin installs the same policy itself, pointing at its own
177
+ listener, once the listener is up (`src/egress.ts`). Nothing to configure.
178
+
179
+ Settings → Smoothly Proxy states which of the following is true:
180
+
181
+ - **routed, self-installed** — the normal case: model traffic follows the host
182
+ checkboxes above, per host. The listen port is an ephemeral one the plugin
183
+ picks itself: there is nothing to configure, and the diagnostics panel (collapsed
184
+ unless something needs attention) only reports it for debugging.
185
+ - **routed, launch environment** — your launcher declared
186
+ `HTTP_PROXY=http://127.0.0.1:<listenPort>`, so dsh installed the policy before
187
+ the plugin did; set `listenPort` (`proxy.json`) to a fixed port to keep that
188
+ address stable across restarts.
189
+ - **a launcher proxy owns egress** — the launch environment declared a
190
+ *different* proxy. This plugin deliberately does **not** layer over it (its own
191
+ proxy sends un-ticked hosts direct, which would silently bypass the launcher's
192
+ policy). Point the launcher at this proxy, or drop the variable, to use
193
+ per-host routing for dsh's own requests.
194
+ - **unavailable** — the harness policy package could not be resolved in this
195
+ composition (unusual layouts). The page then prints the exact `Environment=`
196
+ line to declare instead, and everything else keeps working.
197
+
198
+ ### Which deployment does what
199
+
200
+ | How dsh was started | What routes dsh's own requests | Page shows |
201
+ |---|---|---|
202
+ | `dsh web` from a terminal or a unit you own | this plugin (it installs the policy itself) | *routing through this plugin* |
203
+ | dsh-desktop shell | the shell's proxy — it sets `*PROXY` before spawning dsh | *the launch environment declares a different proxy* (by design: layering ours over it would bypass the shell's policy) |
204
+ | a launcher that sets `HTTP_PROXY=http://127.0.0.1:<port>` itself | dsh, before any plugin loads | *routing through this plugin (named by the launch environment)* |
205
+ | a headless profile | nothing — this plugin is not loaded without the web server | — |
206
+ | a build that does not expose `@deepseek-ai/dsh-http-proxy` | nothing; the plugin cannot join | *could not take over automatically* + the line to paste |
207
+
131
208
  ### Removing
132
209
 
133
210
  ```sh
@@ -152,8 +229,11 @@ src/client/index.ts # client apply: register settings.section (id dsh-proxy)
152
229
  src/client/ProxySection.tsx # the settings page (upstream card + host lists)
153
230
  src/client/styles.ts # design-token styles (--dsw-alias-*) + injection
154
231
  src/client/locales.ts # en/zh copy
155
- scripts/*.spec.mjs # behavioral tests: proxy-core.ts (13 scenarios) plus
232
+ scripts/*.spec.mjs # behavioral tests: proxy-core.ts plus
156
233
  # host route / client boot / cordis mount — no network
234
+ scripts/packed-artifact.spec.mjs
235
+ # what npm PUBLISHES: member set, in-tarball bundles,
236
+ # installed-layout load + egress self-install
157
237
  ```
158
238
 
159
239
  ### The /proxy/api route
@@ -161,10 +241,27 @@ scripts/*.spec.mjs # behavioral tests: proxy-core.ts (13 scenarios) plus
161
241
  The host half serves the settings page over a same-origin HTTP route (the
162
242
  built-in `/api` prefix is reserved for the gateway, so this uses `/proxy/api`):
163
243
 
164
- - `GET /proxy/api` → `{ ok, upstream, proxiedHosts, knownHosts, hosts, providers, port }`
165
- - `POST /proxy/api` `{ op: 'save', upstream, proxiedHosts }` → sanitized + persisted config
244
+ - `GET /proxy/api` → `{ ok, upstream, proxiedHosts, knownHosts, hosts, providers,
245
+ port, routeMode, … }` plus the egress diagnostics the panel reports
246
+ - `POST /proxy/api` `{ op: 'save', upstream, proxiedHosts, listenPort?, routeMode? }`
247
+ → sanitized + persisted config
166
248
  - `POST /proxy/api` `{ op: 'test', upstream }` → `{ ok, detail }`
167
249
  - `POST /proxy/api` `{ op: 'persist' }` → merge observed hosts into `knownHosts`
250
+ - `POST /proxy/api` `{ op: 'forget' }` → clear the observed-host list (live set +
251
+ `knownHosts`)
252
+
253
+ **It sits behind the same operator-session gate as the harness's own `/api`**
254
+ (401/403 without the browser cookie) — the route exposes the upstream address and
255
+ credentials and can rewrite the routing, and on a shared machine any local process
256
+ can reach 127.0.0.1. A CLI check therefore has to carry that cookie:
257
+
258
+ ```sh
259
+ COOKIE=$(node -e "import('./scripts/lib/auth-cookie.mjs').then(m=>{const c=m.mintBrowserCookie();console.log(c.name+'='+c.value)})")
260
+ curl -s -H "cookie: $COOKIE" http://127.0.0.1:3080/proxy/api | head -c 200
261
+ ```
262
+
263
+ In a composition without the harness connection service there is no credential
264
+ model to consult, so the route stays open as before.
168
265
 
169
266
  ## Build
170
267
 
@@ -172,7 +269,7 @@ built-in `/api` prefix is reserved for the gateway, so this uses `/proxy/api`):
172
269
  pnpm install
173
270
  pnpm bundle # emits lib/index.js + lib/client.js
174
271
  pnpm typecheck # tsc --noEmit
175
- pnpm test # node --test: proxy-core (13 scenarios) + host-route /
272
+ pnpm test # node --test: proxy-core + host-route /
176
273
  # client-boot / cordis-mount specs (no network)
177
274
  pnpm release:check # release gate: docs/changelog/tag/tree/build/registry must all pass
178
275
  pnpm publish # runs the gate (prepack/prepublishOnly), then postpublish verifies the live release
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **思磨力代理插件**(**Smoothly Proxy** / 思磨力)是一个**外部** DeepSeek Harness 插件:为**模型提供方**提供带**按主机路由**的**正向代理**,并附带一个专职设置页。
10
10
 
11
- 它在 dsh host 进程内启动一个 loopback 正向代理,把进程的出站流量指向它(`HTTP(S)_PROXY` + `NODE_USE_ENV_PROXY`),**只把你在设置页勾选的主机**转发到可选的上游代理(HTTP / HTTPS / SOCKS5,支持可选 Basic 认证),其余全部直连。模型提供方主机从你的 dsh `settings.yaml` 读取,流量中观测到的主机也会被收集——两者都以复选框形式出现。
11
+ 它在 dsh host 进程内启动一个 loopback 正向代理,把进程的出站流量指向它(`HTTP(S)_PROXY` + `NODE_USE_ENV_PROXY`),把你在设置页勾选的主机转发到可选的上游代理(也可以一键切换成"**除勾选外全部走代理**")(HTTP / HTTPS / SOCKS5,支持可选 Basic 认证),其余全部直连。模型提供方主机**实时**读自正在运行的 dsh 配置,流量中观测到的主机也会被收集——两者都以复选框形式出现。
12
12
 
13
13
  为什么要做成外部插件:内置设置页不覆盖出站代理,而给内置包加一页会在官方下次发布时被覆盖。本包作为可安装的 **bundle** 交付,从不触碰仓库源码,官方更新无法覆盖它。
14
14
 
@@ -19,7 +19,8 @@
19
19
  一个新的设置项 **「思磨力代理 / Smoothly Proxy」**(导航顺序 25),排在内置 **Models** 页以及(若装了该插件)外部 **Model reasoning** 页之后,包含:
20
20
 
21
21
  - **上游代理卡片**:启用开关、协议选择(HTTP / HTTPS / SOCKS5)、主机、端口、可选用户名/密码,以及**测试连接**按钮(SOCKS5 上游走 SOCKS5 握手、HTTP 上游走 `CONNECT` 探测;HTTPS 上游只验证 TCP 可达——不校验其 TLS 握手);
22
- - **模型提供方**列表——从你的 dsh `settings.yaml` 读取的主机(`llm-deepseek.baseURL`、`llm-pi-ai.providers.<n>.baseURL`、任意 `llm-*` 命名空间),有友好显示名时以显示名标注;
22
+ - **代理规则**开关——同一份勾选列表的两种读法:*只代理勾选的主机*(默认)或*除勾选外全部走代理*,适合本机代理自己已经做了分流的场景;
23
+ - **模型提供方**列表——**实时**读自正在运行的 dsh 配置的主机(`llm-deepseek.baseURL`、`llm-pi-ai.providers.<n>.baseURL`、任意 `llm-*` 命名空间),有友好显示名时以显示名标注。取值来自 harness 自己的 settings 文档,因此列表跟着运行中的配置走,与 dsh 把文档放在哪里无关;仍在用 0.2 之前 `<DSH_HOME>/settings.yaml` 的 harness 会回退读那个文件。loopback 端点不会出现——它们永远不可能被代理;
23
24
  - **其它已观测主机**列表——代理在流量中见过的主机(含 `registry.npmjs.org` 等安装/更新流量),持久化进 `proxy.json` 以跨重启保留;
24
25
  - **搜索框**——输入时按主机 / 名称过滤上面两个列表。
25
26
 
@@ -44,11 +45,28 @@ dsh host 进程
44
45
  上游代理(HTTP/HTTPS/SOCKS5,可选 Basic 认证)
45
46
  ```
46
47
 
48
+ 同一份勾选列表有两种读法(列表上方的**代理规则**):
49
+
50
+ | 规则 | 勾选的主机 | 其余主机 | 什么时候用 |
51
+ |---|---|---|---|
52
+ | *只代理勾选的主机*(默认) | 走上游 | 直连 | 只有少数提供方需要走上游 |
53
+ | *除勾选外全部走代理* | 直连 | 走上游 | 你的本机代理已经做了分流 |
54
+
55
+ ```
56
+ # 少数提供方走上游
57
+ 规则:只代理勾选的主机 勾选:new-api.example、opencode.ai
58
+
59
+ # 全部走本机代理,只留必须直连的
60
+ 规则:除勾选外全部走代理 勾选:api.internal.example(即"直连白名单")
61
+ ```
62
+
63
+ 两种读法下列表标题都会跟着变(「走代理的主机」↔「直连的主机」),每行的状态显示的是**实际结果**——上游没启用时,勾了也显示"直连"。
64
+
47
65
  安全规则(硬编码,不可配置):
48
66
 
49
67
  - **loopback 目标恒直连**——绝不发给上游代理;
50
68
  - 指向本代理自身的上游被当作**禁用**(防自环保护);
51
- - 代理自身只用裸 `net`/`http`,所以永远不可能把自己出站连接再路由回自己。
69
+ - 代理自身只用裸 `net`/`http`,且走一个**显式 `Agent`**、无视环境代理——即使启动环境为 dsh 自己的 fetch 设了 `NODE_USE_ENV_PROXY=1`,它的出站连接也永远不会被路由回自己;错配的 `Host` 会被替换为请求目标的 authority(RFC 9112 §3.2.2)。
52
70
 
53
71
  ### 配置文件
54
72
 
@@ -60,11 +78,12 @@ dsh host 进程
60
78
  {
61
79
  "upstream": { "enabled": false, "protocol": "http", "host": "", "port": 0, "username": "", "password": "" },
62
80
  "proxiedHosts": ["api.deepseek.com"],
63
- "knownHosts": ["api.deepseek.com", "registry.npmjs.org"]
81
+ "knownHosts": ["api.deepseek.com", "registry.npmjs.org"],
82
+ "listenPort": 45999
64
83
  }
65
84
  ```
66
85
 
67
- `knownHosts` 由插件写入(观测到的流量);`upstream` 与 `proxiedHosts` 由设置页写入。你也可以在 dsh 运行中手改该文件——它是实时重读的。
86
+ `knownHosts` 由插件写入(观测到的流量);`upstream`、`proxiedHosts` 与 `listenPort` 由设置页写入(`listenPort` 重启后生效;`0` 表示随机端口)。`listenPort` 是本插件**监听**的地址,**不是上游地址**——上游地址属于 `upstream`;把它填成上游自己的端口会导致绑定失败并回退到临时端口。你也可以在 dsh 运行中手改该文件——它是实时重读的。
68
87
 
69
88
  ## 安装
70
89
 
@@ -99,6 +118,30 @@ dsh plugin --profile web update @karoc/dsh-proxy
99
118
 
100
119
  然后**重启 `dsh web`** 以加载新客户端 bundle。
101
120
 
121
+ ### dsh 自己的请求(模型调用)会自动走本代理
122
+
123
+ 本代理有两类客户端,接线方式不同:
124
+
125
+ - **子进程**(bash、npm、pnpm、git、curl…)继承本插件写进环境的 `*PROXY` 变量——无需配置;
126
+ - **dsh 自己的请求**(模型调用、web fetch/search)由 harness 的出网策略决定,而该策略是 dsh **在启动时、早于任何插件**从启动环境解析的。当启动环境没有声明代理时,本插件会在自己的监听就绪后**自己装上同一条策略**并指向本端口(见 `src/egress.ts`)——无需任何配置。
127
+
128
+ 设置 → 思磨力代理会明说当前属于哪一种:
129
+
130
+ - **已路由(自装)**——常态:模型流量按上面的主机勾选逐台分流。监听端口是插件自己挑的临时端口:**没有任何需要配置的东西**,诊断面板(默认折叠,只在需要你注意时才展开)只是把端口报出来便于排查;
131
+ - **已路由(启动环境)**——你的启动环境声明了 `HTTP_PROXY=http://127.0.0.1:<listenPort>`,dsh 在插件之前就装好了策略;把 `listenPort`(`proxy.json`)固定成某个端口,这个地址才能跨重启保持稳定;
132
+ - **出网归启动环境的另一个代理管**——启动环境声明了**别的**代理。本插件**故意不覆盖**它(本插件的代理会把未勾选主机直连,覆盖上去等于静默绕过启动方的策略)。想让 dsh 自己的请求也按本插件分流,就把启动环境指向本代理,或删掉那个变量;
133
+ - **不可用**——当前组合里解析不到 harness 的策略包(少见的布局)。页面会打印可直接粘贴的 `Environment=` 行作为兜底,其它功能不受影响。
134
+
135
+ ### 不同部署下会发生什么
136
+
137
+ | dsh 是怎么启动的 | dsh 自己的请求归谁 | 页面显示 |
138
+ |---|---|---|
139
+ | 终端或你自己的 unit 里跑 `dsh web` | 本插件(自己装策略) | *已由本插件接管* |
140
+ | dsh-desktop 桌面壳 | 壳的代理——它在 spawn dsh 之前就设好 `*PROXY` | *启动环境声明了别的代理*(有意为之:覆盖上去会绕过壳的策略) |
141
+ | 启动器自己设了 `HTTP_PROXY=http://127.0.0.1:<port>` | dsh(早于任何插件装载) | *已由本插件接管(启动环境指名)* |
142
+ | headless profile | 没有——没有 web server 就不加载本插件 | — |
143
+ | 不暴露 `@deepseek-ai/dsh-http-proxy` 的构建 | 没有,插件接不进去 | *未能自动接管* + 可直接粘贴的那行 |
144
+
102
145
  ### 卸载
103
146
 
104
147
  ```sh
@@ -120,18 +163,31 @@ src/client/index.ts # client apply:注册 settings.section(id dsh-proxy)
120
163
  src/client/ProxySection.tsx # 设置页(上游卡片 + 主机列表)
121
164
  src/client/styles.ts # design-token 样式(--dsw-alias-*)+ 注入
122
165
  src/client/locales.ts # en/zh 文案
123
- scripts/*.spec.mjs # 行为测试:proxy-core.ts(13 场景)+ host route / client
166
+ scripts/*.spec.mjs # 行为测试:proxy-core.ts + host route / client
124
167
  # boot / cordis mount,全程无外部网络
168
+ scripts/packed-artifact.spec.mjs
169
+ # 真正被 npm 发出去的那份:成员清单、包内产物、
170
+ # 以"已安装布局"加载 + 自装出网策略
125
171
  ```
126
172
 
127
173
  ### /proxy/api 路由
128
174
 
129
175
  host 半区用同源 HTTP 路由为设置页供数(内置 `/api` 前缀被 gateway 占用,故用 `/proxy/api`):
130
176
 
131
- - `GET /proxy/api` → `{ ok, upstream, proxiedHosts, knownHosts, hosts, providers, port }`
132
- - `POST /proxy/api` `{ op: 'save', upstream, proxiedHosts }` → 清洗后持久化的配置
177
+ - `GET /proxy/api` → `{ ok, upstream, proxiedHosts, knownHosts, hosts, providers, port, routeMode, … }` 以及面板用到的出网诊断字段
178
+ - `POST /proxy/api` `{ op: 'save', upstream, proxiedHosts, listenPort?, routeMode? }` → 清洗后持久化的配置
133
179
  - `POST /proxy/api` `{ op: 'test', upstream }` → `{ ok, detail }`
134
180
  - `POST /proxy/api` `{ op: 'persist' }` → 把观测主机并入 `knownHosts`
181
+ - `POST /proxy/api` `{ op: 'forget' }` → 清空观测列表(会话内集合 + `knownHosts`)
182
+
183
+ **它与 harness 自带的 `/api` 走同一道门**(没有浏览器会话 cookie 时返回 401/403)——这条路由会暴露上游地址与凭据、还能改写路由规则,而在多用户机器上任何本地进程都能访问 127.0.0.1。因此命令行自查必须带上该 cookie:
184
+
185
+ ```sh
186
+ COOKIE=$(node -e "import('./scripts/lib/auth-cookie.mjs').then(m=>{const c=m.mintBrowserCookie();console.log(c.name+'='+c.value)})")
187
+ curl -s -H "cookie: $COOKIE" http://127.0.0.1:3080/proxy/api | head -c 200
188
+ ```
189
+
190
+ 组合里没有 harness 的 connection 服务时,没有凭据模型可依,该路由保持原样。
135
191
 
136
192
  ## 构建
137
193
 
@@ -139,7 +195,7 @@ host 半区用同源 HTTP 路由为设置页供数(内置 `/api` 前缀被 gat
139
195
  pnpm install
140
196
  pnpm bundle # 产出 lib/index.js + lib/client.js
141
197
  pnpm typecheck # tsc --noEmit
142
- pnpm test # node --test:proxy-core(13 场景)+ host-route /
198
+ pnpm test # node --test:proxy-core(16 场景)+ host-route /
143
199
  # client-boot / cordis-mount,无外部网络
144
200
  pnpm release:check # 发布门禁:文档/changelog/tag/工作区/构建/registry 全过
145
201
  pnpm publish # 跑门禁(prepack/prepublishOnly),随后 postpublish 验证线上发布