@modusensus/dsh-mneme 0.4.4-beta.1 → 0.4.4

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.md CHANGED
@@ -162,6 +162,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
162
162
  | **v0.4.0** | ✅ 完成 | 系统级睡眠 Sleep Mode | 空闲触发的四阶段深度维护(冲突消解 / 归档降级 / 模式发现 / 关系补全)、分层压缩、可中断串行 fail-safe;471 测试全绿 |
163
163
  | **v0.4.2** | ✅ 完成 | autoSummarize 自定义模型 | `summarizeProvider`/`summarizeModel` 配置项支持,可独立指定轻量模型(如 qwen3.6-plus)用于会话摘要,节省主模型 token;473 测试全绿 |
164
164
  | **v0.4.3** | ✅ 完成 | autoDream 大记忆量修复 | issue#9 B+A:`dreamMaxTokens` 上限 32768→131072 + `dreamReasoningEffort`/`sleepReasoningEffort` 思考开关(none 默认,主对话不受影响);478 测试全绿 |
165
+ | **v0.4.4** | ✅ 完成 | autoDream 决策覆盖修复 | issue#9 方案C:滑动窗口 `dreamMaxSnapshotSize`(默认200,updated_at 倒序截断) + 隐式 keep `dreamImplicitKeep`(默认true) + 覆盖率下限 `dreamMinExplicitCoverage`(默认50%) + 固定决策 schema;487 测试全绿 |
165
166
  | **v0.5.0+** | 🚀 远期 | 自进化记忆 | 兴趣漂移跟踪 + 跨 workspace 记忆共享(等 DSH 支持) |
166
167
 
167
168
  > 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
package/lib/dream.js CHANGED
@@ -6,11 +6,31 @@ export { validateDecisions, applyDecisions };
6
6
  const SUMMARY_PROMPT = `你是记忆库摘要助手。根据整理后的记忆,生成一段 150-200 字的记忆库总览,覆盖:用户偏好、活跃项目、关键决策。之后作为会话上下文注入。只输出摘要文本,不要其他内容。`;
7
7
 
8
8
  const CONSOLIDATION_PROMPT = `你是记忆库整理助手。下面是全部记忆条目(id、类型、标题、内容、重要性、更新时间)。
9
- 请执行记忆巩固(consolidation):
10
- 1. 识别主题相近的条目 → 输出 merge(合并为更精炼的摘要,保留信息最完整的 id 作为 keepSource)
11
- 2. 识别重复/过时信息 → 输出 archive
12
- 3. 识别内容矛盾的条目 → 输出 conflict(根据时间新旧、来源完整性、信息具体程度判断 winner/loser)
13
- 4. 发现单条记忆中的信息已过时、错误或遗漏 输出 update(直接修正内容)
9
+ 请执行记忆巩固(consolidation),输出一个决策 JSON 数组。
10
+
11
+ 【决策格式(必须严格遵守)】
12
+ 每个决策必须是对象,字段固定:
13
+ - "action":必填。取值只能是 "keep" / "merge" / "archive" / "update" / "conflict" 之一(字段名必须是 action,严禁写成 type)
14
+ - "ids":必填,数组,本决策涉及的记忆 id 列表
15
+ - "reason":可选,字符串,决策理由
16
+ - "importance":可选,整数 1-5
17
+ - merge 额外字段:"keepSource"(单个 id 字符串,必须是 ids 之一)+ 合并后的 "title"、"content"
18
+ - conflict 额外字段:"winner" 与 "loser",都是【单个 id 字符串,不是数组】
19
+ - update 额外字段:修正后的 "title" 和/或 "content";"ids" 只能包含一个 id
20
+
21
+ 【决策 JSON 示例】
22
+ [
23
+ { "action": "merge", "ids": ["m1", "m2"], "keepSource": "m1", "title": "合并标题", "content": "合并后的摘要内容", "importance": 4, "reason": "主题相近" },
24
+ { "action": "conflict", "winner": "m3", "loser": "m4", "reason": "内容矛盾,保留更新的信息" },
25
+ { "action": "update", "ids": ["m5"], "content": "修正后的内容", "reason": "信息过时" },
26
+ { "action": "archive", "ids": ["m6"], "reason": "重复或过时" }
27
+ ]
28
+
29
+ 【任务】
30
+ 1. 识别主题相近的条目 → merge(合并为更精炼的摘要,保留信息最完整的 id 作为 keepSource)
31
+ 2. 识别重复/过时信息 → archive
32
+ 3. 识别内容矛盾的条目 → conflict(按时间新旧、来源完整性、信息具体程度判断 winner/loser)
33
+ 4. 发现单条记忆中的信息过时、错误或遗漏 → update(直接修正内容)
14
34
  - update 的 ids 只能包含一个 id
15
35
  - 必须提供修正后的 title 和/或 content
16
36
  - 仅当内容确实需要修正时才使用,不要滥用
@@ -18,13 +38,15 @@ const CONSOLIDATION_PROMPT = `你是记忆库整理助手。下面是全部记
18
38
  - 24 小时内新建的记忆不可 update
