@ppagent/memory 0.1.4 → 0.4.0

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/dist/index.d.ts CHANGED
@@ -27,7 +27,7 @@ interface Paginated<T> {
27
27
  * 多模态消息的单个内容块,与 OpenAI content part 对齐
28
28
  */
29
29
  interface ContentPart {
30
- type: "text" | "image_url" | "file_url";
30
+ type: string;
31
31
  /** type 为 "text" 时的文本内容 */
32
32
  text?: string;
33
33
  /** type 为 "image_url" 时的图片信息 */
@@ -39,6 +39,8 @@ interface ContentPart {
39
39
  url: string;
40
40
  name?: string;
41
41
  };
42
+ /** 保留宿主框架自定义 content part,写入和回读时不丢字段。 */
43
+ [key: string]: unknown;
42
44
  }
43
45
  interface RawMessage {
44
46
  messageId?: string;
@@ -57,6 +59,8 @@ interface RawMessage {
57
59
  * 序列化后存入 LanceDB 的 parts 字段。
58
60
  */
59
61
  parts?: ContentPart[];
62
+ /** 宿主框架的原始消息载荷;记忆框架仅透明保存和回读。 */
63
+ payload?: unknown;
60
64
  usage?: number;
61
65
  metadata?: Record<string, unknown>;
62
66
  createdAt?: number;
@@ -72,6 +76,8 @@ interface StoredMessage {
72
76
  content: string;
73
77
  /** JSON.stringify(ContentPart[]),纯文本消息为 "[]" */
74
78
  parts: string;
79
+ /** JSON.stringify(payload),未提供时为 undefined。 */
80
+ payload?: string;
75
81
  usage: number;
76
82
  metadata: string;
77
83
  vector: number[];
@@ -84,9 +90,17 @@ interface Topic {
84
90
  chatId: string;
85
91
  /** 压缩时自动生成的主题标题(一句话),存量数据可能为空串 */
86
92
  title: string;
87
- detail: string;
93
+ /** 唯一的压缩正文。长度由内容重要性动态决定。 */
88
94
  summary: string;
89
- concise: string;
95
+ /** 压缩结果的 token 数;旧数据缺失时由本地 tokenizer 回填。 */
96
+ tokens: number;
97
+ /** 本 Topic 覆盖的首尾原始消息 id,便于同毫秒边界精确恢复。 */
98
+ startMessageId?: string;
99
+ endMessageId?: string;
100
+ /** @deprecated 仅用于读取尚未迁移的 0.3.x 数据。 */
101
+ detail?: string;
102
+ /** @deprecated 仅用于读取尚未迁移的 0.3.x 数据。 */
103
+ concise?: string;
90
104
  startTime: number;
91
105
  endTime: number;
92
106
  createdAt: number;
@@ -100,8 +114,12 @@ interface Fact {
100
114
  chatId: string;
101
115
  sessionId: string;
102
116
  userId: string;
117
+ /** 同一 level/owner 内用于 upsert 的稳定键;旧数据可能为空。 */
118
+ key?: string;
103
119
  content: string;
104
120
  createdAt: number;
121
+ /** 最近一次同 key 更新时刻;旧数据回落 createdAt。 */
122
+ updatedAt?: number;
105
123
  }
106
124
  interface EntityMeta {
107
125
  messageId?: string;
@@ -179,9 +197,9 @@ interface UpdateEntityOptions {
179
197
  interface CompressOutput {
180
198
  /** 自动生成的主题标题(一句话) */
181
199
  title: string;
182
- detail: string;
183
200
  summary: string;
184
- concise: string;
201
+ /** 模型报告的摘要输出 token;缺失时由本地 tokenizer 计算。 */
202
+ tokens?: number;
185
203
  entities: Array<{
186
204
  name: string;
187
205
  type: string;
@@ -198,10 +216,44 @@ interface CompressOutput {
198
216
  interface SessionEntry {
199
217
  messages: StoredMessage[];
200
218
  totalTokens: number;
219
+ topics: Topic[];
220
+ topicTokens: number;
201
221
  ids: {
202
222
  chatId: string;
203
223
  userId: string;
204
224
  };
225
+ lastModelContextTokens?: number;
226
+ lastAccessAt: number;
227
+ }
228
+ /** getHistoryWindow 返回的原始消息;字段形态与 updateChat 输入保持一致。 */
229
+ interface MemoryRawMessage {
230
+ messageId: string;
231
+ talkerId: string;
232
+ chatId: string;
233
+ userId: string;
234
+ sessionId: string;
235
+ type: MessageType;
236
+ content: string;
237
+ parts?: ContentPart[];
238
+ payload?: unknown;
239
+ usage: number;
240
+ metadata?: Record<string, unknown>;
241
+ createdAt: number;
242
+ }
243
+ interface MemoryContextWindowUsage {
244
+ modelContextTokens: number;
245
+ usableContextTokens: number;
246
+ compressedTokenLimit: number;
247
+ compressedTokens: number;
248
+ rawTokenLimit: number;
249
+ rawTokens: number;
250
+ }
251
+ /** 固定预算的压缩记忆 + 近期未压缩原始消息。 */
252
+ interface MemoryContextWindow {
253
+ sessionId: string;
254
+ compressedContext: string;
255
+ recentMessages: MemoryRawMessage[];
256
+ usage: MemoryContextWindowUsage;
205
257
  }
206
258
  /** 是否为文档构建知识图谱:true/false 显式控制,"auto" 由内置 LLM 判定 */
207
259
  type BuildGraphMode = boolean | "auto";
@@ -264,6 +316,8 @@ interface AddDocumentOptions {
264
316
  userId?: string;
265
317
  chatId?: string;
266
318
  sessionId?: string;
319
+ /** 内容哈希去重范围;默认 session(兼容旧行为)。 */
320
+ scope?: SearchScope;
267
321
  /** 不传则从首个 H1 / sourceName 推断 */
268
322
  title?: string;
269
323
  sourceName?: string;
@@ -305,7 +359,18 @@ interface KnowledgeSearchResult {
305
359
  }
306
360
 
307
361
  interface MemoryConfig {
362
+ /**
363
+ * 向量存储后端:
364
+ * - "auto"(默认):运行时探测 @lancedb/lancedb 原生绑定,可用则 lancedb,否则回退 sqlite
365
+ * (覆盖 Intel Mac / musl 等无预编译二进制的平台)。首次选定后写入数据目录 marker 固化,
366
+ * 避免环境变化导致静默换库(表现为「记忆丢失」)。
367
+ * - "lancedb" / "sqlite":显式指定。
368
+ * 注意:两个后端的数据文件互不相通,切换需自行迁移。
369
+ */
370
+ provider?: "auto" | "lancedb" | "sqlite";
308
371
  lancedbPath: string;
372
+ /** sqlite 后端的数据库文件路径(默认 lancedbPath 同级目录下的 memory.sqlite3) */
373
+ sqlitePath?: string;
309
374
  grafeoPath: string;
310
375
  embeddingBaseUrl: string;
311
376
  embeddingApiKey: string;
@@ -322,16 +387,42 @@ interface MemoryConfig {
322
387
  embeddingBatchSize?: number;
323
388
  /** embedding 批次并发数(默认 2)。*/
324
389
  embeddingConcurrency?: number;
390
+ /** 压缩 Topic 在历史窗口中的固定预算,默认 16K。 */
391
+ compressedContextTokenLimit?: number;
392
+ /** 模型上下文中允许历史记忆使用的比例,默认 0.75。 */
393
+ contextUsageRatio?: number;
394
+ /** 原始消息达到其可用预算的此比例时后台预压缩,默认 0.75。 */
395
+ precompressionRatio?: number;
396
+ /** 单次压缩目标占原始消息预算的比例,默认 0.5。 */
397
+ compressionBatchRatio?: number;
398
+ /** 单次压缩硬上限;0 表示只受 compressionBatchRatio 控制。 */
399
+ compressionBatchTokenLimit?: number;
400
+ /** 单条 Topic 摘要硬上限,实际长度由重要性决定,默认 2048。 */
401
+ topicSummaryMaxTokens?: number;
402
+ /** 未传 getHistoryWindow 模型窗口时使用,默认 256K。 */
403
+ defaultModelContextTokens?: number;
404
+ /** 冷启动最多恢复多久以前的压缩 Topic;0 表示永久。 */
405
+ maxHistoryAgeMs?: number;
406
+ /** 会话缓存空闲释放时间,默认 30 分钟;<=0 关闭。 */
407
+ sessionIdleTtlMs?: number;
408
+ /** 空闲会话扫描周期,默认 1 分钟。 */
409
+ sessionSweepIntervalMs?: number;
410
+ /** @deprecated 0.3.x 原始消息固定阈值;仅为配置兼容保留。 */
325
411
  sessionTokenLimit?: number;
412
+ /** @deprecated 使用 compressedContextTokenLimit。 */
326
413
  historyWindowTokenLimit?: number;
414
+ /** @deprecated 三级 Topic 窗口已移除。 */
327
415
  topicRatio?: [number, number, number];
416
+ /** @deprecated 使用 topicSummaryMaxTokens。 */
328
417
  detailMaxTokens?: number;
418
+ /** 文档摘要仍使用此限制;对话 Topic 使用 topicSummaryMaxTokens。 */
329
419
  summaryMaxTokens?: number;
420
+ /** @deprecated 三级 Topic 窗口已移除。 */
330
421
  conciseMaxTokens?: number;
331
422
  maxConcurrentCompressions?: number;
332
423
  entitySimilarityThreshold?: number;
333
424
  defaultSearchLimit?: number;
334
- /** 每次召回相当于多少毫秒的时间权重加成(默认 3_600_000 即 1 小时)*/
425
+ /** @deprecated 连续历史窗口不再按召回热度重排。 */
335
426
  recallBoostMs?: number;
336
427
  /** 切块策略,目前仅 "markdown-heading"(默认)*/
337
428
  chunkStrategy?: "markdown-heading";
@@ -389,7 +480,122 @@ interface MemoryConfig {
389
480
  interface ResolvedConfig extends Required<MemoryConfig> {
390
481
  }
391
482
 
392
- /** 单表压实结果(compaction + 历史版本清理)*/
483
+ /**
484
+ * 向量存储后端抽象层的类型定义。
485
+ *
486
+ * 设计原则:接口只表达「语义」(结果意味着什么),不表达「机制」(怎么算出来的)。
487
+ * - 混合检索(RRF 融合)、回退策略、业务重排等组合逻辑在共享域层 memory.store.ts 实现,
488
+ * provider 只需提供 vectorSearch / ftsSearch 两个检索原语,保证跨后端行为一致。
489
+ * - ANN、索引维护、存储压实等机制细节由各 provider 自行决定,接口仅以声明式
490
+ * IndexSpec 提示与 capabilities 能力声明的方式暴露。
491
+ * - 过滤条件为结构化对象(而非 SQL 字符串),各 provider 翻译为自己的方言,
492
+ * 从根上消除转义/注入问题。
493
+ */
494
+ type FilterCondition =
495
+ /** 等值匹配 */
496
+ {
497
+ op: "eq";
498
+ field: string;
499
+ value: string | number;
500
+ }
501
+ /** 数值大于 */
502
+ | {
503
+ op: "gt";
504
+ field: string;
505
+ value: number;
506
+ }
507
+ /** 数值大于等于 */
508
+ | {
509
+ op: "gte";
510
+ field: string;
511
+ value: number;
512
+ }
513
+ /** 字段值属于集合。values 为空数组表示「不匹配任何行」 */
514
+ | {
515
+ op: "in";
516
+ field: string;
517
+ values: string[];
518
+ }
519
+ /**
520
+ * 匹配序列化为 JSON 字符串的 metadata 列中的键值对。
521
+ * 语义等同旧 LanceService.buildMetadataFilter 的 LIKE '%"key":value%' 模式:
522
+ * 仅适合标量值精确匹配,嵌套对象/含特殊字符的值不保证可靠。
523
+ */
524
+ | {
525
+ op: "jsonContains";
526
+ field: string;
527
+ key: string;
528
+ value: string | number | boolean;
529
+ };
530
+ /** 多个条件为 AND 关系。空数组等同于不过滤。 */
531
+ type Filter = FilterCondition[];
532
+ type ColumnType =
533
+ /** UTF-8 文本 */
534
+ "text"
535
+ /** 32 位整数 */
536
+ | "int"
537
+ /** 64 位整数(毫秒时间戳等)。JS 侧统一以 number 读写 */
538
+ | "long"
539
+ /** 定长 float32 向量,维度由 TableDef.vectorDimension 决定 */
540
+ | "vector";
541
+ interface ColumnDef {
542
+ name: string;
543
+ type: ColumnType;
544
+ /** 默认 false。nullable 列用于 schema 演进的兼容(旧数据无此列) */
545
+ nullable?: boolean;
546
+ }
547
+ interface IndexSpec {
548
+ column: string;
549
+ /**
550
+ * - scalar:标量索引(btree 等价物),加速等值/范围过滤
551
+ * - fts:全文检索索引(BM25)
552
+ * - vector:向量检索加速提示。当前所有 provider 均为精确暴力扫描(ANN 暂不启用,
553
+ * 见 plans/vector-db-replacement-research.md 决议),该值仅作为将来启用 ANN 的声明位。
554
+ */
555
+ kind: "scalar" | "fts" | "vector";
556
+ }
557
+ interface TableDef {
558
+ name: string;
559
+ columns: ColumnDef[];
560
+ /** 有 vector 列时必填 */
561
+ vectorDimension?: number;
562
+ indexes: IndexSpec[];
563
+ }
564
+ interface OrderBySpec {
565
+ column: string;
566
+ ascending: boolean;
567
+ }
568
+ interface QueryOptions {
569
+ filter?: Filter;
570
+ orderBy?: OrderBySpec[];
571
+ limit?: number;
572
+ /** 只取指定列(性能优化,如 trendDaily 只读 created_at)。省略取全部列 */
573
+ select?: string[];
574
+ }
575
+ interface VectorSearchOptions {
576
+ filter?: Filter;
577
+ limit: number;
578
+ }
579
+ interface FtsSearchOptions {
580
+ /** 参与全文匹配的列(须已建 fts 索引) */
581
+ columns: string[];
582
+ filter?: Filter;
583
+ limit: number;
584
+ }
585
+ /** 通用行类型:列名 → 值。vector 列为 number[],long 列为 number */
586
+ type Row = Record<string, unknown>;
587
+ /**
588
+ * 向量检索结果附带 _distance(L2 平方距离,越小越相近)。
589
+ * 各 provider 必须保持相同度量(L2),依赖距离阈值的上层逻辑才能跨后端一致。
590
+ */
591
+ type ScoredRow = Row & {
592
+ _distance: number;
593
+ };
594
+ /** FTS 检索结果附带 _score(BM25 相关度,越大越相关;仅用于排序,绝对值跨后端无可比性) */
595
+ type FtsRow = Row & {
596
+ _score: number;
597
+ };
598
+ /** 单表压实结果。不同后端的语义映射:LanceDB=碎片合并+版本清理;SQLite=incremental vacuum 等 */
393
599
  interface StorageOptimizeResult {
394
600
  table: string;
395
601
  fragmentsRemoved: number;
@@ -398,133 +604,46 @@ interface StorageOptimizeResult {
398
604
  oldVersionsRemoved: number;
399
605
  bytesRemoved: number;
400
606
  }
401
- declare class LanceService {
402
- private readonly config;
403
- private conn;
404
- private messagesTable;
405
- private topicsTable;
406
- private factsTable;
407
- private sessionsTable;
408
- private documentsTable;
409
- private chunksTable;
410
- private isNewMessagesTable;
411
- private isNewTopicsTable;
412
- private isNewSessionsTable;
413
- private isNewDocumentsTable;
414
- private isNewChunksTable;
415
- constructor(config: ResolvedConfig);
416
- init(): Promise<void>;
417
- /**
418
- * 按需补齐索引:先经 listIndices 判存在,缺失的列才 createIndex(且 replace:false)。
419
- * createIndex 默认 replace:true 会在每次启动时全量重建索引并提交新表版本——
420
- * 这正是历史上「启动越来越慢 + _versions 目录膨胀」的根源,绝不可回退到无条件 createIndex。
421
- */
422
- private ensureIndexes;
423
- /**
424
- * 存储压实:逐表执行碎片合并 + 清理 retentionMs 之前的历史版本。
425
- * 嵌入式场景下 LanceDB 不会自动做这件事,长期运行后版本/碎片无限累积会显著拖慢启动与查询。
426
- */
427
- optimizeStorage(retentionMs?: number): Promise<StorageOptimizeResult[]>;
428
- /**
429
- * 为存量 messages 表添加 parts 列(如果缺失)。
430
- * LanceDB 0.14+ 支持 addColumns;旧版本会 throw,此时 rowToMessage 的 ?? "[]" 兜底。
431
- */
432
- private _ensurePartsColumn;
433
- /**
434
- * 为存量 topics 表添加 title 列(如果缺失)。
435
- * 旧版本不支持 addColumns 时忽略,读取时 rowToTopic 的 ?? "" 兜底。
436
- */
437
- private _ensureTopicTitleColumn;
438
- addMessages(messages: StoredMessage[]): Promise<void>;
439
- addTopic(topic: Topic): Promise<void>;
440
- updateTopicRecallCount(summaryId: string, count: number): Promise<void>;
441
- getRecentTopics(chatId: string, userId: string, n1: number, n2: number, n3: number): Promise<{
442
- detail: Topic[];
443
- summary: Topic[];
444
- concise: Topic[];
445
- }>;
446
- getMessagesSince(sessionId: string, since: number, limit?: number): Promise<StoredMessage[]>;
447
- getLatestMessages(sessionId: string, limit: number): Promise<StoredMessage[]>;
448
- /** 取某会话的全部消息(按 createdAt 升序),供管理面板分页切片使用 */
449
- getAllMessagesBySession(sessionId: string): Promise<StoredMessage[]>;
450
- searchMessages(vector: number[], filter?: string, limit?: number): Promise<Array<StoredMessage & {
451
- _distance: number;
452
- }>>;
453
- hybridSearchMessages(query: string, vector: number[], filter?: string, limit?: number): Promise<StoredMessage[]>;
454
- hybridSearchTopics(query: string, vector: number[], filter?: string, limit?: number): Promise<Topic[]>;
455
- saveFact(fact: Fact): Promise<void>;
456
- getAllFacts(): Promise<Fact[]>;
457
- getAllSessionIds(): Promise<string[]>;
458
- insertSession(session: Session): Promise<void>;
459
- upsertSession(session: Session): Promise<void>;
460
- getAllSessions(): Promise<Session[]>;
461
- deleteSession(sessionId: string): Promise<void>;
462
- /** 全量读取 topics(管理面板列表用,不做 recall 加权排序,按 endTime 倒序)*/
463
- getAllTopics(): Promise<Topic[]>;
464
- /** 删除单条 fact */
465
- deleteFact(factId: string): Promise<void>;
466
- /** 删除某会话下的全部消息(级联删除会话时使用)*/
467
- deleteMessagesBySession(sessionId: string): Promise<void>;
468
- /** 删除某会话下的全部 topics(级联删除会话时使用)*/
469
- deleteTopicsBySession(sessionId: string): Promise<void>;
470
- /** 各表行数统计(概览卡片用)*/
471
- countAll(): Promise<{
472
- sessions: number;
473
- messages: number;
474
- topics: number;
475
- facts: number;
476
- }>;
477
- /**
478
- * 按天聚合最近 days 天的活跃趋势(概览图表用)。
479
- * 返回连续日期序列(含无数据的零值天),按本地日期分桶。
480
- */
481
- trendDaily(days: number): Promise<{
482
- date: string;
483
- sessions: number;
484
- messages: number;
485
- facts: number;
486
- }[]>;
487
- addDocument(doc: Document): Promise<void>;
488
- addChunks(chunks: Chunk[]): Promise<void>;
489
- updateDocumentGraphFlag(docId: string, hasGraph: boolean): Promise<void>;
490
- getDocument(docId: string): Promise<Document | null>;
491
- /** 查找同域同 hash 的文档(去重用)*/
492
- findDocumentByHash(contentHash: string, filter?: string): Promise<Document | null>;
493
- /** 按域过滤返回 docId 列表(chunkRedundantIds=false 时用于 chunk 过滤)*/
494
- getDocIdsByDomain(filter?: string): Promise<string[]>;
495
- /** 文档级粗召回:对摘要向量做向量搜索,定位候选文档 */
496
- searchDocuments(vector: number[], filter?: string, limit?: number): Promise<Array<Document & {
497
- _distance: number;
498
- }>>;
499
- /** 知识片段混合检索(BM25 + 向量),失败回退纯向量 */
500
- searchChunks(query: string, vector: number[], filter?: string, limit?: number): Promise<Chunk[]>;
501
- deleteDocument(docId: string): Promise<void>;
502
- deleteChunksByDoc(docId: string): Promise<void>;
503
- /** 全量读取文档(管理面板用),按更新时间倒序 */
504
- getAllDocuments(): Promise<Document[]>;
505
- /** 知识库行数统计 */
506
- countKnowledge(): Promise<{
507
- documents: number;
508
- chunks: number;
509
- }>;
510
- /**
511
- * 根据 metadata 字段内容构建 SQL LIKE 过滤条件。
512
- *
513
- * metadata 以 JSON 字符串存储,此方法将键值对转换为 SQL LIKE 表达式,
514
- * 可直接传给 searchMessages / hybridSearchTopics 的 filter 参数。
515
- *
516
- * 示例:buildMetadataFilter({ env: "prod", version: 2 })
517
- * → `metadata LIKE '%"env":"prod"%' AND metadata LIKE '%"version":2%'`
518
- *
519
- * 注意:仅适合简单标量值(字符串、数字、布尔)的精确匹配。
520
- * 复杂嵌套对象或含空格的 JSON 值可能无法可靠匹配。
521
- */
522
- static buildMetadataFilter(conditions: Record<string, string | number | boolean>): string;
607
+ interface ProviderCapabilities {
608
+ /** 是否支持原生 FTS(BM25)。false 时域层混合检索自动降级为纯向量 */
609
+ fts: boolean;
610
+ /** 向量检索是否为 ANN 近似(false = 精确暴力)。当前所有 provider 均为 false */
611
+ ann: boolean;
612
+ /** optimize() 是否有实际效果(LanceDB 版本回收必需;SQLite 可选) */
613
+ optimize: boolean;
614
+ }
615
+ /**
616
+ * 向量存储后端需要实现的最小原语集合。
617
+ *
618
+ * 实现约定:
619
+ * - init 必须幂等:表已存在则打开并按 TableDef 补齐缺失的列(schema 演进)与索引;
620
+ * 不存在则创建。
621
+ * - 所有 filter 参数为结构化 Filter,provider 内部翻译为自己的方言并负责转义/参数化。
622
+ * - vectorSearch 使用 L2 距离,返回按 _distance 升序的前 limit 行。
623
+ * - ftsSearch 返回按 _score(BM25)降序的前 limit 行;查询词的分词由 provider 负责
624
+ * (中文场景 sqlite provider 使用 jieba;lancedb provider 使用 tantivy 内置分词)。
625
+ * - add/update/delete 需保证同一表内的派生结构(如 sqlite 的 FTS 影子表)事务一致。
626
+ */
627
+ interface VectorStoreProvider {
628
+ /** provider 标识,用于日志与数据目录 marker */
629
+ readonly kind: string;
630
+ init(tables: TableDef[]): Promise<void>;
631
+ add(table: string, rows: Row[]): Promise<void>;
632
+ update(table: string, values: Row, filter: Filter): Promise<void>;
633
+ deleteWhere(table: string, filter: Filter): Promise<void>;
634
+ query(table: string, opts?: QueryOptions): Promise<Row[]>;
635
+ vectorSearch(table: string, vector: number[], opts: VectorSearchOptions): Promise<ScoredRow[]>;
636
+ ftsSearch(table: string, query: string, opts: FtsSearchOptions): Promise<FtsRow[]>;
637
+ count(table: string, filter?: Filter): Promise<number>;
638
+ /** 存储压实与历史版本回收。retentionMs:保留多久内的历史版本(不支持版本概念的后端可忽略) */
639
+ optimize(retentionMs: number): Promise<StorageOptimizeResult[]>;
640
+ capabilities(): ProviderCapabilities;
641
+ close(): Promise<void>;
523
642
  }
524
643
 
525
644
  declare class MemoryManager {
526
645
  private readonly config;
527
- private readonly lance;
646
+ private readonly store;
528
647
  private readonly grafeo;
529
648
  private readonly embed;
530
649
  private readonly llm;
@@ -534,7 +653,13 @@ declare class MemoryManager {
534
653
  private readonly knowledgeManager;
535
654
  private readonly sessionMap;
536
655
  private optimizeTimer?;
656
+ private sessionSweepTimer?;
537
657
  private optimizeRunning;
658
+ private optimizeTask?;
659
+ private destroyTask?;
660
+ private readonly hydration;
661
+ private readonly pendingWrites;
662
+ private warnedDefaultModelContext;
538
663
  constructor(config: MemoryConfig);
539
664
  init(): Promise<void>;
540
665
  /** 后台压实的统一入口:防重入(上一轮未结束则跳过),失败仅告警不影响服务。*/
@@ -544,13 +669,17 @@ declare class MemoryManager {
544
669
  * @param retentionMs 保留多久内的历史版本,默认取配置 optimizeVersionRetentionMs
545
670
  */
546
671
  optimizeStorage(retentionMs?: number): Promise<StorageOptimizeResult[]>;
547
- private restoreFromStorage;
672
+ private ensureSessionHydrated;
673
+ private isSessionBusy;
674
+ private waitForPendingWrites;
675
+ private queueSessionWrite;
548
676
  updateChat(messages: RawMessage[], opts?: UpdateChatOptions): Promise<void>;
677
+ private doUpdateChat;
549
678
  flushChat(sessionId?: string, opts?: {
550
679
  wait?: boolean;
551
680
  waitGraph?: boolean;
552
681
  }): Promise<void>;
553
- updateFacts(content: string, level: FactLevel, userId: string, chatId: string, sessionId?: string): Promise<void>;
682
+ updateFacts(content: string, level: FactLevel, userId: string, chatId: string, sessionId?: string, key?: string): Promise<void>;
554
683
  updateEntity(entities: Entity[], relations: Relation[], context?: UpdateEntityOptions): Promise<void>;
555
684
  search(opts: SearchOptions): Promise<SearchResult[]>;
556
685
  ask(opts: SearchOptions & {
@@ -566,8 +695,9 @@ declare class MemoryManager {
566
695
  * 返回 user 级(userId) ∪ chat 级(chatId),按时间正序拼接。
567
696
  */
568
697
  getFactsForContext(userId: string, chatId: string): Promise<string>;
569
- getHistoryWindow(sessionId: string): string;
570
- private buildLanceFilter;
698
+ getHistoryWindow(sessionId: string, modelContextTokens?: number): Promise<MemoryContextWindow>;
699
+ private calculateWindowUsage;
700
+ private buildScopeFilter;
571
701
  private deserializeSession;
572
702
  private serializeSession;
573
703
  getRecentMessages(sessionId: string, limit: number): Promise<StoredMessage[]>;
@@ -652,7 +782,7 @@ declare class MemoryManager {
652
782
  /** 由 chatId 反查所属 userId:优先用会话表映射,兜底用 chat 级 fact 自身。 */
653
783
  private resolveChatOwner;
654
784
  /** 手动新增一条事实 */
655
- addFact(content: string, level: FactLevel, userId: string, chatId: string, sessionId?: string): Promise<void>;
785
+ addFact(content: string, level: FactLevel, userId: string, chatId: string, sessionId?: string, key?: string): Promise<void>;
656
786
  /** 删除单条事实,返回是否命中 */
657
787
  deleteFact(factId: string): Promise<boolean>;
658
788
  /**
@@ -703,9 +833,139 @@ declare class MemoryManager {
703
833
  chatId?: string;
704
834
  sessionId?: string;
705
835
  } | undefined, page: PageParams): Promise<Paginated<Document>>;
706
- destroy(): void;
836
+ destroy(): Promise<void>;
707
837
  }
708
838
 
839
+ interface MemoryMigrationReport {
840
+ provider: string;
841
+ topicsScanned: number;
842
+ topicsUpdated: number;
843
+ messagesScanned: number;
844
+ messagesUpdated: number;
845
+ factsScanned: number;
846
+ factsUpdated: number;
847
+ }
848
+ /**
849
+ * 记忆系统的存储域层:保持原 LanceService 的公开方法面,内部组合
850
+ * VectorStoreProvider 的通用原语实现。业务逻辑(混合检索融合、话题回退、
851
+ * recall 加权重排)全部在此层,各后端 provider 不感知。
852
+ */
853
+ declare class MemoryStore {
854
+ private readonly config;
855
+ private provider;
856
+ private readonly providerOverride?;
857
+ /** provider 省略时在 init() 阶段经 provider.resolver 自动探测创建(测试可显式注入) */
858
+ constructor(config: ResolvedConfig, provider?: VectorStoreProvider);
859
+ /** 当前后端 provider 标识(日志/诊断用) */
860
+ get providerKind(): string;
861
+ init(): Promise<void>;
862
+ close(): Promise<void>;
863
+ /**
864
+ * 存储压实(碎片合并 + 历史版本清理)。
865
+ * LanceDB 后端长期运行必须定期执行;SQLite 后端为可选的空间回收。
866
+ */
867
+ optimizeStorage(retentionMs?: number): Promise<StorageOptimizeResult[]>;
868
+ addMessages(messages: StoredMessage[]): Promise<void>;
869
+ /** 0.3.x → 0.4.x 数据回填;幂等,不调用 LLM、不删除原始消息。 */
870
+ migrateLegacyData(): Promise<MemoryMigrationReport>;
871
+ /** message_id 稳定时更新原行,否则新增;用于流式 assistant 消息最终态覆盖。 */
872
+ upsertMessages(messages: StoredMessage[]): Promise<void>;
873
+ getMessagesSince(sessionId: string, since: number, limit?: number): Promise<StoredMessage[]>;
874
+ getMessagesAfterBoundary(sessionId: string, endTime: number, endMessageId?: string): Promise<StoredMessage[]>;
875
+ getLatestMessages(sessionId: string, limit: number): Promise<StoredMessage[]>;
876
+ /** 取某会话的全部消息(按 createdAt 升序),供管理面板分页切片使用 */
877
+ getAllMessagesBySession(sessionId: string): Promise<StoredMessage[]>;
878
+ searchMessages(vector: number[], filter?: Filter, limit?: number): Promise<Array<StoredMessage & {
879
+ _distance: number;
880
+ }>>;
881
+ hybridSearchMessages(query: string, vector: number[], filter?: Filter, limit?: number): Promise<StoredMessage[]>;
882
+ hybridSearchTopics(query: string, vector: number[], filter?: Filter, limit?: number): Promise<Topic[]>;
883
+ /**
884
+ * 通用混合检索:FTS(BM25)与向量两路各取 limit*HYBRID_OVERFETCH 候选,RRF 融合。
885
+ * FTS 不可用(后端不支持或查询失败)时降级为纯向量。返回融合后的完整候选序列(未截断)。
886
+ */
887
+ private hybridSearch;
888
+ private keyColumnOf;
889
+ addTopic(topic: Topic): Promise<void>;
890
+ updateTopic(topic: Topic): Promise<void>;
891
+ updateTopicRecallCount(summaryId: string, count: number): Promise<void>;
892
+ incrementTopicRecallCounts(topics: Topic[]): Promise<void>;
893
+ getTopicsBySession(sessionId: string, since?: number): Promise<Topic[]>;
894
+ getRecentTopics(chatId: string, userId: string, n1: number, n2: number, n3: number): Promise<{
895
+ detail: Topic[];
896
+ summary: Topic[];
897
+ concise: Topic[];
898
+ }>;
899
+ /** 全量读取 topics(管理面板列表用,不做 recall 加权排序,按 endTime 倒序)*/
900
+ getAllTopics(): Promise<Topic[]>;
901
+ deleteTopicsBySession(sessionId: string): Promise<void>;
902
+ deleteTopicsByIds(summaryIds: string[]): Promise<void>;
903
+ saveFact(fact: Fact): Promise<void>;
904
+ updateFact(fact: Fact): Promise<void>;
905
+ getAllFacts(): Promise<Fact[]>;
906
+ /** 删除单条 fact */
907
+ deleteFact(factId: string): Promise<void>;
908
+ getAllSessionIds(): Promise<string[]>;
909
+ insertSession(session: Session): Promise<void>;
910
+ upsertSession(session: Session): Promise<void>;
911
+ getAllSessions(): Promise<Session[]>;
912
+ deleteSession(sessionId: string): Promise<void>;
913
+ /** 删除某会话下的全部消息(级联删除会话时使用)*/
914
+ deleteMessagesBySession(sessionId: string): Promise<void>;
915
+ /** 各表行数统计(概览卡片用)*/
916
+ countAll(): Promise<{
917
+ sessions: number;
918
+ messages: number;
919
+ topics: number;
920
+ facts: number;
921
+ }>;
922
+ /**
923
+ * 按天聚合最近 days 天的活跃趋势(概览图表用)。
924
+ * 返回连续日期序列(含无数据的零值天),按本地日期分桶。
925
+ */
926
+ trendDaily(days: number): Promise<{
927
+ date: string;
928
+ sessions: number;
929
+ messages: number;
930
+ facts: number;
931
+ }[]>;
932
+ addDocument(doc: Document): Promise<void>;
933
+ addChunks(chunks: Chunk[]): Promise<void>;
934
+ updateDocumentGraphFlag(docId: string, hasGraph: boolean): Promise<void>;
935
+ getDocument(docId: string): Promise<Document | null>;
936
+ /** 查找同域同 hash 的文档(去重用)*/
937
+ findDocumentByHash(contentHash: string, filter?: Filter): Promise<Document | null>;
938
+ /** 按域过滤返回 docId 列表(chunkRedundantIds=false 时用于 chunk 过滤)*/
939
+ getDocIdsByDomain(filter?: Filter): Promise<string[]>;
940
+ /** 文档级粗召回:对摘要向量做向量搜索,定位候选文档 */
941
+ searchDocuments(vector: number[], filter?: Filter, limit?: number): Promise<Array<Document & {
942
+ _distance: number;
943
+ }>>;
944
+ /** 知识片段混合检索(BM25 + 向量),失败回退纯向量 */
945
+ searchChunks(query: string, vector: number[], filter?: Filter, limit?: number): Promise<Chunk[]>;
946
+ deleteDocument(docId: string): Promise<void>;
947
+ deleteChunksByDoc(docId: string): Promise<void>;
948
+ /** 全量读取文档(管理面板用),按更新时间倒序 */
949
+ getAllDocuments(): Promise<Document[]>;
950
+ /** 知识库行数统计 */
951
+ countKnowledge(): Promise<{
952
+ documents: number;
953
+ chunks: number;
954
+ }>;
955
+ /**
956
+ * 根据 metadata 字段内容构建结构化过滤条件。
957
+ *
958
+ * metadata 以 JSON 字符串存储,此方法将键值对转换为 jsonContains 条件,
959
+ * 可直接传给 searchMessages / hybridSearchTopics 的 filter 参数。
960
+ *
961
+ * 注意:仅适合简单标量值(字符串、数字、布尔)的精确匹配。
962
+ * 复杂嵌套对象或含空格的 JSON 值可能无法可靠匹配。
963
+ */
964
+ static buildMetadataFilter(conditions: Record<string, string | number | boolean>): Filter;
965
+ }
966
+
967
+ type ProviderKind = "lancedb" | "sqlite";
968
+
709
969
  declare const DEFAULT_NODE_TYPES: readonly ["Person", "Group", "Organization", "Project", "Task", "Decision", "Plan", "Event", "Product", "Technology", "Data", "Document", "Topic", "Concept", "Preference", "Habit", "Goal", "Skill", "Attribute", "Value", "Status", "Time", "Location", "Resource", "Relationship"];
710
970
  declare const DEFAULT_RELATION_TYPES: readonly ["is_a", "part_of", "belongs_to", "contains", "has_member", "mentioned_in", "refers_to", "same_as", "alias_of", "related_to", "associated_with", "uses", "creates", "updates", "buys", "owns", "consumes", "works_on", "prefers", "likes", "dislikes", "interested_in", "favorite", "plans", "decides", "habit_of", "tends_to", "avoids", "skilled_in", "learning", "knows", "friends_with", "married_to", "parent_of", "child_of", "lives_with", "depends_on", "built_with", "integrates_with", "deployed_on", "inputs", "outputs", "trained_on", "predicts", "happens_at", "started_at", "ended_at", "affects", "causes", "leads_to", "improves", "reduces", "assigned_to", "executed_by", "blocks", "completes", "describes", "explains", "references"];
711
971
  type NodeType = (typeof DEFAULT_NODE_TYPES)[number];
@@ -714,4 +974,4 @@ type RelationType = (typeof DEFAULT_RELATION_TYPES)[number];
714
974
  declare const KIND_CONVERSATION = "conversation";
715
975
  declare const KIND_KNOWLEDGE = "knowledge";
716
976
 
717
- export { type AddDocumentOptions, type BuildGraphMode, type Chunk, type ChunkHit, type ChunkPiece, type CompressOutput, type ContentPart, DEFAULT_NODE_TYPES, DEFAULT_RELATION_TYPES, type Document, type DomainIds, type Entity, type EntityMeta, type Fact, type FactLevel, KIND_CONVERSATION, KIND_KNOWLEDGE, type KnowledgeSearchOptions, type KnowledgeSearchResult, LanceService, type MemoryConfig, MemoryManager, type MessageType, type NodeType, type PageParams, type Paginated, type RawMessage, type Relation, type RelationType, type SearchMode, type SearchOptions, type SearchResult, type SearchScope, type Session, type SessionEntry, type SessionSearchOptions, type SessionView, type StorageOptimizeResult, type StoredMessage, type Topic, type UpdateChatOptions, type UpdateEntityOptions, type UpdateSessionOptions };
977
+ export { type AddDocumentOptions, type BuildGraphMode, type Chunk, type ChunkHit, type ChunkPiece, type CompressOutput, type ContentPart, DEFAULT_NODE_TYPES, DEFAULT_RELATION_TYPES, type Document, type DomainIds, type Entity, type EntityMeta, type Fact, type FactLevel, type Filter, type FilterCondition, KIND_CONVERSATION, KIND_KNOWLEDGE, type KnowledgeSearchOptions, type KnowledgeSearchResult, type MemoryConfig, type MemoryContextWindow, type MemoryContextWindowUsage, MemoryManager, type MemoryMigrationReport, type MemoryRawMessage, MemoryStore, type MessageType, type NodeType, type PageParams, type Paginated, type ProviderCapabilities, type ProviderKind, type RawMessage, type Relation, type RelationType, type SearchMode, type SearchOptions, type SearchResult, type SearchScope, type Session, type SessionEntry, type SessionSearchOptions, type SessionView, type StorageOptimizeResult, type StoredMessage, type Topic, type UpdateChatOptions, type UpdateEntityOptions, type UpdateSessionOptions, type VectorStoreProvider };