DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Winnie-0721 /

Winnie-0721/dsh-dialogue-cleanup

Verified

DeepSeek Harness plugin: delete sessions from the running Web runtime — live store, workspace records, and on-disk artifacts. Host half + web client half in one package.

★ 2 Stars1 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@6b903be8

dsh-dialogue-cleanup

English · 简体中文

npm version npm downloads GitHub release GitHub stars License: MIT DeepSeek Harness 0.2.0-rc.2

Delete DeepSeek Harness sessions from the running Web GUI — without restarting, without leaving the window.

One row in the session ... menu for a single delete, plus a bulk management page inside Settings.

Bulk management panel in Settings: search, filters, per-row status badges and 16 session rows

Install · Gallery · How it works · Releasing · Releases

Why it exists

One-line positioning: deleting a session should actually delete it. DeepSeek Harness keeps a session in four places at once — the live store, the workspace records, the registry leftovers (pin / archive), and the files on disk. Removing one of them leaves a ghost row, a stale workspace, or a directory that silently keeps occupying disk. Everything below is that positioning made concrete:

  • One irreversible operation, implemented once. deleteSession lives in the Host (lib/service.js) and is reached through two thin adapters — the HTTP contract and the slot UI — so the single-delete path and the bulk path cannot drift apart.
  • A running session is refused, not guessed at. The Host decides deletable and the UI simply does not offer what the Host would reject. The panel never decides by itself whether something is safe to delete.
  • Archived sessions are still shown by default. Archiving is not deleting: an archived session still occupies disk, so hiding it by default would hide exactly the files you came to clean up. (The official sidebar hides them; here it is a tri-state filter — hide archived / all / archived only — and archived are shown by default.)
  • Subagent rows are labelled. The official sidebar hides origin: 'subagent' rows entirely, so whenever those rows do appear (kind = subagent / all) each one is explicitly badged — a subagent session must never look like an ordinary conversation.
  • It says what it actually did. The result line reports ok / failed / degraded / directories removed, and each failure keeps its own reason verbatim. When the live store entry cannot be detached, the plugin reports detached: false rather than claiming success.
  • Errors are semantically truthful. Unlike the upstream package (which always answered 200), this one uses real status codes: 400 / 403 / 404 / 405 / 409 / 429 / 500.

UI at a glance

The confirmation overlay: irreversible, and it says so. Two closing paths only — an explicit Cancel button, or Esc. Clicking the scrim deliberately does not dismiss it, because a mis-click on an irreversible delete should not cost you your chance to confirm:

Delete confirmation: permanent deletion warning, session id, and Cancel / 永久删除 buttons

The overlay is a native <dialog> driven by showModal(), so it enters the browser's top layer and therefore stacks above the Settings panel it was opened from (a plain positioned element could not). That also brings a real focus trap and correct Esc semantics for free — and it needs no portal outside the component tree.

The bulk panel (inside Settings) — search, project / state / session-kind / archive filters, sort, paging, and per-row status badges. The badges in the screenshot above come from real render output: 空闲 (idle) / 运行中 (running, not selectable) / 子代理 (subagent) / 已归档 (archived).

The two screenshots are generated from the actual component tree and the actual CSS by scripts/render-shot.mjs (the same React stand-in the render tests use), rasterised with headless Edge. They are not hand-drawn mockups; they are also not a photograph of a live GUI — see Honest limitations.

Quick Start

dsh plugin --profile web add dsh-dialogue-cleanup
  1. Single delete — hover a session row in the sidebar, open the ... menu and pick Delete session (registered at order: 500, right after the shipped Archive entry at 400). A themed confirmation appears; confirming removes the live entry, the workspace records, the registry leftovers and the on-disk artifacts.
  2. Bulk delete — open Settings → Dialogue Cleanup. Tick rows (or Select all deletable), then delete them in one request.
  3. Host-side changes need a restart of the DSH process; client-side changes do not (see the table in docs/RELEASING.md).

Compatible with DSH 0.2.0-rc.2. The npm package and the GitHub Release are published together; versioning rules and the release chain live in docs/RELEASING.md.

How it works