19
39
  5. 无问题的条目无需输出(未提及的条目将自动保留 keep)
20
40
 
21
- 规则:
41
+ 【硬性规则】
42
+ - 字段名必须精确为 "action",严禁写成 "type";字段名统一用双引号
43
+ - conflict 的 winner/loser、merge 的 keepSource 都是【单个 id 字符串,绝不是数组】
44
+ - 每条记忆最多被 claim 一次:同一个 id 不能出现在多个决策中(同一 id 不能被 merge 和 conflict/archive 等重复占用)
22
45
  - 未在决策中提及的记忆将自动保留(keep),无需为每条记忆输出 keep
23
46
  - merge 的 keepSource 必须是 ids 之一
24
47
  - 仅合并同类型条目(type 相同)
25
48
  - 不要编造 ids;只使用提供的 id
26
49
  - 重要性 1-5,合并后取最高
27
- - update 只能改一条,且要有实际变化
28
50
  - 只输出 JSON 数组,不要其他文字`;
29
51
 
30
52
  function totalChars(memories) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@modusensus/dsh-mneme",
3
3
  "description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
4
- "version": "0.4.4-beta.1",
4
+ "version": "0.4.4",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
package/src/dream.js CHANGED
@@ -6,11 +6,31 @@ export { validateDecisions, applyDecisions };
6
6
  const SUMMARY_PROMPT = `你是记忆库摘要助手。根据整理后的记忆,生成一段 150-200 字的记忆库总览,覆盖:用户偏好、活跃项目、关键决策。之后作为会话上下文注入。只输出摘要文本,不要其他内容。`;
7
7
 
8
8
  const CONSOLIDATION_PROMPT = `你是记忆库整理助手。下面是全部记忆条目(id、类型、标题、内容、重要性、更新时间)。
9
- 请执行记忆巩固(consolidation):
10
- 1. 识别主题相近的条目 → 输出 merge(合并为更精炼的摘要,保留信息最完整的 id 作为 keepSource)
11
- 2. 识别重复/过时信息 → 输出 archive
12
- 3. 识别内容矛盾的条目 → 输出 conflict(根据时间新旧、来源完整性、信息具体程度判断 winner/loser)
13
- 4. 发现单条记忆中的信息已过时、错误或遗漏 输出 update(直接修正内容)
9
+ 请执行记忆巩固(consolidation),输出一个决策 JSON 数组。
10
+
11
+ 【决策格式(必须严格遵守)】
12
+ 每个决策必须是对象,字段固定:
13
+ - "action":必填。取值只能是 "keep" / "merge" / "archive" / "update" / "conflict" 之一(字段名必须是 action,严禁写成 type)
14
+ - "ids":必填,数组,本决策涉及的记忆 id 列表
15
+ - "reason":可选,字符串,决策理由
16
+ - "importance":可选,整数 1-5
17
+ - merge 额外字段:"keepSource"(单个 id 字符串,必须是 ids 之一)+ 合并后的 "title"、"content"
18
+ - conflict 额外字段:"winner" 与 "loser",都是【单个 id 字符串,不是数组】
19
+ - update 额外字段:修正后的 "title" 和/或 "content";"ids" 只能包含一个 id
20
+
21
+ 【决策 JSON 示例】
22
+ [
23
+ { "action": "merge", "ids": ["m1", "m2"], "keepSource": "m1", "title": "合并标题", "content": "合并后的摘要内容", "importance": 4, "reason": "主题相近" },
24
+ { "action": "conflict", "winner": "m3", "loser": "m4", "reason": "内容矛盾,保留更新的信息" },
25
+ { "action": "update", "ids": ["m5"], "content": "修正后的内容", "reason": "信息过时" },
26
+ { "action": "archive", "ids": ["m6"], "reason": "重复或过时" }
27
+ ]
28
+
29
+ 【任务】
30
+ 1. 识别主题相近的条目 → merge(合并为更精炼的摘要,保留信息最完整的 id 作为 keepSource)
31
+ 2. 识别重复/过时信息 → archive
32
+ 3. 识别内容矛盾的条目 → conflict(按时间新旧、来源完整性、信息具体程度判断 winner/loser)
33
+ 4. 发现单条记忆中的信息过时、错误或遗漏 → update(直接修正内容)
14
34
  - update 的 ids 只能包含一个 id
