@deepseek-ai/dsh-session-query-sqlite 0.1.1-rc.2 → 0.1.2-alpha.3

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/session-query/session-query-sqlite/README.md
5
- README.md: 25257ddbe672d47faf39e1472a1bd882205d8c4e
6
- README.zh.md: 584e466a5042334e22797a6c2794cce910844d5a
5
+ README.md: 96ad49d6b14b38849ad800e5802ecdc78344bdc8
6
+ README.zh.md: 241f652bb85cc4a8bee489babd699522e3236b3c
package/README.md CHANGED
@@ -1,49 +1,133 @@
1
+ ---
2
+ description: "The SQLite FTS5 full-text search backend for session history, for deployments and maintainers choosing, configuring, or debugging full-text search over the query service."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-session-query-sqlite
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- Concrete `ctx.sessionQuery` provider. `SqliteSessionQueryEngine` inherits exact reads, traces, and provider-independent filters from the Service Definition package and implements its two full-text methods with SQLite FTS5. Search uses the live-preferred logical session corpus and groups cross-session results by their strongest event.
10
+ ## Summary
11
+
12
+ `dsh-session-query-sqlite` searches session history with a SQLite FTS5 index and returns ranked, cursor-paginated results grouped by session or within one session. Mount it together with `dsh-session-query` and you get full-text search plus the whole query surface — exact reads, filters, and traces — at once. Live sessions are indexed from memory and persisted sessions from a dedicated derived-index database, so results always reflect the newest state without touching the session-persistence store. Search is opt-in and off by default in shipped compositions: `openAt` decides whether the index opens at startup, at the first search, or never. Setup and usage come first; the implementation internals live in a collapsible developer section below.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount this package when a composition needs ranked full-text search over session history — for example the Web content search or `/resume` prior-work retrieval. The common path is explicit: mount the plugin, give it a dedicated database path, and call `ctx.sessionQuery.searchSessions` or `searchEvents` from code.
29
+
30
+ ### When to choose it
31
+
32
+ Choose it when you want full-text recall over prior sessions with ranking and paging. Choose it together with `dsh-session-query` and the session service; a persistence backend is optional but recommended so persisted history is searchable after restarts. Avoid pointing `path` at the session-persistence database — this package owns a separate derived index.
33
+
34
+ ### Minimal configuration
35
+
36
+ ```yaml
37
+ - name: '@deepseek-ai/dsh-session'
38
+ - name: '@deepseek-ai/dsh-session-query-sqlite'
39
+ config:
40
+ path: /absolute/path/to/session-search.db
41
+ ```
42
+
43
+ | Field | Default | Meaning |
44
+ |---|---|---|
45
+ | `path` | required | Dedicated derived-index SQLite path, or `:memory:`; missing paths are created owner-only on POSIX |
46
+ | `openAt` | `startup` | `startup` opens at activation; `first-search` defers the SQLite module until the first search; `never` disables full-text search while inherited reads stay available |
47
+ | `journalMode` | `wal` | `wal`, `delete`, `truncate`, or `persist` |
48
+ | `defaultLimit` | `20` | Page size when a request omits `limit` |
49
+ | `maxLimit` | `100` | Largest accepted request page size |
50
+ | `snippetChars` | `240` | Maximum snippet length in Unicode code points |
51
+ | `readWindowMax` | `50` | Maximum `before`/`after` raw events for the inherited `readEvent()` |
52
+ | `persistedInspectConcurrency` | `4` | Concurrent persisted-log inspections for inherited batch reads |
53
+
54
+ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-session-query-sqlite) is the exhaustive source for every accepted field and its JSDoc.
55
+
56
+ ### Search behavior
57
+
58
+ `searchSessions` searches the whole corpus and groups results by each session's strongest matching event; `searchEvents` searches one logical session. Queries are literal phrases: they are trimmed and whitespace-normalized, and FTS5 syntax such as quotes, `OR`, `NEAR`, and `*` is treated as data, never as executable query syntax. Metadata filters (session id, cwd, created-at, parent, availability, event seq/time/type/surface) narrow results before ranking. All `current`, `shadowed`, and `log-only` events are searchable by default; pass a surface filter to narrow.
59
+
60
+ Ranking is deterministic: more actual FTS5 highlighted-match spans first, then shorter documents, with event time, session id, and seq breaking ties. Results carry plain-text snippets bounded by `snippetChars` Unicode code points, with no provider-specific numeric score. Pages continue through an opaque `SessionSearchCursor` bound to the exact normalized request; a cursor becomes stale when its relevant corpus changes (`SESSION_QUERY_STALE_CURSOR`), and a within-session cursor survives changes to unrelated sessions while a cross-session cursor does not.
61
+
62
+ The `unicode61` tokenizer matches tokens and phrases, not arbitrary substrings: `AI` does not match the token `BRAID`. Use `ctx.sessionQuery.filterEvents()` with a `text` clause when a literal whitespace-flexible substring scan is required.
63
+
64
+ ### When to defer or disable search
6
65
 
