@modusensus/dsh-mneme 0.12.0 → 0.13.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 (154) hide show
  1. package/README.md +48 -19
  2. package/docs/CONFIGURATION.md +43 -4
  3. package/docs/DIRECTORIES.md +114 -0
  4. package/docs/DISTILL.md +64 -0
  5. package/docs/DSH-GO.md +97 -0
  6. package/docs/ENTITIES.md +5 -3
  7. package/docs/FORGET.md +77 -0
  8. package/docs/KB.md +110 -0
  9. package/docs/SLEEP.md +28 -3
  10. package/docs/STORAGE.md +23 -0
  11. package/lib/api-standalone.js +4 -1
  12. package/lib/api.js +581 -29
  13. package/lib/attachment-text.js +122 -0
  14. package/lib/choro-command.js +9 -2
  15. package/lib/client.js +3537 -1205
  16. package/lib/config.js +139 -1
  17. package/lib/continuity.js +7 -6
  18. package/lib/diary/auto.js +190 -0
  19. package/lib/diary/compose.js +440 -0
  20. package/lib/diary/dir.js +59 -0
  21. package/lib/diary/export.js +97 -0
  22. package/lib/diary/index.js +26 -0
  23. package/lib/diary/library.js +189 -0
  24. package/lib/diary/qq-inbox.js +142 -0
  25. package/lib/diary/rollover.js +61 -0
  26. package/lib/diary/session-provider.js +122 -0
  27. package/lib/dream/decisions.js +56 -5
  28. package/lib/dream/sleep.js +616 -15
  29. package/lib/dream.js +45 -13
  30. package/lib/dsh-go/host.js +162 -0
  31. package/lib/dsh-go/panel.js +2091 -0
  32. package/lib/dsh-go/text.js +342 -0
  33. package/lib/dsh-go/views/common.js +68 -0
  34. package/lib/dsh-go/views/entity.js +48 -0
  35. package/lib/dsh-go/views/memory.js +198 -0
  36. package/lib/dsh-go/views/root.js +30 -0
  37. package/lib/dsh-go/views/settings.js +398 -0
  38. package/lib/dsh-go/views/status.js +51 -0
  39. package/lib/embedding.js +42 -8
  40. package/lib/entities/extractor.js +36 -7
  41. package/lib/hot-memory.js +8 -9
  42. package/lib/index.js +92 -2
  43. package/lib/inject.js +189 -18
  44. package/lib/kb/index.js +309 -0
  45. package/lib/kb/routes.js +220 -0
  46. package/lib/kb/tools.js +148 -0
  47. package/lib/lang.js +87 -4
  48. package/lib/peak-hours.js +57 -2
  49. package/lib/semantic.js +347 -60
  50. package/lib/service.js +193 -66
  51. package/lib/settings.js +112 -14
  52. package/lib/store.js +347 -21
  53. package/lib/summarize.js +38 -11
  54. package/lib/tools.js +35 -4
  55. package/lib/user-settings.js +128 -32
  56. package/lib/vector-index.js +76 -6
  57. package/lib/wiki/cli-hint.js +90 -0
  58. package/lib/wiki/queries.js +116 -2
  59. package/lib/wiki/t1-jobs.js +232 -0
  60. package/lib/wiki/t1.js +7 -1
  61. package/package.json +25 -1
  62. package/src/api-standalone.js +4 -1
  63. package/src/api.js +581 -29
  64. package/src/attachment-text.js +122 -0
  65. package/src/choro-command.js +9 -2
  66. package/src/config.js +139 -1
  67. package/src/continuity.js +7 -6
  68. package/src/diary/auto.js +190 -0
  69. package/src/diary/compose.js +440 -0
  70. package/src/diary/dir.js +59 -0
  71. package/src/diary/export.js +97 -0
  72. package/src/diary/index.js +26 -0
  73. package/src/diary/library.js +189 -0
  74. package/src/diary/qq-inbox.js +142 -0
  75. package/src/diary/rollover.js +61 -0
  76. package/src/diary/session-provider.js +122 -0
  77. package/src/dream/decisions.js +56 -5
  78. package/src/dream/sleep.js +616 -15
  79. package/src/dream.js +45 -13
  80. package/src/dsh-go/host.js +162 -0
  81. package/src/dsh-go/panel.js +2091 -0
  82. package/src/dsh-go/text.js +342 -0
  83. package/src/dsh-go/views/common.js +68 -0
  84. package/src/dsh-go/views/entity.js +48 -0
  85. package/src/dsh-go/views/memory.js +198 -0
  86. package/src/dsh-go/views/root.js +30 -0
  87. package/src/dsh-go/views/settings.js +398 -0
  88. package/src/dsh-go/views/status.js +51 -0
  89. package/src/embedding.js +42 -8
  90. package/src/entities/extractor.js +36 -7
  91. package/src/hot-memory.js +8 -9
  92. package/src/index.js +92 -2
  93. package/src/inject.js +189 -18
  94. package/src/kb/index.js +309 -0
  95. package/src/kb/routes.js +220 -0
  96. package/src/kb/tools.js +148 -0
  97. package/src/lang.js +87 -4
  98. package/src/peak-hours.js +57 -2
  99. package/src/semantic.js +347 -60
  100. package/src/service.js +193 -66
  101. package/src/settings.js +112 -14
  102. package/src/store.js +347 -21
  103. package/src/summarize.js +38 -11
  104. package/src/tools.js +35 -4
  105. package/src/user-settings.js +128 -32
  106. package/src/vector-index.js +76 -6
  107. package/src/wiki/cli-hint.js +90 -0
  108. package/src/wiki/queries.js +116 -2
  109. package/src/wiki/t1-jobs.js +232 -0
  110. package/src/wiki/t1.js +7 -1
  111. package/test/api-routes-gaps.test.js +63 -0
  112. package/test/api.test.js +375 -4
  113. package/test/attachment-text.test.js +140 -0
  114. package/test/bm25-corpus-cache.test.js +145 -0
  115. package/test/capacity-warning.test.js +262 -0
  116. package/test/client.test.js +779 -154
  117. package/test/conflict-freeze.test.js +45 -1
  118. package/test/continuity.test.js +8 -1
  119. package/test/diary-library.test.js +187 -0
  120. package/test/diary.test.js +576 -0
  121. package/test/dream-maxtokens-fallback.test.js +3 -3
  122. package/test/dream.test.js +62 -1
  123. package/test/dsh-go-host.test.js +134 -0
  124. package/test/dsh-go-panel-actions.test.js +854 -0
  125. package/test/dsh-go-panel.test.js +251 -0
  126. package/test/embedding-ready.test.js +33 -3
  127. package/test/entities.test.js +73 -0
  128. package/test/forget-corrections.test.js +70 -0
  129. package/test/image-memory.test.js +171 -0
  130. package/test/issue-617-step1-query-and-fusion.test.js +361 -0
  131. package/test/kb-api-wiring.test.js +128 -0
  132. package/test/kb-collections.test.js +272 -0
  133. package/test/kb-routes.test.js +379 -0
  134. package/test/mint-source.test.js +26 -0
  135. package/test/peak-hours-observability.test.js +70 -0
  136. package/test/persona-api.test.js +3 -3
  137. package/test/persona-by-session.test.js +568 -0
  138. package/test/persona-settings.test.js +7 -7
  139. package/test/reflection-update-gate.test.js +129 -0
  140. package/test/reindex-backfill.test.js +334 -0
  141. package/test/reindex-scheduler.test.js +369 -0
  142. package/test/scope-candidates.test.js +249 -0
  143. package/test/sleep-action-set.test.js +14 -0
  144. package/test/sleep-heat.test.js +49 -0
  145. package/test/sleep.test.js +598 -0
  146. package/test/standalone-api.test.js +24 -0
  147. package/test/store.test.js +20 -0
  148. package/test/summarize.test.js +26 -0
  149. package/test/user-settings-segments.test.js +528 -0
  150. package/test/vector-index.test.js +120 -6
  151. package/test/wiki-api.test.js +355 -0
  152. package/test/wiki-cli-hint.test.js +118 -0
  153. package/test/wiki-queries.test.js +7 -0
  154. package/test/wiki-t1-jobs.test.js +125 -0
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
3
3
  import { validateDecisions, applyDecisions, createDreamScheduler } from "../src/dream.js";