15
35
  - 必须提供修正后的 title 和/或 content
16
36
  - 仅当内容确实需要修正时才使用,不要滥用
@@ -18,13 +38,15 @@ const CONSOLIDATION_PROMPT = `你是记忆库整理助手。下面是全部记
18
38
  - 24 小时内新建的记忆不可 update
19
39
  5. 无问题的条目无需输出(未提及的条目将自动保留 keep)
20
40
 
21
- 规则:
41
+ 【硬性规则】
42
+ - 字段名必须精确为 "action",严禁写成 "type";字段名统一用双引号
43
+ - conflict 的 winner/loser、merge 的 keepSource 都是【单个 id 字符串,绝不是数组】
44
+ - 每条记忆最多被 claim 一次:同一个 id 不能出现在多个决策中(同一 id 不能被 merge 和 conflict/archive 等重复占用)
22
45
  - 未在决策中提及的记忆将自动保留(keep),无需为每条记忆输出 keep
23
46
  - merge 的 keepSource 必须是 ids 之一
24
47
  - 仅合并同类型条目(type 相同)
25
48
  - 不要编造 ids;只使用提供的 id
26
49
  - 重要性 1-5,合并后取最高
27
- - update 只能改一条,且要有实际变化
28
50
  - 只输出 JSON 数组,不要其他文字`;
29
51
 
30
52
  function totalChars(memories) {
@@ -814,3 +814,36 @@ test("runDream sliding window keeps the newest N memories and excludes the oldes
814
814
  assert.equal(result.decisions.length, 3, "decisions cover exactly the window");
815
815
  store.close();
816
816
  });
817
+
818
+ // --- v0.4.4 fix: 决策 JSON schema 固化(kimi 等模型输出合规) -----------------
819
+
820
+ test("consolidation prompt pins the decision schema (action field, single-string winner/loser, single claim per id)", async () => {
821
+ const { store, service } = dreamSetup();
822
+ service.saveWithDedupe({ type: "project", title: "a", content: "x" });
823
+ let systemText = "";
824
+ const ctx = {
825
+ logger: { warn: () => {} },
826
+ llm: {
827
+ async *stream(options) {
828
+ systemText = options.messages.find((m) => m.role === "system")?.content?.[0]?.text ?? "";
829
+ yield { type: "text-delta", text: "[]" };
830
+ yield { type: "finish", reason: { kind: "ok" } };
831
+ }
832
+ }
833
+ };
834
+ const dream = createDreamScheduler({ thresholdCount: 1, thresholdChars: 0, delayMs: 0 });
835
+ await dream.runDream(ctx, service, { dreamProvider: "deepseek", dreamModel: "deepseek-chat" });
836
+ // 字段名必须写死为 action(kimi 曾输出 "type" 导致整单拒绝)
837
+ assert.match(systemText, /"action"/, "prompt names the action field");
838
+ assert.match(systemText, /严禁写成\s*type/, "prompt forbids the type field name");
839
+ // conflict winner/loser 是单个 id 字符串而非数组
840
+ assert.match(systemText, /"winner"/, "prompt names the winner field");
841
+ assert.match(systemText, /"loser"/, "prompt names the loser field");
842
+ assert.match(systemText, /单个 id 字符串/, "winner/loser must be a single id string");
843
+ assert.match(systemText, /不是数组|绝不是数组/, "winner/loser must not be an array");
844
+ // 同一 id 不可被多个决策重复 claim
845
+ assert.match(systemText, /最多被 claim 一次/, "each memory claimed at most once");
846
+ // 决策 JSON 示例块
847
+ assert.match(systemText, /决策 JSON 示例/, "prompt includes a canonical example block");
848
+ store.close();
849
+ });