7
- ## Search contract
66
+ With `openAt: first-search`, the service activates without importing `node:sqlite` or opening the index, deferring SQLite's experimental warning until the first actual search; an invalid database fails that first search instead of service activation. With `openAt: never`, full-text search is off for the deployment: `searchSessions` and `searchEvents` fail with `SESSION_QUERY_SEARCH_DISABLED` before any request normalization, while every inherited exact read, filter, and trace keeps working. Requests that exceed the compiled-predicate budget (14 combined predicates across sessions, 13 within a session) or SQLite's portable 32,766-binding limit fail with `SESSION_QUERY_INVALID_FILTER` before statement preparation.
8
67
 
9
- `searchSessions(request, exec?)` returns `SessionSearchHit` pages across the corpus; `searchEvents(request, exec?)` returns `SessionEventSearchHit` pages within one session. Queries are required, trimmed, whitespace-normalized literal phrases. FTS5 syntax such as quotes, `OR`, `NEAR`, and `*` is treated as data rather than executable MATCH syntax. Metadata filters are parameterized SQL predicates applied before ranking. To keep SQLite FTS5 MATCH in a supported outer-predicate context, cross-session requests may compile at most 14 combined session and event filter predicates; within-session requests may compile at most 13 filter predicates because the fixed target-session predicate consumes one slot. Each range endpoint compiles as one predicate. A request exceeding either predicate budget or SQLite's portable limit of 32,766 total bindings, including fixed query and pagination values, fails with `SESSION_QUERY_INVALID_FILTER` before statement preparation.
68
+ ### Failures and recovery
10
69
 
11
- Relevance is source-comparable across persistent and TEMP tables: actual FTS5 highlighted-match span count descending, then stored document code-point length ascending. Event time, session id where applicable, and seq break remaining ties. Cross-session results expose the selected event as `bestMatch`; both scopes derive whitespace-normalized plain text from FTS5 highlight positions and bound it in Unicode code points. Cursors are opaque branded values, bind to the normalized request and service instance, and fail when the relevant generation changes. A within-session cursor survives unrelated-session changes; a cross-session cursor does not.
70
+ Typed `SessionQueryError` failures carry stable codes: `SESSION_QUERY_SEARCH_DISABLED` when search is configured off; `SESSION_QUERY_INDEX_FAILED` when the index cannot open or reconcile; `SESSION_QUERY_SESSION_NOT_FOUND` when a search target is absent; `SESSION_QUERY_STALE_CURSOR` when the corpus changed between pages — retry the complete search call; and `SESSION_QUERY_INVALID_CURSOR` for a cursor that does not belong to this request. Cancellation is honored between synchronous SQLite calls; a statement already executing on the JavaScript thread cannot be interrupted.
12
71
 
13
- All three surfaces (`current`, `shadowed`, and `log-only`) are searchable by default. Pass a surface filter to narrow them.
72
+ -----
14
73
 
15
- ## Source and index lifecycle
74
+ <a id="understand-the-implementation"></a>
75
+ ## Understand the implementation
16
76
 
17
- The service requires `ctx.sessions` and observes optional `ctx.sessionPersistence` dynamically. One serialized state machine compares source-qualified lightweight durable snapshot revisions, non-mutatingly inspects only new or changed logs, extracts shared semantic documents, reconciles changes transactionally, and runs the query. Session queries never invoke the persistence backend's crash-repairing `load()`; an owner attaching during inspection cannot mutate its log, and the stable-observation retry makes the result live-preferred. The TEMP live row still records persisted availability, and the durable base refreshes after that live owner detaches. Repeated queries and an unchanged same-store reopen perform no full durable-log inspection; switching stores, or observing new, changed, deleted, or externally load-repaired sources, reconciles on the next stable observation. Source or transaction failure commits nothing, and the next search retries.
77
+ <details>
78
+ <summary>Implementation internals — click to expand</summary>
18
79
 
19
- `openAt: startup` is the default: service activation imports `node:sqlite`, opens the handle, and fails before publication when the index is invalid. `openAt: first-search` publishes the service as ACTIVE without importing the SQLite module or opening a handle; the first concurrent searches share one readiness promise, and disposal before any search opens nothing. This mode supports compositions that need clean Node 22 startup output by deferring SQLite's experimental warning until the first actual search; it does not suppress a warning at that point. An invalid database likewise fails the first search instead of service activation. `openAt: never` turns full-text search off for the deployment: `searchSessions` and `searchEvents` fail with `SESSION_QUERY_SEARCH_DISABLED` before any request normalization, node:sqlite is never imported or opened, and no source observation or reconciliation runs, while every inherited exact read, filter, and trace on `ctx.sessionQuery` keeps working.
80
+ This section explains the design decisions behind the backend and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package).
20
81
 
21
- Persisted FTS rows live in a dedicated derived database. Connection-local TEMP tables hold live rows, which shadow the durable base for the same session and reveal it when the live owner disappears. Unmounting persistence hides durable rows without discarding the cache; remounting reconciles it. Closing or reopening the database drops every live overlay while retaining persisted rows.
82
+ ### Design philosophy
22
83
 
