Bundle
dsh-clear-session-history
Clear session history from disk: per workspace (workspace menu) and across all workspaces (red sidebar button), each behind a confirm dialog
- Source
- n0pe-sled
- Updated
- Updated 9 days ago
Readme
# dsh-clear-session-history
Adds destructive "clear session history" affordances to the dsh web GUI, each
behind an are-you-sure dialog that shows exactly what will be deleted:
1. **Clear session history.** A red row inside a workspace's own "…" menu in
the sidebar (next to Rename / Delete workspace). Deletes every session log
stored on disk for that one workspace, then removes the workspace itself
from the sidebar.
2. **Clear All Session History.** A red trash-can icon beside the sidebar's
New Session control (wide sidebar only; the 56px rail is left alone).
Deletes every session log on disk across all workspaces, then removes every
workspace from the sidebar (a full reset to the blank state).
3. **Delete session.** A red row (trash icon) inside any session's own "…"
menu in the sidebar (next to Rename / Fork / Archive). Deletes that one
session's log from disk. The workspace and its other sessions are
untouched. Works on open (attached) sessions too: the log is removed and
the row leaves the sidebar immediately. The only thing it refuses is a
session whose agent is actively running (a turn in flight), since deleting
that would break the write in progress; the dialog says so and the delete
stays disabled until it finishes.
## What "delete" means
Session logs live under `$DSH_HOME/sessions/<project-key>/<session-id>/`. The
plugin deletes those directories through the host's own session persistence
backend (`locate()` resolves each session's directory, no slug reimplementation),
so what disappears from disk is exactly what the sidebar lists. Effects:
- After a fully successful clear the GUI reloads the page, which closes the
confirm dialog and repulls both lists fresh: deleted sessions and removed
workspaces leave the sidebar in one step. (The harness has no "session
deleted" push event, so a reload is the reliable way for a third-party
plugin to reflect the change; it is the same result as the manual reload
that originally confirmed the delete.)
- Empty project directories are pruned afterwards.
- The cleared workspace is removed from the registration registry
(`$DSH_HOME/storages/workspace.json`), so its row disappears from the
sidebar. A clear-all removes every workspace registration. Any
still-running session that belonged to a removed workspace moves to the
Ungrouped bucket, matching the app's own Delete workspace behavior.
- A partial clear (some logs could not be resolved to safe directories) keeps
the workspace, so the leftover sessions stay grouped instead of being
orphaned; the dialog reports how many were and were not deleted.
- For workspaces that are kept, the registry's stored session ids may go
stale until the next host start. Harmless: the sidebar joins membership
against `session.list` and skips ids without a summary.
The plugin is home-agnostic by construction: it never resolves a path itself
(no `~/.dsh` literal, no home lookup), and every path it touches comes from
host services whose wiring is `$DSH_HOME`-derived (`dshHomePath('sessions')`).
Verified against a harness booted with a custom `DSH_HOME`: previews read that
home's sessions, a clear removed them, and the default home stayed untouched.
### What is deliberately kept
Every action shares one rule:
- The only sessions any clear refuses are those whose agent is **actively
running** (a turn is in flight and the log is being appended), plus cold
subagent logs whose lineage reaches a running session (fixpoint over
`parentSession` chains): deleting a log underneath its writer would leave a
recreated, headerless file. The dialog reports the kept count.
- Attached-but-idle (open) sessions ARE deleted. A host keeps every session
it has touched in memory for the rest of the run, so protecting attachment
would make workspace and clear-all no-ops until a restart. Because the host
still lists such a session from memory (it would linger in the sidebar
after a reload), the clear first archives it — the host's own archive hides
the row and clears the selection if it was the current session — then
removes the log.
- Anything the persistence backend cannot resolve to a well-shaped session
directory (basename must equal the session id, parent must match the
project-key shape). Such entries are counted as unresolved and reported
back instead of deleted.
The dialog states the kept count before the user acknowledges.
## How it fits the GUI
The clear-all button is a real slot occupant: it registers into the sidebar
shell's `sidebar.header.action` list slot via `ctx.slots.inject` (the shell
renders the row's occupants beside its own New Session icon and hides them on
the collapsed rail).
The two row menus belong to `ui-workspace`'s browser and declare no plugin
slots, so the browser half integrates them at the DOM level:
- A body-level observer recognizes a portal menu by its item set when it
opens: the Rename + Delete-workspace pair is a workspace menu (a "Clear
session history" danger row is cloned in), the Rename + Fork + Archive set
is a session menu (a red "Delete session" row with a trash icon is cloned
in). The cloned rows reuse the menu's own classes and the theme's danger
color.
- The target is captured from the anchor button's aria-label at
pointerdown/keydown: a workspace row (title + occurrence among same-titled
rows, so same-basename workspaces resolve deterministically) or a session
row (title, enclosing workspace group, and the row's index within the
group). The session id never reaches the DOM, so the plugin resolves it
from the click by addressing `workspace.sessionIds` at the group-relative
row index, mirroring the sidebar's own render order (skip archived and
summary-less ids), and double-checks the session title before acting. An
ungrouped row matches by title among sessions no workspace owns.
Every hook degrades to a no-op on a template mismatch (unknown locale,
restructured DOM) rather than breaking the sidebar. Both shipped locales
(English and 简体中文) are matched.
The dialog is the harness's own `RiskConfirmation` primitive (checkbox-gated),
hosted on the plugin's private React root. All calls round-trip through the
Typert remote (`/api/clearSessionHistory/*`):
- `preview(input)` / `clear(input)` → workspace scope
`{ workspaceTitle, titleOccurrence }` (empty title = every workspace);
returns `{ targets, kept }` / `{ deleted, targets, kept, removed }`.
- `previewSession(input)` / `clearSession(input)` → single session
`{ sessionId }`; preview reports whether the log is deletable, clear
returns `{ deleted, targets, kept, removed }` and never removes a workspace.
Workspace titles are resolved through `workspaceRegistry` (registry display
order), matching the order the sidebar renders them.
## Install
```sh
dsh plugin --profile web add /path/to/deepseek-harness/plugins/clear-session-history
```
Restart the GUI. Verify:
```sh
curl -s -o /dev/null -w "%{http_code}\n" \
http://127.0.0.1:3080/plugins/dsh-clear-session-history/client.js # 200
```
## Safety notes
- The plugin operates underneath the host's append-only persistence service.
It only ever removes directories the backend itself locates for a listed
session, and only after re-checking the directory shape on disk.
- Workspace registrations are only removed by the host's own
`workspaceRegistry.delete()`, and only after a fully successful clear (every
targeted log gone), so a partial failure never orphans sessions.
- There is no trash or undo: deletion is permanent. The checkbox in the
dialog exists for that reason.
## Verification
- `pnpm test` (node half against stubbed services + temp sessions root):
scope matching, running/subagent keep rules, occurrence resolution, unknown
workspace soft failure, workspace-registration removal (per-workspace and
clear-all), partial-clear keeps the workspace, single-session delete
(cold deletes, attached-but-idle deletes and archives to hide, running
agent refused, unknown id fails, workspace never removed), degenerate
`locate()` refusal, missing-service failure.
- `node tests/client-session-flow.mjs` (built client bundle under the harness
checkout's jsdom): drives the real pointerdown → session-menu → Delete
session → id-resolution path against a simulated grouped sidebar and asserts
the dialog acts on the resolved session id.
- Live probe against a running instance (non-destructive):
```sh
curl -s -X POST http://127.0.0.1:3080/api/clearSessionHistory/preview \
-H 'content-type: application/json' \
-d '{"type":"client-request","rpcId":"probe","method":"clearSessionHistory/preview",
"payload":{"args":{"input":{"workspaceTitle":"","titleOccurrence":0}}}}'
```
The returned `targets` count matches `find ~/.dsh/sessions -mindepth 2
-maxdepth 2 -type d | wc -l` (minus sessions whose agent is running at that
moment, reported as `kept`).
Install
dsh plugin --profile web add github:n0pe-sled/clear-session-history
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-clear-session-history from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.