@modusensus/dsh-mneme 0.12.0 → 0.14.0-beta.1

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 (182) hide show
  1. package/README.md +51 -20
  2. package/bin/dsh-mneme-choro.mjs +9 -1
  3. package/docs/CONFIGURATION.md +47 -6
  4. package/docs/DIRECTORIES.md +114 -0
  5. package/docs/DISTILL.md +64 -0
  6. package/docs/DSH-GO.md +97 -0
  7. package/docs/ENTITIES.md +5 -3
  8. package/docs/FORGET.md +77 -0
  9. package/docs/KB.md +110 -0
  10. package/docs/SLEEP.md +28 -3
  11. package/docs/STORAGE.md +23 -0
  12. package/lib/api-standalone.js +4 -1
  13. package/lib/api.js +788 -30
  14. package/lib/attachment-text.js +122 -0
  15. package/lib/choro-command.js +9 -2
  16. package/lib/client.js +4627 -1409
  17. package/lib/config.js +166 -1
  18. package/lib/continuity.js +7 -6
  19. package/lib/diary/auto.js +190 -0
  20. package/lib/diary/compose.js +440 -0
  21. package/lib/diary/dir.js +59 -0
  22. package/lib/diary/export.js +97 -0
  23. package/lib/diary/index.js +26 -0
  24. package/lib/diary/library.js +189 -0
  25. package/lib/diary/qq-inbox.js +142 -0
  26. package/lib/diary/rollover.js +61 -0
  27. package/lib/diary/session-provider.js +122 -0
  28. package/lib/dream/decisions.js +95 -6
  29. package/lib/dream/sleep.js +760 -19
  30. package/lib/dream/triage.js +139 -0
  31. package/lib/dream.js +168 -21
  32. package/lib/dsh-go/host.js +162 -0
  33. package/lib/dsh-go/panel.js +2091 -0
  34. package/lib/dsh-go/text.js +342 -0
  35. package/lib/dsh-go/views/common.js +68 -0
  36. package/lib/dsh-go/views/entity.js +48 -0
  37. package/lib/dsh-go/views/memory.js +198 -0
  38. package/lib/dsh-go/views/root.js +30 -0
  39. package/lib/dsh-go/views/settings.js +398 -0
  40. package/lib/dsh-go/views/status.js +51 -0
  41. package/lib/embedding.js +42 -8
  42. package/lib/entities/extractor.js +36 -7
  43. package/lib/hot-memory.js +22 -11
  44. package/lib/index.js +123 -3
  45. package/lib/inject.js +203 -20
  46. package/lib/kb/index.js +309 -0
  47. package/lib/kb/routes.js +220 -0
  48. package/lib/kb/tools.js +148 -0
  49. package/lib/lang.js +98 -4
  50. package/lib/peak-hours.js +57 -2
  51. package/lib/semantic.js +347 -60
  52. package/lib/service.js +268 -69
  53. package/lib/settings.js +128 -15
  54. package/lib/store.js +585 -26
  55. package/lib/summarize.js +38 -11
  56. package/lib/tools.js +35 -4
  57. package/lib/user-settings.js +128 -32
  58. package/lib/vector-index.js +76 -6
  59. package/lib/wiki/claims.js +267 -0
  60. package/lib/wiki/cli-hint.js +90 -0
  61. package/lib/wiki/queries.js +282 -2
  62. package/lib/wiki/render.js +74 -0
  63. package/lib/wiki/t0.js +43 -6
  64. package/lib/wiki/t1-jobs.js +232 -0
  65. package/lib/wiki/t1.js +145 -32
  66. package/package.json +39 -1
  67. package/scripts/release-docs.mjs +7 -1
  68. package/scripts/release-notes.mjs +4 -1
  69. package/src/api-standalone.js +4 -1
  70. package/src/api.js +788 -30
  71. package/src/attachment-text.js +122 -0
  72. package/src/choro-command.js +9 -2
  73. package/src/config.js +166 -1
  74. package/src/continuity.js +7 -6
  75. package/src/diary/auto.js +190 -0
  76. package/src/diary/compose.js +440 -0
  77. package/src/diary/dir.js +59 -0
  78. package/src/diary/export.js +97 -0
  79. package/src/diary/index.js +26 -0
  80. package/src/diary/library.js +189 -0
  81. package/src/diary/qq-inbox.js +142 -0
  82. package/src/diary/rollover.js +61 -0
  83. package/src/diary/session-provider.js +122 -0
  84. package/src/dream/decisions.js +95 -6
  85. package/src/dream/sleep.js +760 -19
  86. package/src/dream/triage.js +139 -0
  87. package/src/dream.js +168 -21
  88. package/src/dsh-go/host.js +162 -0
  89. package/src/dsh-go/panel.js +2091 -0
  90. package/src/dsh-go/text.js +342 -0
  91. package/src/dsh-go/views/common.js +68 -0
  92. package/src/dsh-go/views/entity.js +48 -0
  93. package/src/dsh-go/views/memory.js +198 -0
  94. package/src/dsh-go/views/root.js +30 -0
  95. package/src/dsh-go/views/settings.js +398 -0
  96. package/src/dsh-go/views/status.js +51 -0
  97. package/src/embedding.js +42 -8
  98. package/src/entities/extractor.js +36 -7
  99. package/src/hot-memory.js +22 -11
  100. package/src/index.js +123 -3
  101. package/src/inject.js +203 -20
  102. package/src/kb/index.js +309 -0
  103. package/src/kb/routes.js +220 -0
  104. package/src/kb/tools.js +148 -0
  105. package/src/lang.js +98 -4
  106. package/src/peak-hours.js +57 -2
  107. package/src/semantic.js +347 -60
  108. package/src/service.js +268 -69
  109. package/src/settings.js +128 -15
  110. package/src/store.js +585 -26
  111. package/src/summarize.js +38 -11
  112. package/src/tools.js +35 -4
  113. package/src/user-settings.js +128 -32
  114. package/src/vector-index.js +76 -6
  115. package/src/wiki/claims.js +267 -0
  116. package/src/wiki/cli-hint.js +90 -0
  117. package/src/wiki/queries.js +282 -2
  118. package/src/wiki/render.js +74 -0
  119. package/src/wiki/t0.js +43 -6
  120. package/src/wiki/t1-jobs.js +232 -0
  121. package/src/wiki/t1.js +145 -32
  122. package/test/api-routes-gaps.test.js +63 -0
  123. package/test/api.test.js +385 -4
  124. package/test/attachment-text.test.js +140 -0
  125. package/test/bm25-corpus-cache.test.js +145 -0
  126. package/test/capacity-warning.test.js +262 -0
  127. package/test/client.test.js +1051 -185
  128. package/test/conflict-freeze.test.js +45 -1
  129. package/test/conflict-ledger-enforce.test.js +452 -0
  130. package/test/conflict-ledger.test.js +242 -0
  131. package/test/continuity.test.js +8 -1
  132. package/test/contrast.test.js +124 -12
  133. package/test/diary-library.test.js +187 -0
  134. package/test/diary.test.js +576 -0
  135. package/test/dream-baseline-clamp.test.js +174 -0
  136. package/test/dream-maxtokens-fallback.test.js +3 -3
  137. package/test/dream.test.js +62 -1
  138. package/test/dsh-go-host.test.js +134 -0
  139. package/test/dsh-go-panel-actions.test.js +854 -0
  140. package/test/dsh-go-panel.test.js +251 -0
  141. package/test/embedding-ready.test.js +33 -3
  142. package/test/entities.test.js +73 -0
  143. package/test/forget-corrections.test.js +70 -0
  144. package/test/graph-api.test.js +124 -0
  145. package/test/hot-memory.test.js +48 -0
  146. package/test/image-memory.test.js +171 -0
  147. package/test/inject-parent-gate.test.js +6 -3
  148. package/test/inject.test.js +59 -0
  149. package/test/issue-617-step1-query-and-fusion.test.js +361 -0
  150. package/test/kb-api-wiring.test.js +128 -0
  151. package/test/kb-collections.test.js +272 -0
  152. package/test/kb-routes.test.js +379 -0
  153. package/test/mint-source.test.js +26 -0
  154. package/test/panel-chart-scale.test.js +90 -0
  155. package/test/panel-tokens.test.js +245 -0
  156. package/test/peak-hours-observability.test.js +70 -0
  157. package/test/persona-api.test.js +3 -3
  158. package/test/persona-by-session.test.js +568 -0
  159. package/test/persona-settings.test.js +7 -7
  160. package/test/reflection-update-gate.test.js +129 -0
  161. package/test/reindex-backfill.test.js +334 -0
  162. package/test/reindex-scheduler.test.js +369 -0
  163. package/test/release-docs.test.js +20 -0
  164. package/test/release-notes.test.js +25 -0
  165. package/test/scope-candidates.test.js +249 -0
  166. package/test/security-doc.test.js +10 -0
  167. package/test/sleep-action-set.test.js +14 -0
  168. package/test/sleep-heat.test.js +49 -0
  169. package/test/sleep.test.js +635 -0
  170. package/test/standalone-api.test.js +24 -0
  171. package/test/store.test.js +20 -0
  172. package/test/summarize.test.js +26 -0
  173. package/test/user-settings-segments.test.js +528 -0
  174. package/test/vector-index.test.js +120 -6
  175. package/test/wiki-api.test.js +463 -1
  176. package/test/wiki-claims.test.js +194 -0
  177. package/test/wiki-cli-hint.test.js +118 -0
  178. package/test/wiki-provenance.test.js +202 -0
  179. package/test/wiki-queries.test.js +128 -0
  180. package/test/wiki-t0.test.js +77 -1
  181. package/test/wiki-t1-jobs.test.js +125 -0
  182. package/test/wiki-t1.test.js +226 -1