4
4
  import { createStore } from "../src/store.js";
5
5
  import { createService } from "../src/service.js";
6
- import { mockCtx } from "./helpers/dream-mock.js";
6
+ import { mockCtx, sessionDecisions } from "./helpers/dream-mock.js";
7
7
 
8
8
  function snapshot(ids, type = "project") {
9
9
  return new Map(ids.map((id, i) => [id, { id, type, title: `t${i}`, content: `c${i}`, importance: 3, archived: false, forgotten: false }]));
@@ -905,6 +905,67 @@ test("runDream sliding window keeps the newest N memories and excludes the oldes
905
905
  store.close();
906
906
  });
907
907
 
908
+ // --- issue #557:dream 审计行输入快照瘦身(dreamInputSummaryEnabled)-----------
909
+ //
910
+ // dream_runs.input 单行体积随记忆量涨(#557 实测平均 40-79 KB/轮,大头是
911
+ // content),轮数又随天数涨,超线性。本组用例锁三件事:
912
+ // ① 默认关 = 审计行逐字节一致(content 原样落库);
913
+ // ② 开启后 auto 轮只落骨架(id/type/title/importance/updated_at),且
914
+ // input_count / snapshot_hash / receipt / decisions 都不变——瘦身的只是
915
+ // 落库副本,LLM 输入与 receipt 完整性不受影响;
916
+ // ③ organize 轮的重放载荷不经过 dream.js 的快照构造(结构性分叉),这里锁
917
+ // auto 路径的形状,organize 侧由 organize.test.js 的 apply 重放用例兜住。
918
+
919
+ test("issue#557: 默认关 = 审计行快照带 content(行为与改动前一致)", async () => {
920
+ const { store, service } = dreamSetup();
921
+ const content = "很长的正文,瘦身前应原样落库。".repeat(20);
922
+ for (let i = 0; i < 3; i++) {
923
+ service.saveWithDedupe({ type: "project", title: `主题${i}`, content, importance: 3 });
924
+ }
925
+ const ctx = mockCtx({ onConsolidation: sessionDecisions });
926
+ const dream = createDreamScheduler({ thresholdCount: 1, thresholdChars: 0, delayMs: 0 });
927
+ await dream.runDream(ctx, service, {
928
+ dreamProvider: "deepseek", dreamModel: "deepseek-chat"
929
+ });
930
+ const run = store.listDreamRuns()[0];
931
+ assert.equal(run.input_count, 3);
932
+ assert.deepEqual(
933
+ Object.keys(run.input[0]).sort(),
934
+ ["content", "id", "importance", "title", "type", "updated_at"],
935
+ "默认快照含 content"
936
+ );
937
+ assert.equal(run.input[0].content, content, "content 原样落库");
938
+ store.close();
939
+ });
940
+
941
+ test("issue#557: dreamInputSummaryEnabled 开启后快照只留骨架,判定与回执不变", async () => {
942
+ const { store, service } = dreamSetup();
943
+ for (let i = 0; i < 3; i++) {
944
+ service.saveWithDedupe({ type: "project", title: `主题${i}`, content: `很长的正文${i}`.repeat(20), importance: 3 });
945
+ }
946
+ const ctx = mockCtx({ onConsolidation: sessionDecisions });
947
+ const dream = createDreamScheduler({ thresholdCount: 1, thresholdChars: 0, delayMs: 0 });
948
+ const result = await dream.runDream(ctx, service, {
949
+ dreamProvider: "deepseek", dreamModel: "deepseek-chat",
950
+ dreamInputSummaryEnabled: true
951
+ });
952
+ const run = store.listDreamRuns()[0];
953
+ assert.equal(run.input_count, 3, "input_count 语义不变");
954
+ for (const m of run.input) {
955
+ assert.deepEqual(
956
+ Object.keys(m).sort(),
957
+ ["id", "importance", "title", "type", "updated_at"],
958
+ "瘦身快照不含 content"
959
+ );
960
+ }
961
+ assert.deepEqual(run.input.map((m) => m.title).sort(), ["主题0", "主题1", "主题2"], "骨架字段带出标题");
962
+ assert.ok(run.snapshot_hash && run.snapshot_hash.length === 64, "snapshot_hash 形状不变(内存里对含 content 快照计算)");
963
+ assert.match(run.receipt, /^dsh-mneme:run:/, "receipt 形状不变");
964
+ assert.ok(Array.isArray(run.decisions) && run.decisions.length > 0, "决策不受影响");
965
+ assert.ok(JSON.stringify(run.input).length < 2048, "骨架行确实小(content 不在)");
966
+ store.close();
967
+ });
968
+
908
969
  // --- v0.4.4 fix: 决策 JSON schema 固化(kimi 等模型输出合规) -----------------
