Memory
dsh-session-search-chipphil
CHIP-PHILO-GH/dsh-session-search · ★ 0
DeepSeek Harness 侧边栏会话搜索增强:搜索范围由用户自己定——时间范围(全部 / 最近 N 天)与内容来源(我说的话 / DSH 的回复 / 项目名)都在搜索结果上方可改,选择存进官方设置文档、重启后仍然有效;命中片段显示两行。
Manifest details
安装
dsh plugin --profile web add 'github:CHIP-PHILO-GH/dsh-session-search#6f53036893482010b065de62e64edd3ba8510dc1'GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
If you only want the "do this once, step by step" packaged version (install, configuration and a troubleshooting table), see the skill pack of the same name dsh-session-search; this repository is the code itself.
Makes the search box in the DSH Web left sidebar use a scope you choose yourself: both the time range (everything / the last N days) and the searched content (what I said / DSH replies / project names) can be changed right above the search results, and the choice is stored in the official settings document, so it is still the same choice after reopening the browser or restarting DSH. The match excerpt shows two lines of the original text.
- Host half
lib/index.js: rewrites the sidebar search filters from one "scope object" (time range + content sources) and wires that scope into the official settings servicectx.settings. - Client half
lib/client.js: puts a "range" control above the search results; changes the match excerpt from a single ellipsized line to two lines; replaces the official misattributed message with one that states the current scope. - Bundle line
cordis.patch.yml: mounts this plugin and turns the official full-text index on (see below).
Why content search did not work before
The DSH sidebar search chain is:
搜索框 → 客户端 sessions.search → 远程 session.search → 宿主 SessionController.search
→ ctx.sessionQuery.searchSessions(...) → 官方 SQLite FTS5 全文索引
The official full-text index (@deepseek-ai/dsh-session-query-sqlite) is an optional capability in the web composition:
@deepseek-ai/dsh-web-app pins its line to path: ':memory:' + openAt: never:
openAt: never… while search calls fail withSESSION_QUERY_SEARCH_DISABLEDand SQLite is never opened; the Web sidebar search matches titles and workspace names only.
So the search box can only match names; while you type a keyword, the page also shows "content search is not available, name matches only" below the results —
that sentence blames "name matching only", while the real reason is usually that the index is not built yet. This plugin then replaces it with a message that tells the truth.
To turn content search on, the official way is to override that line's openAt in a later patch layer (the profile's cordis.patch.yml).
What this plugin does
Open the index (in the profile's
cordis.patch.yml, one of this plugin's install steps):- id: session-query-sqlite config: path: '<DSH_HOME>\profiles\web\session-search.db' openAt: first-searchopenAt: first-search= do not open the database at startup; the index is built on the first search (Node 22 stays quiet at startup). The index is a separate derived database: it only reads the session logs and never writes back; delete this file and it is fully rebuilt.Rewrite the filters from the scope (
lib/index.js): wrapsearchSessionsonctx.sessionQuery. The official caller originally sends "user/message+assistant/message+currentsurface"; this plugin drops itstypeandtimeclauses and uses the ones the current scope gives:Filter Value Effect surfacecurrentOnly the current model surface; ignore replaced history typeuser/message/assistant/message(per checkbox, both allowed)Decides whether "what I said", "DSH replies" or both are searched timefrom= local today 00:00 minus N-1 daysOnly the last N days; not injected when the range is "everything" When none of the three content sources is checked (for example "project names only"), a content search is meaningless: the host returns an empty page and never calls the official index, so the sidebar only shows its own name matches.
The window takes "whole-day boundaries" rather than
now - N×24h: semantically it is "the last N days (today included)", and the value is constant within the same day — the pagination cursor ofsearchSessionsis bound to the exact normalized request, and drift between calls makes paging fail asSESSION_QUERY_STALE_CURSOR.query/limit/cursorare passed through unchanged, the return value is unchanged, and the match excerpts come from the official index.A changeable scope that sticks (
lib/client.js+lib/index.js): the scope object has two layers. The default comes from the bundle line config above; what the user changes in the drop-down panel goes through the official settings servicectx.settings(namespacesession-search) and is stored insettings.yaml:session-search: days: 7 user: true assistant: true project: trueSo a change takes effect immediately (no restart needed), and it is still that choice after reopening the browser or restarting DSH. When the settings service is absent, the plugin falls back to the browser
localStorageand says so at the bottom of the panel.Two-line excerpt (
lib/client.js): the official result row renders the excerpt as a single ellipsized line (white-space:nowrap; text-overflow:ellipsis); this adds one style rule to make it two lines. The selector uses[class*="searchResultSnippet"](the class hash prefix changes with the build, the local name is stable); writing it as a double attribute selector is there to raise specificity, so it does not depend on style insertion order. Emptying thecssarray in this file restores the single line.Index prewarm (optional, on by default): the index is "built on the first search", so when the session logs have accumulated, the first search has to build the index for all history at once (after that it only reconciles incrementally). Prewarm moves that time to the background: after startup it waits
prewarmDelayMs(45 seconds by default) and runs a search that cannot match anything, ignoring failures silently. Setprewarm: falseif you do not want it.
Changing the search scope
While the search box is expanded in the sidebar and a keyword is typed, a "range" control appears above the search results; its right side shows the current scope and how many sessions were listed:
- Click the search icon to expand the sidebar search box and type any keyword;
- Click "range" above the results: the panel has two groups — time range (everything / 1 day / 3 days / 7 days / 30 days / custom days) and searched content (what I said / DSH replies / project names);
- One click takes effect immediately, no Enter and no restart; the bottom of the panel says where the choice is stored;
- The "project names" item controls name matching on session names / workspace names (done by the official sidebar itself): checked = name hits keep appearing; unchecked = only result rows with a content match remain. It is a name-matching switch, not a working-directory filter.
- When there are no results, the message carries the current scope (for example "no hit within the current scope (last 3 days · what I said)"), and while the index is still being built the message states the real reason instead of letting you think only names can be searched.
Configuration (bundle line config)
| Field | Default | Meaning |
|---|---|---|
days |
3 |
Time range in days (today included); 0 = no time limit (all history) |
user |
true |
Search what I said |
assistant |
false |
Search DSH replies |
project |
true |
Name-matching switch (session name / workspace name) |
userOnly |
none | The old 1.0.0 spelling: true ≈ user: true, assistant: false; false ≈ both of them true |
prewarm |
true |
Prewarm the full-text index in the background after startup |
prewarmDelayMs |
45000 |
Prewarm delay in milliseconds |
Install
Below, the DSH configuration directory is written <DSH_HOME>: if the environment variable DSH_HOME is set it is used,
otherwise it is %USERPROFILE%\.dsh; the profile name is web in the examples. The checkout directory of this repository is written <REPO>.
Three install steps; restart dsh web afterwards for them to take effect:
Make the plugin appear in the profile's
node_modules:<DSH_HOME>\profiles\web\node_modules\dsh-session-search. On Windows you can use a junction (takes no extra space, and edits to files in the repository take effect immediately):$home = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $env:USERPROFILE '.dsh' } $modules = Join-Path $home 'profiles\web\node_modules' New-Item -ItemType Directory -Force -Path $modules | Out-Null New-Item -ItemType Junction -Path (Join-Path $modules 'dsh-session-search') -Target '<REPO>'A plain directory copy works just as well: all that matters is that the path above shows the contents of this repository.
The profile's
package.json: adddsh-session-searchtodsh.profile.bundles, and add"dsh-session-search": "link:<REPO>"todependencies(write<REPO>with forward slashes).The profile's
cordis.patch.yml: add the line that opens the index (point 1 of "What this plugin does" above).
Rollback
Delete the three places above (the plugin line disappears with the bundle; once the index-opening line is removed, search goes back to "name matches only").
An existing choice lives in the session-search: section of settings.yaml; delete that section too when you no longer want it.
The index file <DSH_HOME>\profiles\web\session-search.db can be deleted directly; the next search rebuilds it.
Test
Zero dependencies, no DSH running and no network needed:
node test.mjs # 汇总入口:语法检查 + 两个验证台 + 文档一致性 + 仓库卫生
node tools/verify-host.cjs # 宿主半边离线验证台
node tools/verify-client.cjs # 客户端半边离线验证台
node test.mjs runs node --check, the two harnesses above, then verifies that the README configuration-table fields match
lib/index.js, that README and README.en.md are structurally aligned, and that the fields and listed files of package.json / LICENSE /
CHANGELOG.md are all in place.
The two harnesses build their own stubs: they do not touch a real session database, do not read the profile, and write no files.
tools/verify-host.cjs(78 checks): filter rewriting, field pass-through, time-range boundaries (including "everything"), content-source combinations, the project-names-only short circuit, old-config compatibility, scope normalization boundaries, settings schema and wiring, exact restoration on unmount, silent skip when the service is absent, prewarm timer registration and cleanup.tools/verify-client.cjs(62 checks): bundle registration and theinjectdeclaration, zerorequire, style tag and dedupe key, the four key style rules, control placement and wording, the three places a scope change lands (local /localStorage/ settings service), the two body-class switches, the settings-ready and settings-unavailable paths, and DOM-change throttling plus restoration on unmount.
These three commands are everything CI runs.
License
MIT. The plugin itself is free to use.
DSH itself and the official full-text index (@deepseek-ai/dsh-session-query-sqlite, @deepseek-ai/dsh-web-app and others)
are upstream projects and follow their own licenses.