dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Delete a conversation from DeepSeek Harness — DSH ships archive only.
DSH can only archive a conversation. Archiving hides the row from the sidebar and keeps every
artifact: the log, the workspace account, the projection checkpoint all stay on disk, and the
conversation can be restored at any time. The persistence seam has no deletion API at all —
@deepseek-ai/dsh-session-persistence-jsonl states it plainly:
Nothing deletes session files — logs accumulate under
rootuntil removed externally; the seam has no deletion API.
So a long-lived profile accumulates conversations forever. This plugin adds the missing step: a
Delete conversation row in each session's ⋯ menu that really removes the data.
For the chosen session, the Host half:
ctx.sessionPersistence.locate(header) gives the
path);SessionHeader.parentSession), and writes
that field for every relation it creates: the Subagent runtime sets it with origin: 'subagent',
a fork sets it with isSeeded, and Agent Teams resolves its roster through the same field. The
plugin walks that lineage breadth-first and unions it with the durable subagentCatalog
projection (ctx.subagents.listDescendants), so a child whose own header no longer reads is still
named. Descendants are deleted deepest first, gathered before anything is removed, and only
the ticked ones go — up to 200 per request;sessionIds
(Workspace.detachSession) and the registry-global archive and pin sets;session_projcache domain record and its <id>.json
file;ctx.terminals are part of no admission family, and the
service only reaps them when the Agent is released, which can be long after the log is gone;api-session/removed per removed id, the same event the shipped
Session controller emits when a Session is disposed, so the rows leave the sidebar immediately.The dialog lists the session's whole family rather than assuming all of it:
Confirming posts exactly the ticked ids and the button states how many conversations will go. Unticked descendants survive as conversation roots. The selection is validated against the Host's own walk, so a request can only ever name Sessions in that lineage. A fork is a conversation in its own right, which is exactly why it is listed with a checkbox instead of being taken silently.
Running work blocks it — being open does not. The gate is DSH's own archive admission, the
workspace/session-activity waterfall: the Agent registry reports a running turn, the job registry
reports background jobs, the Subagent runtime reports running descendants, Schedule reports active
reminders. The question is asked once per Session in the delete set, because the admission answers
per Session: a descendant's running turn or background job is not reported when only the target is
asked. The dialog names what is still running — on each row and in the summary — and offers
Stop and delete, which dispatches workspace/session-stop for every busy Session first, exactly
what archiveSession(id, { stopActivity: true }) does.
A session the Host still holds open (ctx.sessions / ctx.agents) is deleted anyway. Measured on
Windows: rm succeeds while the append handle is open, the directory entry disappears at once, and
later appends land in the unlinked file instead of resurrecting it. An earlier version refused such
sessions with "switch away first", which wrongly blocked conversations that were not on screen.
~/.dsh/attachments/v1/objects/<hash>, a content-addressed store shared by sessions, and the
service has no reference counting, so those blobs stay.dsh-session-query-sqlite is a derived index that
reconciles against persistence on every search, so a deleted source stops being returned from the
next search on.Everything below goes through DSH's own plugin manager, which accepts an install spec
(@deepseek-ai/dsh-plugin-manager): a registry name, a git host shorthand, a repository URL, a
tarball, or an absolute local path. Pick whichever route your network allows.
Spec:
github:Lzcdebear/dsh-delete-session
or, equivalently:
https://github.com/Lzcdebear/dsh-delete-session
Give it to DSH:
plugin_manager tool take exactly the same spec string.)plugin_manager with
action: "install_bundle" and target: "github:Lzcdebear/dsh-delete-session".Pin a ref with #: github:Lzcdebear/dsh-delete-session#v0.1.0.
If the connection check fails, DSH reports a bounded log path. On a network where github.com is not
reachable, use route 2 or 3 — or point git at your proxy first
(git config --global http.proxy http://127.0.0.1:7890).
Download the repository (ZIP or git clone), unpack it anywhere, then install the absolute
directory:
plugin_manager { action: "install_bundle", target: "D:\\plugins\\dsh-delete-session" }
DSH records a link: dependency and reloads the profile. This is the route used to develop the
plugin, and the one to use behind a restrictive network.
https://github.com/Lzcdebear/dsh-delete-session/archive/refs/heads/main.tar.gz
Same install entry as route 1; useful when git is unavailable but HTTPS is not.
The Host half loads with the profile. The Client half appears after the page is refreshed. The row
shows up in the session ⋯ menu as 删除会话 / Delete conversation.
To remove the plugin again, use the same manager:
plugin_manager { action: "remove_bundle", target: "dsh-session-delete" } (the bundle key is the
package name).
⋯ menu on any session row and pick Delete conversation.The Client half reaches the Host over two same-origin exact routes on ctx.webServer. A
build-free plain-JavaScript bundle cannot declare a typed ctx.remote namespace (that needs
generated Typert descriptors), so this uses the same transport the community plugin dshmarket
uses.
| Route | Method | Purpose |
|---|---|---|
/dsh-session-delete/inspect?sessionId=… | GET | stored / open / agent / running / activity / artifactDirectory, and descendants = { count, subagents, derived, truncated, maxDeletable, items[] } where each item carries id, kind, depth, parentId, title, open, agent, running and its own activity |
/dsh-session-delete/delete | POST | body { sessionId, stop?, descendants? } — descendants omitted means the whole family, an empty array means the session alone; returns removed, descendants (each with its kind), kept, stoppedActivity, terminalsKilled, warnings, runtime, activity. A name outside the family is refused with 400 unknown-descendant before anything is removed |
Both routes carry their own same-origin gate: Host must be loopback, sec-fetch-site must not be
cross-site, and a present Origin must match Host. Another site's page cannot reach them.
| File | Role |
|---|---|
host.js | Host half: the two routes, artifact/accounting/cache removal, subagent subtree |
client.js | Client half: the sidebar.workspaces.session.menu.item row (order 500) and the shell.overlay confirmation dialog |
cordis.patch.yml | Inserts the Host row into the profile's layer stack |
test/host.test.mjs | Route tests over real temporary directories |
icon.svg | Plugin artwork |
Styles use only host theme tokens (--dsw-alias-*), so light and dark both read; the UI is
localized (English / 简体中文) through the Client locale service.
node --test test/host.test.mjs
The tests drive the real apply() registration against fake Host services and real temporary
directories: deletion, the running-work gate and its stop path, the subagent subtree, the inspect
report, and the refusal paths (bad id, bad body, wrong method, cross-site origin).