909
970
 
910
971
  test("consolidation prompt pins the decision schema (action field, single-string winner/loser, single claim per id)", async () => {
@@ -0,0 +1,134 @@
1
+ // src/dsh-go/host.js —— 进程内派发的判据。
2
+ //
3
+ // 这个文件锁的是「面板能不能拿到宿主真的答复」,而不是某一屏长什么样:
4
+ // - 请求体必须在 handler 挂好监听之后再 flush,否则 readBody 永远等不到 end;
5
+ // - handler 返回 undefined 但稍后才 res.end(/version-check 就是这样)时,
6
+ // 调用方必须等到响应真的结束,不能拿着空 body 当成结果;
7
+ // - 配了 apiToken 之后写路由要带令牌,否则被自家路由注册器拦成 401;
8
+ // - 超时、未注册路径都要抛错,而不是返回一个空对象让面板显示成「0 条」。
9
+ import { test } from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import { createHostCall } from "../src/dsh-go/host.js";
12
+
13
+ function fakeServer(routes) {
14
+ return {
15
+ match(pathname) {
16
+ const handler = routes[pathname];
17
+ return handler === undefined ? undefined : { kind: "exact", path: pathname, handler };
18
+ },
19
+ };
20
+ }
21
+
22
+ function readAll(req) {
23
+ return new Promise((resolve) => {
24
+ let data = "";
25
+ req.on("data", (chunk) => {
26
+ data += String(chunk);
27
+ });
28
+ req.on("end", () => resolve(data));
29
+ });
30
+ }
31
+
32
+ const ctxWith = (routes) => ({ get: (name) => (name === "webServer" ? fakeServer(routes) : undefined) });
33
+
34
+ test("进程内调用能读到请求体,并且同步响应即可返回", async () => {
35
+ let seen = null;
36
+ const call = createHostCall(
37
+ ctxWith({
38
+ "/api/dsh-mneme/echo": (req, res) => {
39
+ seen = { method: req.method, url: req.url };
40
+ void readAll(req).then((body) => {
41
+ seen.body = body;
42
+ res.writeHead(200, { "content-type": "application/json" });
43
+ res.end(JSON.stringify({ ok: true, body: JSON.parse(body) }));
44
+ });
45
+ },
46
+ }),
47
+ );
48
+ const result = await call("/echo", { method: "POST", body: { title: "x" } });
49
+ assert.deepEqual(result, { ok: true, body: { title: "x" } });
50
+ assert.equal(seen.method, "POST");
51
+ assert.equal(seen.body, JSON.stringify({ title: "x" }));
52
+ });
53
+
54
+ test("handler 同步返回 undefined、稍后 res.end 时仍然等到响应结束", async () => {
55
+ // /version-check 的真实形状:handler 立即 return,答复在 promise 回调里写完。
56
+ const call = createHostCall(
57
+ ctxWith({
58
+ "/api/dsh-mneme/version-check": (req, res) => {
59
+ setTimeout(() => {
60
+ res.writeHead(200, { "content-type": "application/json" });
61
+ res.end(JSON.stringify({ version: "0.10.1", status: "latest" }));
62
+ }, 5);
63
+ return undefined;
64
+ },
65
+ }),
66
+ );
67
+ const result = await call("/version-check");
68
+ assert.equal(result.version, "0.10.1");
69
+ assert.equal(result.status, "latest");
70
+ });
71
+
72
+ test("配了 apiToken 就把令牌放进 x-dsh-mneme-token 头", async () => {
73
+ const call = createHostCall(
74
+ ctxWith({
75
+ "/api/dsh-mneme/update": (req, res) => {
76
+ const token = req.headers["x-dsh-mneme-token"] ?? "";
77
+ res.writeHead(token === "secret-token" ? 200 : 401);
78
+ res.end(JSON.stringify(token === "secret-token" ? { ok: true } : { error: "unauthorized" }));
79
+ },
80
+ }),
81
+ { apiToken: "secret-token" },
82
+ );
83
+ assert.deepEqual(await call("/update", { method: "POST", body: { id: "m1" } }), { ok: true });
84
+ });
85
+
86
+ test("没有令牌时写路由的 401 会变成异常,而不是空结果", async () => {
87
+ const call = createHostCall(
88
+ ctxWith({
89
+ "/api/dsh-mneme/update": (req, res) => {
90
+ res.writeHead(401);
91
+ res.end(JSON.stringify({ error: "unauthorized" }));
92
+ },
93
+ }),
94
+ );
95
+ await assert.rejects(call("/update", { method: "POST", body: { id: "m1" } }), /401/);
96
+ });
97
+
98
+ test("未注册的路径直接报错,不会静默返回空对象", async () => {
99
+ const call = createHostCall(ctxWith({}));
100
+ await assert.rejects(call("/list"), /mneme 未注册/);
101
+ });
102
+
103
+ test("宿主没有 webServer 时报错", async () => {
104
+ const call = createHostCall({ get: () => undefined });
105
+ await assert.rejects(call("/list"), /webServer 不可用/);
106
+ });
107
+
108
+ test("超时抛错(避免面板永远等着)", async () => {
109
+ const call = createHostCall(
110
+ ctxWith({
111
+ "/api/dsh-mneme/slow": () => {
112
+ // 永不响应
113
+ },
114
+ }),
115
+ );
116
+ await assert.rejects(call("/slow", { timeoutMs: 30 }), /超时/);
117
+ });
118
+
119
+ test("空响应体归一成 {},非 JSON 归一成 { text }", async () => {
120
+ const call = createHostCall(
121
+ ctxWith({
122
+ "/api/dsh-mneme/empty": (req, res) => {
123
+ res.writeHead(200);
124
+ res.end("");
125
+ },
126
+ "/api/dsh-mneme/plain": (req, res) => {
127
+ res.writeHead(200);
128
+ res.end("hello");
129
+ },
130
+ }),
131
+ );
132
+ assert.deepEqual(await call("/empty"), {});
133
+ assert.deepEqual(await call("/plain"), { text: "hello" });
134
+ });