# dsh-workspace-hot **Give a running DeepSeek Harness Host the ability to create and archive Sessions from the outside — no restart, no page refresh.** A plugin for [DeepSeek Harness](https://deepseek.com/harness) that turns "a sibling process started a Session" into "the Session is in the sidebar, right now". ```bash dsh plugin --profile web add dsh-workspace-hot ``` Then restart the app **once** so the plugin (and its Host-side service) loads. After that, registration and archiving take effect immediately, with no further restarts. ## What it solves The desktop Host owns `~/.dsh/storages/workspace.json`. Anything else that writes that file is racing a cache it cannot see: the Host holds the whole table in memory and writes it back, so external edits are silently lost, or worse, corrupt the integrity check and blank the entire workspace panel. There is also a second gap that is easy to miss. Attaching a Session to a Workspace updates the membership list, but the client keeps a separate Session-summary table; without the Host emitting `api-session/added`, the row never appears even though the registry is correct. This plugin closes both gaps from inside the Host: - **Writes go through the Host.** The plugin runs in the Host process and calls the public registry API (`create`, `attachSession`, `archiveSession`, `unarchiveSession`), so `workspace.json` keeps a single writer. - **The client is told.** Registration emits the same `api-session/added` summary event the official client consumes, so the Session appears at once. - **Archiving is a durable Host write.** The registry publishes the archive set itself, so the UI updates with no extra event. Archiving never deletes anything. ## How it works ``` 外部进程 Host 进程 (插件) 客户端 -------- ---------------- ------ 写 requests/.json → fs.watch(requests) 去抖 ↓ validateRequest(id, action) ↓ workspaceRegistry.create() workspace.attachSession() ↓ ctx.emit('api-session/added') → 侧边栏立即出现 ↓ 读 receipts/.json ← 原子写入回执(成功/失败都会写) ``` Requests are immutable and acknowledged once. A failed request keeps its receipt; retry with a **new id**. The queue is a directory, so any language or tool can submit — no sockets, no ports, no auth surface. ## Request protocol Any JSON file dropped into `$DSH_HOME/dsh-workspace-hot/requests/` (default `~/.dsh/dsh-workspace-hot/requests/`) is a request. Write to `.json.tmp` first, then rename, so a partially written file is never read. | field | type | required | meaning | |---|---|---|---| | `version` | `1` | yes | protocol version | | `id` | string `[A-Za-z0-9_-]{1,100}` | yes | must equal the filename stem; also the receipt name | | `action` | `register` \| `archive` \| `unarchive` | no, default `register` | operation | | `workspacePath` | absolute path | for `register` | Workspace to create or reuse | | `sessionId` | `session-…` | for `register` (optional), required for archive ops | Session to place or hide | | `title` | non-empty string | no | Workspace title when it is created | | `stopActivity` | boolean | no | archive a Session that is still running (the Host refuses otherwise) | ### Receipt Written to `receipts/.json`: ```json { "version": 1, "id": "register-…", "ok": true, "action": "register", "workspaceId": "…", "workspacePath": "E:\\code\\app", "sessionId": "session-…", "registered": true, "summaryPublished": true, "completedAt": "2026-10-22T06:29:41.512Z" } ``` `ok: false` always carries `error`. Archive receipts report `archived` (final state) and `changed` (whether this call actually changed anything). ## CLI helpers (Windows PowerShell) Both helpers validate `status.json` (plugin loaded, matching version, live pid) and refuse to touch anything if the Host is not ready. ```powershell # place a Session under a Workspace, publish its summary .\register-workspace.ps1 -WorkspacePath 'E:\code\app' -SessionId 'session-…' # hide / restore a Session (files are kept, position is restored) .\session-actions.ps1 -Action archive -SessionId 'session-…' .\session-actions.ps1 -Action unarchive -SessionId 'session-…' # force-archive a Session that is still running .\session-actions.ps1 -Action archive -SessionId 'session-…' -StopActivity # show plugin status .\register-workspace.ps1 -Check ``` Use `-DshHome` if your DSH home is not `~/.dsh`. ## Why a "hot" registrar is useful Dispatch systems usually start work in a **separate process** (`dsh --profile headless `). That Session is real, but it was born outside the desktop Host, so nothing puts it in the sidebar. People then face a choice: restart the app (losing the conversation) or hand-edit `workspace.json` (losing data). With this plugin, an external dispatcher can: 1. start the Session, 2. write one `register` request, 3. have it appear in the sidebar immediately — clickable, resumable, archivable. The same channel also covers cleanup: archive throwaway Sessions without deleting their files, and unarchive to bring them back to their original position. ## Compatibility and limits - Built against DSH `0.2.0-rc.2` desktop (Cordis plugin API, `ctx.inject` / `ctx.effect`). - Injects `workspaceRegistry`, `sessionPersistence`, `sessionQuery`, `sessions`, `agents`. If a future release renames one of these, the plugin fails to activate and reports why in `~/.dsh/logs/dsh-workspace-hot.log` instead of half-working. - Registration requires the Session to be persisted already (`sessionPersistence.stat`) and its header `cwd` to resolve to the target Workspace; that check belongs to the Host and is intentional. - Archiving is **not** deletion. `session-*` files stay on disk; the registry only hides them from grouping surfaces. - File-inbox means "same machine, same user". There is no network listener and no authentication to configure. ## Development ```bash node tests/smoke.mjs ``` The suite runs against a temporary `DSH_HOME` with a fake registry: registration, summary event, replay/idempotence, unknown Session, invalid action, missing fields, archive/unarchive idempotence and lifecycle shutdown. It never calls a model. ## License MIT — see [LICENSE](LICENSE).