dsh-plugin-session-delete
Manifest validA DSH plugin that deletes a conversation from disk — the destructive Session action DSH deliberately omits — plus bulk cleanup of empty Sessions. For DSH 0.2.x.
description: "Delete one conversation permanently from the Web sidebar, plus bulk cleanup of unused blank Sessions." kind: "package-reference"
dsh-plugin-session-delete
English | 中文
A DSH plugin: delete a conversation, for real, from disk — plus bulk cleanup of empty Sessions.
DSH has no delete on purpose: archiving is the shipped non-destructive path, and its
storage backend (SessionPersistence) only creates, opens, flushes, stats and lists. This
plugin exists for the other case — when you want the bytes gone — and keeps every policy
decision (what may be removed, what must be refused) in the plugin rather than in a storage
layer.

Before you install: removal is a move, and there is a window to undo it. The whole Session directory — every format generation of its log — plus its projection-cache record is renamed into a trash beside the sessions root (the same volume, so the move is atomic), where it stays restorable for a retention window — seven days by default, or whatever the profile configures. The sidebar foot lists what is in there so you can put one back or destroy it now; anything left when the window closes is purged. A Session that is live in the process is refused (
session-live), and the cleanup action additionally skips the open Session, anything running, and anything with activity inside a one-hour grace window. Attachments, exported files and workspace files are never touched.

Install
# from GitHub — use the https form (see the note below)
dsh plugin --profile <profile> add https://github.com/SereinHK/dsh-plugin-session-delete.git
# or the packaged tarball attached to the latest release: no git needed
dsh plugin --profile <profile> add https://github.com/SereinHK/dsh-plugin-session-delete/releases/download/v0.1.2/dsh-plugin-session-delete-0.1.2.tgz
Or paste the same spec into the sidebar's Plugins page. The package carries its
own bundle patch (dsh.bundle.patch), so the plugin row is inserted for you — no
hand-editing of the profile's cordis.patch.yml. Reload the window (or the instance)
afterwards; lib/ ships prebuilt, so nothing is compiled during install.
Two distribution forms, one package. The git address is what the project itself uses and follows a branch; the release tarball is a frozen snapshot of the same bytes and needs no
giton the installing machine — pnpm fetches an https URL like any other. Both install identically: the tarball shipslib/,cordis.patch.ymland the manifest that declares the bundle patch.Not published to npm. This repository is the distribution channel, so the registry form (
dsh plugin --profile <profile> add dsh-plugin-session-delete) has nothing to resolve — use thehttps://…address above.Use the
https://…address, not thegithub:owner/reposhorthand. pnpm normalizes the shorthand to an SSH URL (git+ssh://git@github.com/…), which fails on any machine without an SSH key for GitHub. Both forms select the same commit; the https one needs nothing but the public repo.What gets pinned, and what does not. The install records the dependency without a ref, so it follows the default branch — but pnpm's lockfile pins the exact commit it resolved, so re-running the install does not move it on its own. To follow a release deliberately, write the ref into the profile's
package.jsonand re-run the install:"dsh-plugin-session-delete": "git+https://github.com/SereinHK/dsh-plugin-session-delete.git#v0.1.0"The desktop app supplies its own package manager, so the Plugins page works without
pnpmonPATH. A baredsh plugincommand needspnpmavailable.
Requirements: DSH 0.2.x. The row menu extension point this plugin registers
into (sidebar.workspaces.session.menu.item) does not exist in the 0.1.x line — on
that build only the sidebar cleanup action can appear. Verified against
@deepseek-ai/dsh-desktop-runtime 0.2.0-rc.2.
Uninstall: remove it from the Plugins page, or dsh plugin --profile <profile> remove dsh-plugin-session-delete. From a source checkout, node install.mjs --uninstall.
Summary
sidebar.workspaces.session.menu.item— a red “Delete conversation” row in every Session's...menu, after the shipped pin/rename/fork/archive rows.sidebar.footer.action— “Clean up empty conversations” beside Settings: removes blank Sessions (blank: true, i.e. never started a turn) in one run, and “Trash” next to it, listing what was removed and offering restore or destroy.shell.overlay— the confirmations and the trash listing, each naming Sessions by their title (or path) and saying what they hold: the cleanup shows what a run would reclaim, and the trash shows how long each entry has left.- The node half registers five routes inside Connection's authenticated
/apifence:POST /api/session.delete(the move into the trash),POST /api/session.unused(the durable per-Session facts — is the log empty, and when was it last prompted — which the page cannot derive for itself),POST /api/session.trash(the listing, which purges what has expired before answering),POST /api/session.restore, andPOST /api/session.purge(one entry, or every one of them).

