dsh-reference-session-collector 0.2.0 → 0.2.2
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 +4 -2
- package/lib/index.js +66 -54
- package/package.json +2 -2
- package/src/index.ts +84 -68
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.2",
|
|
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
|
@@ -690,12 +690,14 @@ interface DrsHandlerDeps {
|
|
|
690
690
|
trustedHosts?: readonly string[];
|
|
691
691
|
}
|
|
692
692
|
/**
|
|
693
|
-
* op 分发:GET `?op=…`;POST
|
|
693
|
+
* op 分发:GET `?op=…`;POST `?op=sync` 触发后台同步。
|
|
694
|
+
* POST 的 op 只从 **query** 读(`{op:'sync'}` 落进 body 会 400 unknown POST op)。
|
|
694
695
|
* 未知 op / 缺参 → 400(不留空壳)。
|
|
695
696
|
*/
|
|
696
697
|
declare function handleDrsRequest(deps: DrsHandlerDeps, req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
697
698
|
/**
|
|
698
|
-
*
|
|
699
|
+
* 插件主体:apply() 立即返回,重活(开库/建 sync/注册路由)推迟到 setImmediate。
|
|
700
|
+
* 宿主插件装载串行同步——apply 不返回则整列插件都装不上,前端一直 Loading plugins…。
|
|
699
701
|
* @param ctx - host 插件上下文(webServer 声明注入;webStartup/webRuntime 回退臂)。
|
|
700
702
|
*/
|
|
701
703
|
declare function apply(ctx: Context): void;
|
package/lib/index.js
CHANGED
|
@@ -2234,7 +2234,8 @@ function parseTail(url) {
|
|
|
2234
2234
|
return Math.floor(parsed);
|
|
2235
2235
|
}
|
|
2236
2236
|
/**
|
|
2237
|
-
* op 分发:GET `?op=…`;POST
|
|
2237
|
+
* op 分发:GET `?op=…`;POST `?op=sync` 触发后台同步。
|
|
2238
|
+
* POST 的 op 只从 **query** 读(`{op:'sync'}` 落进 body 会 400 unknown POST op)。
|
|
2238
2239
|
* 未知 op / 缺参 → 400(不留空壳)。
|
|
2239
2240
|
*/
|
|
2240
2241
|
async function handleDrsRequest(deps, req, res) {
|
|
@@ -2245,6 +2246,13 @@ async function handleDrsRequest(deps, req, res) {
|
|
|
2245
2246
|
});
|
|
2246
2247
|
return;
|
|
2247
2248
|
}
|
|
2249
|
+
if (deps.query === void 0) {
|
|
2250
|
+
writeJson(res, 503, {
|
|
2251
|
+
ok: false,
|
|
2252
|
+
error: "initializing"
|
|
2253
|
+
});
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2248
2256
|
let url;
|
|
2249
2257
|
try {
|
|
2250
2258
|
url = new URL(req.url ?? "/api/dsh-reference-session-collector", "http://localhost");
|
|
@@ -2277,7 +2285,8 @@ async function handleDrsRequest(deps, req, res) {
|
|
|
2277
2285
|
case "status":
|
|
2278
2286
|
writeJson(res, 200, {
|
|
2279
2287
|
ok: true,
|
|
2280
|
-
...status(deps.query, deps.sync.lastSyncResult ?? null)
|
|
2288
|
+
...status(deps.query, deps.sync.lastSyncResult ?? null),
|
|
2289
|
+
syncing: deps.sync.isSyncing
|
|
2281
2290
|
});
|
|
2282
2291
|
return;
|
|
2283
2292
|
case "sessions": {
|
|
@@ -2428,7 +2437,8 @@ function safeReadService(ctx, name) {
|
|
|
2428
2437
|
}
|
|
2429
2438
|
}
|
|
2430
2439
|
/**
|
|
2431
|
-
*
|
|
2440
|
+
* 插件主体:apply() 立即返回,重活(开库/建 sync/注册路由)推迟到 setImmediate。
|
|
2441
|
+
* 宿主插件装载串行同步——apply 不返回则整列插件都装不上,前端一直 Loading plugins…。
|
|
2432
2442
|
* @param ctx - host 插件上下文(webServer 声明注入;webStartup/webRuntime 回退臂)。
|
|
2433
2443
|
*/
|
|
2434
2444
|
function apply(ctx) {
|
|
@@ -2440,43 +2450,12 @@ function apply(ctx) {
|
|
|
2440
2450
|
else console[level]("[dsh-reference-session-collector]", message, ...args);
|
|
2441
2451
|
} catch {}
|
|
2442
2452
|
};
|
|
2443
|
-
const storeDir = resolveIndexDir();
|
|
2444
2453
|
let store;
|
|
2445
2454
|
let query;
|
|
2446
|
-
|
|
2447
|
-
store = openProjectionStore(storeDir);
|
|
2448
|
-
query = createQueryContext(store);
|
|
2449
|
-
} catch (error) {
|
|
2450
|
-
log("warn", "projection store 不可用(node:sqlite 缺席/目录不可写):插件停用—— %s", String(error?.message ?? error));
|
|
2451
|
-
return;
|
|
2452
|
-
}
|
|
2453
|
-
const sync = new ProjectionSync({
|
|
2454
|
-
store: createStorePort(store),
|
|
2455
|
-
project: (events, options) => projectSessionEvents(events, {
|
|
2456
|
-
dirId: options.dirId,
|
|
2457
|
-
...options.header === void 0 ? {} : { header: options.header },
|
|
2458
|
-
...options.exactCounter === void 0 ? {} : { exactCounter: (text) => {
|
|
2459
|
-
const count = options.exactCounter?.(text);
|
|
2460
|
-
return count !== void 0 && count.kind === "exact-tokenizer" ? count.tokens : Math.ceil(text.length / 4);
|
|
2461
|
-
} }
|
|
2462
|
-
}),
|
|
2463
|
-
log: (level, message, ...args) => log(level, message, ...args)
|
|
2464
|
-
});
|
|
2455
|
+
let sync;
|
|
2465
2456
|
let bootTimer;
|
|
2466
2457
|
let disposeInterval = () => {};
|
|
2467
|
-
|
|
2468
|
-
bootTimer = setTimeout(() => {
|
|
2469
|
-
sync.syncAll().then((result) => {
|
|
2470
|
-
log("info", "boot sync done: updated=%d skipped=%d basis=%s %dms", result.updated, result.skipped, result.basis, result.durationMs);
|
|
2471
|
-
}).catch((error) => log("warn", "boot sync failed: %s", String(error?.message ?? error)));
|
|
2472
|
-
}, BOOT_SYNC_DELAY_MS);
|
|
2473
|
-
disposeInterval = startIntervalSync(sync);
|
|
2474
|
-
return () => {
|
|
2475
|
-
if (bootTimer !== void 0) clearTimeout(bootTimer);
|
|
2476
|
-
disposeInterval();
|
|
2477
|
-
store.close();
|
|
2478
|
-
};
|
|
2479
|
-
}, "dsh-reference-session-collector: projection lifecycle");
|
|
2458
|
+
let registered = false;
|
|
2480
2459
|
const readStartupFace = () => {
|
|
2481
2460
|
const direct = safeReadService(ctx, "webStartup") ?? safeReadService(ctx, "webRuntime");
|
|
2482
2461
|
return direct !== null && typeof direct === "object" ? direct : void 0;
|
|
@@ -2489,7 +2468,6 @@ function apply(ctx) {
|
|
|
2489
2468
|
...startup?.trustedHosts !== void 0 ? { trustedHosts: startup.trustedHosts } : {}
|
|
2490
2469
|
};
|
|
2491
2470
|
};
|
|
2492
|
-
let registered = false;
|
|
2493
2471
|
const register = (webServer, channel) => {
|
|
2494
2472
|
if (registered) return;
|
|
2495
2473
|
try {
|
|
@@ -2510,24 +2488,58 @@ function apply(ctx) {
|
|
|
2510
2488
|
log("warn", "drs api route registration failed (%s): %s", channel, String(error?.message ?? error));
|
|
2511
2489
|
}
|
|
2512
2490
|
};
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2491
|
+
ctx.effect(() => {
|
|
2492
|
+
return () => {
|
|
2493
|
+
if (bootTimer !== void 0) clearTimeout(bootTimer);
|
|
2494
|
+
disposeInterval();
|
|
2495
|
+
store?.close();
|
|
2496
|
+
};
|
|
2497
|
+
}, "dsh-reference-session-collector: projection lifecycle");
|
|
2498
|
+
setImmediate(() => {
|
|
2499
|
+
try {
|
|
2500
|
+
store = openProjectionStore(resolveIndexDir());
|
|
2501
|
+
query = createQueryContext(store);
|
|
2502
|
+
sync = new ProjectionSync({
|
|
2503
|
+
store: createStorePort(store),
|
|
2504
|
+
project: (events, options) => projectSessionEvents(events, {
|
|
2505
|
+
dirId: options.dirId,
|
|
2506
|
+
...options.header === void 0 ? {} : { header: options.header },
|
|
2507
|
+
...options.exactCounter === void 0 ? {} : { exactCounter: (text) => {
|
|
2508
|
+
const count = options.exactCounter?.(text);
|
|
2509
|
+
return count !== void 0 && count.kind === "exact-tokenizer" ? count.tokens : Math.ceil(text.length / 4);
|
|
2510
|
+
} }
|
|
2511
|
+
}),
|
|
2512
|
+
log: (level, message, ...args) => log(level, message, ...args)
|
|
2513
|
+
});
|
|
2514
|
+
bootTimer = setTimeout(() => {
|
|
2515
|
+
sync.syncAll().then((result) => {
|
|
2516
|
+
log("info", "boot sync done: updated=%d skipped=%d basis=%s %dms", result.updated, result.skipped, result.basis, result.durationMs);
|
|
2517
|
+
}).catch((error) => log("warn", "boot sync failed: %s", String(error?.message ?? error)));
|
|
2518
|
+
}, BOOT_SYNC_DELAY_MS);
|
|
2519
|
+
disposeInterval = startIntervalSync(sync);
|
|
2520
|
+
const readService = (name) => safeReadService(ctx, name);
|
|
2521
|
+
const active = asWebServer(readService("webServer"));
|
|
2522
|
+
if (active !== void 0) {
|
|
2523
|
+
register(active, "direct");
|
|
2524
|
+
if (registered) return;
|
|
2525
2525
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2526
|
+
try {
|
|
2527
|
+
ctx.inject(["webServer"], (injected) => {
|
|
2528
|
+
const webServer = asWebServer(injected.webServer);
|
|
2529
|
+
if (webServer === void 0) {
|
|
2530
|
+
log("warn", "webServer exposes no register() — drs api route not registered (projection sync keeps running)");
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
register(webServer, "inject");
|
|
2534
|
+
});
|
|
2535
|
+
} catch (error) {
|
|
2536
|
+
log("warn", "webServer inject arm unavailable: %s — HTTP 面缺席,同步本体照常", String(error?.message ?? error));
|
|
2537
|
+
}
|
|
2538
|
+
log("info", "deferred init done (store opened, sync started, routes registered)");
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
log("warn", "deferred init failed — projection store 不可用(node:sqlite 缺席/目录不可写):插件停用—— %s", String(error?.message ?? error));
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2531
2543
|
}
|
|
2532
2544
|
//#endregion
|
|
2533
2545
|
export { BOOT_SYNC_DELAY_MS, DEFAULT_SYNC_INTERVAL_MS, DISCOVERY_LIMIT, DRS_API_PATH, INJECTED_BLOCK_TAGS, LEDGER_LRU_CAP, LedgerCache, PROJECTION_SCHEMA_VERSION, ProjectionSync, SYNC_INTERVAL_ENV, apply, buildSearchSnippet, classifySessionHeader, createQueryContext, createStorePort, decodeSessionLogBytes, decodeSessionLogFile, discoverSessionLogs, extractSearchEventText, extractTitleFromEvents, generationLogFilename, handleDrsRequest, inject, listSessions, openProjectionStore, projectSessionEvents, readArchivedSessionIds, readSessionGenerations, recallReport, reloadSummaries, resolveDshHome, resolveExactCounter, resolveIndexDir, resolveSyncIntervalMs, sanitizeFtsQuery, search, searchGrouped, segmentForIndex, segmentQueryTerm, sessionDetail, startIntervalSync, status, stripInjectedBlocks, surfaceLedger };
|
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.2",
|
|
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
|
@@ -195,7 +195,8 @@ function parseTail(url: URL): number | undefined {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* op 分发:GET `?op=…`;POST
|
|
198
|
+
* op 分发:GET `?op=…`;POST `?op=sync` 触发后台同步。
|
|
199
|
+
* POST 的 op 只从 **query** 读(`{op:'sync'}` 落进 body 会 400 unknown POST op)。
|
|
199
200
|
* 未知 op / 缺参 → 400(不留空壳)。
|
|
200
201
|
*/
|
|
201
202
|
export async function handleDrsRequest(deps: DrsHandlerDeps, req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
@@ -203,6 +204,11 @@ export async function handleDrsRequest(deps: DrsHandlerDeps, req: IncomingMessag
|
|
|
203
204
|
writeJson(res, 403, { ok: false, error: 'forbidden' })
|
|
204
205
|
return
|
|
205
206
|
}
|
|
207
|
+
// 延迟初始化未完成——投影面不可用(503)。
|
|
208
|
+
if (deps.query === undefined) {
|
|
209
|
+
writeJson(res, 503, { ok: false, error: 'initializing' })
|
|
210
|
+
return
|
|
211
|
+
}
|
|
206
212
|
let url: URL
|
|
207
213
|
try {
|
|
208
214
|
url = new URL(req.url ?? DRS_API_PATH, 'http://localhost')
|
|
@@ -227,7 +233,9 @@ export async function handleDrsRequest(deps: DrsHandlerDeps, req: IncomingMessag
|
|
|
227
233
|
|
|
228
234
|
switch (op) {
|
|
229
235
|
case 'status': {
|
|
230
|
-
|
|
236
|
+
// syncing:真实在跑旗标(单次触发即回、后台跑完才落 last_sync),
|
|
237
|
+
// 消费方(dsh-search-index 深度同步)靠它判断慢扫何时落定。
|
|
238
|
+
writeJson(res, 200, { ok: true, ...status(deps.query, deps.sync.lastSyncResult ?? null), syncing: deps.sync.isSyncing })
|
|
231
239
|
return
|
|
232
240
|
}
|
|
233
241
|
case 'sessions': {
|
|
@@ -345,7 +353,8 @@ function safeReadService(ctx: Context, name: string): unknown {
|
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
/**
|
|
348
|
-
*
|
|
356
|
+
* 插件主体:apply() 立即返回,重活(开库/建 sync/注册路由)推迟到 setImmediate。
|
|
357
|
+
* 宿主插件装载串行同步——apply 不返回则整列插件都装不上,前端一直 Loading plugins…。
|
|
349
358
|
* @param ctx - host 插件上下文(webServer 声明注入;webStartup/webRuntime 回退臂)。
|
|
350
359
|
*/
|
|
351
360
|
export function apply(ctx: Context): void {
|
|
@@ -358,53 +367,13 @@ export function apply(ctx: Context): void {
|
|
|
358
367
|
} catch { /* 日志面永不致命 */ }
|
|
359
368
|
}
|
|
360
369
|
|
|
361
|
-
|
|
362
|
-
let store: ProjectionStore
|
|
363
|
-
let query: QueryContext
|
|
364
|
-
|
|
365
|
-
store = openProjectionStore(storeDir)
|
|
366
|
-
query = createQueryContext(store)
|
|
367
|
-
} catch (error) {
|
|
368
|
-
log('warn', 'projection store 不可用(node:sqlite 缺席/目录不可写):插件停用—— %s',
|
|
369
|
-
String((error as Error)?.message ?? error))
|
|
370
|
-
return
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const sync = new ProjectionSync({
|
|
374
|
-
store: createStorePort(store),
|
|
375
|
-
project: (events, options) => projectSessionEvents(events, {
|
|
376
|
-
dirId: options.dirId,
|
|
377
|
-
...(options.header === undefined ? {} : { header: options.header }),
|
|
378
|
-
// 端口计数器返 TokenCount;投影只需数值臂(CCI verify 同款降级)。
|
|
379
|
-
...(options.exactCounter === undefined ? {} : {
|
|
380
|
-
exactCounter: (text: string): number => {
|
|
381
|
-
const count = options.exactCounter?.(text)
|
|
382
|
-
return count !== undefined && count.kind === 'exact-tokenizer'
|
|
383
|
-
? count.tokens
|
|
384
|
-
: Math.ceil(text.length / 4)
|
|
385
|
-
},
|
|
386
|
-
}),
|
|
387
|
-
}),
|
|
388
|
-
log: (level, message, ...args) => log(level, message, ...args),
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
// 生命周期:dispose 清定时器 + 关库。
|
|
370
|
+
// 延迟初始化状态(setImmediate 臂赋值;请求面兜底 503)。
|
|
371
|
+
let store: ProjectionStore | undefined
|
|
372
|
+
let query: QueryContext | undefined
|
|
373
|
+
let sync: ProjectionSync | undefined
|
|
392
374
|
let bootTimer: ReturnType<typeof setTimeout> | undefined
|
|
393
375
|
let disposeInterval: () => void = () => {}
|
|
394
|
-
|
|
395
|
-
bootTimer = setTimeout(() => {
|
|
396
|
-
void sync.syncAll().then(result => {
|
|
397
|
-
log('info', 'boot sync done: updated=%d skipped=%d basis=%s %dms',
|
|
398
|
-
result.updated, result.skipped, result.basis, result.durationMs)
|
|
399
|
-
}).catch((error: unknown) => log('warn', 'boot sync failed: %s', String((error as Error)?.message ?? error)))
|
|
400
|
-
}, BOOT_SYNC_DELAY_MS)
|
|
401
|
-
disposeInterval = startIntervalSync(sync)
|
|
402
|
-
return () => {
|
|
403
|
-
if (bootTimer !== undefined) clearTimeout(bootTimer)
|
|
404
|
-
disposeInterval()
|
|
405
|
-
store.close()
|
|
406
|
-
}
|
|
407
|
-
}, 'dsh-reference-session-collector: projection lifecycle')
|
|
376
|
+
let registered = false
|
|
408
377
|
|
|
409
378
|
// trustedHosts best-effort(双名回退链,属性/get 两层 safe;缺席=放行)。
|
|
410
379
|
const readStartupFace = (): WebStartupLike | undefined => {
|
|
@@ -415,13 +384,12 @@ export function apply(ctx: Context): void {
|
|
|
415
384
|
const depsFor = (): DrsHandlerDeps => {
|
|
416
385
|
const startup = readStartupFace()
|
|
417
386
|
return {
|
|
418
|
-
query
|
|
419
|
-
sync
|
|
387
|
+
query: query!,
|
|
388
|
+
sync: sync!,
|
|
420
389
|
...(startup?.trustedHosts !== undefined ? { trustedHosts: startup.trustedHosts } : {}),
|
|
421
390
|
}
|
|
422
391
|
}
|
|
423
392
|
|
|
424
|
-
let registered = false
|
|
425
393
|
const register = (webServer: WebServerLike, channel: 'direct' | 'inject'): void => {
|
|
426
394
|
if (registered) return
|
|
427
395
|
try {
|
|
@@ -445,24 +413,72 @@ export function apply(ctx: Context): void {
|
|
|
445
413
|
}
|
|
446
414
|
}
|
|
447
415
|
|
|
448
|
-
|
|
416
|
+
// 生命周期:dispose 清定时器 + 关库(同步登记,cleanup 在 dispose 时执行)。
|
|
417
|
+
ctx.effect(() => {
|
|
418
|
+
return () => {
|
|
419
|
+
if (bootTimer !== undefined) clearTimeout(bootTimer)
|
|
420
|
+
disposeInterval()
|
|
421
|
+
store?.close()
|
|
422
|
+
}
|
|
423
|
+
}, 'dsh-reference-session-collector: projection lifecycle')
|
|
449
424
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
425
|
+
// 延迟初始化:开库/建 sync/注册路由全部推迟,保证 apply 立即返回。
|
|
426
|
+
setImmediate(() => {
|
|
427
|
+
try {
|
|
428
|
+
const storeDir = resolveIndexDir()
|
|
429
|
+
store = openProjectionStore(storeDir)
|
|
430
|
+
query = createQueryContext(store)
|
|
455
431
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
432
|
+
sync = new ProjectionSync({
|
|
433
|
+
store: createStorePort(store),
|
|
434
|
+
project: (events, options) => projectSessionEvents(events, {
|
|
435
|
+
dirId: options.dirId,
|
|
436
|
+
...(options.header === undefined ? {} : { header: options.header }),
|
|
437
|
+
...(options.exactCounter === undefined ? {} : {
|
|
438
|
+
exactCounter: (text: string): number => {
|
|
439
|
+
const count = options.exactCounter?.(text)
|
|
440
|
+
return count !== undefined && count.kind === 'exact-tokenizer'
|
|
441
|
+
? count.tokens
|
|
442
|
+
: Math.ceil(text.length / 4)
|
|
443
|
+
},
|
|
444
|
+
}),
|
|
445
|
+
}),
|
|
446
|
+
log: (level, message, ...args) => log(level, message, ...args),
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
// 启动延迟首轮 sync + interval 轮。
|
|
450
|
+
bootTimer = setTimeout(() => {
|
|
451
|
+
void sync!.syncAll().then(result => {
|
|
452
|
+
log('info', 'boot sync done: updated=%d skipped=%d basis=%s %dms',
|
|
453
|
+
result.updated, result.skipped, result.basis, result.durationMs)
|
|
454
|
+
}).catch((error: unknown) => log('warn', 'boot sync failed: %s', String((error as Error)?.message ?? error)))
|
|
455
|
+
}, BOOT_SYNC_DELAY_MS)
|
|
456
|
+
disposeInterval = startIntervalSync(sync)
|
|
457
|
+
|
|
458
|
+
// 双通道注册路由。
|
|
459
|
+
const readService = (name: string): unknown => safeReadService(ctx, name)
|
|
460
|
+
const active = asWebServer(readService('webServer'))
|
|
461
|
+
if (active !== undefined) {
|
|
462
|
+
register(active, 'direct')
|
|
463
|
+
if (registered) return
|
|
462
464
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
465
|
+
try {
|
|
466
|
+
ctx.inject(['webServer'], (injected) => {
|
|
467
|
+
const webServer = asWebServer((injected as { webServer?: unknown }).webServer)
|
|
468
|
+
if (webServer === undefined) {
|
|
469
|
+
log('warn', 'webServer exposes no register() — drs api route not registered (projection sync keeps running)')
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
register(webServer, 'inject')
|
|
473
|
+
})
|
|
474
|
+
} catch (error) {
|
|
475
|
+
log('warn', 'webServer inject arm unavailable: %s — HTTP 面缺席,同步本体照常', String((error as Error)?.message ?? error))
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
log('info', 'deferred init done (store opened, sync started, routes registered)')
|
|
479
|
+
} catch (error) {
|
|
480
|
+
log('warn', 'deferred init failed — projection store 不可用(node:sqlite 缺席/目录不可写):插件停用—— %s',
|
|
481
|
+
String((error as Error)?.message ?? error))
|
|
482
|
+
}
|
|
483
|
+
})
|
|
468
484
|
}
|