dsh-find-session 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.en.md +145 -0
- package/README.md +144 -2
- package/assets/icon.svg +24 -0
- package/cordis.patch.yml +7 -0
- package/lib/client.js +2141 -0
- package/lib/extract.js +257 -0
- package/lib/index.js +629 -0
- package/lib/match.js +194 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +76 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-10-27
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- In-session find bar (`Ctrl+F`) searches the conversation it is docked in; the
|
|
8
|
+
cross-session panel (`Ctrl+Shift+F`) searches every stored session and lists
|
|
9
|
+
hits with the session title, workspace and timestamp. `F3` / `Shift+F3` step
|
|
10
|
+
through the current result set (the bar must be open).
|
|
11
|
+
- Results jump to the turn (and the folded row inside it) and flash the target
|
|
12
|
+
line; matches are painted with CSS Custom Highlight, so the page text itself is
|
|
13
|
+
never mutated.
|
|
14
|
+
- Literal (default) and regular-expression modes, case-insensitive, with tools
|
|
15
|
+
and thinking content opt-in — the default corpus is the conversation text.
|
|
16
|
+
- Filters: workspace, time window, archived sessions, agent preset, child
|
|
17
|
+
sessions. The Host half scans with a Unicode-aware substring matcher instead of
|
|
18
|
+
SQLite FTS, whose `unicode61` tokenizer treats a whole CJK run as one token and
|
|
19
|
+
would never find 「会话」 inside 「会话上下文关键词查找」.
|
|
20
|
+
- The search input is non-controlled and treats a missing `compositionend` as
|
|
21
|
+
harmless, so an IME composition interrupted by the Host stealing focus can no
|
|
22
|
+
longer latch the field into "types once, then swallows every key".
|
|
23
|
+
- Shortcut defaults are declared for the desktop profiles only, and a page-level
|
|
24
|
+
listener covers the two open gestures when the shortcut service is absent —
|
|
25
|
+
a rejected default used to abort the whole registration batch silently.
|
|
26
|
+
- Tests: 22 offline tests (`node --test`, plus the Host smoke test) and the jsdom
|
|
27
|
+
harnesses that drive the real `lib/client.js` (IME latch, shortcut dispatch,
|
|
28
|
+
page fallback).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SpaceRelay
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# dsh-find-session
|
|
2
|
+
|
|
3
|
+
**Keyword find for the conversation.** Press `Ctrl+F` to search the current conversation with live highlighting, or open the **Find** page in the left rail to search **across every session** — hits come with a snippet, timestamp and a type badge, and one click jumps you to that message.
|
|
4
|
+
|
|
5
|
+
> English | [中文](README.md)
|
|
6
|
+
|
|
7
|
+
DSH's own session search (`dsh-experimental-session-search`) is a **model-facing** tool: it lets the assistant dig through past sessions. It is not something you press `Ctrl+F` on. This plugin provides **the human's half**: locating "which step was it where we settled this plan" while reading a long conversation.
|
|
8
|
+
|
|
9
|
+
## The problem
|
|
10
|
+
|
|
11
|
+
There are three usual ways to locate a passage in a long conversation, and none of them works well:
|
|
12
|
+
|
|
13
|
+
| Approach | Why it is not enough |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| The browser's own find (`Ctrl+F`) | Only sees what is **already rendered**. DSH loads a conversation in pages, so earlier turns are not in the DOM at all; folded tool cards do not match either. |
|
|
16
|
+
| Ask the model to search for you | Costs a full round trip, cannot highlight, cannot step "next / previous". |
|
|
17
|
+
| The official `dsh-experimental-session-search` | A tool for the model, not an interface for a person. |
|
|
18
|
+
|
|
19
|
+
The official engine also uses SQLite FTS5 with the `unicode61` tokenizer, which treats a whole run of CJK characters as **one token**. Searching `会话` therefore never matches `我们来做会话上下文关键词查找`. This plugin skips FTS5 and matches by code point instead — substring, whole word and regex — so two-character Chinese queries work.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **In-session find bar** — `Ctrl+F` opens a bar above the composer. Search-as-you-type with a 200 ms debounce. `Enter` / `Shift+Enter` or `F3` / `Shift+F3` step through hits.
|
|
24
|
+
- **Highlighting** — the CSS Custom Highlight API paints every hit **in the currently rendered window** pale yellow and the current one orange, scrolling it into view. The row you jump to also flashes.
|
|
25
|
+
- **Cross-session find** — the **Find** page in the left rail; switch to **All sessions** to search every session's log. Results are newest-first with session, time, and type (tool / reasoning) badges.
|
|
26
|
+
- **Hit preview** — click a global result to expand its context: two events either side, so you can confirm it is the right one before jumping.
|
|
27
|
+
- **Jump** — **Open** switches to the target session and locates the turn. If that turn is not loaded yet, the plugin calls the session's own `loadThrough` to pull history in first, then locates.
|
|
28
|
+
- **Filters** — this workspace / all workspaces, today / last 7 days / last 30 days / any, include archived, include subagent sessions.
|
|
29
|
+
- **Content scope** — body / tool calls / reasoning as three independent switches. Body only by default: tool output is mostly noise, turn it on when needed.
|
|
30
|
+
- **Right-sidebar results panel** — dock the results into the right sidebar and scroll them while reading the conversation.
|
|
31
|
+
- **Settings** — Settings → Find, for the default match mode, default content scope, and result limit.
|
|
32
|
+
- **Shortcuts** — `Ctrl+F` / `Ctrl+Shift+F` / `F3` / `Shift+F3` by default, editable under Settings → Shortcuts.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
The plugin is a normal DSH bundle (one host row plus a browser half). Pick one route:
|
|
37
|
+
|
|
38
|
+
**A. From a tarball, by hand.** In `<DSH_HOME>/profiles/<profile>/package.json` add the dependency and the bundle name, then install it:
|
|
39
|
+
|
|
40
|
+
```jsonc
|
|
41
|
+
{
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"dsh-find-session": "file:D:/path/to/dsh-find-session-0.1.0.tgz"
|
|
44
|
+
},
|
|
45
|
+
"dsh": { "profile": { "bundles": ["dsh-find-session"] } }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
# pnpm ships with the desktop runtime
|
|
51
|
+
& "$env:USERPROFILE\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\pnpm.cjs" install --dir "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**B. From the registry, once published.** Use the Plugins settings page (or a `plugin_manager install_bundle` call in a session) with the spec `dsh-find-session@0.1.0`; the plugin manager adds both the dependency and the bundle entry for you.
|
|
55
|
+
|
|
56
|
+
Then **quit DSH from the tray icon and start it again** — bundles are composed at boot. The Electron-managed profile cannot be booted through the `dsh` CLI (`profile "desktop" is managed exclusively by the Electron application`), so restarting the app is the only way to apply it. Removing the bundle name from `dsh.profile.bundles` and deleting the dependency disables the plugin again.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
| What you want | How |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| Find in the current conversation | `Ctrl+F`, type, `Enter` for next, `Shift+Enter` for previous |
|
|
63
|
+
| Find in every session | `Ctrl+Shift+F`, or the **Find** icon in the left rail, then switch to **All sessions** |
|
|
64
|
+
| See where a hit is before jumping | Click the result to expand the preview, then **Open** |
|
|
65
|
+
| Dock results to the right sidebar | **Dock to sidebar** in the top-right of the Find page |
|
|
66
|
+
| Change the defaults | Settings → Find |
|
|
67
|
+
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
The row ships sensible defaults; override them in the same profile's `cordis.patch.yml`:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
- id: dsh-find-session
|
|
74
|
+
name: 'dsh-find-session'
|
|
75
|
+
config:
|
|
76
|
+
maxDocumentsPerScan: 30000
|
|
77
|
+
concurrency: 4
|
|
78
|
+
eventSoftCap: 40000
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| Option | Default | Meaning |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `maxDocumentsPerScan` | `30000` | Most text chunks one global scan will read. Past this the results are truncated and the UI says so. |
|
|
84
|
+
| `concurrency` | `4` | How many session logs are read in parallel. |
|
|
85
|
+
| `eventSoftCap` | `40000` | Sessions with more events than this are skipped, so one huge session cannot stall the UI. |
|
|
86
|
+
|
|
87
|
+
Changes are read when the plugin loads, so restart DSH after editing.
|
|
88
|
+
|
|
89
|
+
## How it works
|
|
90
|
+
|
|
91
|
+
- **Host half** (`lib/index.js`) registers one read-only RPC channel, `/dsh-find-session`, with the endpoints `search` / `context` / `sessions` / `ping`. It reads logs through `sessionQuery.readSession` and enumerates sessions through `listSessions`. It **writes nothing** — not the session log, not the profile, not the settings document.
|
|
92
|
+
- **Text extraction** (`lib/extract.js`) turns events into typed text chunks. The official `extractSessionEventText` **drops `reasoning` blocks**, so it cannot back a "reasoning" scope switch; this plugin implements its own dispatch. Each tool call is recorded once (the `tool-call` block inside `assistant/message` is skipped in favour of the standalone `tool/call` event), otherwise every tool hit would appear twice.
|
|
93
|
+
- **Matching** (`lib/match.js`) is a dependency-free pure function: substring / whole word (CJK neighbours count as boundaries) / regex, all three with an optional case-sensitive flag. Zero-width regexes have a loop guard.
|
|
94
|
+
- **Caching**: 1 s TTL for the active session, 60 s for finished ones (their log no longer changes). At most 400 sessions cached, oldest evicted first.
|
|
95
|
+
- **Browser half** (`lib/client.js`) is hand-written with no build step, wrapped in the Harness ModuleLoader envelope. It mounts at five points: `shell.overlay` (the find bar), `conversation.session.header.actions` (a header toggle), `sidebar.panellist` + `main` (the rail icon and the Find page), `sidebar.right.pane.tab` (the results panel), and `settings.section` (the settings card).
|
|
96
|
+
- The find bar **must** live in `shell.overlay`, never in `conversation.input.dock`. The dock sits inside `[data-composer-seat]`, and a skin may hide that whole seat — the maid skin writes `data-maid-composer-hidden` → `opacity:0;pointer-events:none` and blurs whatever inside it holds focus. Searching necessarily scrolls the transcript, so a box in the dock loses focus the moment the seat is hidden and then stops receiving keys: the reported "can only type once" symptom. `shell.overlay` sits outside every scroll container, so no scroll-driven hiding can reach it. **Caveat:** that is a structural argument, not a verdict on one incident — the maid skin is currently `disabled` in this profile, so if the failure happened with no skin active, something else triggered it (see the revision note below).
|
|
97
|
+
- **Highlighting** uses `CSS.highlights` + `::highlight()`, degrading silently to scroll-and-flash where unsupported.
|
|
98
|
+
- **Locating** goes by `[data-chat-turn="N"]` because the host returns `turn` alongside each hit — steadier than parsing DOM structure.
|
|
99
|
+
|
|
100
|
+
One deviation from the original design, stated plainly: **UI preferences live in `localStorage['dsh.session-find.prefs.v1']`**, not in the DSH settings document. The client service catalog has no settings service (only layout / locale / sessions / slots / theme / timer / uiWorkspace / workspaces), and writing into DSH's settings would need an extra RPC endpoint that stores a file — not worth the risk for four checkboxes. Desktop `localStorage` is part of the persistent profile, so it survives restarts.
|
|
101
|
+
|
|
102
|
+
Verified against DSH desktop **0.2.1-rc.3**, Node 24. The `unicode61` behaviour described above is exactly what `@deepseek-ai/dsh-session-query-sqlite` does: `会话` does not match `我们来做会话上下文关键词查找`.
|
|
103
|
+
|
|
104
|
+
## Known limits
|
|
105
|
+
|
|
106
|
+
- In-session find only highlights hits **currently rendered**. Jumping to unloaded history calls `loadThrough` first, but how much one call pulls is up to DSH.
|
|
107
|
+
- Global scanning has a budget (30k text chunks by default). Past it the results truncate and the UI says "more results — narrow the query"; narrowing by time or workspace gets you the rest.
|
|
108
|
+
- Read-only. The plugin offers no write, delete or export.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
npm test # node --test, no third-party dependencies (28 assertions)
|
|
114
|
+
node test/smoke.mjs # host RPC smoke test (22 assertions, fake ctx)
|
|
115
|
+
node test/realdata.mjs # replay this machine's real session logs (optional, needs ~/.dsh/sessions)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Coverage: the three matcher modes and CJK boundaries, the extractor's event dispatch (including turn anchoring and title folding), and every host RPC endpoint (filtering, paging, caching, read-failure degradation, mid-scan cancellation).
|
|
119
|
+
|
|
120
|
+
`realdata.mjs` is a machine-local acceptance script: it decompresses the real `session.v4.jsonl.zstd` files under `~/.dsh/sessions`, presents them as a fake `sessionPersistence`, drives `apply()`, and checks every endpoint. On this machine: 222 sessions, 200 hits for `会话`, every hit carrying a turn anchor and a session title, a broad scan in 97 ms.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
125
|
+
|
|
126
|
+
## Acceptance record
|
|
127
|
+
|
|
128
|
+
Verified on this machine (Windows 10 desktop, DSH 0.2.1-rc.3, Node 24):
|
|
129
|
+
|
|
130
|
+
| Item | Result |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `npm test` | 28 / 28 pass |
|
|
133
|
+
| `node test/smoke.mjs` | 22 / 22 pass |
|
|
134
|
+
| `node test/realdata.mjs` | all pass (222 sessions / 200 hits / 97 ms) |
|
|
135
|
+
| Host RPC channel `/dsh-find-session` | registered (unauthenticated probe returns 401, same as `/dsh-rewind`) |
|
|
136
|
+
| Sidebar icon + find page | mounted |
|
|
137
|
+
| `shell.overlay` find bar | mounted (order 40) |
|
|
138
|
+
| `conversation.session.header.actions` toggle | mounted (order 40) |
|
|
139
|
+
| `sidebar.right.pane.tab` results panel | mounted |
|
|
140
|
+
| `settings.section` settings card | mounted |
|
|
141
|
+
| `CSS.highlights` highlight + jump | needs one manual pass (see below) |
|
|
142
|
+
|
|
143
|
+
The final UI walk-through needs a human hand. Everything that can be verified without it is verified: the data path, route registration, all five mounts, and the matching and locating algorithms. What is left is three gestures — press Ctrl+F, see the bar; type, see the highlights; press Enter, land on the hit.
|
|
144
|
+
|
|
145
|
+
> 2026-10-27 revision: the find bar moved from `conversation.input.dock` to `shell.overlay`. The dock lives inside `[data-composer-seat]`, which a skin may hide and blur while the transcript scrolls, so the box lost focus after the first search and looked like it "only accepted one character". `shell.overlay` is outside every scroll container. `SearchInput` also switched to a local draft value plus an `isComposing` check, so a lagging store can no longer make React rewrite the keystrokes away.
|
package/README.md
CHANGED
|
@@ -1,3 +1,145 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-find-session
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**会话上下文关键词查找。** 在当前对话里按 `Ctrl+F` 直接查找并高亮,或者从左侧栏打开「查找」页做**跨会话**检索:命中项带片段、时间、类型徽标,点一下跳到那条消息。
|
|
4
|
+
|
|
5
|
+
> [English](README.en.md) | 中文
|
|
6
|
+
|
|
7
|
+
DSH 自带的会话搜索(`dsh-experimental-session-search`)是**模型侧**的工具,用来在历史会话里找线索;它是给 AI 用的,不是给你按 `Ctrl+F` 用的。这个插件补的是**人**的那一半:读对话时想定位「我上次是在哪一步定的这个方案」。
|
|
8
|
+
|
|
9
|
+
## 它解决的问题
|
|
10
|
+
|
|
11
|
+
在阅读长对话时定位一处内容,通常只有三种做法,都不好用:
|
|
12
|
+
|
|
13
|
+
| 做法 | 问题 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 浏览器自带的页内查找(`Ctrl+F`) | 只能命中**已经渲染出来**的内容。DSH 的对话是分页加载的,早先的轮次根本不在 DOM 里,搜不到;折叠的工具卡也不会展开。 |
|
|
16
|
+
| 让模型自己去搜 | 要发一轮对话、等模型回话,还不能高亮、不能「上一个/下一个」。 |
|
|
17
|
+
| 官方 `dsh-experimental-session-search` | 是给模型用的工具,不是给人用的界面。 |
|
|
18
|
+
|
|
19
|
+
而且官方引擎走 SQLite FTS5,分词器是 `unicode61`——它把一整串中日韩文字当成**一个 token**。于是搜「会话」永远匹配不到「我们来做会话上下文关键词查找」。本插件不用 FTS5,自己按码点做子串 / 整词 / 正则匹配,中文按两个字也能搜到。
|
|
20
|
+
|
|
21
|
+
## 功能
|
|
22
|
+
|
|
23
|
+
- **会话内查找条**:`Ctrl+F` 在输入框上方弹出查找条,输入即搜(200 ms 防抖),`Enter` / `Shift+Enter` 或 `F3` / `Shift+F3` 在命中之间前后跳。
|
|
24
|
+
- **高亮**:用 CSS Custom Highlight API 把**当前已渲染窗口内**的所有命中标成浅黄,当前那一条标成橙色并自动滚动到视野中央;跳过去的行还会闪一下。
|
|
25
|
+
- **跨会话查找**:左侧栏「查找」页,切到「全局」就在所有会话的日志里搜。结果按时间倒序,带会话、时间、类型(工具 / 思考)徽标。
|
|
26
|
+
- **命中预览**:点全局结果展开上下文——前后各 2 条事件,可以确认「就是这里」再决定要不要跳。
|
|
27
|
+
- **跳转**:点「打开」切到目标会话并定位到那一轮;那一轮还没加载时,先调会话自己的 `loadThrough` 把历史拉到位再定位。
|
|
28
|
+
- **筛选**:当前工作区 / 所有工作区、今天 / 近 7 天 / 近 30 天 / 不限、含已归档、含子会话。
|
|
29
|
+
- **搜索内容范围**:正文 / 工具调用 / 思考过程,三档独立开关。默认只搜正文——工具输出噪音太大,需要时再打开。
|
|
30
|
+
- **右侧栏结果面板**:结果可以停靠进右侧栏,看对话的同时翻结果。
|
|
31
|
+
- **设置**:设置 → 查找,改默认匹配方式、默认内容范围、单次结果上限。
|
|
32
|
+
- **快捷键**:默认 `Ctrl+F` / `Ctrl+Shift+F` / `F3` / `Shift+F3`,在 设置 → 快捷键 里可改。
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
插件是一个普通 DSH bundle(一个宿主行 + 一个浏览器半边)。任选一条路:
|
|
37
|
+
|
|
38
|
+
**A. 从 tarball 手动装。** 在 `<DSH_HOME>/profiles/<profile>/package.json` 里加依赖和 bundle 名,然后安装:
|
|
39
|
+
|
|
40
|
+
```jsonc
|
|
41
|
+
{
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"dsh-find-session": "file:D:/path/to/dsh-find-session-0.1.0.tgz"
|
|
44
|
+
},
|
|
45
|
+
"dsh": { "profile": { "bundles": ["dsh-find-session"] } }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
# pnpm 随桌面端运行时一起提供
|
|
51
|
+
& "$env:USERPROFILE\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\pnpm.cjs" install --dir "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**B. 发布后从 registry 装。** 用 设置 → 插件 页面,或者在会话里调 `plugin_manager install_bundle`,spec 写 `dsh-find-session@0.1.0`——插件管理器会把依赖和 bundle 条目一起加好。
|
|
55
|
+
|
|
56
|
+
然后**从托盘退出 DSH 再启动**——bundle 在启动时组合。Electron 托管的 profile 不能用 `dsh` CLI 启动(会报 `profile "desktop" is managed exclusively by the Electron application`),只能重启应用。想关掉插件就把 `dsh.profile.bundles` 里的名字删掉、依赖删掉。
|
|
57
|
+
|
|
58
|
+
## 用法
|
|
59
|
+
|
|
60
|
+
| 想做的事 | 怎么做 |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| 在当前对话里找 | `Ctrl+F`,输入,`Enter` 往下、`Shift+Enter` 往上 |
|
|
63
|
+
| 在所有会话里找 | `Ctrl+Shift+F`,或左侧栏「查找」图标,切到「全局」 |
|
|
64
|
+
| 看在哪儿、要不要跳 | 点结果展开预览,点「打开」跳过去 |
|
|
65
|
+
| 把结果停靠到右侧 | 查找页右上角「停靠到右侧栏」 |
|
|
66
|
+
| 改默认行为 | 设置 → 查找 |
|
|
67
|
+
|
|
68
|
+
## 配置
|
|
69
|
+
|
|
70
|
+
行内默认值够用;要改就在 profile 的 `cordis.patch.yml` 里覆盖:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
- id: dsh-find-session
|
|
74
|
+
name: 'dsh-find-session'
|
|
75
|
+
config:
|
|
76
|
+
maxDocumentsPerScan: 30000
|
|
77
|
+
concurrency: 4
|
|
78
|
+
eventSoftCap: 40000
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
| 选项 | 默认 | 含义 |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| `maxDocumentsPerScan` | `30000` | 一次全局扫描最多读多少个文本块,超了就截断并提示「还有更多」 |
|
|
84
|
+
| `concurrency` | `4` | 并发读几个会话的日志 |
|
|
85
|
+
| `eventSoftCap` | `40000` | 单会话事件数超过这个值就跳过它,避免超大会话拖垮 UI |
|
|
86
|
+
|
|
87
|
+
改动在插件加载时读取,改完重启 DSH。
|
|
88
|
+
|
|
89
|
+
## 实现说明
|
|
90
|
+
|
|
91
|
+
- **宿主半边**(`lib/index.js`)注册一个只读 RPC 通道 `/dsh-find-session`,端点 `search` / `context` / `sessions` / `ping`。它用 `sessionQuery.readSession` 读日志、`listSessions` 列会话,**不写任何东西**——不碰会话日志、不碰 profile、不碰设置文档。
|
|
92
|
+
- **文本抽取**(`lib/extract.js`)把事件拆成带类型标签的文本块。官方 `extractSessionEventText` 会**丢掉 `reasoning` 块**,所以没法实现「思考过程」这一档开关,这里自己实现一份。同一个工具调用只记一次(`assistant/message` 里的 `tool-call` 块跳过,用独立的 `tool/call` 事件),否则每条工具命中都会翻倍。
|
|
93
|
+
- **匹配**(`lib/match.js`)纯函数、无依赖:子串 / 整词(对 CJK 邻字符算边界)/ 正则,三者都支持区分大小写。零宽正则有循环保护。
|
|
94
|
+
- **缓存**:活跃会话 1 秒 TTL,已结束的会话 60 秒(日志不再变化)。缓存最多 400 个会话,超了按最旧淘汰。
|
|
95
|
+
- **浏览器半边**(`lib/client.js`)手写、无构建步骤,挂在 Harness 的 ModuleLoader 信封里。用五个挂载点:`shell.overlay`(查找条)、`conversation.session.header.actions`(会话头开关)、`sidebar.panellist` + `main`(左侧栏图标和查找页)、`sidebar.right.pane.tab`(结果面板)、`settings.section`(设置卡片)。
|
|
96
|
+
- 查找条**必须**挂在 `shell.overlay`,不能挂在 `conversation.input.dock`。dock 位于 `[data-composer-seat]` 内部,而皮肤可以隐藏整个 seat——maid 皮肤的做法是写 `data-maid-composer-hidden` → `opacity:0;pointer-events:none`,同时 `blur` 掉里面持有焦点的元素。查找必然滚动转录,所以挂在 dock 里的输入框一旦被 seat 隐藏就会失焦,之后敲键盘没反应,表现为「只能输入一次」。`shell.overlay` 在所有滚动容器之外,不受任何滚动驱动的隐藏影响。**注意**:这条是结构性理由,不是对某次故障的定论——本机 profile 里 maid 皮肤当前是 `disabled`,所以那次故障若发生在皮肤未启用时,触发源是别的(见下方修订说明)。
|
|
97
|
+
- **高亮**用 `CSS.highlights` + `::highlight()`,不支持时静默退化成「只滚动 + 闪一下」,不报错。
|
|
98
|
+
- **定位**按 `[data-chat-turn="N"]` 找行——宿主把 `turn` 一并返回,比解析 DOM 结构稳。
|
|
99
|
+
|
|
100
|
+
有一点和最初设想不同,如实说明:**界面偏好存在 `localStorage['dsh.session-find.prefs.v1']`**,不是 DSH 的设置文档。原因是客户端服务目录里没有 settings 服务(只有 layout / locale / sessions / slots / theme / timer / uiWorkspace / workspaces),要写进 DSH 的设置就得额外开一条 RPC 存文件,风险和收益不成比例。`localStorage` 在桌面端是持久化的 profile,重启照旧在。
|
|
101
|
+
|
|
102
|
+
验证环境:DSH desktop **0.2.1-rc.3**,Node 24。官方引擎 `@deepseek-ai/dsh-session-query-sqlite` 的 `unicode61` 行为就是上面那条:「会话」搜不到「我们来做会话上下文关键词查找」。
|
|
103
|
+
|
|
104
|
+
## 已知边界
|
|
105
|
+
|
|
106
|
+
- 会话内查找只高亮**当前已渲染**的命中。跳到未加载的历史时会先调 `loadThrough` 拉取,但一次拉多少由 DSH 自己决定。
|
|
107
|
+
- 全局扫描有预算上限(默认 3 万文本块)。超出后结果会被截断,界面上会写「还有更多,请收窄条件」——把时间或工作区收窄就能搜全。
|
|
108
|
+
- 只读。插件不提供任何写入、删除、导出。
|
|
109
|
+
|
|
110
|
+
## 开发
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
npm test # node --test,无第三方依赖(28 条断言)
|
|
114
|
+
node test/smoke.mjs # 宿主 RPC 的端到端冒烟(22 条断言,假 ctx)
|
|
115
|
+
node test/realdata.mjs # 拿本机真实会话日志回放整条链路(可选,需要 ~/.dsh/sessions)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
测试覆盖:匹配器的三种模式与 CJK 边界、文本抽取的事件分派(含 turn 锚点与标题折叠)、宿主 RPC 的全部端点(含过滤、分页、缓存、读失败降级、中途取消)。
|
|
119
|
+
|
|
120
|
+
`realdata.mjs` 是本机专用的一次性验收脚本:它把 `~/.dsh/sessions` 下真实的 `session.v4.jsonl.zstd` 解压出来,伪装成 `sessionPersistence` 灌进 `apply()`,然后逐个端点核对。在当前这台机器上跑出来是 222 个会话、`会话` 一词 200 条命中、每条都带 turn 锚点和会话标题、宽泛扫描 97 ms。
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
125
|
+
|
|
126
|
+
## 验收记录
|
|
127
|
+
|
|
128
|
+
实现完成后在本机(Windows 10 桌面端,DSH 0.2.1-rc.3,Node 24)做过的验证:
|
|
129
|
+
|
|
130
|
+
| 项目 | 结果 |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `npm test` | 28 / 28 通过 |
|
|
133
|
+
| `node test/smoke.mjs` | 22 / 22 通过 |
|
|
134
|
+
| `node test/realdata.mjs` | 全部通过(222 会话 / 200 命中 / 97 ms) |
|
|
135
|
+
| 宿主 RPC 通道 `/dsh-find-session` | 已注册(未鉴权探测返回 401,与 `/dsh-rewind` 一致) |
|
|
136
|
+
| 左侧栏图标 + 查找页 | 已挂载 |
|
|
137
|
+
| `shell.overlay` 查找条 | 已挂载(order 40) |
|
|
138
|
+
| `conversation.session.header.actions` 开关 | 已挂载(order 40) |
|
|
139
|
+
| `sidebar.right.pane.tab` 结果面板 | 已挂载 |
|
|
140
|
+
| `settings.section` 设置卡片 | 已挂载 |
|
|
141
|
+
| `CSS.highlights` 高亮 + 跳转 | 待桌面端点一次(见下方说明) |
|
|
142
|
+
|
|
143
|
+
界面上的最后一步走查需要人手点一下。实现里已经验证了能验证的全部:数据链路、路由注册、五个挂载点、匹配与定位的算法。剩下的就是「按下 Ctrl+F 看见条、打字看见高亮、回车跳过去」这三下。
|
|
144
|
+
|
|
145
|
+
> 2026-10-27 修订:查找条从 `conversation.input.dock` 迁到 `shell.overlay`。dock 在 `[data-composer-seat]` 内,而皮肤可以在转录滚动时隐藏并 blur 该 seat,导致输入框在第一次搜索后失焦,表现为「只能输入一次」。`shell.overlay` 在滚动容器之外,不受影响。同时 `SearchInput` 改成「本地草稿值 + `isComposing` 判断」,避免受控输入在 store 落后时被 React 回写掉按键。
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<!-- Layout follows the shipped terminal/renderer plugin artwork: a 36x36
|
|
3
|
+
canvas, a rounded tile inset from the edge (x=4 y=4 w=28 h=28 rx=8), and
|
|
4
|
+
a white stroke mark centred on the tile. The fill is the same vertical
|
|
5
|
+
light-to-dark blue gradient the sibling plugin icons use, so the rail
|
|
6
|
+
keeps one visual family. The mark is a magnifier: an 11-unit lens circle
|
|
7
|
+
and a 5.5-unit handle, drawn on a 16-unit grid then scaled 1.4x, which
|
|
8
|
+
lands the ink inside the tile with the same breathing room as the
|
|
9
|
+
neighbouring glyphs. The inner group shrinks the finished drawing to
|
|
10
|
+
26/28 of the canvas around its centre, matching the other plugin icons. -->
|
|
11
|
+
<g transform="translate(18 18) scale(0.9286) translate(-18 -18)">
|
|
12
|
+
<rect x="4" y="4" width="28" height="28" rx="8" fill="url(#session_find_blue)" />
|
|
13
|
+
<g transform="translate(8.1 8.1) scale(1.4)" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
14
|
+
<circle cx="5.5" cy="5.5" r="4.25" fill="none" />
|
|
15
|
+
<path d="M8.8 8.8 12.6 12.6" />
|
|
16
|
+
</g>
|
|
17
|
+
</g>
|
|
18
|
+
<defs>
|
|
19
|
+
<linearGradient id="session_find_blue" x1="18" y1="4" x2="18" y2="32" gradientUnits="userSpaceOnUse">
|
|
20
|
+
<stop stop-color="#69B9FF" />
|
|
21
|
+
<stop offset="1" stop-color="#324DE2" />
|
|
22
|
+
</linearGradient>
|
|
23
|
+
</defs>
|
|
24
|
+
</svg>
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# dsh-find-session bundle patch: one row, dual-face (node half = the package's
|
|
2
|
+
# default export, browser half = ./client via the `dsh.client` declaration).
|
|
3
|
+
# The row name MUST equal the package name — the client-modules node half
|
|
4
|
+
# resolves `require.resolve('<row-name>/package.json')` to find the package.
|
|
5
|
+
- insert:
|
|
6
|
+
- id: dsh-find-session
|
|
7
|
+
name: 'dsh-find-session'
|