dsh-zen-remote 1.1.16 → 2.0.1
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/AGENTS.md +54 -9
- package/README.md +249 -190
- package/README.zh-CN.md +247 -201
- package/assets/home.png +0 -0
- package/assets/info.png +0 -0
- package/assets/pairing.png +0 -0
- package/assets/session.png +0 -0
- package/assets/sheet.png +0 -0
- package/cordis.patch.yml +12 -11
- package/cordis.patch.yml.example +24 -9
- package/docs/interface.md +57 -2
- package/docs/remote-access.en.md +99 -58
- package/docs/remote-access.md +99 -42
- package/dsh-push.mjs +67 -54
- package/lan-gate.mjs +88 -20
- package/lib/activity.js +168 -0
- package/lib/activity.js.map +1 -0
- package/lib/admin-routes.js +526 -0
- package/lib/admin-routes.js.map +1 -0
- package/lib/client-pairing.js +224 -0
- package/lib/client-pairing.js.map +1 -0
- package/lib/client-routes.js +674 -0
- package/lib/client-routes.js.map +1 -0
- package/lib/client.js +11498 -5990
- package/lib/config.js +346 -0
- package/lib/config.js.map +1 -0
- package/lib/fetch-route-intercept.js +418 -0
- package/lib/fetch-route-intercept.js.map +1 -0
- package/lib/fingerprint.js +365 -0
- package/lib/fingerprint.js.map +1 -0
- package/lib/http.js +69 -0
- package/lib/http.js.map +1 -0
- package/lib/index.js +675 -54
- package/lib/index.js.map +1 -1
- package/lib/intercept-shape.js +81 -0
- package/lib/intercept-shape.js.map +1 -0
- package/lib/intercept.js +3181 -0
- package/lib/intercept.js.map +1 -0
- package/lib/lan-gate-server.cjs +532 -121
- package/lib/merge-streams.js +1172 -0
- package/lib/merge-streams.js.map +1 -0
- package/lib/relay-access.js +496 -0
- package/lib/relay-access.js.map +1 -0
- package/lib/relay-client.js +1303 -0
- package/lib/relay-client.js.map +1 -0
- package/lib/relay-filter.js +282 -0
- package/lib/relay-filter.js.map +1 -0
- package/lib/relay-server.js +1813 -0
- package/lib/relay-server.js.map +1 -0
- package/lib/restart-fields.js +34 -0
- package/lib/restart-fields.js.map +1 -0
- package/lib/restart-watcher.js +106 -0
- package/lib/restart-watcher.js.map +1 -0
- package/lib/session-reference.js +157 -0
- package/lib/session-reference.js.map +1 -0
- package/lib/share-export.js +16 -1
- package/lib/share-export.js.map +1 -1
- package/lib/share-ops.js +86 -0
- package/lib/share-ops.js.map +1 -0
- package/lib/share-store.js +294 -0
- package/lib/share-store.js.map +1 -0
- package/lib/types/activity.d.ts +99 -0
- package/lib/types/activity.d.ts.map +1 -0
- package/lib/types/admin-routes.d.ts +129 -0
- package/lib/types/admin-routes.d.ts.map +1 -0
- package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
- package/lib/types/client/MobileHome.d.ts +1 -1
- package/lib/types/client/MobileHome.d.ts.map +1 -1
- package/lib/types/client/MobileHomeChips.d.ts +1 -1
- package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
- package/lib/types/client/MobileSessionHeader.d.ts +13 -2
- package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
- package/lib/types/client/MobileSessionInfo.d.ts +20 -3
- package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
- package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
- package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
- package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
- package/lib/types/client/RemoteShareMenu.d.ts +28 -0
- package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
- package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
- package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
- package/lib/types/client/compat/desktop.d.ts +21 -0
- package/lib/types/client/compat/desktop.d.ts.map +1 -0
- package/lib/types/client/compat/types.d.ts +57 -0
- package/lib/types/client/compat/types.d.ts.map +1 -1
- package/lib/types/client/current-session-report.d.ts +37 -0
- package/lib/types/client/current-session-report.d.ts.map +1 -0
- package/lib/types/client/effects/header-status.d.ts.map +1 -1
- package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
- package/lib/types/client/effects/model-group-side.d.ts +13 -0
- package/lib/types/client/effects/model-group-side.d.ts.map +1 -0
- package/lib/types/client/effects/permission-sheet.d.ts +39 -0
- package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
- package/lib/types/client/effects/remote-session.d.ts +25 -0
- package/lib/types/client/effects/remote-session.d.ts.map +1 -0
- package/lib/types/client/effects/turn-fold.d.ts +9 -0
- package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +187 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/remote-fetch.d.ts +51 -0
- package/lib/types/client/remote-fetch.d.ts.map +1 -0
- package/lib/types/client/remote-group-expansion.d.ts +41 -0
- package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client/remote-share-css.d.ts +19 -0
- package/lib/types/client/remote-share-css.d.ts.map +1 -0
- package/lib/types/client/remote-share-register.d.ts +113 -0
- package/lib/types/client/remote-share-register.d.ts.map +1 -0
- package/lib/types/client/remote-status-css.d.ts +15 -0
- package/lib/types/client/remote-status-css.d.ts.map +1 -0
- package/lib/types/client/remote-status-register.d.ts +46 -0
- package/lib/types/client/remote-status-register.d.ts.map +1 -0
- package/lib/types/client/settings/SettingsSection.d.ts +46 -0
- package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
- package/lib/types/client/settings/register-settings.d.ts +58 -0
- package/lib/types/client/settings/register-settings.d.ts.map +1 -0
- package/lib/types/client/settings/settings-css.d.ts +10 -0
- package/lib/types/client/settings/settings-css.d.ts.map +1 -0
- package/lib/types/client/share/share-preview.d.ts.map +1 -1
- package/lib/types/client/styles/composer.css.d.ts +1 -1
- package/lib/types/client/styles/composer.css.d.ts.map +1 -1
- package/lib/types/client/styles/header.css.d.ts +1 -1
- package/lib/types/client/styles/header.css.d.ts.map +1 -1
- package/lib/types/client/styles/info.css.d.ts +1 -1
- package/lib/types/client/styles/info.css.d.ts.map +1 -1
- package/lib/types/client/styles/remote-session.css.d.ts +2 -0
- package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
- package/lib/types/client-data/current-session.d.ts +41 -0
- package/lib/types/client-data/current-session.d.ts.map +1 -0
- package/lib/types/client-data/model-group-side.d.ts +44 -0
- package/lib/types/client-data/model-group-side.d.ts.map +1 -0
- package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
- package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
- package/lib/types/client-data/remote-status.d.ts +148 -0
- package/lib/types/client-data/remote-status.d.ts.map +1 -0
- package/lib/types/client-data/settings-form.d.ts +774 -0
- package/lib/types/client-data/settings-form.d.ts.map +1 -0
- package/lib/types/client-data/shares.d.ts +211 -0
- package/lib/types/client-data/shares.d.ts.map +1 -0
- package/lib/types/client-pairing.d.ts +114 -0
- package/lib/types/client-pairing.d.ts.map +1 -0
- package/lib/types/client-routes.d.ts +112 -0
- package/lib/types/client-routes.d.ts.map +1 -0
- package/lib/types/config.d.ts +200 -0
- package/lib/types/config.d.ts.map +1 -0
- package/lib/types/fetch-route-intercept.d.ts +156 -0
- package/lib/types/fetch-route-intercept.d.ts.map +1 -0
- package/lib/types/fingerprint.d.ts +166 -0
- package/lib/types/fingerprint.d.ts.map +1 -0
- package/lib/types/http.d.ts +41 -0
- package/lib/types/http.d.ts.map +1 -0
- package/lib/types/index.d.ts +56 -24
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/intercept-shape.d.ts +32 -0
- package/lib/types/intercept-shape.d.ts.map +1 -0
- package/lib/types/intercept.d.ts +442 -0
- package/lib/types/intercept.d.ts.map +1 -0
- package/lib/types/merge-streams.d.ts +398 -0
- package/lib/types/merge-streams.d.ts.map +1 -0
- package/lib/types/relay-access.d.ts +310 -0
- package/lib/types/relay-access.d.ts.map +1 -0
- package/lib/types/relay-client.d.ts +274 -0
- package/lib/types/relay-client.d.ts.map +1 -0
- package/lib/types/relay-filter.d.ts +103 -0
- package/lib/types/relay-filter.d.ts.map +1 -0
- package/lib/types/relay-server.d.ts +208 -0
- package/lib/types/relay-server.d.ts.map +1 -0
- package/lib/types/restart-fields.d.ts +22 -0
- package/lib/types/restart-fields.d.ts.map +1 -0
- package/lib/types/restart-watcher.d.ts +83 -0
- package/lib/types/restart-watcher.d.ts.map +1 -0
- package/lib/types/session-reference.d.ts +66 -0
- package/lib/types/session-reference.d.ts.map +1 -0
- package/lib/types/share-export.d.ts +14 -1
- package/lib/types/share-export.d.ts.map +1 -1
- package/lib/types/share-ops.d.ts +79 -0
- package/lib/types/share-ops.d.ts.map +1 -0
- package/lib/types/share-store.d.ts +87 -0
- package/lib/types/share-store.d.ts.map +1 -0
- package/lib/types/virtual-id.d.ts +52 -0
- package/lib/types/virtual-id.d.ts.map +1 -0
- package/lib/virtual-id.js +66 -0
- package/lib/virtual-id.js.map +1 -0
- package/llms.txt +17 -14
- package/package.json +60 -50
- package/scripts/build-client.mjs +5 -3
- package/scripts/check-host-shadow.mjs +19 -2
- package/scripts/check-settings-form.mjs +933 -0
- package/scripts/check-shares.mjs +397 -0
- package/scripts/check-upload-endpoint.mjs +59 -3
- package/scripts/sync-doc-version.mjs +8 -15
- package/scripts/verify-fingerprint-live.mjs +111 -0
- package/src/activity.ts +218 -0
- package/src/admin-routes.ts +627 -0
- package/src/client/MobileAttachButton.tsx +2 -2
- package/src/client/MobileAttachChips.tsx +3 -3
- package/src/client/MobileDrawerFooter.tsx +7 -4
- package/src/client/MobileHome.tsx +20 -16
- package/src/client/MobileHomeChips.tsx +16 -16
- package/src/client/MobileNavOverlay.tsx +2 -2
- package/src/client/MobileNavToggle.tsx +3 -3
- package/src/client/MobileSessionHeader.tsx +63 -42
- package/src/client/MobileSessionInfo.tsx +118 -17
- package/src/client/RemoteComposerBanner.tsx +76 -0
- package/src/client/RemoteHeaderIcon.tsx +103 -0
- package/src/client/RemoteShareMenu.tsx +57 -0
- package/src/client/RemoteStatusIcon.tsx +91 -0
- package/src/client/compat/desktop.ts +22 -0
- package/src/client/compat/types.ts +62 -0
- package/src/client/current-session-report.ts +164 -0
- package/src/client/effects/header-status.ts +21 -17
- package/src/client/effects/keyboard-guard.ts +65 -1
- package/src/client/effects/model-group-side.ts +132 -0
- package/src/client/effects/permission-sheet.ts +149 -0
- package/src/client/effects/remote-session.ts +68 -0
- package/src/client/effects/turn-fold.ts +52 -0
- package/src/client/index.tsx +158 -36
- package/src/client/locales.ts +396 -2
- package/src/client/remote-fetch.ts +125 -0
- package/src/client/remote-group-expansion.ts +190 -0
- package/src/client/remote-share-css.ts +54 -0
- package/src/client/remote-share-register.ts +244 -0
- package/src/client/remote-status-css.ts +64 -0
- package/src/client/remote-status-register.ts +107 -0
- package/src/client/settings/SettingsSection.tsx +1229 -0
- package/src/client/settings/register-settings.ts +116 -0
- package/src/client/settings/settings-css.ts +236 -0
- package/src/client/share/share-preview.tsx +6 -3
- package/src/client/share/share-sheet.tsx +3 -3
- package/src/client/styles/composer.css.ts +119 -26
- package/src/client/styles/header.css.ts +7 -7
- package/src/client/styles/info.css.ts +74 -0
- package/src/client/styles/remote-session.css.ts +274 -0
- package/src/client-data/current-session.ts +57 -0
- package/src/client-data/model-group-side.ts +65 -0
- package/src/client-data/remote-group-expansion.ts +145 -0
- package/src/client-data/remote-status.ts +374 -0
- package/src/client-data/settings-form.ts +1371 -0
- package/src/client-data/shares.ts +467 -0
- package/src/client-pairing.ts +244 -0
- package/src/client-routes.ts +732 -0
- package/src/config.ts +447 -0
- package/src/fetch-route-intercept.ts +510 -0
- package/src/fingerprint.ts +410 -0
- package/src/http.ts +68 -0
- package/src/index.ts +719 -62
- package/src/intercept-shape.ts +87 -0
- package/src/intercept.ts +3510 -0
- package/src/merge-streams.ts +1279 -0
- package/src/relay-access.ts +672 -0
- package/src/relay-client.ts +1514 -0
- package/src/relay-filter.ts +291 -0
- package/src/relay-server.ts +2063 -0
- package/src/restart-fields.ts +36 -0
- package/src/restart-watcher.ts +140 -0
- package/src/session-reference.ts +156 -0
- package/src/share-export.ts +26 -1
- package/src/share-ops.ts +114 -0
- package/src/share-store.ts +373 -0
- package/src/virtual-id.ts +74 -0
package/README.md
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
<h1 align="center">dsh-zen-remote</h1>
|
|
2
|
-
<p align="center">
|
|
2
|
+
<p align="center">One DeepSeek Harness plugin, two roles. The <b>host</b> turns DSH into a phone-reachable PWA (mobile UI, pairing gateway, lock-screen push) and shares chosen sessions to paired desktops. The <b>client</b> runs inside another DSH desktop app and puts the host's shared sessions right into its own sidebar — full history, live progress, messages, approvals, terminal, everything executes on the host.</p>
|
|
3
3
|
|
|
4
4
|
<p align="center">
|
|
5
|
+
<a href="https://www.npmjs.com/package/dsh-zen-remote"><img src="https://img.shields.io/npm/v/dsh-zen-remote?style=flat-square&color=5B4CF0&logo=npm&label=npm" alt="npm version"></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/dsh-zen-remote"><img src="https://img.shields.io/npm/dm/dsh-zen-remote?style=flat-square&color=0B7285&label=downloads" alt="npm downloads"></a>
|
|
7
|
+
<a href="https://github.com/KyoMio/dsh-zen-remote/actions/workflows/publish.yml"><img src="https://img.shields.io/github/actions/workflow/status/KyoMio/dsh-zen-remote/publish.yml?style=flat-square&label=publish&logo=githubactions&logoColor=white" alt="publish workflow"></a>
|
|
5
8
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-0B7285?style=flat-square" alt="MIT"></a>
|
|
6
|
-
<
|
|
7
|
-
<img src="https://img.shields.io/badge/
|
|
9
|
+
<br>
|
|
10
|
+
<img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.7%20%E2%80%93%200.2.x-4D6BFE?style=flat-square" alt="DeepSeek Harness 0.1.7 – 0.2.x">
|
|
11
|
+
<img src="https://img.shields.io/node/v/dsh-zen-remote?style=flat-square&color=339933&logo=nodedotjs&logoColor=white" alt="Node">
|
|
12
|
+
<a href="https://www.npmjs.com/package/dsh-zen-remote#provenance"><img src="https://img.shields.io/badge/npm-provenance%20signed-CB3837?style=flat-square&logo=npm&logoColor=white" alt="npm provenance"></a>
|
|
13
|
+
<img src="https://img.shields.io/badge/PWA-Web%20Push-5A0FC8?style=flat-square&logo=pwa&logoColor=white" alt="PWA + Web Push">
|
|
14
|
+
<img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript strict">
|
|
8
15
|
</p>
|
|
9
16
|
|
|
10
17
|
<p align="center"><a href="README.zh-CN.md">中文文档</a></p>
|
|
@@ -17,215 +24,233 @@
|
|
|
17
24
|
| --- | --- |
|
|
18
25
|
|  |  |
|
|
19
26
|
|
|
20
|
-
> Screenshots are a 390×844 phone viewport in the light theme; both themes are supported. The pairing page is drawn by the gateway
|
|
27
|
+
> Screenshots are a 390×844 phone viewport in the light theme; both themes are supported. The pairing page is drawn by the gateway in DSH's official style, following the system light / dark appearance.
|
|
21
28
|
|
|
22
29
|
---
|
|
23
30
|
|
|
24
|
-
##
|
|
31
|
+
## One plugin, two roles
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
dsh plugin add dsh-zen-remote
|
|
28
|
-
```
|
|
33
|
+
Same package, same plugin row. What runs is decided by the `role` setting in the DSH plugin settings page:
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
| | **Host** (`role: host`, the default) | **Client** (`role: client`) |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| Machine | The one that runs the sessions | Another computer running the DSH desktop app |
|
|
38
|
+
| What it loads | Mobile UI, gateway (child process), Web Push, session sharing, the relay server | The relay client and the sidebar/session-page remote parts |
|
|
39
|
+
| What it does NOT load | — | No gateway, no push, no phone UI — it never fights the host for the gateway port |
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
>
|
|
34
|
-
> 0.1.2 removed `@deepseek-ai/dsh-client-runtime` and put the web UI behind a signed cookie, which would have broken the mobile UI and locked the phone out of the gateway. The plugin carries its own store engine instead of importing that package, probes for the services 0.1.2 moved (`uiWorkspace`, `uiSession`) and falls back to the 0.1.1 shapes when they are absent, reads a turn's events through either `session.events` or `session.snapshotEvents()`, and lets the gateway do the browser-token handshake on the phone's behalf — all of it inert on 0.1.1, where the token endpoint does not exist.
|
|
35
|
-
>
|
|
36
|
-
> One thing to know on 0.1.2: the web UI now rejects requests whose `Host` is not trusted. Reaching DSH through this plugin's gateway is unaffected (the gateway presents every request as `127.0.0.1`), but pointing a browser straight at `http://<LAN-IP>:3080` returns 403 unless you start `dsh web --trusted-host <host>`.
|
|
41
|
+
A paired **desktop client** sees exactly the sessions the host has turned remote access on for, grouped under "server name · workspace name" after the local workspaces. A server workspace only appears once at least one of its sessions has remote access on, so a workspace you never touched never shows up as an empty group; a group that was already showing and later loses all its sessions stays as an empty group until the page is reloaded. Local sessions on the client machine are untouched.
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
---
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
## Remote connection
|
|
46
|
+
|
|
47
|
+
**What it is for**: one always-on computer runs the sessions (the host), and the DSH desktop app on another computer (the client) watches and carries on with them remotely — history, live progress, messages, approvals, the terminal all work, and everything executes on the host. Phones and tablets take a different road: a browser reaching the host through the gateway (a web app device), see [Setting up public access](#setting-up-public-access-host).
|
|
48
|
+
|
|
49
|
+
**How it works**, in four steps:
|
|
50
|
+
|
|
51
|
+
1. **Pair**: on the host's settings page, generate a 桌面应用端 (desktop app device) pairing code; on the client's settings page, enter the server address and the code. Step by step in the [settings page walkthrough](#settings-page-walkthrough) below.
|
|
52
|
+
2. **Turn remote access on**: remote access is per session. On the host, pick 开启远程 (turn remote on) from the session's "…" menu (or use the globe icon in the title row, or the list in the settings page — see [Session sharing](#session-sharing)).
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
3. **Use it on the client**: after the local workspaces, the sidebar shows a "host device name · workspace name" group; sessions in it open like local ones, and the globe icon in the title row shows the connection state (online / offline / version differs). A host workspace with no remote-enabled session never shows up.
|
|
57
|
+
|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
4. **Closing and coming back**: when the host closes remote access, the session idle-sleeps (48 hours without activity by default), or you close it from the client, an open session page shows a "remote closed" banner with input disabled, and the session hides from the list once you switch away; turning remote access back on brings it back. During a disconnect the page holds still and resumes by itself once the link is back.
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
**Toggle it from your phone too**: when a phone (web app device) opens a session on the host, the session info card has a 远程访问 (remote access) row showing whether it is on and the idle time left; the switch turns it on or off.
|
|
65
|
+
|
|
66
|
+
<p align="center"><img src="docs/images/mobile-info-remote.png" alt="The remote access switch in the phone's session info card" width="320"></p>
|
|
67
|
+
|
|
68
|
+
What it can and cannot do: see [Features](#features) and [Known limitations](#known-limitations). A paired desktop client is treated as a trusted device — the trust boundary is in [Security model](#security-model).
|
|
69
|
+
|
|
70
|
+
> In these screenshots the red frames mark what this plugin adds to the DSH interface; session titles are demo names and conversation content is blurred. The UI is shown in Chinese.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
**From the UI** (desktop app and web UI alike; the desktop app is how a 2.0 host actually runs): open the **Plugins** page in the sidebar → **Add plugin** → type `dsh-zen-remote` → click **Enable now** when the install finishes, then restart the desktop app (or `dsh web`).
|
|
77
|
+
|
|
78
|
+
**Web service from the command line** (host on a headless `dsh web` box, also where phone access still points):
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
dsh plugin --profile web add dsh-zen-remote
|
|
82
|
+
# then restart dsh web
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`dsh plugin` requires `--profile` to name the profile to install into and errors out without it. The desktop app's `desktop` profile is managed exclusively by the app — the CLI refuses `--profile desktop` — so on the desktop app use the UI above.
|
|
42
86
|
|
|
43
|
-
|
|
87
|
+
**Editing the profile by hand** (local development, `link:` installs): add the dependency to the profile's `package.json` (`~/.dsh/profiles/desktop` for the desktop app, `~/.dsh/profiles/web` for the web service), install, restart:
|
|
44
88
|
|
|
45
89
|
```jsonc
|
|
90
|
+
// ~/.dsh/profiles/desktop/package.json
|
|
46
91
|
{
|
|
47
92
|
"dependencies": {
|
|
48
|
-
"dsh-zen-remote": "^
|
|
49
|
-
}
|
|
50
|
-
"dsh": { "profile": { "bundles": [
|
|
51
|
-
"@deepseek-ai/dsh-base",
|
|
52
|
-
"@deepseek-ai/dsh-web-app",
|
|
53
|
-
"dsh-zen-remote"
|
|
54
|
-
] } }
|
|
93
|
+
"dsh-zen-remote": "^2.0.1" // for local development: "link:/path/to/dsh-zen-remote"
|
|
94
|
+
}
|
|
55
95
|
}
|
|
56
96
|
```
|
|
57
97
|
|
|
58
98
|
```sh
|
|
59
|
-
cd ~/.dsh/profiles/
|
|
60
|
-
# restart dsh web
|
|
99
|
+
cd ~/.dsh/profiles/desktop && pnpm install
|
|
100
|
+
# then restart the DSH desktop app (or dsh web)
|
|
61
101
|
```
|
|
62
102
|
|
|
63
|
-
|
|
103
|
+
`pnpm install` only installs the package; it does not switch it on. Turn it on in the **Plugins** page, or append `"dsh-zen-remote"` to the end of the `dsh.profile.bundles` array in the same file (leave the existing entries alone). The UI and `dsh plugin` write that entry for you; a hand edit has to add it itself.
|
|
104
|
+
|
|
105
|
+
Either way there is exactly **one** bundle row to mount. Since 2.0.0 the composition layer is a single line — `dsh-zen-remote` — and the main entry loads the gateway and push sub-plugins itself, according to the role. Do **not** re-add the old `dsh-zen-remote-gateway` / `dsh-zen-remote-push` rows: the loader warns and skips them, and two gateway rows would mean two gateway processes fighting over port 3088.
|
|
106
|
+
|
|
107
|
+
<details>
|
|
108
|
+
<summary>Static mount / local development</summary>
|
|
109
|
+
|
|
110
|
+
Copy [`cordis.patch.yml.example`](cordis.patch.yml.example) into your profile's `cordis.patch.yml` with an absolute path — it is the same single row, with an optional `config:` block showing the row fields. A normal install from the UI or with `dsh plugin` never needs it.
|
|
64
111
|
|
|
65
112
|
</details>
|
|
66
113
|
|
|
114
|
+
To uninstall: open dsh-zen-remote in the **Plugins** page and click **Uninstall**, then confirm; on the web service you can also run `dsh plugin --profile web remove dsh-zen-remote`; for a hand-edited install, delete it from the profile's `dependencies` and `dsh.profile.bundles`. Then restart. To also wipe pairing and share state, delete `~/.dsh/lan-gate-state.json`, `~/.dsh/lan-gate.config.json`, `~/.dsh/zen-remote-shares.json` and `~/.dsh/zen-remote-server.json` (the server id — once it is gone, paired clients see a brand-new server).
|
|
115
|
+
|
|
67
116
|
---
|
|
68
117
|
|
|
69
|
-
##
|
|
118
|
+
## Upgrading from 1.x
|
|
70
119
|
|
|
71
|
-
|
|
120
|
+
One-time facts, in order of likelihood to bite:
|
|
72
121
|
|
|
73
|
-
|
|
122
|
+
- **The three bundle rows collapse into one.** Old `dsh-zen-remote-gateway` / `dsh-zen-remote-push` rows left in a profile patch are warned about and skipped by the loader. Any `config:` you had written **on those old rows** no longer applies — move it onto the `dsh-zen-remote` row or, better, set it in the plugin settings page.
|
|
123
|
+
- **If you hand-inserted a `dsh-zen-remote/dsh-push.mjs` line into a profile patch back in 1.0, remove it** — the main entry loads the push half by role now, and a leftover line loads push twice.
|
|
124
|
+
- **`~/.dsh/lan-gate.config.json` still works.** Nothing to migrate; its values show up in the settings page. Priority per field: environment variable > plugin row settings > `lan-gate.config.json` > built-in default. The file is never written or deleted by the plugin.
|
|
125
|
+
- **Paired devices become Web 应用端 automatically** and keep working; no re-pairing.
|
|
126
|
+
- **The admin page moved.** `/lan-gate/admin` is now just a notice pointing at the plugin settings page (it still 403s anything that is not a direct local connection). Pairing codes, device management, push test and sharing all live in the DSH Plugins page → dsh-zen-remote settings block.
|
|
127
|
+
- With no configuration touched at all, the plugin comes up as a host and the gateway, push and phone UI behave exactly as 1.1.x did.
|
|
74
128
|
|
|
75
|
-
|
|
129
|
+
---
|
|
76
130
|
|
|
77
|
-
|
|
78
|
-
<summary><b>nginx</b></summary>
|
|
131
|
+
## Setting up public access (host)
|
|
79
132
|
|
|
80
|
-
|
|
81
|
-
# once, inside the http {} block
|
|
82
|
-
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
|
|
133
|
+
Once installed, the mobile UI already works at `127.0.0.1:3080` on the machine itself. To reach it from outside: put your own TLS-terminating reverse proxy in front of the gateway, verify it with a 403 self-check, then pair devices. The full walkthroughs — nginx / Caddy / Cloudflare Tunnel / Lucky — live in [docs/remote-access.en.md](docs/remote-access.en.md). The short form:
|
|
83
134
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
135
|
+
1. The gateway listens on `127.0.0.1:3088` only; a reverse proxy (nginx, Caddy, Cloudflare Tunnel, Lucky) terminates HTTPS and forwards to it. **Self-check**: from **mobile data** (not home Wi-Fi), open `https://your-domain/lan-gate/admin` — the correct result is a **403**. If you can see the notice page, your proxy is not forwarding `X-Forwarded-*` headers and public requests are being treated as local ones — fix that before continuing.
|
|
136
|
+
2. Open the DSH Plugins page **on the host machine**, open the dsh-zen-remote settings block, pick the device role for the code you are about to mint, and generate an 8-character pairing code (valid 10 minutes, single use).
|
|
137
|
+
3. On the phone, open your HTTPS domain and enter the code on the pairing page. Once paired you land in DSH; identity lives in a long-lived cookie. Use "Add to Home Screen" to install it as an app, grant notification permission, and the agent reaches your lock screen when it needs you.
|
|
87
138
|
|
|
88
|
-
|
|
89
|
-
ssl_certificate_key /etc/letsencrypt/live/dsh.example.com/privkey.pem;
|
|
139
|
+
---
|
|
90
140
|
|
|
91
|
-
|
|
92
|
-
proxy_pass http://127.0.0.1:3088;
|
|
93
|
-
proxy_http_version 1.1;
|
|
141
|
+
## Pairing devices
|
|
94
142
|
|
|
95
|
-
|
|
96
|
-
proxy_set_header Connection $connection_upgrade;
|
|
143
|
+
A pairing code is minted **for one device role**, and the role is checked at redemption — a code offered to the wrong channel is refused with a clear message, is not consumed, and does not count toward the wrong-code lockout.
|
|
97
144
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
145
|
+
| | **Web 应用端 (web app device)** | **桌面应用端 (desktop app device)** |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| Shape | Phones, tablets, browsers — anything through the gateway | A computer running the DSH desktop app with this plugin in its `client` role |
|
|
148
|
+
| Sees | **All** server sessions | **Only** the sessions the host has enabled remote access on (enforced server-side) |
|
|
149
|
+
| How to pair | Open the gateway URL in a browser, enter the code on the pairing page | Fill the server URL + code into the client's plugin settings page |
|
|
101
150
|
|
|
102
|
-
|
|
103
|
-
proxy_read_timeout 3600s;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
```
|
|
151
|
+
**Desktop-client pairing**, step by step:
|
|
107
152
|
|
|
108
|
-
|
|
153
|
+
1. On the host's plugin settings page, choose 桌面应用端 and generate a code.
|
|
154
|
+
2. On the other computer's DSH desktop app, open the plugin settings page, set the role to **client**, enter the server address and the code.
|
|
155
|
+
3. The address must be `https://` unless it is a private-network host — LAN ranges (`192.168.x`, `10.x`, `172.16–31.x`), Tailscale/CGNAT (`100.64.x`), loopback, `localhost` / `*.local`, or IPv6 `::1` / `fc00::/7` / `fe80::/10`. A public address over plain http is refused, so the device token never travels in the clear.
|
|
156
|
+
4. On success the token is stored in the row (a secret field) and the client connects automatically. The settings block shows the connection state — connected / offline / version mismatch / token revoked — and can unpair.
|
|
109
157
|
|
|
110
|
-
|
|
111
|
-
<summary><b>Caddy</b></summary>
|
|
158
|
+
The server-side settings page can rename devices, change a device's role (`set-role`), and revoke one or all. Revocation is immediate: open connections and push subscriptions die with it. Desktop-client devices can never reach any admin route — the gateway only ever forwards them into the relay prefix.
|
|
112
159
|
|
|
113
|
-
|
|
114
|
-
dsh.example.com {
|
|
115
|
-
reverse_proxy 127.0.0.1:3088
|
|
116
|
-
}
|
|
117
|
-
```
|
|
160
|
+
**Device name**: the 设备名称 (device name) field in the settings page's role card — the `serverName` config — is shared by both roles. On a host it is the name that titles the group in other devices' sidebars (default: the computer's name); on a client it is the name registered with the server when pairing. The name syncs both ways by itself: the server renaming itself reaches the clients' group titles in about 15 seconds; a client saving a new name updates the server's device list; the server renaming a client in its device list reaches that client in about 30 seconds. A client pushes its name only when you explicitly save the settings — everything else defers to the server's record, and a rename made while offline is dropped if the process exits before it can be pushed. Names are 1–40 characters.
|
|
118
161
|
|
|
119
|
-
|
|
162
|
+
---
|
|
120
163
|
|
|
121
|
-
|
|
122
|
-
<summary><b>No public IP? Use a Cloudflare Tunnel</b></summary>
|
|
164
|
+
## Session sharing
|
|
123
165
|
|
|
124
|
-
|
|
166
|
+
Remote access is per session, with four toggle entries:
|
|
125
167
|
|
|
126
|
-
|
|
168
|
+
1. 开启远程 / 关闭远程 (remote on / off) in the sidebar session row's "…" menu;
|
|
169
|
+
2. the remote icon in the session title row (click to toggle, with a confirmation; hover shows the remaining idle time; a dot on the icon means a desktop client is watching right now);
|
|
170
|
+
3. the shared-sessions list in the plugin settings page, including a close-all;
|
|
171
|
+
4. the 远程访问 (remote access) switch in the phone's (web app device's) session info card.
|
|
127
172
|
|
|
128
|
-
|
|
129
|
-
2. Run that command on the machine running DSH (it looks like this, with the token from the page):
|
|
173
|
+
**Idle sleep**: a remote-enabled session closes its remote access by itself after **48 hours** (configurable, `idleHours`) with no session activity — turns starting or ending, messages, approvals or question answers, from either the server or a client. Sessions that are mid-turn or waiting on an approval/question are never swept, and merely having a viewer open does not keep one awake. The remaining time is hoverable on the title-row icon and listed in the settings page.
|
|
130
174
|
|
|
131
|
-
|
|
132
|
-
# macOS / Linux: install as a service that starts at boot
|
|
133
|
-
cloudflared service install eyJhIjoi...your-token
|
|
134
|
-
```
|
|
175
|
+
**New sessions**: with `autoShareNewSessions` on, every session the host creates is shared automatically; sessions created **through** the relay (from a client's remote group) are always auto-shared, which is what makes the entry safe. Subagent and fork sessions follow their parent — no per-session toggling.
|
|
135
176
|
|
|
136
|
-
|
|
177
|
+
**What "remote off" means**: an unshared session's list row, history, live progress and approval/question events are all refused or filtered at the relay — not merely hidden in the UI. @-mention candidates list **only shared sessions**, and a `dsh-session:` reference embedded in a message, a queue edit or a slash command is refused for the whole call when it names an unshared session.
|
|
137
178
|
|
|
138
|
-
|
|
139
|
-
| --- | --- |
|
|
140
|
-
| Subdomain / Domain | `dsh` / `example.com` (i.e. `dsh.example.com`) |
|
|
141
|
-
| Service Type | `HTTP` |
|
|
142
|
-
| URL | `127.0.0.1:3088` |
|
|
179
|
+
**What a closed remote looks like on the client**: when the server closes a session's remote access (or the session idle-sleeps), the session is hidden from the client's sidebar list — it counts as archived, so DSH's "show archived" filter brings it back into view. The one exception is the session page you have open right now: it stays where it is, a "remote closed" banner with the reason (server closed / idle sleep / turned off on this machine) appears above the composer, and input is disabled; once you switch to another session, it hides too. When the server turns remote access back on (or re-shares), the session returns to its original workspace and an open page recovers by itself — the banner clears and input comes back.
|
|
143
180
|
|
|
144
|
-
|
|
181
|
+
**Disconnects and recovery**: while the server (or the link) is down, an open remote session page holds its ground — the banner says offline and input is disabled; once the connection is back the page resumes by itself: the session stream and task-list-type panels pull a fresh snapshot automatically, no manual refresh needed. The exceptions are the file-tree changes and terminal panels, which must be reopened after a disconnect.
|
|
145
182
|
|
|
146
|
-
|
|
183
|
+
---
|
|
147
184
|
|
|
148
|
-
|
|
149
|
-
>
|
|
150
|
-
> Cloudflare's free tier supports WebSockets (DSH's conversation stream needs them) and caps a request body at 100MB, above this plugin's 20MB upload default, so nothing is affected.
|
|
185
|
+
## Configuration
|
|
151
186
|
|
|
152
|
-
|
|
153
|
-
</details>
|
|
187
|
+
### Settings page walkthrough
|
|
154
188
|
|
|
155
|
-
|
|
189
|
+
**Where it is**: in DSH, click 插件 (Plugins) in the left bar → click dsh-zen-remote in the plugin list → click dsh-zen-remote once more under 包含的组件 (components); the settings block is below.
|
|
156
190
|
|
|
157
|
-
|
|
191
|
+
**Common controls**: 已覆盖 (overridden) at a field's top right means the value is saved in the plugin settings (overriding the config file or the default); 重置 (reset) falls back to the next layer. Click 保存 (save) at the bottom of the card when done. Fields that need a reload say so, and the plugin reloads itself after saving — no app restart.
|
|
158
192
|
|
|
159
|
-
|
|
193
|
+
**Host**
|
|
160
194
|
|
|
161
|
-
|
|
195
|
+
1. Set 运行角色 (role) to 主服务端 (host) and give it a recognizable 设备名称 (device name), e.g. "书房" (study) — it titles the group in clients' sidebars and shows on the phone. Save.
|
|
162
196
|
|
|
163
|
-
|
|
197
|
+

|
|
164
198
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
open http://127.0.0.1:3088/lan-gate/admin
|
|
168
|
-
```
|
|
199
|
+
2. 网关与反代 (gateway and reverse proxy): keep the defaults for local use and phone access through a proxy; for public access see [Setting up public access](#setting-up-public-access-host). For clients on the LAN to reach the gateway **directly**, set 监听地址 (listen address) to `0.0.0.0` (it must include loopback — the settings page manages the gateway over loopback).
|
|
200
|
+
3. 配对 (pairing): choose Web 应用端 (phones, browsers) or 桌面应用端 (another DSH desktop app), click 生成配对码 (generate code) and enter it on the other device within 10 minutes. The 设备 (devices) list renames devices, changes their role and revokes them.
|
|
169
201
|
|
|
170
|
-
|
|
171
|
-
2. On the phone, open your HTTPS domain and enter the code on the pairing page;
|
|
172
|
-
3. Once paired you land in DSH. Identity lives in a long-lived cookie, so switching networks doesn't log you out;
|
|
173
|
-
4. Use the browser's "Add to Home Screen" to install it as an app;
|
|
174
|
-
5. Grant notification permission, and the agent reaches your lock screen when it needs you.
|
|
202
|
+

|
|
175
203
|
|
|
176
|
-
|
|
204
|
+
4. 远程共享 (remote sharing): the idle-sleep window and whether new sessions are shared automatically. Below the card, 已开启远程的会话 (remote-enabled sessions) lists what is shared right now, the idle time left and whether a device is watching; close them one by one or all at once.
|
|
177
205
|
|
|
178
|
-
|
|
206
|
+

|
|
179
207
|
|
|
180
|
-
|
|
208
|
+
5. 推送 (push): push summaries, turn-end pushes and so on, as you like — see [When notifications fire](#when-notifications-fire).
|
|
181
209
|
|
|
182
|
-
|
|
210
|
+
**Client**
|
|
183
211
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
| `LAN_GATE_TARGET_PORT` | `3080` | Local DSH Web UI port |
|
|
189
|
-
| `LAN_GATE_RATE_LIMIT` | `120` | Per-minute cap on unpaired requests, counted per real client IP |
|
|
190
|
-
| `LAN_GATE_TRUSTED_PROXIES` | empty | Comma-separated IPs; required when the proxy is on another machine |
|
|
191
|
-
| `LAN_GATE_VAPID_SUBJECT` | `mailto:admin@localhost` | Push contact. **On iOS this must be a real email or https URL**, or Apple refuses to deliver |
|
|
192
|
-
| `LAN_GATE_LANG` | `auto` | Language of the pairing page, the admin page and the push opt-in card. `auto` follows the browser's `Accept-Language` (falling back to Chinese when there is none); `zh`/`en` pin it |
|
|
193
|
-
| `DSH_PUSH_TURN_END` | **off** | Set `1` to also push when a turn ends. Off by default — a finished turn doesn't mean you're needed (it pushed by default before 1.0.3; this was a behaviour change). Approval-pending and question-pending notifications are unaffected and always fire |
|
|
194
|
-
| `DSH_PUSH_EVENTS` | `agent/turn-stopping` | Which events count as "turn ended", comma-separated; only meaningful with `DSH_PUSH_TURN_END=1` |
|
|
195
|
-
| `DSH_PUSH_DEBOUNCE_MS` | `15000` | Minimum gap between two automatic pushes; approval/question notifications are never suppressed by it |
|
|
196
|
-
| `DSH_PUSH_SUMMARY` | off | Set `1` to include this turn's final reply (prose only, never the reasoning; clipped to 120 chars) and the question text |
|
|
197
|
-
| `DSH_PUSH_TOOL` | on | Set `0` to remove the model-callable `push_notify` tool |
|
|
198
|
-
| `DSH_PUSH_LANG` | `zh` | Language of the notification copy. A notification carries no signal about who will read it, and the host process has no reliable system locale either (launchd starts it without `LANG`), so this is not autodetected: set `en` for English |
|
|
199
|
-
| `DSH_PUSH_APPROVAL_GRACE_MS` | `5000` | How long to wait before pushing "approval pending". With a plugin that answers approvals automatically (dsh-auto-approve and the like) installed, the push waits for its verdict — answered means no push. A slower judge still gets pushed over, so raise this if yours is |
|
|
200
|
-
|
|
201
|
-
The upload size limit (20MB by default) is `config.maxUploadBytes` on the plugin row.
|
|
202
|
-
|
|
203
|
-
To enable turn-process folding on desktop too (it only applies at phone widths by default), set `config.turnFoldDesktop` to `true` on the plugin row — that is, add this to the profile's `cordis.patch.yml`:
|
|
204
|
-
|
|
205
|
-
```yaml
|
|
206
|
-
- id: dsh-zen-remote
|
|
207
|
-
config:
|
|
208
|
-
turnFoldDesktop: true
|
|
209
|
-
```
|
|
212
|
+
1. Set 运行角色 (role) to 子客户端 (client) and save — the plugin reloads and the page switches to the client's settings.
|
|
213
|
+
2. Give this computer a 设备名称 (device name), e.g. "客厅" (living room); it is registered in the host's device list when pairing. Save.
|
|
214
|
+
3. 连接服务端 (connect to server): enter the host's gateway address (on a LAN e.g. `http://192.168.1.10:3088`; public addresses must be `https://`) and a 桌面应用端 pairing code from the host, then click 配对 (pair).
|
|
215
|
+
4. Done when 连接状态 (connection state) reads 已连接到「host name」 (connected to "host name"); the host's groups appear in the sidebar right away. The 诊断 (diagnostics) area lists the request interceptor state and recent remote-call failures — the first place to look when something is off.
|
|
210
216
|
|
|
211
|
-
|
|
217
|
+

|
|
212
218
|
|
|
213
|
-
|
|
219
|
+
> Addresses and pairing codes in the screenshots are demo values.
|
|
214
220
|
|
|
215
|
-
|
|
216
|
-
| --- | --- | --- | --- |
|
|
217
|
-
| `keyboardLiftRatio` | `0.42` | Estimate the lift as this fraction of screen height | 0 – 1 |
|
|
218
|
-
| `keyboardLiftMaxPx` | `400` | Cap on the estimate (pixels), so the input box doesn't end up mid-screen on a tall phone | 0 – 2000 |
|
|
219
|
-
| `keyboardSafetyPadPx` | `15` | A little extra clearance above the keyboard, **Android only**. Third-party IMEs routinely under-report their own height (leaving out the toolbar strip above the keys); this makes up for it | 0 – 200 |
|
|
221
|
+
### Field reference
|
|
220
222
|
|
|
221
|
-
|
|
222
|
-
- id: dsh-zen-remote
|
|
223
|
-
config:
|
|
224
|
-
keyboardLiftRatio: 0.45
|
|
225
|
-
keyboardSafetyPadPx: 30
|
|
226
|
-
```
|
|
223
|
+
Everything is edited in the DSH Plugins page → dsh-zen-remote settings block. Per field the first legal value wins in this order: **environment variable > plugin row settings > `~/.dsh/lan-gate.config.json` > default**. A value that fails its field's check (type, range, enum) makes that layer transparent. Saving a field that needs a gateway/push restart (`role`, `port`, `host`, `targetPort`, `rateLimit`, `trustedProxies`, `vapidSubject`, `lang`, and the push fields) reloads the plugin row automatically — no app restart required; everything else takes effect immediately.
|
|
227
224
|
|
|
228
|
-
|
|
225
|
+
Row fields (matching `src/config.ts`):
|
|
226
|
+
|
|
227
|
+
| Field | Default | What it does |
|
|
228
|
+
| --- | --- | --- |
|
|
229
|
+
| `role` | `host` | `host` runs gateway + push + sharing; `client` connects to a server. Anything not exactly `client` degrades to `host` |
|
|
230
|
+
| `port` | `3088` | Gateway port; if taken it retries upward (up to +20) |
|
|
231
|
+
| `host` | `127.0.0.1` | Gateway listen address; only open this up when the proxy is on another machine |
|
|
232
|
+
| `targetPort` | *(auto)* | Local DSH Web UI port. Leave unset: the gateway discovers the host's real listening port (desktop builds use a configurable one) |
|
|
233
|
+
| `rateLimit` | `120` | Per-minute cap on unpaired requests, per real client IP |
|
|
234
|
+
| `trustedProxies` | empty | Comma-separated IPs; required when the proxy is on another machine |
|
|
235
|
+
| `vapidSubject` | `mailto:admin@localhost` | Push contact. **On iOS this must be a real email or https URL**, or Apple refuses to deliver |
|
|
236
|
+
| `lang` | `auto` | Language of the gateway's pages, the push opt-in card and the notification copy. `auto` follows the browser's `Accept-Language` (falling back to Chinese); `zh`/`en` pin it |
|
|
237
|
+
| `pushTurnEnd` | off | Also push when a turn ends. Off by default — a finished turn doesn't mean you're needed. Approval/question pushes are unaffected and always fire |
|
|
238
|
+
| `pushEvents` | `agent/turn-stopping` | Which events count as "turn ended", comma-separated; only meaningful with `pushTurnEnd` |
|
|
239
|
+
| `pushDebounceMs` | `15000` | Minimum gap between two automatic pushes; approval/question notifications are never suppressed by it |
|
|
240
|
+
| `pushSummary` | off | Include the turn's final reply (prose only, never the reasoning; clipped to 120 chars) and the question text in the body |
|
|
241
|
+
| `pushTool` | on | Set `false` to remove the model-callable `push_notify` tool |
|
|
242
|
+
| `serverName` | computer name | **Device name** (≤ 40 chars), shared by both roles: on a host it is the display name in clients' group titles; on a client it is the name registered when pairing. Syncs both ways with the server automatically |
|
|
243
|
+
| `idleHours` | `48` | Idle-sleep window in hours for remote-enabled sessions; range (0, 8760] |
|
|
244
|
+
| `autoShareNewSessions` | off | Auto-enable remote on every session the host creates |
|
|
245
|
+
| `serverUrl` *(client)* | empty | The server's gateway address, normalized per the rules above |
|
|
246
|
+
| `deviceToken` *(client)* | empty | The paired device token; stored as a secret field, redacted everywhere |
|
|
247
|
+
| `turnFoldDesktop` | off | Turn-process folding at every width, not just phone widths |
|
|
248
|
+
| `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx` | `0.42` / `400` / `15` | Soft-keyboard lift calibration for browsers that never report the keyboard (see the known-issues note below) |
|
|
249
|
+
| `maxUploadBytes` | 20 MB | Attachment upload cap |
|
|
250
|
+
|
|
251
|
+
The legacy environment variables keep working and outrank the row: `LAN_GATE_PORT`, `LAN_GATE_HOST`, `LAN_GATE_TARGET_PORT`, `LAN_GATE_RATE_LIMIT`, `LAN_GATE_TRUSTED_PROXIES`, `LAN_GATE_VAPID_SUBJECT`, `LAN_GATE_LANG`, `DSH_PUSH_TURN_END`, `DSH_PUSH_EVENTS`, `DSH_PUSH_DEBOUNCE_MS`, `DSH_PUSH_SUMMARY`, `DSH_PUSH_TOOL`, `DSH_PUSH_LANG` (notification-copy override; without it `lang` decides), and `DSH_PUSH_APPROVAL_GRACE_MS` (how long to wait before pushing "approval pending" so a faster auto-approver's verdict wins; default 5000; it and the file-only `pushApprovalGraceMs` key are not part of the settings surface). `LAN_GATE_RELAY_SECRET` is **not** settable: the plugin mints it per load and hands it to the gateway itself.
|
|
252
|
+
|
|
253
|
+
The upload-size, keyboard-calibration and turn-fold knobs behave exactly as in 1.x — just moved from "plugin row YAML" to "settings page" (a hand-edited row works too).
|
|
229
254
|
|
|
230
255
|
---
|
|
231
256
|
|
|
@@ -240,62 +265,79 @@ By default only when you are **actually needed**, along two independent lines.
|
|
|
240
265
|
| A tool is waiting for your authorization | "DSH needs your approval", with the tool name |
|
|
241
266
|
| The model called `ask_user_question` and is waiting | "DSH is waiting for your answer" |
|
|
242
267
|
|
|
243
|
-
Neither looks at session depth — a subagent stuck on an approval still calls out, because it's still you it's waiting for. Neither is suppressed by
|
|
268
|
+
Neither looks at session depth — a subagent stuck on an approval still calls out, because it's still you it's waiting for. Neither is suppressed by the debounce either: "something needs your nod" is the one notification that must never be swallowed. Since 2.0.0 this includes sessions running inside the desktop app: the host-side hook fires wherever the session lives.
|
|
244
269
|
|
|
245
|
-
**Timing when a machine answers**: approval events arrive as "record asked → consult the answerer → record decided", so the push
|
|
246
|
-
|
|
247
|
-
Approvals a policy waves through don't disturb you: after the request lands, the push waits 1.5s and cancels if the matching "decided" arrives. Only genuinely unattended ones go out.
|
|
270
|
+
**Timing when a machine answers**: approval events arrive as "record asked → consult the answerer → record decided", so the push waits `DSH_PUSH_APPROVAL_GRACE_MS` (5 seconds by default) and skips anything answered within it. Raise it if you run a slower model-backed approver (measured: 2.4s average). Approvals a policy waves through don't disturb you.
|
|
248
271
|
|
|
249
272
|
**2. Decided by the model**
|
|
250
273
|
|
|
251
|
-
The `push_notify` tool. The model should call it when you explicitly asked to be told when something finished, when it needs you to continue, or when something unexpected happened that you'd probably want to know right away. The tool description also spells out when *not* to call it
|
|
252
|
-
|
|
253
|
-
**What does not fire by default**
|
|
274
|
+
The `push_notify` tool. The model should call it when you explicitly asked to be told when something finished, when it needs you to continue, or when something unexpected happened that you'd probably want to know right away. The tool description also spells out when *not* to call it, and the same guidance is injected as standing session context from one shared constant, so the two cannot drift apart.
|
|
254
275
|
|
|
255
|
-
|
|
256
|
-
- **A subagent finishing never pushes**, regardless of that switch.
|
|
276
|
+
**What does not fire by default**: a plain finished turn does not push (set `pushTurnEnd` / `DSH_PUSH_TURN_END=1` for the old behaviour); a subagent finishing never pushes, regardless of that switch.
|
|
257
277
|
|
|
258
|
-
**What's in a notification**: by default the title only
|
|
278
|
+
**What's in a notification**: by default the title only. With `pushSummary` the body carries this turn's final reply — prose only, never the reasoning; a turn that produced no prose falls back to "Last executed: <tool>". The push payload is aes128gcm end-to-end encrypted.
|
|
259
279
|
|
|
260
280
|
---
|
|
261
281
|
|
|
262
282
|
## Features
|
|
263
283
|
|
|
284
|
+
**Host (phone access, unchanged in spirit from 1.x)**
|
|
285
|
+
|
|
264
286
|
- Two-level page stack — session list home plus a standalone session page, pushed in and out horizontally
|
|
265
287
|
- Plugin entry chips on the home screen, appearing automatically for what you have installed, individually hideable
|
|
266
288
|
- Reworked composer: controls become icons, the permission and model menus become bottom sheets
|
|
267
|
-
- Session info card: six stats plus export log / rename / fork / archive
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
289
|
+
- Session info card: six stats plus export log / rename / fork / archive; share-image export as ONE long PNG; since 2.0 a remote access switch row
|
|
290
|
+
- Turn-process folding, left-edge swipe back, Android back-gesture takeover, phone-local attachment upload
|
|
291
|
+
- A pairing code buys a long-lived device token; identity follows the token, not the IP, revocable at any time
|
|
292
|
+
- Real PWA + real Web Push (VAPID + aes128gcm), firing only for approvals and questions by default
|
|
293
|
+
|
|
294
|
+
**Host (2.0 additions)**
|
|
295
|
+
|
|
296
|
+
- Session sharing with four toggle entries (including the phone info card), idle sleep, auto-share and subagent/fork following
|
|
297
|
+
- Everything above works when DSH runs as the **desktop app**: the gateway forwards to the desktop backend's real port automatically, and approval/question pushes fire for desktop sessions
|
|
298
|
+
|
|
299
|
+
**Desktop client (2.0 additions)**
|
|
300
|
+
|
|
301
|
+
- Remote groups in the sidebar ("server name · workspace name"), remote sessions open like local ones: full history, live progress, messages, cancels, queue edits, approvals and question answers, model selection, file tree, changes list plus changes summary/diff, goals, slash commands, agent presets, subagent prompts/interrupts, attachments and @ references, terminals running server-side
|
|
302
|
+
- Remote sessions can attach **non-image files**: the upload request is caught by the sub-client's own backend and streamed through the relay to the server session; the 100 MiB cap is the relay's own (local uploads have no such limit), an over-limit pick is refused locally on the client with a readable message, and slow links are bounded by the 300-second timeout
|
|
303
|
+
- The model picker in a remote session lists the **server's** model catalog (groups named "server name · group name"), local sessions list only local models; the last-fetched server groups survive while the relay is offline; server groups never appear in local settings such as the vision router. Known boundary: a session created on the server that has never sent a message shows the local default model in the picker
|
|
304
|
+
- New sessions created inside a remote group run on the server's workspace and are shared automatically; remote sessions can be renamed, archived, pinned, forked, and their remote access closed (with confirmation) from the client
|
|
305
|
+
- Title-row connection icon per remote session (online / offline / version differs); a "remote closed" banner with the reason when a session is slept or closed server-side; offline groups go grey with input disabled and reconnect automatically with backoff
|
|
306
|
+
- First-come-first-served approvals/questions: if the server's own UI answers first, the client syncs to "already handled"
|
|
307
|
+
|
|
308
|
+
In depth: [interface](docs/interface.md) · [public access + relay protocol](docs/remote-access.en.md)
|
|
280
309
|
|
|
281
310
|
---
|
|
282
311
|
|
|
283
|
-
##
|
|
312
|
+
## Version tolerance
|
|
284
313
|
|
|
285
|
-
|
|
314
|
+
Three layers, strictest first:
|
|
286
315
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
| [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | The AgentTeams activity overlay moves below the session header (its original position covered the header buttons) and hides itself on the session list; a subagent session keeps a tappable parent-session title in its header for jumping back | 0.1.9 |
|
|
291
|
-
| [@ychris12138/dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) | Usage and balance entries fold into the home-screen chips row | 0.2.9 |
|
|
292
|
-
| [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins) | The scheduled-tasks entry folds into the home-screen chips row | 0.2.3 |
|
|
293
|
-
| dsh-at-file | `@` file references, used alongside attachment upload's `@` paths. It and this plugin's attachment chips read the same draft token, so on the phone its `.dsh-uploads/` rows are hidden to stop one file being drawn twice (thumbnail plus filename); other `@` references are left alone | 0.6.7 |
|
|
294
|
-
| [@ace-zone/dsh-market](https://www.npmjs.com/package/@ace-zone/dsh-market) | The plugin market dialog's top bar doesn't fit on a phone and the × gets squeezed out of the panel (no Esc on a touchscreen, so it can't be closed at all). Three decorative slots — tagline, version, homepage link — are hidden, the title shrinks with an ellipsis, and the language switch and × stay with a bigger tap target | 0.1.66 |
|
|
295
|
-
| [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit) | Image Q&A / OCR, used with phone-side attachment upload | — |
|
|
296
|
-
| [dsh-web-ui suite](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all) | Inherits the compatibility rules from upstream dsh-web-mobile (file tree, width-capped centred preview overlay, and so on) | — |
|
|
316
|
+
1. **Relay protocol version** must match — in practice, **both ends need zen-remote 2.0.x**. A 1.x server's gateway doesn't know the client's bearer token and answers its requests with the generic pairing wall, which the client reports as "token invalid (or the server's zen-remote is older than 2.0.0)". DSH versions may differ.
|
|
317
|
+
2. **DSH interface fingerprints** decide the rest: both ends compute normalized fingerprints of the remote-interface groups the relay uses and compare them at handshake. Identical is fully compatible even when version strings differ; a difference connects anyway, annotates the remote group "(版本有差异)" (version differs), lists the differing groups in the settings diagnostics, and degrades only the affected panels.
|
|
318
|
+
3. **Runtime degradation**: a forwarded call that fails parameter/result validation shows "incompatible with the server version" in that one panel and is recorded in the diagnostics — everything else keeps working.
|
|
297
319
|
|
|
298
|
-
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Security model
|
|
323
|
+
|
|
324
|
+
What a paired device is trusted with, stated plainly:
|
|
325
|
+
|
|
326
|
+
- **A paired desktop client is a trusted device.** The remote session's terminal is a shell running as the **server's user**, without the agent sandbox or approval restrictions. The file-preview interfaces (`workspaceFiles/read` and friends) are **not** contained to the session directory — anything the server process can read is readable. A paired client can create sessions in **any** of the server's workspaces (not limited to remote-enabled ones; the created session is auto-shared).
|
|
327
|
+
- **"Only remote-enabled sessions are visible" constrains session data**: an unshared session's list, history, live progress and approval/question events are refused or filtered by the relay; @-mention candidates list **only shared sessions**; and a `dsh-session:` reference embedded in a message, a queue edit or a slash command refuses the whole call when it names a session without remote access. It is not a sandbox around the machine.
|
|
328
|
+
- **Admin actions stay local**: generating pairing codes, changing roles and revoking devices only work from the settings page on the server machine itself — requests that arrived through the gateway can view status but mutations are refused — the one exception is toggling a session's remote access, which a web app device (a phone) may do, and that is what the phone info card's remote access switch uses; desktop clients cannot reach the admin routes at all.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Known limitations
|
|
333
|
+
|
|
334
|
+
- With several DSH windows open at the same time (including the desktop app's backend page opened in a browser), "which session is currently open" can be judged wrongly on a client: a closed-remote session may hide at the wrong moment, and in the worst case a session you have open is taken for a background one and dismissed to the home page.
|
|
335
|
+
- Switching to a different server and back to the original one — or a server deleting a workspace and recreating one under the same id — leaves the affected remote groups invisible until the client page is **reloaded** (DSH's sidebar never re-accepts a removed workspace id within one page lifetime).
|
|
336
|
+
- "Export session" is disabled in a remote session (the menu item is hidden, and the backend refuses virtual session ids); the changes panel's **summary and diff do work** (relayed), while the entries that would pop a dialog on the server machine — "open", "reveal in Finder", "open in app", on the changes panel and deliverable cards alike — are hidden there.
|
|
337
|
+
- The host truncates @-mention candidates to the **first 50 rows before** share-filtering: on a server with many sessions, a shared one may be missing from the candidate list (the reference check itself is unaffected — references you type out are still verified one by one).
|
|
338
|
+
- Third-party plugins' own non-standard endpoints are not forwarded — their panels degrade or hide in remote sessions. A plugin's **tools** run on the server and are unaffected; what breaks is the part of its UI that calls the plugin's own backend with a session id (dsh-better-sidebar is the prime example, handled by the next bullet).
|
|
339
|
+
- With [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) installed, its file browser, editor, git, tasks, bottom workbench and similar surfaces are hidden in remote sessions — they read and write the **client machine's own** files; and because it takes over the host's "Files" tab, a remote session has no sidebar file browser for now; file links in chat or deliverables open an empty right column in a remote session.
|
|
340
|
+
- Both ends need zen-remote **2.0.x** (identical relay protocol version). DSH versions may differ; interface differences are annotated "(版本有差异)".
|
|
299
341
|
|
|
300
342
|
---
|
|
301
343
|
|
|
@@ -303,23 +345,40 @@ The technical detail behind each adaptation — anchor selectors, breakpoints, w
|
|
|
303
345
|
|
|
304
346
|
**iOS 26.x standalone PWA viewport shrinkage**: after adding to the home screen, the viewport loses a status-bar's height at the bottom; an ordinary Safari tab is fine. This is an iOS defect — the missing region is outside the document and CSS cannot reach it. The plugin ships three layers of mitigation (light manifest background, safe-area compensation, forced reflow) which reduce it without guaranteeing a fix. Only quitting and reopening the whole app restores it fully.
|
|
305
347
|
|
|
306
|
-
**In a few environments the soft keyboard is completely invisible to the browser, and the lift falls back to an estimate**: in some combinations
|
|
348
|
+
**In a few environments the soft keyboard is completely invisible to the browser, and the lift falls back to an estimate**: in some combinations the system never tells the page the keyboard's height (visualViewport and the VirtualKeyboard API both fail; both ruled out by measurement). The fallback probes for ~1.2s after focus and lifts the input box by an estimated height when the keyboard is judged invisible. If the lift is visibly off, tune `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx` in the settings page. Normal environments never take this path.
|
|
307
349
|
|
|
308
|
-
**Settings pages don't open through the reverse proxy (the plugin config list is blank, model cards report "settings are unavailable in this browser")
|
|
350
|
+
**Settings pages don't open through the reverse proxy** (the plugin config list is blank, model cards report "settings are unavailable in this browser"): DSH's settings RPCs are loopback-only by design — a remote browser's settings mirror starts out `unavailable`. Workaround: change settings from a browser on the machine running DSH. The gateway-forwarded plugin settings page shows the same limitation. Nothing to do with this plugin.
|
|
309
351
|
|
|
310
|
-
|
|
352
|
+
---
|
|
311
353
|
|
|
312
|
-
|
|
354
|
+
## Permissions and data
|
|
355
|
+
|
|
356
|
+
- **Network**: the gateway listens on the local machine only (`127.0.0.1:3088` by default); what gets exposed is entirely up to your reverse proxy or tunnel. Push travels through the browser vendor's push service (aes128gcm end-to-end encrypted). The relay between a desktop client and the host runs over the same gateway; its authorization is re-checked per request server-side. The plugin itself reports nothing to any third party.
|
|
357
|
+
- **Files**: attachment uploads are written only to `.dsh-uploads/` inside the current session's working directory; pairing state lives in `~/.dsh/lan-gate-state.json`, config in `~/.dsh/lan-gate.config.json`, the share table in `~/.dsh/zen-remote-shares.json`, the server id in `~/.dsh/zen-remote-server.json`.
|
|
358
|
+
- **Credentials**: no account or password is ever collected or stored; a device's identity is a random token this plugin issues itself (HttpOnly cookie for web devices, a secret row field for desktop clients), plus a per-load relay shared secret that never leaves the machine pair.
|
|
359
|
+
|
|
360
|
+
Troubleshooting: runtime logs are in `~/.dsh/logs/web.log` (gateway and push lines are prefixed `[dsh-zen-remote-*]`; desktop builds print to the app console). Security issues: please report privately through GitHub Security Advisories rather than opening a public issue.
|
|
313
361
|
|
|
314
362
|
---
|
|
315
363
|
|
|
316
|
-
##
|
|
364
|
+
## Third-party plugins with mobile support
|
|
365
|
+
|
|
366
|
+
The mobile UI has specific adaptations for the plugins below. Every adaptation is anchored on that plugin's own DOM markers: if you don't have it installed the rules simply don't match, and installing a plugin that isn't listed here can't be caught in the crossfire.
|
|
317
367
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
-
|
|
368
|
+
| Plugin | What the mobile adaptation does | Version tested |
|
|
369
|
+
| --- | --- | --- |
|
|
370
|
+
| [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) | 0.19+: all its tabs live in DSH 0.1.5's native right sidebar, which the host takes full screen on a phone (padded clear of the notch / home indicator) — the session header keeps an entry button for it, the left-edge swipe-back closes it, and tapping an @-reference in the file tree closes it. ≤ 0.18 (own right panel, DSH < 0.1.5 only): full-width phone drawer with notch safe area and a centred close pill | 0.19.1 (legacy rules: 0.15.0) |
|
|
371
|
+
| [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | The AgentTeams activity overlay moves below the session header (its original position covered the header buttons) and hides itself on the session list; a subagent session keeps a tappable parent-session title in its header for jumping back | 0.1.9 |
|
|
372
|
+
| [@ychris12138/dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) | Usage and balance entries fold into the home-screen chips row | 0.2.9 |
|
|
373
|
+
| [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins) | The scheduled-tasks entry folds into the home-screen chips row | 0.2.3 |
|
|
374
|
+
| dsh-at-file | `@` file references, used alongside attachment upload's `@` paths. It and this plugin's attachment chips read the same draft token, so on the phone its `.dsh-uploads/` rows are hidden to stop one file being drawn twice (thumbnail plus filename); other `@` references are left alone | 0.6.7 |
|
|
375
|
+
| [@ace-zone/dsh-market](https://www.npmjs.com/package/@ace-zone/dsh-market) | The plugin market dialog's top bar doesn't fit on a phone and the × gets squeezed out of the panel (no Esc on a touchscreen, so it can't be closed at all). Three decorative slots — tagline, version, homepage link — are hidden, the title shrinks with an ellipsis, and the language switch and × stay with a bigger tap target | 0.1.66 |
|
|
376
|
+
| [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit) | Image Q&A / OCR, used with phone-side attachment upload | — |
|
|
377
|
+
| [dsh-web-ui suite](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all) | Inherits the compatibility rules from upstream dsh-web-mobile (file tree, width-capped centred preview overlay, and so on) | — |
|
|
321
378
|
|
|
322
|
-
|
|
379
|
+
The technical detail behind each adaptation — anchor selectors, breakpoints, what was traded away — is in the "compatible plugins" section of the [interface doc](docs/interface.md).
|
|
380
|
+
|
|
381
|
+
---
|
|
323
382
|
|
|
324
383
|
## Upstream credits
|
|
325
384
|
|