Conversation sync done properly: fork-shaped seeded import adopted by the session controller, paged transfer, replicas of remote chats for offline continuation, move/divergence ledger with queued notices delivered on reconnect, native sidebar menu item + row markers, toasts
This commit is contained in:
1 parent
0f866b1bbd
commit
2e699a061f
7 files changed
+904
-461
No files matched your search
@@ -3,9 +3,8 @@
|
||||
Remote control for [DeepSeek Harness](https://github.com/deepseek-ai/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.
|
||||
- **Continue a conversation on another device.** "Continue here" on any remote chat (Devices page or sidebar), or "Continue on another device…" in a chat's "…" menu, copies the conversation to that device as a real native chat (its own history, tools and workspace), and the original is marked as moved. See [Conversation sync](#conversation-sync).
|
||||
- **Works while the other device is offline.** Each device keeps synced copies of the other devices' recent chats, so you can continue one even when its device is off. That device is told the moment it is reachable again, and if it got new messages in the meantime both sides are warned that the copies diverged.
|
||||
- **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.
|
||||
@@ -135,10 +134,24 @@ GitHub `gh` logins are not shipped.
|
||||
tools: true # register remote_* tools (default: on for the master, off for slaves)
|
||||
enabled: true # false = refuse agent control of THIS device
|
||||
allowPeerControl: false # master: let slaves relay exec/file/task ops to other devices
|
||||
sessionSync:
|
||||
mirrorRecent: 10 # keep synced copies of each other device's 10 most recent chats (0 = off)
|
||||
mirrorIntervalMs: 60000 # how often copies are refreshed (only chats that changed are fetched)
|
||||
watchIntervalMs: 20000 # how often moved chats are checked for divergence and queued notices retried
|
||||
```
|
||||
|
||||
Never synced: the master's `dsh-remote` and `dsh-lanmode` entries, and `connection`/`webserver` settings, since these are machine-local.
|
||||
|
||||
## Conversation sync
|
||||
|
||||
A chat can move between devices the way a branch moves between git clones.
|
||||
|
||||
- **Continue here / on another device.** The conversation is copied through its **last finished turn** (a turn that is still running is left behind) and written on the target as a *seeded* session, which is the same log shape DSH writes for its own *Fork*. The target's session controller then adopts it like any native chat: own agent, tools and preset, attached to the matching workspace (same path, else a workspace with the same folder name, else the original path if it exists, else the home directory). It shows up in the normal sidebar and is marked `⇣` (continued from …).
|
||||
- **The original is told it moved** and shows `→` (continues on …) in its sidebar row and the Devices page.
|
||||
- **Versioning.** Each copy records its base: the event seq it was cut at plus a commit id (a hash of that event). Logs are append-only, so the same commit id at the same seq means the same history up to there.
|
||||
- **Offline.** Every device keeps synced copies of the other devices' recent chats (`<profile>/dsh-remote/mirrors/`, fetched incrementally). When a device is offline, the Devices page and the sidebar list its chats from those copies, and *Continue here* uses them. The move notice is queued and delivered **as soon as the device is reachable**: on link-up, on the master's peer broadcast, and retried every `watchIntervalMs`.
|
||||
- **Conflicts.** If the original got new messages after the base (someone kept chatting there while the devices could not see each other, or after the move), both sides are marked `⚠ diverged` and get a toast. Nothing is merged or deleted, and both chats stay usable. *Continue here* on the newer branch creates a fresh copy from it.
|
||||
|
||||
## How sync works
|
||||
|
||||
| Master has | Slave gets |
|
||||
@@ -195,7 +208,7 @@ A master published behind an identity-aware proxy (Authentik, oauth2-proxy, Clou
|
||||
| --- | --- |
|
||||
| `GET /dsh-remote/state` | role, self, link status, devices |
|
||||
| `GET /dsh-remote/sessions` | sessions of every reachable device |
|
||||
| `GET /dsh-remote/stream` | SSE: `session-finished`, `devices`, `sync` events |
|
||||
| `GET /dsh-remote/stream` | SSE: `session-finished`, `session-moved`, `session-conflict`, `devices`, `sync` events |
|
||||
| `POST /dsh-remote/pairing` | master: create a pairing code |
|
||||
| `POST /dsh-remote/pair` | slave: `{masterUrl, code}` |
|
||||
| `POST /dsh-remote/unpair` | slave: forget the master |
|
||||
@@ -203,6 +216,9 @@ A master published behind an identity-aware proxy (Authentik, oauth2-proxy, Clou
|
||||
| `POST /dsh-remote/sync` | slave: pull now · master: push to all |
|
||||
| `GET /dsh-remote/sync/status` | manifest (master) / last result (slave) |
|
||||
| `POST /dsh-remote/new-session` | `{device, workspaceId? , cwd?}` |
|
||||
| `POST /dsh-remote/continue-session` | `{sourceDevice, sessionId, targetDevice?, workspaceId?, title?}`: continue a chat on a device (default: this one) |
|
||||
| `GET /dsh-remote/session-sync` | this device's lineage ledger (moved / continued-from / conflicts) and queued notices |
|
||||
| `POST /dsh-remote/session-sync/resolve` | `{sessionId}`: dismiss a divergence warning (both chats are kept) |
|
||||
| `GET /dsh-remote/debug/inspect` | `?sessionId=…&device=…`: last events of a session on any device |
|
||||
| `GET/POST /dsh-remote/edge-auth` | slave: show / set `{edgeAuth}` and probe whether the auth proxy lets it through |
|
||||
| `GET/POST /dsh-remote/node` | `{nodeType, authMiddleware}`: node settings and the pending `authRequired` state |
|
||||
|
||||
Reference in new issue
Block a user