@modusensus/dsh-mneme 0.6.9 → 0.6.10
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/LICENSE +21 -21
- package/README.md +468 -219
- package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +15 -15
- package/{dsh-mneme/src → lib}/api.js +783 -783
- package/{dsh-mneme/lib → lib}/client.js +1754 -1757
- package/{dsh-mneme/src → lib}/commands.js +64 -64
- package/{dsh-mneme/lib → lib}/config.js +298 -298
- package/{dsh-mneme/lib → lib}/dream/clustering.js +118 -118
- package/{dsh-mneme/lib → lib}/dream/decisions.js +488 -488
- package/{dsh-mneme/lib → lib}/dream/sleep.js +561 -561
- package/{dsh-mneme/src → lib}/dream/tag-extractor.js +156 -156
- package/{dsh-mneme/lib → lib}/dream.js +958 -958
- package/{dsh-mneme/src → lib}/embedding.js +154 -154
- package/{dsh-mneme/src → lib}/entities/extractor.js +242 -242
- package/{dsh-mneme/lib → lib}/hot-memory.js +53 -53
- package/{dsh-mneme/lib → lib}/index.js +361 -361
- package/{dsh-mneme/src → lib}/inject.js +208 -208
- package/{dsh-mneme/lib → lib}/local-embedder.js +282 -282
- package/{dsh-mneme/lib → lib}/mirror.js +170 -170
- package/{dsh-mneme/lib → lib}/parser/tag.js +59 -59
- package/{dsh-mneme/lib → lib}/parser/wiki-link.js +38 -38
- package/{dsh-mneme/src → lib}/quality-filter.js +123 -123
- package/{dsh-mneme/lib → lib}/reranker.js +218 -218
- package/{dsh-mneme/src → lib}/search/adaptive.js +22 -22
- package/{dsh-mneme/src → lib}/search/bm25.js +96 -96
- package/{dsh-mneme/src → lib}/search/tag-boost.js +61 -61
- package/{dsh-mneme/src → lib}/service.js +1726 -1726
- package/{dsh-mneme/lib → lib}/settings.js +172 -172
- package/{dsh-mneme/src → lib}/store.js +2238 -2238
- package/{dsh-mneme/src → lib}/summarize.js +236 -236
- package/{dsh-mneme/lib → lib}/tools.js +290 -290
- package/{dsh-mneme/lib → lib}/vector-index.js +116 -116
- package/package.json +40 -18
- package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +201 -201
- package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +133 -133
- package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +166 -166
- package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +218 -218
- package/{dsh-mneme/scripts → scripts}/stress-dsh.js +255 -255
- package/{dsh-mneme/scripts → scripts}/sync-lib.js +52 -52
- package/{dsh-mneme/lib → src}/api.js +783 -783
- package/{dsh-mneme/lib → src}/commands.js +64 -64
- package/{dsh-mneme/src → src}/config.js +298 -298
- package/{dsh-mneme/src → src}/dream/clustering.js +118 -118
- package/{dsh-mneme/src → src}/dream/decisions.js +488 -488
- package/{dsh-mneme/src → src}/dream/sleep.js +561 -561
- package/{dsh-mneme/lib → src}/dream/tag-extractor.js +156 -156
- package/{dsh-mneme/src → src}/dream.js +958 -958
- package/{dsh-mneme/lib → src}/embedding.js +154 -154
- package/{dsh-mneme/lib → src}/entities/extractor.js +242 -242
- package/{dsh-mneme/src → src}/hot-memory.js +53 -53
- package/{dsh-mneme/src → src}/index.js +361 -361
- package/{dsh-mneme/lib → src}/inject.js +208 -208
- package/{dsh-mneme/src → src}/local-embedder.js +282 -282
- package/{dsh-mneme/src → src}/mirror.js +170 -170
- package/{dsh-mneme/src → src}/parser/tag.js +59 -59
- package/{dsh-mneme/src → src}/parser/wiki-link.js +38 -38
- package/{dsh-mneme/lib → src}/quality-filter.js +123 -123
- package/{dsh-mneme/src → src}/reranker.js +218 -218
- package/{dsh-mneme/lib → src}/search/adaptive.js +22 -22
- package/{dsh-mneme/lib → src}/search/bm25.js +96 -96
- package/{dsh-mneme/lib → src}/search/tag-boost.js +61 -61
- package/{dsh-mneme/lib → src}/service.js +1726 -1726
- package/{dsh-mneme/src → src}/settings.js +172 -172
- package/{dsh-mneme/lib → src}/store.js +2238 -2238
- package/{dsh-mneme/lib → src}/summarize.js +236 -236
- package/{dsh-mneme/src → src}/tools.js +290 -290
- package/{dsh-mneme/src → src}/vector-index.js +116 -116
- package/{dsh-mneme/test → test}/api.test.js +594 -594
- package/{dsh-mneme/test → test}/audit.test.js +448 -448
- package/{dsh-mneme/test → test}/benchmark.test.js +35 -35
- package/{dsh-mneme/test → test}/boundary-v0625.test.js +82 -82
- package/{dsh-mneme/test → test}/client.test.js +368 -368
- package/{dsh-mneme/test → test}/clustering.test.js +100 -100
- package/{dsh-mneme/test → test}/commands.test.js +69 -69
- package/{dsh-mneme/test → test}/config.test.js +50 -50
- package/{dsh-mneme/test → test}/conflict-freeze.test.js +290 -290
- package/{dsh-mneme/test → test}/directory.test.js +134 -134
- package/{dsh-mneme/test → test}/dream.test.js +1060 -1060
- package/{dsh-mneme/test → test}/entities.test.js +522 -522
- package/{dsh-mneme/test → test}/epistemic.test.js +298 -298
- package/{dsh-mneme/test → test}/fnew-0112.test.js +311 -311
- package/{dsh-mneme/test → test}/fnew-03.test.js +422 -422
- package/{dsh-mneme/test → test}/graph-api.test.js +175 -175
- package/{dsh-mneme/test → test}/helpers/dream-mock.js +82 -82
- package/{dsh-mneme/test → test}/hot-memory.test.js +174 -174
- package/{dsh-mneme/test → test}/inject.test.js +103 -103
- package/{dsh-mneme/test → test}/llm-audit.test.js +279 -279
- package/{dsh-mneme/test → test}/local-embedder.test.js +227 -227
- package/{dsh-mneme/test → test}/mirror-dirty.test.js +424 -424
- package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +187 -187
- package/{dsh-mneme/test → test}/mirror-generation.test.js +499 -499
- package/{dsh-mneme/test → test}/mirror.test.js +249 -249
- package/{dsh-mneme/test → test}/normalize-decisions.test.js +120 -120
- package/{dsh-mneme/test → test}/peer-blockers.test.js +190 -190
- package/{dsh-mneme/test → test}/policy-epoch.test.js +259 -259
- package/{dsh-mneme/test → test}/provenance.test.js +103 -103
- package/{dsh-mneme/test → test}/quality-filter.test.js +118 -118
- package/{dsh-mneme/test → test}/reasoning-effort.test.js +199 -199
- package/{dsh-mneme/test → test}/recall-evals.test.js +235 -235
- package/{dsh-mneme/test → test}/recall-layer.test.js +315 -315
- package/{dsh-mneme/test → test}/receipt-chain.test.js +451 -451
- package/{dsh-mneme/test → test}/reflection.test.js +226 -226
- package/{dsh-mneme/test → test}/reranker.test.js +240 -240
- package/{dsh-mneme/test → test}/search-fusion.test.js +90 -90
- package/{dsh-mneme/test → test}/semantic.test.js +124 -124
- package/{dsh-mneme/test → test}/service-search.test.js +199 -199
- package/{dsh-mneme/test → test}/service.test.js +435 -435
- package/{dsh-mneme/test → test}/settings.test.js +118 -118
- package/{dsh-mneme/test → test}/sleep.test.js +365 -365
- package/{dsh-mneme/test → test}/store.test.js +436 -436
- package/{dsh-mneme/test → test}/stress.test.js +209 -209
- package/{dsh-mneme/test → test}/summarize.test.js +191 -191
- package/{dsh-mneme/test → test}/tag-boost.test.js +125 -125
- package/{dsh-mneme/test → test}/tag.test.js +312 -312
- package/{dsh-mneme/test → test}/tools.test.js +285 -285
- package/{dsh-mneme/test → test}/vector-index.test.js +221 -221
- package/{dsh-mneme/test → test}/wiki-link.test.js +332 -332
- package/.github/workflows/test.yml +0 -32
- package/.release-notes-v0.6.9.md +0 -13
- package/CHANGELOG.md +0 -89
- package/SECURITY.md +0 -544
- package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
- package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
- package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
- package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
- package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
- package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
- package/dsh-mneme/CHANGELOG.md +0 -248
- package/dsh-mneme/LICENSE +0 -21
- package/dsh-mneme/README.md +0 -465
- package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
- package/dsh-mneme/docs/ENTITIES.md +0 -245
- package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
- package/dsh-mneme/docs/MIGRATION.md +0 -127
- package/dsh-mneme/docs/SEMANTIC.md +0 -256
- package/dsh-mneme/docs/SLEEP.md +0 -163
- package/dsh-mneme/package-lock.json +0 -1936
- package/dsh-mneme/package.json +0 -80
- package//346/250/252/345/271/205.png +0 -0
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
// Regression for issue #9:
|
|
2
|
-
// - B: dreamMaxTokens cap widened (min 256, max 131072) so large memory
|
|
3
|
-
// libraries no longer starve the consolidation output.
|
|
4
|
-
// - A: dreamReasoningEffort / sleepReasoningEffort pass-through. Default
|
|
5
|
-
// 'none' must OMIT the reasoningEffort field entirely (the provider's own
|
|
6
|
-
// default applies); low/medium/high are forwarded verbatim on every dream /
|
|
7
|
-
// sleep LLM call. Asserted by capturing the options each llm.stream() sees.
|
|
8
|
-
import test from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { Config } from "../src/config.js";
|
|
11
|
-
import { createDreamScheduler } from "../src/dream.js";
|
|
12
|
-
import { runSleep } from "../src/dream/sleep.js";
|
|
13
|
-
import { createStore } from "../src/store.js";
|
|
14
|
-
import { createService } from "../src/service.js";
|
|
15
|
-
import { createVectorIndex } from "../src/vector-index.js";
|
|
16
|
-
|
|
17
|
-
const embedder = {
|
|
18
|
-
embedSingle: async () => [1, 0, 0],
|
|
19
|
-
embed: async () => [1, 0, 0],
|
|
20
|
-
schedule: () => {},
|
|
21
|
-
modelHash: "mock#1",
|
|
22
|
-
dimension: 3
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------- config schema
|
|
26
|
-
|
|
27
|
-
test("issue#9: dreamMaxTokens accepts the widened 131072 cap and defaults to 4096", () => {
|
|
28
|
-
assert.equal(Config({}).dreamMaxTokens, 8192, "default unchanged");
|
|
29
|
-
assert.equal(Config({ dreamMaxTokens: 131072 }).dreamMaxTokens, 131072, "new upper bound accepted");
|
|
30
|
-
assert.equal(Config({ dreamMaxTokens: 65536 }).dreamMaxTokens, 65536, "intermediate value accepted");
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("issue#9: reasoningEffort config defaults to none and rejects unknown values", () => {
|
|
34
|
-
const cfg = Config({});
|
|
35
|
-
assert.equal(cfg.dreamReasoningEffort, "none");
|
|
36
|
-
assert.equal(cfg.sleepReasoningEffort, "none");
|
|
37
|
-
assert.equal(Config({ dreamReasoningEffort: "high" }).dreamReasoningEffort, "high");
|
|
38
|
-
assert.equal(Config({ sleepReasoningEffort: "medium" }).sleepReasoningEffort, "medium");
|
|
39
|
-
// v0.5.3: 'off' explicitly disables thinking — required for thinking-type
|
|
40
|
-
// models (deepseek-v4-flash) whose reasoning otherwise drains the whole
|
|
41
|
-
// token budget and returns an empty consolidation body.
|
|
42
|
-
assert.equal(Config({ dreamReasoningEffort: "off" }).dreamReasoningEffort, "off");
|
|
43
|
-
assert.equal(Config({ sleepReasoningEffort: "off" }).sleepReasoningEffort, "off");
|
|
44
|
-
assert.throws(() => Config({ dreamReasoningEffort: "bogus" }), "invalid effort rejected");
|
|
45
|
-
assert.throws(() => Config({ sleepReasoningEffort: "ultra" }), "invalid effort rejected");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// ---------------------------------------------------------------- dream passthrough
|
|
49
|
-
|
|
50
|
-
/** dream ctx that records every llm.stream() call's options for inspection. */
|
|
51
|
-
function dreamCtx({ onConsolidation, summaryText = "记忆库总览:用户偏好中文。", captured = [] } = {}) {
|
|
52
|
-
return {
|
|
53
|
-
logger: { warn: () => {} },
|
|
54
|
-
agentDefaultModel: { currentSelection: () => ({ provider: "mock", model: "mock-model" }) },
|
|
55
|
-
llm: {
|
|
56
|
-
async *stream(options) {
|
|
57
|
-
captured.push(options);
|
|
58
|
-
const userText = options.messages.find((m) => m.role === "user")?.content?.[0]?.text ?? "";
|
|
59
|
-
if (userText.startsWith("id=")) {
|
|
60
|
-
yield { type: "text-delta", index: 0, text: onConsolidation ? onConsolidation(userText) : "[]" };
|
|
61
|
-
} else {
|
|
62
|
-
yield { type: "text-delta", index: 0, text: summaryText };
|
|
63
|
-
}
|
|
64
|
-
yield { type: "finish", reason: { kind: "stop" } };
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
test("issue#9: dream omits reasoningEffort under default 'none' and still consolidates (applied>0)", async () => {
|
|
71
|
-
const store = createStore(":memory:");
|
|
72
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
73
|
-
const dream = createDreamScheduler({ onRun: () => Promise.resolve({ ok: true, skipped: true }) });
|
|
74
|
-
const { memory: a } = service.saveWithDedupe({ type: "project", title: "插件", content: "旧", importance: 3 });
|
|
75
|
-
const { memory: b } = service.saveWithDedupe({ type: "project", title: "插件2", content: "新细节", importance: 4 });
|
|
76
|
-
const captured = [];
|
|
77
|
-
const ctx = dreamCtx({
|
|
78
|
-
captured,
|
|
79
|
-
onConsolidation: () => JSON.stringify([
|
|
80
|
-
{ action: "merge", ids: [a.id, b.id], keepSource: b.id, title: "插件总览", content: "合并内容", importance: 4 }
|
|
81
|
-
])
|
|
82
|
-
});
|
|
83
|
-
const result = await dream.runDream(ctx, service, {});
|
|
84
|
-
assert.equal(result.ok, true);
|
|
85
|
-
assert.ok(result.applied > 0, "end-to-end dream run still lands changes");
|
|
86
|
-
assert.equal(captured.length, 2, "consolidation + summary both hit the LLM");
|
|
87
|
-
for (const options of captured) {
|
|
88
|
-
assert.equal("reasoningEffort" in options, false, `default 'none' must not forward reasoningEffort (${options.purpose})`);
|
|
89
|
-
}
|
|
90
|
-
store.close();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("issue#9: dream forwards dreamReasoningEffort on both LLM calls", async () => {
|
|
94
|
-
const store = createStore(":memory:");
|
|
95
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
96
|
-
const dream = createDreamScheduler({ onRun: () => Promise.resolve({ ok: true, skipped: true }) });
|
|
97
|
-
const { memory: a } = service.saveWithDedupe({ type: "project", title: "插件", content: "旧", importance: 3 });
|
|
98
|
-
const { memory: b } = service.saveWithDedupe({ type: "project", title: "插件2", content: "新细节", importance: 4 });
|
|
99
|
-
const captured = [];
|
|
100
|
-
const ctx = dreamCtx({
|
|
101
|
-
captured,
|
|
102
|
-
onConsolidation: () => JSON.stringify([
|
|
103
|
-
{ action: "merge", ids: [a.id, b.id], keepSource: b.id, title: "插件总览", content: "合并内容", importance: 4 }
|
|
104
|
-
])
|
|
105
|
-
});
|
|
106
|
-
const result = await dream.runDream(ctx, service, { dreamReasoningEffort: "high" });
|
|
107
|
-
assert.equal(result.ok, true);
|
|
108
|
-
assert.equal(captured.length, 2);
|
|
109
|
-
for (const options of captured) {
|
|
110
|
-
assert.equal(options.reasoningEffort, "high", `reasoningEffort forwarded on ${options.purpose}`);
|
|
111
|
-
}
|
|
112
|
-
store.close();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("issue#9: dream forwards dreamReasoningEffort:off (thinking disabled) on both LLM calls", async () => {
|
|
116
|
-
const store = createStore(":memory:");
|
|
117
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
118
|
-
const dream = createDreamScheduler({ onRun: () => Promise.resolve({ ok: true, skipped: true }) });
|
|
119
|
-
const { memory: a } = service.saveWithDedupe({ type: "project", title: "插件", content: "旧", importance: 3 });
|
|
120
|
-
const { memory: b } = service.saveWithDedupe({ type: "project", title: "插件2", content: "新细节", importance: 4 });
|
|
121
|
-
const captured = [];
|
|
122
|
-
const ctx = dreamCtx({
|
|
123
|
-
captured,
|
|
124
|
-
onConsolidation: () => JSON.stringify([
|
|
125
|
-
{ action: "merge", ids: [a.id, b.id], keepSource: b.id, title: "插件总览", content: "合并内容", importance: 4 }
|
|
126
|
-
])
|
|
127
|
-
});
|
|
128
|
-
const result = await dream.runDream(ctx, service, { dreamReasoningEffort: "off" });
|
|
129
|
-
assert.equal(result.ok, true);
|
|
130
|
-
assert.equal(captured.length, 2);
|
|
131
|
-
for (const options of captured) {
|
|
132
|
-
assert.equal(options.reasoningEffort, "off", `reasoningEffort:"off" forwarded on ${options.purpose}`);
|
|
133
|
-
}
|
|
134
|
-
store.close();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ---------------------------------------------------------------- sleep passthrough
|
|
138
|
-
|
|
139
|
-
function sleepSetup() {
|
|
140
|
-
const store = createStore(":memory:");
|
|
141
|
-
const service = createService({ store, mirror: null, config: {} });
|
|
142
|
-
const vectorIndex = createVectorIndex({ store });
|
|
143
|
-
service.setEmbedder(embedder);
|
|
144
|
-
service.setVectorIndex(vectorIndex);
|
|
145
|
-
return { store, service, vectorIndex };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function baseConfig(overrides = {}) {
|
|
149
|
-
return {
|
|
150
|
-
sleepModeEnabled: true,
|
|
151
|
-
sleepIdleMinutes: 5,
|
|
152
|
-
sleepMinIntervalHours: 8,
|
|
153
|
-
sleepConflictStrictness: "normal",
|
|
154
|
-
sleepArchiveDays: 30,
|
|
155
|
-
sleepCompressDays: 90,
|
|
156
|
-
sleepPatternMinMemories: 10,
|
|
157
|
-
sleepMaxPatternPerRun: 3,
|
|
158
|
-
...overrides
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** sleep ctx that records every llm.stream() call's options. */
|
|
163
|
-
function sleepCtx(onConsolidation, selection = { provider: "mock", model: "sleep-model" }, captured = []) {
|
|
164
|
-
return {
|
|
165
|
-
logger: { warn: () => {}, info: () => {} },
|
|
166
|
-
agentDefaultModel: { currentSelection: () => selection },
|
|
167
|
-
llm: {
|
|
168
|
-
async *stream(options) {
|
|
169
|
-
captured.push(options);
|
|
170
|
-
const userText = options.messages.find((m) => m.role === "user")?.content?.[0]?.text ?? "";
|
|
171
|
-
yield { type: "text-delta", index: 0, text: onConsolidation ? onConsolidation(userText) : "[]" };
|
|
172
|
-
yield { type: "finish", reason: { kind: "stop" } };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
test("issue#9: sleep forwards sleepReasoningEffort on its LLM passes", async () => {
|
|
179
|
-
const { store, service, vectorIndex } = sleepSetup();
|
|
180
|
-
const a = service.saveWithDedupe({ type: "project", title: "主题X", content: "内容A 关于主题X", importance: 3 }).memory;
|
|
181
|
-
const b = service.saveWithDedupe({ type: "project", title: "主题X副本", content: "内容B 关于主题X", importance: 3 }).memory;
|
|
182
|
-
vectorIndex.saveEmbedding(a.id, [1, 0, 0]);
|
|
183
|
-
vectorIndex.saveEmbedding(b.id, [1, 0, 0]);
|
|
184
|
-
const captured = [];
|
|
185
|
-
const ctx = sleepCtx(
|
|
186
|
-
(userText) => userText.startsWith("候选冲突")
|
|
187
|
-
? JSON.stringify([{ action: "conflict", winner: a.id, loser: b.id, reason: "重复覆盖" }])
|
|
188
|
-
: "[]",
|
|
189
|
-
{ provider: "mock", model: "sleep-model" },
|
|
190
|
-
captured
|
|
191
|
-
);
|
|
192
|
-
const result = await runSleep(ctx, service, baseConfig({ sleepReasoningEffort: "medium" }), ctx.logger, { embedder, vectorIndex }, null);
|
|
193
|
-
assert.equal(result.status, "ok");
|
|
194
|
-
assert.ok(captured.length >= 2, "conflict + pattern passes both hit the LLM");
|
|
195
|
-
for (const options of captured) {
|
|
196
|
-
assert.equal(options.reasoningEffort, "medium", `reasoningEffort forwarded on ${options.purpose}`);
|
|
197
|
-
}
|
|
198
|
-
store.close();
|
|
199
|
-
});
|
|
1
|
+
// Regression for issue #9:
|
|
2
|
+
// - B: dreamMaxTokens cap widened (min 256, max 131072) so large memory
|
|
3
|
+
// libraries no longer starve the consolidation output.
|
|
4
|
+
// - A: dreamReasoningEffort / sleepReasoningEffort pass-through. Default
|
|
5
|
+
// 'none' must OMIT the reasoningEffort field entirely (the provider's own
|
|
6
|
+
// default applies); low/medium/high are forwarded verbatim on every dream /
|
|
7
|
+
// sleep LLM call. Asserted by capturing the options each llm.stream() sees.
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { Config } from "../src/config.js";
|
|
11
|
+
import { createDreamScheduler } from "../src/dream.js";
|
|
12
|
+
import { runSleep } from "../src/dream/sleep.js";
|
|
13
|
+
import { createStore } from "../src/store.js";
|
|
14
|
+
import { createService } from "../src/service.js";
|
|
15
|
+
import { createVectorIndex } from "../src/vector-index.js";
|
|
16
|
+
|
|
17
|
+
const embedder = {
|
|
18
|
+
embedSingle: async () => [1, 0, 0],
|
|
19
|
+
embed: async () => [1, 0, 0],
|
|
20
|
+
schedule: () => {},
|
|
21
|
+
modelHash: "mock#1",
|
|
22
|
+
dimension: 3
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------- config schema
|
|
26
|
+
|
|
27
|
+
test("issue#9: dreamMaxTokens accepts the widened 131072 cap and defaults to 4096", () => {
|
|
28
|
+
assert.equal(Config({}).dreamMaxTokens, 8192, "default unchanged");
|
|
29
|
+
assert.equal(Config({ dreamMaxTokens: 131072 }).dreamMaxTokens, 131072, "new upper bound accepted");
|
|
30
|
+
assert.equal(Config({ dreamMaxTokens: 65536 }).dreamMaxTokens, 65536, "intermediate value accepted");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("issue#9: reasoningEffort config defaults to none and rejects unknown values", () => {
|
|
34
|
+
const cfg = Config({});
|
|
35
|
+
assert.equal(cfg.dreamReasoningEffort, "none");
|
|
36
|
+
assert.equal(cfg.sleepReasoningEffort, "none");
|
|
37
|
+
assert.equal(Config({ dreamReasoningEffort: "high" }).dreamReasoningEffort, "high");
|
|
38
|
+
assert.equal(Config({ sleepReasoningEffort: "medium" }).sleepReasoningEffort, "medium");
|
|
39
|
+
// v0.5.3: 'off' explicitly disables thinking — required for thinking-type
|
|
40
|
+
// models (deepseek-v4-flash) whose reasoning otherwise drains the whole
|
|
41
|
+
// token budget and returns an empty consolidation body.
|
|
42
|
+
assert.equal(Config({ dreamReasoningEffort: "off" }).dreamReasoningEffort, "off");
|
|
43
|
+
assert.equal(Config({ sleepReasoningEffort: "off" }).sleepReasoningEffort, "off");
|
|
44
|
+
assert.throws(() => Config({ dreamReasoningEffort: "bogus" }), "invalid effort rejected");
|
|
45
|
+
assert.throws(() => Config({ sleepReasoningEffort: "ultra" }), "invalid effort rejected");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------- dream passthrough
|
|
49
|
+
|
|
50
|
+
/** dream ctx that records every llm.stream() call's options for inspection. */
|
|
51
|
+
function dreamCtx({ onConsolidation, summaryText = "记忆库总览:用户偏好中文。", captured = [] } = {}) {
|
|
52
|
+
return {
|
|
53
|
+
logger: { warn: () => {} },
|
|
54
|
+
agentDefaultModel: { currentSelection: () => ({ provider: "mock", model: "mock-model" }) },
|
|
55
|
+
llm: {
|
|
56
|
+
async *stream(options) {
|
|
57
|
+
captured.push(options);
|
|
58
|
+
const userText = options.messages.find((m) => m.role === "user")?.content?.[0]?.text ?? "";
|
|
59
|
+
if (userText.startsWith("id=")) {
|
|
60
|
+
yield { type: "text-delta", index: 0, text: onConsolidation ? onConsolidation(userText) : "[]" };
|
|
61
|
+
} else {
|
|
62
|
+
yield { type: "text-delta", index: 0, text: summaryText };
|
|
63
|
+
}
|
|
64
|
+
yield { type: "finish", reason: { kind: "stop" } };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test("issue#9: dream omits reasoningEffort under default 'none' and still consolidates (applied>0)", async () => {
|
|
71
|
+
const store = createStore(":memory:");
|
|
72
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
73
|
+
const dream = createDreamScheduler({ onRun: () => Promise.resolve({ ok: true, skipped: true }) });
|
|
74
|
+
const { memory: a } = service.saveWithDedupe({ type: "project", title: "插件", content: "旧", importance: 3 });
|
|
75
|
+
const { memory: b } = service.saveWithDedupe({ type: "project", title: "插件2", content: "新细节", importance: 4 });
|
|
76
|
+
const captured = [];
|
|
77
|
+
const ctx = dreamCtx({
|
|
78
|
+
captured,
|
|
79
|
+
onConsolidation: () => JSON.stringify([
|
|
80
|
+
{ action: "merge", ids: [a.id, b.id], keepSource: b.id, title: "插件总览", content: "合并内容", importance: 4 }
|
|
81
|
+
])
|
|
82
|
+
});
|
|
83
|
+
const result = await dream.runDream(ctx, service, {});
|
|
84
|
+
assert.equal(result.ok, true);
|
|
85
|
+
assert.ok(result.applied > 0, "end-to-end dream run still lands changes");
|
|
86
|
+
assert.equal(captured.length, 2, "consolidation + summary both hit the LLM");
|
|
87
|
+
for (const options of captured) {
|
|
88
|
+
assert.equal("reasoningEffort" in options, false, `default 'none' must not forward reasoningEffort (${options.purpose})`);
|
|
89
|
+
}
|
|
90
|
+
store.close();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("issue#9: dream forwards dreamReasoningEffort on both LLM calls", async () => {
|
|
94
|
+
const store = createStore(":memory:");
|
|
95
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
96
|
+
const dream = createDreamScheduler({ onRun: () => Promise.resolve({ ok: true, skipped: true }) });
|
|
97
|
+
const { memory: a } = service.saveWithDedupe({ type: "project", title: "插件", content: "旧", importance: 3 });
|
|
98
|
+
const { memory: b } = service.saveWithDedupe({ type: "project", title: "插件2", content: "新细节", importance: 4 });
|
|
99
|
+
const captured = [];
|
|
100
|
+
const ctx = dreamCtx({
|
|
101
|
+
captured,
|
|
102
|
+
onConsolidation: () => JSON.stringify([
|
|
103
|
+
{ action: "merge", ids: [a.id, b.id], keepSource: b.id, title: "插件总览", content: "合并内容", importance: 4 }
|
|
104
|
+
])
|
|
105
|
+
});
|
|
106
|
+
const result = await dream.runDream(ctx, service, { dreamReasoningEffort: "high" });
|
|
107
|
+
assert.equal(result.ok, true);
|
|
108
|
+
assert.equal(captured.length, 2);
|
|
109
|
+
for (const options of captured) {
|
|
110
|
+
assert.equal(options.reasoningEffort, "high", `reasoningEffort forwarded on ${options.purpose}`);
|
|
111
|
+
}
|
|
112
|
+
store.close();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("issue#9: dream forwards dreamReasoningEffort:off (thinking disabled) on both LLM calls", async () => {
|
|
116
|
+
const store = createStore(":memory:");
|
|
117
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
118
|
+
const dream = createDreamScheduler({ onRun: () => Promise.resolve({ ok: true, skipped: true }) });
|
|
119
|
+
const { memory: a } = service.saveWithDedupe({ type: "project", title: "插件", content: "旧", importance: 3 });
|
|
120
|
+
const { memory: b } = service.saveWithDedupe({ type: "project", title: "插件2", content: "新细节", importance: 4 });
|
|
121
|
+
const captured = [];
|
|
122
|
+
const ctx = dreamCtx({
|
|
123
|
+
captured,
|
|
124
|
+
onConsolidation: () => JSON.stringify([
|
|
125
|
+
{ action: "merge", ids: [a.id, b.id], keepSource: b.id, title: "插件总览", content: "合并内容", importance: 4 }
|
|
126
|
+
])
|
|
127
|
+
});
|
|
128
|
+
const result = await dream.runDream(ctx, service, { dreamReasoningEffort: "off" });
|
|
129
|
+
assert.equal(result.ok, true);
|
|
130
|
+
assert.equal(captured.length, 2);
|
|
131
|
+
for (const options of captured) {
|
|
132
|
+
assert.equal(options.reasoningEffort, "off", `reasoningEffort:"off" forwarded on ${options.purpose}`);
|
|
133
|
+
}
|
|
134
|
+
store.close();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------- sleep passthrough
|
|
138
|
+
|
|
139
|
+
function sleepSetup() {
|
|
140
|
+
const store = createStore(":memory:");
|
|
141
|
+
const service = createService({ store, mirror: null, config: {} });
|
|
142
|
+
const vectorIndex = createVectorIndex({ store });
|
|
143
|
+
service.setEmbedder(embedder);
|
|
144
|
+
service.setVectorIndex(vectorIndex);
|
|
145
|
+
return { store, service, vectorIndex };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function baseConfig(overrides = {}) {
|
|
149
|
+
return {
|
|
150
|
+
sleepModeEnabled: true,
|
|
151
|
+
sleepIdleMinutes: 5,
|
|
152
|
+
sleepMinIntervalHours: 8,
|
|
153
|
+
sleepConflictStrictness: "normal",
|
|
154
|
+
sleepArchiveDays: 30,
|
|
155
|
+
sleepCompressDays: 90,
|
|
156
|
+
sleepPatternMinMemories: 10,
|
|
157
|
+
sleepMaxPatternPerRun: 3,
|
|
158
|
+
...overrides
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** sleep ctx that records every llm.stream() call's options. */
|
|
163
|
+
function sleepCtx(onConsolidation, selection = { provider: "mock", model: "sleep-model" }, captured = []) {
|
|
164
|
+
return {
|
|
165
|
+
logger: { warn: () => {}, info: () => {} },
|
|
166
|
+
agentDefaultModel: { currentSelection: () => selection },
|
|
167
|
+
llm: {
|
|
168
|
+
async *stream(options) {
|
|
169
|
+
captured.push(options);
|
|
170
|
+
const userText = options.messages.find((m) => m.role === "user")?.content?.[0]?.text ?? "";
|
|
171
|
+
yield { type: "text-delta", index: 0, text: onConsolidation ? onConsolidation(userText) : "[]" };
|
|
172
|
+
yield { type: "finish", reason: { kind: "stop" } };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
test("issue#9: sleep forwards sleepReasoningEffort on its LLM passes", async () => {
|
|
179
|
+
const { store, service, vectorIndex } = sleepSetup();
|
|
180
|
+
const a = service.saveWithDedupe({ type: "project", title: "主题X", content: "内容A 关于主题X", importance: 3 }).memory;
|
|
181
|
+
const b = service.saveWithDedupe({ type: "project", title: "主题X副本", content: "内容B 关于主题X", importance: 3 }).memory;
|
|
182
|
+
vectorIndex.saveEmbedding(a.id, [1, 0, 0]);
|
|
183
|
+
vectorIndex.saveEmbedding(b.id, [1, 0, 0]);
|
|
184
|
+
const captured = [];
|
|
185
|
+
const ctx = sleepCtx(
|
|
186
|
+
(userText) => userText.startsWith("候选冲突")
|
|
187
|
+
? JSON.stringify([{ action: "conflict", winner: a.id, loser: b.id, reason: "重复覆盖" }])
|
|
188
|
+
: "[]",
|
|
189
|
+
{ provider: "mock", model: "sleep-model" },
|
|
190
|
+
captured
|
|
191
|
+
);
|
|
192
|
+
const result = await runSleep(ctx, service, baseConfig({ sleepReasoningEffort: "medium" }), ctx.logger, { embedder, vectorIndex }, null);
|
|
193
|
+
assert.equal(result.status, "ok");
|
|
194
|
+
assert.ok(captured.length >= 2, "conflict + pattern passes both hit the LLM");
|
|
195
|
+
for (const options of captured) {
|
|
196
|
+
assert.equal(options.reasoningEffort, "medium", `reasoningEffort forwarded on ${options.purpose}`);
|
|
197
|
+
}
|
|
198
|
+
store.close();
|
|
199
|
+
});
|