dsh-reference-session-collector 0.2.0-beta.0 → 0.2.1
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/README.de.md +2 -2
- package/README.en.md +7 -7
- package/README.es.md +2 -2
- package/README.fr.md +2 -2
- package/README.it.md +2 -2
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +6 -6
- package/README.ru.md +2 -2
- package/dsh.plugin.json +2 -2
- package/lib/index.d.ts +6 -0
- package/lib/index.js +14 -11
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/surface.ts +18 -7
package/README.de.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — Sitzungs-Projektionssynchronisierer
|
|
2
2
|
|
|
3
3
|
**DeepSeek-Harness(DSH)-Plugin**: Liest die Sitzungsprotokolle unter
|
|
4
4
|
`~/.dsh/sessions/**` **nur lesend**, dekodiert sie und projiziert sie in eine
|
|
@@ -50,7 +50,7 @@ Memory-Anker-Rückfüllung) und Effizienz-Benchmark T9.1.
|
|
|
50
50
|
`~/.dsh-reference-session-collector/projection.db`;
|
|
51
51
|
- pro Sitzung vollständiger Neu-Schreib-Ersatz (DELETE+INSERT in einer
|
|
52
52
|
Transaktion), idempotent;
|
|
53
|
-
- **Maskierung liegt nicht bei
|
|
53
|
+
- **Maskierung liegt nicht bei DRSC**: Rohtext-Speicherung,
|
|
54
54
|
Expositionsfläche = die Sitzungsdateien selbst.
|
|
55
55
|
|
|
56
56
|
## Umgebungsvariablen
|
package/README.en.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC)
|
|
2
2
|
|
|
3
3
|
**Session reference projection synchronizer** — a DeepSeek Harness (DSH) plugin.
|
|
4
4
|
|
|
@@ -9,8 +9,8 @@ replacement markers, recall pointers, and an FTS5 full-text index — so context
|
|
|
9
9
|
compression (cci), the session steward, and prime-memory all read the same
|
|
10
10
|
projection instead of each decoding logs on their own.
|
|
11
11
|
|
|
12
|
-
> Naming: `dsh-reference-session-collector` abbreviates to **
|
|
13
|
-
>
|
|
12
|
+
> Naming: `dsh-reference-session-collector` abbreviates to **DRSC** — the initials of
|
|
13
|
+
> dsh-**R**eference-**S**ession-**C**ollector.
|
|
14
14
|
|
|
15
15
|
## Status
|
|
16
16
|
|
|
@@ -69,7 +69,7 @@ missing (fail-soft).
|
|
|
69
69
|
and it has no write counterpart — ever. The plugin writes only to
|
|
70
70
|
`~/.dsh-reference-session-collector/projection.db`. That database holds **unsanitized
|
|
71
71
|
original text**; its exposure equals the session files themselves. Any
|
|
72
|
-
"sanitized corpus" requirement belongs to consumers, not
|
|
72
|
+
"sanitized corpus" requirement belongs to consumers, not DRSC.
|
|
73
73
|
|
|
74
74
|
## Configuration (v0)
|
|
75
75
|
|
|
@@ -83,16 +83,16 @@ No settings face yet (lands with phase-2 wiring). Environment variables:
|
|
|
83
83
|
|
|
84
84
|
## Roadmap
|
|
85
85
|
|
|
86
|
-
1. **Phase 1 · cci reconciliation**: monitor/verify gains a
|
|
86
|
+
1. **Phase 1 · cci reconciliation**: monitor/verify gains a DRSC recall column —
|
|
87
87
|
claims vs formula vs projection, three-column accounting (read-only HTTP).
|
|
88
88
|
2. **Phase 2 · steward corpus switch**: the steward's inlined search engine
|
|
89
|
-
reads
|
|
89
|
+
reads DRSC (segments.time/on_surface/search_text + last_time_ms/archived
|
|
90
90
|
cover its whole hit face; field mapping in
|
|
91
91
|
`.agents/plans/drs-start/findings.md`).
|
|
92
92
|
3. **Phase 3 · memory history projection (read-only)**: the l0 write path does
|
|
93
93
|
**not** move (sanitization/redaction/record_id ownership stays in memory's
|
|
94
94
|
capture side; it is semantically incompatible with full-session rewrite).
|
|
95
|
-
|
|
95
|
+
DRSC only serves historical anchor backfill and corpus. Vector layer
|
|
96
96
|
(BGE-M3 int8) is a separate optional batch job.
|
|
97
97
|
|
|
98
98
|
## Development
|
package/README.es.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — sincronizador de proyección de sesiones
|
|
2
2
|
|
|
3
3
|
**Complemento de DeepSeek Harness (DSH)**: escanea **solo en lectura** los
|
|
4
4
|
registros de sesión en `~/.dsh/sessions/**`, los decodifica y los proyecta
|
|
@@ -49,7 +49,7 @@ fase 3 relleno de anclas de memory) y banco de pruebas de eficiencia T9.1.
|
|
|
49
49
|
`~/.dsh-reference-session-collector/projection.db`;
|
|
50
50
|
- reemplazo de reescritura completa por sesión (DELETE+INSERT en una
|
|
51
51
|
transacción), idempotente;
|
|
52
|
-
- **la desensibilización no es responsabilidad de
|
|
52
|
+
- **la desensibilización no es responsabilidad de DRSC**: almacena texto
|
|
53
53
|
original; la superficie de exposición son los propios archivos de sesión.
|
|
54
54
|
|
|
55
55
|
## Variables de entorno
|
package/README.fr.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — Synchroniseur de projection de sessions
|
|
2
2
|
|
|
3
3
|
**Plugin DeepSeek Harness (DSH)** : balaie les journaux de session sous
|
|
4
4
|
`~/.dsh/sessions/**` en **lecture seule**, les décode et les projette dans
|
|
@@ -49,7 +49,7 @@ d'efficacité T9.1.
|
|
|
49
49
|
`~/.dsh-reference-session-collector/projection.db` ;
|
|
50
50
|
- remplacement réécriture-complète par session (DELETE+INSERT dans une
|
|
51
51
|
transaction), idempotent ;
|
|
52
|
-
- **l'anonymisation n'est pas la responsabilité de
|
|
52
|
+
- **l'anonymisation n'est pas la responsabilité de DRSC** : texte brut
|
|
53
53
|
stocké, surface d'exposition = les fichiers de session eux-mêmes.
|
|
54
54
|
|
|
55
55
|
## Variables d'environnement
|
package/README.it.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — sincronizzatore di proiezione delle sessioni
|
|
2
2
|
|
|
3
3
|
**Plugin DeepSeek Harness (DSH)**: scansisce **in sola lettura** i log di
|
|
4
4
|
sessione in `~/.dsh/sessions/**`, li decodifica e li proietta in una
|
|
@@ -49,7 +49,7 @@ steward, fase 3 riempimento ancore memory) e benchmark di efficienza T9.1.
|
|
|
49
49
|
`~/.dsh-reference-session-collector/projection.db`;
|
|
50
50
|
- sostituzione con riscrittura completa per sessione (DELETE+INSERT in una
|
|
51
51
|
transazione), idempotente;
|
|
52
|
-
- **la sanitizzazione non è responsabilità di
|
|
52
|
+
- **la sanitizzazione non è responsabilità di DRSC**: memorizza testo grezzo,
|
|
53
53
|
superficie di esposizione = i file di sessione stessi.
|
|
54
54
|
|
|
55
55
|
## Variabili d'ambiente
|
package/README.ja.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — セッション原文プロジェクション同期器
|
|
2
2
|
|
|
3
3
|
**DeepSeek Harness (DSH) プラグイン**: `~/.dsh/sessions/**` のセッション
|
|
4
4
|
ログを**読み取り専用**で走査し、復号後に独自 SQLite プロジェクション
|
|
@@ -44,7 +44,7 @@ HTTP ルート。ゲートチェーン `pnpm check` 全緑(9スイート / 66
|
|
|
44
44
|
- セッションファイルと `workspace.json` は常時読み取り専用;
|
|
45
45
|
- 本プラグインの全書き込みは `~/.dsh-reference-session-collector/projection.db` にのみ;
|
|
46
46
|
- セッション単位の全量書換式置換(トランザクション内 DELETE+INSERT)、冪等;
|
|
47
|
-
- **マスキング責任は
|
|
47
|
+
- **マスキング責任は DRSC にない**: 原文を保存、露出面=セッションファイル自体。
|
|
48
48
|
|
|
49
49
|
## 環境変数
|
|
50
50
|
|
package/README.ko.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — 세션 원문 투영 동기화기
|
|
2
2
|
|
|
3
3
|
**DeepSeek Harness(DSH) 플러그인**: `~/.dsh/sessions/**` 세션 로그를
|
|
4
4
|
**읽기 전용**으로 스캔하여 디코딩 후 자체 SQLite 투영
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
- 세션 파일과 `workspace.json`은 상시 읽기 전용;
|
|
45
45
|
- 모든 기록은 `~/.dsh-reference-session-collector/projection.db`에만;
|
|
46
46
|
- 세션 단위 전체 재작성 치환(트랜잭션 내 DELETE+INSERT), 멱등;
|
|
47
|
-
- **마스킹 책임은
|
|
47
|
+
- **마스킹 책임은 DRSC에 없음**: 원문 저장, 노출면=세션 파일 자체.
|
|
48
48
|
|
|
49
49
|
## 환경 변수
|
|
50
50
|
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — 会话原文投影同步器
|
|
2
2
|
|
|
3
3
|
> 只读扫描 `~/.dsh/sessions/**` 会话日志,解码后投影进插件自有 SQLite
|
|
4
4
|
> (segments / replaces / FTS5),经单条 fenced HTTP 路由对外提供
|
|
5
5
|
> surface 标记、召回对账与中文可用的内容检索面。**绝不写会话文件。**
|
|
6
6
|
|
|
7
|
-
# Session Reference Projector (
|
|
7
|
+
# Session Reference Projector (DRSC) — English summary
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
DRSC is a read-only projector over the host's session logs. It walks
|
|
10
10
|
`~/.dsh/sessions/**`, decodes zstd/teiled-frame logs with a vendored leaf
|
|
11
11
|
decoder, and rewrites per-session projections into
|
|
12
12
|
`~/.dsh-reference-session-collector/projection.db` (SQLite via `node:sqlite`, four
|
|
@@ -15,12 +15,12 @@ tables + one FTS5 virtual table). A single fenced HTTP route
|
|
|
15
15
|
per-session segment windows, the surface ledger, recall/compress pair
|
|
16
16
|
accounting and full-text search. Token counts share the exact same
|
|
17
17
|
DeepSeek-V4 tokenizer and formula as CCI's `verify`, falling back to a
|
|
18
|
-
labelled `chars/4` basis when assets are absent.
|
|
18
|
+
labelled `chars/4` basis when assets are absent. DRSC never writes to any
|
|
19
19
|
session file; its only writes land in its own data directory.
|
|
20
20
|
|
|
21
21
|
## 定位 / Positioning
|
|
22
22
|
|
|
23
|
-
| 消费方 | 用
|
|
23
|
+
| 消费方 | 用 DRSC 做什么 | 分期 |
|
|
24
24
|
|---|---|---|
|
|
25
25
|
| CCI(context-compression) | surface 标记(compressed/surface_text/replaced_by)、召回对账(Σb 回吐、Σ(a−b) 毛节省)、verify 免全量解码 | 一期 |
|
|
26
26
|
| steward(会话管家) | 内容搜索语料源切换(search_text/time/on_surface 检索面) | 二期 |
|
|
@@ -68,7 +68,7 @@ session file; its only writes land in its own data directory.
|
|
|
68
68
|
- 会话文件(`~/.dsh/sessions/**`)与 `workspace.json` 一律只读;
|
|
69
69
|
- 插件的全部写入落在 `~/.dsh-reference-session-collector/projection.db`;
|
|
70
70
|
- 每 session 全量重写式替换(事务内 DELETE+INSERT),幂等可重放;
|
|
71
|
-
- **脱敏责任不在
|
|
71
|
+
- **脱敏责任不在 DRSC**:projection.db 存原文,泄露面=会话文件本身。
|
|
72
72
|
|
|
73
73
|
## 分期路线
|
|
74
74
|
|
package/README.ru.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-reference-session-collector (
|
|
1
|
+
# dsh-reference-session-collector (DRSC) — синхронизатор проекции сессий
|
|
2
2
|
|
|
3
3
|
**Плагин DeepSeek Harness (DSH)**: сканирует **только на чтение** журналы
|
|
4
4
|
сессий в `~/.dsh/sessions/**`, декодирует их и проецирует в собственную
|
|
@@ -48,7 +48,7 @@ SQLite-проекцию (segments / replaces / FTS5). Одна защищённ
|
|
|
48
48
|
- все записи плагина идут исключительно в
|
|
49
49
|
`~/.dsh-reference-session-collector/projection.db`;
|
|
50
50
|
- полная перезапись-замена на сессию (DELETE+INSERT в транзакции), идемпотентно;
|
|
51
|
-
- **ответственность за обезличивание не на
|
|
51
|
+
- **ответственность за обезличивание не на DRSC**: хранится исходный текст,
|
|
52
52
|
площадь утечки = сами файлы сессий.
|
|
53
53
|
|
|
54
54
|
## Переменные окружения
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-reference-session-collector",
|
|
3
3
|
"name": "dsh-reference-session-collector",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Session reference projector (DRS): read-only scan of ~/.dsh/sessions logs, decoded into a private SQLite (segments/replaces/FTS5) with surface flags, recall-pair accounting and a Chinese-capable search face, served via /api/dsh-reference-session-collector. Never writes session files.",
|
|
7
7
|
"zh": "会话原文投影同步器(DRS):只读扫描 ~/.dsh/sessions 会话日志,解码后投影进自有 SQLite(segments/replaces/FTS5),经 /api/dsh-reference-session-collector 提供 surface 标记、召回对账与中文可用的内容检索面。绝不写会话文件。"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"engines": {
|
|
16
|
-
"dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2"
|
|
16
|
+
"dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3"
|
|
17
17
|
},
|
|
18
18
|
"components": {
|
|
19
19
|
"host": "lib/index.js",
|
package/lib/index.d.ts
CHANGED
|
@@ -388,6 +388,12 @@ interface GroupedSearchRequest {
|
|
|
388
388
|
limit?: number;
|
|
389
389
|
/** R3:true=snippet 取材切 redacted_search_text(命中/排序不变,fts 仍原文)。 */
|
|
390
390
|
redact?: boolean;
|
|
391
|
+
/**
|
|
392
|
+
* 命中展开(steward「分开显示」开关,wire `expand=1`):true=每条候选行
|
|
393
|
+
* 各自成命中(同会话多行按全局分数序出现);缺省 false=会话分组各取
|
|
394
|
+
* 最强命中(既有聚合臂行为零改动,limit 语义随之从会话数变行数)。
|
|
395
|
+
*/
|
|
396
|
+
expand?: boolean;
|
|
391
397
|
}
|
|
392
398
|
/** 分组命中(wire 蛇形,会话级最强命中)。 */
|
|
393
399
|
interface GroupedSearchHit {
|
package/lib/index.js
CHANGED
|
@@ -385,18 +385,20 @@ function searchGrouped(ctx, request) {
|
|
|
385
385
|
WHERE segments_fts MATCH ?
|
|
386
386
|
AND seg.kind IN (${placeholders}) ${surfaceClause} ${archivedClause} ${dateClause}
|
|
387
387
|
ORDER BY rank LIMIT ?`;
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
388
|
+
const scored = ctx.store.db.prepare(sql).all(match, ...kinds, ...dateParams, SEARCH_SCAN_LIMIT).map((doc) => ({
|
|
389
|
+
doc,
|
|
390
|
+
score: -Number(doc.rank) * (SEARCH_TYPE_WEIGHT[doc.kind] ?? 1)
|
|
391
|
+
}));
|
|
392
|
+
let grouped;
|
|
393
|
+
if (request.expand === true) grouped = scored;
|
|
394
|
+
else {
|
|
395
|
+
const bestBySession = /* @__PURE__ */ new Map();
|
|
396
|
+
for (const entry of scored) {
|
|
397
|
+
const best = bestBySession.get(entry.doc.session_id);
|
|
398
|
+
if (best === void 0 || entry.score > best.score) bestBySession.set(entry.doc.session_id, entry);
|
|
399
|
+
}
|
|
400
|
+
grouped = [...bestBySession.values()];
|
|
398
401
|
}
|
|
399
|
-
const grouped = [...bestBySession.values()];
|
|
400
402
|
if (request.sortBy === "time") grouped.sort((a, b) => b.doc.updated_at - a.doc.updated_at || b.score - a.score);
|
|
401
403
|
else grouped.sort((a, b) => b.score - a.score);
|
|
402
404
|
const hits = grouped.slice(0, limit).map(({ doc }) => ({
|
|
@@ -2388,6 +2390,7 @@ async function handleDrsRequest(deps, req, res) {
|
|
|
2388
2390
|
...url.searchParams.get("surface") === "0" ? { surfaceOnly: false } : {},
|
|
2389
2391
|
...url.searchParams.get("sort") === "time" ? { sortBy: "time" } : {},
|
|
2390
2392
|
...limitRaw !== null && Number.isFinite(Number(limitRaw)) ? { limit: Number(limitRaw) } : {},
|
|
2393
|
+
...url.searchParams.get("expand") === "1" ? { expand: true } : {},
|
|
2391
2394
|
...redact ? { redact: true } : {}
|
|
2392
2395
|
})
|
|
2393
2396
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-reference-session-collector",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org",
|
|
6
6
|
"tag": "beta",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
],
|
|
73
73
|
"engines": {
|
|
74
74
|
"node": ">=22",
|
|
75
|
-
"dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2"
|
|
75
|
+
"dsh": "0.1.0-rc.2 || 0.1.0-rc.3 || 0.1.0-rc.6 || 0.1.0-rc.7 || 0.1.0-rc.8 || 0.1.1-rc.1 || 0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.5-rc.1 || 0.1.5-rc.2 || 0.1.5-rc.3 || 0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3"
|
|
76
76
|
},
|
|
77
77
|
"license": "MIT",
|
|
78
78
|
"dependencies": {
|
package/src/index.ts
CHANGED
|
@@ -309,6 +309,7 @@ export async function handleDrsRequest(deps: DrsHandlerDeps, req: IncomingMessag
|
|
|
309
309
|
...(url.searchParams.get('surface') === '0' ? { surfaceOnly: false } : {}),
|
|
310
310
|
...(url.searchParams.get('sort') === 'time' ? { sortBy: 'time' as const } : {}),
|
|
311
311
|
...(limitRaw !== null && Number.isFinite(Number(limitRaw)) ? { limit: Number(limitRaw) } : {}),
|
|
312
|
+
...(url.searchParams.get('expand') === '1' ? { expand: true } : {}),
|
|
312
313
|
...(redact ? { redact: true } : {}),
|
|
313
314
|
})
|
|
314
315
|
writeJson(res, 200, { ok: true, ...grouped })
|
package/src/surface.ts
CHANGED
|
@@ -379,6 +379,12 @@ export interface GroupedSearchRequest {
|
|
|
379
379
|
limit?: number
|
|
380
380
|
/** R3:true=snippet 取材切 redacted_search_text(命中/排序不变,fts 仍原文)。 */
|
|
381
381
|
redact?: boolean
|
|
382
|
+
/**
|
|
383
|
+
* 命中展开(steward「分开显示」开关,wire `expand=1`):true=每条候选行
|
|
384
|
+
* 各自成命中(同会话多行按全局分数序出现);缺省 false=会话分组各取
|
|
385
|
+
* 最强命中(既有聚合臂行为零改动,limit 语义随之从会话数变行数)。
|
|
386
|
+
*/
|
|
387
|
+
expand?: boolean
|
|
382
388
|
}
|
|
383
389
|
|
|
384
390
|
/** 分组命中(wire 蛇形,会话级最强命中)。 */
|
|
@@ -445,14 +451,19 @@ export function searchGrouped(ctx: QueryContext, request: GroupedSearchRequest):
|
|
|
445
451
|
title: string
|
|
446
452
|
updated_at: number
|
|
447
453
|
}[]
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
+
const scored = docs.map(doc => ({ doc, score: -Number(doc.rank) * (SEARCH_TYPE_WEIGHT[doc.kind] ?? 1) }))
|
|
455
|
+
let grouped: typeof scored
|
|
456
|
+
if (request.expand === true) {
|
|
457
|
+
// 命中展开:不折叠,每条候选行都是命中(同会话多行各自显示)。
|
|
458
|
+
grouped = scored
|
|
459
|
+
} else {
|
|
460
|
+
const bestBySession = new Map<string, { doc: typeof docs[number]; score: number }>()
|
|
461
|
+
for (const entry of scored) {
|
|
462
|
+
const best = bestBySession.get(entry.doc.session_id)
|
|
463
|
+
if (best === undefined || entry.score > best.score) bestBySession.set(entry.doc.session_id, entry)
|
|
464
|
+
}
|
|
465
|
+
grouped = [...bestBySession.values()]
|
|
454
466
|
}
|
|
455
|
-
const grouped = [...bestBySession.values()]
|
|
456
467
|
if (request.sortBy === 'time') {
|
|
457
468
|
grouped.sort((a, b) => (b.doc.updated_at - a.doc.updated_at) || (b.score - a.score))
|
|
458
469
|
} else {
|