dsh-prime-memory 0.26.4 → 0.26.5

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.
Files changed (42) hide show
  1. package/dist/client.js +36785 -134
  2. package/dist/clients/drsc.d.ts +93 -0
  3. package/dist/clients/drsc.js +139 -0
  4. package/dist/contract.d.ts +74 -0
  5. package/dist/drs-anchor-backfill.d.ts +88 -0
  6. package/dist/drs-anchor-backfill.js +150 -0
  7. package/dist/graph/constraints.d.ts +3 -1
  8. package/dist/graph/constraints.js +4 -2
  9. package/dist/graph/preview.d.ts +92 -0
  10. package/dist/graph/preview.js +186 -0
  11. package/dist/graph/search.d.ts +22 -5
  12. package/dist/graph/search.js +69 -7
  13. package/dist/graph/types.d.ts +26 -1
  14. package/dist/graph/types.js +26 -1
  15. package/dist/hooks/capture.d.ts +4 -4
  16. package/dist/hooks/capture.js +34 -27
  17. package/dist/hooks/recall.js +8 -1
  18. package/dist/index.js +58 -20
  19. package/dist/pipeline/graph.d.ts +9 -0
  20. package/dist/pipeline/graph.js +29 -2
  21. package/dist/pipeline/runner.d.ts +17 -4
  22. package/dist/pipeline/runner.js +33 -6
  23. package/dist/stats.js +30 -1
  24. package/dist/store/embedding-source.d.ts +1 -3
  25. package/dist/store/embedding-source.js +6 -13
  26. package/dist/store/graph-store.d.ts +40 -3
  27. package/dist/store/graph-store.js +104 -8
  28. package/dist/store/l0-watermark.d.ts +46 -0
  29. package/dist/store/l0-watermark.js +93 -0
  30. package/dist/store/l0.d.ts +12 -42
  31. package/dist/store/l0.js +2 -208
  32. package/dist/store/l1.d.ts +12 -9
  33. package/dist/store/l1.js +37 -16
  34. package/dist/store/session-recalled.d.ts +9 -3
  35. package/dist/store/session-recalled.js +24 -5
  36. package/dist/store/sqlite.d.ts +3 -0
  37. package/dist/store/sqlite.js +7 -0
  38. package/dist/tools/index.d.ts +3 -2
  39. package/dist/tools/index.js +27 -10
  40. package/dist/types.d.ts +9 -0
  41. package/dsh.plugin.json +1 -1
  42. package/package.json +8 -3
package/dist/store/l1.js CHANGED
@@ -9,7 +9,6 @@
9
9
  import { existsSync, mkdirSync, promises as fs, writeFileSync } from 'node:fs';
10
10
  import * as path from 'node:path';
11
11
  import { familyForType, isScopeVisible } from '../types.js';
12
- import { graphHitRecordIds } from '../graph/search.js';
13
12
  import { isRetired } from './supersede.js';
14
13
  import { exportThenPurge, readSnapshotManifest, readSnapshotRecords, restoreL1Snapshot, selectSnapshotTargets, snapshotDirFor, listSnapshots as listSnapshotsIn } from './l1-snapshot.js';
15
14
  import { EmbedHelper, NoopEmbeddingService } from './embedding.js';
