dsh-session-link
Pass context between DSH sessions without the download–transfer–unzip dance.
dsh-session-link is a plugin for DeepSeek Harness. One click in the session header copies a reference to the current session; any other session picks it up and reads the whole conversation back as clean user:/assistant: text.
  
<div align="center">
| Copy | Mention | Read |
|---|---|---|
Header button → @session-<uuid> on the clipboard | @ menu lists sessions by title | session_read projects the conversation |
</div>
When you'd want this
- Session A figured something out; session B needs it. Without this, you export the session log, move the file, unzip it, and read zstd JSONL by hand. Here, A's header button copies
@session-<uuid>, you paste it into B, and the model callssession_read— done. - The reference stays a first-class object.
@session-<uuid>is all ASCII, so it renders as a colored chip in the composer and in sent bubbles — not as a blob of text you have to explain. - What comes back is readable, not raw.
session_readprojects the event stream to conversation rows and skips chunk/tool/reasoning noise; a 7MB log typically reads back as ~60KB of dialogue.
What you get
- Header action「🔗 复制 Session ID」 — one click copies the current session's
@session-<uuid>. Pure clipboard work; no host round-trip. @mention sourcesession— typing@in the composer lists every local session by title (searchable by title or id); picking one inserts the reference as a chip.- Chip repaint —
@session-…references show blue-on-white in sent bubbles, scoped precisely so other@mentions are never touched. session_readtool — the model resolves@session-<uuid>, adsh-session:URI, or@titleand receives{ sessionId, title, cwd, logPath, seqRange, transcript, truncated }. Read-only, live-first, works for sandboxed sessions too.
Preview
Copy — one click in the session header puts @session-<uuid> on the clipboard:
<img src="assets/copy.gif" alt="Copying a session reference from the header" width="720">
Paste — the reference lands in another session's composer as a chip, ready to send and read:
<img src="assets/paste.gif" alt="Pasting the session reference as a chip" width="720">
Install
Pinned to a release — build artifacts are committed, so there is nothing to build and no registry involved:
dsh plugin --profile web add "github:jinhuang712/dsh-session-link#v0.1.0"
# restart dsh web, then refresh the pageOr track main to pick up unreleased commits:
dsh plugin --profile web add "github:jinhuang712/dsh-session-link#main"Or from a local checkout, linked so edits show up on refresh:
git clone https://github.com/jinhuang712/dsh-session-link.git
dsh plugin --profile web add "link:$PWD/dsh-session-link"If your dsh profile directory is a pnpm workspace, pnpm asks for -w before touching its root — pass it through: dsh plugin --profile web add -w ….
After install (and one restart), the header button, the @ menu group and the session_read tool are available permanently in every session. The companion skill dsh-session-link registers with the install (dsh.skills declaration) and teaches the model when and how to read a reference.
Usage
Share a session: open it, click「🔗 复制 Session ID」in the header, paste into any other session's composer — the reference appears as a chip — and send.
Pick a session without switching: type @ in the composer, find the session by title in the session group, insert it.
Read a reference: the model calls
session_read({ "link": "@session-7fc2d98e-…" })| Input | Behaviour |
|---|---|
@session-<uuid> | Exact, recommended — what the button produces |
dsh-session:<b64> | The URI form from older link payloads |
@title | Exact title match; a miss or ambiguity returns candidates/ids — retry by id |
Optional: maxChars (projection budget, default 64000), truncate ("tail" keeps the most recent rows — default; "head" keeps the earliest), raw (debug: first 200000 chars of the original JSONL).
Boundaries (measured, not guessed)
- Chinese titles (
@分析…) don't chip — the shipped@-mention regex has no
/u flag. The copy format is the id, never the title, precisely for this.
@titleresolution needs the host'ssessionQuerytitle snapshots; when
unavailable the tool says so and points at the id form.
- Reading is read-only and live-first:
sessionQuery.readSessionreflects a
running session at its current state; the on-disk log path is resolved for reference (logPath) but never written.
Architecture
- Host half (
lib/index.mjs): Cordis entry
- defineTool registers session_read; execute parses the reference, resolves titles via sessionQuery, projects the JSONL event stream, and caps it by budget - no webServer routes — the copy button is pure clipboard
- Client half (
src/→lib/client.js):__ModuleLoader__.loadbundle
- runtime.js binds the host's React from the loader's require — never bundled, so the plugin shares the host's React instance - copy-button.js the header action; mention.js the @ session source; chips.js the MutationObserver repaint
- Skill (
skills/dsh-session-link/SKILL.md): usage guide shipped with the install
Develop
lib/client.js and its source map are build output, committed so the GitHub install stays one line. Edit src/, then:
pnpm install
pnpm build # esbuild src/index.js -> lib/client.js + lib/client.js.mapVerify
It loaded at all:
__DSH_BOOT__includes thedsh-session-linkclient row, and
/plugins/dsh-session-link/client.js returns 200
cordis_inspect_query(Tool.listTools) listssession_read
Then exercise the loop:
| Look at | Expect |
|---|---|
| the session header | the「🔗 复制 Session ID」button; clicking flips it to「✓ 已复制」 and the clipboard holds @session-<uuid> |
| the composer | pasting the reference shows a chip; typing @ offers the session group by title |
| a sent message | the reference renders as a blue chip, other @ mentions unchanged |
session_read on the pasted reference | returns the session's title/cwd and a user:/assistant: transcript |
Uninstall
- Remove the
dsh-session-linkinsert row from the web profile's
cordis.patch.yml
- Remove the
dsh-session-linkdependency from the web profile's
dsh.profile.bundles and run pnpm remove
License
MIT