Deletion is irreversible: the whole Session directory under the sessions root (every format generation) and its projection-cache record are removed. Exported files, attachments and workspace files are untouched.
Use this package
A Session row's ... menu gains:
Pin / Rename / Fork / Archive
─────────────────────────────
Delete conversation ← this package, order 900, danger
The dialog names the conversation, states the scope, and only then offers
Delete permanently. After a successful removal the Host emits
api-session/removed (the row disappears immediately) and the browser re-pulls
the authoritative list.
Cleanup lists the blank Sessions it found — workspace path and idle age — before
offering a count-labelled confirm, then runs one removal at a time and reports
deleted / failed / skipped when it settles.
What is skipped, and why
| Situation | Behaviour | Reason |
|---|---|---|
| The conversation is open in a window | Host answers 409 session-live | Its write handle would recreate the log or corrupt it |
| A blank Session has activity inside the last hour | Skipped by the client plan | “New Session” is blank too, and another window may be sitting in it |
| A blank Session's Agent is running | Skipped by the client plan | Not an abandoned draft |
| The id is not session-id shaped | Host answers 400 invalid-session-id | The id becomes a path segment; it is validated before any path work |
The target is not <root>/<project>/<encoded id> | Host answers 500 unsafe-target | Last gate before a recursive removal |
Attachments (<DSH_HOME>/attachments) | Never touched | Content-addressed and shared across Sessions, with no refcount |
Failures
The route answers { ok: false, error: { code, message } }; the dialog words the
codes it owns (session-live, session-not-found, unsafe-target) in the
active language and shows the Host's own diagnostic beneath it.
How it works
SessionPersistence exposes create/open/flush/stat/list and nothing
else, /clear only drops the selection, and archive only hides a row — so a
plugin that deletes must own the removal. This one addresses the layout the
shipped JSONL backend writes:
<root>/<projectKey(header.cwd)>/<encodeSegment(sessionId)>/session[.vN].jsonl[.zstd]
<root> → dshHomePath('sessions')
projectKey and encodeSegment are re-implemented here (the backend does not
export them) and are exercised against the real session root by the test suite. A
directory scan under the root backs them up, so a layout change degrades to
session-not-found rather than to a deletion somewhere else; containmentRefusal
then re-checks that the target is exactly one project directory below the root and
named after the encoded id.
The whole surface arrives through one route rather than Typert Remotes, because a
plugin's browser half cannot ship a generated ./remote contribution without the
in-tree generator — ctx.connection.fetch.register is the supported seam for a
plugin-owned endpoint, and it inherits the Host/Origin + browser-token fence.
Verified against
| Item | Value |
|---|---|
| Runtime | @deepseek-ai/dsh-desktop-runtime 0.2.0-rc.2 (cordis 4.0.4) |
| Slots | sidebar.workspaces.session.menu.item, sidebar.footer.action, shell.overlay |
| Host services | connection, sessionPersistence, sessions |
| Browser seed modules | react, @deepseek-ai/dsh-client-ui-primitives |
| Storage | JSONL session backend at dshHomePath('sessions'); projection cache at dshHomePath('storages')/session_projcache |
Known limitations
- A live Session cannot be deleted. This is the deliberate half of the
contract: switching away and deleting it afterwards is the supported path.
A core
SessionPersistence.removeplus a session-controller endpoint would let a Host stop and release the Session first; see the proposal in this workspace'sdocs/upstream-issue-session-persistence-location.md. - Deleting a parent orphans its children. Child logs are separate
(
parentSessionis lineage metadata);sessionQuery.traceSessiondegrades to the first unresolvable parent instead of failing. - No trash, no undo. The removal is
rm -rfon the Session directory. Export first (/export) when a conversation matters. - Version gating is deliberately absent. A
@deepseek-ai/dsh-*peer would be checked against the runtime version by app-boot, so^0.2.0would reject a running0.2.0-rc.2. This package codes against stable service and slot shapes and would rather fail loudly at use than refuse to mount. - README.i18n.yaml is not hand-written here: their pairing tooling generates the
section hashes (
pnpm run verify-translation-pairing --write …).
Development
The repository root is the package, so that a git install resolves its manifest.
node tools/build.mjs # src/ -> lib/ (also: node tools/build.mjs --check)
node --test "tests/*.test.ts" # 30 tests: host routes, the trash, cleanup plan, dictionaries
node tools/verify-artifact.mjs # 87 host-route checks against the built bytes
node tools/verify-browser.mjs # 64 render/interaction checks, refusals included
node tools/verify-bundle.mjs # is the package installable, and installed coherently?
node tools/verify-runtime-contract.mjs # do this plugin's assumptions about the
# installed runtime still hold? (run after a DSH upgrade)
node tools/verify-boot.mjs --scan # which running instance carries the row
node tools/demo-home.mjs # an isolated home (own DSH_HOME) with blank
# Sessions, for screenshots or a safe review
node install.mjs [--profile web] [--bundle] [--register-only] [--uninstall]
tools/demo-home.mjs copies a working profile into a separate DSH_HOME and seeds
blank Sessions there, so the UI can be exercised (or photographed) without exposing a
real workspace. Run that home with DSH_HOME=<dir> dsh --profile demo --port <n>
— note that with a profile name, dsh takes no subcommand: --profile <name> is the
request to run it.
Requires Node 22.6+: the build and the suites use Node's own TypeScript stripping
rather than a toolchain. CI runs exactly the commands above on Node 24 (.github/workflows/verify.yml),
with no install step and no network.
lib/ is committed on purpose: DSH serves prebuilt client bundles and runs no
bundler at install time, so a git install needs the built bytes in the tree. That is
the one place this package differs from the upstream monorepo convention, which
ignores lib/ and builds in CI before publishing. After editing src/, run
node tools/build.mjs and commit lib/ alongside it — a stale lib/ is a plugin
that silently does nothing.
The build is dependency-free: Node's own module.stripTypeScriptTypes plus a small
assembly of the src/client/* graph into the exact
window.__ModuleLoader__.load({ id, factory }) script the host serves. It accepts a
declared subset of TypeScript and fails loudly outside it (a require() for anything
but a platform seed module would take the whole page's boot down). Upstream would run
pnpm run bundle (tsdown) instead; the sources are written to their conventions.
tools/ also carries the diagnostics this plugin was debugged with, and they are
worth keeping for the next plugin: verify-boot.mjs --scan (every instance on the
machine, and whether it carries the row), force-reload.mjs (does the Loader still
rebuild the profile tree?), probe-reload.mjs (do patch edits still apply?),
verify-live.mjs (are the served bundle bytes the installed ones?), asar.mjs (read
the running app's app.asar).
Repository layout
package.json cordis.patch.yml LICENSE .gitignore # the package itself
src/ lib/ tests/ # sources, built artifacts, suites
tools/ install.mjs install.ps1 # build, verification, local install
docs/development.md # debugging notes and pitfalls
PR.md docs/upstream-issue-*.md upstream/ # material for an upstream PR; unrelated to installing
Comments
Loading…
From the same category
by ranxianglei
基本稳定可用 100K tokens is enough. Universal context-compression proxy for ALL AI coding agents,10w上下文足矣
★ 814
↓ 423.4k/wk
MIT
TypeScript
Oct 11, 2026
dsh plugin --profile web add billion-contextby Han-1413141
DeepSeek Harness session cost meter plugin: session/daily cost, budget, history, OpenCode Go quota, official & custom-provider balance, Codex-like token heatmap, peak/off-peak pricing with pre-switch
★ 388
↓ 23k/wk
MIT
JavaScript
Oct 10, 2026
dsh plugin --profile web add dsh-cost-meterby Nwflower
Import 14+ external agent chat histories (Claude Code, Codex, ChatGPT, Cursor, Gemini, Reasonix, opencode, ZCode, Grok Build, OpenClaw, Pi, Hermes, Kimi CLI, DSH) into DeepSeek Harness as resumable se
★ 224
MIT
JavaScript
Oct 7, 2026
dsh plugin --profile web add dsh-chat-importby Totoro-qaq
DeepSeek Harness plugin for previewable cross-preset session migration. Fixed-schema handoffs preserve state, source-model intent, and unresolved images; the original session stays untouched.
★ 165
MIT
JavaScript
Oct 11, 2026
dsh plugin --profile web add dsh-plugin-bridgeby Anionex
deepseek harness对话和代码状态回退插件 | DSH — rewind conversation and workspace state, powered by a persistent Change Ledger
★ 131
BSD-3-Clause
JavaScript
Oct 1, 2026
dsh plugin --profile web add @anionex/dsh-turn-rewindby SiriLee
DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspac
★ 123
↓ 5.5k/wk
MIT
TypeScript
Oct 9, 2026
dsh plugin --profile web add dsh-rewind-plugin