@deepseek-ai/dsh-session-query-sqlite 0.1.2-alpha.5 → 0.1.3-alpha.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.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: 1db3999186beebc10a6a3c6874122fa65f2787a3
6
- README.zh.md: f6ef419293ab0df3a3edfd171cc60bc62ee4bcb0
5
+ README.md: 81518cb5c902583f91ef3194e7c9311f49ffe229
6
+ README.zh.md: b8a7e268d5d1771d75f8a4fb6cae2e2d33e38169
package/README.md CHANGED
@@ -49,7 +49,8 @@ Choose it when you want full-text recall over prior sessions with ranking and pa
49
49
  | `maxLimit` | `100` | Largest accepted request page size |
50
50
  | `snippetChars` | `240` | Maximum snippet length in Unicode code points |
51
51
  | `readWindowMax` | `50` | Maximum `before`/`after` raw events for the inherited `readEvent()` |
52
- | `persistedInspectConcurrency` | `4` | Concurrent persisted-log inspections for inherited batch reads |
52
+ | `persistedReadConcurrency` | `4` | Concurrent persisted-log reads for inherited batch reads |
53
+ | `preparedSessionCacheSize` | `5` | Cold prepared-Session observations the inherited `observeSession` reader retains for reuse |
53
54
 
54
55
  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
 
@@ -84,11 +85,11 @@ This section explains the design decisions behind the backend and points at the
84
85
  The backend is built on one separation and three commitments:
85
86
 
86
87
  - **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
+ - **Live-preferred observation.** One serialized state machine compares persistence snapshot revisions, reads only new or changed logs through short-lived read handles, and reconciles in one transaction, so a search reflects the newest stable state.
88
89
  - **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
90
  - **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.
90
91
 
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).
92
+ The design history lives in the [SQLite FTS5 session search note](../../../.agents/notes/archived/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).
92
93
 
93
94
  ### Source map
94
95
 
