Install
Inside DeepSeek Harness, with dsh-market
dsh plugin --profile web add dshmarket
Or from the command line
dsh plugin --profile web add dsh-search-index
Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.
Screenshots
README
dsh-search-index
Sidebar search index for DSH web: adds a "Search" entry at the sidebar footer whose floating panel toggles between title search ↔ content search; content mode groups results by session (title + snippet) and filters by user / reply / tool. Ships its own index (independent of DSH's built-in full-text index) with incremental sync, a non-destructive rebuild, and snapshot export/import.
This package owns search and the index only. The session-history viewer (the old "archived sessions" panel) moved to
dsh-session-steward. This package still reads the official archive set to keep archived sessions out of the index, but no longer writes it — the archive set has exactly one writer: the steward.
A cordis client + host plugin assembled via the dsh plugin command and a bundle patch — no dsh source changes, no PR required.
Predecessor and this release
Predecessor: dsh-session-search-toggle. That version relied on defineStore from @deepseek-ai/dsh-client-runtime to provide the settings-row seat. DSH 0.1.2 renamed and restructured the client engine packages (dsh-client-runtime → dsh-client-store), so the old code could not load on the new host — and no single artifact could serve both releases.
This release (dsh-search-index 0.6.0) targets DSH 0.2.0 as its main line (peer >=0.2.0-rc.1 <0.2.1-0). Historical host lines are served by separate version lines: DSH 0.1.7 is served by 0.5.7 (npm dist-tag dsh-0.1.7, compat/0.1.7 branch), and earlier 0.1.1/0.1.2 hosts by the legacy artifact, which is frozen. Historical background:
- One artifact, runtime-adaptive: the same
lib/client.jsloads on both 0.1.1-rc.2 and 0.1.2-rc.1 with no version-string branching anywhere. The client bundle onlyrequiresreact/react-dom, both of which sit in the shared module table of either release. - Neither engine package is imported: it imports neither
dsh-client-runtimenordsh-client-store, so that rename cannot affect it. - The store seat is implemented locally: the settings row needs a store seat (
StoreHandle/StoreInstance, a contract owned by@deepseek-ai/dsh-client-ui-slotsand identical in both releases). It used to come fromdefineStore; it is now a ~30-line local implementation that only providescreate()→{ actions, getSnapshot, subscribe, clearPersisted }— no release-specific specifier. - Every other contract is identical across releases: the
settings.general.itemslot,SettingsScope.{getSnapshot,subscribe,set,unset}, and the threesessionQueryfaces have the same signatures in both.
▼ DSH version support
DSH version Status Carrier and key difference 0.2.0-rc.1 ✅ this release 0.6.0; peer/engines = >=0.2.0-rc.1 <0.2.1-0, zero code changes (pure-caller consumption of the slots/locale/configForms seats)0.1.7-rc.1+ ✅ 0.5.7 (dist-tag dsh-0.1.7); configForms resolves by entry id, older hosts fall back to settingsScope bound by namespace0.1.1-rc.2 ✅ (legacy artifact, frozen) the store engine lives in @deepseek-ai/dsh-client-runtime/client0.1.2-rc.1 ✅ (legacy artifact, frozen) the engine was renamed to @deepseek-ai/dsh-client-store; this plugin imports neither
Upgrading from the old name: this package was renamed from dsh-session-search-toggle; the client registration id, the cordis patch id and the repository URL were renamed with it. GitHub keeps redirects for renamed repositories, so the old name still resolves — but switch the profile dependency to the new name rather than letting both coexist:
dsh plugin --profile web add github:drscrewdriver/dsh-search-index#master
dsh plugin --profile web remove dsh-session-search-toggle
dsh web # restart
The settings namespace stays switch-search (storage key kept stable, no migration), so existing configuration keeps working under the new name.
What it does
- Title ↔ content toggle: two ways to search from one entry — "Title" filters by session title / working-directory substring live; "Content" searches message bodies through this plugin's own index.
- Content grouped by session: each content result is one row (session title + strongest snippet + type tag); clicking opens that session — no per-message flood.
- Content-type filter: filter chips at the top of content mode — All / User / Reply / Tool;
Toolopenstool/callandtool/resultevents into the index, so you can search tool call arguments and results directly. - Result ordering: Relevance / Time on the right of the same row — "Time" orders by session last activity, newest first; the choice persists locally across reloads. Hits carry both the document timestamp and the session clock, so a client can re-sort on its own.
- Realtime titles: the host subscribes to
session/event, so a rename (session/title) folds into the index immediately instead of waiting for the next sync (30s by default). - Settings card: Settings → Plugins gains a "Search Index" card — enable toggle, default search mode, sync/retention/index-dir knobs, and the index-lifecycle block (status, non-destructive rebuild, snapshot export/import). When it reports "N archived session(s) excluded" it also points at the owner: browsing and disposing of archived sessions belongs to dsh-session-steward, and this plugin only reads the archive set. When that plugin is absent the hint says so — the host decides by resolving it from the plugin's own module graph, and any inconclusive probe falls back to the neutral wording rather than reporting "not installed" for a package it merely could not check.
- Invoke key and platform-native key hints: both the sidebar entry and the panel's key bar show the invoke chord —
⌘Kon macOS,Ctrl Kon Windows/Linux, decided from the running system; the panel's close key is platform-native too (escon macOS,Escelsewhere). Detection falls back UA-CH →navigator.platform→ UA string, so a privacy mode never demotes a Mac user to the Windows glyphs. The hint and the binding share one owner: the chord printed on the cap is the chord thekeydownhandler matches. - Jump to session: clicking a result opens that session, landing on the context around the hit.
UI preview
Sidebar entry and the search panel layout:

The independent index: three mechanisms
Content mode builds its own database; it does not depend on DSH's session-query-sqlite full-text index. The index lives in src/host/: schema.ts creates the tables (including its own FTS5 table docs_fts), engine.ts runs queries, and extract.ts pulls searchable text out of session events — including tool/call (tool name + arguments) and tool/result (result text), which is what the tool filter is built on. DSH's sessionQuery is used only as a corpus reader (listSessions / readSession), never as the search backend.
1. An independent conversation-content index
The index is this plugin's own SQLite file, with no effect on the official index. The directory is configurable, the index can be exported/imported as a snapshot and rebuilt wholesale. On host activation it inspects the index directory: a leftover index.building.sqlite next to a live active index means "the last rebuild never finished" and is discarded; a leftover with no active index means "the crash hit the rename window", so the newest archive is rolled back as active.
2. Archived — i.e. no longer usable — sessions are pruned, on a rolling update
archive-source.ts reads global.archivedSessionIds from the official storage hub (~/.dsh/storages/workspace.json) and keeps archived sessions out of the index. The archive set has exactly one writer — the steward; this package only reads it.
Sync is rolling: SwitchWatermarkSync in sync.ts keeps a version watermark per session and, on each pass, only diffs and re-ingests sessions that changed — never a full rebuild. The index files themselves are retained in a bounded number of copies under archiveKeep, with older ones aged out.
3. Housekeeping never blocks the working index
Housekeeping goes through a shadow index: rebuild.ts builds index.building.sqlite from scratch beside the active one, and the active index keeps serving searches the whole time — queries are never blocked. Once the build completes, the swap is three synchronous rename calls (active → archive, shadow → active), i.e. a single atomic window.
That also fixes the failure semantics: shadow present + active present = the build never finished, so the shadow is garbage and is discarded — the active index was never at risk.
Installation
# Option 1: install directly from GitHub (recommended) — lib/ is committed, no local build
dsh plugin --profile web add github:drscrewdriver/dsh-search-index#master
# Option 2: assemble from a local path / source (see Development)
# Restart dsh web — required! A running instance does not hot-load the bundle layer
dsh web
After install a "Search" button appears at the sidebar footer; Settings → Plugins gains the "Search Index" card.
⚠️ GitHub reachability: installing via github: requires access to github.com; if your network is restricted, set up a working proxy or mirror first, otherwise add may stall while fetching.
Development
pnpm install # includes the @deepseek-ai client chain + tsdown/tsc
pnpm typecheck # tsc --noEmit
pnpm build # tsc (lib/types) + tsdown (lib/index.mjs + lib/client.js)
Layout
src/
├── index.ts # host half (node): Config schema + installSettingsSection + routes
├── config.ts # pure shared config (enabled/defaultMode + namespace constant, schemastery-free for client)
├── host/ # the independent index (host side)
│ ├── schema.ts # tables: docs / docs_fts(FTS5) / sessions / watermarks
│ ├── engine.ts # queries
│ ├── extract.ts # pulls searchable text from session events (incl. tool/call, tool/result)
│ ├── sync.ts # SwitchWatermarkSync: rolling incremental sync by version watermark
│ ├── rebuild.ts # shadow build + atomic swap + crash-recovery inspection
│ ├── archive-source.ts # reads the archive set to keep archived sessions out of the index
│ └── snapshot.ts # snapshot export/import
└── client/
└── index.ts # browser half: sidebar.footer.action entry + floating panel + settings.general.item row
- Host half: registers the fenced HTTP route
/switch-search/api(list-sessions/content-search/search-status), with a browser-trust fence identical to the DSH/apigateway (loopback Host or trustedHosts; cross-site refused). - Config pattern: the host registers the
switch-searchnamespace through thesettingsservice with a schemasteryConfig; the client mirrors/edits it with a local store seat +settingsScope.bind; the shared pure modulesrc/config.tskeeps schemastery out of the client bundle. - Build chain: tsdown mirrors the harness
packages/client/tsdown.client.tssemantics (__ModuleLoader__.loadbanner, platform externals table, bundle purity gate). - lib/ committed: GitHub installs run off the committed build output (dsh does not run
prepareon a git install);.gitignoredoes not excludelib/.
Relation to the official sidebar search
- The official sidebar search box lives in
sidebar.workspaces(a single slot); an external plugin cannot replace it. This plugin adds a separate entry at the sidebar footer viasidebar.footer.action; the two coexist. - The official content search hard-codes
user/message+assistant/messagein apiproxy; this plugin searches its own index and opens uptool/call+tool/result, enabling tool-level search.
Compatibility and privacy
- Requires DeepSeek Harness with the web profile; no official source is modified. The index is this plugin's own file, so whether the official
session-query-sqliteis enabled makes no difference to this plugin. - Configuration lives only in the DSH settings namespace and browser panel state; it reads/upload nothing beyond session-search data.
- Host/client contract types are declared structurally in
src/*.ts(the npm dsh client chain is incomplete) and mirror the harness sources at build-verification time.
drscrewdriver DSH Plugin Family
This project is one of the DSH plugins maintained by drscrewdriver. If this one helps you, the others likely will too:
| Plugin | One-liner |
|---|---|
| dsh-input-traffic | Busy-time input queue: three-tier traffic control, drag-to-reorder, session freeze |
| dsh-thinking-levels | Per-round reasoning_effort control: Auto scheduling or manual wire level |
| dsh-seatbelt-sandbox | macOS Seatbelt sandbox adapter: native libsandbox loader replacing deprecated sandbox-exec |
| dsh-prime-memory | Layered distilled memory: automatic L0–L3 distillation, recall injected before each step |
| dsh-search-index | Session content search sidebar: title/content toggle, type-filter by user/reply/tool |
License
MIT
Comments
Comments live in GitHub Discussions. Sign in with GitHub to post or react.