dsh-layered-memory 0.8.3 → 0.8.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.
package/dist/stats.js CHANGED
@@ -9,14 +9,15 @@
9
9
  import { createRequire } from 'node:module';
10
10
  import { closeSync, openSync, readSync, statSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
- import { resolveDataDir } from './config.js';
12
+ import { EFFORT_CHOICES, resolveDataDir } from './config.js';
13
13
  import { effectiveCfg } from './pipeline/runner.js';
14
+ import { emptyRecallStats } from './hooks/recall.js';
14
15
  import { decideSendableEffort, LAYER_DEFAULT_BUDGETS, resolveModelEfforts, resolveModelRoute } from './llm.js';
15
16
  import { errDetail } from './util/filelog.js';
16
17
  const require = createRequire(import.meta.url);
17
18
  export const PLUGIN_VERSION = require('../package.json').version;
18
19
  /** 注册状态 RPC(web 侧 connection 服务可选,缺失时跳过,不影响插件主体)。 */
19
- export function registerMemoryRpc(ctx, cfg, stores, logger, status, live, modes, dataDir, rebuild, embedManager) {
20
+ export function registerMemoryRpc(ctx, cfg, stores, logger, status, live, modes, dataDir, rebuild, embedManager, sessionInfo) {
20
21
  /** 当前是否持有一段有效注册(dispose 完成后清空,允许服务重上线时重注册)。 */
21
22
  let holding = false;
22
23
  /** 当前 handle 绑定的 connection 实例(internal/service 第二参;用于识别实例替换)。 */
@@ -43,6 +44,7 @@ export function registerMemoryRpc(ctx, cfg, stores, logger, status, live, modes,
43
44
  logger,
44
45
  rebuild,
45
46
  embedManager,
47
+ sessionInfo,
46
48
  });
47
49
  return { ok: true, value };
48
50
  }
@@ -142,7 +144,7 @@ function expectSessionId(v) {
142
144
  return v;
143
145
  }
144
146
  async function handleEndpoint(endpoint, payload, deps) {
145
- const { cfg, stores, status, live, modes, dataDir, rebuild, embedManager } = deps;
147
+ const { cfg, stores, status, live, modes, dataDir, rebuild, embedManager, sessionInfo } = deps;
146
148
  switch (endpoint) {
147
149
  case 'dsh-memory/stats':
148
150
  return buildStats(cfg, stores, status);
@@ -166,6 +168,39 @@ async function handleEndpoint(endpoint, payload, deps) {
166
168
  deps.logger.info(`[memory] 会话档位设置 session=${sessionId} mode=${p.mode}`);
167
169
  return { sessionId, mode: p.mode };
168
170
  }
171
+ // ── 会话级统计(悬浮卡信息区;热路径端点,见 SessionInfoSource 的零 I/O 硬规则) ──
172
+ case 'dsh-memory/session-stats': {
173
+ if (!sessionInfo)
174
+ return { supported: false };
175
+ const p = (payload ?? {});
176
+ const sessionId = expectSessionId(p.sessionId);
177
+ const mode = modes ? modes.get(sessionId) : 'auto';
178
+ const caps = sessionInfo.capabilities();
179
+ const l0Count = await sessionInfo.l0Count(sessionId);
180
+ const s = live?.get();
181
+ const recallOn = cfg.recall.enabled && (s?.recall ?? true) && mode !== 'off';
182
+ const view = sessionInfo.runnerView(sessionId, mode);
183
+ // lastDistillAt 统一转 ISO(与 global.lastExtractAt 口径一致,client 直接 fmtAgo)
184
+ const distillView = { ...view, lastDistillAt: view.lastDistillAt ? new Date(view.lastDistillAt).toISOString() : null };
185
+ const chat = stores.state.forFamily('chat');
186
+ const work = stores.state.forFamily('work');
187
+ const lastAt = Math.max(chat.lastExtractAt, work.lastExtractAt);
188
+ return {
189
+ supported: true,
190
+ sessionId,
191
+ mode,
192
+ defaultMode: modes?.default ?? cfg.family,
193
+ recall: { enabled: recallOn, ...(sessionInfo.recallStats(sessionId) ?? emptyRecallStats()) },
194
+ distill: distillView,
195
+ l0Count,
196
+ retrieval: caps.vectorSearch ? (caps.ftsSearch ? 'hybrid' : 'vector') : caps.ftsSearch ? 'keyword' : 'none',
197
+ global: {
198
+ degraded: status?.degraded() ?? false,
199
+ pendingTotal: status?.pending() ?? 0,
200
+ lastExtractAt: lastAt ? new Date(lastAt).toISOString() : null,
201
+ },
202
+ };
203
+ }
169
204
  case 'dsh-memory/settings-get': {
170
205
  const s = live?.get();
171
206
  const budgets = s?.distillBudgets ?? { extract: 0, dedup: 0, l2: 0, l3: 0 };
@@ -234,8 +269,10 @@ async function handleEndpoint(endpoint, payload, deps) {
234
269
  }
235
270
  if (patch.reasoningEffort !== undefined) {
236
271
  const v = String(patch.reasoningEffort);
237
- if (!['', 'off', 'high', 'max'].includes(v)) {
238
- throw new Error(`非法思考档位: ${v}(允许 ''/off/high/max)`);
272
+ // 白名单与 schema/settings 同源(config.ts EFFORT_CHOICES)——此前此处漏扩词表,
273
+ // 设置页新词汇(none/minimal/low/medium/xhigh)被拒并回滚
274
+ if (!EFFORT_CHOICES.includes(v)) {
275
+ throw new Error(`非法思考档位: ${v}(允许 '' 或 ${EFFORT_CHOICES.filter((x) => x !== '').join('/')})`);
239
276
  }
240
277
  clean.reasoningEffort = v;
241
278
  }
@@ -15,6 +15,8 @@ export declare class L0Store {
15
15
  append(sessionId: string, messages: ConversationMessage[]): Promise<void>;
16
16
  /** 今日已捕获消息数(SQL 计数,不再读整文件)。 */
17
17
  countToday(): Promise<number>;
18
+ /** 该会话累计已捕获消息数(session-stats 数据源;索引 COUNT)。 */
19
+ countBySession(sessionId: string): Promise<number>;
18
20
  /** 该会话最近 n 条消息(时间升序;蒸馏背景参考用,按会话现查——ADR-0003)。 */
19
21
  recentBySession(sessionId: string, limit: number): Promise<ConversationMessage[]>;
20
22
  /** 检索:FTS + 向量 hybrid(RRF 融合),返回按相关性排序的消息。 */
package/dist/store/l0.js CHANGED
@@ -104,6 +104,10 @@ export class L0Store {
104
104
  const d = new Date();
105
105
  return this.db.countL0Since(new Date(d.getFullYear(), d.getMonth(), d.getDate()).toISOString());
106
106
  }
107
+ /** 该会话累计已捕获消息数(session-stats 数据源;索引 COUNT)。 */
108
+ async countBySession(sessionId) {
109
+ return this.db.countL0BySession(sessionId);
110
+ }
107
111
  /** 该会话最近 n 条消息(时间升序;蒸馏背景参考用,按会话现查——ADR-0003)。 */
108
112
  async recentBySession(sessionId, limit) {
109
113
  return this.db.recentL0BySession(sessionId, limit);
@@ -148,6 +148,8 @@ export declare class MemoryDb {
148
148
  countL0(): number;
149
149
  /** 统计 recorded_at >= iso 的消息数(状态面板"今日捕获"用)。 */
150
150
  countL0Since(iso: string): number;
151
+ /** 统计某会话已捕获消息数(session-stats 数据源;idx_l0_session_id 索引点查)。 */
152
+ countL0BySession(sessionId: string): number;
151
153
  /** 按会话取最近消息(时间升序返回;走 idx_l0_session_id 索引)。
152
154
  * 蒸馏背景参考专用——按会话现查替代全局内存数组(ADR-0003)。 */
153
155
  recentL0BySession(sessionId: string, limit: number): L0MessageRecord[];
@@ -989,6 +989,20 @@ export class MemoryDb {
989
989
  return 0;
990
990
  }
991
991
  }
992
+ /** 统计某会话已捕获消息数(session-stats 数据源;idx_l0_session_id 索引点查)。 */
993
+ countL0BySession(sessionId) {
994
+ if (this.degraded)
995
+ return 0;
996
+ try {
997
+ const row = this.db
998
+ .prepare('SELECT COUNT(*) AS n FROM l0_conversations WHERE session_id = ?')
999
+ .get(sessionId);
1000
+ return row?.n ?? 0;
1001
+ }
1002
+ catch {
1003
+ return 0;
1004
+ }
1005
+ }
992
1006
  /** 按会话取最近消息(时间升序返回;走 idx_l0_session_id 索引)。
993
1007
  * 蒸馏背景参考专用——按会话现查替代全局内存数组(ADR-0003)。 */
994
1008
  recentL0BySession(sessionId, limit) {
package/dist/types.d.ts CHANGED
@@ -43,7 +43,15 @@ export interface ExtractedMemory {
43
43
  metadata: Record<string, unknown>;
44
44
  /** 所属情境名(L1 抽取的情境切分结果)。 */
45
45
  scene_name: string;
46
+ /** auto 档抽取输出的显式族判定(chat|work;纯档 Prompt 无此字段)。
47
+ * 语境归族、形状不归族——修复"个人计划性事实被 work_* 形状吸走"的族错标
48
+ * (2026-08-23 lifecycle 赛道实测发现)。 */
49
+ family?: string;
46
50
  }
51
+ /** 抽取输出的 family 字段归一:只认 chat|work,其余(缺省/非法值)交由调用方回落。 */
52
+ export declare function normExtractedFamily(raw: unknown): MemoryFamily | undefined;
53
+ /** 记录族三级兜底链:会话档位强制(纯档)→ 抽取显式判定(auto)→ type 前缀推导(旧输出兜底)。 */
54
+ export declare function resolveRecordFamily(forced: MemoryFamily | undefined, extracted: unknown, type: string): MemoryFamily;
47
55
  /** L1 持久化记录(字段对齐 MemoryCore;version/source_message_ids/metadata 由写入侧补默认)。 */
48
56
  export interface MemoryRecord {
49
57
  id: string;
package/dist/types.js CHANGED
@@ -5,3 +5,11 @@
5
5
  export function familyForType(type) {
6
6
  return type.startsWith('work') ? 'work' : 'chat';
7
7
  }
8
+ /** 抽取输出的 family 字段归一:只认 chat|work,其余(缺省/非法值)交由调用方回落。 */
9
+ export function normExtractedFamily(raw) {
10
+ return raw === 'chat' || raw === 'work' ? raw : undefined;
11
+ }
12
+ /** 记录族三级兜底链:会话档位强制(纯档)→ 抽取显式判定(auto)→ type 前缀推导(旧输出兜底)。 */
13
+ export function resolveRecordFamily(forced, extracted, type) {
14
+ return forced ?? normExtractedFamily(extracted) ?? familyForType(type);
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-layered-memory",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,25 +58,25 @@
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@deepseek-ai/cordis": "^4.0.1",
61
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
62
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
63
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
64
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
65
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
66
- "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
67
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
61
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
62
+ "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
63
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
64
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
65
+ "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
66
+ "@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.2",
67
+ "@deepseek-ai/dsh-tools": "^0.1.1-rc.2"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@deepseek-ai/cordis": "4.0.1",
71
- "@deepseek-ai/dsh-agent": "0.1.0-rc.8",
72
- "@deepseek-ai/dsh-agent-default-model": "0.1.0-rc.8",
73
- "@deepseek-ai/dsh-client-connection": "0.1.0-rc.8",
74
- "@deepseek-ai/dsh-home-paths": "0.1.0-rc.8",
75
- "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
76
- "@deepseek-ai/dsh-session": "0.1.0-rc.8",
77
- "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
78
- "@deepseek-ai/dsh-system-prompt": "0.1.0-rc.8",
79
- "@deepseek-ai/dsh-tools": "0.1.0-rc.8",
71
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
72
+ "@deepseek-ai/dsh-agent-default-model": "0.1.1-rc.2",
73
+ "@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
74
+ "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
75
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
76
+ "@deepseek-ai/dsh-session": "0.1.1-rc.2",
77
+ "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
78
+ "@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2",
79
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
80
80
  "@types/node": "^22.0.0",
81
81
  "typescript": "^5.6.0"
82
82
  }