dsh-find-in-session 0.0.0-stage → 0.1.5

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 ADDED
@@ -0,0 +1,88 @@
1
+ # Changelog
2
+
3
+ ## 0.1.5 — 2026-11-15
4
+
5
+ - Fix: the “located the event, but its text is not rendered yet” message could
6
+ stay on screen forever and ended up covering the composer. It was a
7
+ `position:fixed` overlay appended to `document.body`, and `showToast()` created
8
+ a fresh `<div>` per message while keeping one shared `setTimeout` id — a second
9
+ message inside the 2.2 s window cancelled the *first* element's dismissal, and
10
+ nothing else (closing the find bar, unloading the plugin) ever removed it. No
11
+ bookkeeping and no cap meant every failed jump could leave one more overlay
12
+ behind, all at `bottom:96px`, i.e. right on the message input box.
13
+ - The message is now a single status line inside the find panel
14
+ (`role="status"`, `aria-live="polite"`), directly above the result list: one
15
+ element, one timer, cleared by a new query, by a scope change, by `Esc`/close
16
+ and by plugin teardown. It cannot overlap the composer because it is not an
17
+ overlay at all.
18
+ - The hit whose jump failed also keeps a `未渲染` / `not rendered` marker in its
19
+ own row, so the reason survives the notice fading.
20
+ - Jump locating no longer claims “not rendered” for text that is on screen but
21
+ split across inline nodes (`code` / `strong` / links): the snippet is matched
22
+ against the squeezed (whitespace-free, case-folded) text of each event block
23
+ (`[data-chat-node-key]`), longest prefix first, then that block is scrolled to
24
+ and flashed. Blocks are cached and invalidated by the transcript mutation
25
+ observer, so paging does not re-walk the transcript on every poll.
26
+
27
+ ## 0.1.4 — 2026-11-01
28
+
29
+ - Replaced the single-glyph `当 / 全` scope button with a labelled segmented
30
+ control on its own row under the bar: `范围 [仅当前视图] [含已替换]`. The bar now
31
+ only carries fixed-size controls, so no text can be squeezed inside it, and the
32
+ wording explains itself instead of relying on a tooltip.
33
+ - Each segment carries its own tooltip that matches its label, and the control is
34
+ a `role="radiogroup"` with `aria-checked`, so the state is exposed to assistive
35
+ technology as well.
36
+ - Result header no longer repeats an empty state: with zero history hits the
37
+ header itself reads `全部历史中没有命中`; the count slot shows a short `无命中`
38
+ instead of a full sentence.
39
+ - Screenshot of the verified layout: [docs/panel.png](docs/panel.png).
40
+
41
+ ## 0.1.3 — 2026-11-01
42
+
43
+ - Scope toggle moved out of the text-button shape entirely: the bar now carries a
44
+ fixed `26x26` button with a single CJK glyph (`当` / `全`, English `C` / `A`), and
45
+ the readable wording (`仅当前视图` / `含已替换`) is a chip in the results header,
46
+ where the row has width to spare. A single character in a fixed box cannot wrap,
47
+ shrink, or clip regardless of host CSS.
48
+ - Measured in the real GUI at a 504px viewport: button `26x26`,
49
+ `scrollWidth == clientWidth`, bar has no overflow, header chip `66x17` unclipped;
50
+ toggling flips glyph and chip together.
51
+
52
+ ## 0.1.2 — 2026-11-01
53
+
54
+ - Fix the scope toggle label being clipped (`当前范围` / `含已替换` rendered
55
+ incomplete). The text button is now `flex: 0 0 auto` with
56
+ `min-width: max-content`, `white-space: nowrap`, `overflow: visible` and an
57
+ explicit `line-height`, so no host reset or tight bar can squeeze it; the count
58
+ label is `nowrap` too.
59
+ - Below a 330px viewport the scope button falls back to a two-character label
60
+ (`当前` / `全部`, tooltip keeps the full explanation), and a runtime box
61
+ measurement drops to that form if any host rule still squeezes the label.
62
+ Measured in the real GUI at 1264 / 380 / 320px: `scrollWidth == clientWidth`
63
+ and the bar never overflows.
64
+ - The panel re-renders on window resize so the label form tracks the width.
65
+
66
+ ## 0.1.1 — 2026-11-01
67
+
68
+ - Default search scope is `surface: current` (what the transcript renders); the
69
+ panel's scope button switches to `含已替换 / all events` for replaced or
70
+ compacted history. Measured on a 4.28 MB session: 143 hits / 108 ms instead of
71
+ 724 hits / 131 ms, with the first hit actually locatable in the view.
72
+ - `/ping` is now a self-diagnosis endpoint: `ready` plus
73
+ `client.{registered,path,bootListed,activated,hello,opens,lastOpen}`.
74
+ - The browser half beacons once on activation and once per find-bar open
75
+ (`POST /dsh-find-in-session/client-hello`), so "Ctrl+F does nothing" can be
76
+ narrowed to boot-graph / page-load / activation / shortcut without DevTools.
77
+ - `apply()` and both slot registrations are individually guarded: a failure can
78
+ no longer take down the client plugin group.
79
+ - Dropped `dsh.client.inject` (only baseline `react` is required, matching
80
+ `dsh-widgets` / `dsh-quick-ask`).
81
+ - Added `scripts/ui-probe/`: dependency-free CDP harness that verifies the find
82
+ bar, highlights and the full-history jump in a real Chromium.
83
+
84
+ ## 0.1.0 — 2026-11-01
85
+
86
+ - First cut: host `/dsh-find-in-session/{ping,search}` over
87
+ `ctx.sessionQuery.filterEvents`, Ctrl+F find bar with CSS Custom Highlight
88
+ painting, debounced full-history list, `loadThrough(seq)` paging jump.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abraham
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.md CHANGED
@@ -1,3 +1,151 @@
1
- # Temporary Holding Version
1
+ # dsh-find-in-session
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **DeepSeek Harness 会话内搜索插件 —— Ctrl+F 查找条 + 全历史检索 + 点击跳到那条消息。**
4
+
5
+ DSH 桌面端(Electron 外壳)没有接 `findInPage`,所以长会话里按 `Ctrl+F` 没有任何反应,只能靠肉眼滚。这个插件把「在会话中查找」补回来,两段式工作:
6
+
7
+ 1. **即时命中** —— 扫描已渲染的转写文本节点,用 CSS Custom Highlight API 高亮全部命中(不改 DOM,React 重渲染与流式输出都不受影响),`Enter` / `Shift+Enter` 逐条跳转。
8
+ 2. **全历史命中** —— 去抖后调用宿主路由 `/dsh-find-in-session/search`,由 `ctx.sessionQuery.filterEvents()` 扫描**整段事件日志**(含尚未翻页加载的早期历史),列出带时间与角色的命中片段;点一条就用会话自己的 `loadThrough(seq)` 翻页到那条事件,再滚动并闪一下对应正文。
9
+
10
+ ![查找条](docs/panel.png)
11
+
12
+ ## 安装
13
+
14
+ 从 npm:
15
+
16
+ ```sh
17
+ dsh plugin --profile desktop add dsh-find-in-session
18
+ # Web 端:dsh plugin --profile web add dsh-find-in-session
19
+ ```
20
+
21
+ 直接从仓库:
22
+
23
+ ```sh
24
+ dsh plugin --profile desktop add github:<owner>/dsh-find-in-session
25
+ ```
26
+
27
+ 装完**完全退出并重启** DSH Desktop —— 浏览器半需要重新载入模块清单。
28
+
29
+ <details>
30
+ <summary>从源码本地安装(插件开发用)</summary>
31
+
32
+ ```powershell
33
+ pwsh -File .\scripts\install.ps1 # 以 link: 方式装进 desktop profile;应用需先退出,或加 -Force
34
+ pwsh -File .\scripts\install.ps1 -Uninstall
35
+ ```
36
+
37
+ 脚本只做两件事:`pnpm add link:<插件目录>`,再把包名追加到 `<profile>/package.json` 的 `dsh.profile.bundles`。
38
+
39
+ </details>
40
+
41
+ ## 自检
42
+
43
+ ```powershell
44
+ # 端口取当前 GUI 地址(桌面端窗口 URL / 设置里可见)
45
+ curl http://127.0.0.1:<port>/dsh-find-in-session/ping
46
+ # {"ok":true,"plugin":"dsh-find-in-session","ready":true,
47
+ # "client":{"registered":true,"path":".../lib/client.js","bootListed":true,
48
+ # "activated":true,"hello":{...},"opens":1,"lastOpen":{...}}}
49
+ ```
50
+
51
+ | 字段 | 含义 |
52
+ |---|---|
53
+ | `ready` | 本 profile 的 `ctx.sessionQuery.filterEvents` 可用(出厂组合都可用;`openAt: never` 也能读,只是不做 FTS 排序) |
54
+ | `client.registered` | 模块系统解析到了本包的 `./client` 产物 |
55
+ | `client.bootListed` | 启动图(boot graph)里已列出本包 —— 页面重开/刷新即可执行 |
56
+ | `client.activated` | 浏览器半**真的执行了 `apply()`**:激活时 POST 一次 `/dsh-find-in-session/client-hello`,宿主记录时间、URL、UA、Highlight 支持情况;`client.hello` 就是那份报文 |
57
+ | `client.opens` / `client.lastOpen` | 查找条被打开的次数与最近一次的触发方式(`shortcut` / `button`) |
58
+ | `client.error` | 模块系统查询抛错时的信息 |
59
+
60
+ 这四个 `client.*` 字段合起来,能把「Ctrl+F 没反应」定位到唯一一段:启动图没收录 / 页面没加载 / 加载了但激活失败 / 激活了但快捷键被吞。
61
+
62
+ ## 使用
63
+
64
+ | 操作 | 效果 |
65
+ |---|---|
66
+ | `Ctrl+F`(macOS `Cmd+F`) | 打开/聚焦查找条(会话头部也有放大镜按钮) |
67
+ | 输入关键词 | 即时高亮当前视图全部命中,下方异步出现「全部历史命中」列表 |
68
+ | `Enter` / `Shift+Enter` | 下一处 / 上一处(无视图内命中时在全历史命中间跳) |
69
+ | 点击历史命中 | 翻页加载到该事件、滚动定位并闪烁;这条命中在视图里确实没有正文时,该行标注「未渲染」,查找条里再出现一行提示(提示在面板内,不会浮到输入框上) |
70
+ | `范围 [仅当前视图] [含已替换]` | `仅当前视图` = 只搜转写里显示的正文(`surface: current`,默认);`含已替换` = 连被改写、压缩掉的旧事件一起搜。两段各带匹配的 tooltip |
71
+ | `Esc` | 关闭查找条并清除高亮 |
72
+
73
+ 实测(4.28 MB 会话,查 `error`):默认范围 108 ms / 143 处,首条命中是可见的 `assistant/message`;改成「含已替换」则 131 ms / 724 处,首条是 `shadowed` 的 `tool/result`(视图里根本不显示,点了也定位不到)—— 这就是默认收窄范围的原因。
74
+
75
+ ## 兼容性
76
+
77
+ - `dsh.engines.dsh`:`>=0.1.7-rc.1`(宽区间,与生态里其它插件一致)
78
+ - 实测宿主:`0.2.1-rc.5`(桌面端 Electron 44 / Chrome 152)
79
+ - **降级行为**:若宿主没有 `ctx.sessionQuery.filterEvents`,`/search` 返回 `503 unavailable`,客户端自动退化为「只搜已渲染转写」,不会崩、不会拦启动。
80
+
81
+ ## 设计取舍
82
+
83
+ - **不改宿主、不加依赖**:全历史扫描走 `filterEvents()`,它是 provider-independent 的字面量扫描(`text` 子句大小写不敏感、空白弹性),所以**不需要**挂 `dsh-session-query-sqlite` 的 FTS、不需要建索引库,也不与官方 `dsh-experimental-session-search` 抢服务。
84
+ - **高亮用 CSS Custom Highlight API**:不给文本节点套 `<mark>`,避免与 React 重渲染、流式增量、代码块复制打架;宿主不支持该 API 时退化为「只滚动不染色」。
85
+ - **定位靠文本而不是事件键**:DSH 的 Chat 没有公开「按事件跳转」的 API,但 DOM 上有 `[data-chat-flow]` / `[data-chat-node-key]`。插件先在单个文本节点里匹配命中片段里最长的词,再把片段的**去空白**形式和每个事件块(`[data-chat-node-key]`)比对一次——转写是 markdown 渲染,同一句话会被行内 `code` / `strong` / 链接切成多个文本节点,只按单节点匹配会把屏幕上的文字误判成「未渲染」。块文本按 DOM 变更缓存,配合 `loadThrough(seq)` 翻页;找不到时再点转写自己的「加载更早」,最多 14 轮。
86
+ - **提示不浮在页面上**:定位失败只写面板里的一行状态栏(`role="status"`,6 秒自清,换词/换范围/`Esc`/卸载都会清掉),并在该条命中行留一个「未渲染」小标。0.1.4 及以前是往 `document.body` 上挂 `position:fixed` 的浮层——每个提示新建一个节点、共用一个定时器,2.2 秒内的第二条会把第一条的消失定时器清掉,于是第一条永久停在 `bottom:96px`,正好压住消息输入框。
87
+ - **只在本机可用**:宿主路由与其它 DSH 本机路由一致,做回环 socket + 回环 Host 头 + 同源三道校验,隧道/局域网客户端一律 403。
88
+ - **条栏里不放多字文本**:查找条只承载输入框、计数、`↑`/`↓`/`✕` 这类定尺寸控件;「范围」放在条下方独立一行,靠 flex-wrap 换行而不是挤压。
89
+
90
+ ## 验证记录
91
+
92
+ 在本机 `0.2.1-rc.5` 上实测(2026-11-01;2026-11-15 的提示浮层修复重测了 `version=0.1.5` 与单元测试数):
93
+
94
+ | 检查 | 结果 |
95
+ |---|---|
96
+ | 桌面端真机 | `activated=true`、`version=0.1.5`、`highlight=true`、`opens=2 via=button` |
97
+ | 宿主路由存活 | `GET /dsh-find-in-session/ping` → `{"ok":true,"ready":true}` |
98
+ | 真实会话检索 | 本会话搜 `findInPage` → `total=8`,命中带 `seq / type / surface / snippet`(`surface` 能区分 `current` 与 `log-only`) |
99
+ | 浏览器半被收录 | 另起 `dsh web`(临时挂载后已卸载):`client.registered=true`、`client.bootListed=true` |
100
+ | 浏览器半真的下发 | 页面启动图里出现预载行 `plugins/??...,dsh-find-in-session/client.js,...&rev=...`;抓该 combo 路由 → `200 text/javascript`,内容含 `dsh-find-in-session-anchor`、`conversation.input.overlay` 与中文文案 |
101
+ | 改文件后自动重算 | 改 `lib/client.js` → 图里的 `rev` 按 mtime 变化并推给页面 |
102
+ | 真实 Chromium 激活 | 无头 Chrome 156:`activated=true`、`hello.userAgent=HeadlessChrome/156`、`highlight=true`;合成 `ctrl+f` → 面板 `data-open=true`、焦点在输入框、宿主 `opens=1 via=shortcut` |
103
+ | 高亮与逐条跳转 | 注入 2 处关键词的转写:计数 `1/2`、`CSS.highlights` 里 2 个 Range、上下切换 `1/2 -> 2/2 -> 1/2` 回绕 |
104
+ | 全历史命中 + 翻页定位 | 打开真实会话(转写 2,282 字符)搜 `chrome`:视图内 `1/9`、宿主回报 19 处;点第 1 条 → 转写涨到 8,927 字符(`loadThrough` 生效)、滚动 `0 -> 5847`;点最后一条 → 该行 `data-active=true` |
105
+ | 面板布局 | 504px 视口下:面板 442×106,两段按钮 `75×21` / `64×21`,`scrollWidth == clientWidth`,面板无横向溢出 |
106
+ | 单元测试 | `node --test --test-isolation=none "test/*.test.js"` → 24 pass |
107
+
108
+ 浏览器侧验证可复现:起一个临时 `dsh web`(把本插件临时挂到某个 profile),用无头 Chrome 打开它,再跑:
109
+
110
+ ```powershell
111
+ pwsh -ExecutionPolicy Bypass -File .\scripts\ui-probe\run-fixture-check.ps1 -PageWs ws://127.0.0.1:<debug-port>/devtools/page/<id>
112
+ pwsh -ExecutionPolicy Bypass -File .\scripts\ui-probe\run-jump-check.ps1 -PageWs <同上> -PingUrl http://127.0.0.1:<port>/dsh-find-in-session/ping
113
+ ```
114
+
115
+ `scripts/ui-probe/` 是一个零依赖的最小 CDP 客户端(`cdp.ps1`)+ 若干被测页面片段(`step-search.js` 触发 `ctrl+f` 并输入、`step-read.js` 读回面板状态、`open-session.js` 打开真实会话),不引入 Playwright/Puppeteer。
116
+
117
+ ## 开发注意
118
+
119
+ 1. `lib/client.js` 刻意保持**纯 ASCII**(中文文案写成 `\uXXXX` 转义)。它曾被 PowerShell 5.1 的 `Get-Content -Raw` + `Set-Content`(按 ANSI 读 UTF-8)往返一次,中文与 `↑ ↓ ✕ …` 全部变乱码并导致 `SyntaxError`。纯 ASCII 源码对任何编辑路径免疫。
120
+ 2. **CSS 模板字符串里不能出现反引号**:在 CSS 注释里给 button 加反引号会截断模板串,插件随之激活失败(`/ping` 里会看到 `activated:false`)。这个坑踩过两次。
121
+ 3. 宿主插件代码**不热重载**:改 `lib/index.js` 必须重启应用;`lib/client.js` 也要重新载入页面。模块系统会按 mtime 重算 `rev` 并推给页面,但旧宿主进程里的路由仍是旧代码。
122
+
123
+ ## 已知限制
124
+
125
+ - 全历史检索是**字面量短语**匹配,不是 FTS5 排序检索;结果按事件顺序,不按相关度。
126
+ - 命中上限 50 条(路由接受 `limit` 至 200),超出时列表标「仅显示前若干条」。
127
+ - 折叠(fold)状态下的行仍可被命中并展开父级折叠,但不会自动展开每一层嵌套折叠。
128
+ - 侧边栏搜索的行为不受影响(本插件只服务会话内查找)。
129
+ - 依赖 `ctx.sessionQuery.filterEvents` 与客户端 `session.loadThrough`;缺失时的降级行为见「兼容性」。
130
+
131
+ ## 测试
132
+
133
+ ```powershell
134
+ node --test --test-isolation=none "test/*.test.js" # 24 pass(DSH 沙箱内必须带 --test-isolation=none)
135
+ node --test "test/*.test.js" # 普通环境
136
+ ```
137
+
138
+ 零依赖:覆盖字面量编译、片段窗口、上限与 surface 过滤,宿主路由的守卫/校验/错误码/正常返回,浏览器半在 stub DOM 下的 factory → exports → apply → 两处 slot 注册 + 激活探针,以及 `test/client-jump.test.js` 里用假 DOM 走完整条「Ctrl+F → 输入 → 全历史命中 → 点击」路径:定位失败只留面板内的一行状态(不再往 `document.body` 挂浮层)、该命中行带「未渲染」标、换词/`Esc`/卸载都会清掉,且跨行内节点的片段能被定位到事件块。
139
+
140
+ ## 文件
141
+
142
+ | 路径 | 作用 |
143
+ |---|---|
144
+ | `lib/index.js` | 宿主半:`/dsh-find-in-session/{ping,search,client-hello}`,三道本机校验,调用 `ctx.sessionQuery.filterEvents()` 并拼装片段 |
145
+ | `lib/text.js` | 与宿主 session-query 语义一致的字面量编译与片段构造(纯函数) |
146
+ | `lib/client.js` | 浏览器半:Ctrl+F 查找条、Highlight 染色、全历史列表、`loadThrough` 翻页定位、自检探针 |
147
+ | `cordis.patch.yml` | 插入宿主插件行 |
148
+ | `scripts/install.ps1` | 以 `link:` 方式装进 profile(本地开发) |
149
+ | `scripts/ui-probe/` | 无头浏览器验证工具(CDP 客户端 + 页面片段) |
150
+
151
+ MIT
@@ -0,0 +1,8 @@
1
+ # dsh-find-in-session bundle layer.
2
+ #
3
+ # Inserts the host plugin row. The host half serves /dsh-find-in-session/* over
4
+ # the loopback web server; the browser half (exports "./client") mounts the
5
+ # Ctrl+F find bar. No configuration is required.
6
+ - insert:
7
+ - id: dsh-find-in-session
8
+ name: 'dsh-find-in-session'
package/docs/panel.png ADDED
Binary file