23
- The database is disposable but reset is guarded: every recognized schema version rejects unknown user tables before mutating journal mode, and only a recognized incompatible schema containing derived tables rebuilds in place. An unrelated or canonical database is refused. Never point `path` at the session-persistence database. On filesystems with POSIX modes, missing directories and databases are created owner-only (`0700` and `0600` before the process umask), and SQLite sidecars inherit the database mode; existing modes are preserved. Exactly one service in one process owns a derived-index path; external writers or a second process are unsupported because generations and TEMP shadow state are connection-owned.
84
+ The backend is built on one separation and three commitments:
24
85
 
25
- ## Configuration
86
+ - **Derived index, never the source store.** The FTS rows live in a dedicated disposable database; the session-persistence database is never opened here.
87
+ - **Live-preferred observation.** One serialized state machine compares persistence snapshot revisions, inspects only new or changed logs, and reconciles in one transaction, so a search reflects the newest stable state.
88
+ - **Generation-bound cursors.** Every corpus change bumps a generation; cursors carry the generation they were created under and fail stale rather than returning a shifted page.
89
+ - **Literal phrases as data.** Caller query text is quoted into one FTS5 phrase so query syntax stays inert, and reserved highlight markers are stripped from documents before indexing.
26
90
 
27
- | Key | Default | Contract |
28
- |---|---:|---|
29
- | `path` | required | Dedicated derived-index SQLite path; `:memory:` is supported. Missing filesystem paths are created owner-only on POSIX filesystems. |
30
- | `openAt` | `startup` | `startup` opens before service activation completes; `first-search` defers the SQLite module and handle until search; `never` disables full-text search (typed `SESSION_QUERY_SEARCH_DISABLED` failures) while inherited reads stay available. |
31
- | `journalMode` | `wal` | `wal`, `delete`, `truncate`, or `persist`. |
32
- | `defaultLimit` | `20` | Page size when a request omits `limit`; at most `Number.MAX_SAFE_INTEGER - 1`. |
33
- | `maxLimit` | `100` | Largest accepted request page size; at most `Number.MAX_SAFE_INTEGER - 1`. |
34
- | `snippetChars` | `240` | Maximum snippet length in Unicode code points. |
35
- | `readWindowMax` | `50` | Maximum `before` or `after` raw-event count for inherited `readEvent()`. |
36
- | `persistedInspectConcurrency` | `4` | Maximum concurrent persisted-log inspections for inherited batch reads; must be a positive safe integer. |
91
+ The design history lives in the [SQLite FTS5 session search note](../../../.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md) and the [unified service decision](../../../.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.md).
37
92
 
38
- ## Tokenizer and limits
93
+ ### Source map
39
94
 
40
- The index uses FTS5 `unicode61`. The trade-off is token/phrase recall rather than arbitrary substring recall: `AI` does not match the token `BRAID`. Use `ctx.sessionQuery.filterEvents()` with a `text` clause when a literal whitespace-flexible substring scan is required. NUL is rejected in queries; reserved highlight markers and NUL in documents are normalized before indexing so presentation markers cannot collide with source text.
95
+ | File | Role |
96
+ |---|---|
97
+ | [`src/index.ts`](src/index.ts) | Service: config, openAt lifecycle, serialized reconciliation, query execution, cursors |
98
+ | [`src/query.ts`](src/query.ts) | Request normalization, parameterized predicates, snippets, predicate and binding budgets |
99
+ | [`src/schema.ts`](src/schema.ts) | Database schema, application-id ownership, in-place reset, owner-only file creation |
100
+ | [`src/invariant.ts`](src/invariant.ts) | Invariant companion (no runtime invariant; boundaries are validated per serialized query) |
41
101
 
42
- Abort signals stop queued work and flow unchanged through snapshot listing and non-mutating inspection. Once source work starts, the serialized state machine awaits that backend promise itself—even when a backend ignores cancellation—then checks the signal before starting any further listing, inspection, reconciliation, or query work. The caller therefore observes cancellation only after started backend work is quiescent, and a later search cannot enter the serializer while that cleanup is pending. Node's synchronous `DatabaseSync` API cannot interrupt a metadata or MATCH statement already executing on the JavaScript thread; signals are checked immediately before and after those non-preemptible calls.
102
+ ### Index lifecycle
43
103
 
