dsh-layered-memory 0.5.3

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/assets/readme/hero.svg +58 -0
  4. package/cordis.patch.yml +16 -0
  5. package/dist/client.js +1066 -0
  6. package/dist/config.d.ts +263 -0
  7. package/dist/config.js +65 -0
  8. package/dist/hooks/capture.d.ts +16 -0
  9. package/dist/hooks/capture.js +160 -0
  10. package/dist/hooks/recall.d.ts +31 -0
  11. package/dist/hooks/recall.js +201 -0
  12. package/dist/index.d.ts +190 -0
  13. package/dist/index.js +234 -0
  14. package/dist/llm.d.ts +32 -0
  15. package/dist/llm.js +128 -0
  16. package/dist/pipeline/l1.d.ts +19 -0
  17. package/dist/pipeline/l1.js +226 -0
  18. package/dist/pipeline/l2.d.ts +13 -0
  19. package/dist/pipeline/l2.js +77 -0
  20. package/dist/pipeline/l3.d.ts +15 -0
  21. package/dist/pipeline/l3.js +72 -0
  22. package/dist/pipeline/runner.d.ts +44 -0
  23. package/dist/pipeline/runner.js +132 -0
  24. package/dist/prompts/l1-dedup.d.ts +20 -0
  25. package/dist/prompts/l1-dedup.js +244 -0
  26. package/dist/prompts/l1-extraction.d.ts +18 -0
  27. package/dist/prompts/l1-extraction.js +434 -0
  28. package/dist/prompts/persona.d.ts +23 -0
  29. package/dist/prompts/persona.js +234 -0
  30. package/dist/prompts/scene.d.ts +33 -0
  31. package/dist/prompts/scene.js +408 -0
  32. package/dist/settings.d.ts +27 -0
  33. package/dist/settings.js +73 -0
  34. package/dist/stats.d.ts +46 -0
  35. package/dist/stats.js +234 -0
  36. package/dist/store/bm25.d.ts +19 -0
  37. package/dist/store/bm25.js +62 -0
  38. package/dist/store/embedding.d.ts +68 -0
  39. package/dist/store/embedding.js +133 -0
  40. package/dist/store/io.d.ts +13 -0
  41. package/dist/store/io.js +75 -0
  42. package/dist/store/l0.d.ts +28 -0
  43. package/dist/store/l0.js +148 -0
  44. package/dist/store/l1.d.ts +70 -0
  45. package/dist/store/l1.js +239 -0
  46. package/dist/store/persona.d.ts +15 -0
  47. package/dist/store/persona.js +60 -0
  48. package/dist/store/scenes.d.ts +23 -0
  49. package/dist/store/scenes.js +150 -0
  50. package/dist/store/search-utils.d.ts +22 -0
  51. package/dist/store/search-utils.js +71 -0
  52. package/dist/store/session-modes.d.ts +24 -0
  53. package/dist/store/session-modes.js +102 -0
  54. package/dist/store/sqlite.d.ts +121 -0
  55. package/dist/store/sqlite.js +922 -0
  56. package/dist/store/state.d.ts +35 -0
  57. package/dist/store/state.js +62 -0
  58. package/dist/tools/index.d.ts +21 -0
  59. package/dist/tools/index.js +169 -0
  60. package/dist/types.d.ts +86 -0
  61. package/dist/types.js +7 -0
  62. package/dist/util/filelog.d.ts +4 -0
  63. package/dist/util/filelog.js +43 -0
  64. package/dist/util/sanitize.d.ts +10 -0
  65. package/dist/util/sanitize.js +66 -0
  66. package/dist/util/text.d.ts +11 -0
  67. package/dist/util/text.js +43 -0
  68. package/package.json +80 -0
