viplocco/dsh-delete-message

dsh-delete-message

DeepSeek Harness 消息级删除插件,用于避免用户误发或错误的助手消息污染会话上下文。

Uncategorizeddeepseek-harnessdshdsh-plugin-desktop

dsh-delete-message

简体中文 | English

A per-message delete plugin for DeepSeek Harness that keeps accidental or mistaken messages out of the model context. It adds a delete button to the action area of every chat message: once confirmed, the message is removed from the model context through the host's official surface-replace contract (an assistant reply takes its thinking, tool calls, and injected context with it) and hidden from the visible transcript. Raw log bytes are never rewritten and can be recovered at any time.

Features

  • All message types — assistant replies mount in the official conversation.chat.assistant-actions slot; user input, machine-injected context rows, tool-call cards, THINK cards, model-retry lines, and terminal turn-error banners are enhanced via DOM augmentation (the host has no extension point for these sides). Chrome-row trash buttons (anything outside the user/assistant action strips) stay hidden by default and appear on message hover.
  • Native visual consistency — reuses the host's icon and button geometry, the primitives Tooltip, and the Modal+Button confirm dialog; adapts to light and dark themes automatically.
  • Context-level deletion — after confirmation, a surfaceOp: { op: 'replace' } placeholder node is appended (the same mechanism as the host's /compact); shadowed messages no longer enter deriveMessages().
  • Transcript-level hiding — the host's visible transcript is append-only by design, so the plugin maintains a per-session deleted-seq ledger (persisted in localStorage), resolves row wrappers' React fiber identities to hide matching rows, and heals historically deleted rows unknown to this browser via a /status preflight at load time.
  • Failed-turn cleanup — turns that failed or were interrupted before any assistant reply landed (e.g. a 502 retry exhaustion) leave no assistant message, so the delete button had nowhere to mount and the injected context was stranded forever. Clicking the trash on any context row, tool-call card, or retry line replaces the whole user-input unit at once (injections, the reply, tool calls, and retry/error chrome go together; real user input is never touched).
  • Step-level deletion — in multi-step turns, clicking the trash on a THINK card or tool-call card removes only that step's reply and tool calls (assistant/message + paired tool/result); other steps in the same window survive. The confirm title and tooltip explicitly state the scope ("Delete this step" vs "Delete this entire attempt").
  • Role-aware confirmation — the confirm dialog body is selected automatically from each mount's static message role; no user judgment required: a user message states its single-message scope, while an assistant reply states that its thinking, tool calls, and injected context are removed together with it.
  • Preflight verdicts & graying — trash targets are checked through a TTL-cached /status preflight on hover/click: refused targets gray out and state the localized reason instead of firing a doomed request; already-deleted rows heal silently; an unreachable host leaves icons neutral and clickable (the server always has final say).
  • Delete transition feedback — after confirming, the dialog enters a "Deleting…" pending state (spinner, with duplicate clicks and cancellation suppressed); failures show inline and can be retried in place; success collapses the affected rows with a graceful staggered leave animation (honors the system reduced-motion setting).
  • Bilingual UI (zh/en) — all UI copy (confirm dialogs, failure reasons, accessibility labels) ships with Chinese and English dictionaries, following the Language choice in system settings live; falls back to the browser languages when unset.

Behavior Reference

Clicking the trash in different locations produces different deletion scopes, dialog titles, and tooltips:

Click targetDeletion scopeDialog titleTooltip
THINK card 🗑️This step onlyDelete this step?Delete this step
Tool-call card 🗑️This step only (owning assistant/msg + tool/results)Delete this step?Delete this step
Context-injection row 🗑️Entire windowDelete entire attempt?Delete entire attempt
Retry line / error banner 🗑️Entire windowDelete entire attempt?Delete entire attempt
User message 🗑️That message onlyDelete this message?Delete
Assistant slot (beside copy) 🗑️Entire window (reply + thinking + tools + injected context)Delete this message?Delete

Step-level deletion applies to multi-step turns: only the target step's assistant/message and paired tool/result are removed; other steps in the same window survive. Whole-window cleanup is for one-shot sweeps of failed or interrupted turns.

Screenshots

Delete button in the message action area

Delete confirmation dialog

Deleted message disappears from the session

Screenshot files live in docs/screenshots/; see that directory's README for what each image should show.

Safety

  • The server re-runs full validation before every deletion: only user/message and assistant/message surface nodes inside closed turns are accepted — real user input is a single-node replace, while assistant messages, machine-injected rows, and non-surface chrome anchors such as tool/call or llm/retry plan the whole user-input window as one unit; messages carrying tool calls cannot be deleted alone, already-shadowed messages and in-progress turns are refused with machine reason codes (localized in the UI).
  • The HTTP write path requires a dual-condition fence: loopback peer address AND a local Host header.
  • Zero configuration, zero runtime dependencies; the plugin never deletes files or rewrites logs.

Installation (web profile)

dsh plugin --profile web add github:viplocco/dsh-delete-message#v0.2.3

After installing, fully restart the DSH Web process (the host-side plugin tree is read only at startup); the client bundle is served dynamically per request by the host, so updates take effect on a hard refresh.

Installation (desktop profile)

The DSH Desktop shell renders the same web frontend (the @deepseek-ai/dsh-web-app composition + the same WebServer origin), so this plugin is natively desktop-compatible — the only requirement is installing it into the desktop-owned profile and restarting DSH Desktop:

# The desktop app uses a separate desktop profile; install it there too
dsh plugin --profile desktop add github:viplocco/dsh-delete-message#v0.2.3
# or from a local checkout: dsh plugin --profile desktop add link:<path>

After installing, fully restart DSH Desktop and hard-refresh the renderer. To confirm it loaded:

dsh --profile desktop --dump-config   # should show a "# == dsh-delete-message" section
# in the renderer devtools console: "[delete-message] bundle script executing"

Why "web worked but desktop didn't": the plugin's host half used to read session.events, which the current harness (dsh-session ≥ 0.1.2-rc.1, bundled by DSH Desktop) renamed to session.snapshotEvents(), so /status threw Cannot read properties of undefined (reading 'find') on desktop. v0.2.2 routes event reads through eventsOf(), which prefers snapshotEvents(), tolerates the legacy .events array, and returns not-found instead of crashing.

Development

pnpm test    # node --test

See docs/DESIGN.md for architecture, host contracts, and design trade-offs. Contact: viplocco@qq.com

License

MIT