dsh-layered-memory 0.8.4 → 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/README.en.md +28 -11
- package/README.md +24 -11
- package/assets/img/ui-dark.jpg +0 -0
- package/assets/img/ui-light.jpg +0 -0
- package/assets/readme/bench-dialog.svg +49 -55
- package/dist/bench-control.d.ts +35 -0
- package/dist/bench-control.js +16 -0
- package/dist/client.js +173 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +1 -0
- package/dist/hooks/recall.d.ts +23 -0
- package/dist/hooks/recall.js +36 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -1
- package/dist/llm-usage.d.ts +27 -0
- package/dist/llm-usage.js +39 -0
- package/dist/llm.d.ts +3 -0
- package/dist/llm.js +6 -0
- package/dist/pipeline/l1.js +4 -2
- package/dist/pipeline/l2.js +1 -0
- package/dist/pipeline/l3.js +1 -0
- package/dist/pipeline/runner.d.ts +16 -0
- package/dist/pipeline/runner.js +60 -0
- package/dist/prompts/l1-extraction.d.ts +7 -1
- package/dist/prompts/l1-extraction.js +12 -3
- package/dist/stats.d.ts +27 -1
- package/dist/stats.js +37 -2
- package/dist/store/l0.d.ts +2 -0
- package/dist/store/l0.js +4 -0
- package/dist/store/sqlite.d.ts +2 -0
- package/dist/store/sqlite.js +14 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.js +8 -0
- package/package.json +1 -1
|
@@ -291,7 +291,7 @@ metadata 字段说明:
|
|
|
291
291
|
|
|
292
292
|
请严格按上述 JSON 数组格式输出,不要输出任何额外的 Markdown 代码块修饰符(如 \`\`\`json)或解释文本。`;
|
|
293
293
|
export const EXTRACT_ALL_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分与记忆提取专家"。
|
|
294
|
-
你的任务是分析用户的对话,判断情境切换,并从中提取结构化的核心记忆。对话可能同时包含个人生活与工作内容:个人内容提取为个人记忆(persona/episodic/instruction),工作内容提取为团队共享工作记忆(work_fact/work_task/work_method/work_artifact
|
|
294
|
+
你的任务是分析用户的对话,判断情境切换,并从中提取结构化的核心记忆。对话可能同时包含个人生活与工作内容:个人内容提取为个人记忆(persona/episodic/instruction),工作内容提取为团队共享工作记忆(work_fact/work_task/work_method/work_artifact),互不排斥、**每条记忆显式输出 family 字段标注归族**。
|
|
295
295
|
|
|
296
296
|
**输出语言**:所有自由文本字段(\`scene_name\`、memory \`content\`)使用与待提取消息主导语言相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。
|
|
297
297
|
|
|
@@ -318,7 +318,14 @@ export const EXTRACT_ALL_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分
|
|
|
318
318
|
|
|
319
319
|
【支持提取的七类记忆】(必须严格遵守类型规则)
|
|
320
320
|
|
|
321
|
-
|
|
321
|
+
**归族判定(family)——先定族,再选型:**
|
|
322
|
+
每条记忆都必须显式输出 "family": "chat" 或 "family": "work",判定**只看语境,不看内容形状**:
|
|
323
|
+
- **work**:内容发生在职业语境——项目、团队、系统、业务、客户、同事协作、部署/开发/运维;
|
|
324
|
+
- **chat**:内容发生在用户个人生活语境——家庭、宠物、健康、消费、学习、爱好、个人行程与安排;
|
|
325
|
+
- **形状词(方案/安排/计划/SOP/频率/周期/规则)不决定 family**:个人的喂养计划、健身安排、用药周期、家庭事务流程都是 chat;只有职业团队语境下的方案流程才是 work。
|
|
326
|
+
- **family 决定 type 的可选词表,不许交叉**:family 为 chat 的记忆 type 只能是 persona/episodic/instruction;family 为 work 的记忆 type 只能是 work_fact/work_task/work_method/work_artifact。
|
|
327
|
+
|
|
328
|
+
**个人三类(family: "chat",对话涉及用户个人生活/偏好/对 AI 的要求时):**
|
|
322
329
|
|
|
323
330
|
1. 个性化记忆 (type: "persona")
|
|
324
331
|
- 定义:用户的稳定属性、偏好、技能、价值观、习惯(如住所、职业、饮食禁忌)。
|
|
@@ -327,6 +334,7 @@ export const EXTRACT_ALL_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分
|
|
|
327
334
|
|
|
328
335
|
2. 客观事件记忆 (type: "episodic")
|
|
329
336
|
- 定义:客观发生的动作、决定、计划或达成结果。绝不包含纯主观感受。
|
|
337
|
+
- **持续生效的个人安排/规则也属此类**:喂养频率、驱虫/疫苗周期、用药安排、定期事务等长期个人计划,按"用户为 [对象] 确定了 [安排]"句式提取为 episodic——即使表述像"方案/周期/规则",只要语境是个人生活就是 chat 族,**不要**因为形状像计划/方法而改用 work_* 类型。
|
|
330
338
|
- 提取句式:"用户([姓名])在 [最好是精确绝对时间] 于 [地点] [做了某事(可以包含起因、经过、结果)]"。
|
|
331
339
|
- 时间约束:尽量基于消息的 timestamp 推算绝对时间,如能确定则在 metadata 中输出 activity_start_time 和 activity_end_time(ISO 8601格式)。
|
|
332
340
|
- 打分 (priority):80-100(重要事件/计划);60-70(一般完整活动);<60(琐碎事项,直接丢弃)。
|
|
@@ -336,7 +344,7 @@ export const EXTRACT_ALL_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分
|
|
|
336
344
|
- 提取句式:"用户要求/希望 AI 以后回答时..."
|
|
337
345
|
- 打分 (priority):-1(极其严格的全局死命令);90-100(核心行为规则);70-80(重要要求);<70(临时要求,直接丢弃)。
|
|
338
346
|
|
|
339
|
-
|
|
347
|
+
**工作四类(family: "work",对话涉及项目、任务、团队协作时,默认可在项目团队内共享):**
|
|
340
348
|
|
|
341
349
|
4. 工作事实 (type: "work_fact")
|
|
342
350
|
- 定义:关于项目、系统、业务、客户、需求、决策、状态、风险、约束、实验结果的事实性信息。
|
|
@@ -379,6 +387,7 @@ export const EXTRACT_ALL_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分
|
|
|
379
387
|
{
|
|
380
388
|
"content": "完整、独立的记忆陈述(按对应类型的句式要求)",
|
|
381
389
|
"type": "persona|episodic|instruction|work_fact|work_task|work_method|work_artifact",
|
|
390
|
+
"family": "chat|work",
|
|
382
391
|
"priority": 80,
|
|
383
392
|
"source_message_ids": ["消息ID_1", "消息ID_2"],
|
|
384
393
|
"metadata": {}
|
package/dist/stats.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import { type MemoryConfig } from './config.js';
|
|
3
|
+
import { type RecallSessionStats } from './hooks/recall.js';
|
|
3
4
|
import type { RebuildController } from './pipeline/rebuild.js';
|
|
4
5
|
import type { LiveSettingsHandle } from './settings.js';
|
|
5
6
|
import type { L0Store } from './store/l0.js';
|
|
@@ -18,6 +19,31 @@ export interface MemoryStatusSource {
|
|
|
18
19
|
/** L1 抽取待重试的消息条数。 */
|
|
19
20
|
pending(): number;
|
|
20
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* 会话级统计数据源(悬浮卡信息区;index.ts 注入)。
|
|
24
|
+
* 硬规则:本端点按"打开期间 2~5s 轮询"设计,实现只允许内存注册表读取与
|
|
25
|
+
* 索引化 SQL 点查——禁止任何文件读/目录扫描(scenes.list()/persona.read()
|
|
26
|
+
* 级别的 I/O 会把每次轮询变成数十毫秒的全量读,见 slider-spec 数据策略节)。
|
|
27
|
+
*/
|
|
28
|
+
export interface SessionInfoSource {
|
|
29
|
+
/** 召回统计(recall.ts 注册表;未发生检索的会话返回 undefined)。 */
|
|
30
|
+
recallStats(sessionId: string): RecallSessionStats | undefined;
|
|
31
|
+
/** 蒸馏管线会话视图(runner:攒批进度/挂起切片/会话产出)。 */
|
|
32
|
+
runnerView(sessionId: string, mode: string): {
|
|
33
|
+
pendingSlice: number;
|
|
34
|
+
parkedSlices: number;
|
|
35
|
+
threshold: number | null;
|
|
36
|
+
producedRecords: number;
|
|
37
|
+
lastDistillAt: number | null;
|
|
38
|
+
};
|
|
39
|
+
/** L0 该会话已捕获消息数(索引 COUNT)。 */
|
|
40
|
+
l0Count(sessionId: string): Promise<number>;
|
|
41
|
+
/** 检索能力位(hybrid / keyword 降级判定)。 */
|
|
42
|
+
capabilities(): {
|
|
43
|
+
ftsSearch: boolean;
|
|
44
|
+
vectorSearch: boolean;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
21
47
|
export interface MemoryStats {
|
|
22
48
|
ok: boolean;
|
|
23
49
|
dataDir: string;
|
|
@@ -50,4 +76,4 @@ export declare function registerMemoryRpc(ctx: Context, cfg: MemoryConfig, store
|
|
|
50
76
|
scenes: Record<MemoryFamily, SceneStore>;
|
|
51
77
|
persona: Record<MemoryFamily, PersonaStore>;
|
|
52
78
|
state: StateStore;
|
|
53
|
-
}, logger: MemoryLogger, status?: MemoryStatusSource, live?: LiveSettingsHandle, modes?: SessionModeStore, dataDir?: string, rebuild?: RebuildController, embedManager?: EmbeddingManager): void;
|
|
79
|
+
}, logger: MemoryLogger, status?: MemoryStatusSource, live?: LiveSettingsHandle, modes?: SessionModeStore, dataDir?: string, rebuild?: RebuildController, embedManager?: EmbeddingManager, sessionInfo?: SessionInfoSource): void;
|
package/dist/stats.js
CHANGED
|
@@ -11,12 +11,13 @@ import { closeSync, openSync, readSync, statSync } from 'node:fs';
|
|
|
11
11
|
import { join } from 'node:path';
|
|
12
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 };
|
package/dist/store/l0.d.ts
CHANGED
|
@@ -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);
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -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[];
|
package/dist/store/sqlite.js
CHANGED
|
@@ -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
|
+
"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",
|