Geralt4 / dsh-plugin-session-delete

Listed

DSH plugin: permanently delete a session's on-disk log and attachments from the Web UI (header button + confirm dialog).

mainToolSession View source

Installation

npx -y @deepseek-ai/dsh plugin --profile web add github:Geralt4/dsh-plugin-session-delete

This installation command is an unverified starting point generated from the GitHub repository address.

README

Maintainer-authored documentation snapshot.

View on GitHub ↗
Commit e1ddcceSynced Aug 18, 2026

dsh-plugin-session-delete

A DeepSeek Harness plugin that adds a real, permanent delete for sessions (chats) to the Web UI — a "Delete session" button in the conversation header plus a confirm dialog. On confirm it removes the session's on-disk conversation log and attachments and hides the row from the sidebar, without a page reload.

Today the stock UI can only archive a session (hide it; the log stays on disk forever). Core has no deleteSession RPC, so this plugin adds a small host-side HTTP route that performs the durable deletion, plus a client-side header action that calls it.

⚠️ Irreversible. Deleted sessions cannot be restored. Stop any running agent on a session before deleting it (the route also best-effort cancels the agent). Works in the Web profile and the desktop client.

Install

Into a dsh web profile (the dsh plugin command forwards to pnpm in the profile directory), then add the Loader entry row and restart the profile:

# from npm
dsh plugin --profile web add dsh-plugin-session-delete

# or directly from GitHub (lib/ is committed, so no build needed)
dsh plugin --profile web add github:Geralt4/dsh-plugin-session-delete

Then append the plugin's Loader entry to the profile's cordis.patch.yml ($DSH_HOME/profiles/web/cordis.patch.yml) and restart dsh web:

- insert:
    - id: session-delete
      name: dsh-plugin-session-delete

After a page refresh, a Delete session button appears in the conversation header.

How it works

┌──────────────── browser ────────────────┐    ┌──────── host (Node) ────────┐
│ conversation.session.header.actions slot│    │ webServer.register(exact)   │
│   DeleteSessionHeaderAction (button)    │──> │   POST /api/session-delete  │
│   DeleteSessionDialog (confirm modal)   │    │     { sessionId }           │
│        │ confirm()                       │    │        │                    │
│        │ fetch POST /api/session-delete   │    │        ▼                    │
│        ▼                                 │    │ cancel agent (best-effort)  │
│   on ok: ctx.workspaces.archiveSession() │    │ sessionPersistence.inspect  │
│   (row disappears from sidebar now)     │    │   → absolute log path       │
└─────────────────────────────────────────┘    │ fs.rm(dir, recursive)        │
                                               │   (log + attachments)        │
                                               │ SQLite FTS rows auto-drop on │
                                               │ next persistence observation │
                                               └─────────────────────────────┘

Why a plugin and not a core PR

DeepSeek Harness core has no session.delete / workspace.deleteSession RPC (only workspace.archiveSession, which hides, and session.cancel, which stops). The static UNARY_ROUTES table in dsh-host-apiproxy cannot be extended by a plugin, so a plugin cannot add a routed RPC. It can, however, register its own HTTP route via the webServer service (dsh-host-webserver) and reach the same on-disk result through the public sessionPersistence.inspect(id).path. This plugin does exactly that.

This is a best-effort approximation of what really belongs in core (the persistence coordinator owns live-session retirement and exact path encoding). If maintainers ever add a first-class deleteSession, this plugin can be reduced to a thin UI wrapper around it.

Project layout

dsh-plugin-session-delete/
  package.json          # dsh.client manifest + peerDeps + tsdown scripts
  tsdown.config.ts      # two-entry build (host + client)
  tsconfig.json
  src/
    index.ts            # host half: webServer route + fs.rm
    client.ts           # browser half: header action + confirm dialog
  lib/                  # build output (index.js + client.js)

Build