@@ -101,7 +102,7 @@ The design history lives in the [SQLite FTS5 session search note](../../../.agen
101
102
 
102
103
  ### Index lifecycle
103
104
 
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. Both tables retain the exact inherited cut in numeric `seed_length`; reconstructed headers expose only `isSeeded`, while the cut participates in live fingerprints and persisted source revisions. 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
+ 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. Both tables retain the exact inherited cut in numeric `seed_length`; reconstructed headers expose only `isSeeded`, while the cut participates in live fingerprints and persisted source revisions. Each search runs one serialized observation: list persistence snapshots, compare per-session revisions with the indexed rows, read only new or changed logs through a read handle (balancing an interrupted final turn in memory, never writing back), extract semantic documents, and commit the reconciliation in one transaction before running the query. Repeated queries and unchanged reopens read 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
 
106
107
  ### Schema ownership
107
108
 
@@ -119,7 +120,7 @@ Read these pages when the package-level contract is not enough. They move from t
119
120
  - [Session Query subsystem reference](../../../docs/subsystems/session-query.md) — the full type-level contract this backend implements.
120
121
  - [dsh-session-query](../session-query/README.md) — the service definition: exact reads, filters, and traces this backend inherits.
121
122
  - [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
+ - [SQLite FTS5 session search](../../../.agents/notes/archived/feature/2026-07-10-sqlite-session-query-provider.md) — search semantics, reconciliation, and the tokenizer decision.
123
124
  - [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
 
125
126
  -----
package/README.zh.md CHANGED
@@ -49,7 +49,8 @@ kind: "package-reference"
49
49
  | `maxLimit` | `100` | 接受的最大请求分页大小 |
50
50
  | `snippetChars` | `240` | 按 Unicode 码点计算的最大 snippet 长度 |
51
51
  | `readWindowMax` | `50` | 继承的 `readEvent()` 的 `before`/`after` 原始事件数上限 |
52
- | `persistedInspectConcurrency` | `4` | 继承批量读取的并发持久化日志检查数 |
52
+ | `persistedReadConcurrency` | `4` | 继承批量读取的并发持久化日志读取数 |
53
+ | `preparedSessionCacheSize` | `5` | 继承的 `observeSession` 读取器为复用保留的冷 prepared-Session 观察数 |
53
54
 
54
55
  生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-session-query-sqlite)是每个受支持字段及其 JSDoc 的穷尽式真源。
55
56
 
@@ -84,11 +85,11 @@ kind: "package-reference"
84
85
  本后端建立在一个分离与三项承诺之上:
85
86
 
86
87
  - **派生索引,绝不动源存储。** FTS 行存放在专用可丢弃数据库中;这里的代码从不打开 session-persistence 数据库。
87
- - **实时优先的观察。** 一个串行化状态机比较持久化快照修订,只检查新增或已更改日志,并在一个事务中对账,因此搜索反映最新的稳定状态。
88
+ - **实时优先的观察。** 一个串行化状态机比较持久化快照修订,只通过短生命周期读取 handle 读取新增或已更改日志,并在一个事务中对账,因此搜索反映最新的稳定状态。
88
89
  - **世代绑定的游标。** 每次语料库变化都会递增世代;游标携带其创建时的世代,宁可陈旧失败也不返回偏移后的页面。
89
90
  - **字面短语即数据。** 调用方查询文本被引成一个 FTS5 短语,查询语法保持惰性;保留高亮标记在索引前从文档中剥离。
90
91
 
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)中。
92
+ 设计历史记录在 [SQLite FTS5 会话搜索笔记](../../../.agents/notes/archived/feature/2026-07-10-sqlite-session-query-provider.md)与[统一服务决策](../../../.agents/notes/archived/architecture/2026-07-23-unified-session-query-service.md)中。
92
93
 
93
94
  ### 源码地图
94
95
 
@@ -101,7 +102,7 @@ kind: "package-reference"
101
102
 
102
103
  ### 索引生命周期
103
104
 
104
- 持久化 FTS 行存放在专用派生数据库中并跨重启保留;实时会话使用连接本地 TEMP 表,遮蔽同一会话的持久化基库,并在实时所有者脱离后再次显示基库。两类表都在数字 `seed_length` 中保留精确继承切点;重建的 header 只公开 `isSeeded`,而切点参与实时 fingerprint 与持久来源修订。每次搜索执行一次串行化观察:列出持久化快照、把逐会话修订与已索引行比较、只检查新增或已更改日志、提取语义文档,并在运行查询前于一个事务中提交对账。重复查询与不变的重新打开不会检查任何内容;切换存储或观察到新增、已更改、已删除或经外部修复的来源时,会在下次稳定观察时对账。来源或事务失败不提交任何内容,下一次搜索重试。
105
+ 持久化 FTS 行存放在专用派生数据库中并跨重启保留;实时会话使用连接本地 TEMP 表,遮蔽同一会话的持久化基库,并在实时所有者脱离后再次显示基库。两类表都在数字 `seed_length` 中保留精确继承切点;重建的 header 只公开 `isSeeded`,而切点参与实时 fingerprint 与持久来源修订。每次搜索执行一次串行化观察:列出持久化快照、把逐会话修订与已索引行比较、只通过读取 handle 读取新增或已更改日志(在内存中补齐被中断的末尾轮次,从不写回)、提取语义文档,并在运行查询前于一个事务中提交对账。重复查询与不变的重新打开不读取任何内容;切换存储或观察到新增、已更改、已删除或经外部修复的来源时,会在下次稳定观察时对账。来源或事务失败不提交任何内容,下一次搜索重试。
105
106
 
106
107
  ### Schema 归属
107
108
 
@@ -119,7 +120,7 @@ kind: "package-reference"
119
120
  - [会话查询子系统参考](../../../docs/subsystems/session-query.zh.md)——本后端实现的完整类型级约定。
120
121
  - [dsh-session-query](../session-query/README.zh.md)——服务定义:本后端继承的精确读取、过滤与追踪。
121
122
  - [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
+ - [SQLite FTS5 会话搜索](../../../.agents/notes/archived/feature/2026-07-10-sqlite-session-query-provider.md)——搜索语义、对账与 tokenizer 决策。
123
124
  - [JSONL 会话持久化](../../session/session-persistence-jsonl/README.zh.md)——本可丢弃索引观察的权威 Session store;其 root 必须与本包的数据库路径分开。
124
125
 
125
126
  -----
package/lib/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
+ import { SESSION_FORMAT_VERSION, SessionSeq } from "@deepseek-ai/dsh-session";
2
3
  import { Service } from "@deepseek-ai/cordis";
3
4
  import z from "@deepseek-ai/schemastery";
4
- import { SessionSeq } from "@deepseek-ai/dsh-session";
5
- import SessionQueryEngine, { SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY, SESSION_QUERY_READ_WINDOW_MAX, SessionQueryError, SessionSearchCursor, assertSessionHeadersCompatible, buildSessionEventSearchDocuments, materializeSessionEventResultFilters, materializeSessionResultFilters } from "@deepseek-ai/dsh-session-query";
5
+ import SessionQueryEngine, { SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY, SESSION_QUERY_DEFAULT_PREPARED_SESSION_CACHE_SIZE, SESSION_QUERY_READ_WINDOW_MAX, SessionQueryError, SessionSearchCursor, assertSessionHeadersCompatible, buildSessionEventSearchDocuments, materializeSessionEventResultFilters, materializeSessionResultFilters, readColdSessionLog } from "@deepseek-ai/dsh-session-query";
6
6
  import { mkdir, open } from "node:fs/promises";
7
7
  import { dirname, resolve } from "node:path";
8
8
  //#region lib/types/schema.js
@@ -492,7 +492,8 @@ var SqliteSessionQueryEngine = class extends SessionQueryEngine {
492
492
  maxLimit: z.number().step(1).min(1).max(SQLITE_MAX_PAGE_LIMIT).default(100),
493
493
  snippetChars: z.number().step(1).min(1).default(240),
494
494
  readWindowMax: z.number().step(1).min(0).default(SESSION_QUERY_READ_WINDOW_MAX),
495
- persistedInspectConcurrency: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY)
495
+ persistedReadConcurrency: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY),
496
+ preparedSessionCacheSize: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(SESSION_QUERY_DEFAULT_PREPARED_SESSION_CACHE_SIZE)
496
497
  });
