@linxin666/dsh-remote-web-ui 0.4.3 → 0.4.5

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.
Files changed (47) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +23 -8
  3. package/README.zh.md +22 -7
  4. package/lib/client.js +194 -140
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +231 -23
  7. package/lib/types/client/PluginSettingsCard.d.ts +7 -0
  8. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  9. package/lib/types/client/PluginSettingsCard.js +2 -2
  10. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  11. package/lib/types/client/RemoteSettingsCard.js +2 -1
  12. package/lib/types/client/index.js +3 -3
  13. package/lib/types/client/locales.d.ts +0 -4
  14. package/lib/types/client/locales.d.ts.map +1 -1
  15. package/lib/types/client/locales.js +5 -9
  16. package/lib/types/client/mobile-adapt.d.ts +2 -2
  17. package/lib/types/client/mobile-adapt.d.ts.map +1 -1
  18. package/lib/types/client/mobile-adapt.js +163 -136
  19. package/lib/types/client/settings-form.d.ts +16 -0
  20. package/lib/types/client/settings-form.d.ts.map +1 -1
  21. package/lib/types/client/settings-form.js +27 -1
  22. package/lib/types/detached-work.d.ts +45 -0
  23. package/lib/types/detached-work.d.ts.map +1 -0
  24. package/lib/types/detached-work.js +54 -0
  25. package/lib/types/index.d.ts.map +1 -1
  26. package/lib/types/index.js +67 -12
  27. package/lib/types/lan-bind.d.ts +21 -4
  28. package/lib/types/lan-bind.d.ts.map +1 -1
  29. package/lib/types/lan-bind.js +103 -17
  30. package/lib/types/pairing.d.ts.map +1 -1
  31. package/lib/types/pairing.js +43 -0
  32. package/lib/types/remote-api.d.ts +13 -1
  33. package/lib/types/remote-api.d.ts.map +1 -1
  34. package/lib/types/remote-api.js +14 -1
  35. package/package.json +19 -19
  36. package/src/client/PluginSettingsCard.tsx +9 -0
  37. package/src/client/RemoteSettingsCard.tsx +2 -0
  38. package/src/client/index.ts +3 -3
  39. package/src/client/locales.ts +5 -9
  40. package/src/client/mobile-adapt.ts +167 -138
  41. package/src/client/settings-card.module.css +31 -5
  42. package/src/client/settings-form.ts +27 -1
  43. package/src/detached-work.ts +57 -0
  44. package/src/index.ts +67 -12
  45. package/src/lan-bind.ts +111 -17
  46. package/src/pairing.ts +44 -1
  47. package/src/remote-api.ts +14 -1
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # node scripts/verify-docs.mjs --write <dir>
5
- README.md: 3a962a7fe14fb426a6c1beb238a51837dc345766
6
- README.zh.md: 1d3d36c040c5c35ce520299934471132234de457
5
+ README.md: 144ec159702cc3db4ce8f4c288b7273e47a72e56
6
+ README.zh.md: a1970c40c80e079ebb110e7dc6b658eb24345250
package/README.md CHANGED
@@ -1,9 +1,23 @@
1
- # DSH Remote Web UI
1
+ # dsh-remote-web-ui · Mobile & Cross-Device Remote Web Control for DeepSeek Harness (DSH)
2
2
 
3
3
  English | [中文](README.zh.md)
4
- > Remote access for the dsh web GUI that shares **one interface**: pair a phone or another computer from a QR beside the settings button, and both run the same official Web GUI this machine runs — phones get an injected portrait-touch adaptation, PCs get the full desktop — through time-limited pairing tokens and revocable device sessions. A settings toggle binds the server to the LAN, an optional Cloudflare quick tunnel reaches the internet — fronted by a never-changing stable hostname so the phone's bookmark and pairing survive restarts with zero setup.
5
4
 
