dsh-activity-heat · Recent Activity
A DeepSeek Harness plugin that surfaces the sessions and workspaces you most recently talked to, using a heat score derived from elapsed time — so the conversation you just had never gets buried.
Why
Sessions pile up in the DSH sidebar, nested inside workspace groups. The one you just spoke in is pushed out of sight within minutes.
This plugin computes a 0–100% heat score from time since last activity and expresses it in three places plus a panel — the more recent, the more visible.
What you see
| Where | What it does |
|---|---|
| Sidebar entry | A heat-ring icon whose brightness follows the hottest session in the profile |
| Session title | A persistent blue underline beneath the title: the hotter, the deeper and more saturated the blue; only active sessions (within ~6 hours) gently pulse between two shades; hidden after 7 days. The row-leading cell stays with the host's state dot and the schedule clock |
| Session hover card | Heat 4/5 · Warm, Last active: 40 min ago, This workspace is hot |
| Main panel | Sessions / Workspaces views, each with heat filters and a count selector |
Panel
- Top-level switch:
Sessions 58/Workspaces 10— no more mixed list - Count selector: sessions
10 / 20 / 30 / 50, workspaces5 / 10 / 20 - Heat filter: All / Blazing / Warm / Active / Recent / Stale, with live counts
- Every card: the bottom row is split into three groups divided by hairlines — ① cache hit rate + context usage (each with a mini progress bar) ② turns + steps + tokens ③ accumulated conversation time; items inside a group use a dot, groups are separated by a vertical rule. The
Currentstate is a badge before the title, and the relative time lives in the right column, not the meta row. - Token breakdown: hovering the token figure opens a details card — exact total, cache hit rate, uncached input, cached input, cache write (when non-zero) and output, mirroring the host's "Token usage" dialog
- Time breakdown: hovering the conversation time opens a card with the active duration, a donut of wait / thinking / reply / tool-argument / tool-run / other, its legend and a proportional timeline strip — same source (
contextTimeline.timing) and same segmentation as the host's timing panel; the card is portaled todocument.bodyso the scrolling list can never clip it - Right column: a fixed 168 px column stacks the workspace name (top), the heat meter with its level label (middle) and the relative time (bottom), so every row lines up
- One icon language: all five metrics share 12 px stroke icons drawn from
currentColor— no colored emoji - Long lists scroll: the list caps its height, keeps 12 px of vertical padding so the first/last card's glow is not clipped, uses block flow (30–50 rows keep full height instead of being squashed), hides its native scrollbar, and shows a floating
⌃⌄handle on the right while more rows remain - Click to open: a session row opens that session, a workspace row opens that workspace
Running sessions are marked
Cards for sessions with session.running === true get:
- a whale-tail mark with a Working label in the card's right-hand whitespace — the mark is a 12-frame animated GIF that plays its own loop (no CSS wobble), 28 px tall
- an orbiting light band around the card border (
conic-gradient, one turn per 2.2 s) - a breathing outer glow (one cycle per 2.4 s)
- the row-leading cell is left entirely to the host: running/pending rows draw the native
StateDot, sessions with scheduled tasks draw the clock marker, and the plugin puts nothing there. Heat shows only as the persistent underline beneath the title
Workspace cards get the same treatment when any session inside that workspace is running — same whale-tail label, same orbiting band, same breathing glow — so live work is visible from the Workspaces view too.
A workspace card also aggregates everything that can be summed: session count, turns, steps, tokens and total conversation time. Cache hit rate is weighted across the workspace's billing buckets (not an average of percentages), and context usage shows the most loaded session. Nothing that cannot be summarised — prompts, the current-session flag — is carried over.
prefers-reduced-motionis respected: with reduced motion enabled, all animation stops.
Colors
Only host theme tokens are used — no hard-coded colors — so light and dark themes work automatically:
--color-blue-500 /* heat color (blue, falls back to #3b82f6) */
--dsw-alias-bg-base /* opaque card background (hides the orbit band) */
--dsw-alias-label-primary /* primary text */
--dsw-alias-label-secondary /* secondary text */
--dsw-alias-border-l1 /* separators and tracks */
Heat is expressed as one blue hue at different opacities (0.3 → 1.0) plus a partially filled meter. The host brand token reads near-black in light themes, so the plugin prefers the blue scale instead.
Install
This plugin is installed as a local directory or a GitHub repository; it is not published to npm (private: true).
Option 1 — GitHub (recommended)
# inside your DSH profile directory
cd ~/.dsh/profiles/<your-profile>
pnpm add github:MarkTina/dsh-activity-heat#v0.1.0
Pin a tag (#v0.1.0) rather than following main, so an unrelated commit cannot change what you run. To upgrade later, bump the tag and restart DSH.
The repository must be public; a private repo needs a token (https://<token>@github.com/... or an SSH remote) before pnpm add can fetch it.
Then append the package name to dsh.profile.bundles in that profile's package.json:
"@local/activity-heat"
First install normally activates through the host's HMR — if the row does not appear, restart DSH once. Updating an already-installed copy (same path, changed files) always needs a full quit-and-relaunch: a page refresh only reloads the UI, and toggling the plugin does not re-read changed modules.
Desktop profiles are managed by the Electron app. Install through its plugin manager (GitHub / local directory) rather than running
pnpminside~/.dsh/profiles/desktop— the app owns that directory and will reject or overwrite manual changes.
Option 2 — Local checkout
cd ~/.dsh/profiles/<your-profile>
pnpm add link:/absolute/path/dsh-activity-heat
Add @local/activity-heat to dsh.profile.bundles the same way.
Shortcut
Default Ctrl + Alt + H (Cmd + Alt + H on macOS) switches to the Recent Activity panel. Pressing it again while the panel is open returns you to the conversation you came from — the panel never replaces the session, so the toggle is a round trip.
Change it under Settings → Shortcuts, searching for “Recent Activity”.
Why not
Ctrl+H? The host treatsCtrl+C/V/X/Z/Y/Q/Has reserved system keys (browser history etc.), so a bareCtrl+Hdefault is rejected asreserved. Addingaltuses the web whitelist branch. For the same reason the plugin deliberately declares no Linux profile — on Linuxalt+controlis treated as reserved and would fail the whole registration. Linux users can bind a key in Settings.
Configuration
Defaults live in cordis.patch.yml. To override, add a row with the same id to your profile's cordis.patch.yml (config is replaced whole, not deep-merged — write every field):
- id: activity-heat
config:
hotMinutes: 10 # blazing window (minutes)
warmMinutes: 60 # warm
activeMinutes: 360 # active
recentMinutes: 1440 # recent
staleDays: 7 # stale (hidden beyond this)
topCount: 8 # default rows in the panel
tickSeconds: 60 # heat decay refresh interval
showRowDots: true # show the heat underline beneath a session title
showHoverHeat: true # show the heat section in the hover card
Data sources
Everything is read from public host snapshots — no private APIs, nothing written to the session log:
| Purpose | Source |
|---|---|
| Last activity time | sessions.list.byId[id].updatedAt (same as the sidebar row trailing time) |
| Title / draft / subagent / archive | the same title blank origin, plus workspaces.list.archivedSessionIds |
| Workspace membership | workspaces.list.items[].sessionIds (same mapping as the host groupByWorkspace) |
| Turns / steps | sessions.list.projectionsBySession[id].values.sessionStats.turns / .steps — the values the composer activity pill reads |
| Tokens | …values.tokenUsage — billed input (uncachedInputTokens + cacheReadTokens + cacheWriteTokens) plus outputTokens, the same whole-log total as the composer usage pill |
| Cache hit rate | …values.tokenUsage.cacheReadTokens / billed input, using the host's own rounding (a partial hit never displays as 100%) |
| Context usage | …values.contextPressure — projectedTokens ?? pressureTokens over contextWindow, the value the composer context meter reads |
| Session prompt | …values.contextTimeline (initialPrompt / lastUser / lastReply) — the status bar does not show prompts |
| Conversation time & breakdown | …values.contextTimeline.timing (wallMs / ttftMs / genMs / toolsMs and the generation subdivisions) — the same source and segmentation as the host timing panel |
| Running state | sessions.list.byId[id].running |
Excluded: archived sessions, subagent child sessions, blank draft sessions.
Repository layout
The package follows the DSH bundle layout: a Host entry, a Web client entry, one loader patch, and exported display resources.
dsh-activity-heat/
├── index.js Host half — mounts the package, no Host services
├── client.js Web half — ModuleLoader factory, slots, locale
├── cordis.patch.yml Bundle patch — inserts the activity-heat row + defaults
├── locale/
│ ├── zh.json Plugin metadata + UI dictionary (Chinese)
│ └── en.json Plugin metadata + UI dictionary (English)
├── icon.svg Plugin icon (exported as ./icon)
├── assets/
│ ├── active.gif Animated whale-tail badge (inlined into client.js)
│ └── active.png Static source frame for the GIF
├── test/ Dependency-free self-checks, run with node
├── package.json Manifest — dsh.bundle.patch, dsh.client, exports, files
├── README.md This file
├── README.zh.md Chinese README
├── CHANGELOG.md
└── LICENSE
Visible UI text is routed through the host Client locale service (ctx.locale), falling back to the bundled Chinese dictionary when the service is unavailable. The two locale JSON files carry the same keys; node test/locale.cjs keeps them and the inlined dictionary in sync.
Debugging
Release builds log nothing. To trace the full chain, flip the switch at the top of client.js to true and reload the page:
var ACTIVITY_HEAT_DEBUG = false; // ← change to true
Self-checks
The repo ships dependency-free logic tests that load the real client.js against a minimal React stand-in — no browser required:
node test/panel.cjs # registration / sorting / filtering / rendering / click-through
node test/shortcut.cjs # shortcut defaults against host reserved-key rules
node test/boot-timing.cjs # restart timing: recovery when slots become ready late
node test/locale.cjs # locale dictionaries and metadata stay in sync
node test/assets.cjs # inlined running badge matches assets/active.png
pnpm test runs all four.
Known limitations
- Turns, tokens, cache hit rate and context usage read
sessionStats,tokenUsageandcontextPressure— the same projections as the composer status bar, so the numbers match it exactly. Profiles that only exposecontextTimelinefall back to its values; in that case the figures can differ from the status bar. - No Linux default shortcut is declared; Linux users bind one in Settings.
No comments yet. Be the first to write one.