One npm package carries both halves, installed into the host per the official plugin spec:

  • Host half (repo process) — lib/index.js is a Cordis function plugin exporting only name / inject / apply. It registers a single ctx.webServer prefix route and keeps the entire deletion implementation in lib/service.js. Filesystem removal and path containment live in lib/artifacts.js; the audit trail in lib/audit.js.
  • Client half (browser) — client.js, a single-file bundle registered through window.__ModuleLoader__.load({ id, factory }), with zero dependencies beyond require("react").
  • Both entry points are official slots; neither touches the DOM:
    • sidebar.workspaces.session.menu.item at order: 500 → the single-delete row.
    • settings.section (id: dialogue-cleanup, order: 25, after the shipped account(-10) / general(0) / models(10) / plugins(15) / agent-presets(20)) → entry and panel. One registration is one Settings page, whose content renders in the panel's content column.
  • Because the panel lives in Settings it needs no layout service and does not compete for space in main or the sidebar — one fewer optional dependency and one fewer degradation branch.
  • The stylesheet travels with the component. The menu item renders its own <style> beside itself (finding H1 of the project's own audit: the row was rendering unstyled because the stylesheet was only injected on the settings path).

The deletion sequence

  1. Refuse a running session — 409 refused.
  2. Detach the live store entry — otherwise the deleted row survives as a ghost.
  3. Drop workspace records — counting only workspaces that actually detached.
  4. Clear registry leftovers — pin / archive records, via the public idempotent API.
  5. Remove on-disk artifacts — <DSH_HOME>/sessions/<project>/<sessionId>, plus the derived projection cache.
  6. Write an audit line — <DSH_HOME>/logs/dialogue-cleanup.jsonl.

HTTP contract

Method Path Purpose
POST /api-ext/dialogue-cleanup/delete { "sessionId": "..." } → delete one; { "sessionIds": [...] } → delete many (the Host runs the same deleteSession per id and returns a per-id result)
POST/GET /api-ext/dialogue-cleanup/list session rows with deletable / blockedReason
GET /api-ext/dialogue-cleanup/status capabilities, config, runtime paths

Envelope: { ok: true, value } / { ok: false, error: { code, message, hint? } }.

Configuration

All tunables live in the plugin Config (lib/config.js), so a user's patch layer survives upgrades. A matching override replaces the whole config — write every field you care about.

Key Default Meaning
maxBodyBytes 65536 request-body ceiling (upstream: 1 MiB)
readTimeoutMs 10000 body-read timeout (upstream: none)
audit / auditPath true / null audit log; null → <DSH_HOME>/logs/...
rateLimitPerMinute 60 per-minute delete request budget (a batch counts once); 0 disables
requireIdleSession true safety valve for the running-session refusal
stopIdleAgent false see the honest-degradation note below
matchAlternateIdSpelling true probe the other session- spelling, guarded
removeProjectionCache true also drop the derived projection cache
clearRegistryRecords true unpin / unarchive leftovers
confirmInUi true UI confirmation (the Host does not trust it)
titleCacheTtlMs 300000 title-fold cache TTL; 0 disables
titleFetchConcurrency 8 shard concurrency when folding titles
titleLimit 100 max rows per page to fold titles for; 0 skips title reading
runningProbeTtlMs 1000 how long a running-state snapshot is reused. Turns batch delete from O(N) full-corpus list calls into O(1); 0 re-probes every item

Honest limitations

These are measured against the live runtime and differ from what the upstream package assumes. They are reported truthfully rather than papered over:

  • No public stop for an idle agent. The public Agent type is { readonly id: SessionId } — no status, no ctx. Upstream's agent.status === "running" and agent.ctx.fiber._unload() therefore cannot be relied on (_unload is a Cordis private method and is deliberately never called). "Is it running?" comes from the public sessionController.list() → SessionSummary.running. A session that still has an attached agent and cannot be stopped safely is refused, and the UI shows why.
  • Detaching the live entry is best-effort. The public sessions service exposes create/prepare/enter/announce/flush/get/list/fork — no public detach. The plugin tries the public method first, then the internal store entry, and reports detached: false when neither is available instead of claiming success.
  • Two on-disk id spellings coexist. Observed in one <DSH_HOME>: ...\<uuid>\session.v4.jsonl.zstd and ...\session-<uuid>\session.v4.jsonl.zstd. An exact-id join can never delete the prefixed half. This plugin probes the alternate spelling only when it can confirm the candidate is not a different live session; with no such evidence it deletes nothing rather than risk a neighbour.
  • The screenshots are real render output, not a live-GUI photograph. They come from the actual component tree + actual CSS, rasterised headless; the theme tokens are pinned to a light theme and the row data is a demo fixture. What the panel looks like inside a running GUI with the real theme has not been captured here.

Development and acceptance

# Full gate: node --check + migration/contract/render/release regression suites
node --test "test/**/*.test.mjs"

# Release gate only (no version change, no packing, no publishing)
pwsh -File scripts/release.ps1 -LocalOnly

# Regenerate the two documentation screenshots from the real component tree
node scripts/render-shot.mjs

The suites are currently 161 tests across 9 files:

Suite Covers
contract.test.mjs the host ↔ client contract and slot declarations (largest suite)
service.test.mjs deleteSession end-to-end behaviour and refusals
client.test.mjs registration surface, ids, no-cross-package requires
render.test.mjs components actually rendered through a React stand-in
artifacts.test.mjs path containment, symlink refusal, alternate-spelling guards
session-id.test.mjs id validation (no separators or dots)
eol.test.mjs line endings, and that every tracked file is git-declared eol=lf
release-workflow.test.mjs the OIDC publishing structure (reverse assertion: pack-release.yml must not contain its own publish steps)
ps-script.test.mjs PowerShell 5.1 BOM / encoding pitfalls

Two of these exist because the corresponding bug actually shipped and was caught afterwards:

  • eol.test.mjs — the first release produced different bytes from CI (pnpm pack) and from local (npm pack): icon.svg and LICENSE were CRLF in CI, because .gitattributes declared only * text=auto and the GitHub Windows runner defaults to core.autocrlf=true. The first version of this test missed it (it scanned a hardcoded extension list, so *.svg and the extension-less LICENSE were invisible); it now sweeps every git-tracked file and reads the effective attribute via git check-attr. Fixing it also caught .gitignore.
  • release-workflow.test.mjs — the OIDC trust is bound to a workflow filename, and writing the publish steps a second time is exactly the bug that broke that other repository's releases twice. See docs/RELEASING.md.

Repository layout

Path Contents
lib/index.js apply(ctx, config) — assembles and registers the host routes
lib/service.js deleteSession — the single deletion implementation
lib/artifacts.js disk removal + path containment
lib/http.js same-origin checks, body limits, status codes
lib/config.js Config schema + defaults
lib/audit.js JSONL audit trail
client.js the web client half: slot registrations (menu row + confirm overlay + settings section)
cordis.patch.yml inserts the host half, inject: [webServer]
.github/workflows/ pack-release.yml (tag → gate → pack → GitHub Release) and publish-npm.yml (the only npm-publish implementation, reused via workflow_call)
docs/RELEASING.md version rules and the release chain
scripts/release.ps1 version bump + gate (+ CI pack/release)
scripts/render-shot.mjs regenerates the documentation screenshots
test/ the 161-test regression suite

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 6b903be83c33

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout