@modusensus/dsh-mneme 0.8.16 → 0.9.0

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 (73) hide show
  1. package/README.md +16 -3
  2. package/assets/icon.svg +2 -2
  3. package/docs/CONFIGURATION.md +2 -1
  4. package/docs/CONTINUITY.md +18 -3
  5. package/docs/WORKFLOWS.md +299 -0
  6. package/lib/api.js +124 -8
  7. package/lib/candidates.js +454 -0
  8. package/lib/capabilities.js +819 -0
  9. package/lib/client.js +1703 -182
  10. package/lib/config.js +31 -5
  11. package/lib/continuity.js +35 -6
  12. package/lib/dream/decisions.js +17 -1
  13. package/lib/dream/sleep.js +10 -2
  14. package/lib/dream.js +5 -1
  15. package/lib/entities/extractor.js +4 -0
  16. package/lib/index.js +49 -12
  17. package/lib/inject.js +6 -1
  18. package/lib/pipeline.js +534 -0
  19. package/lib/scope.js +16 -4
  20. package/lib/serve.js +1 -1
  21. package/lib/service.js +59 -18
  22. package/lib/session-usage.js +350 -0
  23. package/lib/settings.js +7 -1
  24. package/lib/store.js +55 -3
  25. package/lib/summarize.js +31 -0
  26. package/lib/tools.js +11 -2
  27. package/package.json +1 -1
  28. package/scripts/publish-gate.mjs +197 -0
  29. package/src/api.js +124 -8
  30. package/src/candidates.js +454 -0
  31. package/src/capabilities.js +819 -0
  32. package/src/config.js +31 -5
  33. package/src/continuity.js +35 -6
  34. package/src/dream/decisions.js +17 -1
  35. package/src/dream/sleep.js +10 -2
  36. package/src/dream.js +5 -1
  37. package/src/entities/extractor.js +4 -0
  38. package/src/index.js +49 -12
  39. package/src/inject.js +6 -1
  40. package/src/pipeline.js +534 -0
  41. package/src/scope.js +16 -4
  42. package/src/serve.js +1 -1
  43. package/src/service.js +59 -18
  44. package/src/session-usage.js +350 -0
  45. package/src/settings.js +7 -1
  46. package/src/store.js +55 -3
  47. package/src/summarize.js +31 -0
  48. package/src/tools.js +11 -2
  49. package/test/api.test.js +147 -7
  50. package/test/candidates.test.js +345 -0
  51. package/test/capabilities.test.js +577 -0
  52. package/test/client.test.js +361 -2
  53. package/test/continuity.test.js +84 -0
  54. package/test/embed-ready-poll.test.js +94 -0
  55. package/test/entities.test.js +13 -2
  56. package/test/failure-query.test.js +147 -0
  57. package/test/failure-retention.test.js +105 -0
  58. package/test/graph-api.test.js +36 -2
  59. package/test/inject-recall.test.js +32 -4
  60. package/test/llm-audit.test.js +92 -1
  61. package/test/mint-source.test.js +202 -0
  62. package/test/pipeline.test.js +505 -0
  63. package/test/placement.test.js +1 -1
  64. package/test/publish-gate.test.js +272 -0
  65. package/test/recall-retention.test.js +128 -0
  66. package/test/release-workflow.test.js +77 -0
  67. package/test/scope-distill-stamp.test.js +135 -0
  68. package/test/serve.test.js +1 -1
  69. package/test/session-usage.test.js +308 -0
  70. package/test/store-write-path-columns.test.js +191 -0
  71. package/test/summarize.test.js +176 -0
  72. package/test/supersede-state-tracking.test.js +4 -4
  73. package/test/tools.test.js +16 -1
@@ -0,0 +1,202 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { createStore } from "../src/store.js";
4
+ import { createService } from "../src/service.js";
5
+ import { applyDecisions, validateDecisions } from "../src/dream/decisions.js";
6
+ import { runSleep } from "../src/dream/sleep.js";
7
+ import { createDreamScheduler } from "../src/dream.js";
8
+
9
+ // 机器 mint 的记忆必须带真 source。
10
+ //
11
+ // 背景(现场实测,2026-10-14):pattern 表的 144 行 source 全是 "manual",
12
+ // 而按 dream_runs 的 patterns.applied 逐轮求和恰好也是 144——它们全是 sleep 的
13
+ // phasePatterns mint 的,那个 "manual" 只是 saveWithDedupe 没拿到 source 时的
14
+ // 兜底值。于是「这类条目从哪来」在数据面上不可读,靠它做的判断(service.js 里
15
+ // 那条「pattern 只由手工写入」)也就跟着错了。
16
+ //
17
+ // 这组用例锁两件事:① 三条 mint 管道各自声明自己的来源;② 来源只能由调用方给,
18
+ // 模型写在决策对象上的 source 一律不算数——validateDecisions 不剥离未知字段,
19
+ // 若 applyCreate 信了 d.source,模型就能给自己的输出盖「人写的」戳。
20
+
21
+ function setup() {
22
+ const store = createStore(":memory:");
23
+ const service = createService({ store, mirror: null, config: {} });
24
+ return { store, service };
25
+ }
26
+
27
+ function mint(service, extra = {}) {
28
+ return service
29
+ .saveWithDedupe({ type: "project", title: `铺垫${Math.random()}`, content: "给扫描窗垫一条普通记忆", importance: 3 })
30
+ .memory;
31
+ }
32
+
33
+ // ---------------------------------------------------------------- applyDecisions
34
+
35
+ test("mint source: applyDecisions stamps the caller's mintSource on create", () => {
36
+ const { store, service } = setup();
37
+ const { applied } = applyDecisions(
38
+ [{ action: "create", type: "pattern", title: "真模式", content: "从重复里抽出来的规律", importance: 3 }],
39
+ service,
40
+ null,
41
+ null,
42
+ { mintSource: "sleep" }
43
+ );
44
+ assert.equal(applied, 1);
45
+ const row = service.all().find((m) => m.type === "pattern");
46
+ assert.equal(row.source, "sleep", "minted row carries the pipeline provenance");
47
+ store.close();
48
+ });
49
+
50
+ test("mint source: dream pipeline stamps 'dream'", () => {
51
+ const { store, service } = setup();
52
+ applyDecisions(
53
+ [{ action: "create", type: "pattern", title: "真模式", content: "规律", importance: 3 }],
54
+ service,
55
+ null,
56
+ null,
57
+ { mintSource: "dream" }
58
+ );
59
+ const row = service.all().find((m) => m.type === "pattern");
60
+ assert.equal(row.source, "dream");
61
+ store.close();
62
+ });
63
+
64
+ // 对抗条:来源取自调用方,不取自决策对象。模型自报的 source 必须被忽略——
65
+ // 否则「机器写的不冒充人写的」这条防线就形同虚设。
66
+ test("mint source: a source on the decision object is ignored, pipeline wins", () => {
67
+ const { store, service } = setup();
68
+ const decisions = [
69
+ { action: "create", type: "pattern", title: "真模式", content: "规律", importance: 3, source: "manual" }
70
+ ];
71
+ // 先证明威胁真实存在:这类决策能整个通过校验走到 applyCreate(校验不剥离未知
72
+ // 字段)。若哪天真加了剥离,这条会红——那时下面的对抗断言也就不再承重了。
73
+ const { ok } = validateDecisions(decisions, new Map(), { maxCreatePerRun: 3 });
74
+ assert.equal(ok, true, "a create carrying its own source still validates — that is the threat");
75
+
76
+ applyDecisions(decisions, service, null, null, { mintSource: "sleep" });
77
+ const row = service.all().find((m) => m.type === "pattern");
78
+ assert.equal(row.source, "sleep", "the model-authored source must not win");
79
+ store.close();
80
+ });
81
+
82
+ // 反向:模型自报 source 而没有管道来源时,也不许它自己说了算——回落到兜底,
83
+ // 而不是让模型指哪打哪。
84
+ test("mint source: without a pipeline source the model-authored one still does not win", () => {
85
+ const { store, service } = setup();
86
+ applyDecisions(
87
+ [{ action: "create", type: "pattern", title: "真模式", content: "规律", importance: 3, source: "human" }],
88
+ service
89
+ );
90
+ const row = service.all().find((m) => m.type === "pattern");
91
+ assert.equal(row.source, "manual", "falls back to the existing default, not the model's claim");
92
+ store.close();
93
+ });
94
+
95
+ // 不传 mintSource 时行为与改前逐字一致(不放大行为面)。
96
+ test("mint source: absent mintSource keeps the previous fallback", () => {
97
+ const { store, service } = setup();
98
+ applyDecisions([{ action: "create", type: "pattern", title: "真模式", content: "规律", importance: 3 }], service);
99
+ const row = service.all().find((m) => m.type === "pattern");
100
+ assert.equal(row.source, "manual");
101
+ store.close();
102
+ });
103
+
104
+ // 空白串按「没给」处理,不能写出 source="" 这种假装有来源的空值。
105
+ test("mint source: blank mintSource is treated as absent", () => {
106
+ const { store, service } = setup();
107
+ applyDecisions(
108
+ [{ action: "create", type: "pattern", title: "真模式", content: "规律", importance: 3 }],
109
+ service,
110
+ null,
111
+ null,
112
+ { mintSource: " " }
113
+ );
114
+ const row = service.all().find((m) => m.type === "pattern");
115
+ assert.equal(row.source, "manual");
116
+ store.close();
117
+ });
118
+
119
+ // ------------------------------------------------------------------ end to end
120
+
121
+ // sleep 的 pattern 相位(唯一的真实 mint 路径)端到端:断言落库行的 source。
122
+ // 不能靠等下一次做梦验收(要调度 + LLM 路由),所以在这里驱动相位。
123
+ function sleepCtx(decisionsJson) {
124
+ return {
125
+ logger: { warn: () => {}, info: () => {} },
126
+ agentDefaultModel: { currentSelection: () => ({ provider: "mock", model: "sleep-model" }) },
127
+ llm: {
128
+ async *stream(options) {
129
+ const userText = options.messages.find((m) => m.role === "user")?.content?.[0]?.text ?? "";
130
+ yield { type: "text-delta", index: 0, text: userText.startsWith("id=") ? decisionsJson : "[]" };
131
+ yield { type: "finish", reason: { kind: "stop" } };
132
+ }
133
+ }
134
+ };
135
+ }
136
+
137
+ test("mint source: sleep pattern phase mints with source=sleep", async () => {
138
+ const { store, service } = setup();
139
+ mint(service);
140
+ mint(service);
141
+ const ctx = sleepCtx(
142
+ JSON.stringify([{ action: "create", type: "pattern", title: "真模式", content: "从 a 和 b 提取", importance: 3 }])
143
+ );
144
+ const config = {
145
+ sleepModeEnabled: true,
146
+ sleepPatternMinMemories: 10,
147
+ sleepMaxPatternPerRun: 3
148
+ };
149
+ const result = await runSleep(ctx, service, config, ctx.logger, null, null);
150
+ assert.equal(result.phases.patterns.status, "ok", "pattern phase minted");
151
+
152
+ const row = service.all().find((m) => m.type === "pattern");
153
+ assert.ok(row, "pattern minted");
154
+ assert.equal(row.source, "sleep", "the sleep phase declares its own provenance");
155
+ store.close();
156
+ });
157
+
158
+ test("mint source: dream main chain mints with source=dream", async () => {
159
+ const { store, service } = setup();
160
+ const seed = service.saveWithDedupe({ type: "project", title: "旧条目", content: "第一段内容", importance: 3 }).memory;
161
+ let call = 0;
162
+ const ctx = {
163
+ logger: { warn: () => {}, info: () => {} },
164
+ agentDefaultModel: { currentSelection: () => ({ provider: "mock", model: "mock-model" }) },
165
+ llm: {
166
+ async *stream(options) {
167
+ call++;
168
+ const userText = options.messages.find((m) => m.role === "user")?.content?.[0]?.text ?? "";
169
+ const text = call === 1
170
+ ? JSON.stringify([
171
+ { action: "keep", ids: [seed.id] },
172
+ { action: "create", type: "pattern", title: "主链路模式", content: "巩固时发现的规律", importance: 3 }
173
+ ])
174
+ : "记忆库总览摘要文本";
175
+ void userText;
176
+ yield { type: "text-delta", text };
177
+ yield { type: "finish", reason: { kind: "ok" } };
178
+ }
179
+ }
180
+ };
181
+ const dream = createDreamScheduler({ thresholdCount: 1, thresholdChars: 0, delayMs: 0 });
182
+ const result = await dream.runDream(ctx, service, { dreamProvider: "mock", dreamModel: "mock-model" });
183
+ assert.equal(result.ok, true);
184
+
185
+ const row = service.all().find((m) => m.type === "pattern");
186
+ assert.ok(row, "the create decision minted a row");
187
+ assert.equal(row.source, "dream");
188
+ store.close();
189
+ });
190
+
191
+ // 验收口径本身:全表分组统计随之可读——同一次运行 mint 的条目能按来源数出来。
192
+ test("mint source: rows are countable by source after minting", () => {
193
+ const { store, service } = setup();
194
+ applyDecisions([{ action: "create", type: "pattern", title: "甲", content: "规律甲", importance: 3 }], service, null, null, { mintSource: "sleep" });
195
+ applyDecisions([{ action: "create", type: "pattern", title: "乙", content: "规律乙", importance: 3 }], service, null, null, { mintSource: "dream" });
196
+ const bySource = new Map();
197
+ for (const m of service.all()) bySource.set(m.source, (bySource.get(m.source) ?? 0) + 1);
198
+ assert.equal(bySource.get("sleep"), 1);
199
+ assert.equal(bySource.get("dream"), 1);
200
+ assert.equal(bySource.get("manual"), undefined, "nothing minted here fell back to the default");
201
+ store.close();
202
+ });