Files
dsh-remote/README.md
T

21 KiB

dsh-remote

Remote control for DeepSeek Harness, in the style of Claude Desktop: one master and any number of slaves.

  • Every session on every device. Sessions run on the machine that created them. From any device you can browse all of them, open them, and chat in them; the work still executes on the owning machine.
  • Seamless conversation sync & continuation. Continue any remote conversation directly on the current instance (or any other available device). The conversation history and events are smoothly synced locally so it behaves as if created natively on the current machine.
  • Git-like version control & conflict detection. Tracks branch sequence points (baseSeq), commits (versionToken), and divergence. If a session is modified across multiple instances or continued while an instance was offline, conflicts are flagged immediately to prevent sync anomalies.
  • Offline remote notification ASAP. If a session is moved or continued to another instance while the original host is offline, move notices are securely queued and transmitted the instant the offline device reconnects, safely updating state and preventing split-brain edits.
  • The full remote UI at /r/<device>/. The whole DSH UI of another device is proxied over the link: HTTP, the Remote WebSocket, SSE, plugin bundles and terminals. Slave→slave goes through the master's relay.
  • Plugin and config sync, master → slaves. Slaves get the master's npm/registry bundles, its local link: plugins (packed and shipped over the link) and its cordis.patch.yml. The config lands in a managed bundle layer, so each slave's own cordis.patch.yml still wins and slave-only plugins stay untouched.
  • Opt-in secrets. A credential (OMNI_API_KEY, …) reaches a slave only if three places agree: the master offers it, the master enables it for that device, and the slave accepts it.
  • Devices UI. A sidebar panel lists every device and its sessions (live running state, filter, "New session on "). A "Remote devices" settings page handles pairing, revocation and per-device sync policy. You get a toast when a session on any device finishes.

No dependencies: the WebSocket, the multiplexer and the proxy are plain Node.

 slave (laptop) ──outbound WebSocket /dsh-remote/link──►  master  ◄── slave (ai-pc)
   own sessions + own local plugins                  plugins, config, relay hub

Install

On every machine, register the plugin in the DSH web profile and restart DSH:

# master (this repo as a link: plugin, e.g. in dsh-plugins/)
#   profile package.json  →  "@local/dsh-remote": "link:/path/to/dsh-remote"  + bundle entry
# or use the helper (also creates the node_modules link and a config block):
node deploy/install-local.mjs --role master --name "Pelican-Node"
node deploy/install-local.mjs --role auto   --name "ai-pc"          # on a slave
systemctl --user restart dsh    # or restart `dsh web` however you run it

deploy/dsh-web.user.service is a ready-made systemd user unit for a rootless dsh web (127.0.0.1:3081).

macOS: one-line slave install

Create a pairing code on the master (Settings → Remote devices → Create pairing code). Then, in Terminal on the Mac:

curl -fsSL https://git.rizzy.dev/DSH-Plugins/dsh-remote/raw/branch/main/deploy/install-mac.sh \
  | bash -s -- --master http://192.168.0.111:3080 --code XXXX-XXXX-XXXX-XXXX --name my-mac

No sudo and no Homebrew. The script:

  • installs Node 22 to ~/.local/opt/node (checksum-verified), and DSH and pnpm to ~/.local;
  • puts dsh-remote in ~/.dsh/plugins/dsh-remote and registers it as a slave that accepts OMNI_API_KEY and FORGE_CREDENTIALS (--accept changes the list);
  • pairs with the code (deploy/pair.mjs, no running DSH needed);
  • installs the LaunchAgent dev.rizzy.dsh-web: dsh web on 127.0.0.1:3081, started at login and restarted if it dies. Logs go to ~/.dsh/logs/.

On first link-up the Mac pulls the master's plugins and config. With sync.restartOnSync: true, set by the script, DSH restarts itself once so new plugins load. It waits until no session is running and restarts at most once per 10 minutes.

Re-running the script updates everything and keeps the pairing. To remove it: launchctl bootout gui/$(id -u)/dev.rizzy.dsh-web, then delete ~/Library/LaunchAgents/dev.rizzy.dsh-web.plist, ~/.dsh and ~/.local/opt/node.

Away from home, point --master at the public URL. If an auth proxy guards it, add the --edge … flags (see Masters behind an auth proxy). Secrets are prompted for on the terminal and never passed on the command line:

curl -fsSL https://git.rizzy.dev/DSH-Plugins/dsh-remote/raw/branch/main/deploy/install-mac.sh \
  | bash -s -- --master https://dsh.rizzy.dev --code XXXX-XXXX-XXXX-XXXX --name my-mac \
               --edge basic --edge-user dsh-remote-mac

Pair a device

First choose what this installation is: Settings → Remote devices → Node.

Setting
Type: Master / Slave Master holds plugins, config and sessions and accepts links. Slave connects to a master and syncs from it. The choice is stored in the plugin state and beats role in the config file. A change applies after a restart (a Restart DSH button appears when DSH runs under systemd / launchd).
Origin Master: the address slaves use to reach it (read-only, taken from publicUrl / lanUrl or the page's own origin). Slave: the master's origin; read-only once paired, to change it re-pair.
Master has an auth middleware On: a non-DSH answer on the link opens the sign-in window (see Masters behind an auth proxy), then the link retries. Off: work as usual.
  1. Master: Settings → Remote devices → Create pairing code (single use, valid 15 min).
  2. Slave: Settings → Remote devices → enter the master URL (e.g. http://192.168.0.111:3080 or https://dsh.rizzy.dev) and the code → Pair. Then restart the slave's DSH once: with role: auto it switches from master to slave.

The slave gets its own device key; the master stores only its hash. Revoke cuts the device off immediately: open links close with code 4401 and later connections are refused with 401.

Use it

  • Workspaces list: the workspaces of every other machine appear in the stock Workspaces list, below your own, with a server icon instead of the folder icon and the device name on the right. Click one to fold its sessions open (newest first, "Show N more" for the rest); hover for + to start a session in that workspace on that device. Clicking a remote session opens it as the usual conversation view, served by that device at /r/<device>/#/session/<id>. A "Remote: · back to local" pill marks the remote view. While you are viewing a device, its own workspaces are the native rows and are not repeated.
  • Sidebar → Devices: every device with its state (online / offline, last seen, platform, counts of workspaces, sessions and running ones), then its workspaces and sessions. The same server icon marks other machines' workspaces. Open DSH ↗ opens a device's whole UI, + New session creates a session in one of its workspaces.
  • Toasts: ": '