@@ -735,14 +734,35 @@ export class L1Store {
735
734
  const vecList = vecRaw ? this.db.searchL1Vector(vecRaw, candidateK, opts?.family, opts?.workspaceId) : [];
736
735
  // 第 3 路(图谱)仅在接线时**结构性存在**:未接线不占路数名额,故既有调用方
737
736
  // 仍走 2 路、得分与改动前逐位一致(见 findings.md §12.1 的路数语义)
738
- const lanes = [ftsList, vecList];
737
+ const lanes = [['fts', ftsList], ['vec', vecList]];
739
738
  if (this.graphLaneProvider)
740
- lanes.push(this.graphLane(query, candidateK, opts?.family, opts?.workspaceId));
739
+ lanes.push(['graph', await this.graphLane(query, candidateK, opts?.family, opts?.workspaceId)]);
741
740
  // 第 4 路(时效)在衰减开启时**结构性存在**;关掉衰减 = 该路不存在
742
741
  if (this.decayHalfLifeDays > 0)
743
- lanes.push(this.recencyLane([...ftsList, ...vecList]));
744
- const merged = rrfMerge(lanes, (h) => h.id);
745
- return this.postProcess(this.applyDecay(merged.map(({ rrfScore, ...h }) => ({ ...h, score: normalizeRrf(rrfScore, lanes.length) }))), opts?.type, limit);
742
+ lanes.push(['recency', this.recencyLane([...ftsList, ...vecList])]);
743
+ const laneLists = lanes.map(([, list]) => list);
744
+ // 路归因(与融合并行,不改 rrfMerge 本体):id → 命中路集合,按路构造序
745
+ // 去重追加(fts→vec→graph→recency,确定性输出)。命中对象在下方 map 里
746
+ // 新建(不往变回路的列表元素上写),挂 lanes 后随 postProcess/applyDecay 透传。
747
+ const lanesById = new Map();
748
+ for (const [name, list] of lanes) {
749
+ for (const h of list) {
750
+ const arr = lanesById.get(h.id);
751
+ if (arr) {
752
+ if (!arr.includes(name))
753
+ arr.push(name);
754
+ }
755
+ else {
756
+ lanesById.set(h.id, [name]);
757
+ }
758
+ }
759
+ }
760
+ const merged = rrfMerge(laneLists, (h) => h.id);
761
+ return this.postProcess(this.applyDecay(merged.map(({ rrfScore, ...h }) => ({
762
+ ...h,
763
+ lanes: lanesById.get(h.id),
764
+ score: normalizeRrf(rrfScore, laneLists.length),
765
+ }))), opts?.type, limit);
746
766
  }
747
767
  /**
748
768
  * §D 第 4 路(时效路,hybrid 专用):把候选池按 `applyDecayWeight` 加权后的
@@ -770,36 +790,37 @@ export class L1Store {
770
790
  return this.applyDecay(deduped);
771
791
  }
772
792
  /**
773
- * §D 第 3 路(图谱路径,hybrid 专用):图谱命中 → `sourceRecordIds` 回链 →
774
- * L1 记录,作为第 3 条**已排序**列表参与 RRF。
793
+ * §D 第 3 路(图谱路径,hybrid 专用):provider 给出回链记录 id 序(路内向量种子
794
+ * 并集 + 1-hop 边扩展在 provider 内完成),本缝只做点查回形 + family/scope 双闸,
795
+ * 作为第 3 条**已排序**列表参与 RRF。
775
796
  *
776
797
  * 为什么值得:图谱是按实体/关系组织的**可重建派生投影**,能召回词法与向量
777
798
  * 都命不中的记录(同义表述、关系可达)——这正是本路相对双路的增量。
778
799
  *
779
800
  * 三条边界:
780
801
  * - **异常降级**:图谱是派生投影,不得因它失败而拖垮主检索 → 记 warn、返回空路,
781
- * 融合退回双路(路数随之降为 2,分数回到既有量纲);
782
- * - **族隔离**:图谱节点已按族过滤,但其来源记录可能跨族 → 这里再按 `family`
783
- * 过滤一次。宁可漏不可串(与档位隔离同源,§A 的 P0 关注点);
802
+ * 融合退回双路(路数随之降为 2,分数回到既有量纲);await 在 try 内——
803
+ * provider 的 Promise reject 与同步 throw 同路径降级;
804
+ * - **族隔离**:图谱节点已按族过滤(含向量种子),但 1-hop 邻居与来源记录可能跨族
805
+ * → 这里再按 `family` 过滤一次。宁可漏不可串(与档位隔离同源,§A 的 P0 关注点);
784
806
  * - **墓碑边界**:图谱行可能回链到已被删除的 L1 记录 → 取不到就跳过,
785
807
  * 不补空占位(占位会在 RRF 里凭空加分)。
786
808
  */
