- Home
- Plugins
- Sessions & Messages
- dsh-delete-chat
dsh-delete-chat
fufu1437/dsh-delete-chat
Permanently delete a DeepSeek Harness conversation and every local copy of its data: session log, derived caches, workspace index entries, spill files, and unreferenced attachments.
Install
dsh plugin --profile web add github:fufu1437/dsh-delete-chatREADME
Conversation Deletion (@fufu1437/dsh-delete-chat)
A DeepSeek Harness (DSH) plugin that permanently deletes one conversation together with every local copy of its data.
Every Session row in the sidebar gains a "Delete conversation…" entry in its
… menu. It opens the confirmation at once — no preview request, no artifact
inventory, no byte totals — and confirming runs the deletion in the
background: the dialog closes immediately and the sidebar entry disappears
when the Host broadcasts the removal.
中文文档:README.zh.md
What gets deleted
A conversation leaves more on disk than most people expect. This plugin clears each of these:
| # | Artifact | Location | Notes |
|---|---|---|---|
| 1 | Session log directory | ~/.dsh/sessions/<project-key>/<session-id>/ | Every on-disk format generation (session.v3.jsonl.zstd, session.v4.jsonl.zstd, …) plus the write lease session.lock |
| 2 | Projection cache record | ~/.dsh/storages/session_projcache/sessions/<id>.json | The cached title and first-prompt text, including .json.bak.* copies |
| 3 | Legacy feedback sidecar | ~/.dsh/storages/message_feedback.json | A pre-release format the current service no longer reads; it can hold user-written notes |
| 4 | Workspace index | ~/.dsh/storages/workspace.json | Account order, archive set, and pin set, written through the workspace registry's own API |
| 5 | Spilled tool output | <spill root>/session-<first 12 hex of sha256(id)>/ | Every local spill root, including $TMPDIR/dsh-spill-* |
| 6 | Attachments | ~/.dsh/attachments/v1/{objects,files,file-objects,request-images} | Removed only after proving no surviving session references them |
| 7 | Subagent sessions | As 1–5 | Every durable subagent session the conversation spawned, recursively |
| 8 | DeepSeek upload cache | ~/.dsh/llm-deepseek/files-v3.json | Records of deleted attachments plus the request-image derivatives their variantIds name |
Afterwards the plugin broadcasts api-session/removed to every connected
client, so the sidebar row disappears immediately instead of waiting for the
next list refresh.
Safety model
Deletion is irreversible, so the implementation follows two hard rules.
1. A conversation that is generating is left alone; one that is merely open
is overwritten before it is unlinked. The Host keeps an Agent and an open log
write handle for every conversation opened in this process, and DSH exposes no
public "release this session" API, so a plain rm under a live writer would
leave the bytes readable in the orphaned inode — a fake deletion. Therefore:
- the conversation is generating a response (a turn is in flight) → refused with
409 session-running; stop it first; - it is open in this process but idle → refused once with
409 session-live(carryingforceable: true), and the UI asks for a second confirmation; on confirmation the files are overwritten with zeros and only then unlinked, so the bytes are really gone even though the writer still holds a descriptor; - one of its subagent sessions is still live →
409 descendant-live, same forced confirmation.
The cost: such a conversation leaves the session list only after the Harness restarts, and if it is still open in a tab and you keep sending messages it may create a fresh (empty) log. The second confirmation says exactly that.
2. Nothing is deleted unless its absence is proven safe. Attachment objects are content-addressed and may be shared between sessions, so an id is dropped only after every surviving session's log has been scanned and none of them mentions it. The scan streams and decompresses without materializing events and is bounded by a session ceiling and a decompressed-byte budget; when either is exceeded, or a log cannot be read, the bytes are kept and a warning is reported.
Other engineering constraints:
- Session ids are escaped with an
encodeSegmentbyte-for-byte identical to the JSONL backend's before touching a path, so../, absolute paths, and NUL cannot escape the roots; - The package imports only
node:builtins and no private@deepseek-ai/*package, so it publishes as an ordinary npm package; - Both HTTP routes pass the composition's
connection.requestRejectiontrust fence first (Host/Origin checks plus the browser session cookie); unauthenticated requests get 401/403.
Install
pnpm is assumed (DSH profiles are pnpm-managed).
From npm, once published:
dsh plugin install @fufu1437/dsh-delete-chat
From a local checkout:
pnpm add @fufu1437/dsh-delete-chat # or as a local link dependency
dsh plugin install /absolute/path/to/dsh-delete-chat
The in-Harness plugin manager works too: call install_bundle with the package
directory, a .tgz, or the npm package name.
The Host half requires a Harness restart to take effect after an update: in
this profile the hmr row is configured with root: [] (no plugin module
roots watched), so the process caches the loaded module generation. The Client
half takes effect as the page loads.
Usage
- Hover a conversation row in the left sidebar and open its
…menu; - choose "Delete conversation…";
- the dialog shows the title, the irreversibility warning, and Cancel / Delete permanently — nothing is measured or listed first;
- press "Delete permanently": the dialog closes, the deletion continues in the background, and the row disappears when the Host broadcasts the removal;
- if the conversation is still open in this Harness process, a second confirmation appears ("Delete anyway") explaining that the files are overwritten in place and that it leaves the list only after a restart;
- any other failure (a response currently generating) raises a dismissible alert card in the corner with the reason.
Configuration
Override in the profile's cordis.patch.yml:
- id: fufu-delete-chat
name: '@fufu1437/dsh-delete-chat'
config:
dshHome: /home/me/.dsh
deleteAttachments: true
deleteDescendants: true
scanLimit: 500
scanByteLimit: 2147483648
| Field | Default | Meaning |
|---|---|---|
dshHome | $DSH_HOME → ~/.dsh | Harness home; every other root derives from it |
sessionsRoot | <dshHome>/sessions | Session log root |
storagesRoot | <dshHome>/storages | storage-json root |
attachmentsRoot | <dshHome>/attachments/v1 | Attachment root |
llmFilesRoot | <dshHome>/llm-deepseek | DeepSeek upload-cache root |
spillRoots | discovered $TMPDIR/dsh-spill-* | Spill roots to sweep |
deleteAttachments | true | Run the attachment proof and erase unreferenced uploads |
deleteDescendants | true | Also delete durable subagent sessions |
scanLimit | 500 | Maximum session logs the attachment proof reads |
scanByteLimit | 2 GiB | Maximum decompressed bytes the proof reads |
Verification
pnpm test # fixture self-test: 68 assertions, touches no real data
pnpm run check # syntax-check both halves
scripts/selftest.mjs builds a complete throwaway harness home (session logs,
projection cache, workspace index, legacy feedback sidecar, spill files,
attachments, upload cache), drives the plan/execute pair with a scripted Host
context, and asserts what survived:
- every artifact class is erased while another session's data stays intact;
- live, running, and live-descendant conversations are refused;
- an attachment referenced by another session is kept; an exclusively referenced one is erased;
- a truncated scan or an undecompressable log keeps the bytes and warns;
- a hostile session id cannot escape the roots;
- the in-place overwrite really happens: the test keeps an open descriptor on the log (standing in for the live writer) and reads zeros through it after the deletion, proving the content was erased rather than merely unlinked.
.tmp/e2e.mjs is an end-to-end script against the running Host (never
published): it mints the same browser-session cookie the page uses, creates a
synthetic session in the real DSH home (log + projection cache + spill +
attachment), calls delete through the real trust fence, asserts the artifacts
are gone, and verifies that a live conversation is refused with 409 and that
the removed /inspect route is gone.
It needs write access and cleans up after itself.
.tmp/proof-scan.mjs runs the full attachment proof read-only over the real
corpus (187 session logs on this machine complete in about 1.5 s).
Known limitations
- A conversation that is generating a response cannot be deleted until it stops; that is the one hard refusal, and it is deliberate.
- A conversation that was merely opened can be deleted, but its live session object survives until the Harness restarts (DSH has no public session-release API): the bytes are overwritten and unlinked, yet the session can still appear in the list or create a fresh empty log if you keep sending messages in its open tab.
- Telemetry already exported cannot be recalled:
session-telemetry-otelmay already have shipped a log prefix to a remote collector inFEEDBACK_ONLYmode; this plugin can only erase local data. - Derived-cache boundary: removing a request-image cache entry requires the
matching record in
files-v3.json; other derived copies that leave no attachment reference in the log are out of scope. - The legacy
message_feedback.jsonsidecar is not read by the current service; this plugin reads and rewrites it directly. workspace.jsonis edited through the workspace registry API when that service exists, and directly only when it does not (so nothing can overwrite the change from memory).- The default local spill root is a temp directory; if a Harness once used a
different
rootthat has since been cleaned up, there is nothing left to delete.
Publishing
pnpm run check && pnpm test
pnpm publish --access public # scoped packages need public access
prepublishOnly runs the syntax check and the self-test first.
License
MIT
Related plugins
dsh-web-ui (dsh-chat-recovery)
zhu1090093659/dsh-web-ui
billion-context
ranxianglei/billion-context
dsh-synapse
liangmianya/dsh-synapse
dsh-chat-import
nwflower/dsh-chat-import