@@ -0,0 +1,35 @@
1
+ import type { MemoryFamily } from '../types.js';
2
+ export interface MemoryState {
3
+ /** 上次 L1 抽取时间(epoch ms)。 */
4
+ lastExtractAt: number;
5
+ /** 上次 L1 抽取得到的最后一个情境名(情境连续性用)。 */
6
+ lastSceneName: string;
7
+ /** L1 累计抽取条数。 */
8
+ totalExtracted: number;
9
+ /** 自上次 L2 整合以来的新记忆数。 */
10
+ newMemoriesSinceL2: number;
11
+ /** 上次 L2 整合时间。 */
12
+ lastL2At: number;
13
+ /** 自上次 L3 蒸馏以来的新记忆数。 */
14
+ memoriesSinceL3: number;
15
+ /** 上次 L3 蒸馏时间。 */
16
+ lastL3At: number;
17
+ /** L3 是否已生成过(冷启动判定)。 */
18
+ hasPersona: boolean;
19
+ /** L2 输出请求的 L3 更新原因([PERSONA_UPDATE_REQUEST])。 */
20
+ personaRequestedReason?: string;
21
+ }
22
+ export declare function defaultState(): MemoryState;
23
+ export declare class StateStore {
24
+ private readonly file;
25
+ private buckets;
26
+ private migrated;
27
+ constructor(file: string);
28
+ load(): Promise<void>;
29
+ /** v1 → v2 迁移发生时为 true(调用方记日志/落盘)。 */
30
+ get didMigrate(): boolean;
31
+ /** 取某族的 checkpoint(活引用——改字段后 save 生效)。 */
32
+ forFamily(family: MemoryFamily): MemoryState;
33
+ save(): Promise<void>;
34
+ static pathFor(dataDir: string): string;
35
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 管线 checkpoint 状态(移植 MemoryCore checkpoint/trigger 语义的精简版)。
3
+ * 持久化在 <dataDir>/state.json。
4
+ *
5
+ * v2 起按族分桶(L2/L3 分族隔离后阈值计数、情境链各自独立);
6
+ * 旧平铺格式(v1)在 load 时整体迁入 chat 桶(历史数据由 chat 档蒸馏产出)。
7
+ */
8
+ import * as path from 'node:path';
9
+ import { atomicWriteJson, readJsonIfExists } from './io.js';
10
+ export function defaultState() {
11
+ return {
12
+ lastExtractAt: 0,
13
+ lastSceneName: '',
14
+ totalExtracted: 0,
15
+ newMemoriesSinceL2: 0,
16
+ lastL2At: 0,
17
+ memoriesSinceL3: 0,
18
+ lastL3At: 0,
19
+ hasPersona: false,
20
+ };
21
+ }
22
+ export class StateStore {
23
+ file;
24
+ // 声明即初始化:forFamily 在 load 完成前也安全(stats 面板可能早于 runner.init 拉取)
25
+ buckets = { chat: defaultState(), work: defaultState() };
26
+ migrated = false;
27
+ constructor(file) {
28
+ this.file = file;
29
+ }
30
+ async load() {
31
+ const raw = await readJsonIfExists(this.file);
32
+ if (!raw)
33
+ return;
34
+ if (raw.version === 2 && raw.families && typeof raw.families === 'object') {
35
+ // v2:逐族宽容合并(新字段自动带默认值)
36
+ this.buckets = {
37
+ chat: { ...defaultState(), ...(raw.families.chat ?? {}) },
38
+ work: { ...defaultState(), ...(raw.families.work ?? {}) },
39
+ };
40
+ }
41
+ else {
42
+ // v1 平铺 → 整体迁入 chat 桶(历史数据是 chat 档蒸馏产出)
43
+ this.buckets = { chat: { ...defaultState(), ...raw }, work: defaultState() };
44
+ this.migrated = true;
45
+ }
46
+ }
47
+ /** v1 → v2 迁移发生时为 true(调用方记日志/落盘)。 */
48
+ get didMigrate() {
49
+ return this.migrated;
50
+ }
51
+ /** 取某族的 checkpoint(活引用——改字段后 save 生效)。 */
52
+ forFamily(family) {
53
+ return this.buckets[family];
54
+ }
55
+ async save() {
56
+ const file = { version: 2, families: this.buckets };
57
+ await atomicWriteJson(this.file, file);
58
+ }
59
+ static pathFor(dataDir) {
60
+ return path.join(dataDir, 'state.json');
61
+ }
62
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 模型可调用工具:memory_search(L1)、conversation_search(L0)、memory_read_scene(L2/L3)。
3
+ *
4
+ * 会话档位联动:execute 的 exec.agent 即发起调用的 agent(agent.id === sessionId),
5
+ * memory_search 按会话档位过滤族(auto 不过滤,纯档只查本族);off 档下三工具统一
6
+ * 返回提示(本会话已对记忆系统隐身)。conversation_search 检索范围保持全库。
7
+ */
8
+ import type { Context } from '@deepseek-ai/cordis';
9
+ import type { MemoryConfig } from '../config.js';
10
+ import type { L0Store } from '../store/l0.js';
11
+ import type { L1Store } from '../store/l1.js';
12
+ import type { PersonaStore } from '../store/persona.js';
13
+ import type { SceneStore } from '../store/scenes.js';
14
+ import type { SessionModeStore } from '../store/session-modes.js';
15
+ import type { MemoryFamily, MemoryLogger } from '../types.js';
16
+ export declare function registerMemoryTools(ctx: Context, cfg: MemoryConfig, stores: {
17
+ l0: L0Store;
18
+ l1: L1Store;
19
+ scenes: Record<MemoryFamily, SceneStore>;
20
+ persona: Record<MemoryFamily, PersonaStore>;
21
+ }, logger: MemoryLogger, modes: SessionModeStore): void;
@@ -0,0 +1,169 @@
1
+ import { defineTool } from '@deepseek-ai/dsh-tools';
2
+ const OFF_NOTICE = '本会话的记忆档位为"关闭":该会话对记忆系统完全隐身,不读取也不写入记忆。';
3
+ export function registerMemoryTools(ctx, cfg, stores, logger, modes) {
4
+ if (!cfg.tools)
5
+ return;
6
+ /** 调用会话的检索族(auto → undefined 不过滤;off → null 表示整体禁用)。 */
7
+ const familyOfCaller = (agentId) => {
8
+ if (agentId === undefined)
9
+ return undefined;
10
+ const mode = modes.get(agentId);
11
+ if (mode === 'off')
12
+ return null;
13
+ return mode === 'auto' ? undefined : mode;
14
+ };
15
+ // ── memory_search: L1 结构化记忆 ──
16
+ ctx.tools.register(defineTool({
17
+ name: 'memory_search',
18
+ description: '搜索结构化记忆(L1 原子记忆)。返回与查询相关的记忆片段:用户偏好、历史事件、项目事实、任务、规则、工作方法等。',
19
+ parameters: {
20
+ query: { type: 'string', required: true, description: '搜索查询文本(自然语言)' },
21
+ limit: { type: 'number', description: '最大返回条数(默认 5)' },
22
+ type: { type: 'string', description: '按记忆类型过滤(如 persona/episodic/instruction/work_fact/work_task/work_method/work_artifact)' },
23
+ },
24
+ output: {
25
+ schema: {
26
+ type: 'object',
27
+ properties: {
28
+ items: {
29
+ type: 'array',
30
+ items: {
31
+ type: 'object',
32
+ properties: {
33
+ content: { type: 'string' },
34
+ type: { type: 'string' },
35
+ scene_name: { type: 'string' },
36
+ score: { type: 'number' },
37
+ },
38
+ additionalProperties: false,
39
+ },
40
+ },
41
+ },
42
+ additionalProperties: false,
43
+ },
44
+ render: (_args, value) => [
45
+ { type: 'text', text: renderMemoryItems(value.items ?? []) },
46
+ ],
47
+ },
48
+ execute: async (args, exec) => {
49
+ const family = familyOfCaller(exec.agent?.id);
50
+ if (family === null)
51
+ return { items: [] };
52
+ const limit = Math.min(Math.max(args.limit ?? 5, 1), 20);
53
+ const hits = await stores.l1.search(args.query, limit, { type: args.type || undefined, family: family ?? undefined });
54
+ return {
55
+ items: hits.map((h) => ({
56
+ content: h.content,
57
+ type: h.type,
58
+ scene_name: h.scene_name,
59
+ score: Math.round(h.score * 100) / 100,
60
+ })),
61
+ };
62
+ },
63
+ }));
64
+ // ── conversation_search: L0 原始对话 ──
65
+ ctx.tools.register(defineTool({
66
+ name: 'conversation_search',
67
+ description: '搜索原始对话历史(L0)。返回带时间戳的原始消息,适用于查找具体消息原文、时间线、上下文细节。',
68
+ parameters: {
69
+ query: { type: 'string', required: true, description: '搜索查询文本' },
70
+ limit: { type: 'number', description: '最大返回条数(默认 5)' },
71
+ },
72
+ output: {
73
+ schema: {
74
+ type: 'object',
75
+ properties: {
76
+ items: {
77
+ type: 'array',
78
+ items: {
79
+ type: 'object',
80
+ properties: {
81
+ session_id: { type: 'string' },
82
+ role: { type: 'string' },
83
+ content: { type: 'string' },
84
+ timestamp: { type: 'number' },
85
+ },
86
+ additionalProperties: false,
87
+ },
88
+ },
89
+ },
90
+ additionalProperties: false,
91
+ },
92
+ render: (_args, value) => [
93
+ { type: 'text', text: renderConversationItems(value.items ?? []) },
94
+ ],
95
+ },
96
+ execute: async (args, exec) => {
97
+ if (familyOfCaller(exec.agent?.id) === null)
98
+ return { items: [] };
99
+ const limit = Math.min(Math.max(args.limit ?? 5, 1), 20);
100
+ const records = await stores.l0.search(args.query, limit);
101
+ return {
102
+ items: records.map((r) => ({
103
+ session_id: r.sessionId,
104
+ role: r.role,
105
+ content: r.content,
106
+ timestamp: r.timestamp,
107
+ })),
108
+ };
109
+ },
110
+ }));
111
+ // ── memory_read_scene: 读取 L2 场景块 / L3 画像 ──
112
+ ctx.tools.register(defineTool({
113
+ name: 'memory_read_scene',
114
+ description: '读取记忆文件详情:L2 场景块(场景目录下的 .md 文件)或 L3 画像(persona-chat.md / persona-work.md)。返回文件完整内容。',
115
+ parameters: {
116
+ path: { type: 'string', required: true, description: '场景文件名,或 persona-chat.md / persona-work.md' },
117
+ },
118
+ output: {
119
+ schema: {
120
+ type: 'object',
121
+ properties: {
122
+ content: { type: 'string', description: '文件内容(不存在则为空字符串)' },
123
+ },
124
+ additionalProperties: false,
125
+ },
126
+ render: (_args, value) => [
127
+ { type: 'text', text: value.content ? `\`\`\`markdown\n${value.content}\n\`\`\`` : '(文件不存在或为空)' },
128
+ ],
129
+ },
130
+ execute: async (args, exec) => {
131
+ if (familyOfCaller(exec.agent?.id) === null)
132
+ return { content: OFF_NOTICE };
133
+ const p = args.path.trim();
134
+ let content;
135
+ if (p === 'persona.md' || p === 'persona-chat.md' || p === 'persona' || p === 'persona-chat') {
136
+ content = await stores.persona.chat.read();
137
+ }
138
+ else if (p === 'persona-work.md' || p === 'persona-work') {
139
+ content = await stores.persona.work.read();
140
+ }
141
+ else {
142
+ // 场景文件在两族目录里按名查找(先本族后另一族)
143
+ const primary = familyOfCaller(exec.agent?.id) ?? 'chat';
144
+ const other = primary === 'chat' ? 'work' : 'chat';
145
+ content =
146
+ (await stores.scenes[primary].read(p)) ?? (await stores.scenes[other].read(p));
147
+ }
148
+ return { content: content ?? '' };
149
+ },
150
+ }));
151
+ logger.info('[memory] 工具已注册: memory_search / conversation_search / memory_read_scene');
152
+ }
153
+ function renderMemoryItems(items) {
154
+ if (!items || items.length === 0)
155
+ return '(没有找到相关记忆)';
156
+ return items
157
+ .map((it, i) => `${i + 1}. [${it.type ?? ''}]${it.scene_name ? ` (${it.scene_name})` : ''} ${it.content ?? ''}`)
158
+ .join('\n');
159
+ }
160
+ function renderConversationItems(items) {
161
+ if (!items || items.length === 0)
162
+ return '(没有找到相关对话)';
163
+ return items
164
+ .map((it, i) => {
165
+ const time = it.timestamp ? new Date(it.timestamp).toISOString() : '';
166
+ return `${i + 1}. [${it.role ?? ''}]${time ? ` ${time}` : ''} (session=${it.session_id ?? ''})\n${it.content ?? ''}`;
167
+ })
168
+ .join('\n\n');
169
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 共享类型定义(移植自 MemoryCore 的会话/记忆数据模型,做 DSH 适配裁剪)。
3
+ */
4
+ /** 蒸馏 Prompt 家族:chat = 个人记忆(persona/episodic/instruction + 用户画像),work = 工作记忆(work_fact/work_task/work_method/work_artifact + Team Operating Doctrine)。 */
5
+ export type MemoryFamily = 'chat' | 'work';
6
+ /** 会话记忆档位:auto = 双族自动判定 | chat/work = 单族 | off = 本会话对记忆系统隐身。 */
7
+ export type MemoryMode = 'auto' | 'chat' | 'work' | 'off';
8
+ /** 蒸馏可用的档位(off 在捕获侧被拦截,永远到不了管线)。 */
9
+ export type ExtractMode = 'auto' | 'chat' | 'work';
10
+ /** 记忆族标签推断:work_* 前缀 → work,其余(含 auto 档兜底)→ chat。 */
11
+ export declare function familyForType(type: string): MemoryFamily;
12
+ /** 日志接口(适配 ctx.logger)。 */
13
+ export interface MemoryLogger {
14
+ debug?(msg: string): void;
15
+ info(msg: string): void;
16
+ warn(msg: string): void;
17
+ error(msg: string): void;
18
+ }
19
+ /** L0 会话消息(与 MemoryCore 的 ConversationMessage 对齐)。 */
20
+ export interface ConversationMessage {
21
+ /** 唯一消息 ID(L1 prompt 的 source_message_ids 追踪用)。 */
22
+ id: string;
23
+ role: 'user' | 'assistant';
24
+ content: string;
25
+ /** epoch ms */
26
+ timestamp: number;
27
+ }
28
+ /** L0 JSONL 记录(一条消息一行)。 */
29
+ export interface L0MessageRecord {
30
+ sessionId: string;
31
+ recordedAt: string;
32
+ id: string;
33
+ role: 'user' | 'assistant';
34
+ content: string;
35
+ timestamp: number;
36
+ }
37
+ /** L1 抽取产出(LLM 返回的记忆条目,尚未分配 record id)。 */
38
+ export interface ExtractedMemory {
39
+ content: string;
40
+ type: string;
41
+ priority: number;
42
+ source_message_ids: string[];
43
+ metadata: Record<string, unknown>;
44
+ /** 所属情境名(L1 抽取的情境切分结果)。 */
45
+ scene_name: string;
46
+ }
47
+ /** L1 持久化记录(字段对齐 MemoryCore;version/source_message_ids/metadata 由写入侧补默认)。 */
48
+ export interface MemoryRecord {
49
+ id: string;
50
+ content: string;
51
+ type: string;
52
+ priority: number;
53
+ scene_name: string;
54
+ /** 合并/更新时保留的时间戳并集。 */
55
+ timestamps: number[];
56
+ createdAt: number;
57
+ updatedAt: number;
58
+ /** 每次 update/merge 合并 +1(官方语义)。 */
59
+ version?: number;
60
+ /** 来源消息 id(JSONL 事实源保留;检索库不存储该列)。 */
61
+ source_message_ids?: string[];
62
+ /** 类型附加信息(episodic 的活动起止时间等)。 */
63
+ metadata?: Record<string, unknown>;
64
+ /** 来源会话(缺省 default;跨会话记忆共享)。 */
65
+ sessionId?: string;
66
+ /** 所属族(写入时缺省由 familyForType(type) 回填;召回/浏览/去重候选按族过滤的唯一依据)。 */
67
+ family?: MemoryFamily;
68
+ }
69
+ /** L2 场景块摘要(META 解析结果)。 */
70
+ export interface SceneSummary {
71
+ path: string;
72
+ created: string;
73
+ updated: string;
74
+ summary: string;
75
+ heat: number;
76
+ }
77
+ /** L1 检索命中。 */
78
+ export interface L1Hit {
79
+ id: string;
80
+ content: string;
81
+ type: string;
82
+ scene_name: string;
83
+ score: number;
84
+ priority?: number;
85
+ family?: MemoryFamily;
86
+ }
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 共享类型定义(移植自 MemoryCore 的会话/记忆数据模型,做 DSH 适配裁剪)。
3
+ */
4
+ /** 记忆族标签推断:work_* 前缀 → work,其余(含 auto 档兜底)→ chat。 */
5
+ export function familyForType(type) {
6
+ return type.startsWith('work') ? 'work' : 'chat';
7
+ }
@@ -0,0 +1,4 @@
1
+ import type { MemoryLogger } from '../types.js';
2
+ /** 错误对象转带堆栈的单行描述(诊断日志用,非 Error 直接字符串化)。 */
3
+ export declare function errDetail(err: unknown): string;
4
+ export declare function withFileLog(dataDir: string, logger: MemoryLogger): MemoryLogger;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 文件日志:dsh 宿主只把插件日志打到控制台(无持久化),
3
+ * 这里镜像 warn/error/info 到数据目录 memory.log,供事后诊断蒸馏管线。
4
+ * 写入失败静默忽略——诊断日志绝不能反过来拖垮管线。
5
+ */
6
+ import { appendFileSync, existsSync, renameSync, statSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ const MAX_LOG_BYTES = 2 * 1024 * 1024;
9
+ /** 错误对象转带堆栈的单行描述(诊断日志用,非 Error 直接字符串化)。 */
10
+ export function errDetail(err) {
11
+ if (err instanceof Error)
12
+ return `${err.message} @ ${err.stack?.split('\n')[1]?.trim() ?? err.name}`;
13
+ return String(err);
14
+ }
15
+ export function withFileLog(dataDir, logger) {
16
+ const logPath = join(dataDir, 'memory.log');
17
+ const write = (level, msg) => {
18
+ try {
19
+ if (existsSync(logPath) && statSync(logPath).size > MAX_LOG_BYTES) {
20
+ renameSync(logPath, `${logPath}.1`);
21
+ }
22
+ appendFileSync(logPath, `${new Date().toISOString()} [${level}] ${msg}\n`);
23
+ }
24
+ catch {
25
+ /* ignore */
26
+ }
27
+ };
28
+ return {
29
+ debug: (m) => logger.debug?.(m),
30
+ info: (m) => {
31
+ logger.info(m);
32
+ write('info', m);
33
+ },
34
+ warn: (m) => {
35
+ logger.warn(m);
36
+ write('warn', m);
37
+ },
38
+ error: (m) => {
39
+ logger.error(m);
40
+ write('error', m);
41
+ },
42
+ };
43
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * L0 捕获文本清洗(移植自 MemoryCore openclaw-plugin/src/sanitize.ts)。
3
+ * 保证:清除我们注入的召回标签(防反馈循环)、框架元数据块、媒体标记等。
4
+ */
5
+ /** 剥离注入的记忆标签 + 框架元数据块 + 媒体标记。 */
6
+ export declare function sanitizeText(text: string): string;
7
+ /** 剥离助手回复中的围栏代码块(保留解释性文本)。 */
8
+ export declare function stripCodeBlocks(text: string): string;
9
+ /** L0 捕获过滤——宽松:只丢弃结构性无用消息。 */
10
+ export declare function shouldCaptureL0(text: string): boolean;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * L0 捕获文本清洗(移植自 MemoryCore openclaw-plugin/src/sanitize.ts)。
3
+ * 保证:清除我们注入的召回标签(防反馈循环)、框架元数据块、媒体标记等。
4
+ */
5
+ /** 剥离注入的记忆标签 + 框架元数据块 + 媒体标记。 */
6
+ export function sanitizeText(text) {
7
+ let cleaned = text;
8
+ // 注入的记忆上下文标签(防止再捕获时反馈循环)
9
+ cleaned = cleaned.replace(/<relevant-memories>[\s\S]*?<\/relevant-memories>/g, '');
10
+ cleaned = cleaned.replace(/<user-persona>[\s\S]*?<\/user-persona>/g, '');
11
+ cleaned = cleaned.replace(/<relevant-scenes>[\s\S]*?<\/relevant-scenes>/g, '');
12
+ cleaned = cleaned.replace(/<scene-navigation>[\s\S]*?<\/scene-navigation>/g, '');
13
+ cleaned = cleaned.replace(/<memory-tools-guide>[\s\S]*?<\/memory-tools-guide>/g, '');
14
+ // 任务上下文注入块
15
+ cleaned = cleaned.replace(/<current_task_context>[\s\S]*?<\/current_task_context>/g, '');
16
+ cleaned = cleaned.replace(/<history_task_context[\s\S]*?<\/history_task_context>/g, '');
17
+ // 框架注入的入站元数据块(label + ```json ... ```)
18
+ cleaned = cleaned.replace(/(?:Conversation info|Sender|Thread starter|Replied message|Forwarded message context|Chat history since last reply)\s*\(untrusted[\s\S]*?\):\s*```json\s*[\s\S]*?```/g, '');
19
+ // 旧版会话元数据 JSON 块
20
+ cleaned = cleaned.replace(/```json\s*\{[\s\S]*?"session[\s\S]*?\}\s*```/g, '');
21
+ // 回复指令标签
22
+ cleaned = cleaned.replace(/\[\[reply_to[^\]]*\]\]\s*/g, '');
23
+ // Skill 选择包裹符
24
+ cleaned = cleaned.replace(/¥¥\[[\s\S]*?\]¥¥/g, '');
25
+ // 行首时间戳
26
+ cleaned = cleaned.replace(/^\[[\w\d\-:+ ]+\]\s*/gm, '');
27
+ // 媒体附件标记
28
+ cleaned = cleaned.replace(/\[media attached:[^\]]*\]\s*/g, '');
29
+ // 图片回复指令
30
+ cleaned = cleaned.replace(/To send an image back,[\s\S]*?(?:Keep caption in the text body\.)\s*/g, '');
31
+ // 系统执行块
32
+ cleaned = cleaned.replace(/^System:\s*\[[\s\S]*?$/gm, '');
33
+ // 内联 base64 图片
34
+ cleaned = cleaned.replace(/data:image\/[a-z+]+;base64,[A-Za-z0-9+/=]+/gi, '');
35
+ // 空字符 + 折叠空白
36
+ cleaned = cleaned.replace(/\0/g, '').replace(/\n{3,}/g, '\n\n').trim();
37
+ return cleaned;
38
+ }
39
+ /** 剥离助手回复中的围栏代码块(保留解释性文本)。 */
40
+ export function stripCodeBlocks(text) {
41
+ return text.replace(/```[^\n]*\n[\s\S]*?```/g, '').replace(/\n{3,}/g, '\n\n').trim();
42
+ }
43
+ /** L0 捕获过滤——宽松:只丢弃结构性无用消息。 */
44
+ export function shouldCaptureL0(text) {
45
+ if (!text || !text.trim())
46
+ return false;
47
+ if (isFrameworkNoise(text))
48
+ return false;
49
+ if (text.startsWith('/'))
50
+ return false;
51
+ return true;
52
+ }
53
+ function isFrameworkNoise(text) {
54
+ const t = text.trim();
55
+ if (t === '(session bootstrap)')
56
+ return true;
57
+ if (t.startsWith('A new session was started via'))
58
+ return true;
59
+ if (/^✅\s*New session started/.test(t))
60
+ return true;
61
+ if (t.startsWith('Pre-compaction memory flush'))
62
+ return true;
63
+ if (/^NO_REPLY\s*$/.test(t))
64
+ return true;
65
+ return false;
66
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 文本工具:ContentBlock → 纯文本;BM25 分词。
3
+ */
4
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm';
5
+ /** 把消息的 ContentBlock[] 展平成纯文本(仅 text 块)。 */
6
+ export declare function blocksToText(blocks: readonly ContentBlock[] | undefined): string;
7
+ /**
8
+ * 轻量中英混排分词:英文按词,中文按二元组。
9
+ * 与 MemoryCore 的 BM25 思路一致(无外部分词依赖)。
10
+ */
11
+ export declare function tokenize(text: string): string[];
@@ -0,0 +1,43 @@
1
+ /** 把消息的 ContentBlock[] 展平成纯文本(仅 text 块)。 */
2
+ export function blocksToText(blocks) {
3
+ if (!blocks)
4
+ return '';
5
+ const parts = [];
6
+ for (const b of blocks) {
7
+ if (b.type === 'text')
8
+ parts.push(b.text);
9
+ else if (b.type === 'reasoning')
10
+ parts.push(b.text);
11
+ }
12
+ return parts.join('\n');
13
+ }
14
+ const CJK_RE = /[\u3400-\u9fff\uf900-\ufaff]/;
15
+ const WORD_RE = /[a-zA-Z0-9][a-zA-Z0-9_-]{1,}/g;
16
+ /**
17
+ * 轻量中英混排分词:英文按词,中文按二元组。
18
+ * 与 MemoryCore 的 BM25 思路一致(无外部分词依赖)。
19
+ */
20
+ export function tokenize(text) {
21
+ const tokens = [];
22
+ const lower = text.toLowerCase();
23
+ for (const m of lower.matchAll(WORD_RE))
24
+ tokens.push(m[0]);
25
+ // CJK 二元组
26
+ const cjk = lower.replace(/[^\u3400-\u9fff\uf900-\ufaff]/g, ' ');
27
+ let i = 0;
28
+ while (i < cjk.length) {
29
+ const ch = cjk[i];
30
+ if (CJK_RE.test(ch)) {
31
+ const next = cjk[i + 1];
32
+ if (next && CJK_RE.test(next))
33
+ tokens.push(ch + next);
34
+ else
35
+ tokens.push(ch);
36
+ i += 1;
37
+ }
38
+ else {
39
+ i += 1;
40
+ }
41
+ }
42
+ return tokens.filter((t) => t.length >= 2 || CJK_RE.test(t));
43
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "dsh-layered-memory",
3
+ "version": "0.5.3",
4
+ "description": "L0~L3 分层蒸馏记忆插件 for DeepSeek Harness:自动捕获对话(L0)、抽取原子记忆(L1)、整合场景块(L2)、蒸馏核心画像/团队方法论(L3),并在模型步骤前自动召回注入。移植自 MemoryCore (TencentDB Agent Memory) 的管线设计。",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./client": "./dist/client.js",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "dsh": {
17
+ "bundle": {
18
+ "patch": "./cordis.patch.yml"
19
+ },
20
+ "client": {
21
+ "inject": [
22
+ "@deepseek-ai/dsh-client-runtime",
23
+ "@deepseek-ai/dsh-client-connection"
24
+ ],
25
+ "platform": "web"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "assets",
31
+ "cordis.patch.yml"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json && node scripts/copy-client.mjs",
35
+ "smoke": "node dist-smoke/smoke.js"
36
+ },
37
+ "engines": {
38
+ "node": ">=22.16.0"
39
+ },
40
+ "keywords": [
41
+ "dsh",
42
+ "deepseek-harness",
43
+ "memory",
44
+ "agent-memory",
45
+ "cordis-plugin"
46
+ ],
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/JunNanLYS/dsh-layered-memory.git"
50
+ },
51
+ "license": "MIT",
52
+ "dependencies": {
53
+ "@deepseek-ai/schemastery": "3.18.1",
54
+ "sqlite-vec": "^0.1.7-alpha.2"
55
+ },
56
+ "peerDependencies": {
57
+ "@deepseek-ai/cordis": "^4.0.1",
58
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
59
+ "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6",
60
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
61
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
62
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
65
+ },
66
+ "devDependencies": {
67
+ "@deepseek-ai/cordis": "4.0.1",
68
+ "@deepseek-ai/dsh-agent": "0.1.0-rc.6",
69
+ "@deepseek-ai/dsh-agent-default-model": "0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-client-connection": "0.1.0-rc.6",
71
+ "@deepseek-ai/dsh-home-paths": "0.1.0-rc.6",
72
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
73
+ "@deepseek-ai/dsh-session": "0.1.0-rc.6",
74
+ "@deepseek-ai/dsh-settings": "0.1.0-rc.6",
75
+ "@deepseek-ai/dsh-system-prompt": "0.1.0-rc.6",
76
+ "@deepseek-ai/dsh-tools": "0.1.0-rc.6",
77
+ "@types/node": "^22.0.0",
78
+ "typescript": "^5.6.0"
79
+ }
80
+ }