104
+ Persisted FTS rows live in a dedicated derived database and survive restarts; live sessions use connection-local TEMP tables that shadow the durable base for the same session and reveal it again when the live owner detaches. Each search runs one serialized observation: list persistence snapshots, compare per-session revisions with the indexed rows, inspect only new or changed logs, extract semantic documents, and commit the reconciliation in one transaction before running the query. Repeated queries and unchanged reopens inspect nothing; switching stores or observing new, changed, deleted, or externally repaired sources reconciles on the next stable observation. Source or transaction failure commits nothing and the next search retries.
105
+
106
+ ### Schema ownership
107
+
108
+ The database carries an application id and schema version 8. Opening refuses a file owned by another application or a canonical database, rejects unknown user tables, and only a recognized incompatible derived schema resets in place — so an unrelated or session-persistence database is never touched. On POSIX filesystems, missing directories and database files are created owner-only (`0700` and `0600` before the process umask). Exactly one service in one process owns a derived-index path; generations and TEMP shadow state are connection-owned.
109
+
110
+ </details>
111
+
112
+ -----
113
+
114
+ <a id="further-exploration"></a>
115
+ ## Further Exploration
116
+
117
+ Read these pages when the package-level contract is not enough. They move from the shared query service to the type-level contract and the design evidence.
118
+
119
+ - [Session Query subsystem reference](../../../docs/subsystems/session-query.md) — the full type-level contract this backend implements.
120
+ - [dsh-session-query](../session-query/README.md) — the service definition: exact reads, filters, and traces this backend inherits.
121
+ - [dsh-tool-session-query](../tool-session-query/README.md) — the model-facing consumer that calls these search methods.
122
+ - [SQLite FTS5 session search](../../../.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md) — search semantics, reconciliation, and the tokenizer decision.
123
+ - [JSONL session persistence](../../session/session-persistence-jsonl/README.md) — the authoritative Session store this disposable index observes; keep its root separate from this package's database path.
124
+
125
+ -----
126
+
127
+ <a id="model-experience"></a>
44
128
  ## Model Experience
45
129
 
46
- None, as this trusted search backend returns hits only to callers and registers no model-facing prompt, schema, tool, or message.
130
+ None, as the search backend returns hits only to callers and registers nothing model-facing.
47
131
 
48
132
  #### KV Cache effect
49
133
 
@@ -51,7 +135,26 @@ None; this package neither assembles nor sends a provider request.
51
135
 
52
136
  ## Known Limitations and Deferred Work
53
137
 
138
+ <a id="known-limitations-and-deferred-work"></a>
139
+
140
+
141
+ These limits define when this package is a poor fit or needs special operational care. They are current package constraints, not a general SQLite comparison or a task backlog.
142
+
54
143
  - **No caller authorization** — this is a trusted context-wide service; a model tool or UI must enforce its own access policy.
55
144
  - **Synchronous query execution** — `DatabaseSync` blocks the JavaScript thread during MATCH execution and cannot interrupt a statement already running.
56
145
  - **Token recall, not arbitrary substrings** — the `unicode61` tokenizer does not match substrings inside a larger token; use `filterEvents()` for literal scans.
57
146
  - **Single-owner derived index** — one service in one process must own each index path; external writers and multi-process sharing are unsupported.
