ds-harness-remote 0.4.18 → 0.4.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,13 @@
1
1
  # DeepSeek Harness Remote
2
2
 
3
- Continue your DeepSeek Harness sessions and experimental Codex workspaces from another device over an end-to-end encrypted connection.
3
+ Continue DeepSeek Harness sessions and experimental Codex workspaces from another device over an end-to-end encrypted connection.
4
4
 
5
5
  [GitHub](https://github.com/liguobao/ds-harness-remote) · [Full guide](https://github.com/liguobao/ds-harness-remote#readme) · [中文说明](https://github.com/liguobao/ds-harness-remote/blob/main/README.zh.md) · [Remote Web](https://dsh.r2049.cn/app) · [Android](https://github.com/liguobao/ds-harness-remote/releases/latest)
6
6
 
7
- `ds-harness-remote` is the Remote Host and workspace plugin for DSH. Harness keeps running on your work computer with its existing workspaces, tools, and permission controls; Remote gives your authorized devices another window into that environment.
7
+ `ds-harness-remote` is the Remote Host and workspace plugin for DeepSeek Harness. Harness keeps running on your work computer with its existing workspaces, tools, and permission controls; Remote gives authorized devices another window into that environment.
8
8
 
9
9
  > Install this package with `dsh plugin`, not `npm install`. The DSH command updates the selected profile and adds the required bundle configuration.
10
10
 
11
- ## Highlights
12
-
13
- - Continue active sessions and review progress from another computer, the web, or Android
14
- - Send text and image prompts, answer questions, and handle permission requests
15
- - Open workspaces on another authorized computer without replacing the native Harness interface
16
- - Reach the Host without opening a public listening port or configuring router port forwarding
17
- - Protect session traffic with authenticated end-to-end encryption
18
- - Preview remote files through the optional, read-only `dsh-file-viewer` integration
19
- - Open Host Codex projects in the existing Remote UI through the optional experimental Codex domain
20
-
21
11
  ## Install
22
12
 
23
13
  ### DSH Desktop
@@ -26,10 +16,10 @@ Continue your DeepSeek Harness sessions and experimental Codex workspaces from a
26
16
 
27
17
  ### Existing DSH installation
28
18
 
29
- Add the exact package version to the `web` profile, then restart Harness:
19
+ Add the current package version to the `web` profile, then restart Harness:
30
20
 
31
21
  ```sh
32
- dsh plugin --profile web add -w ds-harness-remote@0.4.13
22
+ dsh plugin --profile web add -w ds-harness-remote@0.4.20
33
23
  ```
34
24
 
35
25
  ### dsh-TUI Host
@@ -37,71 +27,51 @@ dsh plugin --profile web add -w ds-harness-remote@0.4.13
37
27
  Remote can also run as a Host in a terminal-only [dsh-TUI](https://github.com/ccch1mneyyy/dsh-TUI) profile:
38
28
 
39
29
  ```sh
40
- dsh plugin --profile dsh-tui add -w ds-harness-remote@0.4.13
30
+ dsh plugin --profile dsh-tui add -w ds-harness-remote@0.4.20
41
31
  ```
42
32
 
43
- After starting dsh-TUI, manage Remote with its native slash command:
33
+ After starting dsh-TUI, manage Remote with `/remote`, `/remote login`, `/remote status`, and `/remote logout`.
44
34
 
45
- ```text
46
- /remote
47
- /remote login
48
- /remote login github
49
- /remote status
50
- /remote logout
51
- ```
35
+ ## Highlights
52
36
 
53
- `/remote login` uses Zhihu QR authorization by default; GitHub QR authorization is also supported.
37
+ - Continue active Harness sessions and review progress from another computer, the web, or Android.
38
+ - Send text and image prompts, answer questions, and handle permission requests.
39
+ - Open workspaces on another authorized computer without replacing the native Harness interface.
40
+ - Use the official workspace file tree and bounded read-only previews on supported Harness versions.
41
+ - Use an optional Host-local terminal and authorized loopback development-service previews.
42
+ - Open Host Codex projects in the existing Remote UI through the optional experimental Codex domain.
43
+ - Reach the Host without opening a public listening port or configuring router port forwarding.
54
44
 
55
- ## Quick start
45
+ ## Compatibility
56
46
 
57
- 1. Open **Remote** from the Harness sidebar.
58
- 2. Sign in with GitHub or Zhihu QR authorization, or with your account and password.
59
- 3. Enable remote control for the computer that will remain running.
60
- 4. Sign in to the same account from DSH Desktop, [Remote Web](https://dsh.r2049.cn/app), or the [Android client](https://github.com/liguobao/ds-harness-remote/releases/latest).
61
- 5. Select the online Host and open one of its workspaces.
47
+ Plugin `0.4.20` primarily targets DeepSeek Harness `dsh-v0.1.7-rc.1` and also supports `dsh-v0.1.6-alpha.2` and earlier settings hosts. It supports:
62
48
 
63
- The public service currently uses the hosted Remote relay at `dsh.r2049.cn`. A supported self-hosted relay option is not available yet.
49
+ - `dsh-v0.1.1-rc.2` through the official legacy `ApiProxy`;
50
+ - `dsh-v0.1.2-alpha.1` through `dsh-v0.1.2-rc.1` through the official Typert Remote Gateway;
51
+ - `dsh-v0.1.5-rc.1` and `dsh-v0.1.6-alpha.1` Session V3 through the official Typert Remote Gateway.
64
52
 
65
- ## Experimental Codex workspaces
53
+ The same package feature-detects the older settings registry and the `0.1.7-rc.1` Volatile settings entry. Remote Web/Desktop and Android normalize released sessions that still report the retired `code` agent preset to `ptc`.
66
54
 
67
- Codex Remote is an optional domain inside this plugin. It lets authorized clients open projects reported by the Codex App Server while continuing to use the existing Harness or Android workspace and conversation UI.
55
+ ## Self-hosted Server
68
56
 
69
- Codex support is enabled by default and can be disabled in the DeepSeek Remote settings card. It remains experimental while long-running recovery and cross-version compatibility work continue. See the [Codex Remote technical notes](https://github.com/liguobao/ds-harness-remote/blob/main/docs/codex-remote.md) for the current boundaries and validation status.
57
+ This repository includes a minimal, single-account self-hosted Relay Server in [`apps/server`](https://github.com/liguobao/ds-harness-remote/tree/main/apps/server). Configure `DSH_SERVER_ACCOUNT` and `DSH_SERVER_PASSWORD`, then point the Host and clients at the same server URL. It provides account login, device credentials, encrypted Control/Noise forwarding, Relay, and a device-status page. It does not provide the complete multi-account Server, Remote Web session UI, or WebRTC/TURN deployment.
70
58
 
71
59
  ## Security boundary
72
60
 
73
61
  - Session traffic is encrypted on the Client and decrypted only by the selected Host using `Noise_IK_25519_ChaChaPoly_SHA256`.
74
62
  - Account membership and the Host's locally pinned device identity must both authorize a connection.
75
63
  - The Host creates outbound connections only; it does not listen on a public port.
76
- - Remote does not expose a direct shell, PTY, general tool RPC, remote desktop, or file-mutation API. Harness tools continue to operate under the Host's normal permission controls.
77
- - Workspace browsing returns bounded, read-only directory metadata. Optional file previews remain read-only and provider-authorized.
78
- - The relay can observe necessary connection metadata but cannot read session messages, prompts, tool output, workspace paths, or file-preview content.
79
-
80
- ## Compatibility
81
-
82
- Plugin `0.4.13` supports:
83
-
84
- - DeepSeek Harness `dsh-v0.1.1-rc.2` through the official legacy `ApiProxy`
85
- - DeepSeek Harness `dsh-v0.1.2-alpha.1` through `dsh-v0.1.2-rc.1` through the official Typert Remote Gateway
86
- - DeepSeek Harness `dsh-v0.1.5-rc.1` Session V3 through the official Typert Remote Gateway
87
-
88
- Remote Web/Desktop and Android clients normalize released sessions that still report the retired
89
- `code` agent preset to `ptc`, so old sessions can resume on `dsh-v0.1.5-rc.1` without patching
90
- DeepSeek Harness itself.
91
-
92
- Desktop endpoints must use a compatible Harness carrier. Plugin `0.4.13` selects ApiProxy for rc.2 Hosts when available, and Session V3 Desktop clients can open legacy v0.1.2 Typert Remote Hosts through Remote-side normalization. Legacy Typert clients still reject Session V3 Hosts before mutations.
64
+ - Remote does not expose general tool RPC, remote desktop, or file-mutation APIs.
65
+ - Interactive terminals are disabled by default and require a Host-local setting; they run as the Host user independently of Agent approvals.
93
66
 
94
67
  ## Documentation
95
68
 
96
- - [Complete English guide](https://github.com/liguobao/ds-harness-remote#readme)
97
- - [完整中文说明](https://github.com/liguobao/ds-harness-remote/blob/main/README.zh.md)
69
+ - [Complete guide](https://github.com/liguobao/ds-harness-remote#readme)
98
70
  - [dsh-TUI Remote guide](https://github.com/liguobao/ds-harness-remote/blob/main/docs/dsh-tui.md)
71
+ - [Codex Remote technical notes](https://github.com/liguobao/ds-harness-remote/blob/main/docs/codex-remote.md)
99
72
  - [End-to-end encryption](https://github.com/liguobao/ds-harness-remote/blob/main/docs/end-to-end-encryption.md)
100
73
  - [Network and transport](https://github.com/liguobao/ds-harness-remote/blob/main/docs/network.md)
101
74
  - [Protocol reference](https://github.com/liguobao/ds-harness-remote/blob/main/docs/protocol.md)
75
+ - [Changelog](https://github.com/liguobao/ds-harness-remote/blob/main/CHANGELOG.md)
102
76
 
103
- ## Project status
104
-
105
- This is an independent community project and is not an official DeepSeek product. DeepSeek and related names and marks belong to their respective owners.
106
-
107
- Licensed under the [MIT License](https://github.com/liguobao/ds-harness-remote/blob/main/packages/plugin/LICENSE).
77
+ This is an independent community project and is not an official DeepSeek product. Licensed under the [MIT License](./LICENSE).
@@ -1804,6 +1804,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
1804
1804
  saving: "Saving\u2026",
1805
1805
  signOut: "Sign out",
1806
1806
  signingOut: "Signing out\u2026",
1807
+ hostName: "Host name",
1808
+ deviceId: "Device ID",
1807
1809
  serverUrl: "Server URL",
1808
1810
  serverUrlHint: "HTTPS origin used for account authorization and encrypted relay.",
1809
1811
  serverSaved: "Server address saved. Restart DSH to apply it.",
@@ -2043,6 +2045,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2043
2045
  saving: "\u4FDD\u5B58\u4E2D\u2026",
2044
2046
  signOut: "\u9000\u51FA\u6388\u6743",
2045
2047
  signingOut: "\u6B63\u5728\u9000\u51FA\u2026",
2048
+ hostName: "\u4E3B\u673A\u540D\u79F0",
2049
+ deviceId: "\u8BBE\u5907 ID",
2046
2050
  serverUrl: "Server \u5730\u5740",
2047
2051
  serverUrlHint: "\u7528\u4E8E\u8D26\u53F7\u6388\u6743\u548C\u52A0\u5BC6\u4E2D\u7EE7\u7684 HTTPS \u5730\u5740\u3002",
2048
2052
  serverSaved: "Server \u5730\u5740\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F DSH \u540E\u751F\u6548\u3002",
@@ -2440,7 +2444,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2440
2444
  }
2441
2445
  }
2442
2446
  function RemotePluginOptions(props) {
2443
- let { t } = props, [open, setOpen] = React.useState(props.view === "page"), [serverUrl, setServerUrl] = React.useState(""), [codexEnabled, setCodexEnabled] = React.useState(!0), [terminalEnabled, setTerminalEnabled] = React.useState(!1), [terminalBusy, setTerminalBusy] = React.useState(!1), [portsBusy, setPortsBusy] = React.useState(!1), [previewPorts, setPreviewPorts] = React.useState(""), role = "host", [registrationCode, setRegistrationCode] = React.useState(""), [associations, setAssociations] = React.useState({}), [loaded, setLoaded] = React.useState(!1), [writable, setWritable] = React.useState(!1), [busy, setBusy] = React.useState(!1), [codexBusy, setCodexBusy] = React.useState(!1), [acpBackends, setAcpBackends] = React.useState([]), [acpAvailability, setAcpAvailability] = React.useState({}), [acpChecking, setAcpChecking] = React.useState({}), [acpCheckResults, setAcpCheckResults] = React.useState({}), [addingAcp, setAddingAcp] = React.useState(!1), [acpName, setAcpName] = React.useState(""), [acpCommand, setAcpCommand] = React.useState(""), [acpArguments, setAcpArguments] = React.useState("acp"), [reconnectBusy, setReconnectBusy] = React.useState(!1), [hostStatus, setHostStatus] = React.useState(void 0), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0), [settingsView, setSettingsView] = React.useState(void 0), persistedServerUrl = settingsView?.config.serverUrl ?? "https://dsh.r2049.cn", association = associations.client ?? associations.host, serverDirty = settingsView !== void 0 && serverUrl !== persistedServerUrl, draftDirty = serverDirty, applyView = (view) => {
2447
+ let { t } = props, [open, setOpen] = React.useState(props.view === "page"), [serverUrl, setServerUrl] = React.useState(""), [codexEnabled, setCodexEnabled] = React.useState(!0), [terminalEnabled, setTerminalEnabled] = React.useState(!1), [terminalBusy, setTerminalBusy] = React.useState(!1), [portsBusy, setPortsBusy] = React.useState(!1), [previewPorts, setPreviewPorts] = React.useState(""), role = "host", [registrationCode, setRegistrationCode] = React.useState(""), [associations, setAssociations] = React.useState({}), [loaded, setLoaded] = React.useState(!1), [writable, setWritable] = React.useState(!1), [busy, setBusy] = React.useState(!1), [codexBusy, setCodexBusy] = React.useState(!1), [acpBackends, setAcpBackends] = React.useState([]), [acpAvailability, setAcpAvailability] = React.useState({}), [acpChecking, setAcpChecking] = React.useState({}), [acpCheckResults, setAcpCheckResults] = React.useState({}), [addingAcp, setAddingAcp] = React.useState(!1), [acpName, setAcpName] = React.useState(""), [acpCommand, setAcpCommand] = React.useState(""), [acpArguments, setAcpArguments] = React.useState("acp"), [reconnectBusy, setReconnectBusy] = React.useState(!1), [hostStatus, setHostStatus] = React.useState(void 0), [hostName, setHostName] = React.useState(""), [hostDeviceId, setHostDeviceId] = React.useState(""), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0), [settingsView, setSettingsView] = React.useState(void 0), persistedServerUrl = settingsView?.config.serverUrl ?? "https://dsh.r2049.cn", association = associations.client ?? associations.host, serverDirty = settingsView !== void 0 && serverUrl !== persistedServerUrl, draftDirty = serverDirty, applyView = (view) => {
2444
2448
  setSettingsView(view), setServerUrl(view.config.serverUrl ?? "https://dsh.r2049.cn"), setCodexEnabled(view.config.codex?.enabled ?? !0), setTerminalEnabled(view.config.terminal?.enabled ?? !1), setPreviewPorts((view.config.loopback?.ports ?? []).join(", ")), setAcpBackends((view.config.acp?.backends ?? []).map((item) => ({ id: item.id, enabled: item.enabled !== !1 }))), setAcpAvailability(view.acpAvailability ?? {}), setAssociations(view.associations ?? (view.association === void 0 ? {} : { host: view.association })), setWritable(view.writable), setLoaded(!0);
2445
2449
  }, load = async () => {
2446
2450
  let [view, status] = await Promise.all([
@@ -2448,13 +2452,15 @@ Minimum version required to store current data is: ` + bestVersion + `.
2448
2452
  props.control("status").catch(() => {
2449
2453
  })
2450
2454
  ]);
2451
- applyView(view), setHostStatus(status?.host);
2455
+ applyView(view), setHostStatus(status?.host), setHostName(status?.deviceName ?? ""), setHostDeviceId(status?.host?.deviceId ?? "");
2452
2456
  };
2453
2457
  React.useEffect(() => {
2454
2458
  load().catch((reason) => setError(messageOf(reason)));
2455
2459
  }, []), React.useEffect(() => {
2456
2460
  if (association !== void 0)
2457
- return props.statusFeed.subscribe((status) => setHostStatus(status.host));
2461
+ return props.statusFeed.subscribe((status) => {
2462
+ setHostStatus(status.host), setHostName(status.deviceName ?? ""), setHostDeviceId(status.host?.deviceId ?? "");
2463
+ });
2458
2464
  }, [association !== void 0]);
2459
2465
  let save = async (event) => {
2460
2466
  if (event?.preventDefault(), !(!writable || !serverDirty)) {
@@ -2735,6 +2741,22 @@ Minimum version required to store current data is: ` + bestVersion + `.
2735
2741
  React.createElement("p", null, association.account === void 0 ? serverUrl : t("authorizedOn", { role: "Remote", serverUrl }))
2736
2742
  )
2737
2743
  ),
2744
+ React.createElement(
2745
+ "div",
2746
+ { className: "dshRemoteHostIdentity" },
2747
+ React.createElement(
2748
+ "div",
2749
+ null,
2750
+ React.createElement("span", null, t("hostName")),
2751
+ React.createElement("strong", null, hostName || "\u2014")
2752
+ ),
2753
+ React.createElement(
2754
+ "div",
2755
+ null,
2756
+ React.createElement("span", null, t("deviceId")),
2757
+ React.createElement("code", null, hostDeviceId || "\u2014")
2758
+ )
2759
+ ),
2738
2760
  React.createElement(
2739
2761
  "div",
2740
2762
  { className: "dshRemoteField" },
@@ -2811,7 +2833,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2811
2833
  React.Fragment,
2812
2834
  null,
2813
2835
  React.createElement("button", { type: "button", className: "dshRemoteDiscard", disabled: busy, onClick: discard }, t("discard")),
2814
- React.createElement("button", { type: "button", className: "dshRemoteSave", disabled: busy || !writable, onClick: () => void save() }, t(busy ? "saving" : "save"))
2836
+ React.createElement("button", { type: "button", className: "dshRemoteSave", disabled: busy || !writable, onClick: () => void save() }, t(busy ? "saving" : "save")),
2837
+ React.createElement("button", { type: "button", className: "dshRemoteDiscard", disabled: busy || !writable, onClick: () => void logout() }, t(busy ? "signingOut" : "signOut"))
2815
2838
  ) : React.createElement("button", {
2816
2839
  type: "button",
2817
2840
  className: "dshRemoteDiscard",
@@ -2822,6 +2845,22 @@ Minimum version required to store current data is: ` + bestVersion + `.
2822
2845
  ) : React.createElement(
2823
2846
  "form",
2824
2847
  { className: "dshRemoteSettings", noValidate: !0, onSubmit: (event) => void save(event) },
2848
+ React.createElement(
2849
+ "div",
2850
+ { className: "dshRemoteHostIdentity" },
2851
+ React.createElement(
2852
+ "div",
2853
+ null,
2854
+ React.createElement("span", null, t("hostName")),
2855
+ React.createElement("strong", null, hostName || "\u2014")
2856
+ ),
2857
+ React.createElement(
2858
+ "div",
2859
+ null,
2860
+ React.createElement("span", null, t("deviceId")),
2861
+ React.createElement("code", null, hostDeviceId || "\u2014")
2862
+ )
2863
+ ),
2825
2864
  React.createElement(
2826
2865
  "div",
2827
2866
  { className: "dshRemoteField" },
@@ -4003,6 +4042,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
4003
4042
  ".dshRemotePluginCardHeader{display:flex;align-items:center}.dshRemotePluginCardToggle{appearance:none;width:100%;min-width:0;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}.dshRemotePluginCardToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
4004
4043
  ".dshRemotePluginCardHeading{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}.dshRemotePluginCardHeading>strong{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dshRemotePluginCardHeading>span{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dshRemotePluginCardStatus{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshRemotePluginCardStatus.isOnline{color:var(--dsw-alias-state-success-primary)}.dshRemotePluginCardStatus.isReconnecting{color:var(--dsw-alias-state-warn-label)}.dshRemotePluginCardStatus.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemotePluginCardChevron{color:var(--dsw-alias-label-tertiary);font-size:18px;line-height:14px;transition:transform .16s}.dshRemotePluginCard.isOpen .dshRemotePluginCardChevron{transform:rotate(180deg)}",
4005
4044
  ".dshRemotePluginCardBody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.dshRemoteSettings{display:flex;flex-direction:column;max-width:720px}.dshRemoteSettingsTop{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:12px 0}.dshRemoteSettingsState{margin:0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
4045
+ ".dshRemoteHostIdentity{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:12px 0;border-top:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2)}.dshRemoteHostIdentity>div{min-width:0;display:flex;flex-direction:column;gap:3px}.dshRemoteHostIdentity span{color:var(--dsw-alias-label-tertiary);font-size:12px}.dshRemoteHostIdentity strong,.dshRemoteHostIdentity code{min-width:0;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dshRemoteHostIdentity code{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-weight:400}",
4006
4046
  ".dshRemoteField{display:flex;flex-direction:column;gap:6px;padding:12px 0}.dshRemoteField+.dshRemoteField{border-top:1px solid var(--dsw-alias-border-l2)}.dshRemoteField label{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}.dshRemoteField input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.dshRemoteField input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.dshRemoteField input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.dshRemoteField p{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}",
4007
4047
  '.dshRemoteAuthorizationSetting{border-top:1px solid var(--dsw-alias-border-l2);display:flex;align-items:center;justify-content:space-between;gap:20px;padding:12px 0}.dshRemoteAuthorizationSetting>div{min-width:0}.dshRemoteAuthorizationSetting strong{font-size:13px;font-weight:500}.dshRemoteAuthorizationSetting p{margin:3px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px}.dshRemoteAuthorizationSetting>input{appearance:none;position:relative;width:38px;height:22px;flex:0 0 auto;margin:0;border:1px solid var(--dsw-alias-label-secondary);border-radius:999px;background:var(--dsw-alias-bg-layer-3);cursor:pointer;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2);transition:background .16s ease-out,border-color .16s ease-out,box-shadow .16s ease-out}.dshRemoteAuthorizationSetting>input::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-secondary);transition:transform .16s ease-out,background .16s ease-out}.dshRemoteAuthorizationSetting>input:checked{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-primary);box-shadow:none}.dshRemoteAuthorizationSetting>input:checked::after{transform:translateX(16px);background:var(--dsw-alias-bg-layer-1)}.dshRemoteAuthorizationSetting>input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dshRemoteAuthorizationSetting>input:disabled{opacity:.5;cursor:default}@media(prefers-reduced-motion:reduce){.dshRemoteAuthorizationSetting>input,.dshRemoteAuthorizationSetting>input::after{transition:none}}',
4008
4048
  ".dshRemoteAssociation{min-width:0;flex:1;display:flex;flex-direction:column;gap:4px}.dshRemoteAssociation>span{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.dshRemoteAssociation strong{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:1.5}.dshRemoteAssociation p{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}",
package/dist/client.js CHANGED
@@ -1804,6 +1804,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
1804
1804
  saving: "Saving\u2026",
1805
1805
  signOut: "Sign out",
1806
1806
  signingOut: "Signing out\u2026",
1807
+ hostName: "Host name",
1808
+ deviceId: "Device ID",
1807
1809
  serverUrl: "Server URL",
1808
1810
  serverUrlHint: "HTTPS origin used for account authorization and encrypted relay.",
1809
1811
  serverSaved: "Server address saved. Restart DSH to apply it.",
@@ -2043,6 +2045,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2043
2045
  saving: "\u4FDD\u5B58\u4E2D\u2026",
2044
2046
  signOut: "\u9000\u51FA\u6388\u6743",
2045
2047
  signingOut: "\u6B63\u5728\u9000\u51FA\u2026",
2048
+ hostName: "\u4E3B\u673A\u540D\u79F0",
2049
+ deviceId: "\u8BBE\u5907 ID",
2046
2050
  serverUrl: "Server \u5730\u5740",
2047
2051
  serverUrlHint: "\u7528\u4E8E\u8D26\u53F7\u6388\u6743\u548C\u52A0\u5BC6\u4E2D\u7EE7\u7684 HTTPS \u5730\u5740\u3002",
2048
2052
  serverSaved: "Server \u5730\u5740\u5DF2\u4FDD\u5B58\uFF0C\u91CD\u542F DSH \u540E\u751F\u6548\u3002",
@@ -2440,7 +2444,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
2440
2444
  }
2441
2445
  }
2442
2446
  function RemotePluginOptions(props) {
2443
- let { t } = props, [open, setOpen] = React.useState(props.view === "page"), [serverUrl, setServerUrl] = React.useState(""), [codexEnabled, setCodexEnabled] = React.useState(!0), [terminalEnabled, setTerminalEnabled] = React.useState(!1), [terminalBusy, setTerminalBusy] = React.useState(!1), [portsBusy, setPortsBusy] = React.useState(!1), [previewPorts, setPreviewPorts] = React.useState(""), role = "host", [registrationCode, setRegistrationCode] = React.useState(""), [associations, setAssociations] = React.useState({}), [loaded, setLoaded] = React.useState(!1), [writable, setWritable] = React.useState(!1), [busy, setBusy] = React.useState(!1), [codexBusy, setCodexBusy] = React.useState(!1), [acpBackends, setAcpBackends] = React.useState([]), [acpAvailability, setAcpAvailability] = React.useState({}), [acpChecking, setAcpChecking] = React.useState({}), [acpCheckResults, setAcpCheckResults] = React.useState({}), [addingAcp, setAddingAcp] = React.useState(!1), [acpName, setAcpName] = React.useState(""), [acpCommand, setAcpCommand] = React.useState(""), [acpArguments, setAcpArguments] = React.useState("acp"), [reconnectBusy, setReconnectBusy] = React.useState(!1), [hostStatus, setHostStatus] = React.useState(void 0), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0), [settingsView, setSettingsView] = React.useState(void 0), persistedServerUrl = settingsView?.config.serverUrl ?? "https://dsh.r2049.cn", association = associations.client ?? associations.host, serverDirty = settingsView !== void 0 && serverUrl !== persistedServerUrl, draftDirty = serverDirty, applyView = (view) => {
2447
+ let { t } = props, [open, setOpen] = React.useState(props.view === "page"), [serverUrl, setServerUrl] = React.useState(""), [codexEnabled, setCodexEnabled] = React.useState(!0), [terminalEnabled, setTerminalEnabled] = React.useState(!1), [terminalBusy, setTerminalBusy] = React.useState(!1), [portsBusy, setPortsBusy] = React.useState(!1), [previewPorts, setPreviewPorts] = React.useState(""), role = "host", [registrationCode, setRegistrationCode] = React.useState(""), [associations, setAssociations] = React.useState({}), [loaded, setLoaded] = React.useState(!1), [writable, setWritable] = React.useState(!1), [busy, setBusy] = React.useState(!1), [codexBusy, setCodexBusy] = React.useState(!1), [acpBackends, setAcpBackends] = React.useState([]), [acpAvailability, setAcpAvailability] = React.useState({}), [acpChecking, setAcpChecking] = React.useState({}), [acpCheckResults, setAcpCheckResults] = React.useState({}), [addingAcp, setAddingAcp] = React.useState(!1), [acpName, setAcpName] = React.useState(""), [acpCommand, setAcpCommand] = React.useState(""), [acpArguments, setAcpArguments] = React.useState("acp"), [reconnectBusy, setReconnectBusy] = React.useState(!1), [hostStatus, setHostStatus] = React.useState(void 0), [hostName, setHostName] = React.useState(""), [hostDeviceId, setHostDeviceId] = React.useState(""), [notice, setNotice] = React.useState(void 0), [error, setError] = React.useState(void 0), [settingsView, setSettingsView] = React.useState(void 0), persistedServerUrl = settingsView?.config.serverUrl ?? "https://dsh.r2049.cn", association = associations.client ?? associations.host, serverDirty = settingsView !== void 0 && serverUrl !== persistedServerUrl, draftDirty = serverDirty, applyView = (view) => {
2444
2448
  setSettingsView(view), setServerUrl(view.config.serverUrl ?? "https://dsh.r2049.cn"), setCodexEnabled(view.config.codex?.enabled ?? !0), setTerminalEnabled(view.config.terminal?.enabled ?? !1), setPreviewPorts((view.config.loopback?.ports ?? []).join(", ")), setAcpBackends((view.config.acp?.backends ?? []).map((item) => ({ id: item.id, enabled: item.enabled !== !1 }))), setAcpAvailability(view.acpAvailability ?? {}), setAssociations(view.associations ?? (view.association === void 0 ? {} : { host: view.association })), setWritable(view.writable), setLoaded(!0);
2445
2449
  }, load = async () => {
2446
2450
  let [view, status] = await Promise.all([
@@ -2448,13 +2452,15 @@ Minimum version required to store current data is: ` + bestVersion + `.
2448
2452
  props.control("status").catch(() => {
2449
2453
  })
2450
2454
  ]);
2451
- applyView(view), setHostStatus(status?.host);
2455
+ applyView(view), setHostStatus(status?.host), setHostName(status?.deviceName ?? ""), setHostDeviceId(status?.host?.deviceId ?? "");
2452
2456
  };
2453
2457
  React.useEffect(() => {
2454
2458
  load().catch((reason) => setError(messageOf(reason)));
2455
2459
  }, []), React.useEffect(() => {
2456
2460
  if (association !== void 0)
2457
- return props.statusFeed.subscribe((status) => setHostStatus(status.host));
2461
+ return props.statusFeed.subscribe((status) => {
2462
+ setHostStatus(status.host), setHostName(status.deviceName ?? ""), setHostDeviceId(status.host?.deviceId ?? "");
2463
+ });
2458
2464
  }, [association !== void 0]);
2459
2465
  let save = async (event) => {
2460
2466
  if (event?.preventDefault(), !(!writable || !serverDirty)) {
@@ -2735,6 +2741,22 @@ Minimum version required to store current data is: ` + bestVersion + `.
2735
2741
  React.createElement("p", null, association.account === void 0 ? serverUrl : t("authorizedOn", { role: "Remote", serverUrl }))
2736
2742
  )
2737
2743
  ),
2744
+ React.createElement(
2745
+ "div",
2746
+ { className: "dshRemoteHostIdentity" },
2747
+ React.createElement(
2748
+ "div",
2749
+ null,
2750
+ React.createElement("span", null, t("hostName")),
2751
+ React.createElement("strong", null, hostName || "\u2014")
2752
+ ),
2753
+ React.createElement(
2754
+ "div",
2755
+ null,
2756
+ React.createElement("span", null, t("deviceId")),
2757
+ React.createElement("code", null, hostDeviceId || "\u2014")
2758
+ )
2759
+ ),
2738
2760
  React.createElement(
2739
2761
  "div",
2740
2762
  { className: "dshRemoteField" },
@@ -2811,7 +2833,8 @@ Minimum version required to store current data is: ` + bestVersion + `.
2811
2833
  React.Fragment,
2812
2834
  null,
2813
2835
  React.createElement("button", { type: "button", className: "dshRemoteDiscard", disabled: busy, onClick: discard }, t("discard")),
2814
- React.createElement("button", { type: "button", className: "dshRemoteSave", disabled: busy || !writable, onClick: () => void save() }, t(busy ? "saving" : "save"))
2836
+ React.createElement("button", { type: "button", className: "dshRemoteSave", disabled: busy || !writable, onClick: () => void save() }, t(busy ? "saving" : "save")),
2837
+ React.createElement("button", { type: "button", className: "dshRemoteDiscard", disabled: busy || !writable, onClick: () => void logout() }, t(busy ? "signingOut" : "signOut"))
2815
2838
  ) : React.createElement("button", {
2816
2839
  type: "button",
2817
2840
  className: "dshRemoteDiscard",
@@ -2822,6 +2845,22 @@ Minimum version required to store current data is: ` + bestVersion + `.
2822
2845
  ) : React.createElement(
2823
2846
  "form",
2824
2847
  { className: "dshRemoteSettings", noValidate: !0, onSubmit: (event) => void save(event) },
2848
+ React.createElement(
2849
+ "div",
2850
+ { className: "dshRemoteHostIdentity" },
2851
+ React.createElement(
2852
+ "div",
2853
+ null,
2854
+ React.createElement("span", null, t("hostName")),
2855
+ React.createElement("strong", null, hostName || "\u2014")
2856
+ ),
2857
+ React.createElement(
2858
+ "div",
2859
+ null,
2860
+ React.createElement("span", null, t("deviceId")),
2861
+ React.createElement("code", null, hostDeviceId || "\u2014")
2862
+ )
2863
+ ),
2825
2864
  React.createElement(
2826
2865
  "div",
2827
2866
  { className: "dshRemoteField" },
@@ -4003,6 +4042,7 @@ Minimum version required to store current data is: ` + bestVersion + `.
4003
4042
  ".dshRemotePluginCardHeader{display:flex;align-items:center}.dshRemotePluginCardToggle{appearance:none;width:100%;min-width:0;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;display:flex;align-items:center;gap:12px;padding:14px 16px}.dshRemotePluginCardToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
4004
4043
  ".dshRemotePluginCardHeading{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}.dshRemotePluginCardHeading>strong{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.dshRemotePluginCardHeading>span{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.dshRemotePluginCardStatus{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.dshRemotePluginCardStatus.isOnline{color:var(--dsw-alias-state-success-primary)}.dshRemotePluginCardStatus.isReconnecting{color:var(--dsw-alias-state-warn-label)}.dshRemotePluginCardStatus.isOffline{color:var(--dsw-alias-state-error-primary)}.dshRemotePluginCardChevron{color:var(--dsw-alias-label-tertiary);font-size:18px;line-height:14px;transition:transform .16s}.dshRemotePluginCard.isOpen .dshRemotePluginCardChevron{transform:rotate(180deg)}",
4005
4044
  ".dshRemotePluginCardBody{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.dshRemoteSettings{display:flex;flex-direction:column;max-width:720px}.dshRemoteSettingsTop{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:12px 0}.dshRemoteSettingsState{margin:0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}",
4045
+ ".dshRemoteHostIdentity{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:12px 0;border-top:1px solid var(--dsw-alias-border-l2);border-bottom:1px solid var(--dsw-alias-border-l2)}.dshRemoteHostIdentity>div{min-width:0;display:flex;flex-direction:column;gap:3px}.dshRemoteHostIdentity span{color:var(--dsw-alias-label-tertiary);font-size:12px}.dshRemoteHostIdentity strong,.dshRemoteHostIdentity code{min-width:0;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500}.dshRemoteHostIdentity code{font-family:var(--dsw-font-mono,ui-monospace,SFMono-Regular,Menlo,monospace);font-weight:400}",
4006
4046
  ".dshRemoteField{display:flex;flex-direction:column;gap:6px;padding:12px 0}.dshRemoteField+.dshRemoteField{border-top:1px solid var(--dsw-alias-border-l2)}.dshRemoteField label{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}.dshRemoteField input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}.dshRemoteField input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}.dshRemoteField input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.dshRemoteField p{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}",
4007
4047
  '.dshRemoteAuthorizationSetting{border-top:1px solid var(--dsw-alias-border-l2);display:flex;align-items:center;justify-content:space-between;gap:20px;padding:12px 0}.dshRemoteAuthorizationSetting>div{min-width:0}.dshRemoteAuthorizationSetting strong{font-size:13px;font-weight:500}.dshRemoteAuthorizationSetting p{margin:3px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px}.dshRemoteAuthorizationSetting>input{appearance:none;position:relative;width:38px;height:22px;flex:0 0 auto;margin:0;border:1px solid var(--dsw-alias-label-secondary);border-radius:999px;background:var(--dsw-alias-bg-layer-3);cursor:pointer;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2);transition:background .16s ease-out,border-color .16s ease-out,box-shadow .16s ease-out}.dshRemoteAuthorizationSetting>input::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-secondary);transition:transform .16s ease-out,background .16s ease-out}.dshRemoteAuthorizationSetting>input:checked{border-color:var(--dsw-alias-state-success-primary);background:var(--dsw-alias-state-success-primary);box-shadow:none}.dshRemoteAuthorizationSetting>input:checked::after{transform:translateX(16px);background:var(--dsw-alias-bg-layer-1)}.dshRemoteAuthorizationSetting>input:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.dshRemoteAuthorizationSetting>input:disabled{opacity:.5;cursor:default}@media(prefers-reduced-motion:reduce){.dshRemoteAuthorizationSetting>input,.dshRemoteAuthorizationSetting>input::after{transition:none}}',
4008
4048
  ".dshRemoteAssociation{min-width:0;flex:1;display:flex;flex-direction:column;gap:4px}.dshRemoteAssociation>span{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}.dshRemoteAssociation strong{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:1.5}.dshRemoteAssociation p{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}",