kjx-talesofai/dsh-plugin-session-palette ↗★ 0

dsh-plugin-session-palette

Make Cmd+K open a Spotlight-style session palette instead of focusing the sidebar search box, with in-place renaming and up to three tags per session. 适合拥有大量历史会话,需要通过标题、工作区或目录快速模糊检索并切换会话的用户。

패키지
dsh-plugin-session-palette
호환성
미검증
버전
0.8.1
라이선스
MIT
최근 업데이트
2026. 10. 17.

설치

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:kjx-talesofai/dsh-plugin-session-palette

Usage

  1. Press ⌘K. The palette opens with your sessions already listed.
  2. Type to narrow by title, workspace name, or the directory the workspace lives in (fuzzy-matched, ordered-subsequence — the same ranker the host's own / menu uses). The directory matters because a renamed workspace no longer shows its folder, and the folder is what you remember.
  3. ↑/↓ or hover to move the highlight; the right-hand pane previews that session's last turn.
  4. ⏎ opens it. Tab switches to the Pinned or Archived drawer.
  5. ⌘⇧R renames the highlighted session right there — the search field becomes the title field, ⏎ saves, esc cancels.
  6. ⌘⇧P pins, ⌘⇧⌫ archives, and both toggle — so the same chord restores.
  7. In an open conversation, double-click the title in the header to rename it there — the same editor, floated over the title's own box.
  8. ⌘⇧T opens the tag picker for the highlighted session: type to narrow, type a name nothing matches to create it, ⏎ toggles, esc done. Tags show as glyphs on the row and as chips in the preview; clicking a chip filters the list (several = any of them).

Tags

A tag is id + label + optional emoji + colour. Five colours, each a theme token, so light and dark follow without a second palette. A session carries at most three; the ceiling is refused out loud ("Three tags is the limit") rather than truncated, because a fourth tag the user cannot see is worse than a no.

  • Where they live: the plugin's Config, next to takeOver. Both tag fields are .volatile(), which is load-bearing twice — only volatile fields are accepted on write, and the Loader compares volatile fields out of its config diff, so saving a tag updates the running plugin instead of remounting it. Without that, the act of tagging would close the palette it was done from.
  • Identity is the id, derived from the label when the tag is created and stable afterwards, so renaming a tag later cannot orphan the sessions that point at it.
  • The filter means "any of": tags are how you group work that spans workspaces and dates, so narrowing on two tags should show both groups, not their intersection.
  • One picker, attached to the item. The sidebar's session menu row (with a tag icon, styled by the shipped MenuItemButton) opens the palette with that session highlighted and the picker already up, instead of growing a second tag UI to keep in step.

The editor is a popover-like block inside the preview pane, under the session's own tag chips: its own input, its own list, its own Escape. The search field keeps searching sessions the whole time, and the list is never frozen — moving the highlight simply closes the editor, because an editor belongs to the row it was opened on. That is the corrected model; the first version made the search field filter tags while the list was locked, and it was unusable for exactly the reason you would expect: two meanings on one field, and a target that could not be moved.

This needs one DSH restart. The tag store is new host-side Config, and the Host reads index.js at startup — so until DSH restarts, the picker says "Tags need one DSH restart" and refuses to write. It never pretends to save.

Finding your way around a long list

A palette that asks you to browse needs landmarks. Three, in order of how much they do:

  • Sticky group headings. After two screens the only thing left was a wall of identical rows; the heading now stays at the top of the scrollport, so "this is yesterday" is always on screen.
  • A position, not just a total. The footer reads 37 / 340 — the whole point is answering "am I lost?", which 340 sessions cannot.
  • Jumps that always work. ⌘↑/⌘↓ go to the ends from anywhere (Home/End already did, but only once the caret reached an edge), and PgUp/PgDn move a screenful — clamped, because a page that jumps back to the top reads as a bug.

Two smaller ones: rows older than a week show the date you worked on them rather than 23d (a duration is not what you remember about last month), and the card is 1180px × 88vh — this surface exists for browsing, so it should not be shy about space.

Renaming

The highlight is already the thing you are about to act on, so a rename edits that row and nothing else. ⌘⇧R turns the search field into a title field: same place, same line height, focus never moves, the query stays applied and the highlight stays put. ⏎ saves, esc restores the query, and a rejected write leaves the draft in place with the host's reason on its own line underneath.

Three decisions are worth naming:

  • ⌘⇧R, not ⌘R — page.refresh owns ⌘R, and ⌘⌥G is the host's own session.rename. Both are chords the Electron main process intercepts before the page ever sees them, so a handler on either would never run and the host would act behind the palette instead. Shift+R is unclaimed by every shipped command, which is a property of the current catalog rather than a promise.
  • The search field, not a second input — an input inside a role="option" row is invisible to the accessibility tree, and a second text box would have to fight the row's focus handling. One field, two jobs, and its ARIA role changes with the job.
  • sessions.using, not a direct call — the host refuses to rename a session that is not live in its store, so the write goes through a retained reference (the same path the shipped sidebar uses). The retain is attributed to sessionPalette in retainInfo, and it lasts for exactly one call.

The bytes you type are not the bytes stored: the host normalizes whitespace and shortens a title past maxTitleBytes (80 in the shipped bundle). The toast therefore reports the title the host accepted, and Undo is only offered when there was a title to go back to.

How it works

The Chrome-free parts: the palette is one component registered into the frame-wide shell.overlay seat, rendering role="dialog" aria-modal="true" — which is what makes it a real modal for the host's Escape arbitration and for background-shortcut gating. The toast is a second entry in the same seat so it outlives the palette.

Taking ⌘K is the interesting part, and it writes no preference:

StepMechanism
1ctx.shortcuts.register refuses any default that overlaps session.search, and there is no unregister API — so the chord cannot be claimed by registering a command
2ctx.shortcuts.registerFixed performs no conflict check, so a fixed binding on ⌘K is accepted
3That fixed binding makes session.search conflicted, which removes meta+KeyK from the Electron main process's natively intercepted set
4With the chord no longer intercepted, the DOM keydown reaches the page, where fixed observers run before configurable dispatch — and consume it

Steps 2–4 were verified end to end against a running app before the palette was written. A fixed binding is deliberately not user-rebindable, which is why the plugin also registers a normal, unbound command (session.palette) that you can attach your own chord to in Settings → Shortcuts.

The preview comes from the host's turnOutline projection, which already carries every turn's prompt (≤50 code points) and response (≤120) into the session list — so for most sessions it costs no request at all. Measured coverage was about two thirds; the rest are the sessions you have not touched in a long time, and those are read lazily, one at a time, after the highlight settles.