@@ -11,7 +11,7 @@ export const ACTIONS = new Set(["keep", "merge", "archive", "conflict", "update"
11
11
  // Epistemic trust (v0.4.5): when config.trustEpistemicWeighting is on, merge
12
12
  // keepSource and conflict winners prefer the higher-trust memory. Higher value
13
13
  // = preferred. observation (measured) > inferred (derived) > subjective (guess).
14
- const EPISTEMIC_PRIORITY = { observation: 3, inferred: 2, subjective: 1 };
14
+ export const EPISTEMIC_PRIORITY = { observation: 3, inferred: 2, subjective: 1 };
15
15
 
16
16
  // Issue #104 方向 2:archive 类型护栏的长保留类型集合——这些类型的存在意义
17
17
  // 就是长期保存(踩坑记录、约束、用户偏好、挖掘出的模式),LLM 以「价值判断」
@@ -119,6 +119,16 @@ export function validateDecisions(decisions, snapshot, options = {}) {
119
119
  // 不够的——ACTIONS 是两个模块共用的,模型自发出 supersede / differentiate 时校验器
120
120
  // 照样放行,"opt-in 零行为变化"就成了空话。省略 / null = 不限制(沿用全局 ACTIONS)。
121
121
  const allowedActions = Array.isArray(options.allowedActions) ? new Set(options.allowedActions) : null;
122
+ // reflectionUpdateEnabled=false:update 在本次调用里不算合法动作。走与
123
+ // allowedActions 同一条单条跳过的通路(不是整单拒绝)——用户关掉的是「改写已有
124
+ // 记忆」这一种动作,模型多输出了几条 update 不该连带丢掉同批的 merge/archive。
125
+ // 与 prompt 侧后缀双闸:prompt 让模型别写,这里兜住模型仍然写了的情况。
126
+ const allowUpdate = options.allowUpdate !== false;
127
+ // 被用户开关关掉的动作单独计数:它们进 skipped(要留痕),但不该触发下面的
128
+ // 「全部跳过 = 模型坏了」整单拒绝——那是防幻觉 id / 截断输出的护栏,而这里是
129
+ // 用户自己关掉的动作。混在一起会让「只输出了 update」的一轮被判 failed、调度器
130
+ // 拿同一批输入重跑同一条必被拒的输出,无限循环。
131
+ let policySkipped = 0;
122
132
  if (!Array.isArray(decisions)) {
123
133
  return { ok: false, errors: ["decision list must be an array"], resolvedShortIds: 0 };
124
134
  }
@@ -168,6 +178,7 @@ export function validateDecisions(decisions, snapshot, options = {}) {
168
178
  // 单条校验错误先进 local:skipInvalid 模式下整条跳过,严格模式下才并入
169
179
  // 全局 errors(沿用 v0.6.9 的双轨结构)。
170
180
  const local = [];
181
+ let policyDisabled = false;
171
182
  // Issue #126:supersede 与 conflict 一样用 winner/loser 定位双方(沿用同一套
172
183
  // 字段,校验与 receipt 逻辑因此可以直接复用)。
173
184
  const ids = d && (d.action === "conflict" || d.action === "supersede") ? [d.winner, d.loser] : (d?.ids ?? []);
@@ -176,6 +187,12 @@ export function validateDecisions(decisions, snapshot, options = {}) {
176
187
  } else if (allowedActions && !allowedActions.has(d.action)) {
177
188
  // 动作本身合法,但不在当前档位允许的集合里(例如默认档收到 supersede)。
178
189
  local.push(`${at}: action ${JSON.stringify(d.action)} not allowed in this mode`);
190
+ } else if (d.action === "update" && !allowUpdate) {
191
+ // reflectionUpdateEnabled=false:旧记忆永不被自动改写。这条要排在
192
+ // allowedActions 之后、update 字段校验之前——先判「这个动作本次不许用」,
193
+ // 再谈字段合不合规,否则同一条会同时报两个错、日志里读不出真实原因。
194
+ local.push(`${at}: update action is disabled (reflectionUpdateEnabled=false)`);
195
+ policyDisabled = true;
179
196
  } else if (d.action === "conflict" || d.action === "supersede") {
180
197
  if (!d.winner || !d.loser || d.winner === d.loser) {
181
198
  local.push(`${at}: ${d.action} needs distinct winner and loser`);
@@ -328,11 +345,15 @@ export function validateDecisions(decisions, snapshot, options = {}) {
328
345
  // title/content)、还是字段给了但值不合规(has_* 为 true 仍报错)。
329
346
  // 只加布尔不加原文:原文进 llm_audit_logs.metadata 与 dream_runs.raw_output,
330
347
  // 这里保持每条记录足够小(skipped 会按轮累积进一行 JSON)。
348
+ if (policyDisabled) policySkipped++;
331
349
  skipped.push({
332
350
  index,
333
351
  action: d?.action,
334
352
  ids,
335
353
  error: local.join("; "),
354
+ // true = 用户开关关掉了这个动作(不是模型输出有问题)。调用方据此区分
355
+ // 「这轮白跑」与「这轮输出坏了」。
356
+ ...(policyDisabled ? { policyDisabled: true } : {}),
336
357
  has_keepSource: d?.keepSource !== undefined && d?.keepSource !== null,
337
358
  has_title: typeof d?.title === "string" && d.title.trim().length > 0,
338
359
  has_content: typeof d?.content === "string" && d.content.trim().length > 0
@@ -391,7 +412,9 @@ export function validateDecisions(decisions, snapshot, options = {}) {
391
412
  // 子集为空时仍整单拒绝——宽容的前提是「有合法子集可应用」;模型输出整体
392
413
  // 非法(如全部幻觉 id)是真故障,不能被隐式 keep 洗成 ok。#104 只豁免
393
414
  // 「有合法子集但覆盖率不足」的良性挑重点(见下方 coverageShortfall)。
394
- if (options.skipInvalid === true && skipped.length > 0 && survivors.length === 0 && errors.length === 0) {
415
+ // 只把「非策略性跳过」算进这条:policySkipped 是用户关掉的动作,不是模型故障。
416
+ const modelSkipped = skipped.length - policySkipped;
417
+ if (options.skipInvalid === true && modelSkipped > 0 && survivors.length === 0 && errors.length === 0) {
395
418
  errors.push(`all ${skipped.length} decision(s) invalid — nothing valid survived validation`);
396
419
  return { ok: false, errors, skipped, resolvedShortIds };
397
420
  }
@@ -514,6 +537,7 @@ function casGuard(service, snapshot, ids) {
514
537
  */
515
538
  export function applyDecisions(decisions, service, logger = null, snapshot = null, config = {}) {
516
539
  let applied = 0;
540
+ let merged = 0;
517
541
  const conflicts = [];
518
542
  const failures = [];
519
543
  const committed = [];
@@ -528,6 +552,12 @@ export function applyDecisions(decisions, service, logger = null, snapshot = nul
528
552
  const outcome = applyOne(d, service, snapshot, config, logger);
529
553
  if (outcome === "skipped") continue;
530
554
  applied += outcome.applied;
555
+ // `merged` counts creates that hit a same-title row instead of minting a
556
+ // new one. Kept apart from `applied` on purpose: a create that vanished
557
+ // into an existing row is not a new conclusion, and folding it in is how
558
+ // two different dispositions ended up looking like two minted patterns
559
+ // (#619 audit round 5).
560
+ if (outcome.merged) merged += outcome.merged;
531
561
  committed.push(outcome.committed);
532
562
  } catch (error) {
533
563
  if (error instanceof CasConflictError) {
@@ -539,7 +569,7 @@ export function applyDecisions(decisions, service, logger = null, snapshot = nul
539
569
  }
540
570
  }
541
571
  }
542
- return { applied, conflicts, failures, committed };
572
+ return { applied, merged, conflicts, failures, committed };
543
573
  }
544
574
 
545
575
  function applyOne(d, service, snapshot, config = {}, logger = null) {
@@ -597,20 +627,41 @@ function applyCreate(d, service, config = {}, logger = null) {
597
627
  // 信它就等于让模型给自造条目盖「人写的」戳(见 applyDecisions 的 @param config)。
598
628
  // 取不到就一个字段都不传,落回 saveWithDedupe 原有的兜底,行为与改前一致。
599
629
  const mintSource = typeof config?.mintSource === "string" ? config.mintSource.trim() : "";
630
+ // pattern 是「整句重写」而不是「增量补充」:同一 (人, 对象, 情绪) 的标题天然
631
+ // 相同,saveWithDedupe 命中后若走 appendContent,重放一轮就把同一段结论再粘
632
+ // 一遍(实测 161 → 355 → 549 字,外加一串重复的 [证据: …] 尾巴),而 pattern
633
+ // 正是注入面消费的东西。`_overwrite` 是既有逃生口(bootstrap / dream summary /
634
+ // narrative 三处先例):命中同题行时原地刷新而不是追加,旧文进 content_history。
635
+ // 只对 pattern 放开——其他类型被 create 撞上同题行时仍按合并语义走,那多半是
636
+ // 模型自造了不该出现的类型,把既有行整段覆盖掉比追加更危险。
637
+ const overwrite = type === "pattern";
600
638
  const created = service.saveWithDedupe({
601
639
  type,
602
640
  title,
603
641
  content: body,
604
642
  importance,
605
- ...(mintSource ? { source: mintSource } : {})
643
+ ...(mintSource ? { source: mintSource } : {}),
644
+ ...(overwrite ? { _overwrite: true } : {})
606
645
  });
607
646
  const memory = created?.memory;
608
647
  if (!memory) return "skipped"; // deduped/subsumed: nothing minted, clean no-op
648
+ // 命中同题活跃行 ⇒ saveWithDedupe 返回 action:"merged"。它确实动过库(刷新了
649
+ // 那一行),但**没有铸出新条目**:把它算进 applied 会让「一条真实结论被同题行
650
+ // 吃掉了」这种情况看起来像成功(#619 审计第五轮:两个不同对象铸出同一条
651
+ // pattern,applied 照样报 2)。两类结局分开报:applied 只数新铸,merged 数刷新。
652
+ const refreshed = created?.action !== "created";
609
653
  // #512:这条记忆出生自哪一轮。只记 create——merge/conflict/update/supersede 对
610
654
  // 既有行的改动已由 receipt_chain 按 (run_id, record_id) 记账,再造一份就是第二套账。
655
+ //
656
+ // #619 / Ephor 第七轮 ③:`refreshed` 也在这条规矩里。原地刷新没有铸新条目,
657
+ // 它改的是既有行——同一条记忆不该有两份「出生记录」。此前 create_merged 这条
658
+ // 新路没被上面那句规矩覆盖,于是一个 pattern 跑两轮,`memory_mints` 就有两行、
659
+ // run_id 各不相同,面板上显示成「这条记忆出生过两次」(`mintsFor` 由
660
+ // provenance-http.js 读取)。live 库当时还没显形,是因为同题刷新刚上线。
661
+ //
611
662
  // 记账失败不许拖垮这次铸造:归因是审计,记忆已经落盘了。
612
663
  try {
613
- if (typeof service?.saveMemoryMint === "function" && config?.runId) {
664
+ if (!refreshed && typeof service?.saveMemoryMint === "function" && config?.runId) {
614
665
  service.saveMemoryMint({
615
666
  memoryId: memory.id,
616
667
  runId: config.runId,
@@ -622,7 +673,7 @@ function applyCreate(d, service, config = {}, logger = null) {
622
673
  } catch (mintError) {
623
674
  logger?.warn?.(`dsh-mneme dream: failed to record memory mint: ${String(mintError?.message ?? mintError)}`);
624
675
  }
625
- return { applied: 1, committed: { action: "create", id: memory.id, type } };
676
+ return { applied: refreshed ? 0 : 1, merged: refreshed ? 1 : 0, committed: { action: refreshed ? "create_merged" : "create", id: memory.id, type } };
626
677
  }
627
678
 
628
679
  function applyArchive(d, service, snapshot) {
@@ -715,6 +766,44 @@ function applyConflict(d, service, snapshot, config = {}) {
715
766
  return { applied: 1, committed: { action: "conflict", winner: d.winner, loser: d.loser, count_before: 2, count_after: 1 } };
716
767
  }
717
768
 
769
+ /**
770
+ * 确定性信息量优先 supersede(issue #641 enforce 档的 T1 处置,供 sleep/dream
771
+ * 两条链路共用)。与冻结分支自动裁决档(#603 第 4 层,sleep.js 内联实现)同一
772
+ * 条契约,本函数是它的共享出口:
773
+ * · 保留**信息量更大**的那条,不是更新的——#616 实测 55-57% 的新条反而更短
774
+ * (蒸馏压缩过的一版常常更新),留新条等于把更完整的那条归档掉;
775
+ * · 长度相同才退回 updated_at,再退回 id——三级兜底保证同一对每次判出同一个
776
+ * 赢家(重放不翻面);
777
+ * · trustEpistemicWeighting 显式关:自动处置的承诺必须是确定的,可信度换位
778
+ * 不让结果随 epistemic_status 数据漂移;
779
+ * · 赢家正文一字不动——supersede 的可追溯注记落在被归档侧(#126 原设计)。
780
+ * E13b 证明 conflict 动作的「败者前 100 字追加进赢家」是值级泄漏通道,
781
+ * 确定性处置一律走 supersede、不走 conflict。
782
+ * CAS 快照 + 事务 + 幂等(败者已归档 → applied 0)全部沿用 applyDecisions,
783
+ * 不另写第二份裁决路径。
784
+ *
785
+ * @param {{a: object, b: object}} p 候选对(完整记忆行:id/title/content/updated_at)
786
+ * @returns {{applied: boolean, winnerId: string, loserId: string, winner: object, loser: object}}
787
+ */
788
+ export function supersedeInfoFirstPair(p, service, logger = null, config = {}) {
789
+ const an = p.a.content?.length ?? 0;
790
+ const bn = p.b.content?.length ?? 0;
791
+ const [winner, loser] = an !== bn
792
+ ? (an > bn ? [p.a, p.b] : [p.b, p.a])
793
+ : (p.a.updated_at ?? "") !== (p.b.updated_at ?? "")
794
+ ? ((p.a.updated_at ?? "") > (p.b.updated_at ?? "") ? [p.a, p.b] : [p.b, p.a])
795
+ : (p.a.id > p.b.id ? [p.a, p.b] : [p.b, p.a]);
796
+ const snapshot = new Map();
797
+ for (const m of [winner, loser]) {
798
+ snapshot.set(m.id, { updated_at: m.updated_at, content: m.content, title: m.title });
799
+ }
800
+ const { applied } = applyDecisions(
801
+ [{ action: "supersede", winner: winner.id, loser: loser.id }],
802
+ service, logger, snapshot, { ...config, trustEpistemicWeighting: false }
803
+ );
804
+ return { applied: applied > 0, winnerId: winner.id, loserId: loser.id, winner, loser };
805
+ }
806
+
718
807
  /**
719
808
  * supersede(Issue #126,sleepActionSet="full"):演进型重复——新版本取代旧版本。
720
809
  * 与 conflict 的关键差别:**注记追加到 loser(被取代方)而不是 winner**。conflict