147
+
148
+ <a id="dev-note"></a>
149
+ ### Dev Note
150
+
151
+ <details>
152
+ <summary>Working context for maintainers — click to expand</summary>
153
+
154
+ This Dev Note is working context for maintainers: open design questions and directions that are not decided. It is explicitly non-authoritative — shipped behavior, limits, and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
155
+
156
+ #### Future: alternate tokenizers and search providers
157
+
158
+ The `unicode61` tokenizer choice trades substring recall for index size and two-character token support; the trigram alternative was measured and rejected. Switching tokenizers or adding another search backend would change indexed recall and require its own reconciliation and generation story.
159
+
160
+ </details>
package/README.zh.md CHANGED
@@ -1,57 +1,160 @@
1
+ ---
2
+ description: "面向部署方与维护者的 SQLite FTS5 会话历史全文搜索后端,用于选择、配置或排查查询服务之上的全文搜索。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-session-query-sqlite
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- 具体 `ctx.sessionQuery` 提供方。`SqliteSessionQueryEngine` 从 Service Definition 包继承精确读取、跟踪和提供方无关的过滤,并使用 SQLite FTS5 实现其两个全文方法。搜索使用实时优先的逻辑会话语料库,并按每个会话中匹配度最高的事件对跨会话结果分组。
10
+ ## 概述
11
+
12
+ `dsh-session-query-sqlite` 用 SQLite FTS5 索引搜索会话历史,返回按会话分组或会话内排序、游标分页的结果。与 `dsh-session-query` 一起挂载,即可同时获得全文搜索与完整查询表面——精确读取、过滤与追踪。实时会话从内存索引,持久化会话从专用派生索引数据库索引,因此结果始终反映最新状态,且不触碰会话持久化存储。搜索是可选能力,已发布组合默认关闭:`openAt` 决定索引在启动时、首次搜索时打开,还是永不打开。设置与用法在前;实现内部细节放在下方可折叠的开发者章节中。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 当组合需要对会话历史进行排序后的全文搜索时——例如 Web 内容搜索或 `/resume` 既往工作检索——挂载本包。常用路径是显式的:挂载插件、给它一个专用数据库路径,然后从代码调用 `ctx.sessionQuery.searchSessions` 或 `searchEvents`。
29
+
30
+ ### 何时选择
31
+
32
+ 当你想对既往会话进行带排序与分页的全文召回时选择它。它与 `dsh-session-query` 和会话服务一起使用;持久化后端可选但建议挂载,这样重启后持久化历史仍可搜索。不要把 `path` 指向 session-persistence 数据库——本包拥有独立的派生索引。
33
+
34
+ ### 最小配置
35
+
36
+ ```yaml
37
+ - name: '@deepseek-ai/dsh-session'
38
+ - name: '@deepseek-ai/dsh-session-query-sqlite'
39
+ config:
40
+ path: /absolute/path/to/session-search.db
41
+ ```
42
+
43
+ | 字段 | 默认值 | 含义 |
44
+ |---|---|---|
45
+ | `path` | 必填 | 专用派生索引 SQLite 路径,或 `:memory:`;POSIX 上缺失的路径会以仅所有者可访问的方式创建 |
46
+ | `openAt` | `startup` | `startup` 在激活时打开;`first-search` 把 SQLite 模块推迟到首次搜索;`never` 关闭全文搜索,继承的读取保持可用 |
47
+ | `journalMode` | `wal` | `wal`、`delete`、`truncate` 或 `persist` |
48
+ | `defaultLimit` | `20` | 请求省略 `limit` 时的分页大小 |
49
+ | `maxLimit` | `100` | 接受的最大请求分页大小 |
50
+ | `snippetChars` | `240` | 按 Unicode 码点计算的最大 snippet 长度 |
51
+ | `readWindowMax` | `50` | 继承的 `readEvent()` 的 `before`/`after` 原始事件数上限 |
52
+ | `persistedInspectConcurrency` | `4` | 继承批量读取的并发持久化日志检查数 |
53
+
54
+ 生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-session-query-sqlite)是每个受支持字段及其 JSDoc 的穷尽式真源。
55
+
56
+ ### 搜索行为
57
+
58
+ `searchSessions` 搜索整个语料库,并按每个会话匹配最强的事件分组结果;`searchEvents` 搜索一个逻辑会话。查询是字面短语:首尾空白会被移除、内部空白会被规范化,引号、`OR`、`NEAR` 和 `*` 等 FTS5 语法被视为数据,绝不作为可执行查询语法。元数据过滤器(会话 id、cwd、创建时间、父级、可用性、事件 seq/时间/类型/表层)在排序前缩小结果。默认搜索全部 `current`、`shadowed` 与 `log-only` 事件;传入表层过滤器可缩小范围。
59
+
60
+ 排序是确定性的:实际 FTS5 高亮匹配 span 更多的在前,然后文档更短的在前,事件时间、会话 id 与 seq 打破平局。结果携带按 `snippetChars` 个 Unicode 码点截断的纯文本摘录,没有提供方专用数值分数。分页通过不透明 `SessionSearchCursor` 延续,游标绑定到规范化后的确切请求;相关语料库变化时游标变为陈旧(`SESSION_QUERY_STALE_CURSOR`),会话内游标可在不相关会话变化后延续,跨会话游标则不能。
61
+
62
+ `unicode61` tokenizer 匹配 token 与短语,而非任意子字符串:`AI` 不匹配 token `BRAID`。需要执行字面、空白灵活的字符串子串扫描时,使用带 `text` 子句的 `ctx.sessionQuery.filterEvents()`。
63
+
64
+ ### 何时推迟或关闭搜索
6
65
 
7
- ## 搜索约定
66
+ 使用 `openAt: first-search` 时,服务在不导入 `node:sqlite`、不打开索引的情况下激活,把 SQLite 的实验性警告推迟到首次实际搜索;无效数据库让首次搜索失败,而不是服务激活失败。使用 `openAt: never` 时,全文搜索对该部署关闭:`searchSessions` 与 `searchEvents` 在任何请求规范化之前就以 `SESSION_QUERY_SEARCH_DISABLED` 失败,而继承的全部精确读取、过滤与追踪保持可用。请求超过编译谓词预算(跨会话 14 个组合谓词、会话内 13 个)或 SQLite 可移植的 32,766 绑定上限时,会在准备语句前以 `SESSION_QUERY_INVALID_FILTER` 失败。
8
67
 
9
- `searchSessions(request, exec?)` 返回跨语料库的 `SessionSearchHit` 分页结果;`searchEvents(request, exec?)` 返回单个会话内的 `SessionEventSearchHit` 分页结果。查询不得省略,首尾空白会被移除,内部空白会被规范化,并按字面短语处理。引号、`OR`、`NEAR` 和 `*` 等 FTS5 语法被视为数据,而非可执行 MATCH 语法。元数据过滤器是在排名前应用的参数化 SQL 谓词。为使 SQLite FTS5 MATCH 保持在受支持的外层谓词上下文中,跨会话请求最多可编译 14 个组合会话与事件过滤谓词;会话内请求最多可编译 13 个过滤谓词,因为固定目标会话谓词占用一个 slot。每个范围端点编译为一个谓词。请求超过任一谓词预算,或超过 SQLite 可移植的 32,766 总绑定上限(包括固定查询和分页值)时,会在准备语句前以 `SESSION_QUERY_INVALID_FILTER` 失败。
68
+ ### 失败与恢复
10
69
 