787
- graphLane(query, limit, family, workspaceId) {
809
+ async graphLane(query, limit, family, workspaceId) {
788
810
  const provider = this.graphLaneProvider;
789
811
  if (!provider)
790
812
  return [];
791
- let hits;
813
+ let ids;
792
814
  try {
793
- hits = provider(query, limit, family);
815
+ ids = await provider(query, limit, family);
794
816
  }
795
817
  catch (err) {
796
818
  this.logger?.warn(`[memory] 图谱路检索失败,本轮退回双路: ${err instanceof Error ? err.message : String(err)}`);
797
819
  return [];
798
820
  }
799
- const ids = graphHitRecordIds(hits);
800
821
  if (ids.length === 0)
801
822
  return [];
802
- const byId = new Map(this.db.getL1ByIds(ids).map((r) => [r.id, r]));
823
+ const byId = new Map(this.db.getL1ByIds([...ids]).map((r) => [r.id, r]));
803
824
  const out = [];
804
825
  for (const id of ids) {
805
826
  const r = byId.get(id);
@@ -1,4 +1,4 @@
1
- import type { MemoryLogger } from '../types.js';
1
+ import type { MemoryLogger, RecallLane } from '../types.js';
2
2
  export declare class SessionRecalledStore {
3
3
  private readonly logger?;
4
4
  private readonly file;
@@ -12,8 +12,9 @@ export declare class SessionRecalledStore {
12
12
  constructor(dataDir: string, logger?: MemoryLogger | undefined);
13
13
  revision(): number;
14
14
  private init;
15
- /** 记入本 sessionKey 本轮实际注入的记录 id(写穿;插入序淘汰最旧)。 */
16
- add(sessionKey: string, recordIds: readonly string[], injectionId?: string): void;
15
+ /** 记入本 sessionKey 本轮实际注入的记录 id(写穿;插入序淘汰最旧)。
16
+ * laneById 只带真正有归因的条目(hybrid 融合才挂 lanes;单路/旧路不传)。 */
17
+ add(sessionKey: string, recordIds: readonly string[], injectionId?: string, laneById?: ReadonlyMap<string, readonly RecallLane[]>): void;
17
18
  /**
18
19
  * 标为受保护(判过时/软退场):条目留在列表里,且不受上限淘汰与压缩清理影响。
19
20
  * id 不在本会话列表里时忽略(不凭这个调用新建条目)。
@@ -34,6 +35,11 @@ export declare class SessionRecalledStore {
34
35
  * 未出现过取代的会话返回共享空集,不每次新建)。
35
36
  */
36
37
  supersededOf(sessionKey: string): ReadonlySet<string>;
38
+ /**
39
+ * 本会话的路归因表(record id → 管道串;只读共享,投影/端点用)。
40
+ * 不在表里 = 无从归因(单路检索/旧文件条目),展示层不渲染徽标。
41
+ */
42
+ lanesOf(sessionKey: string): ReadonlyMap<string, string>;
37
43
  /**
38
44
  * 用会话日志的可见性事实**对账**取代标记(Phase 0B)。
39
45
  *
@@ -23,9 +23,12 @@ import { SESSION_RECALLED_FILE_VERSION } from './file-versions.js';
23
23
  import { RECALL_DEDUPE_IDS_CAP, RECALL_DEDUPE_SESSION_CAP } from './recall-dedupe.js';
24
24
  /** 共享空集(未出现过取代的会话重复取用,不必每次新建)。 */
25
25
  const EMPTY_SET = new Set();
26
+ /** 共享空归因表(同 EMPTY_SET 口径)。 */
27
+ const EMPTY_LANES = new Map();
26
28
  /**
27
- * 读 `injectedBy` 的宽容解析:非对象/值非字符串一律丢弃。
28
- * 宁可让那条记录“无从判”(对账不动它),也不把脏值喂给可见性判定——
29
+ * 读 `Record<string, string>` 型字段的宽容解析(injectedBy/lanes 共用):
30
+ * 非对象/值非字符串一律丢弃。
31
+ * 宁可让那条记录“无从判”(对账不动它/徽标不渲染),也不把脏值喂给下游判定——
29
32
  * 一个错的 message id 会把仍在本上下文里的正文误标成“已被摘要取代”。
30
33
  */
31
34
  function toIdMap(raw) {
@@ -93,17 +96,19 @@ export class SessionRecalledStore {
93
96
  protectedIds: new Set(Array.isArray(entry.protectedIds) ? entry.protectedIds : []),
94
97
  supersededIds: new Set(Array.isArray(entry.supersededIds) ? entry.supersededIds : []),
95
98
  injectedBy: toIdMap(entry.injectedBy),
99
+ laneOf: toIdMap(entry.lanes),
96
100
  updatedAt: entry.updatedAt ?? now,
97
101
  });
98
102
  }
99
103
  }
100
- /** 记入本 sessionKey 本轮实际注入的记录 id(写穿;插入序淘汰最旧)。 */
101
- add(sessionKey, recordIds, injectionId) {
104
+ /** 记入本 sessionKey 本轮实际注入的记录 id(写穿;插入序淘汰最旧)。
105
+ * laneById 只带真正有归因的条目(hybrid 融合才挂 lanes;单路/旧路不传)。 */
106
+ add(sessionKey, recordIds, injectionId, laneById) {
102
107
  if (!sessionKey || recordIds.length === 0)
103
108
  return;
104
109
  let entry = this.entries.get(sessionKey);
105
110
  if (!entry) {
106
- entry = { ids: new Set(), protectedIds: new Set(), supersededIds: new Set(), injectedBy: new Map(), updatedAt: 0 };
111
+ entry = { ids: new Set(), protectedIds: new Set(), supersededIds: new Set(), injectedBy: new Map(), laneOf: new Map(), updatedAt: 0 };
107
112
  this.entries.set(sessionKey, entry);
108
113
  }
109
114
  const stamp = typeof injectionId === 'string' && injectionId.length > 0 ? injectionId : undefined;
@@ -114,6 +119,11 @@ export class SessionRecalledStore {
114
119
  // 记下这次注入落在哪条消息上:没有它,对账就无从判“这条正文还在不在”。
115
120
  if (stamp !== undefined)
116
121
  entry.injectedBy.set(id, stamp);
122
+ // 路归因:最后一次注入赢;本轮无归因时**不清旧值**(徽标宁可残留一轮
123
+ // 也不要闪无——真实路集在下一次带归因注入时自然覆盖)。
124
+ const laneArr = laneById?.get(id);
125
+ if (laneArr !== undefined && laneArr.length > 0)
126
+ entry.laneOf.set(id, laneArr.join('|'));
117
127
  // 淘汰只能淘汰**没被判过时的**:受保护条目数本身超顶时宁可超顶,也不能把人的裁决
118
128
  // 凭证挤掉(那等于“因为后面又召回了几条,所以之前那条的判定历史丢了”)。
119
129
  while (entry.ids.size > RECALL_DEDUPE_IDS_CAP) {
@@ -124,6 +134,7 @@ export class SessionRecalledStore {
124
134
  entry.protectedIds.delete(oldest);
125
135
  entry.supersededIds.delete(oldest);
126
136
  entry.injectedBy.delete(oldest);
137
+ entry.laneOf.delete(oldest);
127
138
  }
128
139
  }
129
140
  entry.updatedAt = Date.now();
@@ -197,6 +208,13 @@ export class SessionRecalledStore {
197
208
  supersededOf(sessionKey) {
198
209
  return this.entries.get(sessionKey)?.supersededIds ?? EMPTY_SET;
199
210
  }
211
+ /**
212
+ * 本会话的路归因表(record id → 管道串;只读共享,投影/端点用)。
213
+ * 不在表里 = 无从归因(单路检索/旧文件条目),展示层不渲染徽标。
214
+ */
215
+ lanesOf(sessionKey) {
216
+ return this.entries.get(sessionKey)?.laneOf ?? EMPTY_LANES;
217
+ }
200
218
  /**
201
219
  * 用会话日志的可见性事实**对账**取代标记(Phase 0B)。
202
220
  *
@@ -302,6 +320,7 @@ export class SessionRecalledStore {
302
320
  ...(e.protectedIds.size > 0 ? { protectedIds: [...e.protectedIds] } : {}),
303
321
  ...(e.supersededIds.size > 0 ? { supersededIds: [...e.supersededIds] } : {}),
304
322
  ...(e.injectedBy.size > 0 ? { injectedBy: Object.fromEntries(e.injectedBy) } : {}),
323
+ ...(e.laneOf.size > 0 ? { lanes: Object.fromEntries(e.laneOf) } : {}),
305
324
  updatedAt: e.updatedAt,
306
325
  };
307
326
  }
@@ -12,6 +12,7 @@ export interface StoreCapabilities {
12
12
  }
13
13
  import { CostLedger } from './cost-ledger.js';
14
14
  import type { BucketRow, CostAggregate, CostByLayer } from './cost-ledger.js';
15
+ import { L0WatermarkStore } from './l0-watermark.js';
15
16
  export type { BucketRow, CostAggregate, CostByLayer } from './cost-ledger.js';
16
17
  import type { CostByModel } from '../contract.js';
17
18
  import { GraphStore } from './graph-store.js';
@@ -57,6 +58,8 @@ export declare class MemoryDb {
57
58
  readonly costLedger: CostLedger;
58
59
  /** 图谱存储(graph_* 表族;init 独立 try/catch,失败仅图谱 no-op)。 */
59
60
  readonly graphStore: GraphStore;
61
+ /** L0 水位表(T8.4 写停;§A 恢复幂等判据的数据源,init 内建表+基线物化)。 */
62
+ readonly l0Watermark: L0WatermarkStore;
60
63
  private stmtUpsertL0;
61
64
  private stmtGetL0;
62
65
  private stmtL0Exists;
@@ -45,6 +45,8 @@ function retiredL1Sql(alias) {
45
45
  // 成本账本(token_cost 表族)独立职责,此处 re-export 不断裂既有引用;
46
46
  // MemoryDb 的四个公开方法为一行委托。
47
47
  import { CostLedger } from './cost-ledger.js';
48
+ // L0 水位表(T8.4 写停后 §A 恢复幂等的唯一数据源;正文三表退役,见 l0-watermark.ts)
49
+ import { L0WatermarkStore } from './l0-watermark.js';
48
50
  // 图谱存储(graph_* 表族)同为独立职责类;init 失败仅图谱 no-op,不传染主库降级
49
51
  import { GraphStore } from './graph-store.js';
50
52
  import { RECEIPTS_MAX_RUNS, RECEIPTS_QUERY_LIMIT_MAX } from './receipts.js';
@@ -88,6 +90,8 @@ export class MemoryDb {
88
90
  costLedger = new CostLedger();
89
91
  /** 图谱存储(graph_* 表族;init 独立 try/catch,失败仅图谱 no-op)。 */
90
92
  graphStore = new GraphStore();
93
+ /** L0 水位表(T8.4 写停;§A 恢复幂等判据的数据源,init 内建表+基线物化)。 */
94
+ l0Watermark = new L0WatermarkStore();
91
95
  stmtUpsertL0;
92
96
  stmtGetL0;
93
97
  stmtL0Exists;
@@ -547,6 +551,8 @@ export class MemoryDb {
547
551
  this.prepareL0VecStatements();
548
552
  // ── token_cost:蒸馏成本明细表(成本账本自治) ──
549
553
  this.costLedger.init(this.db, this.logger);
554
+ // ── l0_watermark:写停后的 §A 恢复水位(建表 + 从存量 l0_conversations 基线物化) ──
555
+ this.l0Watermark.init(this.db, this.logger);
550
556
  // ── graph_*:知识图谱投影表族(GraphStore.init 自带 try/catch,失败仅图谱 no-op) ──
551
557
  // §F 节点向量列的维度**复用既有探测结果**(`vecLoaded` / `dimensions` 由
552
558
  // `prepareL1VecStatements` 之前的探测决定),不新增一套能力探测。探测未通过 →
@@ -2657,6 +2663,7 @@ export class MemoryDb {
2657
2663
  }
2658
2664
  }
2659
2665
  close() {
2666
+ this.l0Watermark.close();
2660
2667
  try {
2661
2668
  this.db.close();
2662
2669
  }
@@ -3,7 +3,7 @@ import type { MemoryConfig } from '../config.js';
3
3
  import type { LiveSettingsHandle } from '../settings.js';
4
4
  import type { RuminateController } from '../pipeline/ruminate.js';
5
5
  import type { GraphStore } from '../store/graph-store.js';
6
- import type { L0Store } from '../store/l0.js';
6
+ import type { DrscClient } from '../clients/drsc.js';
7
7
  import type { L1Store } from '../store/l1.js';
8
8
  import type { PersonaStore } from '../store/persona.js';
9
9
  import type { SceneStore } from '../store/scenes.js';
@@ -20,7 +20,8 @@ export interface ToolSealRegistry {
20
20
  reg(group: string, make: () => () => void): void;
21
21
  }
22
22
  export declare function registerMemoryTools(ctx: Context, cfg: MemoryConfig, stores: {
23
- l0: L0Store;
23
+ /** DRSC 客户端(T8.4 读切):conversation_search 的历史供给面;缺席=显式报错。 */
24
+ drsc?: DrscClient;
24
25
  l1: L1Store;
25
26
  scenes: Record<MemoryFamily, SceneStore>;
26
27
  persona: Record<MemoryFamily, PersonaStore>;
@@ -14,6 +14,7 @@
14
14
  import { createHash } from 'node:crypto';
15
15
  import { defineTool } from '@deepseek-ai/dsh-tools';
16
16
  import { DOWNGRADE_TARGET, familyOf, isKnownType, typeEnumSep } from '../memory-types.js';
17
+ import { drscRoleOf } from '../clients/drsc.js';
17
18
  import { RECEIPTS_QUERY_LIMIT_MAX, dimensionOf, toReceiptView } from '../store/receipts.js';
18
19
  import { listConflictPairs, renderConflictResolution, renderConflicts, resolveConflictPair } from '../conflict-service.js';
19
20
  import { WING_CATALOG, WING_FALLBACK, normPersistence, normScope, resolveRecordScope } from '../types.js';
@@ -399,7 +400,7 @@ roomRegistry) {
399
400
  // ── conversation_search: L0 原始对话 ──
400
401
  ctx.tools.register(defineTool({
401
402
  name: 'conversation_search',
402
- description: '搜索原始对话历史(L0)。返回带时间戳的原始消息,适用于查找具体消息原文、时间线、上下文细节。',
403
+ description: '搜索原始对话历史。返回带时间戳的原始消息节选(已脱敏),适用于查找具体消息原文、时间线、上下文细节。',
403
404
  parameters: {
404
405
  query: { type: 'string', required: true, description: '搜索查询文本' },
405
406
  limit: { type: 'number', description: '最大返回条数(默认 5)' },
@@ -433,15 +434,31 @@ roomRegistry) {
433
434
  if (familyOfCaller(exec) === null)
434
435
  return { items: [], notice: blockNoticeOf(exec) };
435
436
  const limit = Math.min(Math.max(args.limit ?? 5, 1), 20);
436
- const records = await stores.l0.search(args.query, limit);
437
- return {
438
- items: records.map((r) => ({
439
- session_id: r.sessionId,
440
- role: r.role,
441
- content: r.content,
442
- timestamp: r.timestamp,
443
- })),
444
- };
437
+ // T8.4 读切(spec R7):检索命中走 DRSC 原文 fts,返回文本取 redact=1 脱敏列。
438
+ // 硬依赖无本地回退(l0 本地检索已退役)——collector 缺席/故障显式报错给模型,
439
+ // 与「空结果」口径分清,不抛穿主流程。
440
+ const drsc = stores.drsc;
441
+ if (!drsc) {
442
+ return { items: [], notice: '历史对话检索不可用:DRSC collector 未装配(本地 L0 检索已退役,无回退)。' };
443
+ }
444
+ try {
445
+ const hits = await drsc.search(args.query, limit);
446
+ return {
447
+ items: hits
448
+ .map((h) => ({ h, role: drscRoleOf(h.kind) }))
449
+ .filter((e) => e.role !== undefined)
450
+ .map(({ h, role }) => ({
451
+ session_id: h.session_id,
452
+ role,
453
+ content: h.snippet,
454
+ timestamp: h.time,
455
+ })),
456
+ };
457
+ }
458
+ catch (err) {
459
+ const detail = err instanceof Error ? err.message : String(err);
460
+ return { items: [], notice: `历史对话检索暂不可用(DRSC collector: ${detail})。` };
461
+ }
445
462
  },
446
463
  }));
447
464
  // ── memory_read_scene: 读取 L2 场景块 / L3 画像 ──
package/dist/types.d.ts CHANGED
@@ -258,6 +258,13 @@ export interface SceneSummary {
258
258
  summary: string;
259
259
  heat: number;
260
260
  }
261
+ /**
262
+ * 召回路标识(§D hybrid 多路归因):hybrid 融合时记录每条命中**来自哪几路**,
263
+ * 供面板/回捞展示层渲染徽标。与路数语义同源:未接线的路不存在,不会出现在
264
+ * 本联合的取值里(如图谱路未注入 provider 时无 'graph')。
265
+ * 单路检索分支(纯 fts/纯 vec)不挂归因——无歧义可归,lanes 保持 undefined。
266
+ */
267
+ export type RecallLane = 'fts' | 'vec' | 'graph' | 'recency';
261
268
  /** L1 检索命中。 */
262
269
  export interface L1Hit {
263
270
  id: string;
@@ -267,6 +274,8 @@ export interface L1Hit {
267
274
  score: number;
268
275
  priority?: number;
269
276
  family?: MemoryFamily;
277
+ /** hybrid 融合命中的来源路集合(可选;缺省=未归因,序列化面零漂移)。 */
278
+ lanes?: RecallLane[];
270
279
  }
271
280
  /**
272
281
  * dsh-llm ≥0.1.7-rc.1(会话格式 v4)的 producer-owned source kind 登记。
package/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dsh-prime-memory",
3
3
  "name": "dsh-prime-memory",
4
- "version": "0.26.4",
4
+ "version": "0.26.5-beta.0",
5
5
  "description": {
6
6
  "en": "Layered distilled memory plugin (L0 capture → L1 atomic memory → L2 scenes → L3 persona) with automatic recall injection before each model step.",
7
7
  "zh": "L0~L3 分层蒸馏记忆插件:自动捕获对话、抽取原子记忆、整合场景块、蒸馏核心画像,并在模型步骤前自动召回注入。"
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "dsh-prime-memory",
3
- "version": "0.26.4",
3
+ "version": "0.26.5",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",
7
- "tag": "latest"
7
+ "tag": "beta"
8
8
  },
9
9
  "description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
10
10
  "type": "module",
@@ -27,7 +27,10 @@
27
27
  "@deepseek-ai/dsh-client-ui-primitives"
28
28
  ],
29
29
  "platform": "web"
30
- }
30
+ },
31
+ "pluginDependencies": [
32
+ "dsh-reference-session-collector"
33
+ ]
31
34
  },
32
35
  "files": [
33
36
  "dist",
@@ -79,6 +82,7 @@
79
82
  "dependencies": {
80
83
  "@deepseek-ai/schemastery": "^3.18.4",
81
84
  "@node-rs/jieba": "^2.0.2",
85
+ "dsh-reference-session-collector": ">=0.2.0-beta.0 <0.3.0",
82
86
  "sqlite-vec": "^0.1.7-alpha.2",
83
87
  "undici": "^7.29.0"
84
88
  },
@@ -124,6 +128,7 @@
124
128
  }
125
129
  },
126
130
  "devDependencies": {
131
+ "@antv/g6": "5.1.1",
127
132
  "@deepseek-ai/cordis": "^4.0.4",
128
133
  "@deepseek-ai/cordis-plugin-loader": "^1.0.5",
129
134
  "@deepseek-ai/cosmokit": "1.8.5",