Foreword¶
Running long conversations in DSH and checking back for a keyword usually relies on scrolling and manual visual search. dsh-content-search does something very straightforward: it turns already-written logs into searchable content, places a search button in the top-right of the conversation area, brings up a search box in the sidebar, highlights matching words in yellow, and jumps to a result when clicked. The maintainer is appthin, categorized as “Memory,” and the plugin directory page shows 2 stars. It is a community plugin with no official affiliation with DeepSeek or Huanfang.
What is it¶
One-sentence positioning: adds search to the current conversation without generating any new content.
The plugin explicitly states it doesn’t touch the conversation window, doesn’t write session events, doesn’t modify the patch layer, and only does two things—reading and locating. It turns existing logs into searchable ones rather than adding a new layer of structure to the conversation.
Core Features¶
The capabilities listed below all come from the README.
- Search Button: Placed in the top-right of the conversation header (
conversation.session.header.utilities, order 15). If the panel is already open, clicking focuses and expands the sidebar; it won’t open another one. - Sidebar Search Box: Opens as a formal tab in the sidebar (type
contentSearch), and its position is remembered per session. - Two Scopes: “Loaded” refers to the event window currently held, with results appearing instantly on the pure frontend; “Full History” queries the host’s full logs.
- Yellow Highlight: Adds a yellow background to all matching words in the conversation area, treating user messages and assistant replies equally.
- Hit Location: Click result → scroll history to that point → scroll to the line → fade to a pale yellow background, then fade out after 2 seconds.
- Selectable to Copy: Each result is a
div role="button"instead of a<button>, so the text can be selected and copied by dragging. - Expand More Context: When hovering, “Expand More” floats in the bottom-right corner; clicking adds about 300 characters before and after.
- Keyboard:
Enterjumps to the first result,↑/↓moves,Escclears. - Chinese/English Bilingual: Namespace
content-search, follows the interface language.
Two Scopes, The Difference is Real¶
“Loaded” and “Full History” are not two switches for the same feature; their data sources and costs differ.
| Loaded | Full History | |
|---|---|---|
| Data Source | Event window currently held by the conversation | Full event logs from the host |
| Implementation | Direct scan within the browser | GET /api/content-search.search → Host |
| Latency | Instant | One network round-trip |
| Limit | 300 results | 200 per page, cumulative 1000 |
| Modifies Conversation | No | No |
It defaults to “Loaded”. The README explains: the slow option isn’t suitable as a default, otherwise every search incurs a network cost first. Switch to it when full history is needed.
Installation and Enablement¶
Version Requirements: DSH >= 0.1.6-alpha.1 and < 0.3.0; the README notes testing on 0.2.1-rc.5. DSH itself depends on Node.js >= 24.2.0 and Git >= 2.31.0.
The Desktop version is installed within the app because the Desktop profile is exclusively managed by Electron, and dsh plugin --profile desktop will be rejected. There are two ways: via the plugin page “Plugin → Add”, source filled with https://github.com/appthin/dsh-content-search.git; or by asking the assistant in a session to call plugin_manager’s install_bundle, target filled with the absolute path of the current directory.
For web / tui profiles, install via command:
# Local directory (link method, changes take effect immediately, suitable for development/debugging)
dsh plugin --profile web add file:E:/dev/deepseek_harness/dsh-content-search
dsh plugin --profile web ls # View installed
dsh plugin --profile web remove dsh-content-search # Uninstall
After installation, open “Plugin → dsh-content-search”. The card should have a title and description taken from meta in locale/*.json.
After changes in the same directory, remove_bundle + install_bundle allows hot updates without restarting. Note: Changing the plugin directory path requires restarting the Desktop client; otherwise, the host cache still holds the old path, manifesting as “plugin installed, button missing”. The solution is to restart or change the package name.
Uninstall:
dsh plugin --profile web remove dsh-content-search
The plugin does not write profile files, does not install dependencies, and does not modify cordis.patch.yml; removal clears everything.
Typical Usage: Verify in this order after installation¶
The README provides verification steps. First do a syntax check, then interact with the interface:
node --check index.js
node --check client.js
Confirm in the interface in order:
- A magnifying glass button appears in the top-right of the conversation area;
- Click → “Search” tab appears in the sidebar (focuses and expands if it exists);
- Input keyword → matching words in the conversation area turn yellow (both user and assistant);
- Click a result → the conversation scrolls to that position and briefly turns pale yellow;
- Hover over a result → “Expand More” floats in the bottom-right corner → clicking adds about 300 characters before and after, link turns “Collapse”;
- Switch to “Full History” → prompt line shows source of hits, result count changes to “X hits (Full History)”.
How it is implemented¶
The browser half is responsible for the button, panel, highlighting, and location. Highlighting uses the CSS Custom Highlight API (CSS.highlights) and does not touch the conversation’s DOM:
registry.set('dsh-content-search', new Highlight(...ranges))
::highlight(dsh-content-search) { background-color: rgba(255, 206, 0, .55); color: inherit }
::highlight(dsh-content-search-hit) { background-color: rgba(255, 206, 0, .18) }
It doesn’t use <mark> because that would require modifying the DOM of other plugins. Highlighting only covers lines that have already been rendered; collapsed content won’t be lit up out of thin air.
The host side has only one responsibility: read the complete persistent log of the session and expose it to the browser via a read-only route:
GET /api/content-search.search?sessionId=…&query=…&limit=…&cursor=…
Default 100 per page, max 300; the cursor is a decimal offset. Each hit brings back the original text of about 480 characters before and after the keyword. Location is handled by session.loadThrough(hit.seq) to scroll history, then finding the line based on Chat’s rendering attributes (data-chat-node-key, etc.). These attributes are rendering conventions, not a public API. The plugin performs feature detection; if not found, it prompts “Scrolled to the location, but this line is not currently rendered.”
A deliberate trade-off is that “Full History” does not use the host’s full-text index sessionQuery.searchEvents because it can be disabled during deployment (throws SESSION_QUERY_SEARCH_DISABLED when openAt: "never"). The plugin uses sessionQuery.filterEvents without the switch for literal scanning instead. The cost is that the first page of large sessions is slower than indexing, but the reasoning is “it is more important to be searchable than to be fast.”
Use Cases and Notes¶
Suitable for checking back for keywords in long conversations or jumping back to a specific message. It only searches the current session; cross-session search is not supported.
Notes:
- The plugin runs with the current dsh process permissions. Check source code and license before installing.
- Full History is literal scanning; the first page of large sessions will be slow. No fuzzy matching, no word segmentation, no regex support.
- Context window per hit is ±480 characters; the 300-character expand limit is cut from this window.
- Highlighting only covers rendered lines; collapsed content won’t be forced to highlight.
- Brief duplicate lines may appear during streaming replies.
- Location depends on Chat rendering contracts; upstream changes to
data-chat-*attributes will cause degradation. - If the host log is unreadable, the panel displays the host’s original text and falls back to “Load earlier content” to stretch the window (max 2 times, 5 seconds timeout each).
License: MIT, no third-party code, no bundled third-party binaries, no external dependencies.
Conclusion¶
Its value lies in its lightness: it doesn’t add anything to the conversation, only makes existing logs searchable and jumpable. Directory page at https://www.skillhub.cn/plugins/appthin/dsh-content-search, source code at https://github.com/appthin/dsh-content-search.