11
- 持久表和 TEMP 表之间的相关性排名可直接比较:先按实际 FTS5 高亮匹配 span 数降序,再按已存储文档码点长度升序。事件时间、适用时的会话 id 和 seq 打破其余平局。跨会话结果将所选事件公开为 `bestMatch`;两种范围都从 FTS5 高亮位置派生空白规范化的纯文本,并按 Unicode 码点限制长度。游标是带品牌类型的不透明值,绑定到规范化请求和服务实例,并在相关世代变更时失败。会话内游标可在不相关会话变更后延续使用;跨会话游标则不能。
70
+ 带类型的 `SessionQueryError` 失败携带稳定代码:搜索配置为关闭时 `SESSION_QUERY_SEARCH_DISABLED`;索引无法打开或对账时 `SESSION_QUERY_INDEX_FAILED`;搜索目标不存在时 `SESSION_QUERY_SESSION_NOT_FOUND`;语料库在分页之间变化时 `SESSION_QUERY_STALE_CURSOR`——请重试完整的搜索调用;游标不属于该请求时 `SESSION_QUERY_INVALID_CURSOR`。取消在同步 SQLite 调用之间被尊重;已在 JavaScript 线程上执行的语句无法被中断。
12
71
 
13
- 默认可搜索全部三种表层(`current`、`shadowed` 和 `log-only`)。传入表层过滤器可缩小范围。
72
+ -----
14
73
 
15
- ## 来源与索引生命周期
74
+ <a id="understand-the-implementation"></a>
75
+ ## 理解实现
16
76
 
17
- 该服务需要 `ctx.sessions`,并动态观察可选的 `ctx.sessionPersistence`。一个串行化状态机比较来源限定的轻量持久化快照修订,仅以不修改日志的方式检查新日志或已更改日志,提取共享语义文档,以事务方式对账变更,然后运行查询。会话查询绝不会调用持久化后端会修复崩溃的 `load()`;检查期间接入的活动所有者无法修改其日志,稳定观察重试使结果优先使用实时来源。TEMP 实时行仍会记录持久化可用性,而持久基库会在该活动所有者脱离后刷新。重复查询以及同一存储未发生变化的重新打开操作不会执行完整持久化日志检查;切换存储,或观察到新增、已更改、已删除或经外部 load 修复的来源时,会在下次稳定观察时对账。来源或事务失败不会提交任何内容,下一次搜索会重试。
77
+ <details>
78
+ <summary>实现细节——点击展开</summary>
18
79
 
19
- `openAt: startup` 是默认值:服务激活会导入 `node:sqlite` 并打开句柄;如果索引无效,则会在服务发布前失败。`openAt: first-search` 会将服务以 ACTIVE 状态发布,同时不导入 SQLite 模块也不打开句柄;首批并发搜索共享同一个就绪 promise,在任何搜索前 dispose(资源释放)服务时也不会导入模块或打开句柄。此模式通过把 SQLite 的实验性警告推迟到首次实际搜索,支持需要干净 Node 22 启动输出的组合;它不会抑制届时的警告。无效数据库同样会使首次搜索失败,而不是服务激活失败。`openAt: never` 为该部署关闭全文搜索:`searchSessions` 和 `searchEvents` 在任何请求规范化之前就以 `SESSION_QUERY_SEARCH_DISABLED` 失败,node:sqlite 绝不会被导入或打开,也不运行任何来源观察或对账,而 `ctx.sessionQuery` 上继承的全部精确读取、过滤和跟踪保持可用。
80
+ 本节解释后端背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
20
81
 
21
- 持久化 FTS 行位于专用派生数据库中。连接本地 TEMP 表保存实时行,这些行会遮蔽同一会话的持久化基库,并在实时所有者消失后使其重新可见。卸载持久化会隐藏持久行,但不会丢弃缓存;重新挂载会对账缓存。关闭或重新打开数据库会删除全部实时覆盖层,但保留持久行。
82
+ ### 设计理念
22
83
 
23
- 该数据库虽可丢弃重建,但 reset 操作受到保护:每个已识别 schema 版本都会在修改 journal mode 前拒绝未知用户表;只有包含派生表的已识别不兼容 schema 才会原地重建。不相关数据库或规范数据库将被拒绝。绝不能将 `path` 指向 session-persistence 数据库。在支持 POSIX 权限模式的文件系统上,缺失的目录和数据库会以仅所有者可访问的方式创建(进程 umask 前为 `0700` 和 `0600`),SQLite 伴随文件继承数据库的权限模式;现有权限模式保持不变。每个派生索引路径在一个进程中只能由一个服务拥有;不支持外部写入者或第二个进程,因为世代和 TEMP 遮蔽状态由连接持有。
84
+ 本后端建立在一个分离与三项承诺之上:
24
85
 