pnpm install
pnpm bundle        # tsdown -> lib/index.js, lib/client.js (+ .d.ts)

Client bundle format note

The browser loads client halves via window.__ModuleLoader__.load({ id, factory }) (see @deepseek-ai/dsh-client-modules). In the DSH monorepo a shared tsdown plugin wraps the client entry into that form; the host entry is plain ESM and builds unchanged.

  • In repo (recommended for first-time development): drop this package under packages/ (e.g. packages/plugin/session-delete) and copy packages/session-query/session-log-export/tsdown.config.ts so the client output is wrapped. Add the package to the web profile's bundle list.
  • Out of tree: either copy that in-repo tsdown config, or author src/client.ts directly in the factory form so a plain externalised build is loadable (see Out-of-tree build below). The peer deps in tsdown.config.ts must stay external so the loader's require resolves them at runtime.

Install into a profile

Out-of-tree plugins are added to a profile with the dsh plugin command, which forwards to pnpm in the profile directory:

# from a checkout of this plugin, or an npm tarball
dsh plugin --profile web add <path-or-tarball-of-this-package>
dsh plugin --profile web install

Then rebuild the profile's frontend artifacts and refresh the running URL (the shell is not a standalone app; dsh web injects window.__DSH_BOOT__).

To develop against the running GUI at http://127.0.0.1:3080: the client half reloads without a refresh only while pnpm run dev:web is also running from the same checkout to rebuild client bundles. The host half and the shell require a rebuild + refresh.

Run / verify

  1. Start the web profile: dsh web (or pnpm dsh web from a source checkout).
  2. Open a session. A "Delete session" button appears in the conversation header.
  3. Click it → confirm dialog → "Delete permanently".
  4. The session's on-disk directory under the persistence root is removed and the row disappears from the sidebar.

The on-disk session log lives at <persistenceRoot>/<projectKey(cwd)>/<encodeSegment(id)>/session.jsonl[.zstd] (+ co-located attachments). This plugin never recomputes that path: it reads the absolute path from sessionPersistence.inspect(id) and removes that artifact's directory.

Out-of-tree build (client factory form)

If you cannot use the in-repo tsdown client plugin, replace src/client.ts with the factory-form equivalent so a plain externalised ESM build is loadable. The shape is (mirroring the published @deepseek-ai/dsh-session-log-export/lib/client.js):

window.__ModuleLoader__.load({
  id: "dsh-plugin-session-delete",
  factory: (require) => {
    const { jsx, jsxs, Fragment } = require("react/jsx-runtime");
    const { createSnapshotStore } = require("@deepseek-ai/dsh-client-runtime/client");
    const { Modal, Button, IconTrashOutline16 } = require("@deepseek-ai/dsh-client-ui-primitives");
    // …the controller, dialog, header action, and apply/inject from src/client.ts…
    return { apply, inject };
  },
});

Keep react, react/jsx-runtime, and @deepseek-ai/* external in tsdown.config.ts so these require(...) calls are resolved by the loader at materialisation, not bundled.

Limitations

  • Live sessions: if an agent is actively writing when you delete, the persistence coordinator may recreate the log after this plugin removes it. The route best-effort cancels the agent first; the dialog warns the user.
  • Descendants: this v1 deletes one session's directory only. Subagent children are not cascaded. (Inspect session.export's includeDescendants pattern if you want a cascade follow-up.)
  • Workspace accounting: the row is hidden via workspace.archiveSession, which keeps the id in its workspace sessionIds slot. Stale accounting is reconciled by core on the next persistence observation. True removal from accounting belongs in core.
  • SQLite FTS index: dropped automatically by core's reconciliation once the log file is gone; this plugin does not touch the index directly.

License

MIT

Repository information

Language
TypeScript
License
MIT
Last updated
Aug 17, 2026, 8:58 PM

Install deliberately

Review source code, permissions, lifecycle hooks, dependencies and network access. Test untrusted plugins in an isolated environment.