6
- This repository is an external plugin package for DeepSeek Harness (DSH). It is a single dual-face package: the host half owns pairing tokens, device sessions, the `/api/pair` route family, the gated `/remote` channel, the LAN bind toggle; the browser half renders the sidebar-foot remote-access entry beside the settings button, the pairing panel with a QR code, live device status, the authorized-device roster, the settings card, and the portrait-touch adaptation layer over the official UI.
5
+ <p align="center">
6
+ <img src="https://img.shields.io/npm/v/@linxin666/dsh-client-ui-remote-web-ui?style=flat-square" alt="Version">
7
+ &nbsp;
8
+ <img src="https://img.shields.io/badge/DSH-%3E%3D0.2.0--rc.2-4c6ef5?style=flat-square&amp;labelColor=454a54" alt="DSH">
9
+ &nbsp;
10
+ <img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License">
11
+ </p>
12
+
13
+ <p align="center">
14
+ <strong>Mobile & Cross-Device Remote Web Control for DeepSeek Harness (DSH)</strong><br>
15
+ <em>Instant QR Pairing · Cross-Device PC Access · Mobile Touch Gestures · Cloudflare Tunnels · Secure Device Session</em>
16
+ </p>
17
+
18
+ > Seamless remote access for DeepSeek Harness (DSH) Web GUI and desktop client sharing the **exact same interface**: pair an iPhone, Android smartphone, tablet, or another computer from the QR code next to settings. Both ends run the authentic official Web GUI — phones receive an injected portrait touch layer while remote computers enjoy the complete desktop workbench. Access is guarded by expiring pairing tokens and revocable sessions. Built-in LAN binding and optional Cloudflare quick tunnels enable effortless external access with persistent hostnames.
19
+
20
+ This package is an official external plugin for DeepSeek Harness (DSH), structured as a cordis bundle: the host half manages pairing tokens, device sessions, `/api/pair` routes, the gated `/remote` channel, and LAN binding; the browser half renders the remote access trigger, QR pairing modal, live device roster, settings card, and mobile portrait touch adaptation.
7
21
 
8
22
  ## What it does
9
23
 
@@ -58,7 +72,7 @@ Restart the profile (`dsh web`), then open the phone icon in the sidebar foot. T
58
72
 
59
73
  ## Use
60
74
 
61
- 1. Open the settings card (设置 → Web 插件 → 远程访问设置) and flip **局域网访问** on if the server binds loopback; the card shows the live bind, the firewall state, and the reachable LAN URLs. The bind change takes effect from the next `dsh web` start.
75
+ 1. Open the settings card (设置 → Web 插件 → 远程访问) and flip **局域网访问** on if the server binds loopback; the card shows the live bind, the firewall state, and the reachable LAN URLs. The bind change takes effect from the next `dsh web` start.
62
76
  2. Start `dsh web`, click the phone icon, and the panel mints a fresh time-limited QR.
63
77
  3. Scan with the phone (or open the copied link): the device pairs and boots the **official Web GUI** served cookieless by the plugin (`/pair-accept` → `/pair-app`), which reloads into `/`. On a phone, the portrait adaptation layer is already active — same layout as the desktop, same live state. Later reopens from history or a bookmark go straight back into the app (https origins; see Security model).
64
78
  4. **To pair a PC instead**: copy the same link and open it in a browser on the other computer. After the same round trip the full Web GUI runs there over the gated `/remote` channel; unpaired PCs see the guided blocking page and no data. One active token pairs one device; mint a fresh QR for the next device.
@@ -122,7 +136,7 @@ pnpm --filter @linxin666/dsh-remote-web-ui test
122
136
  pnpm --filter @linxin666/dsh-remote-web-ui run typecheck