25
- ## 配置
86
+ - **派生索引,绝不动源存储。** FTS 行存放在专用可丢弃数据库中;这里的代码从不打开 session-persistence 数据库。
87
+ - **实时优先的观察。** 一个串行化状态机比较持久化快照修订,只检查新增或已更改日志,并在一个事务中对账,因此搜索反映最新的稳定状态。
88
+ - **世代绑定的游标。** 每次语料库变化都会递增世代;游标携带其创建时的世代,宁可陈旧失败也不返回偏移后的页面。
89
+ - **字面短语即数据。** 调用方查询文本被引成一个 FTS5 短语,查询语法保持惰性;保留高亮标记在索引前从文档中剥离。
26
90
 
27
- | 键 | 默认值 | 约定 |
28
- |---|---:|---|
29
- | `path` | 必填 | 专用派生索引 SQLite 路径;支持 `:memory:`。在 POSIX 文件系统上,缺失的文件系统路径会以仅所有者可访问的方式创建。 |
30
- | `openAt` | `startup` | `startup` 会在服务激活完成前打开;`first-search` 把 SQLite 模块与句柄推迟到搜索时再加载和打开;`never` 关闭全文搜索(以类型化的 `SESSION_QUERY_SEARCH_DISABLED` 失败),继承的读取保持可用。 |
31
- | `journalMode` | `wal` | `wal`、`delete`、`truncate` 或 `persist`。 |
32
- | `defaultLimit` | `20` | 请求省略 `limit` 时的分页大小;最多为 `Number.MAX_SAFE_INTEGER - 1`。 |
33
- | `maxLimit` | `100` | 接受的最大请求分页大小;最多为 `Number.MAX_SAFE_INTEGER - 1`。 |
34
- | `snippetChars` | `240` | 按 Unicode 码点计算的最大 snippet 长度。 |
35
- | `readWindowMax` | `50` | `before` 或 `after` 的最大原始事件数,用于继承的 `readEvent()`。 |
36
- | `persistedInspectConcurrency` | `4` | 继承批量读取的最大并发持久化日志检查数;必须是正安全整数。 |
91
+ 设计历史记录在 [SQLite FTS5 会话搜索笔记](../../../.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md)与[统一服务决策](../../../.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.md)中。
37
92
 
38
- ## 分词器与限制
93
+ ### 源码地图
39
94
 