497
498
  /** Validated and defaulted backend configuration. */
498
499
  config;
@@ -712,20 +713,21 @@ var SqliteSessionQueryEngine = class extends SessionQueryEngine {
712
713
  let persisted = /* @__PURE__ */ new Map();
713
714
  if (persistence !== void 0) try {
714
715
  const canReuseIndexed = this._lastPersistenceIdentity === void 0 || this._lastPersistenceIdentity === persistenceBinding.identity;
715
- const before = await persistence.listSnapshots(signal);
716
+ const listOptions = signal === void 0 ? void 0 : { signal };
717
+ const before = await persistence.list(listOptions);
716
718
  assertNotAborted(signal);
717
719
  persisted = materializePersistenceSnapshots(before);
718
720
  for (const entry of persisted.values()) {
719
721
  if (canReuseIndexed && indexed.get(entry.header.id)?.revision === entry.revision) continue;
720
722
  if (initiallyLive.has(entry.header.id) || this.ctx.sessions.get(entry.header.id) !== void 0) continue;
721
723
  assertNotAborted(signal);
722
- const loaded = await persistence.inspect(entry.header.id, signal);
724
+ const loaded = await readColdSessionLog(persistence, entry.header.id, signal);
723
725
  assertNotAborted(signal);
724
- assertSessionHeadersCompatible(entry.header, loaded.meta);
725
- entry.loaded = observeSession(loaded.meta, loaded.inheritedEventCount, loaded.events);
726
+ assertSessionHeadersCompatible(entry.header, loaded.header);
727
+ entry.loaded = observeSession(loaded.header, loaded.inheritedEventCount, loaded.events);
726
728
  }
727
729
  assertNotAborted(signal);
728
- const afterSnapshots = await persistence.listSnapshots(signal);
730
+ const afterSnapshots = await persistence.list(listOptions);
729
731
  assertNotAborted(signal);
730
732
  const after = materializePersistenceSnapshots(afterSnapshots);
731
733
  if (!samePersistenceSnapshots(persisted, after)) continue;
@@ -1034,11 +1036,11 @@ function sameSessionIds(before, after) {
1034
1036
  return true;
1035
1037
  }
1036
1038
  function sameHeader(a, b) {
1037
- return a.version === b.version && a.id === b.id && a.createdAt === b.createdAt && a.cwd === b.cwd && a.parentSession === b.parentSession && a.isSeeded === b.isSeeded && (a.delegationDepth ?? 0) === (b.delegationDepth ?? 0) && a.agentPreset === b.agentPreset;
1039
+ return a.id === b.id && a.createdAt === b.createdAt && a.cwd === b.cwd && a.parentSession === b.parentSession && a.isSeeded === b.isSeeded && (a.delegationDepth ?? 0) === (b.delegationDepth ?? 0) && a.agentPreset === b.agentPreset;
1038
1040
  }
1039
1041
  function rowHeader(row) {
1040
1042
  return {
1041
- version: row.version,
1043
+ version: SESSION_FORMAT_VERSION,
1042
1044
  id: row.session_id,
1043
1045
  createdAt: row.created_at,
1044
1046
  ...row.cwd === null ? {} : { cwd: row.cwd },
@@ -1081,7 +1083,8 @@ function resolveConfig(config) {
1081
1083
  maxLimit: config.maxLimit ?? 100,
1082
1084
  snippetChars: config.snippetChars ?? 240,
1083
1085
  readWindowMax: config.readWindowMax ?? SESSION_QUERY_READ_WINDOW_MAX,
1084
- persistedInspectConcurrency: config.persistedInspectConcurrency ?? SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY
1086
+ persistedReadConcurrency: config.persistedReadConcurrency ?? SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY,
1087
+ preparedSessionCacheSize: config.preparedSessionCacheSize ?? SESSION_QUERY_DEFAULT_PREPARED_SESSION_CACHE_SIZE
1085
1088
  };
1086
1089
  if (typeof resolved.path !== "string" || resolved.path.trim().length === 0) throw invalidConfig("path must not be blank");
1087
1090
  if (![
@@ -1093,7 +1096,8 @@ function resolveConfig(config) {
1093
1096
  assertPageLimit("maxLimit", resolved.maxLimit);
1094
1097
  assertPositiveInteger("snippetChars", resolved.snippetChars);
1095
1098
  if (!Number.isInteger(resolved.readWindowMax) || resolved.readWindowMax < 0) throw invalidConfig("readWindowMax must be a non-negative integer");
1096
- if (!Number.isSafeInteger(resolved.persistedInspectConcurrency) || resolved.persistedInspectConcurrency < 1) throw invalidConfig("persistedInspectConcurrency must be a positive safe integer");
1099
+ if (!Number.isSafeInteger(resolved.persistedReadConcurrency) || resolved.persistedReadConcurrency < 1) throw invalidConfig("persistedReadConcurrency must be a positive safe integer");
1100
+ if (!Number.isSafeInteger(resolved.preparedSessionCacheSize) || resolved.preparedSessionCacheSize < 1) throw invalidConfig("preparedSessionCacheSize must be a positive safe integer");
1097
1101
  if (resolved.defaultLimit > resolved.maxLimit) throw invalidConfig("defaultLimit must be less than or equal to maxLimit");
1098
1102
  if (![
1099
1103
  "wal",
@@ -49,8 +49,10 @@ export interface Config extends SessionQueryConfig {
49
49
  maxLimit?: number;
50
50
  /** Maximum snippet length in Unicode code points. Defaults to 240. */
51
51
  snippetChars?: number;
52
- /** Maximum concurrent persisted-log inspections in one inherited batch read. Defaults to 4. */
53
- persistedInspectConcurrency?: number;
52
+ /** Maximum concurrent persisted-log reads in one inherited batch read. Defaults to 4. */
53
+ persistedReadConcurrency?: number;
54
+ /** Maximum cold prepared-Session observations the inherited reader retains for reuse. Defaults to 5. */
55
+ preparedSessionCacheSize?: number;
54
56
  }
55
57
  interface ResolvedConfig {
56
58
  path: string;
@@ -60,7 +62,8 @@ interface ResolvedConfig {
60
62
  maxLimit: number;
61
63
  snippetChars: number;
62
64
  readWindowMax: number;
63
- persistedInspectConcurrency: number;
65
+ persistedReadConcurrency: number;
66
+ preparedSessionCacheSize: number;
64
67
  }
65
68
  /** Concrete SQLite owner of the combined `ctx.sessionQuery` service. */
66
69
  export declare class SqliteSessionQueryEngine extends SessionQueryEngine {
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.2-alpha.5",
4
+ "version": "0.1.3-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,9 +27,9 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
31
- "@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.5",
32
- "@deepseek-ai/dsh-session-query": "^0.1.2-alpha.5",
30
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
31
+ "@deepseek-ai/dsh-session-persistence": "^0.1.3-alpha.2",
32
+ "@deepseek-ai/dsh-session-query": "^0.1.3-alpha.2",
33
33
  "@deepseek-ai/cordis": "^4.0.2"
34
34
  },
35
35
  "peerDependenciesMeta": {
@@ -41,12 +41,12 @@
41
41
  "@deepseek-ai/schemastery": "^3.18.2"
42
42
  },
43
43
  "devDependencies": {
44
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
45
- "@deepseek-ai/dsh-session-persistence": "^0.1.2-alpha.5",
46
44
  "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
47
- "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.2-alpha.5",
48
- "@deepseek-ai/dsh-session-query": "^0.1.2-alpha.5",
49
- "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.5",
50
- "@deepseek-ai/cordis": "^4.0.2"
45
+ "@deepseek-ai/dsh-session": "^0.1.3-alpha.2",
46
+ "@deepseek-ai/dsh-session-persistence": "^0.1.3-alpha.2",
47
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.3-alpha.2",
48
+ "@deepseek-ai/cordis": "^4.0.2",
49
+ "@deepseek-ai/dsh-session-query": "^0.1.3-alpha.2",
50
+ "@deepseek-ai/dsh-session-projection": "^0.1.3-alpha.2"
51
51
  }
52
52
  }