123
137
  ```
124
138
 
125
- The peer APIs come from the official NPM SDK: every `@deepseek-ai/*` package used here is declared in devDependencies (0.1.2-alpha.2 cohort), and TypeScript/Vitest resolve types straight from node_modules — no DSH source checkout is required. The consumer-side `prepare` build (`tsdown.prepare.config.ts`) transpiles without type checking, so git installs work without any harness checkout either.
139
+ The peer APIs come from the official NPM SDK: every `@deepseek-ai/*` package used here is declared in devDependencies (0.2.0-rc.2 cohort), and TypeScript/Vitest resolve types straight from node_modules — no DSH source checkout is required. The consumer-side `prepare` build (`tsdown.prepare.config.ts`) transpiles without type checking, so git installs work without any harness checkout either.
126
140
 
127
141
  ## Checks
128
142
 
@@ -134,9 +148,9 @@ pnpm run build
134
148
 
135
149
  ## Harness contract dependencies
136
150
 
137
- Pinned to the 0.1.2-alpha.2 line; the seams this build relies on:
151
+ Pinned to the 0.2.0-rc.2 line; the seams this build relies on:
138
152
 
139
- - **`sidebar.footer.action` foot seat** (the 0.1.2 shell composition): the sidebar declares and renders the seat the remote entry occupies.
153
+ - **`sidebar.footer.action` foot seat** (the 0.1.7 shell composition): the sidebar declares and renders the seat the remote entry occupies.
140
154
  - **`ctx.layout.toggleSidebar()`** (packages/client/ui-layout): the whale button expands the collapsed sidebar through the official panel-action face.
141
155
  - **`ctx.connection.authenticatedUrl()`** (packages/client/connection): the sanctioned launch-token seam the proxy redeems once for its inner credential (`src/inner-auth.ts`), so re-issued `/api` calls satisfy the harness browser-auth check.
142
156
  - **`__DSH_FILE_UPLOAD__`** (file-upload client hooks): the optional pre-Cordis transport the upload service reads once at construction. The remote boot patch publishes it so background uploads stay on the rewritten main-thread fetch instead of a Web Worker that escapes the channel (issue #1580).
@@ -161,7 +175,7 @@ The public path is the same round trip through a tunnel (see "Remote access over
161
175
 
162
176
  - **Pairing is the access control for the `/remote` channel, unconditionally**: every request must carry a live paired-device cookie, enforced before any bytes are forwarded. A missing or revoked session receives HTTP 403 with a JSON rejection carrying `error.code: "unpaired"`; the browser's `EventSource` API exposes only the stream failure, not that response body. This gate does **not** follow `requirePairingForLan`: the channel re-issues every call with the process's own browser-auth credential, so admitting an unpaired caller would hand out that authority. The LAN policy governs the plain `/api` surface only and can never widen the channel (issue #1665).
163
177
  - **The channel carries the process's own inner credential.** The harness browser-auth cookie is authority-bound (minted for the exact `host:port` a browser visited) and has no loopback exemption, so a proxied re-issue to `127.0.0.1` cannot reuse a device's cookie. The plugin therefore redeems its own launch token once — the same exchange a first browser visit performs — and attaches that cookie to re-issued requests. The credential is only ever exercised behind the pairing gate above; 停止/取消配对 immediately stop exercising it.
164
- - **Cohort reality: pairing does not gate direct `/api`.** On the pinned 0.1.2-alpha.2 line nothing emits the `api/gate` seam, so a direct `/api` call from a LAN origin is governed solely by the harness fence (which auto-trusts LAN literals under a `0.0.0.0` bind) plus the harness browser-auth cookie. A browser credential a device has already redeemed therefore survives 停止/取消配对 until its natural expiry (30 days) — revocation binds the `/remote` channel and the pairing cookie, not that credential. The plugin probes the `/api` posture and warns loudly; treat a LAN-exposed bind as a deliberate decision, and prefer loopback plus tunnels when the machine is shared.
178
+ - **Runtime reality: pairing does not gate direct `/api`.** Nothing in the official runtime emits the `api/gate` seam (verified against the 0.2.0-rc.2 packages installed by this repository), so a direct `/api` call from a LAN origin is governed solely by the harness fence (which auto-trusts LAN literals under a `0.0.0.0` bind) plus the harness browser-auth cookie. A browser credential a device has already redeemed therefore survives 停止/取消配对 until its natural expiry (30 days) — revocation binds the `/remote` channel and the pairing cookie, not that credential. The plugin probes the `/api` posture and warns loudly; treat a LAN-exposed bind as a deliberate decision, and prefer loopback plus tunnels when the machine is shared.
165
179
  - **A paired device is a full-control credential.** With host mode active it reaches the complete host API — chat, sessions, settings, credentials, agent presets, deliverables — mirroring the SDK's own stance that a loopback desktop is trusted. Only the three control planes (pairing, self-update, plugin install/remove) stay physically local. Only pair devices you control; 停止 or per-device 取消配对 revokes immediately.
166
180
  - **Control endpoints stay loopback-only**: mint/stop/revoke, the device roster, and the lan-bind status answer only to loopback, and the gated channel keeps the self-update and plugin-install paths physically local. A LAN-origin browser sees the "配对面板仅限本机使用" banner.
167
181
  - **Background file uploads ride the channel too.** The official upload service prefers a Web Worker carrier whose own globals no main-thread patch reaches, so the boot patch (and the browser patch as a fallback) publishes the official pre-Cordis hook `__DSH_FILE_UPLOAD__` and hands it the patched `fetch`: the raw `/api/session/uploadFileBinary` POST is rewritten onto `/remote` and carries the device credential exactly like every other gated call. Without the hook a paired browser's uploads bypass the channel and the harness browser-auth fence answers 401 (issue #1580). The hook is only published on non-loopback origins, only while the channel is installed, and never overwrites a hook the page already owns.
@@ -182,6 +196,7 @@ The public path is the same round trip through a tunnel (see "Remote access over
182
196
  - **Plain-HTTP LAN reopens need a re-scan**: the reopen service worker registers only on secure contexts (https tunnels, localhost); a phone paired over a plain-HTTP LAN URL that navigates back to `/` hits the harness 401 and must scan a fresh QR. Pairing again is cheap in-network and restores everything.
183
197
  - **The adaptation selectors track the official build**: the semantic-suffix strategy survives hash churn but not semantic renames; each official GUI update needs a visual QA pass (per the dsh-LAN reference, these suffixes have been stable across many official releases).
184
198
  - **Dev HMR**: `dsh web --dev` polls every roster bundle by path, so rebuilding this package (its own `tsdown --watch`) hot-reloads the client bundle; the host half needs a restart.
199
+ - **A mounted `@deepseek-ai/dsh-hmr` row blocks every settings save**: the official config editor serializes its writes through `hmr.runExclusive` whenever the `hmr` service is mounted, and on this cohort such a write is rejected as a nested transaction - the Host answers `HMR transactions cannot be nested` and each family settings card (and the official settings pages) reports a failed save. The row is user-added (no bundle ships it); a Desktop profile carries it to hot-reload a local plugin's host half. Remove the `- id: hmr` row from the profile patch to restore saves, and re-add it only while iterating on that plugin. Observed on the Desktop client 0.2.0-rc.2 (2026-09-30).
185
200
 
186
201
  ## Dependency rationale
187
202
 
package/README.zh.md CHANGED
@@ -1,7 +1,21 @@
1
- # DSH 远程访问(Remote Web UI)
1
+ # dsh-remote-web-ui · DeepSeek Harness (DSH) 手机端与跨设备浏览器远程控制插件
2
2
 
3
3
  [English](README.md) | 中文
4
- > 为 dsh web GUI 提供共享**同一份界面**的远程访问:在设置按钮旁的二维码配对手机或另一台电脑,两端运行的都和本机一样是官方 Web GUI——手机获得注入的竖屏触控适配,电脑获得完整桌面;准入基于限时配对令牌与可撤销设备会话。设置卡片可将服务绑定到局域网,可选 Cloudflare 快速隧道触达公网——并由一个永不变化的固定主机名前置,手机的书签与配对零配置跨重启有效。
4
+
5
+ <p align="center">
6
+ <img src="https://img.shields.io/npm/v/@linxin666/dsh-client-ui-remote-web-ui?style=flat-square" alt="Version">
7
+ &nbsp;
8
+ <img src="https://img.shields.io/badge/DSH-%3E%3D0.2.0--rc.2-4c6ef5?style=flat-square&amp;labelColor=454a54" alt="DSH">
9
+ &nbsp;
10
+ <img src="https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square" alt="License">
11
+ </p>
12
+
13
+ <p align="center">
14
+ <strong>DeepSeek Harness(DSH)官方 Web GUI 与桌面客户端手机与跨设备远程控制中心</strong><br>
15
+ <em>手机扫码即连 · 跨设备 PC 浏览器访问 · 竖屏触控手势优化 · Cloudflare 公网隧道 · 安全设备配对</em>
16
+ </p>
17
+
18
+ > 为 DeepSeek Harness (DSH) Web GUI 与官方桌面客户端提供共享**同一份界面**的远程访问:在设置按钮旁的二维码配对手机或另一台电脑,两端运行的都和本机一样是官方 Web GUI——手机获得注入的竖屏触控适配,电脑获得完整桌面;准入基于限时配对令牌与可撤销设备会话。设置卡片可将服务绑定到局域网,可选 Cloudflare 快速隧道触达公网——并由一个永不变化的固定主机名前置,手机的书签与配对零配置跨重启有效。
5
19
 
6
20
  本仓库是 DeepSeek Harness(DSH)的外部插件包,为单一双面包:host 半区持有配对令牌、设备会话、`/api/pair` 路由族、门控 `/remote` 通道、局域网绑定开关;浏览器半区渲染侧栏底部、设置按钮旁的远程访问入口、带二维码的配对面板、实时设备状态、已授权设备列表、设置卡片,以及官方界面之上的竖屏触控适配层。
7
21
 
@@ -58,7 +72,7 @@ dsh plugin --profile web add link:$(pwd)/packages/dsh-remote-web-ui
58
72
 
59
73
  ## 使用
60
74
 
61
- 1. 打开设置卡片(设置 → Web 插件 → 远程访问设置),若服务绑定回环则打开**局域网访问**;卡片展示运行中的绑定、防火墙状态与可达局域网地址。绑定变化自下一次 `dsh web` 启动生效。
75
+ 1. 打开设置卡片(设置 → Web 插件 → 远程访问),若服务绑定回环则打开**局域网访问**;卡片展示运行中的绑定、防火墙状态与可达局域网地址。绑定变化自下一次 `dsh web` 启动生效。
62
76
  2. 启动 `dsh web`,点手机图标,面板铸造一枚全新的限时二维码。
63
77
  3. 手机扫码(或打开复制的链接):设备完成配对,进入由插件不依赖 cookie 交付的**官方 Web GUI**(`/pair-accept` → `/pair-app`),并重载到 `/`。手机上竖屏适配层已生效——与桌面同布局、同实时状态。此后的重开(历史、书签)直接回到应用(https 源;见安全模型)。
64
78
  4. **改为配对电脑**:复制同一链接,在另一台电脑的浏览器打开。相同往返后完整 Web GUI 在彼处经门控 `/remote` 通道运行;未配对电脑看到引导式拦截页,其后没有数据。一枚令牌配一台设备;下一台设备请刷新二维码。
@@ -122,7 +136,7 @@ pnpm --filter @linxin666/dsh-remote-web-ui test
122
136
  pnpm --filter @linxin666/dsh-remote-web-ui run typecheck
123
137
  ```
124
138
 
125
- 对端 API 来自官方 NPM SDK:用到的每个 `@deepseek-ai/*` 包都声明在 devDependencies(0.1.2-alpha.2 cohort)中,TypeScript/Vitest 直接从 node_modules 解析类型——不需要 DSH 源码 checkout。消费侧 `prepare` 构建(`tsdown.prepare.config.ts`)不做类型检查地转译,git 安装同样无需 harness checkout。
139
+ 对端 API 来自官方 NPM SDK:用到的每个 `@deepseek-ai/*` 包都声明在 devDependencies(0.2.0-rc.2 cohort)中,TypeScript/Vitest 直接从 node_modules 解析类型——不需要 DSH 源码 checkout。消费侧 `prepare` 构建(`tsdown.prepare.config.ts`)不做类型检查地转译,git 安装同样无需 harness checkout。
126
140
 
127
141
  ## 检查
128
142
 
@@ -134,9 +148,9 @@ pnpm run build
134
148
 
135
149
  ## Harness 契约依赖
136
150
 
137
- 锚定 0.1.2-alpha.2 线;本构建依赖的接缝:
151
+ 锚定 0.2.0-rc.2 线;本构建依赖的接缝:
138
152
 
139
- - **`sidebar.footer.action` 底部席位**(0.1.2 shell 组合):侧栏声明并渲染远程入口占据的席位。
153
+ - **`sidebar.footer.action` 底部席位**(0.1.7 shell 组合):侧栏声明并渲染远程入口占据的席位。
140
154
  - **`ctx.layout.toggleSidebar()`**(packages/client/ui-layout):鲸鱼按钮经官方面板动作面展开折叠侧栏。
141
155
  - **`ctx.connection.authenticatedUrl()`**(packages/client/connection):代理为内部凭据一次性兑换启动令牌的官方接缝(`src/inner-auth.ts`),使再发起的 `/api` 调用满足 harness 浏览器认证校验。
142
156
  - **`__DSH_FILE_UPLOAD__`**(file-upload 客户端钩子):上传服务在构造时读取一次的可选启动前传输。远程引导补丁会发布它,使后台上传留在被改写的主线程 fetch 上,而不是逃出通道的 Web Worker(issue #1580)。
@@ -161,7 +175,7 @@ pnpm run build
161
175
 
162
176
  - **配对是 `/remote` 通道的访问控制,且无条件生效**:每个请求必须携带有效配对设备 cookie,在任何字节转发之前强制。缺失或被撤销的会话收到 HTTP 403,JSON 拒绝携带 `error.code: "unpaired"`;浏览器 `EventSource` API 只暴露流失败,不暴露响应体。该门**不**跟随 `requirePairingForLan`:通道会用进程自身的浏览器认证凭据重新发起每一次调用,放行未配对调用者等于把该权限交出去。局域网策略只作用于普通 `/api` 面,永远不能放宽本通道(issue #1665)。
163
177
  - **通道携带进程自己的内部凭据。** harness 浏览器认证 cookie 与 authority 绑定(为浏览器访问过的确切 `host:port` 签发)且没有回环豁免,因此转发到 `127.0.0.1` 的再发起请求无法复用设备的 cookie。插件因此自行兑换一次自己的启动令牌——与浏览器首次访问执行的是同一次交换——并把所得 cookie 附到再发起请求上。该凭据只在上面的配对门之后被使用;停止/取消配对会立即停止对它的使用。
164
- - **本 cohort 的现实:配对不门控直连 `/api`。** 在锚定的 0.1.2-alpha.2 线上,没有任何组件发出 `api/gate` seam,因此来自局域网源头的直连 `/api` 仅由 harness 围栏(`0.0.0.0` 绑定下自动信任局域网字面量)加 harness 浏览器认证 cookie 约束。设备已经兑换过的浏览器凭据在停止/取消配对后仍然有效,直到其自然过期(30 天)——撤销约束的是 `/remote` 通道与配对 cookie,而不是那个凭据。插件会对 `/api` 姿态做探测并大声告警;请把局域网绑定当作深思熟虑的决定,在共享机器上优先回环加隧道。
178
+ - **运行时现实:配对不门控直连 `/api`。** 官方运行时没有任何组件发出 `api/gate` seam(对照本仓库安装的 0.2.0-rc.2 包核实),因此来自局域网源头的直连 `/api` 仅由 harness 围栏(`0.0.0.0` 绑定下自动信任局域网字面量)加 harness 浏览器认证 cookie 约束。设备已经兑换过的浏览器凭据在停止/取消配对后仍然有效,直到其自然过期(30 天)——撤销约束的是 `/remote` 通道与配对 cookie,而不是那个凭据。插件会对 `/api` 姿态做探测并大声告警;请把局域网绑定当作深思熟虑的决定,在共享机器上优先回环加隧道。
165
179
  - **配对设备是完全控制凭据。** host 模式下它可达完整 host API——聊天、会话、设置、凭据、Agent 预设、产出物——与 SDK 对回环桌面的信任一致。只有三个控制面(配对、自更新、插件安装/卸载)保持物理本地。只配对你控制的设备;停止或逐设备取消配对立即撤销。
166
180
  - **控制端点仅限回环**:铸造/停止/撤销、设备列表与 lan-bind 状态只应答回环,门控通道同时让自更新与插件安装路径保持物理本地。局域网源浏览器看到「配对面板仅限本机使用」横幅。
167
181
  - **后台文件上传同样走通道。** 官方上传服务优先使用 Web Worker 载体,其独立全局对象不受主线程补丁影响;因此引导补丁(以及作为兜底的浏览器补丁)会发布官方启动前钩子 `__DSH_FILE_UPLOAD__`,并把打过补丁的 `fetch` 交给它:原始 `/api/session/uploadFileBinary` POST 会被改写到 `/remote`,并像其他受门控调用一样携带设备凭据。没有该钩子时,配对浏览器的上传会绕过通道,被 harness 浏览器认证围栏拒为 401(issue #1580)。该钩子仅在非回环源、且通道安装期间发布,且永不覆盖页面已有的钩子。
@@ -182,6 +196,7 @@ pnpm run build
182
196
  - **纯 HTTP 局域网的重开需要重扫**:重开 service worker 只在安全上下文(https 隧道、localhost)注册;经纯 HTTP 局域网 URL 配对的手机导航回 `/` 时会撞上 harness 401,需要重新扫码。局域网内重配对成本很低,配对后一切恢复。
183
197
  - **适配选择器跟随官方构建**:语义后缀策略可在 hash 变化中存活,但语义改名不行;每次官方 GUI 升级需要一轮视觉 QA(参考 dsh-LAN,这些后缀在多次官方发布中保持稳定)。
184
198
  - **开发 HMR**:`dsh web --dev` 按路径轮询每个 roster bundle,重建本包(自身 `tsdown --watch`)即热重载客户端 bundle;host 半区需要重启。
199
+ - **profile 里挂了 `@deepseek-ai/dsh-hmr` 行会让所有设置保存失败**:只要 `hmr` 服务存在,官方配置编辑器就会把写入包进 `hmr.runExclusive`,而在本 cohort 上这种写入会被判为嵌套事务——Host 返回 `HMR transactions cannot be nested`,本插件的各设置卡片(以及官方设置页)都显示保存失败。该行由用户自行添加(没有任何 bundle 自带),桌面版 profile 用它为本地插件的 host 半区做热重载。从 profile 补丁里删掉 `- id: hmr` 这一行即可恢复保存;只在需要热重载该插件时再加回。观察于 2026-09-30,桌面版客户端 0.2.0-rc.2。
185
200
 
186
201
  ## 依赖说明
187
202