40
- 该索引使用 FTS5 `unicode61`。取舍是 token/短语召回而非任意子字符串召回:`AI` 不匹配 token `BRAID`。需要执行字面的空白弹性子字符串扫描时,使用 `ctx.sessionQuery.filterEvents()` 并传入 `text` 子句。查询会拒绝 NUL;文档中的保留高亮标记和 NUL 会在索引前被规范化,使展示标记无法与源文本冲突。
95
+ | 文件 | 职责 |
96
+ |---|---|
97
+ | [`src/index.ts`](src/index.ts) | 服务:配置、openAt 生命周期、串行化对账、查询执行、游标 |
98
+ | [`src/query.ts`](src/query.ts) | 请求规范化、参数化谓词、摘录、谓词与绑定预算 |
99
+ | [`src/schema.ts`](src/schema.ts) | 数据库 schema、application id 归属、原地重置、仅所有者文件创建 |
100
+ | [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件(无运行时不变式;边界在每次串行化查询时校验) |
41
101
 
42
- 中止信号会停止已排队工作,并原样流经快照枚举和非修改式检查。来源工作一旦开始,串行化状态机会自行等待该后端 promise,即使后端忽略取消,之后也会在启动任何进一步的枚举、检查、对账或查询工作前检查信号。因此,调用方只会在已启动后端工作完全停稳后观察到取消,而后续搜索在该清理尚未完成时无法进入 serializer。Node 的同步 `DatabaseSync` API 无法中断已在 JavaScript 线程上执行的元数据或 MATCH 语句;系统会在这些不可抢占调用前后立即检查信号。
102
+ ### 索引生命周期
43
103
 
104
+ 持久化 FTS 行存放在专用派生数据库中并跨重启保留;实时会话使用连接本地 TEMP 表,遮蔽同一会话的持久化基库,并在实时所有者脱离后再次显示基库。每次搜索执行一次串行化观察:列出持久化快照、把逐会话修订与已索引行比较、只检查新增或已更改日志、提取语义文档,并在运行查询前于一个事务中提交对账。重复查询与不变的重新打开不会检查任何内容;切换存储或观察到新增、已更改、已删除或经外部修复的来源时,会在下次稳定观察时对账。来源或事务失败不提交任何内容,下一次搜索重试。
105
+
106
+ ### Schema 归属
107
+
108
+ 数据库携带 application id 与 schema 版本 8。打开时拒绝其他应用程序拥有的文件或规范数据库,拒绝未知用户表;只有已识别的不兼容派生 schema 才会原地重置——因此不相关或 session-persistence 数据库绝不会被触碰。在 POSIX 文件系统上,缺失的目录与数据库文件以仅所有者可访问的方式创建(进程 umask 前为 `0700` 与 `0600`)。每个派生索引路径在一个进程中只能由一个服务拥有;世代与 TEMP 遮蔽状态由连接持有。
109
+
110
+ </details>
111
+
112
+ -----
113
+
114
+ <a id="further-exploration"></a>
115
+ ## 进一步探索
116
+
117
+ 当包级约定不够用时阅读以下页面。它们从共享查询服务逐步进入类型级约定与设计证据。
118
+
119
+ - [会话查询子系统参考](../../../docs/subsystems/session-query.zh.md)——本后端实现的完整类型级约定。
120
+ - [dsh-session-query](../session-query/README.zh.md)——服务定义:本后端继承的精确读取、过滤与追踪。
121
+ - [dsh-tool-session-query](../tool-session-query/README.zh.md)——调用这些搜索方法的面向模型消费方。
122
+ - [SQLite FTS5 会话搜索](../../../.agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.zh.md)——搜索语义、对账与 tokenizer 决策。
123
+ - [JSONL 会话持久化](../../session/session-persistence-jsonl/README.zh.md)——本可丢弃索引观察的权威 Session store;其 root 必须与本包的数据库路径分开。
124
+
125
+ -----
126
+
127
+ <a id="model-experience"></a>
44
128
  ## 模型体验
45
129
 
46
- 无。该可信搜索后端只向调用方返回命中,不注册面向模型的提示词、schema、工具或消息。
130
+ 无,因为该搜索后端只向调用方返回命中,且不注册任何面向模型的内容。
47
131
 
48
132
  #### KV Cache 影响
49
133
 
50
- 无;该包既不组装也不发送提供方请求。
134
+ 无;本包既不组装也不发送提供方请求。
135
+
136
+ ## 已知限制与延期工作
137
+
138
+ <a id="known-limitations-and-deferred-work"></a>
139
+
140
+
141
+ 这些限制说明本包何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用 SQLite 对比或任务积压。
142
+
143
+ - **无调用方授权**——这是上下文范围内的可信服务;模型工具或 UI 必须强制执行自己的访问策略。
144
+ - **同步查询执行**——`DatabaseSync` 在 MATCH 执行期间会阻塞 JavaScript 线程,且无法中断已运行的语句。
145
+ - **Token 召回,而非任意子字符串**——`unicode61` tokenizer 不会匹配更大 token 中的子字符串;对字面扫描使用 `filterEvents()`。
146
+ - **单一所有者的派生索引**——每个索引路径必须仅归一个进程中的一个服务所有;不支持外部写入者与多进程共享。
147
+
148
+ <a id="dev-note"></a>
149
+ ### 开发备注
150
+
151
+ <details>
152
+ <summary>维护者的工作上下文——点击展开</summary>
153
+
154
+ 本开发备注是维护者的工作上下文:开放设计问题与尚未决定的探索方向。它明确不具权威性——已交付的行为、限制与既定理由以上文、包代码和相关 Agent Note 为准。
155
+
156
+ #### 未来:其他 tokenizer 与搜索提供方
51
157
 
52
- ## 已知限制与暂缓事项
158
+ `unicode61` tokenizer 的选择以索引体积与双字符 token 支持换取子字符串召回;trigram 备选方案曾被测量并否决。切换 tokenizer 或增加另一个搜索后端会改变索引召回,并需要各自的对账与世代方案。
53
159
 
54
- - **无调用方授权**:这是上下文范围内的可信服务;模型工具或 UI 必须强制执行自己的访问策略。
55
- - **同步查询执行**:`DatabaseSync` 在 MATCH 执行期间会阻塞 JavaScript 线程,且无法中断已运行的语句。
56
- - **Token 召回,而非任意子字符串**:`unicode61` tokenizer 不会匹配更大 token 中的子字符串;对字面扫描使用 `filterEvents()`。
57
- - **单一所有者的派生索引**:每个索引路径必须仅归一个进程中的一个服务所有;不支持外部写入者和多进程共享。
160
+ </details>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session-query-sqlite",
3
3
  "description": "Concrete ctx.sessionQuery backend with SQLite FTS5 search",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,11 +32,11 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
36
- "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
37
- "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
38
- "@deepseek-ai/cordis": "^4.0.1",
39
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
35
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
36
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
37
+ "@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.3",
38
+ "@deepseek-ai/dsh-session-query": "^0.1.2-alpha.3",
39
+ "@deepseek-ai/cordis": "^4.0.2"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@deepseek-ai/dsh-session-persistence": {
@@ -44,15 +44,16 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@deepseek-ai/schemastery": "^3.18.1"
47
+ "@deepseek-ai/schemastery": "^3.18.2"
48
48
  },
49
49
  "devDependencies": {
50
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
51
- "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
52
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
53
- "@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.2",
54
- "@deepseek-ai/dsh-session-persistence-sqlite": "^0.1.1-rc.2",
55
- "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
56
- "@deepseek-ai/cordis": "^4.0.1"
50
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
51
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
52
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
53
+ "@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.3",
54
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.2-alpha.3",
55
+ "@deepseek-ai/dsh-session-query": "^0.1.2-alpha.3",
56
+ "@deepseek-ai/cordis": "^4.0.2",
57
+ "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3"
57
58
  }
58
59
  }