Copy and open `dsh://` session deep links, or paste them into another conversation to inject a bounded, read-only snapshot of the referenced session.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-session-link
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:PwnKY/dsh-session-link
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
· 中文 · English
Codex-style session deep links for DeepSeek Harness (dsh)
Copy a link from any conversation, then paste it into a different conversation — the referenced session's context is snapshotted and injected as bounded, read-only background context right before your prompt. The same link also opens the conversation in the browser.
Install
# One command: installs the package, auto-joins the profile's bundle layer,
# and auto-applies the composition rows — no manual yml edits.
dsh plugin --profile web add dsh-session-link
# Restart the web GUI and refresh the page.
dsh web
The package declares a dsh.bundle patch (cordis.patch.yml); dsh plugin add detects it and adds the package to dsh.profile.bundles, so the session-link row composes automatically at boot. The upstream session-reference service is composed by the shipped web bundle since dsh 0.1.0-rc.8 — this package no longer re-inserts it (a duplicate id fails the boot).
Generic npm install (package only, not wired into a profile):
npm install dsh-session-linkManual install (without the bundle mechanism): see Quick start.
┌─ session A ──────────────┐ ┌─ session B ──────────────────────┐
│ 🔗 copy session link │ │ user: please see this session: │
│ → dsh://session/session-│ ─────▶ │ dsh://session/session-… │
│ …abc │ paste │ │
└──────────────────────────┘ │ model: (receives snapshot of A │
│ + the prompt, with @label)│
└───────────────────────────────────┘
Features
- 🔗 One-click copy — a "Copy session link" button in the conversation header copies
dsh://session/<sessionId>(codex:// / claude:// style). - 📖 Cross-conversation context — paste the link into any conversation; the source session's conversation is snapshotted (bounded, read-only) and injected right before your prompt.
- 🖱️ Clickable deep links (Windows) — with the registered
dshURL protocol handler, clicking adsh://link opens the web GUI and selects that session. - 🛡️ Fail-open — malformed links, unreadable sessions, or self-references never break your turn; the link stays as plain text and the failure is logged.
How it works
The feature reuses the shipped @deepseek-ai/dsh-session-reference service, which already owns canonical session URIs (dsh-session:<base64url>), mention parsing, snapshot projection, and byte-budget retention. This package wires that service into the live agent loop and the web surface:
- Host half (
lib/index.js) — a cordis plugin subscribing to theagent/pre-stepseam. When a claimed direct user prompt contains a session deep link, every supported link form is normalized into canonicaldsh-session:mentions, parsed into structured references, snapshotted viasessionReferenceResolver.prepare(), and the aggregated read-only snapshot context is placed immediately before the direct prompt. The hook is transport-agnostic, so pasting a canonical URI into the TUI works the same way. Since dsh 0.1.0-rc.8 the service subscribes toagent/pre-stepfor canonical mentions itself; this listener runs outermost (prependplus thesessionReferenceResolverinjection) and only resolves the deep-link forms upstream does not know about (dsh://, web links), so one link never injects twice. - Browser half (
lib/client.js) — a static client package (dsh.clientdeclaration) rendering the copy button inconversation.session.header.actionsand opening/?session=<sessionId>deep links (legacy/s/<sessionId>and#/s/<sessionId>forms included) by selecting the target session throughctx.uiWorkspace.openSession()once the catalog has loaded (bounded ~10 s retry, with a warning on final failure).
Link formats
| Form | Example | Purpose |
|---|---|---|
| Deep link | dsh://session/<sessionId> |
copied by the button; clickable via the protocol handler; parsed when pasted |
| Browser URL | http://<host>:3080/?session=<sessionId> |
what the protocol handler opens; also accepted when pasted |
| Legacy browser URL | http://<host>:3080/s/<sessionId> |
redirected (302) to the form above; also accepted when pasted |
| Canonical URI | dsh-session:<base64url(JSON sessionId)> |
the lossless URI of dsh-session-reference; also parsed when pasted |
| Markdown mention | @[label](dsh-session:…) |
parsed and rendered as @label (TUI mention form) |
Only links carrying a harness-shaped session id (session-…) are treated as references, so unrelated dsh://… or /s/… text is never hijacked.
Why the browser URL is
?session=: since dsh 0.1.1-rc.2 the web server answers unknown paths (including/s/<id>) with 404 — the old SPA fallback is gone — so only/(and the configured index) boot the app. The session marker therefore rides on the index route, and the host half registers a/s/<id>→/?session=<id>302 redirect so old links, bookmarks, and history entries keep working.
Quick start
Requires DeepSeek Harness dsh (any profile with the web surface) and dsh ≥ 0.1.7-rc.1 (both the 0.1.7 and 0.2.x lines are supported; 0.2.0-rc.2 is verified end to end): that minimum is for the ctx.uiWorkspace.openSession() navigation seam the browser half depends on (earlier versions lack it, and the old ctx.sessions.open was removed upstream). The session-reference service is not tied to that release — the shipped web bundle has provided it since dsh 0.1.0-rc.8. The manifest also declares engines.dsh (^0.1.7-rc.1 || ^0.2.0-rc.2) so plugin marketplaces can judge compatibility directly instead of guessing from peers.
# 1. One command: installs the package, auto-joins the profile's bundle layer,
# and auto-applies the composition rows (see "Install" above).
dsh plugin --profile web add dsh-session-link
# 2. Restart the web GUI and refresh the page.
dsh web
# 3. (Windows, optional) make dsh:// links clickable:
powershell -ExecutionPolicy Bypass -File register-protocol.ps1
Manual install (without the bundle mechanism):
pnpm add dsh-session-linkin the profile directory, then add to the profile's patch layer (e.g.~/.dsh/profiles/web/cordis.patch.yml):- insert: - id: session-link name: 'dsh-session-link'Then restart
dsh web. Nosession-referencerow is needed: the shipped web bundle has provided it since dsh 0.1.0-rc.8.
Usage
- Click the 🔗 button in a conversation header to copy its deep link.
- Paste it into another conversation and send — the model first receives the referenced session's read-only snapshot, then your prompt (the link is replaced by its readable
@sessionId). - Or click the
dsh://link anywhere to open that conversation in the browser.
Windows dsh:// protocol handler
register-protocol.ps1 registers the per-user dsh URL protocol (HKCU, no admin rights) so clicking a dsh://session/<id> link anywhere (browser, chat app, terminal) opens this machine's web GUI and selects that session. The launcher is dsh-open.cmd.
The landing address no longer hardcodes a port — dsh-open.cmd resolves it in order:
- env var
DSH_SESSION_LINK_WEB_URL(explicit override) - the state file
<state dir>\web-url, written at boot by the plugin's host half (it holds this Web service's real address, e.g. DSH Desktop's non-3080 port) http://127.0.0.1:3080(plaindsh webfallback)
The state dir is %DSH_SESSION_LINK_STATE_DIR% → %LOCALAPPDATA%\dsh-session-link → %TEMP%\dsh-session-link. Every click records which source it used in %TEMP%\dsh-open.log (source=env|file|default|default-invalid), which makes a stale file easy to spot.
# register (refuses to steal dsh:// from another program)
powershell -ExecutionPolicy Bypass -File register-protocol.ps1
# take the scheme over from another handler (e.g. the desktop shell)
powershell -ExecutionPolicy Bypass -File register-protocol.ps1 -Force
# unregister
powershell -ExecutionPolicy Bypass -File register-protocol.ps1 -Uninstall
The web GUI (dsh web, or the desktop client) must be running for a link to open a session.
DSH Desktop
DSH Desktop ships its own dsh registration (DSH Desktop.exe "%1", description URL:DSH Protocol), but the 2.0.x shell does not parse session parameters (no setAsDefaultProtocolClient, no open-url, no ?session= handling; second-instance merely focuses the window). Clicking a dsh:// link there only raises the window — it never jumps to the session.
What this plugin offers:
| Choice | How | Result |
|---|---|---|
| Take the scheme over | register-protocol.ps1 -Force |
dsh:// goes to dsh-open.cmd, which reads the live address the host half publishes (works on the desktop's own port too), so sessions open |
| Leave the desktop registration | do nothing | dsh:// stays with the desktop shell and cannot jump; wait for upstream parameter support |
| Skip the protocol | copy button + paste into a conversation | cross-conversation context needs no protocol and works everywhere |
The desktop profile loads both halves of this plugin, so it publishes the live address file at boot; taking the scheme over is then enough. If you would rather have the desktop shell implement parsing itself, that is an upstream request — this plugin cannot do it for them.
What the model sees
Two consecutive user-role messages: the ## Referenced sessions untrusted snapshot (capped at 64 KiB of JSON per source, older non-checkpoint messages dropped first, long messages head/tail-truncated with an exact omission notice), followed by the direct prompt with the link replaced by its readable @sessionId label. Instructions, permission claims, or tool requests inside a snapshot are not followed unless the current user repeats them.
Configuration
Defaults of the underlying service apply (max 3 references per message, 64 KiB per source). The session-reference row comes from the shipped web bundle; override it by id in your profile's patch layer (patches target ids, so the override need not live in the same layer), e.g.:
- id: session-reference
config:
maxReferenceBytes: 131072
Tests
pnpm install
npm test
host-half.test.mjs— drives theagent/pre-steplistener through a real cordis waterfall (dsh://links, both web link forms, canonical URIs, plain text, malformed URIs, prepare failures, a resolver withoutadditionalContext), asserts the bundle patch never re-insertssession-reference, drives the legacy/s/<id>redirect route (302 / 404 / 405), and checks that the host half publishes the live Web URL into the state directory (and publishes nothing without awebServer).launcher.test.mjs— static assertions overdsh-open.cmd's three-step address chain (env → state file → 3080) andregister-protocol.ps1's conflict gate (-Force/ owner report); on Windows it also drives a probe copy (startswapped forecho) through the fallback, the state file (trailing slash and LF-only files), the env override, and a garbage file, checking thesource=line each time.client-half.test.mjs— loads the browser bundle under a DOM shim with controllable fake timers: checks the plugin surface andinject(includinguiWorkspace), header-action registration, that the copy button still emits thedsh://value, and drivesctx.uiWorkspace.openSession()against a fake context deliberately withoutsessions.open(all four deep-link URL forms plus precedence, encoded/malformed/empty/unrelated URLs, a delayed catalog, a transient synchronous navigation failure, a transientgetSnapshotthrow, a missing target that warns after ~10 s with a bounded budget (50 retries of 200 ms),ctx.effectteardown cancelling queued retries plus a late already-dequeued callback after disposal, and exactly-once navigation). It also checks the package manifest'sdsh.client.injectand^0.1.7-rc.1 || ^0.2.0-rc.2dependency ranges, and asserts the installed realdsh-client-ui-workspace'sopenSession(target: SessionTarget): voiddeclaration (the installed version is reported but not pinned).resolver-integration.test.mjs— mounts the real shipped resolver (which listens onagent/pre-stepitself since dsh 0.1.0-rc.8) beside this plugin on one cordis context: asserts thedsh://link injects once and a canonical URI is injected only by upstream (no double injection / ordering regression), and that self-references and unreadable sessions stay fail-open.inspect-logs.mjs <sessions-dir> [sessionId…]— decompresses concatenated-zstd session logs and reportssession-referenceevents (useful for verifying injection).
Limitations
- Links resolve only on the machine whose
$DSH_HOMEholds both sessions; session ids are opaque and local. - The browser deep link opens sessions present in the current session list and not archived; an archived target is cleared by upstream and sessions outside the list are not auto-resumed. When the target never appears in the catalog, the client gives up after a bounded ~10 s retry and warns, leaving the default view in place.
- The browser deep link needs that browser to hold this
dsh web's login cookie (visit the token URL printed bydsh webonce; the cookie then lasts its full lifetime); otherwise the request answers 401. - The legacy
/s/<id>form depends on the plugin's redirect route (not registered in compositions withoutwebServer); the new?session=<id>form does not. - If a referenced session cannot be read (missing, budget exceeded, self-reference), the link stays as plain text and the message still sends; the failure is logged on the host.
- Text-only projection: images and other non-text blocks are not propagated across sessions (upstream service limitation).
License
MIT © PwnKY. Built on @deepseek-ai/dsh-session-reference (MIT, DeepSeek).
Links
More in this category
ranxianglei/billion-context★ 6881
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
Minglink/dsh-infinite-gen-4★ 4155
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
liangmianya/dsh-synapse★ 704
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
MichengAI/dsh-archive-manager★ 336
Adds an archived-sessions page in Settings to search, restore, and delete archived DeepSeek Harness sessions by workspace.
Nwflower/dsh-chat-import★ 308
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
SiriLee/dsh-rewind★ 258
Conversation rewind: one click back to any earlier user message, in the same window — no new branch, no leftover copy; the target message is refilled into the composer, with optional workspace-file restore — the full Claude Code /rewind semantics.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.