liangmianya/dsh-synapse
liangmianya/dsh-synapseDeepSeek Harness 的可视化非线性对话工作区:把会话、追问与分支变成可浏览、可拖拽的对话地图。
DSH Web 会话管理插件:在设置页查看全部与已归档会话,支持恢复(取消归档)、二次确认删除与对话内容预览。
Session manager for DeepSeek Harness.
dsh-session-manager gives the Harness Web UI a full session-management surface: browse all sessions and the archived ones, restore (unarchive) archived sessions back to their original workspace, archive any session, delete sessions with a two-step confirmation, and preview conversation content — all from the Settings panel, with zero modification to official packages. Its UI follows the Harness language setting (English / 中文) and it ships a dedicated icon.
Stock DSH archives sessions one-way: the sidebar's Archive action hides a session from every view (workspace groups, ungrouped, search, flat list), and there is no view or API to bring it back — archived sessions are effectively lost until you hand-edit ~/.dsh/storages/workspace.json. This plugin makes archiving a reversible, manageable state and adds an overview of every session on disk:
right-click a session -> Archive (stock DSH)
-> the session vanishes from the sidebar (nowhere to see it again)
-> this plugin: Settings -> Session Manager -> archived tab
preview its content, Restore it (back to its original workspace
position), or Delete it from disk (two-step confirmation)
-> the all-sessions tab lists every persisted session (grouped by
workspace, ungrouped ones included) so nothing is ever lost sight of
Current DSH returns listing snapshots ({ header, revision, ... }) from sessionPersistence.list(), not bare headers. The previous release read snapshot.id (always undefined), so it could not find the stored session during delete: it pruned the archive/workspace bookkeeping and reported success without removing the files. Removing the archive entry made the still-existing session visible in the sidebar again — exactly the reported ghost.
0.2.0 normalizes both the current snapshot shape and the legacy header shape, resolves the real JSONL session directory, and only prunes bookkeeping after the directory is removed (or proven absent). If a listed session's artifact cannot be located, delete refuses with an error and leaves every bookkeeping record untouched.
The /dsh-sm prefix was registered outside the plugin fiber: apply() called ctx.webServer.register() directly and never handed the disposer to ctx.effect(). Unloading therefore left the prefix in the web server table, and the next load — HMR reload, disable/enable, or a reinstall through the plugin manager — failed activation with webserver: duplicate prefix route "/dsh-sm", while the stale route kept answering requests. 0.2.1 registers the route inside ctx.effect(), so the disposer follows the fiber and reloads are clean. The leaked route only exists in the already-running process, so restart dsh web once after updating.
ctx.locale, so switching Settings → General → Language (English / 中文) updates the plugin immediately; Chinese users get Chinese dates and all labels.assets/icon.svg for the Plugins inventory, and the Settings navigation reuses DSH's archive glyph instead of the generic gear.dsh.bundle patch layer); every operation goes through official services (workspaceRegistry, sessionQuery, sessionPersistence, sessions, agents, dshHomePath) — no official package is patched or overridden./dsh-sm/* routes reject non-loopback hosts and cross-origin requests (Host + Origin / Sec-Fetch-Site fence).# one line, from a git source
dsh plugin --profile web add github:wuxiangru915/dsh-session-manager
# restart the web server, then hard-refresh the page
Local development:
dsh plugin --profile web add /path/to/dsh-session-manager
# restart the web server, then hard-refresh the page
The repository ships plain-JS lib/ artifacts with no build step, so git-source installs work without any build tooling on the user's machine.

Open Settings → Session Manager:
—), status badges for archived / live / running. Actions: Archive, Delete, and Delete all (two-step; skips running/open sessions).
lib/
├── index.js Host half: cordis plugin (injects webServer) exposing POST /dsh-sm/* routes
└── client.js Browser half: __ModuleLoader__ bundle registering the Settings section
cordis.patch.yml dsh.bundle patch layer — one insert row drives both halves
assets/icon.svg Dedicated plugin icon (manifest-relative, ≤256 KiB)
locale/en.json Plugin-inventory metadata (English)
locale/zh.json Plugin-inventory metadata (Chinese)
browser (Settings page) --POST /dsh-sm/*--> host handlers
archived/list · archived/unarchive · sessions/list · sessions/archive
· sessions/delete · sessions/detail
sessionQuery (list + titles + content) + sessionPersistence (headers/legacy locate)
+ workspaceRegistry (archive/pin/workspace accounting) + sessions (evict/flush)
+ dshHomePath + node:fs (locate, size, delete the JSONL session directory)
+ agents (running guard)
Host routes:
| Route | Payload | Returns |
|---|---|---|
POST /dsh-sm/archived/list | {} | { items, totalBytes } |
POST /dsh-sm/archived/unarchive | { sessionId } | { ok, changed, archivedSessionIds } |
POST /dsh-sm/sessions/list | {} | { items } |
POST /dsh-sm/sessions/archive | { sessionId } | { ok, archivedSessionIds } |
POST /dsh-sm/sessions/delete | { sessionId } | { ok, deleted, sessionId, path?, sizeBytes?, reason? } |
POST /dsh-sm/sessions/detail | { sessionId } | { id, createdAt, cwd, totalEvents, messageCount, truncated, messages } |
Unarchive/delete use the registry's public unarchiveSession / unpinSession paths (with a durable-state fallback for older registries), so the registry's in-memory state, the on-disk global.archivedSessionIds, and the workspace feed stay consistent; the client refreshes the workspace/session stores afterwards so the sidebar updates immediately.
Session directories are resolved through ctx.dshHomePath("sessions") as <home>/sessions/<project>/<sessionId>/; an older persistence locate(header) implementation is preferred when present.
npm test # 19 host checks + 3 client checks
node test/smoke.mjs # host only: stub services + a real temp Harness home
node test/client.mjs # client only: locale dictionaries + section registration
The host smoke test drives apply(ctx) with stub services and creates actual session directories in a temporary Harness home. It covers route wiring, the loopback/origin fence, archive-set read/write, the current snapshot list() shape, the legacy header shape, real on-disk deletion, live-session flush/refusal, orphan-directory cleanup, the “cannot locate the artifact ⇒ refuse to prune” guard, path-traversal rejection and detail extraction. The client test loads lib/client.js in a vm and verifies that both locale dictionaries are complete and that the Settings entry uses the archived-sessions id and re-reads the active locale.
End-to-end verification was performed against a real second profile: archive → archived list → two-step delete → sidebar/list no longer contain the session, plus a browser walkthrough of the English ↔ 中文 switch and the icon in the Plugins inventory.
sessionPersistence.list() snapshots).sessionPersistence.list() returns bare headers (legacy shape) still work.locate() are also supported. A backend with neither locate() nor the shipped JSONL tree makes delete fail closed rather than risk orphan files.