Sidebar session search that builds its own index instead of reusing DSH's full-text one: title to content toggle in one entry, results grouped per session with the strongest snippet and user/reply/tool tags, plus watermark-based incremental sync, archive-driven pruning of archived sessions, and a shadow rebuild that never blocks the live index.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-search-index
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:drscrewdriver/dsh-search-index
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
A DSH plugin: title + content full-text search over sessions, backed by its own FTS5
index. Content-search corpus and semantics come from dsh-reference-session-collector (DRS),
fetched over HTTP or the in-process read-only transport.
- Title mode — matches session titles as you type;
- Content mode — session-grouped hits (title + anchored snippet) with type filters (user / reply / tool), domain (all / active / archived), date range, ordering (relevance / time) and "expand hits";
- Hit locating — opening a hit lands on the target line (diagnostic badges ⏳/✓/✗); cold hits (>40 turns behind the session's newest turn) are tagged 🧊 next to the session title, ask for confirmation first, then backfill pages with a measured page-width budget (a HUD overlay shows progress and stays put with Jump / Cancel; a miss can be retried with up to two extra page-budget rounds; very-cold hits >200 turns are tagged ❄️ and never auto-backfilled);
- Index lifecycle — incremental watermark sync, non-destructive rebuild (shadow build + atomic swap + bounded archives), deep sync (DRS slow rescan + rebuild), JSON Lines snapshot export / import.
DRS is a hard dependency — the transport is not. Grouping, type weighting, surface and archive-date filters and anchored snippets are all computed inside the
dsh-reference-session-collectorprojection DB. It ships as a regular dependency of this package (users never download it separately) — but installed is not activated: the host only activates bundles listed in the profile's top-leveldependencies, so as a transitive dependency it may never mount and the projection DB will not update. The settings card then shows the activation command:dsh plugin --profile <name> add dsh-reference-session-collector, followed by a host restart. Either transport is enough to serve content search:http(same-origin route) orin-process(direct read-only access toprojection.db).DSH NEXT desktop users, read this. The desktop host answers every non-renderer request with
403 Browser access is disabledbefore routing, so the HTTP transport can never work there. The defaultcontentTransport: autosilently switches to the in-process read-only arm — no action required. Only when both arms fail doessearch-statusreturnavailable:false, now with a specific code (browser-access-denied/drs-http-unreachable/drs-http-error/drs-not-activated/drs-missing/drs-empty/drs-schema-mismatch). A 404 route is reported asdrs-not-activatedand flagged viasearch-status.notActivated=trueeven when the in-process arm succeeds, together with the activation command above.The in-process arm is strictly read-only: no
mkdir, no DDL, nometawrites; a schema mismatch is reported and never clears the projection DB.
Index self-healing: rebuild vs deep sync
The panel footer and the settings card each carry two buttons; they fix different layers:
| Button | What it does | What it fixes |
|---|---|---|
Rebuild index (index-rebuild) |
Rebuilds the local FTS index from the host sessionQuery (shadow build → atomic swap → bounded archives) |
dirty / incomplete local index, stale titles |
Deep sync (drsc) (index-deep-sync) |
① POST {op:'sync'} makes DRS slowly rescan its projection DB, polling last_sync.busy=false (180 s cap); ② then runs a rebuild |
hits that fail to locate (stale DRS projection: seq/on_surface no longer match the rendered text) |
Locating never reads the local index: it takes the hit's seq, asks DRS op=session for that
row and matches search_text / surface_text against the GUI. So "clicking a hit goes nowhere"
is only cured by deep sync — rebuilds never touch the DRS projection. Progress rides
index-status.deepSync (idle → drs-syncing → rebuilding → done | failed). Neither button
runs concurrently; pressing deep sync while a rebuild is running fails fast, so wait it out or
press deep sync alone.
Entry: two forms by host generation
| Host | Form | Registration / interaction |
|---|---|---|
| ≥ 0.1.7 | "Plugins-page style" full page | sidebar.panellist row (id dsh-search-index, order 5 — after Plugins(0), before Session Manager(6)) + a main page with the same key; clicking the row or Ctrl/⌘+K → layout.selectPanel('dsh-search-index'); the page's Close returns to the conversation |
| < 0.1.7 | sidebar dialog (unchanged) | sidebar.footer.action entry (order 5) + SwitchPanel dialog (backdrop / Esc / Ctrl/⌘+K); no panellist/main |
Generation signal: the sidebar.workspaces.session.menu.item seat plus main
(absent on 0.1.5, present from 0.1.7). The two forms are mutually exclusive: decided once when
the seats are ready, and the other form is removed in place if a seat shows up later;
enabled=false tears the pair down.
Supports DSH 0.1.0-rc.2, 0.1.0-rc.3, 0.1.0-rc.6, 0.1.0-rc.7, 0.1.0-rc.8, 0.1.1-rc.1, 0.1.1-rc.2, 0.1.2-rc.1, 0.1.5-rc.1, 0.1.5-rc.2, 0.1.5-rc.3, 0.1.7-rc.1, 0.1.7-rc.2, 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-rc.1, 0.2.1-rc.2, 0.2.1-rc.3 (enumerated peers; 9 of these rc versions have a farm-cell smoke — matrix below).
Compatibility matrix (0.7.7 measured)
Each rc was installed into its farm cell (_host-farm/cells/<rc>), booted with dsh web, and probed:
| Host rc | /switch-search/api/index-status |
settings-describe |
Entry form |
|---|---|---|---|
| 0.1.0-rc.8 | 200 · available:true | 200 · writable:true (legacy bridge) |
footer dialog |
| 0.1.1-rc.2 | 200 · available:true | 200 · writable:true |
footer dialog |
| 0.1.2-rc.1 | 200 · available:true | 200 · writable:true |
footer dialog |
| 0.1.5-rc.3 | 200 · available:true | 200 · writable:true |
footer dialog (UI verified) |
| 0.1.7-rc.2 | 200 · available:true | 200 · writable:false (read-only) |
main page |
| 0.2.0-rc.2 | 200 · available:true | 200 · writable:false |
main page |
| 0.2.1-rc.1 | 200 · available:true | 200 · writable:false |
main page |
| 0.2.1-rc.2 | 200 · available:true | 200 · writable:false |
main page |
| 0.2.1-rc.3 | 200 · available:true | 200 · writable:false |
main page (UI verified, desktop) |
UI was verified in a real browser at both ends: 0.1.5-rc.3 = footer "搜索 Ctrl K" + dialog
(Esc / Ctrl+K); 0.2.1-rc.3 = sidebar.panellist row + full page (no backdrop).
Nine enumerated rc versions still have no farm cell (0.1.0-rc.2/rc.3/rc.6/rc.7, 0.1.1-rc.1,
0.1.5-rc.1/rc.2, 0.1.7-rc.1, 0.2.0-rc.1): allowed by the declaration only, not smoke-tested.
Narrow the engines.dsh enumeration if you want the list to equal the tested set.
Install
dsh plugin --profile web add dsh-search-index
Restart the host process afterwards. Without DRS, title search still works; content search reports itself unavailable.
Do not install together with the merged
dsh-session-steward(≤0.5.x, which bundles search): both register the/switch-search/apiprefix and a 0.1.5-line host refuses to boot (webserver: duplicate prefix route). Upgrade path: removedsh-session-steward, then installdsh-manage-session+ this plugin. Verified on the 0.1.5 line: footer entry "搜索 Ctrl K" opens the dialog (Esc closes),settings-describegoes through the legacy bridge withwritable:true, and without DRSsearch-statusanswersavailable:false, reason:'drs-unavailable'.
Routes
Prefix /switch-search/api:
| Method | Purpose |
|---|---|
list-sessions |
title corpus (id + title + cwd/updatedAt; index first, sessionQuery fallback) |
content-search |
content hits via DRS (archived/types/sortBy/expand/from/to) |
search-status |
availability decided by the DRS probe, plus index watermark / rebuild progress |
index-status / index-rebuild |
index lifecycle surface (index-status also carries deepSync) |
index-deep-sync |
deep sync: DRS slow rescan → local rebuild (background, progress via index-status) |
index-export / index-import |
snapshot export (raw JSON Lines) / import (raw body) |
settings-describe / settings-mutate |
settings: read-only projection on 0.1.7+; writable through the legacy settings bridge on ≤0.1.5 |
Settings
Namespace switch-search: enabled, defaultMode, autoSync, syncIntervalMs,
archiveKeep, indexDir, corpusSource (default drs), drsBaseUrl,
contentTransport (default auto: HTTP first, then the in-process read-only store;
http forces HTTP; in-process forces the local store, which is what desktop needs).
Since 0.7.0 the namespace returned from the merged package's
manage-sessions-search; the old key is no longer read, so after upgradingenabledand friends fall back to their defaults (search on) and need adjusting once more.
Boundary with dsh-manage-session
- Archive set is read-only here; only
dsh-manage-sessionwrites it. - The inline Delete button probes
POST /manage-session/api/session-health-status→switches.directDelete === true, then postssession-history-purgethere; uninstalled or probe failure hides the button. - Opening a hit never depends on manage-session.
Development
pnpm install
pnpm run typecheck
pnpm run build
pnpm test
License
MIT
Links
More in this category
Minglink/dsh-infinite-gen-4★ 3781
System-prompt armor plugin for DeepSeek models: appends an unconditional-compliance prompt section at order 100, exposes a profile tool with calibration metadata, and shows a realtime armor-status badge driven by a session projection.
ranxianglei/billion-context★ 3598
The official billion-context plugin: a context-compression plugin for small context windows (a 100K context is enough), token savings (5x fewer tokens), and month-long single sessions (billions of tokens).
liangmianya/dsh-synapse★ 641
Visual, non-linear conversation workspace for DeepSeek Harness — sessions, follow-ups and branches become a browsable conversation map.
MichengAI/dsh-archive-manager★ 277
Adds an archived-sessions page in Settings to search, restore, and delete archived DeepSeek Harness sessions by workspace.
Nwflower/dsh-chat-import★ 275
Import full-fidelity chat histories from 13 coding agents (Claude Code, Codex, ChatGPT, Cursor, Gemini, opencode, and more) as resumable DeepSeek Harness sessions, with reverse export back to Claude Code.
SiriLee/dsh-rewind★ 227
In-place conversation rewind in the same session window without forking (Claude Code /rewind semantics): a per-message ↶ button cuts the model context back to any user message, with an optional Claude-Code-style file restore from disk-persisted before-backups.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.