dsh-mobile 0.6.7 → 0.6.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  Notable changes are recorded here. GitHub Releases provide the downloadable packages and generated commit notes.
4
4
 
5
+ ## 0.6.8 - 2026-10-15
6
+
7
+ - Fix remote pairing on computers with the same default state path overwriting one another in the Android device list. Automatically identified entries without a pairing CA use their persisted random installation identifier for new pairings; explicit operator IDs, LAN CA fingerprints and self-signed FRP CA fingerprints remain unchanged.
8
+ - Read both device-state versions and write version 2 on the next mutation, preserving each new credential's pairing identity. Existing credentials keep their previous native renewal/probe identity during ordinary upgrades with the same state path and gateway configuration, with no wire-protocol change or required Android App update. See [upgrade and downgrade precautions](SECURITY.md#installation-identity-and-device-state-upgrades).
9
+ - Publish the complete installation identifier atomically without replacing an existing one, avoiding a concurrent starter reading an empty file. Refuse invalid identity files and unsupported initial publication rather than silently changing the identity.
10
+ - Keep working pairings without re-pairing. A local App credential already overwritten by the collision cannot be recovered automatically: update and restart the plugin, then pair each affected computer again. Usable old rows remain valid; any duplicate local row can be removed explicitly without computer-side revocation.
11
+ - Adapt compatibility checks to DSH 0.2.1-alpha.3's authenticated Host-backed settings while retaining the Remote/Connection dependency checks and earlier settings policies. Mobile typography remains local to its Origin. Extend the browser test matrix to alpha.3 and recognize its first-run account dialog without submitting credentials.
12
+ - Add bilingual remote-channel selection notes distinguishing the maintainer's long-term network experience from provider limits and conditional self-hosted deployment characteristics.
13
+ - Android App 0.6.3 remains the published native release. The pending Android file-picker changes below are not part of this plugin fix. Thanks @Yurzi for reporting [#213](https://github.com/saya-ch/dsh-mobile/issues/213).
14
+
5
15
  ## 0.6.7 - 2026-10-14
6
16
 
7
17
  - Add optional `cpolarSubdomain` and `cpolarRegion` profile settings for a reserved cpolar address. Omitted or blank values keep provider defaults; only the validated HTTPS origin reported by cpolar is used. Existing pairing and authorization checks are unchanged. [Configuration guide](docs/CPOLAR_SUBDOMAIN.en.md).
package/CONTRIBUTORS.md CHANGED
@@ -64,6 +64,8 @@ GitHub's Contributors graph counts commits on the default branch; issue reports
64
64
 
65
65
  ## 问题反馈与建议 / Issue reports and ideas
66
66
 
67
+ 感谢 [@Yurzi](https://github.com/Yurzi) 在 [#213](https://github.com/saya-ch/dsh-mobile/issues/213) 报告不同电脑的远程配对记录互相覆盖。 / Thanks [@Yurzi](https://github.com/Yurzi) for reporting remote pairing records on different computers overwriting one another in [#213](https://github.com/saya-ch/dsh-mobile/issues/213).
68
+
67
69
  感谢 [@Dantezcx](https://github.com/Dantezcx) 在 [#209](https://github.com/saya-ch/dsh-mobile/issues/209) 和 [#210](https://github.com/saya-ch/dsh-mobile/issues/210) 提供侧栏命中、短屏布局和定制资源加载的详细诊断。 / Thanks [@Dantezcx](https://github.com/Dantezcx) for the detailed sidebar hit-testing, short-viewport and customization-loading diagnostics in [#209](https://github.com/saya-ch/dsh-mobile/issues/209) and [#210](https://github.com/saya-ch/dsh-mobile/issues/210).
68
70
 
69
71
  感谢 [@xhwxt](https://github.com/xhwxt) 在 [#203](https://github.com/saya-ch/dsh-mobile/pull/203) 中提出文档选择器改进方案,以及 [@Void-LifeStream](https://github.com/Void-LifeStream) 在 [#205](https://github.com/saya-ch/dsh-mobile/issues/205) 中提供附件选择与回传问题的详细反馈。
@@ -120,5 +122,5 @@ Thanks [@xhwxt](https://github.com/xhwxt) for the document-picker proposal in [#
120
122
  | [@tianyimc](https://github.com/tianyimc) | [#126](https://github.com/saya-ch/dsh-mobile/issues/126) |
121
123
  | [@Xingcheng-wang](https://github.com/Xingcheng-wang) | [#76](https://github.com/saya-ch/dsh-mobile/issues/76) |
122
124
  | [@xhwxt](https://github.com/xhwxt) | [#107](https://github.com/saya-ch/dsh-mobile/issues/107), [#108](https://github.com/saya-ch/dsh-mobile/issues/108), [#145](https://github.com/saya-ch/dsh-mobile/issues/145), [#146](https://github.com/saya-ch/dsh-mobile/issues/146), [#149](https://github.com/saya-ch/dsh-mobile/issues/149), [#171](https://github.com/saya-ch/dsh-mobile/issues/171) |
123
- | [@Yurzi](https://github.com/Yurzi) | [#17](https://github.com/saya-ch/dsh-mobile/issues/17) |
125
+ | [@Yurzi](https://github.com/Yurzi) | [#17](https://github.com/saya-ch/dsh-mobile/issues/17), [#213](https://github.com/saya-ch/dsh-mobile/issues/213) |
124
126
  | [@Ztyss](https://github.com/Ztyss) | [#22](https://github.com/saya-ch/dsh-mobile/issues/22), [#23](https://github.com/saya-ch/dsh-mobile/issues/23) |
package/README.en.md CHANGED
@@ -25,13 +25,13 @@
25
25
  <a href="#security">Security</a> ·
26
26
  <a href="#compatibility">Compatibility</a> ·
27
27
  <a href="#contributors">Contributors</a> ·
28
- <a href="CHANGELOG.md">Changelog</a> ·
28
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.8">Release notes</a> ·
29
29
  <a href="README.md">简体中文</a>
30
30
  </p>
31
31
 
32
32
  > DSH Mobile is a DeepSeek Harness community plugin; the native app supports Android only.
33
33
  >
34
- > **Current stable release: 0.6.7**. Add reserved cpolar subdomain configuration, improve narrow-screen sidebar and overlay interaction, and clarify mobile customization ownership, failures and recovery. [Changelog](CHANGELOG.md) · [Release downloads](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.7).
34
+ > **Current stable release: 0.6.8**. Fix computers with the same default state path overwriting one another in the App device list, adapt to DSH 0.2.1-alpha.3, and add remote-channel selection guidance. Working pairings stay valid; an App update is not required. [Release notes and downloads](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.8) · [Full changelog](CHANGELOG.md).
35
35
  >
36
36
  > **Version policy**: since plugin **0.6.2**, the Android app and plugin use independent version numbers and releases. Their numbers do not need to match, and a plugin update does not imply an app update. [Compatibility notes](#compatibility).
37
37
 
@@ -152,14 +152,16 @@ Remote providers may impose bandwidth and connection limits: the [cpolar Free pl
152
152
 
153
153
  If the page opens but stays on “Reconnecting,” distinguish WebSocket upgrade failures, slow synchronization, and heartbeat deadlines using the [slow-link and reconnection guide](docs/SLOW_CONNECTIONS.en.md). Version 0.5.4 also offers opt-in WebSocket compression per exact path, disabled by default; for long Sessions or metered links, follow the guide and compare actual transfer volume.
154
154
 
155
- | Channel | Prerequisites | Address and limitation |
156
- | --- | --- | --- |
157
- | cpolar | Account and Authtoken | An option to try on mainland networks; free temporary URLs may change |
158
- | cloudflared quick tunnel | No account or domain | Random temporary URL; no SSE, with [notification limits](docs/CLOUDFLARE_TUNNEL.en.md#quick-tunnel-feature-limits) |
159
- | cloudflared named tunnel | Cloudflare account and domain | Fixed hostname; [setup guide](docs/CLOUDFLARE_TUNNEL.en.md) |
160
- | Tailscale Funnel | Sign-in and Funnel authorization | May be unreliable on mainland-China networks |
161
- | Self-hosted FRP | VPS and domain or real public IPv4 | Server maintenance required; [deploy](docs/SELF_HOSTED_FRP.en.md) or [attach to existing frps](docs/ATTACH_EXISTING_FRPS.en.md) |
162
- | Own reverse proxy | Existing public HTTPS proxy | The plugin provides only its private authenticated backend; [guide](docs/SELF_HOSTED_ORIGIN.en.md) |
155
+ The usage notes below reflect the maintainer's long-term experience on a personal mainland-China network, not a universal comparison. Performance depends on the carrier, network and plan; verify the route on your own phone's network.
156
+
157
+ | Channel | Prerequisites | Address and limitation | Maintainer's usage notes |
158
+ | --- | --- | --- | --- |
159
+ | cpolar | Account and Authtoken | Free temporary URLs may change | The free plan's bandwidth limit noticeably slows everyday loading and synchronization |
160
+ | cloudflared quick tunnel | No account or domain | Random temporary URL; no SSE, with [notification limits](docs/CLOUDFLARE_TUNNEL.en.md#quick-tunnel-feature-limits); [officially intended only for development and testing](https://developers.cloudflare.com/tunnel/get-started/quick-tunnels/) | Frequent disconnections in long-term use, with the cause still unconfirmed; a temporary or testing choice |
161
+ | cloudflared named tunnel | Cloudflare account and domain | Fixed hostname; [setup guide](docs/CLOUDFLARE_TUNNEL.en.md) | The most stable of the channels used long-term; worth trying first if you have an account and domain |
162
+ | Tailscale Funnel | Sign-in and Funnel authorization | Availability depends on the network | This personal network often needs proxy assistance and may still fail to connect; not every user needs a proxy |
163
+ | Self-hosted FRP | VPS and domain or real public IPv4 | Server maintenance required; [deploy](docs/SELF_HOSTED_FRP.en.md) or [attach to existing frps](docs/ATTACH_EXISTING_FRPS.en.md) | More control over deployment and resources; long-term stability depends on the VPS route, configuration and maintenance |
164
+ | Own reverse proxy | Existing public HTTPS proxy | The plugin provides only its private authenticated backend; [guide](docs/SELF_HOSTED_ORIGIN.en.md) | Reuses your proxy infrastructure; stability depends on the proxy, network route and maintenance |
163
165
 
164
166
  <p align="center">
165
167
  <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/screenshots/remote-access-en.png" width="82%" alt="DSH Mobile remote access and provider selection">
@@ -188,7 +190,7 @@ After selecting the channel in the panel:
188
190
 
189
191
  > **Remote notifications**: browser `Notification` permission is granted per Origin, and a web-page system toast appears only on the device running that page. Android task reminders are a separate 0.4.0 feature: enable them under DSH **Settings → General** in the app, and keep the WebView page alive; they are not a general background push service. For reliable background delivery, use a server-side webhook or bot channel you have configured.
190
192
 
191
- Tailscale Funnel has broad reach but may be unreliable from mainland China. Its runtime ties the public listener to the parent process and a bounded control channel; parent exit, channel closure, or an explicit stop ends the current generation and cleans up its resources. cpolar is better suited to mainland networks, while self-hosted FRP fits users who already have a VPS and want to avoid public-provider bandwidth quotas. cloudflared runs in two modes: a quick tunnel needs no account or sign-in, but its hostname is random, changes on every reconnect, and is [intended by Cloudflare for testing](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/) without an uptime guarantee, so it suits temporary or verification use rather than a permanent channel; a named tunnel uses a Cloudflare account token and keeps one fixed public hostname across restarts. An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; public IPv4 mode avoids that dependency. The plugin validates pinned on-demand components; locally managed configuration and binaries live under `$DSH_HOME/mobile-access/` and can be cleaned up from the panel. VPS files require separate cleanup through the self-hosted connection guide.
193
+ The Funnel runtime ties the public listener to the parent process and a bounded control channel; parent exit, channel closure, or an explicit stop ends the current generation and cleans up its resources. An unregistered domain on a mainland-China VPS may be intercepted by the cloud provider; public IPv4 mode avoids that dependency. The plugin validates pinned on-demand components; locally managed configuration and binaries live under `$DSH_HOME/mobile-access/` and can be cleaned up from the panel. VPS files require separate cleanup through the self-hosted connection guide.
192
194
 
193
195
  Managed self-hosted FRP uses an HTTP vhost to the DSH loopback gateway. Its plaintext VPS listener must be loopback-only, with Caddy providing public HTTPS. Version 0.4.6 adds existing-frps attachment without installing or changing the server: its public-CA mode still needs a restricted HTTP vhost and Caddy, while its self-signed mode forwards raw TCP to a computer-side HTTPS gateway whose CA the 0.4.6 Android app pins during pairing. The self-signed mode currently uses public IPv4 and is unavailable to older apps. Neither mode exposes arbitrary FRP configuration. Because frps `proxyBindAddr` governs proxy listeners globally, do not change it for the new TCP entry without checking existing plaintext vhosts. See the [attachment guide](docs/ATTACH_EXISTING_FRPS.en.md).
194
196
 
@@ -239,6 +241,8 @@ The examples above, applied:
239
241
 
240
242
  The Android app lists computers and their LAN/remote records under **Paired computers**. The two authorization types remain separate, so one computer can have two rows. Upgrades migrate old credentials without re-pairing; after a changed address is verified, matching identities merge and keep their names. Self-signed FRP uses a separate CA fingerprint and may create another row. Android Keystore encrypts tokens and pinned CAs. A pairing QR code contains temporary pairing information, never a long-lived device token or CA private key.
241
243
 
244
+ Unless an identifier is configured manually, new pairings through a public-certificate remote entry use a random identifier saved on the computer, preventing identical installation paths from overwriting another computer's row. Working connections need no re-pairing or Android App update; LAN and self-signed FRP still use their respective CAs. Quit DSH normally and restart after updating; read the [state-backup precautions](SECURITY.md#installation-identity-and-device-state-upgrades) before downgrading.
245
+
242
246
  Each row shows its custom name, transport, Origin, periodically refreshed reachability, and last connection time. A green dot means **Reachable**; a gray dot means **Checking**, **Temporarily unreachable**, **Pairing expired**, or **Removed on computer**. The check validates the DSH Gateway over HTTPS instead of using ICMP, so a temporary network outage is not mistaken for computer-side revocation.
243
247
 
244
248
  - **Startup behavior → Open DSH directly** (default): one device connects directly; with multiple devices, the app tries the last-used device first, then the still-valid device with the most recent connection. A bounded connection budget returns to the list instead of spinning forever.
@@ -361,6 +365,7 @@ See [SECURITY.md](SECURITY.md).
361
365
 
362
366
  ## Troubleshooting
363
367
 
368
+ - **Pairing a second computer overwrites the first**: see [#213](https://github.com/saya-ch/dsh-mobile/issues/213). Update to plugin 0.6.8 or newer, restart DSH normally, then scan each affected computer's current QR code. An App credential already overwritten cannot be recovered automatically; working old rows need no re-pairing. If the same address has both an old and a new row, confirm the new one works, then delete only the extra local App row. Do not reset remote access. Read the [device-state backup and downgrade precautions](SECURITY.md#installation-identity-and-device-state-upgrades) before upgrading.
364
369
  - **Pairing succeeds but renewal returns `401` or repeatedly asks to pair again**: other services on the same hostname or stale Cookies may interfere with authentication. Update to the latest plugin and app, then retry through the current Mobile Access entry. In a browser, scanning the current QR code again refreshes this plugin's authentication Cookies without clearing all website data. If it still fails, inspect the failed request and sanitized logs; other authentication or proxy errors can also return `401`.
365
370
  - **The testing notice cannot be confirmed or settings cannot be saved**: open DSH directly at its local address on the computer, then inspect the terminal log or the failed settings request in the browser's Network panel. Connection diagnostics do not test settings writes. Keep the existing configuration and `settings.yaml.imported`, and investigate the specific error; the same UI message can have different causes. See [DSH #860](https://github.com/deepseek-ai/deepseek-harness/discussions/860).
366
371
  - **A settings request returns HTTP `403`**: first inspect DSH's Host/Origin trust checks and any proxy response. Check whether an iframe, proxy, or browser extension changes the request's origin, and verify direct local access. Keep the trust checks enabled.
@@ -399,6 +404,7 @@ On macOS, local network, cloudflared, self-hosted FRP and the own reverse proxy
399
404
  | `0.6.4` | `0.2.0-rc.2` (packed installation, isolated pairing, mobile boot and WebSocket Workspace reads); additionally 72 real public-slot composer layouts, Windows Desktop directory-picker composition and cross-platform regressions. The reported remote timeout in community Desktop 2.0.17 remains unclaimed as accepted |
400
405
  | `0.6.5` | Packed module-management and mobile-settings regressions on `0.2.0-rc.2` and `0.2.1-alpha.2`; additionally Windows cpolar extraction and populated Trajectory layout with published `maid-atelier` 0.1.7. This does not establish acceptance of every third-party skin or reporter device |
401
406
  | `0.6.6` | Packed attachment intake and decoded-preview regressions on `0.2.0-rc.2` and `0.2.1-alpha.2`, covering multiple images, cancellation, late results and superseded requests. Android system-picker changes are released independently, not through the npm update |
407
+ | `0.6.8` | Packed mobile boot, legacy-WebView API simulation, WebSocket Workspace reads, mobile settings, module management, sidebar and composer regressions on `0.2.1-alpha.3`; separate real HTTP/TLS and legacy-credential upgrade tests cover the identity fix. The local official Desktop `0.2.0-rc.2` loads this plugin version with existing pairings preserved. This does not establish phone cellular or every third-party plugin combination |
402
408
 
403
409
  <details>
404
410
  <summary>Historical version checks</summary>
package/README.md CHANGED
@@ -25,13 +25,13 @@
25
25
  <a href="#安全">安全</a> ·
26
26
  <a href="#兼容性">兼容性</a> ·
27
27
  <a href="#贡献者">贡献者</a> ·
28
- <a href="CHANGELOG.md">更新记录</a> ·
28
+ <a href="https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.8">更新记录</a> ·
29
29
  <a href="README.en.md">English</a>
30
30
  </p>
31
31
 
32
32
  > DSH Mobile 是 DeepSeek Harness 社区插件,原生 App 仅支持 Android。
33
33
  >
34
- > **当前正式版本:0.6.7**。支持 cpolar 预留子域名配置,改善窄屏侧栏与浮层交互,并补全移动定制的入口识别、失败提示和恢复处理。[更新记录](CHANGELOG.md) · [版本下载](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.7)。
34
+ > **当前正式版本:0.6.8**。修复相同默认状态路径的电脑在 App 设备列表中相互覆盖的问题,适配 DSH 0.2.1-alpha.3,并补充远程通道选择建议。已有正常配对无需重建,也不需要升级 App。[更新与下载](https://github.com/saya-ch/dsh-mobile/releases/tag/v0.6.8) · [完整更新记录](CHANGELOG.md)。
35
35
  >
36
36
  > **版本说明**:自插件 **0.6.2** 起,Android App 与插件独立编号、独立发布,版本号无需一致;插件升级不代表 App 需要同步升级。[兼容说明](#兼容性)。
37
37
 
@@ -152,14 +152,16 @@ dsh plugin --profile web add dshmarket
152
152
 
153
153
  页面能打开但一直“重连中”,应先区分 WebSocket 升级失败、慢速数据同步和心跳超时,操作见[慢链路与反复重连指南](docs/SLOW_CONNECTIONS.md)。0.5.4 还提供按确切路径启用的 WebSocket 压缩,默认关闭;长会话或计量网络可按指南尝试,并对比实际传输量。
154
154
 
155
- | 连接方式 | 需要准备 | 地址特点与注意事项 |
156
- | --- | --- | --- |
157
- | cpolar | cpolar 账号与 Authtoken | 国内网络可优先尝试;免费临时地址可能变化 |
158
- | cloudflared 快速隧道 | 无需账号或域名 | 临时随机地址;不支持 SSE,[即时通知存在限制](docs/CLOUDFLARE_TUNNEL.md#快速隧道的功能限制) |
159
- | cloudflared 命名隧道 | Cloudflare 账号与域名 | 固定域名;[配置指南](docs/CLOUDFLARE_TUNNEL.md) |
160
- | Tailscale Funnel | Tailscale 登录与 Funnel 授权 | 中国大陆网络可能不稳定 |
161
- | 自建 FRP | 已有 VPS,域名或真实公网 IPv4 | 需管理服务器;[部署](docs/SELF_HOSTED_FRP.md)或[接入既有 frps](docs/ATTACH_EXISTING_FRPS.md) |
162
- | 自有反向代理 | 已有公开 HTTPS 代理 | 插件只提供私有认证后端;[配置指南](docs/SELF_HOSTED_ORIGIN.md) |
155
+ 下表的使用参考来自维护者在中国大陆自用网络的长期体验,不是普适测评;实际表现受运营商、网络和套餐影响,请在自己的手机网络验证。
156
+
157
+ | 连接方式 | 需要准备 | 地址特点与注意事项 | 维护者使用参考 |
158
+ | --- | --- | --- | --- |
159
+ | cpolar | cpolar 账号与 Authtoken | 免费临时地址可能变化 | 免费方案限速明显,日常加载与同步体验较差 |
160
+ | cloudflared 快速隧道 | 无需账号或域名 | 临时随机地址;不支持 SSE,[即时通知存在限制](docs/CLOUDFLARE_TUNNEL.md#快速隧道的功能限制);[官方仅定位为开发测试用途](https://developers.cloudflare.com/tunnel/get-started/quick-tunnels/) | 长期使用中经常断连,原因尚未确认;只作临时或测试选择 |
161
+ | cloudflared 命名隧道 | Cloudflare 账号与域名 | 固定域名;[配置指南](docs/CLOUDFLARE_TUNNEL.md) | 已长期使用的通道中最稳定;有账号和域名可优先尝试 |
162
+ | Tailscale Funnel | Tailscale 登录与 Funnel 授权 | 实际可用性取决于网络 | 自用网络常需代理辅助,仍可能连接失败;并非所有用户都需要代理 |
163
+ | 自建 FRP | 已有 VPS,域名或真实公网 IPv4 | 需管理服务器;[部署](docs/SELF_HOSTED_FRP.md)或[接入既有 frps](docs/ATTACH_EXISTING_FRPS.md) | 部署与资源更可控,长期稳定性取决于 VPS 线路、配置与运维 |
164
+ | 自有反向代理 | 已有公开 HTTPS 代理 | 插件只提供私有认证后端;[配置指南](docs/SELF_HOSTED_ORIGIN.md) | 可沿用自己的代理设施,稳定性取决于代理、线路与运维 |
163
165
 
164
166
  <p align="center">
165
167
  <img src="https://raw.githubusercontent.com/saya-ch/dsh-mobile/main/assets/screenshots/remote-access.png" width="82%" alt="DSH Mobile 远程访问与通道选择">
@@ -188,7 +190,7 @@ dsh plugin --profile web add dshmarket
188
190
 
189
191
  > **远程通知说明**:浏览器的 `Notification` 权限按 Origin 分别授权,网页系统通知只显示在运行该网页的设备上。Android App 的任务提醒是独立的 0.4.0 功能,需要在 App 内 DSH 的 **设置 → 通用设置** 中主动开启,且依赖 WebView 页面仍存活;它不是通用的后台推送。需要可靠的后台推送时,请使用你已配置的服务端 webhook 或机器人通道。
190
192
 
191
- Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。其运行组件把公开监听生命周期绑定到父进程和受限控制通道;父进程退出、控制通道关闭或显式停止时会结束当前代次并清理资源。cpolar 更适合国内网络;自建 FRP 适合已有 VPS、希望避开公共服务带宽限制的用户。cloudflared 有两种模式:快速隧道不需要账号或登录,但地址随机、每次重连都会变化,[官方定位为测试用途](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/)且无可用性保证,因此只适合临时或验证场景;命名隧道使用 Cloudflare 账号令牌,能保留重启后不变的固定公网域名。中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时可使用公网 IPv4 模式。插件会校验按需下载的固定版本组件;本机托管的配置与程序保存在 `$DSH_HOME/mobile-access/`,可在面板中清理。VPS 侧的文件须按自建连接指南单独清理。
193
+ Funnel 的运行组件把公开监听生命周期绑定到父进程和受限控制通道;父进程退出、控制通道关闭或显式停止时会结束当前代次并清理资源。中国大陆 VPS 上的未备案域名可能被云厂商拦截,此时可使用公网 IPv4 模式。插件会校验按需下载的固定版本组件;本机托管的配置与程序保存在 `$DSH_HOME/mobile-access/`,可在面板中清理。VPS 侧的文件须按自建连接指南单独清理。
192
194
 
193
195
  自建 FRP 的托管部署使用指向 DSH 回环网关的 HTTP vhost:VPS 上的明文 vhost 只允许回环访问,由 Caddy 提供公网 HTTPS。0.4.6 新增“接入既有 frps”,不会安装或改动服务器;公开 CA 档仍需受限 HTTP vhost 与 Caddy,自签档则用 frps TCP 透传至电脑端 HTTPS 网关,须由 0.4.6 Android App 在配对时固定 CA。自签档目前面向公网 IPv4,旧版 App 不支持;两档都不开放任意 FRP 配置。frps 的 `proxyBindAddr` 是全局代理监听设置,不能只为新增 TCP 入口调整而忽略已有明文 vhost。[接入指南](docs/ATTACH_EXISTING_FRPS.md)。
194
196
 
@@ -239,6 +241,8 @@ Tailscale Funnel 覆盖范围广,但在中国大陆网络下可能不稳定。
239
241
 
240
242
  Android App 在“已配对设备”中统一显示电脑及其局域网/远程连接记录。两种方式的授权彼此独立,同一台电脑可能有两条记录。升级会迁移旧凭据,无需重新配对;重新验证变化的地址后,相同身份的记录会合并并保留名称。自签 FRP 使用独立 CA 指纹,可能另成一条记录。Token 和固定的 CA 由 Android Keystore 加密;配对二维码含短期配对信息,不含长期设备 Token 或 CA 私钥。
241
243
 
244
+ 未手动指定标识时,公开证书远程入口的新配对使用电脑保存的随机标识,避免相同安装路径让两台电脑的记录互相覆盖。已有连接无需为此重新配对,也不需要更新 Android App;局域网和自签 FRP 仍按各自的 CA 区分。更新后正常退出并重启 DSH,降级前请先阅读[状态备份注意事项](SECURITY.md#installation-identity-and-device-state-upgrades)。
245
+
242
246
  每条记录显示自定义名称、连接方式、Origin、定期更新的可达状态和最近连接时间。绿色状态点表示“可达”,灰色状态点表示“检测中”“暂不可达”“配对已过期”或“电脑端已移除”;可达性检查直接验证 DSH Gateway,不依赖 ICMP,也不会把暂时断网误判成电脑端撤销。
243
247
 
244
248
  - **启动行为 → 直接进入 DSH**(默认):单设备直接连接;多设备优先连接上次使用的设备,其次按最近连接时间选择仍有效的设备。连接超过有限重试预算后自动回到列表,不会无限转圈。
@@ -362,6 +366,7 @@ flowchart LR
362
366
 
363
367
  ## 故障排查
364
368
 
369
+ - **配对第二台电脑后,第一台被覆盖**:见 [#213](https://github.com/saya-ch/dsh-mobile/issues/213);请升级至插件 0.6.8 或更新版本并正常重启 DSH,再分别扫描受影响电脑当前的二维码。已经被覆盖的 App 本地凭据不能自动找回;仍可连接的旧条目无需重配。若同一地址出现新旧两条记录,确认新条目正常后,只删除 App 中多余的本地条目,不要重置远程访问。升级前请阅读[设备状态备份与降级注意事项](SECURITY.md#installation-identity-and-device-state-upgrades)。
365
370
  - **配对成功后仍反复提示重新配对或续期 `401`**:同一域名上的其他服务或旧 Cookie 可能干扰认证。更新至最新插件及 App,再从当前移动访问入口重试;浏览器重新扫描当前二维码可更新本插件的认证 Cookie,无需清除所有网站数据。若仍失败,查看失败请求与脱敏日志,其他认证或代理错误也可能返回 `401`。
366
371
  - **内测声明无法确认或设置无法保存**:先在电脑端直接打开 DSH 的本机地址,查看终端日志或浏览器 Network 中失败的设置请求。连接诊断不检查设置写入;保留现有配置及 `settings.yaml.imported`,按具体错误排查。相同界面提示可能有不同原因,见 [DSH #860](https://github.com/deepseek-ai/deepseek-harness/discussions/860)。
367
372
  - **设置请求返回 HTTP `403`**:先检查 DSH 的 Host/Origin 信任校验及代理响应。检查 iframe、代理和浏览器扩展是否改变请求来源,使用本机直接访问验证;保持信任检查启用。
@@ -400,6 +405,7 @@ macOS 上局域网、cloudflared、自建 FRP 与自有反向代理可用;Funn
400
405
  | `0.6.4` | `0.2.0-rc.2`(打包安装、隔离配对、移动页面与 WebSocket 工作区读取);另验证 72 组真实公共插槽输入栏布局、Windows Desktop 目录选择器组合及跨平台回归。社区 Desktop 2.0.17 的原报告远程超时仍未宣称验收 |
401
406
  | `0.6.5` | `0.2.0-rc.2`、`0.2.1-alpha.2` 的打包模块管理与移动设置回归;另验证 Windows cpolar 解包及正式 `maid-atelier` 0.1.7 下有内容轨迹的布局修复。不代表全部第三方皮肤或报告者设备已验收 |
402
407
  | `0.6.6` | `0.2.0-rc.2`、`0.2.1-alpha.2` 的打包附件接收与图片预览回归,覆盖多图、取消、迟到结果和旧请求;Android 系统选择器改进独立发布,不包含在 npm 更新中 |
408
+ | `0.6.8` | `0.2.1-alpha.3` 的打包移动页面、旧 WebView API 模拟、WebSocket 工作区读取、移动设置、模块管理、侧栏及输入栏回归;身份修复另有真实 HTTP/TLS 与旧凭据升级测试。本机官方 Desktop `0.2.0-rc.2` 已加载该版本插件,原配对保留;不代表手机 5G 或全部第三方插件组合已验收 |
403
409
 
404
410
  <details>
405
411
  <summary>历史版本验证记录</summary>
package/SECURITY.md CHANGED
@@ -27,6 +27,15 @@ The maintainer will acknowledge a complete report within seven days. Publication
27
27
  - The 0.4.6 existing-frps self-signed entry instead forwards raw TCP to the computer-side HTTPS gateway. Its QR link and bare App key carry a versioned `dsh2` CA-required marker, which Android 0.4.5 and earlier reject. During explicit remote pairing, the 0.4.6 app retrieves the gateway's public CA without sending a credential, checks its validity and SHA-256 fingerprint against the pairing key, and pins it to the exact Origin; HTTP 404, a missing, changed, or untrusted CA cannot downgrade this flow to platform trust. Re-pairing an existing pinned identity retains that CA requirement. The existing exact-Origin device-token restriction still applies: a CA fingerprint alone cannot renew a device credential. The CA is not installed in Android's system trust store. The 0.4.5 app lacks this remote CA-pinning path, and ordinary phone browsers will not trust the self-signed certificate.
28
28
  - Keep pairing closed except during a short local onboarding action.
29
29
  - Revoke a lost device immediately and rotate the device registry if credential theft is suspected.
30
+
31
+ ### Installation identity and device-state upgrades
32
+
33
+ - Automatically identified entries without a pairing CA use the random `installation-id` file in the active Mobile state directory for new pairings, not a hash of the state-file path. Explicit operator IDs and CA-bound LAN/self-signed FRP identities remain unchanged. The identifier is public, not an authentication secret. Copying the entire state directory also copies its identity and credentials; this fix does not distinguish cloned installations. Do not delete the identity file to troubleshoot a connection.
34
+ - Initial identity creation requires a filesystem that supports hard links within the Mobile state directory. A complete private candidate is published without replacing an existing identity, so concurrent starts cannot read a partially written identifier. Unsupported filesystems or invalid identity files fail explicitly; the plugin does not silently generate a replacement. Reading an existing valid identity does not require creating a hard link.
35
+ - Plugin 0.6.8 reads device-state versions 1 and 2 and writes version 2 on the next mutation. New records retain an optional `nativeInstanceId` binding, so authenticated native renewal and probe return the identity used for their pairing. Older unbound records keep the gateway's legacy identity during an ordinary upgrade with the same state path and gateway configuration; moving their state or changing their certificate/operator identity is not covered by this preservation. They are not reassigned from discovery metadata. The native response fields remain unchanged and the published Android App 0.6.3 needs no update for this fix.
36
+ - Stop the old Gateway normally before updating, then restart DSH. Do not run old and new plugins against the same state concurrently. Plugin 0.6.7 and earlier cannot read version 2; if downgrade may be necessary, stop DSH and back up the complete Mobile state directory before upgrading. There is no automatic rollback or data reset. Restoring a pre-upgrade backup is a separate operator action and can also restore credentials revoked since that backup: review and revoke those devices again before exposing the entry.
37
+ - Working pairings do not need to be recreated. A credential already overwritten in the App cannot be reconstructed by the plugin; pair each affected computer again after updating. A usable legacy row may remain beside a new row at the same Origin. Remove a redundant local row only after confirming the new one works; the upgrade does not automatically delete local rows or revoke their computer-side credentials.
38
+
30
39
  ### Remote components, diagnostics and cleanup
31
40
 
32
41
  - Do not expose the LAN gateway through router port forwarding. Funnel, cpolar, cloudflared, and FRP reach a separate loopback gateway through their selected remote path. The own reverse-proxy provider instead exposes a plugin-owned private HTTP origin restricted by its bind address and allowed source CIDRs; the operator's proxy must provide public HTTPS. Funnel, cpolar, cloudflared, Caddy, or the own proxy terminates public TLS in their respective modes; the 0.4.6 self-signed FRP entry terminates TLS on the computer. DSH pairing, device authentication, CSRF checks, and session revocation remain enforced by the plugin gateway.
package/lib/index.d.mts CHANGED
@@ -14,17 +14,21 @@ interface StoredDevice {
14
14
  readonly createdAt: number;
15
15
  readonly expiresAt: number;
16
16
  readonly lastSeenAt: number;
17
+ /** Native pairing identity bound to this credential; older records omit it. */
18
+ readonly nativeInstanceId?: string;
17
19
  /** Legacy tombstone accepted on load; AccessController removes it during initialization. */
18
20
  readonly revokedAt?: number;
19
21
  }
20
22
  /** Versioned device state. Raw device and Session credentials are never members. */
21
23
  interface DeviceSnapshot {
22
- readonly version: 1;
24
+ readonly version: 1 | 2;
23
25
  readonly devices: readonly StoredDevice[];
24
26
  }
25
27
  /** Persistence seam for device-token digests and revocation metadata. */
26
28
  interface DeviceStore {
29
+ /** Read either supported version without assigning identities to legacy rows. */
27
30
  load(): Promise<DeviceSnapshot>;
31
+ /** Validate and persist version 2, retaining any credential identity bindings. */
28
32
  save(snapshot: DeviceSnapshot): Promise<void>;
29
33
  }
30
34
  /** Validate durable data before it can authorize a device. */
@@ -64,10 +68,15 @@ interface AccessControllerOptions {
64
68
  readonly rateLimitWindowMs: number;
65
69
  readonly maxPairingAttempts: number;
66
70
  readonly maxRateLimitKeys: number;
71
+ /** Native identity stored with newly paired device credentials. */
72
+ readonly pairingInstanceId?: string;
73
+ /** Native response identity for older credentials without a stored binding. */
74
+ readonly legacyInstanceId?: string;
67
75
  readonly now?: () => number;
68
76
  }
69
77
  /** Values issued once after pairing; only digests survive the response. */
70
78
  interface PairingResult {
79
+ readonly nativeInstanceId?: string;
71
80
  readonly deviceId: string;
72
81
  readonly deviceToken: string;
73
82
  readonly deviceExpiresAt: number;
@@ -77,6 +86,7 @@ interface PairingResult {
77
86
  }
78
87
  /** Values issued after renewal with the persistent HttpOnly device Cookie. */
79
88
  interface RenewalResult {
89
+ readonly nativeInstanceId?: string;
80
90
  readonly deviceId: string;
81
91
  readonly sessionToken: string;
82
92
  readonly csrfToken: string;
@@ -90,6 +100,7 @@ interface SessionAuthorization {
90
100
  }
91
101
  /** Result of a persistent-device reachability check without opening a Session. */
92
102
  interface DeviceProbeResult {
103
+ readonly nativeInstanceId?: string;
93
104
  readonly deviceId: string;
94
105
  readonly deviceExpiresAt: number;
95
106
  }
@@ -136,6 +147,7 @@ export declare class AccessController {
136
147
  private requireInitialized;
137
148
  private exclusive;
138
149
  private snapshot;
150
+ private nativeIdentity;
139
151
  private emitSessionEnded;
140
152
  private removeSession;
141
153
  private pruneSessions;
@@ -377,6 +389,8 @@ interface ResolvedGatewayConfig {
377
389
  readonly excludedClientModules: readonly string[];
378
390
  readonly websocketCompression: ResolvedWebSocketCompressionConfig;
379
391
  readonly instanceId: string;
392
+ /** Identity returned only for authenticated legacy native credentials without a stored binding. */
393
+ readonly legacyInstanceId?: string;
380
394
  readonly pairingCaFile?: string;
381
395
  readonly tls: TlsConfig;
382
396
  /** Whether the public hop is HTTPS, even when a trusted loopback proxy terminates TLS. */
@@ -1000,6 +1014,8 @@ export declare class MobileAccessGateway {
1000
1014
  private setBrowserDeviceBinding;
1001
1015
  private handlePair;
1002
1016
  private handleRenew;
1017
+ /** CA-backed entries retain their pin; public entries project the authenticated credential's identity. */
1018
+ private nativeResponseInstanceId;
1003
1019
  private handleNativePair;
1004
1020
  private handleNativeRenew;
1005
1021
  private handleNativeProbe;
@@ -2292,7 +2308,7 @@ export declare const name = "dsh-mobile";
2292
2308
  /** The stock WebServer serves the control card; Connection authenticates the loopback DSH origin. */
2293
2309
  export declare const inject: string[];
2294
2310
  /** Reuse remote HTTPS policy while binding a separately validated private HTTP origin. */
2295
- export declare function originGatewayConfig(template: ResolvedGatewayConfig, settings: OriginSettings, stateFile: string, instanceId: string, listenPort?: number): ResolvedGatewayConfig;
2311
+ export declare function originGatewayConfig(template: ResolvedGatewayConfig, settings: OriginSettings, stateFile: string, instanceId: string, listenPort?: number, legacyInstanceId?: string): ResolvedGatewayConfig;
2296
2312
  /** Mount the resident control route and its optional authenticated LAN gateway. */
2297
2313
  export declare function apply(ctx: Context, config: PluginConfig): Promise<void>;
2298
2314
  //#endregion