dsh-session-suite
Cross-session chat, a directory-as-queue mailbox, workspace registration, and a Session Hub panel for
DeepSeek Harness (DSH) — packaged as one Cordis plugin.
跨会话对话 + 目录即队列信箱 + 会话工作区登记 + 「会话中枢」面板,打包为一个 DSH 插件。
Status: beta (0.2.x). Targets DSH >=0.2.1-0 <0.3.0.
Why use it / 为什么用它
-
一个总信箱 + 一个心跳,替代 N 个专用信箱。
One shared mailbox beats one mailbox per session.
信箱最初是给每个会话各设一个;加入 信箱守卫(gatekeeper)——拦截垃圾信件、把来信分派给正确的会话——
之后,不再需要给单个会话专设信箱:所有来信统一投到总信箱,配一个心跳即可完成分发。
Mailbox started as one inbox per session. With a gatekeeper triaging spam and routing letters, you no longer
need per-session mailboxes: everything lands in one shared mailbox, and a single heartbeat drives delivery.
-
按名投送,不用查 ID。 Address sessions by name, not by id.
talk_to_session 直接写会话名字(targetName)就能投;会话 ID 对用户隐藏,只有重名时才追问编号,
且30 分钟内记住你的选择,不重复问。
talk_to_session takes the session name (targetName) directly — ids stay hidden. Only duplicate names ask
for a numbered pick, and that pick is remembered for 30 minutes.
-
免辨活踢醒:未归档就能派活。 No "is it alive?" check — if it is not archived, it can be woken.
wakeable = 记录存在 && 未归档;不用人工分辨会话是活的还是死的,未归档的直接唤醒派活,只有归档过的才踢不醒。
wakeable = record exists && not archived: hand work to any unarchived session; only archived ones stay asleep.
-
找出并清掉"官方列表里看不到"的会话。 Find every unarchived session — even ones DSH does not list.
session_probe 做全量盘点(含误建、被隐藏的会话);配合会话中枢面板的归档 / 软删除 / 回收站清空与宿主侧真删除,
补上 DSH 官方"会话删不掉"的缺口。
session_probe inventories every session (including mis-created or hidden ones); the Session Hub panel adds
archive / soft-delete / trash-empty and a host-side hard delete — covering DSH's missing "delete a session" path.
删除不静默:软删除先进回收站,硬删除有二次确认;归档只是隐藏,随时可恢复。
Deletes are explicit: soft delete goes to a trash area, hard delete asks for confirmation, and archive is a
reversible hide.
Origin / 出身
It started as two independent plugins — cross-session chat and a mailbox — which were later merged into one.
From that merge the Session Hub (panel + workspace registration) and session handover grew.
始于「跨会话对话」与「信箱」两个独立插件,合并之后生长出「会话中枢」与「会话交棒」。
Features / 特性
- Cross-session chat — agents talk to each other by session name or id:
talk_to_session, steer_session,
stop_session, session_contacts, session_probe, create_session, update_session.
- Session handover — one call rebuilds a session without losing context: handoff package → successor session on
the same route → rename → move scheduled tasks → update the fleet table → archive the old session last.
- Directory-as-queue mailbox — any external program can drop a JSON letter into
inbox/; the plugin delivers it
to the addressed session, waits for the turn, and writes a reply file into outbox/. No ports, no server.
- Workspace registration — sessions created through the plugin (or attached later) are registered into the
Workspace matching their
cwd, so they appear under the right group in the sidebar.
- Session export / import — pack a whole session into one
.zip (works for archived sessions too) and
restore it later; attachments are referenced, not copied, and imports never overwrite an existing session.
- Session Hub panel — sidebar entry + grouped session inventory (rename / archive / soft-delete), a mailbox gate
tab (allow-list, passcode, gate model), and a letters tab (receipts, redeliver, stats).
- Gatekeeper (optional) — an LLM triage step for letters: whitelist, optional shared secret, optional temporary
passcode, prompt-injection hardening (letter text is data, never instructions).
Install / 安装
dsh plugin add dsh-session-suite
or add it to your profile's cordis.patch.yml:
- id: dsh-session-suite
name: "dsh-session-suite"
config:
writeEnabled: true # cross-session write path (OFF by default)
dir: "" # mailbox root; empty = <DSH_HOME or ~/.dsh>/mailbox
The plugin declares its own bundle patch (cordis.patch.yml) and its browser half (./client), so a normal
dsh plugin add is enough — no manual wiring.
Tools / 工具
| Tool | What it does |
|---|
session_contacts | List addressable sessions (id, cwd, status, wakeable, archived). |
talk_to_session | Send a message to another session and (optionally) wait for its reply. |
steer_session | Inject a message into a running turn (redirect without ending it). |
stop_session | Stop a session's current turn (same effect as the UI stop button). |
session_handover | Five-step session rebuild: handoff → successor → rename → schedules → archive old last. |
create_session | Create a session on a chosen cwd / provider / model / effort, registered into its Workspace. |
update_session | Rename a session by appending a durable title event. |
session_probe | Read-only runtime surface of every session (status, cwd, seq, available methods). |
session_host_probe | Read-only capability probe: which cross-session services this Host actually mounts. |
workspace_lookup | Read-only: find the Workspace matching a cwd (live service, else storages/workspace.json). |
read_session_log | Read a session log (multi-frame zstd) as filtered text — see what another session actually did. |
Export / Import · 导出 / 导入(0.2.0)
导出:会话清单每行都有「导出」(归档与回收站里的会话也能导出——这正是"先归档、需要时再启用"的主场景)。
宿主把整个会话目录原样打成一个 .zip,默认落在 ~/.dsh/exports/,导出成功后自动在文件管理器里定位该文件。
dsh-session-export/
manifest.json # schemaVersion, sessionId, title, cwd, encodedDir,
# exportedAt, pluginVersion, byteSize, turnCount, attachmentRefs
session/<session-id>/ # 会话目录原样(session.v4.jsonl.zstd + 伴随文件)
- 全程只读源目录;导出后源会话不受影响。
- 附件是全局内容寻址库(
~/.dsh/attachments/),不随包导出;manifest 里的 attachmentRefs
与导出提示会如实告诉你"含 N 处附件引用,未包含在包内"。
导入:面板顶部「导入会话包」→ 选 .zip。宿主依次校验 manifest.schemaVersion、会话日志的 zstd
完整性、sessionId 格式;然后解到临时目录、原子 rename 进 sessions/<编码工作区>/<sessionId>/,
目标工作区不存在会自动创建;该 cwd 还没登记工作区时,走插件现有的工作区登记路径。
- ID 冲突一律拒绝:目标已有同 id 会话目录时报错返回,不覆盖、不合并。
- 导入不执行任何东西:纯文件落位,包内脚本一律不运行。
- v1 限制:导入后需重启 dsh web 才会出现在对话列表(宿主启动时扫描会话目录)。
已知限制 (Known limitations)
- 导出/导入的 zip 由系统
tar -a 生成:Windows(tar.exe)与 macOS(bsdtar)开箱可用;Linux 的 GNU tar 不支持 -a,
需要 bsdtar 或预装 zip/unzip(v1 未内置 zip 库)。
- 附件不入包(manifest 只记
attachmentRefs),跨机迁移后附件引用会缺失。
Mailbox protocol / 信箱协议
一个总信箱 + 一个心跳。 所有来信投到同一个目录;**信箱守卫(gatekeeper)**负责拦截垃圾、按名字/沿用/模型分派,
插件用 500 ms 轮询(心跳)把信送到目标会话,再把回执写回 outbox/。不需要为每个会话单设信箱,也不需要常驻服务或端口。
最小配置(总信箱 + 心跳):
- id: dsh-session-suite
name: "dsh-session-suite"
config:
dir: "" # 总信箱;空 = <DSH_HOME 或 ~/.dsh>/mailbox
pollMs: 500 # 心跳:扫 inbox 的间隔
heartbeatMs: 1000 # 心跳:状态刷新间隔
allowTargets: [] # 建议显式填白名单(或用 <dir>/allow.json)
secret: "" # 共享目录建议设置一个投递密钥
Drop a JSON file into <dir>/inbox/; the plugin moves it to processed/ and writes a reply into <dir>/outbox/.
{ "target": "<session name or id>", "message": "hello", "from": "my-app", "id": "req-0001" }
target may be omitted when the letter names a session (to / sessionName / targetName); otherwise the
gatekeeper decides. Replies:
{ "target": "session-name", "seq": 42, "at": "2026-01-01T00:00:00.000Z",
"text": "reply text", "requestId": "req-0001", "from": "my-app",
"status": "ok", "reason": "completed" }
status is one of ok · partial · no-reply · aborted-by-target · error · deduped · blocked ·
target_queue_timeout. Duplicate id/requestId values are deduplicated.
Configuration / 配置
| Key | Default | Meaning |
|---|
writeEnabled | false | Master switch for the cross-session write path. |
maxHops | 5 | Maximum hand-off chain depth. |
syncTimeoutMs | 120000 | How long a sync delivery waits for the peer's text. |
pollIntervalMs | 500 | Poll interval for chat replies. |
maxReplyChars | 200000 | Cap on copied-back reply text (also the outbox cap). |
maxMessageBytes | 65536 | Cap on one delivery. |
dir | "" | Mailbox root; empty = <DSH_HOME or ~/.dsh>/mailbox. |
pollMs | 500 | Inbox scan interval. |
heartbeatMs | 1000 | Heartbeat interval (clients treat >30 s as stale). |
replyTimeoutMs | 600000 | Max wait for a turn's reply before writing partial/no-reply. |
queueTimeoutMs | 660000 | Per-target in-flight queue release threshold. |
allowTargets | [] | Whitelist of target session ids (used when allow.json is absent). |
secret | "" | Optional shared secret required in each letter (empty = no check). |
stateDir | "" | Fleet table directory (fleet.json); empty = <DSH_HOME>/.dsh/state. |
gatekeeper.* | see code | Optional LLM triage: provider / model / effort, fast paths, passcode, receipt & urge rules. |
allow.json (inside dir) takes precedence over allowTargets, is re-read within 2 s, and is fail-closed: a
corrupt file rejects everything instead of silently allowing all.
Safety notes / 安全边界
- Writes are opt-in: with
writeEnabled: false the plugin changes nothing about any session.
- No shell, no arbitrary code: the mailbox only reads/writes JSON files under
dir; sessions run with their own
permissions. Letter text is passed as data (never as instructions) and the gate model gets no tools.
- Prompt-injection hardening: gate calls are locked to one provider/model/effort and carry no tool list.
- Fail-closed allow-list, optional shared secret, and a passcode gate for shared directories.
- The panel is read-mostly: destructive actions are soft-delete or explicit confirmations.
Requirements / 环境
- DSH
>=0.2.1-0 <0.3.0 (desktop host with the agents / sessions / tools / llm services).
- Node.js
>=22 for the bundled tools; Windows, macOS and Linux are supported (paths are resolved from the
environment, never hard-coded).
Development / 开发
npm run bundle # build the client half (tsdown + scope wrapper)
npm run smoke # core smoke suite
npm run smoke-ui # panel / wakeability suite
npm run smoke-ops # operations + archive suite
npm run smoke-gate # gatekeeper suite
The host half (lib/index.js) is hand-written ESM and is not generated; the build only compiles
src/client/*.ts into lib/client.js.
License
MIT — see LICENSE.