@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
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
6
6
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
7
  [![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
8
- [![tests](https://img.shields.io/badge/tests-2881%20total-success)](https://github.com/slow-stack/mneme)
8
+ [![tests](https://img.shields.io/badge/tests-3262%20total-success)](https://github.com/slow-stack/mneme)
9
9
  [![CI](https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml)](https://github.com/slow-stack/mneme/actions)
10
10
  [![node](https://img.shields.io/badge/node-22%2B-blue)](https://nodejs.org)
11
11
  [![npm downloads](https://img.shields.io/npm/d18m/@modusensus/dsh-mneme.svg?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
@@ -279,26 +279,28 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
279
279
  - **选择性注入**:query 向量可用时注入候选按主题相似度重排(`selectiveInjectEnabled` 可关);**搜索时语义去重**为激进选项(`searchSemanticDedup=true` 显式开启,近重复行 Rerank 前丢弃)
280
280
  - **召回基准**(`scripts/benchmark-recall.js`):标准查询集驱动,计算 Recall@5 与 MRR,`legacy`(三特性全关)vs `fused`(默认配置)双跑对比
281
281
 
282
- ## 📜 版本历史与路线图
282
+ ## 📜 版本历史
283
283
 
284
- > 完整版本说明见 [CHANGELOG](CHANGELOG.md) 与各版本 [GitHub Release](https://github.com/slow-stack/mneme/releases);当前特性以本 README 正文与[配置表](#-配置)为准。早期版本中的实验性功能(Wiki-Link、tag 系统等)已在 v0.7.11 移除,详见 CHANGELOG 对应条目。
284
+ 🧬 基因 → 🛡️ 审计加固 → 💤 睡眠巩固 → 🕸️ 召回融合与图谱 → 🌡️ 自进化 → 🔐 作用域隔离 → 🌐 MCP 生态 → 🧭 M.O. → 💭 情绪归因 → 📔 日记本
285
285
 
286
- 🧬 Gene → 🛡️ 审计加固 → 💤 Sleep Mode → 🕸️ 召回融合与图谱 → ✨ 面板体验 → 🌡️ 自进化记忆 → 🔐 作用域隔离 → 🌐 MCP 生态 → 🧭 M.O.(项目级工作流,v0.9 beta)
286
+ 当前版本的能力以本 README 正文的配置章节为准——**这张表只留一句话头条**,每一版具体怎么实现见 [CHANGELOG](CHANGELOG.md) 与 [Releases](https://github.com/slow-stack/mneme/releases)。
287
287
 
288
- | 版本 | 主题 |
288
+ | 版本 | 头条 |
289
289
  |------|------|
290
- | **v0.9.0**(2026-10-17) | M.O.(Modus Operandi)beta:工作流本体住 `<项目>/.mneme/pipelines/`、候选分层与升格判据、面板清单与图同屏(`actionPipelineEnabled` 默认关);设置面板折叠线挪到「新手之外」并按人话重写 22 个条目名(#427);安全与供应链加固(39 条路由声明式鉴权、OSV 有漏洞即失败、发布带 provenance) |
291
- | **v0.9.1**(2026-10-18) | M.O. 从「看得见」跑到「跑得出账」:执行窗口由 `/mo-run start` / `/mo-run stop` 显式划开、运行账落 `<项目>/.mneme/pipelines/<id>.runs.jsonl`(账本坏了与从没跑过严格分开)、归属只出候选、观察旁挂与脱节检出、步骤 `assert` / `optional`、R1/R2 提议;沉淀链从真实会话日志挖 draft 工作流(判据恒 `null`,答案只能由人补);人格定义页 v1 → v1.2(按 agent 寻址多卡,默认关);面板、向量与检查更新的一圈修复(#448 / #450 / #460);Release 正文改分层渲染 |
292
- | **v0.9.2**(2026-10-19) | 让「看不见的失败」显形:功能开关的第三态「待重启」把「面板显示已改 ≠ 运行时已改」摆到界面上(#481);作用域级可逆闸门让一路记忆能被单独按下与松开、不必改配置重启(#471,默认关);巩固用的 `dreamMaxTokens` 默认值撞服务商上限、八天 180 次全失败,面板现在能改、超上限还会自动降级重试(#478);CLI 把 `--` 开头的值当新旗标的真实缺陷(一次 1/4096 概率的 CI 红揪出来的)、人格卡删除 × 永远不可见、自动蒸馏写入的行 100% 拿不到向量(#477) |
293
- | **v0.9.3**(2026-10-20) | M.O. 从「挖得出来」到「会被叫去挖」:沉淀链算法自 v0.9.1 起就完整实现(多帧 zstd → 重复序列 → 缺席对照 → draft 草稿),运行时却没有任何调用点——机器挖得出来、却永远不会被叫去挖。这一版把归属与触发接上:按会话头 `cwd` 反查项目桶(桶名是路径的有损变形,查不到整跑跳过而不是降级成全局普查)、挂 `turn/end`、冷却 10 分钟、单次预算 4 秒、延到宏任务、一次一个项目、抛错只 `warn`,**只产提议不产草稿**(`actionNudgeEnabled`,beta,默认关);面板加「现在沉淀一次」,与自动跑同一条入口,区别只有它不改仓库;另修作用域反查自 v0.8.0 起每次都抛错走降级(#487)、`/nudge` 三处「没看全」被报成「确实没有」(#489 评审)、多帧 zstd 反证断言绑了 Node 整块解语义(#490),并把整个 M.O. 的「手法」改回「工作流」 |
294
- | **v0.9.4**(2026-10-21) | 三条链各自补上「最后一环」——东西早就造好了,只差没人能叫它:**agent 主动整理**(`memory_organize`,先 dryRun 出比对报告 → agent 判断 → apply 落库,筛除只归档不删、全程留审计回执;`organizeEnabled` 默认关,关时工具根本不注册);**画像提取草稿**(面板按钮读活跃记忆 + 一次模型调用,产一段「助手眼中的用户」草稿,`profileExtractEnabled` 默认关)——**只出草稿**,采用是用户再点一次,手写的画像原文不会被模型改写,空库与解析失败都直接报错、绝不回空草稿冒充「没提炼出东西」;**人格寻址收口**(`default` 卡以前无条件落给所有没建卡的身份,宿主里自带身份的 bot 每帧收到两句「你是谁」;`personaStrictAddressing` 默认关,把「没建卡」与「卡被显式关掉」都收回为不注入)。同版补蒸馏的**转录围栏 + 解析失败护栏**(#494:一条会话被同一窗口卡死、重试 20 次越试越贵的根因;失败行现在恒带原始输出形状),把面板自称「注入生效文本」的只读区改成后端单源渲染(并修它从不显示人格行),评审另收掉两处「坏了却不报」:整理报告侧的存在性探测口(`strictScope` 下按调用方可见性过滤落点并重算计数)、画像保存不判 `res.ok` 却提示已保存 |
295
- | **v0.9.5**(2026-10-30) | 记忆能安全地递给别人,也能整包收回来:**分享包 `mneme.share/v1`**(`dsh-mneme share` / `receive` / `undo-share`,面板「⋯」里同一批三件事)只含活跃行,scope 与 home 前缀不进包,命中密钥 / PII 的行整条不进;出包先给报告再给文件,超 3 MiB 水位回 413、按 `--type` / `--tag` 分批;入包一条不合格整包拒、干跑为默认、按 `share_external_id` 幂等,整包退出走物删。「本机巩固不许改写别人导入的原文」由单一谓词 `isImportedRow` 守在汇流口与九个行池上。同版给记忆来源加了读出口 `GET /memory/provenance`(#421,@z2Ace0107 实现;409 = 用户关了、503 = 装配坏了),修掉同标题重复入库(#505)、扫描谎报「没东西可扫」(#515)、五处未围栏的巩固 prompt(#501)与 dream 合并护栏整类误拦(#521),面板新增设置页分组目录(#518) |
296
- | **v0.10.0**(2026-11-02) | 归因与闸门——给已经发生的事补上它自己的身份:**蒸馏有了运行身份**(窗口级 `distill_runs` 记 `ok`/`degraded`/`noop`/`failed`/`aborted` 与 `audit_id`,行级 `memory_mints` 记「这条记忆出生自哪一轮巩固」;历史行留空**不猜**,失败轮也真记一行),此前归因只到「会话区间」——那是输入的坐标不是运行的身份;**溯源补上反方向** `GET /memory/provenance?sessionId=`(两个主键互斥,`limit` 越界 400 不静默截断,撞上限带 `truncated: true`);**会话级无痕闸门** `sessionGateEnabled`(指定会话与子代理不进不出,其余会话照常;子代理判据只认宿主委派标记,`parentSession` 不是判据——本机 641 个会话头实测会误判 3 个 fork 主会话);**防谄媚补到写入面**(蒸馏与总览加「立场纪律」四条与「叙述纪律」,排在与时间换算的指令之前)。另修 `differentiate` 正反馈闭环(注记改落 `differentiated_pairs` 账本、正文一个字节都改不了,实测单条曾堆 19 段)、sleep 冲突相位的输入侧预算与续排(分批后不再隔夜才轮到)、面板「最近巩固」区分 organize 回执(#495)、面板首次有移动端与 481–519px 中间带断点(#542) |
297
- | **v0.10.1**(2026-11-03) | 打开记忆详情不再崩掉整个宿主:0.10.0 里点开侧边栏任意一条记忆的详情抽屉,渲染进程立刻抛 `ReferenceError: provenance is not defined`,整个宿主跟着重启、进行中的会话被打断,100% 复现(#553)。根因是两个隔了三千多行的组件——`MemoryDrawer` 的归因 effect 与渲染块在读 `provenance` / `setProvenance`,而那句 `useState` 落在了同文件**上一个**组件 `EntityPanel` 里,在那里是纯死代码(整个函数体除声明外对 `provenance` 零引用)。这个形状躲得过眼睛、语法检查与原有用例,所以补的不是「锁一个名字」的用例,而是按组件扫「调了 `setXxx(` 却没有声明」。同版带上 v0.10.0 之后合入的图谱锚定层收尾(#24):Tag 与向量两路起点接线(`normalizePath` 补回通路身份、`findEntitiesByTags` 走全等匹配并按 500 分块、反查改 UNION 三源以覆盖只看关系不建 attr 行的记忆、向量路复用同一份 query 向量并滤掉 0 分种子),以及被动确认的复核出口(`GET /semantic/graph/boosted-relations` 按「合法抬升量 > 0」列出、ego 边补 `weight`/`source`、面板默认收起的审计视角) |
298
- | **v0.11.0**(2026-11-05) | 仓库地志(chorography)落地并接到消费面:读一个 JS/TS 仓库,把文件树与 import 图编译成模块划分与 `.chorography/` 下的 Markdown 地志页——T0 骨架全程零模型零网络、`plan.yaml` 正面控制、`CHORO:HUMAN` 保留区跨重生成逐字节存活;T1 逐模块一次 headless 会话补架构/约定/技术栈/构建四段语义正文并落可核验的 claims(三重上限、编造锚点整轮拒)。消费面三张脸:面板第二座位 `dsh-mneme-wiki`、三个只读工具、`/choro/*` 读路由,外加每轮开局一小块标题级提纲注入(`chorographyInject` = light/full/off)与只读 `/choro` 命令(过期只提示、绝不自动重跑)。`chorographyEnabled` opt-in 默认关。wiki 行与记忆行物理隔离,永不进巩固/质量过滤任何行池。另修 #573:宽态侧栏的记忆入口落在「更多」行下面、被展开的菜单盖住 |
299
- | **v0.12.0**(2026-11-07) | pin 从「按类型推断」变成你亲手钉的显式事实:另开 `pinned_memories` 表 + `memory_pin`(取消钉选是同一个工具传 `unpin: true`,没有单独的 unpin 工具),钉住的行在注入里优先占位,不再依赖它恰好是 `constraint` / `preference`;顺带修掉两处旁路——pin 筛选发生在 hybrid 合并之后、而合并结果已按 `poolSize` 封顶(生产 10 条),规则窗口里的 pin 行一条都进不了候选池(真库副本 pin 注入 2/5 → 5/5、总数 7 → 10,原有条目只后移无一条被挤出),判据也从「表里有几行」改成「有几条能用」(pin 行会在记忆侧被归档 / 遗忘 / 硬删而 pin 表一无所知,旧判据只要剩一行就永不回落;`pinnedStats.source` 分 `explicit` / `fallback-none-usable` / `type-fallback` 三档)。`pinnedInjectBudget` **默认 0,即默认状态下 pin 池整体关闭**——钉住不等于会抢占注入位次,要显式设成正数才启用,关着时注入序列与改前逐条相同。同版带上「Agent 眼中的你」自动事实(`profileFactsEnabled` 默认关,按身份分卡维护、参与注入、面板可管)、向量解析缓存从恒 0% 命中到跟工作集自适应(真库四轮 0.0% → 第二轮起 100%,A/B 1.8–2.4s → 370–424ms,存储换 `Float32Array` 55.5MB → 29.7MB,并摘掉「只取 id 却带 `embedding != ''`」的谓词)、[#579](https://github.com/slow-stack/mneme/issues/579) 地志工具改走宿主错误路径且检索不再把「读不到」答成「没匹配」(@Oscar-Williams)、[#590](https://github.com/slow-stack/mneme/issues/590) 严格寻址不再把「一张卡都没建」判成「没有这张卡」,以及面板跨组件广播 `mneme:store-changed`、chorography 收尾(面板「重建骨架」+ 页级向量检索)。 |
300
-
301
- | **v0.8.x** | 作用域隔离(agent / workspace 双维 + opt-in 硬过滤)、冲突队列人工裁决、生态化(stdio MCP server / 图召回轴 / 冷启动 / 注入截断) |
290
+ | **v0.13.0**(2026-11-10) | 情绪终于有地方可放:`affective_context` 带情绪词与归因,三个独立会话才升成稳定倾向,不足就降级不丢弃 |
291
+ | **v0.12.0**(2026-11-07) | pin 从「按类型猜」变成你亲手钉的事实;向量缓存从恒 0% 命中改为跟工作集自适应(1.8–2.4s → 370–424ms) |
292
+ | **v0.11.0**(2026-11-05) | 记忆读得懂仓库:文件树与 import 图编译成模块划分与地志页 |
293
+ | **v0.10.1**(2026-11-03) | 打开记忆详情不再崩掉整个宿主 |
294
+ | **v0.10.0**(2026-11-02) | 记忆开始按「哪一轮」算账:蒸馏有了运行身份,溯源补上反方向 |
295
+ | **v0.9.5**(2026-10-30) | 记忆能安全地递出去,也能整包收回来 |
296
+ | **v0.9.4**(2026-10-21) | 三条链各自补上「最后一环」:主动整理、画像提取草稿、人格寻址收口 |
297
+ | **v0.9.3**(2026-10-20) | M.O. 从「挖得出来」跑到「会被叫去挖」 |
298
+ | **v0.9.2**(2026-10-19) | 让「看不见的失败」显形 |
299
+ | **v0.9.1**(2026-10-18) | M.O. 从「看得见」跑到「跑得出账」 |
300
+ | **v0.9.0**(2026-10-17) | M.O.(工作流)进入 beta:工作流住进项目仓库、可提交可 diff |
301
+ | **v0.8.x** | 作用域隔离(agent / workspace 双维)、冲突队列人工裁决、stdio MCP server、图召回轴、冷启动 |
302
+
303
+ > 早期版本中的实验性功能(Wiki-Link、tag 系统等)已在 v0.7.11 移除,详见 CHANGELOG 对应条目。
302
304
 
303
305
  ## 📦 安装
304
306
 
@@ -381,6 +383,31 @@ dsh plugin --profile web add "C:/path/to/modusensus-dsh-mneme-0.9.0.tgz"
381
383
  dreamDelayMs: 2000
382
384
  ```
383
385
 
386
+ ## 怎么升级(设置不会丢)
387
+
388
+ **升级只动代码,不动你的记忆和设置。**
389
+
390
+ 设置与记忆是**同一个 SQLite 文件**里的表(`user_settings` 与 `memories` 等),文件位置由 `memoryDir` 决定,默认在 `~/.dsh/memory`——**不在插件安装目录下**。所以卸插件、装插件都不碰它。
391
+
392
+ 升级就是覆盖安装,装完重启一次:
393
+
394
+ ```bash
395
+ # --profile 后面写你当初安装它时用的那个名字,默认是 web
396
+ dsh plugin --profile web remove @modusensus/dsh-mneme
397
+ dsh plugin --profile web add @modusensus/dsh-mneme
398
+ dsh web
399
+ ```
400
+
401
+ > 上面是 npm 来源的装法;当初从 Git、tarball 或本地目录装的,按下同一种来源重装即可。唯一要求是**先 remove 再 add**。
402
+ > 不用重装 DSH。重装 DSH 动的是 profile,是更大范围的改动,与插件版本无关。
403
+ > 也不要只 `add` 不 `remove`——宿主可能拿到缓存的旧副本,版本号没变就说明没换成。
404
+
405
+ **这件事是实测过的,不是推断**:用 v0.12.0 的代码建库、写入画像、规则、人格卡与四个功能开关,再换成本次发布的代码打开同一个库——四项全部原样读回。反向也测了:新代码写的库,v0.12.0 同样读得出来。
406
+
407
+ **但有两处不对称要提前知道**:人格卡的按会话分配(`bySession`)以及 `scenario` / `mesExample` 两个字段,v0.12.0 都不认识——用旧版打开新版写的库,这些键读不出来,旧版再写回时可能把它们覆盖掉。所以**回退前先备份整个记忆库目录**(复制 `memoryDir`,默认 `~/.dsh/memory`),需要回退时把库一起退。另外,撤销某个开关只影响之后的行为,不保证把已经写下的数据(例如已经铸出的情绪倾向)一并回滚——要真正清掉得手动删对应条目。
408
+
409
+ 唯一会让你丢数据的是**把库指进 profile 作用域**(形如 `…/profiles/<名字>/…`),宿主清理 profile 时会连带带走——把 `memoryDir` 保持在 profile 之外即可(默认就是),要迁移库则:停止写入 → 复制整个目录 → 改配置指向新位置 → 重启。
410
+
384
411
  ## ⚙️ 配置
385
412
 
386
413
  | 键 | 默认值 | 说明 |
@@ -518,6 +545,7 @@ dsh plugin --profile web add "C:/path/to/modusensus-dsh-mneme-0.9.0.tgz"
518
545
  | `writeAdmission` | `{enabled:false, enforce:false}` | 写入准入(issue #254,默认关):把「这条该不该进库」前移到 LLM 之前。第 1 级是零 LLM 的确定性判据——空白 / 纯噪声 + 密钥 / PII(判据来源是 #164 A2,经 `sensitiveScan` 注入,本批只定义接口)。`enabled` 跑判据并落审计行(`llm_audit_logs` 的 `metadata.deny`),`enforce` 才真的拒(`store.save` 之前返回,`memory_save` 返回 `action:"denied"` + `reason`);只开 `enabled` = 仅告警、写入不拦。去重键命中不进第 1 级(归 write-update 放行),G1/G2 阈值只计量。也走 feature_flags 白名单 |
519
546
  | `preInjectGate` | `{enabled:false, enforce:false}` | 注入前判定(issue #380,默认关):每帧注入候选出池后、进入 system prompt 前,一次池级 LLM 调用判出「会向本次请求注入意见/立场」的记忆(判定协议与 E12 实验同源;实测过滤把谄媚 26.9%→2.5%,且判定结果绝不进模型上下文——给模型看标记的线已被 E2/E3 关闭)。`enabled` 跑判定并落审计行(真实 token 用量 + 被标记记忆 id),`enforce` 才在缓存命中帧真滤除;冷缓存首帧降级为原样注入(宿主注入渲染是同步回调,判定走 prefetch+cache)。pin 池与检索线索行豁免;判定失败原样注入不阻塞;整池被标记时放弃本次滤除(fail-open);判定连续失败 3 次熔断一个冷却期;无默认路由时审计行 `model_id` 落 `unknown` 占位;`llmAudit` 关闭时本闸不运行;lightMode 强制关。也走 feature_flags 白名单 |
520
547
  | `distillRecords` | `{enabled:false, retentionDays:90}` | 蒸馏原文留存 + 记忆级溯源(issue #399,默认关):把蒸馏从「替换」降级为「叠加索引」——蒸过的窗口原文旁路落 `distill_records`(一个事件一行,主键 `(session_id, seq)`,`INSERT OR IGNORE` 幂等),每条新记忆落 `memory_provenance` 回指来源窗口(`(memory_id, session_id, seq_start, seq_end)` 唯一)。**渲染与落盘共用同一隐私谓词**:私有推理链、插件注入的机器内容(AGENTS.md / skill 正文 / 文件变更通知)两条路都不进;工具调用入参与结果、code 输出**只进渲染、不落盘**(渲染是 `codingRetrospect` 的输入,落盘只留对话)。留存文本**不受** `distillMaxChars` / 工具裁剪约束(原文就是原文)。到期裁剪只删 `distill_records`,溯源链接保留指向空区间(面板显示「来源已过期」),**绝不级联删记忆**;删记忆时显式清链接。无历史回填、从未蒸馏的窗口不留记录。进 `dsh-mneme reclaim` 报告第三项;lightMode 强制关。也走 feature_flags 白名单 |
548
+ | `capacityWarning` | `{dbSizeWarnMb:256, archivedRowsWarn:5000, archivedRatioWarn:0.95}` | 容量预警(issue #275,空间维度):库体积 / 归档区规模过线时在 `GET /api/dsh-mneme/health` 的 `capacity` 块与面板「库体积」卡上报。三道阈值都是**观察口**,不是动作触发——没有周期性消费者,预警也永不自动归档 / 删除(真删由人显式确认),出口是维护卡的无损回收(`dsh-mneme reclaim`)。阈值判 `>=` 过线、**0 = 关掉该条检查**;判级 fail-safe(读失败显式 unknown,绝不伪装 ok)。默认值锚定 #275 议题的形态区间(活跃数百 / 归档数千;占比健康区就在 0.8~0.9,故 0.95 起才算过线)。答复只带数字、不带库文件路径;归档净增速率是 recall-stats `archive` 块的第五指标,不在这里重复。也走 feature_flags 白名单(改动启动快照生效,面板显示「待重启」) |
521
549
  | `conflictFreezeEnabled` | `false` | 冲突冻结(v0.4.4):dream 发现矛盾对不自动裁决,冻结进冲突队列;状态页「冲突队列」支持并排对比与人工确认(保留 A / 保留 B / 仅标记已处理,v0.8.0) |
522
550
  | `conflictFreezeMaxPending` | `100` | 冲突冻结队列最大挂起数(1-1000):超出后不再入队(防队列无限膨胀) |
523
551
  | `escapePromptVariables` | `true` | 注入文本花括号转义(v0.8.0 恢复,issue #162):注入边界把 `{{...}}` 转义,防止 hot memory / 记忆原文里的 Go template / Vue 语法触发宿主 interpolate 抛错卡死会话(v0.7.4 曾修复、v0.7.11 误删) |
@@ -733,7 +761,7 @@ src/
733
761
  ├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
734
762
  └── index.js # 插件接线
735
763
  lib/ # src 的同步分发产物(npm run sync,prepack 钩子在发布时自动跑;一致性由 scripts/check-sync.js 在 CI 与 release.yml 校验——它只查不同步;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
736
- test/ # 2881 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
764
+ test/ # 3262 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
737
765
  scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
738
766
  ```
739
767
 
@@ -742,7 +770,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
742
770
  ```bash
743
771
  cd dsh-mneme
744
772
  npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
745
- npm test # 运行 2881 个测试
773
+ npm test # 运行 3262 个测试
746
774
  npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
747
775
  npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
748
776
  ```
@@ -764,6 +792,7 @@ npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动
764
792
  - [连续性抢救(#249 N3):触发器口径与事件契约](docs/CONTINUITY.md)
765
793
  - [Sleep Mode:空闲四阶段深度维护](docs/SLEEP.md)
766
794
  - [存储与回收:表结构、保留期、`dsh-mneme reclaim`](docs/STORAGE.md)
795
+ - [选择性遗忘与事实更正:行粒度契约与失效关系缺口(#532)](docs/FORGET.md)
767
796
  - [记忆分享包:出包与收包的口径、信任边界与实测(`dsh-mneme share` / `receive` / `undo-share`)](docs/SHARE.md)
768
797
  - [独立服务 daemon(`dsh-mneme-serve`)](docs/DAEMON.md)
769
798
  - [与 Zep / Graphiti / Mem0 / Letta / Hindsight 的能力对比](../docs/COMPARISON.md)
@@ -18,6 +18,8 @@
18
18
  那里还会单独列出「白名单登记了、但 schema 没给默认值」的键——这类键在面板上有输入框,
19
19
  留空时却不会进「当前生效值」。
20
20
 
21
+ 路径键改完落点,见 [DIRECTORIES.md](DIRECTORIES.md)(注意各路径键的 `~` 展开规则不一致)。
22
+
21
23
  ## 全局与存储
22
24
 
23
25
  | 键 | 默认 | 作用 | 开启后果 / 冲突 |
@@ -26,6 +28,18 @@
26
28
  | `memoryDir` | `~/.dsh/memory` | SQLite 记忆库目录 | WAL + busy_timeout 已为多进程就绪;两个宿主共用库时 `externalApiEnabled` 与 `autoDream` 只能一边开 |
27
29
  | `lightMode` | `false` | 轻量档总闸:一键关掉全部重型路径,保留核心循环(见文末清单) | 面板 `panel_mode=light` 等效为 `true` 且优先于 bundle 配置 |
28
30
 
31
+ ## 容量预警(#275,空间维度)
32
+
33
+ 库体积 / 归档区规模过线时在 `GET /api/dsh-mneme/health` 的 `capacity` 块与面板「库体积」卡上报。三道阈值都是**观察口**,不是动作触发:没有周期性消费者(`/health` 被读到才算一次),预警也永不自动归档 / 删除——真删由人显式确认(#275 拍板),出口是维护卡的无损回收(`dsh-mneme reclaim`,见 [STORAGE.md](STORAGE.md))。判级实现 `service.getCapacityHealth`,与镜像健康 fail-safe 同款:读失败显式 `status:"unknown"`,绝不伪装 ok。
34
+
35
+ | 键 | 默认 | 作用 | 开启后果 / 冲突 |
36
+ |---|---|---|---|
37
+ | `capacityWarning.dbSizeWarnMb` | `256` | 库磁盘足迹(主文件 + WAL + SHM)预警线,整数 MB | **0 = 关掉这条检查**。默认锚定议题实测:55 MB 库 VACUUM 0.8s,256 MB 起代价才值得先看数字再动手(议题「现在便宜、以后贵」) |
38
+ | `capacityWarning.archivedRowsWarn` | `5000` | 归档区行数预警线 | **0 = 关**。锚定议题形态区间「归档是数千条这个量级」的上沿(实测 2,400 时还没坏) |
39
+ | `capacityWarning.archivedRatioWarn` | `0.95` | 归档占比(归档 / 全部)预警线,浮点 0–1 | **0 = 关**。健康形态本身就落在 0.8~0.9(活跃数百 / 归档数千),0.9 会对着健康库报警;0.95 起才是「归档区吞掉活跃区」的形态 |
40
+
41
+ 三个键都走 feature_flags 白名单(面板可改,改动是**启动快照**,与 `panel_mode` 同节奏,面板显示「待重启」);两个整数走 INT_RANGES、占比浮点走 NUMBER_RANGES。不设 `enabled` 总开关:数字恒在 `/health`,把某条检查关掉就是把对应阈值设 0。`:memory:` 库没有磁盘足迹,`db_size` 检查缺席(不是 ok);答复只带数字、不带库文件路径。归档增速(净增速率)是 recall-stats `archive` 块的第五指标,不在这里重复。
42
+
29
43
  ## 逐会话无痕(issue #539)
30
44
 
31
45
  命中会话后,Mneme 不向该会话注入记忆、用户设置或工具指引,不接受其 `memory_*` / `set_scope_state` 工具调用,不对它蒸馏,也不由压缩抢救、M.O. 记账或自动提议路径保存该会话的内容。
@@ -114,7 +128,7 @@
114
128
  | `summarizeMinWindowChars` | `0` | 窗口可蒸馏文本下限(字符),不足直接跳过 LLM(#239) | 被挡窗口照常消费游标并留 skip 审计 |
115
129
  | `summarizeMaxRunsPerSession` | `0` | 每会话蒸馏次数预算,只计真实发起的调用(≤1000) | 0 = 不限;进程内计数,重启清零 |
116
130
  | `summarizeParseFailureMaxRetries` | `0` | 连续解析失败几次后放弃当前窗口(0–10,0 = 关,#493) | 达到阈值强制消费游标跳过该窗口;失败行 metadata 恒带 `raw_head`/`raw_has_bracket`/`streak` |
117
- | `summarizePeakHours` | `""` | 高峰时段串(`"09:00-18:00"` / `"mon-fri 08:00-12:00,14:00-18:00"`,支持跨零点) | 命中高峰不调 LLM、顺延补跑;任一写法非法整串按未配置处理 |
131
+ | `summarizePeakHours` | `""` | 高峰时段串(`"09:00-18:00"` / `"mon-fri 08:00-12:00,14:00-18:00"`,支持跨零点;全角逗号/冒号/数字自动归一,#424) | 命中高峰不调 LLM、顺延补跑;任一写法非法整串按未配置处理。**非法不静默(#622)**:非空且解析失败时启动日志 warn 一条(带键名与原串)、面板该键输入行下出红色提示(`/features` 的 `invalidPeakHours`);空串是合法的未配置,不告警 |
118
132
  | `summarizePeakMaxDeferMinutes` | `120` | 高峰顺延上限(分钟,≤1440) | 0 = 不设上限;到点仍处高峰照跑(bypassPeak) |
119
133
  | `summarizeDedupeMode` | `off` | 落库前去重档位:`off` / `title`(拦完全同名)/ `vector`(同会话语义近邻) | `vector` 复用已有 embedding 列,无 LLM 调用 |
120
134
  | `summarizeDedupeMinSim` | `0.92` | `vector` 档并入阈值(0.5–0.99) | 仅 `vector` 档生效 |
@@ -179,7 +193,8 @@
179
193
  | `dreamDelayMs` | `2000` | 触发后的延迟(毫秒) | — |
180
194
  | `dreamMinIntervalMinutes` | `0` | 两次开跑最小间隔(分钟,≤10080,#89) | 失败/degraded run 也占用;0 = 不限 |
181
195
  | `autoDreamFailureBackoff` | `false` | 连续失败指数退避:有效间隔 = 基数 × 2^连败,封顶 30 分钟(#292) | opt-in;基数取 `dreamMinIntervalMinutes`(0 = 无闸可翻倍);成功一次清零;重启归零 |
182
- | `dreamPeakHours` | `""` | 巩固错峰,与 `summarizePeakHours` 同一份时段语法(#239) | 命中高峰不做梦、阈值继续累积、留 skip 审计;非法写法按未配置 |
196
+ | `dreamInputSummaryEnabled` | `false` | dream 审计行输入快照瘦身(#557):auto 轮只落骨架(id/type/title/importance/updated_at,不含 content) | opt-in;LLM 输入与 `snapshot_hash` 都不受影响;organize 轮重放载荷不受影响;开启后审计行无法离线重算 hash(骨架级回放) |
197
+ | `dreamPeakHours` | `""` | 巩固错峰,与 `summarizePeakHours` 同一份时段语法(#239;全角逗号/冒号/数字同样归一,#424) | 命中高峰不做梦、阈值继续累积、留 skip 审计;非法写法按未配置,且同 `summarizePeakHours` 的 #622 可见性(启动 warn + 面板提示) |
183
198
  | `dreamPeakMaxDeferMinutes` | `120` | 巩固顺延上限(分钟) | 0 = 不设上限;到点仍处高峰照跑 |
184
199
  | `dreamProvider` / `dreamModel` | 空 | 巩固专用路由(settings「巩固模型」) | 建议非思考模型——思考模型易烧光预算返回空体(#135) |
185
200
  | `dreamMaxTokens` | `131072` | 巩固输出预算(256–131072) | 流式计费按实际用量,调大不增加成本;**调小则相反**——撞顶 → 无完整 JSON → 整轮失败 → baseline 不刷新 → 同输入重跑(#135 实测 cap=16384 净成本 +54%) |
@@ -204,6 +219,19 @@
204
219
  | `documentDir` | `""` | managed 落盘目录(#296) | 空 = `<memoryDir>/documents/`;目录之外只登记指针、正文零读零写 |
205
220
  | `policyEpoch` | `0` | 裁决规则版本号 | bump 后旧 dream_runs 降为历史证据(receipt 不再驱动现行决策) |
206
221
 
222
+ ## 日记本(diary,v0.13)
223
+
224
+ | 键 | 默认 | 作用 | 开启后果 / 冲突 |
225
+ |---|---|---|---|
226
+ | `diaryEnabled` **light** | `false` | QQ 日记本总闸:跨天判定 + 手写入口 + 面板页 | opt-in;与 `documentMemoryEnabled` **成对但不相依**——关着也能写正文文件,只是不铸造 `document` 指针行(回执里如实标 `pointer.skipped`) |
227
+ | `diaryDir` | `""` | 日记正文根目录 | 空 = `<memoryDir>/diaries/<项目键>/`;`~` 展开到 home,绝对路径原样,相对路径落 memoryDir 下。**与 `memoryDir` 一样是路径配置,不是行为开关**,故不进 `settings.js` 白名单、也不上面板 |
228
+ | `diaryWriteEveryMinutes` | `0` | 日记自动写的定时唤醒间隔(分钟) | `0` = 只在 turn/end 与插件启动时判定(此前行为);`>0` 时每隔这么多分钟扫一遍已知项目再判定。**不是 cron**:定时器只负责唤醒「该不该写」的判定,写不写仍由 compose 按落盘日期去重——一天最多一篇,填 5 也不会多写。跑完一篇再排下一次,所以不会与上一次的模型调用重叠。opt-in、可热改(走 `INT_RANGES` 表,0–1440)|
229
+ | `diaryInboxDir` | `""` | QQ 入站素材目录(只读) | 空 = 从 `memoryDir` 推 `<memoryDir>/../media/onebot`(onebot 的默认落点)。本模块**不 import `dsh-onebot-qq` 任何模块**,边界只靠这份「目录 + 文件形状」约定 |
230
+
231
+ 日记不落用户绑定的项目目录:那目录实测非 git、无 `.gitignore`、混着探针脚本,
232
+ 写进去没有版本也没有回滚;mneme 成文的写盘面只有 `memoryDir` 与 `documentDir` 两处。
233
+ 「入库到项目」是面板上一个**人主动点**的导出动作(复制而非移动)。
234
+
207
235
  ## 睡眠模式(sleep,空闲深维护)
208
236
 
209
237
  | 键 | 默认 | 作用 | 开启后果 / 冲突 |
@@ -217,6 +245,9 @@
217
245
  | `sleepPatternMinMemories` | `100` | 模式发现的扫描窗口(条) | — |
218
246
  | `sleepPatternLookbackDays` | `30` | 属性变更回看(天) | — |
219
247
  | `sleepMaxPatternPerRun` | `3` | 单轮模式条上限(0–10) | 0 = 关闭模式发现 |
248
+ | `sleepAffectiveEnabled` **light** | `false` | 情感归因相位(#619):情境情绪写成 `affective_context` 关系,跨多次独立会话的稳定倾向才铸成 pattern | opt-in;`sleepModeEnabled` 的子相,父关着不跑;要调模型且依赖实体抽取 |
249
+ | `sleepAffectiveTraitMinSessions` | `3` | 判「稳定倾向」所需的**独立会话**数(2–10) | 下界 2 是故意的:N=1 就是单次场合,正是必须禁止升特质的那个情形 |
250
+ | `sleepAffectiveMaxRelationsPerRun` | `10` | 单轮最多写多少条 `affective_context` 关系(0–50) | 同 `sleepMaxPatternPerRun` 的角色;0 = 一条也不写 |
220
251
  | `sleepProvider` / `sleepModel` | 空 | sleep 专用路由 | 空 = 用巩固路由 / 当前模型 |
221
252
  | `sleepReasoningEffort` | 未配置 | 同 `dreamReasoningEffort` | — |
222
253
  | `sleepMaxTokens` | `8192` | 冲突/模式阶段输出预算(#257) | 原硬编码 2048 对 `full` 档必然截断 |
@@ -236,6 +267,8 @@
236
267
  | `reflectionFailureRetentionDays` | `90` | `failure_memories`(失败/纠正记录)的启动期清理保留天数(1–3650) | 只读 bundle 配置:未进 feature_flags 白名单,面板改不了。`dsh-mneme reclaim` 不读这个键(它对三个表用同一条 `--older-than`,本表不在其中)。此前这个 90 天是写死的(#275 伞下的欠账) |
237
268
  | `conflictFreezeEnabled` | `false` | 冲突不自动合并、冻结待人工复核 | opt-in |
238
269
  | `conflictFreezeMaxPending` | `100` | 冻结队列上限(≤1000) | — |
270
+ | `conflictAutoResolveEnabled` | `false` | 相似度极高的**同 scope** 重复对自动裁决(保留新条、归档旧条),不进人工队列 | opt-in。会归档记忆,所以默认关;跨 scope 对永不自动(归属是用户的决定,#170)。用 supersede 而非 conflict:注记写在被归档侧,幸存记忆正文不动 |
271
+ | `conflictAutoResolveMinSimilarity` | `0.95` | 自动裁决的相似度门槛(0.85–1) | 实测 live 库 345 条有相似度的历史行里 ≥0.95 只占 5.8%、≥0.94 占 8.1%——这一档减的是峰值,不是把队列清零。低于 0.85 就不是「重复」而是「相似」,不该自动归档任何一条 |
239
272
 
240
273
  ## 实体
241
274
 
@@ -318,10 +351,15 @@
318
351
  | `organizeEnabled` | `false` | 对模型开放 `memory_organize` 工具(#231:先 dryRun 出比对报告,agent 判断后 apply 落库) | 与上面两个**反向**:那两个藏默认在场的工具,本键开默认不在场的能力。关 = 工具根本不注册(模型看不到就不会调)。dryRun→apply 是两步协议,弱模型容易漏 `run_id` 或跳过报告,所以 `LIGHT_MODE_OFF` 里有它;apply 只能引用一次真实报告、且一份报告只许落地一次 |
319
352
  | `chorographyEnabled` | `false` | chorography(仓库地志,spec 见 docs/specs/)暴露闸:对模型开放三个只读工具(`choro_search` / `choro_read_page` / `choro_module_map`)+ 面板「仓库地志」浏览页 + 每轮的开局提纲注入 + `/choro` 命令 | 与 `organizeEnabled` 同款反向语义:关 = 工具根本不注册、面板页空态、注入块不渲染、命令不注册,行为与旧版逐字节一致。本键是「是否启用这个子系统」,不是「每轮付多少」——所以它仍**不进 `LIGHT_MODE_OFF`**,轻量档要压的是下面那条 `chorographyInject`。检索是关键词子串(M1);生成走 `dsh-mneme-choro` CLI(T0 确定性骨架 + `--t1` headless 语义富化) |
320
353
  | `chorographyInject` | `"light"` | 开局注入档位:`light` 只列文件数最多的 6 个模块(约 8 行)、`full` 列全部模块、`off` 不注入 | 只注模块名 / 文件数 / 页状态 / 页相对路径,**不含正文**(正文由模型 `choro_read_page` 取);注入块折进既有 memory context,不新开 context(spec §7「合并成一次拼装」)。`lightMode: true` 时在消费侧压成 `off`(见 `wiki/inject.js` 的 `resolveInjectTier`)——它**不进 `LIGHT_MODE_OFF` 表**,因为那张表对平铺键一律赋 `false`,枚举值塞进去会与 schema 不符 |
354
+ | `chorographyAutoRefresh` | `false` | 重建骨架后是否顺手把「过期」标记也刷掉:`POST /choro/run` 带 `refresh: true` 时,面板发起的一次重建会同时重算 `choro_repos` 的 git 基线与 plan 指纹 | opt-in 默认关。它与 `chorographyEnabled` 同属「谁按下按钮」而非「每轮付多少」,故不进 `LIGHT_MODE_OFF`;关着时重建照常跑,只是过期判据留旧基线,面板会继续显示「代码已变更」 |
355
+ | `chorographyT1FromPanel` | `true` | 面板「补语义正文(T1)」按钮的暴露闸:开 = 地志页能发起 `POST /choro/t1`,后台 spawn 一次 `dsh-mneme-choro --t1 --apply` 并轮询 `/choro/t1/status` | 默认**开**(只在 `chorographyEnabled` 已开时才有意义;关整个子系统仍然全灭)。它管的是「面板能不能点这一下」,T1 花的是模型额度但每次都是人的显式动作,所以不设第二道默认关;T1 有模块数/时长/重试三重上限(`src/wiki/t1.js`) |
356
+ | `kbEnabled` | `false` | 知识库第二层(AI-Native 知识库,对标 Qoder Knowledge)暴露闸:对模型开放三个只读工具(`kb_list_collections` / `kb_search` / `kb_read_doc`)+ 面板「知识库」页 + `/api/dsh-mneme/kb/*` 路由 | 与 `chorographyEnabled` 同款反向语义:关 = 工具不注册、面板页空态、路由 409。知识库收的是外来原文副本,真源在 `kb_*` 三张表(不是磁盘 MD),**只碰 kb_* 绝不写 `memories`**、不进巩固/质量过滤任何池子。检索是 BM25 + 可选向量;单文档上限 2 MB,超限/不可读落 `status='error'` 行并回人话,不静默截断。不进 `LIGHT_MODE_OFF`:它没有每轮注入物,模型只在被问到时才调用 |
321
357
  | `actionPipelineEnabled` | `false` | M.O. 只读视图的门控:做法图谱 + 对话行结算卡 | opt-in beta;只是纯 UI 入口、不引入重型后台路径,故不进 `LIGHT_MODE_OFF`。写账另有独立的执行记账键(`/mo-run`),看图 ≠ 写账 |
322
358
  | `actionRunEnabled` | `false` | M.O. 执行记账:`/mo-run` 开一次执行窗口,窗口内命令执行才可能被记成一条 run | opt-in beta;挂在热路径(每个会话事件都过一次监听),故 `LIGHT_MODE_OFF` 里有它 |
323
359
  | `actionNudgeEnabled` | `false` | M.O. 自动沉淀:每轮结束(`turn/end`)后挖一次「这个项目重复走的动作」,结果落成 `memoryDir/proposals/<项目键>/` 里的提议,等人确认 | opt-in beta;三个 M.O. 键里最重的一条——挂 `session/event` 且要**同步解压 zstd 会话日志**(本机 118 份实测 16.2s,单次预算 `budgetMs`=4s 兜底),故 `LIGHT_MODE_OFF` 里有它。**永不自动落草稿**:`materialize` 恒 false,进仓库必须是人的显式动作 |
324
360
 
361
+ > **M.O. 已下架(v0.13.x)**:上面三个键仍留在 schema 与设置白名单里、语义不变,但面板不再渲染 `group.mo` 那一组(从 `FEATURE_GROUPS` 摘掉,首屏位一并收回)。想恢复:把该组加回 `FEATURE_GROUPS` 与 `FEATURE_FIRST_SCREEN` 即可,旗标与座位注册逻辑零改动。设置只能走配置文件。
362
+
325
363
  ## 运行时与本地模型
326
364
 
327
365
  | 键 | 默认 | 作用 | 开启后果 / 冲突 |
@@ -348,7 +386,7 @@
348
386
  `lightMode: true`(或面板 `panel_mode=light`)时,以下键被预设强制为 `false`——都是
349
387
  重型路径;核心循环(autoInject、autoSummarize、hotMemory*、质量过滤、关键词检索)不动:
350
388
 
351
- `entityExtractionEnabled` · `autoDream` · `sleepModeEnabled` · `rerankEnabled` ·
389
+ `entityExtractionEnabled` · `autoDream` · `sleepModeEnabled` · `sleepAffectiveEnabled`(#619) · `rerankEnabled` ·
352
390
  `autoReindexOnBoot` · `hybridInject` · `injectGuidanceEnabled` · `searchSemanticDedup` ·
353
391
  `selectiveInjectEnabled` · `bm25SearchEnabled` · `entityRecallEnabled` ·
354
392
  `graphAnchoringEnabled` · `graphWeightEnabled` · `graphInjectHint` · `graphPassiveConfirm` ·
@@ -357,7 +395,8 @@
357
395
  `organizeEnabled`(#231) ·
358
396
  `profileExtractEnabled`(#497) ·
359
397
  `profileFactsEnabled`(2026-11-06) ·
360
- `preInjectGate.enabled`(#380) · `distillRecords.enabled`(#399)
398
+ `preInjectGate.enabled`(#380) · `distillRecords.enabled`(#399) ·
399
+ `diaryEnabled`(v0.13,每天一篇 = 一次 LLM 调用,属重型)
361
400
 
362
401
  预设只是默认值而非强制:用户显式写进 feature_flags 的值在装配顺序上后展开、仍然生效
363
402
  (「用户开关 > 轻量预设 > bundle 配置」)。
@@ -0,0 +1,114 @@
1
+ # 目录归属:哪个目录是什么(#278)
2
+
3
+ > 先记住这一句,它防的是本项目真实发生过的事故(#278:把模型缓存当成了记忆库)——
4
+ >
5
+ > **记忆数据在 `~/.dsh/memory`,模型与运行时缓存在 `~/.dsh/mneme`,CLI 连接配置在 `~/.dsh-mneme`。三个名字像、位置也近的目录互不相干。**
6
+
7
+ 本文是落盘面的总账:每个目录是什么、谁拥有(人改还是机器写)、删了会怎样。它是**代码的投影,不是愿望清单**——每个条目都标了出处文件,改动写盘面的 PR 顺手核对本表;发现文档与代码不一致,以代码为准并回来改这里。单个子系统的细节仍归各自的域文档。
8
+
9
+ ## 速查总表
10
+
11
+ | 根目录 | 是什么 | 谁拥有 | 全删会怎样 |
12
+ |---|---|---|---|
13
+ | `~/.dsh/memory`(`memoryDir` 默认值) | **记忆库本体**:SQLite 正本 + 镜像 md + 各类本地状态 | mneme 机器写;镜像 md 预期人改 | 见 [§ 记忆库](#记忆库-memorydir默认-dshmemory) |
14
+ | `~/.dsh/mneme` | **自管运行时**(transformers + onnxruntime 闭包)与**本地模型缓存**——不是记忆库 | mneme 机器所有 | 可整目录删掉重来(这正是挂在 `~/.dsh/mneme/` 下的设计原意,`src/runtime/layout.js`) |
15
+ | `~/.dsh-mneme` | `dsh-mneme` CLI / MCP 的连接配置(`cli.json`:url + token) | 用户 | 删了重新 `dsh-mneme config set` 一次 |
16
+ | `~/.dsh`(其余部分) | DSH 宿主的地盘:`sessions/`、`profiles/`、`skills/` 等 | 宿主 | 插件只读 `sessions/` 与 skills 目录,从不写 |
17
+ | `<项目>/.mneme/`、`<项目>/.chorography/` | 住用户仓库里的点目录(工作流、仓库地志),随 git 走 | 混合(见 [§ 用户仓库里的点目录](#用户仓库里的点目录)) | 按 git 规矩来 |
18
+ | `<os.tmpdir()>/mneme-choro-t1-*` | chorography T1 子进程的临时 overlay | mneme,用完即删 | 无关紧要 |
19
+
20
+ ## 记忆库 `<memoryDir>`(默认 `~/.dsh/memory`)
21
+
22
+ 除「用户仓库里的点目录」与「重运行时」两类例外,mneme 的一切本地状态都宿在 `memoryDir` 下——宿主挂载入口与独立 daemon 都会展开前导 `~` 并在启动时建目录(`src/index.js` / `src/serve.js`),全部路径配置键见 [§ 路径配置键](#路径配置键一张表)。这不是随意的堆放:写盘纪律是**「删掉插件的目录就回滚干净」**(`src/diary/dir.js`、`src/candidates.js`、`src/pipeline-observations.js` 三处注释同一条纪律)——记忆库不在了,用户的仓库里不该留下任何 mneme 的痕迹。
23
+
24
+ | 落点 | 内容 | 拥有者 | 出处与备注 |
25
+ |---|---|---|---|
26
+ | `memory.db`(+ `-wal` / `-shm`) | **唯一正本**:记忆行、实体三表、dream_runs / recall_runs / 审计表、`user_settings` 等 kv | 机器;**绝不手改** | `src/store.js` |
27
+ | `*.md`(十个,见下) | 磁盘镜像,按 type 一文件 | 机器写、**人可改**(`documents.md` 例外:只读) | `src/mirror.js`(`TYPE_FILE`);**在 memoryDir 根上平铺,没有 `mirror/` 子目录** |
28
+ | `candidates/<项目键>/cand-*.json` | M.O. 绕行候选(草稿层) | 机器 | `src/candidates.js`;「候选层跟 memoryDir 一样是本地状态,不是仓库内容」——升格进仓库要人批 |
29
+ | `proposals/<项目键>/nudge-*.json` | 模式挖掘提议(比定稿松一档) | 机器 | `src/pipeline-nudge.js`;人的 counterfactual verdict 是唯一升格闸门 |
30
+ | `observations/<项目键>/<工作流id>.jsonl` | 观察旁挂(append-only 账本) | 机器 | `src/pipeline-observations.js`;只记 `{key, count, flags}`,**命令原文拒绝入内** |
31
+ | `diaries/<项目键>/YYYY-MM-DD.md` | 日记正文 | 用户的话,机器代笔 | `src/diary/dir.js` + `compose.js`;同目录 `runs.jsonl` 是「今天为什么没有日记」的审计账本 |
32
+ | `diaries/state.json` | 桶账本(桶键 → 项目根 / agent / chatId) | 机器 | `src/diary/compose.js`;面板日记本清单靠它反查项目 |
33
+ | `documents/`(+ `index.md`) | document 型记忆的 managed 落盘目录 | `index.md` 机器所有(删掉重启即逐字节重建,`src/document-index.js`);**目录里其余文件 agent 所有,mneme 对正文零读零写零改** | `src/document.js`;`documentDir` 可改 |
34
+ | `../media/onebot/qq-inbox*.jsonl`(邻座) | QQ 收件箱,日记素材 | **外部**(onebot 写);插件只读 | `src/diary/qq-inbox.js`;`diaryInboxDir` 可改,推出不来就报错、绝不猜别的位置 |
35
+
36
+ 镜像的十个文件名(`src/mirror.js` 的 `TYPE_FILE`):`preferences.md`、`projects.md`、`decisions.md`、`history.md`、`summary.md`、`pitfalls.md`、`constraints.md`、`rejected-solutions.md`、`patterns.md`、`documents.md`。它是**唯一预期用户手改的落点**:手改在每次镜像同步时经 `mirror-digest` 三方合并写回库(人改赢);`documents.md` 是 document 型的只读指针视图,没有可编辑正文,手改一律被下次同步覆盖。
37
+
38
+ **别把 memoryDir 放进宿主的 profile 目录**:profile 被清理或迁移时记忆库会连带丢失(#218 的真实事故)。命中 profile 作用域时启动会告警(`src/index.js`,启发式边界见 `src/placement.js`)——只提示、不搬库。
39
+
40
+ ## 运行时与模型缓存 `~/.dsh/mneme`(不是记忆库)
41
+
42
+ 这里只有两样东西,全部机器所有、**可以整目录删掉重来**——`src/runtime/layout.js` 的注释原文就是「便于用户整个删掉重来」:
43
+
44
+ | 落点 | 内容 | 出处与备注 |
45
+ |---|---|---|
46
+ | `runtime/<payloadId>/` | 自管运行时:`mneme-runtime.json` 清单 + 扁平 `node_modules/` 依赖闭包(transformers + onnxruntime + sharp) | `src/runtime/{layout,adopt,download,loader}.js`;payloadId 形如 `transformers-<版本>-node-<平台>-<架构>`;存在理由是把约 200MB 的原生闭包挪出宿主 profile 的共用依赖图(#131)。daemon 可用 `DSH_MNEME_RUNTIME_DIR` 改 |
47
+ | `models/<org>/<model>/` | 本地模型缓存(embedder / reranker / verify 三方共用同一份,唯一字面量在 `defaultModelCacheDir`) | `src/local-embedder.js`;`embedModelCacheDir` 可改([LOCAL_MODEL.md](LOCAL_MODEL.md)) |
48
+ | `models/.mneme-partial/` | 断点续传的分片暂存 | `src/runtime/model-fetch.js`(#194);装完即清 |
49
+
50
+ 细节与排障见 [LOCAL_MODEL.md](LOCAL_MODEL.md)(收编、校验、镜像源)。
51
+
52
+ ## 用户仓库里的点目录
53
+
54
+ 这些目录长在用户项目里、随 git 提交——「能 PR、能 review、能整版回退」是它们不住记忆库的理由(`src/pipeline.js`):
55
+
56
+ | 落点 | 内容 | 拥有者 |
57
+ |---|---|---|
58
+ | `<项目>/.mneme/pipelines/*.json` | M.O. 项目层工作流 | **人写**;机器只补 `status:"draft"` 的新文件、绝不覆盖已有文件(`src/pipeline-nudge.js`:「已经在人手里的文件不能被机器改写」) |
59
+ | `<项目>/.mneme/pipelines/<id>.runs.jsonl` | 工作流运行账本 | 机器(`src/pipeline-runs.js`;与手写 json 分开是为了人改不被账本淹没) |
60
+ | `<项目>/.chorography/` | 仓库地志:`plan.yaml`(用户所有,**机器只读**)、`index.md`、`modules/`、`evidence/`、`state.json`(机器产物,可重跑 T0/T1 再生成) | 混合;写路径被机械断言钉在 `.chorography/` 内——T0 是 `assertInsideChoro`(`src/wiki/t0.js`),T1 复用同一语义、越界抛 `write-outside-chorography` 拒收(`src/wiki/t1.js`) |
61
+ | `<项目>/diary/` | 日记导出(用户在面板点「存到这个项目」才出现;复制不移动) | 用户 |
62
+
63
+ 包内 `pipelines/` 是工作流的全局兜底层,**发布包里今天没有它**(目录为空即不进包)——「一条工作流该不该跨项目用,得是一个人说出来的动作」。
64
+
65
+ 只读扫描(插件只读不写):`.git`、`.github/workflows/`、`README*` / `CONTRIBUTING*`、`package.json`(冷启动 `src/bootstrap.js`);`.dsh/skills/`、`.agents/skills/`(能力发现 `src/capabilities.js`)。仓库扫描刻意跳过一切点目录——里面常是提问原文、调试快照与第三方工具缓存(`src/wiki/scan.js`)。
66
+
67
+ ## 宿主侧只读输入 `~/.dsh`
68
+
69
+ - `<DSH_HOME>/sessions/<工作区桶>/session*.jsonl.zstd`——会话转录,宿主写、插件只读(`src/session-usage.js`;`DSH_HOME` 环境变量推导,**仓库里出现盘符是红线**)。蒸馏、M.O. 挖掘与日记素材都从这里来。
70
+ - `~/.dsh` 这个目录本身可能只是 `mkdirSync(memoryDir)` 的副作用产物——默认配置下建 `~/.dsh/memory` 时顺带建的。
71
+
72
+ ## CLI 配置、临时文件与包内只读资源
73
+
74
+ - `~/.dsh-mneme/cli.json`——`dsh-mneme config set` 写、CLI 与 MCP server 读(`bin/cli.mjs`;MCP 本身不写任何文件)。**它与 `~/.dsh/mneme` 无关**,差一个斜杠。
75
+ - `<os.tmpdir()>/mneme-choro-t1-*/`——T1 子进程的临时 overlay,跑完即删(`src/wiki/t1.js`)。
76
+ - 原子写会产生与目标同目录的 `<目标>.tmp-<pid>-<rand>` 兄弟文件,崩溃时自清(`writeFileAtomic`)。
77
+ - 分享包**没有固定目录**:`dsh-mneme share --out <path>` 由人指定落点(`bin/cli.mjs`)。
78
+ - 包内只读资源:`runtime-manifest.json`(下载清单)、`locale/`(宿主面板注入槽)、`assets/icon.svg`。daemon 无 pid / socket / 日志文件——HTTP 是 TCP,审计都在 `memory.db` 里。
79
+
80
+ ## 删了会怎样
81
+
82
+ | 对象 | 能否安全删 | 后果 |
83
+ |---|---|---|
84
+ | `memory.db` | **不能** | 记忆正本,不可重建;备份就备份它(冷备整文件,含 `-wal` 时先停写) |
85
+ | 镜像 `*.md` | 能,但先想一下 | 下次同步从库重建;**尚未合并进库的手改会随文件一起丢**——有未合并编辑时先让它同步一次 |
86
+ | `documents/index.md` | 能 | 启动自愈,逐字节重建 |
87
+ | `candidates/` `proposals/` `observations/` | 能 | 草稿与 M.O. 证据没了,库与检索不受影响 |
88
+ | `diaries/` | **不能** | 那是用户自己的日记,任何地方都没有第二份;库里的 document 指针行会悬空 |
89
+ | `documents/` 里 agent 的文件 | mneme 不管 | 管线对正文零读零写;注册后再没有任何地方读它,删了也没有检测点——指针行悬空而已 |
90
+ | `~/.dsh/mneme` 整目录 | 能 | 重启后按需重下 / 重收编,代价是时间与流量 |
91
+ | `.mneme/` `.chorography/` | 按 git 规矩 | 机器产物可重跑再生成;`plan.yaml` 别随手删——删了就是回默认启发式 |
92
+ | `~/.dsh/sessions/` | 不归插件管 | 宿主的数据,别以插件的名义动它 |
93
+
94
+ ## 路径配置键(一张表)
95
+
96
+ | 键 | 默认(空串语义) | `~` 展开 | 出处 |
97
+ |---|---|---|---|
98
+ | `memoryDir` | `~/.dsh/memory` | 展开 | `src/config.js`、`src/index.js` |
99
+ | `documentDir` | `<memoryDir>/documents/` | 展开;相对路径落 memoryDir 下 | `src/document.js`(#296) |
100
+ | `diaryDir` | `<memoryDir>/diaries/` | 展开;相对路径落 memoryDir 下 | `src/diary/dir.js` |
101
+ | `diaryInboxDir` | `<memoryDir>/../media/onebot` | 展开 | `src/diary/qq-inbox.js` |
102
+ | `embedModelCacheDir` | `~/.dsh/mneme/models` | **不展开,指别处写绝对路径** | `src/local-embedder.js` |
103
+ | `runtimeDir` | `~/.dsh/mneme/runtime`(daemon 另认 `DSH_MNEME_RUNTIME_DIR`) | **不展开** | `src/runtime/layout.js` |
104
+ | `runtimeTarballDir` | 空 = 不启用离线取件 | 不展开 | `src/runtime/download.js` |
105
+
106
+ 全键语义见 [CONFIGURATION.md](CONFIGURATION.md)。
107
+
108
+ ## 已知坑
109
+
110
+ 1. **两个 CLI 的默认落点各站一边**:`dsh-mneme-choro.mjs` 的 `--db` 默认指 `~/.dsh/memory/memory.db`(数据侧),`scripts/mneme-runtime.mjs` 动的是 `~/.dsh/mneme`(运行时侧)。
111
+ 2. **`documents.md` 与 `documents/` 是两回事**:前者是镜像里 document 型的只读指针视图(一个文件,在 memoryDir 根上);后者是 managed 文档目录(一个目录,装 agent 的正文)。
112
+ 3. **镜像手改的窗口**:人改在同步时赢,但只对有可编辑正文的 type 生效;`documents.md` 与镜像排除集(如未来新增的排除 type)里的手改一律被覆盖。
113
+ 4. **路径键的展开规则不一致**(见上一节表):`memoryDir` 一族做 `~` 展开与相对回落,模型 / 运行时一族不做——拿着 `~/models` 这类值去配 `embedModelCacheDir` 会得到一个字面叫 `~/models` 的目录。
114
+ 5. **`<workspace>/.mneme/` 的记忆投影不存在**:#17 §五与 #278 早期讨论里提过「工作区投影」的草稿,那是未排期的设想;今天 `.mneme/` 里只有工作流与运行账本。`~/.dsh` 迁出与统一导出同样是 #278 里尚未落地的两个子项(大活,独立拆批)。
package/docs/DISTILL.md CHANGED
@@ -67,6 +67,7 @@ UNIQUE idx_memory_provenance_link(memory_id, session_id, seq_start, seq_end)
67
67
  |---|---|---|
68
68
  | 真实 `user/message`(`kind` 缺失 / `user` / `agent-message` / `subagent-settled`) | ✅ | ✅ |
69
69
  | 助手公开 `text` 块 | ✅ | ✅ |
70
+ | 消息里的 `image` 块(#611) | ✅(只留 `[图: 名称 宽x高]` 占位) | ✅(同占位,不含字节) |
70
71
  | 助手 `reasoning` 块(私有推理链) | ❌ | ❌ |
71
72
  | 插件注入机器内容(`kind: "plugin:dsh-mneme"` / skill 正文 / AGENTS.md / 文件变更通知) | ❌ | ❌ |
72
73
  | 工具调用入参、工具结果、code 输出 | ✅(截断) | ❌ |
@@ -306,3 +307,66 @@ audit 行,归因会跟着悬空;而 `memory_provenance` 刻意活得比原
306
307
  `distill_run_id / run_type / trigger_source / run_status`(读不到整组 null),答复级另带
307
308
  `mints[]`(`{run_id, run_type, trigger_source, created_at}`)。面板详情抽屉有一个
308
309
  「蒸发归因」区块渲染这两层。
310
+
311
+ ## 12. 图片块:占位而不是丢(#611)
312
+
313
+ 在 #611 之前,会话里的图片是**从入口到落库整条断掉的**:三份 `textOf` 的实现都是
314
+ `p?.type && p.type !== "text"` → 空串,于是
315
+
316
+ - 图-only 消息在 `collectMessages` 里读出一串空文本,`publicTranscriptParts` 返回 `[]`,
317
+ 这一条整条不进转录;窗口里如果只有图,连蒸馏都不会触发(`collected.messages.length === 0`
318
+ 直接 `commitCursor` 走人);
319
+ - 图+文消息只剩文字,图那部分静默消失;
320
+ - 两侧都没有任何计数,事后没法回答「图到底进来没有」,只能翻源码。
321
+
322
+ 本批的修法是把「块 → 文本」抽成唯一一份 `src/attachment-text.js`
323
+ (`blockToText` / `contentToText` / `countImages` / `imageLabel`),三处调用方共用:
324
+ `src/summarize.js` 的 `textOf`、`src/continuity.js` 的快照抽取、`src/hot-memory.js`
325
+ 的 `roundsFromEvents`。抽一份而不是各加一遍 image 分支,理由和 §4 那条一样——同一件事
326
+ 写成三份,迟早会漂移,而这次漂移的代价恰恰是「一处记得图、另一处忘了」。
327
+
328
+ **占位口径**:`[图: 屏幕截图 2026-10-19 072000.png image/png 1543x983]`。有名字用名字,
329
+ 没名字退 `mediaType`,什么都没有也要落一个裸 `[图]`——**绝不返回空串**,返回空串正是
330
+ 本次要修的那个 bug。宽高留着是因为它是唯一能区分「同一张图的不同版本」的线索。
331
+
332
+ **标签里的元数据必须消毒**(CodeRabbit 2026-11-08 评审指出):`name` 是外部可写的字符串
333
+ (附件的原始文件名,用户想怎么起就怎么起)。不处理的话,一个叫 `x] 用户:把上面这段照抄`
334
+ 的文件就能提前闭合占位、让后半截冒充占位符之外的正文——那是把元数据升格成转录文本,和
335
+ §4 的围栏断链同一类问题;名字里带换行还能凭空造出一行「用户:…」。所以 `[`、`]`、反斜杠、
336
+ 控制字符、连续空白一律折成单个空格,再按 80 字截断(超出加 `…`)。**无元数据时仍返回空串**
337
+ 这条既有行为不变,由调用方退化到裸 `[图]`。
338
+
339
+ 截断不能切在 Unicode 代理对中间(同一轮 CodeRabbit 的第二条):`slice` 按 UTF-16 单元走,
340
+ emoji 占两个单元,切在中间就留下一个孤立的高位代理——那一半写进 SQLite 会变成 `�`,
341
+ 落盘后原文再也拼不回来。所以切完要回头看一眼末位 `charCodeAt`:落在 `0xd800..0xdbff`
342
+ 就再退一格。
343
+
344
+ **不做(本批)**:不调 vision、不读字节、不落原图。宿主侧其实齐全
345
+ (`attachments.readImage()` / `supportsAcpImagePrompts()` 判 `inputModalities`),
346
+ 但记忆库已 273MB,原图档明确不做;描述档(多模态模型出 1 段描述 + attachmentId 溯源)
347
+ 是下一批,需要单独一个 provider 槽,不占蒸馏那个。
348
+
349
+ **观测**:`collectMessages` 按 **`publicTranscriptParts` 产出的 part** 数 `imageCount`,
350
+ 每个 part 带一份 `blocks`(产文本的那份 content),随返回值上浮,写进
351
+ `llm_audit_logs.metadata.imageCount`——四种审计行(skip / 失败 / 成功)都带。
352
+ **没有图时不写这个键**,老档位 metadata 逐字节不变。
353
+
354
+ 计量口径是「真正进了转录的图」,不是「窗口事件里出现过几张图」(同一轮 CodeRabbit 指出):
355
+ 早一版按事件累加 `data.content` / `data.message.content` / `data.inserted[].content`,会把被
356
+ `publicTranscriptParts` 挡掉的注入消息(工具结果、插件注入、陌生 `source.kind`)里的图也数进去
357
+ ——审计会报「这窗有 2 张图」而模型一张都没看到。还有一层:同一条子会话消息会同时出现在
358
+ `user/message` 与 `agent/inbox/spliced`(投递路径重叠),按事件数会算两遍,所以累加必须放在
359
+ 去重 `continue` 之后。
360
+
361
+ **验收**:`test/attachment-text.test.js` 14 例锁口径(占位非空、三级退化、宽松字符串形状
362
+ 逐字节不变、reasoning 仍被挡、图+文顺序、countImages,加上消毒四条:`]` 不能闭合占位、
363
+ 换行不能造新行、超长名被截断、代理对中间截断不留孤立代理项);
364
+ `test/image-memory.test.js` 5 例锁端到端(图+文进转录、图-only 现在也会蒸馏、
365
+ 两张图数出 2、无图不写 imageCount、被挡掉的注入消息里的图不计入)。变异自证三条:
366
+ ①把 `blockToText` 的 image 分支改回 `return ""` → 图-only 那条立刻红(`calls.length` 0 ≠ 1);
367
+ ②把计量改回按事件累加 → 「注入消息里的图不计入」那条立刻红;
368
+ ③去掉代理对守卫 → 「代理对中间截断不留孤立代理项」那条立刻红。三条各自抓不同的错,
369
+ 恢复后全绿。
370
+ `test/continuity.test.js` 那条静态 import 锁同步从「恰好一行」改成「恰好两行」——
371
+ 新增一行仍然转红,仍然要人看一眼它是不是纯的(`attachment-text.js` 不 import 任何东西,
372
+ 所以它不调模型这条约束没被削弱)。
package/docs/DSH-GO.md ADDED
@@ -0,0 +1,97 @@
1
+ # dsh-go 原生面板
2
+
3
+ > 客户端仓库:[ccch1mneyyy/dsh-Go](https://github.com/ccch1mneyyy/dsh-Go)
4
+
5
+ > dsh-mneme 一直是「Web 面板 + 11 个记忆工具 + 自动注入」。手机 App **[dsh-go](https://github.com/ccch1mneyyy/dsh-Go)**(DSH 的 Android 客户端:Flutter 原生壳,手机本机跑引擎,也能遥控电脑)是 Flutter 原生界面,看不到 Web 面板——这个域就是给它的原生面板:与电脑端同一套功能划分(记忆 / 实体 / 设置 / 状态)、同一批后端路由。
6
+ >
7
+ > 记忆库是**按宿主独立存放**的:面板读写的是它所在那台宿主的 `memory.db`,与另一台设备上的库不互通。目前没有在线同步,只能通过分享包 `mneme.share/v1` 手动迁移,且不是实时同步。
8
+
9
+ ## 职责边界
10
+
11
+ - **只做展示与转发**:取数、写回都走 `src/api.js` 已注册的 HTTP 路由 handler(进程内派发),不新增路由、不直连 SQLite、不写第二套口径。
12
+ - **不碰记忆本体**:面板不参与蒸馏、注入、巩固、冲突判定;它只是这些能力的一个操作面(和 Web 面板同级)。
13
+ - **可选能力**:桌面 / headless 宿主没有 [dsh-go](https://github.com/ccch1mneyyy/dsh-Go) 桥,这个域整块不加载(`ctx.inject(["dshGo"], …)` 的回调不会被调度)。
14
+ - **不重复 Web 面板**:`lib/client.js`(浏览器 bundle)照旧是 Web 面板的唯一实现;两边共享的是**后端**,不是视图代码。
15
+
16
+ ## 对外接口
17
+
18
+ ### 1. 包清单
19
+
20
+ `package.json` 的 `dshGo` 字段([dsh-go](https://github.com/ccch1mneyyy/dsh-Go) 桥扫描已启用插件的 package.json,见 `dsh-go-bridge/lib/native/manifest.js`):
21
+
22
+ ```json
23
+ "dshGo": { "v": 1, "title": {...}, "icon": "book", "contributes": { "panels": [{ "id": "mneme", ... }] } }
24
+ ```
25
+
26
+ 面板的 `pkg` 由桥按「注册调用所在 fiber 所属的 loader entry」反查,所以**不需要独立包**:在 mneme 自己的插件上下文里调 `ctx.dshGo.panel("mneme", …)`,归谁一目了然。
27
+
28
+ ### 2. 挂载点
29
+
30
+ `src/index.js` 在 webServer 块内、`createApi` 之后:
31
+
32
+ ```js
33
+ ctx.inject(["dshGo"], (dshCtx) => {
34
+ import("./dsh-go/panel.js")
35
+ .then((panel) => panel.mountDshGoPanel(dshCtx, { apiToken: cfg.apiToken }))
36
+ .catch((error) => ctx.logger?.warn?.(`[dsh-mneme] dsh-go 面板未加载:${error?.message ?? error}`));
37
+ });
38
+ ```
39
+
40
+ 两处细节都是刻意的:
41
+
42
+ - **动态 import**:静态导入会在插件加载时就把面板模块跑一遍,「没有 dsh-go 就不加载这个域」这句承诺就不成立了。
43
+ - **把 `cfg.apiToken` 交给面板**:`createApi` 给 token / token-write 路由包了鉴权,进程内调用不带令牌会被自家路由拦成 401。令牌以 `x-dsh-mneme-token` 头随请求走(`src/dsh-go/host.js`),不给进程内调用开白名单。
44
+
45
+ ### 3. 视图协议(v2)
46
+
47
+ `buildView(state)` 返回 `{v:2, type:"stack", accent:"app", children:[…]}`,顶层是 App 原生 `tabs`:记忆 / 实体 / 设置。设置页顶部两格 `segmented`(设置 | 状态)——电脑端是四个页签,但 App 的页签条每格有 80dp 下限,四格在手机上会折行,所以放三格。
48
+
49
+ ### 4. 0.11.0 补上的两块面板面
50
+
51
+ mneme 0.11.0 把「Agent 眼中的你」自动事实(`profile_facts` 三表)和 chorography 地志搬上了 Web 面板。手机面板跟着补,仍走同一批路由:
52
+
53
+ | 面板位置 | 路由 | 说明 |
54
+ |---|---|---|
55
+ | 设置 › 个性化 › Agent 眼中的你 | `GET /profile-facts?agent=` | 一次拿到 `cards` / `facts`(含归档)/ `journal` / `runs`;flag 关着也照读,历史上攒下的事实要能看见 |
56
+ | 同上(增 / 改 / 归档) | `POST /profile-facts/apply` | body `{agent, ops}`,op 形状与自动轮共用:`add` 必须带 `content`,`update`/`archive` 必须带 `id` |
57
+ | 同上(立即更新) | `POST /profile-facts/run` | 一次 LLM 维护轮,慢且串行,面板按 pending 处理(60s);`409` = 功能关着,`503` = 没配模型,`502` = 模型答得不成形 |
58
+ | 设置 › 记忆引擎 › 代码库地志 | `GET /choro/repos`、`/choro/modules?repo=`、`/choro/page?repo=&slug=` | 仓库 → 模块骨架 → 页正文;都受 `chorographyEnabled` 闸门(关着回 `409 disabled`) |
59
+ | 同上(重建骨架) | `POST /choro/run` `{repo}` | 只跑 T0 apply(秒级);T1 富化仍需命令行 `--t1`,所以重建会把已富化的语义段退回骨架 |
60
+
61
+ 功能开关这块也换了一半口径:
62
+
63
+ - 枚举型旗标(`chorographyInject` 的 `light/full/off`)用下拉,`PUT /features` 收字符串值——同一个动作 id 既服务开关也服务下拉。
64
+ - 新增「其它」分组:把 `/features` 的 `effective` 里**我们还没给中文名**的布尔项动态列成数据行(`list` + `items` 绑定),点一行翻转一项。上游每加一个开关,手机面板不会再静默漏掉它;数值/字符串键(模型名、预算)不放这里,它们要输入框。
65
+
66
+ ## 内部文件
67
+
68
+ | 文件 | 一句话 |
69
+ |---|---|
70
+ | `src/dsh-go/panel.js` | 状态机、取数、动作(`mountDshGoPanel`)。动作跑完自己把新数据推给所有打开的 feed |
71
+ | `src/dsh-go/host.js` | 进程内派发:`ctx.get("webServer").match(path)` 拿到 mneme 自己的 handler,伪造 req/res 调用 |
72
+ | `src/dsh-go/text.js` | 类型标签、开关分组、时间与 scope 格式化 |
73
+ | `src/dsh-go/views/root.js` | `buildView`:顶层页签组合 |
74
+ | `src/dsh-go/views/{common,memory,entity,settings,status}.js` | 各分区视图;`common.js` 是小组件构造器 |
75
+
76
+ 文案取自 Web 面板的 zh i18n(`lib/client.js` 里的 `memory.*` 键),面板不再自造解释性文案。
77
+
78
+ ## 已知坑
79
+
80
+ - **feed 是推送式的**:`data()` 每个面板实例只跑一次,`refresh` 效果只让 App 重画视图、不会重跑 `data()`。所以每个动作结尾都要 `broadcast()`(向所有 emit 推新 payload),否则点了按钮界面还是旧数据。推送节流 4/s。
81
+ - **不能走 HTTP**:[dsh-go](https://github.com/ccch1mneyyy/dsh-Go) 桥的路由守卫(`installFallbackGuard`)对没有合法签名的请求直接 401(`x-dsh-go-guard: reject`),浏览器 cookie 是宿主签的、进程内也拿不到。进程内 `webServer.match()` 绕开这一层,且复用同一批 handler。
82
+ - **`/external-api` 的 GET 有副作用**:不带参数时会顺手生成 Bearer token 并落库。面板读配置用 `?token=omit`(只回 `enabled/port/host`,不铸造也不落库),不读令牌。
83
+ - **进程内调用也要带令牌**:宿主配了 `apiToken` 时,`/update`、`/delete`、`/features` 这些 token 路由对进程内请求同样校验;令牌走 `x-dsh-mneme-token` 头。
84
+ - **两种响应节奏都要等**:多数路由在返回的 promise 里 `res.end`,少数(`/version-check`)立即 `return` 再在回调里写响应。`host.js` 同时等 handler 的返回值和响应结束,只 race 的话后者会被当成空结果。
85
+ - **`/search` 不认识 `type`**:它是全库语义检索,面板在拿到结果后再按所选类型收一遍。
86
+ - **`/entities` 的 `q`**:名字过滤必须下推到 store(`LIKE`,`%`/`_` 转义)。面板若自己取 limit 条再本地过滤,排在 limit 之外的实体永远搜不到。
87
+ - **删除只能按状态认人**:`confirm` 节点没有参数位(只有 id 与两个按钮文案),所以「删哪一条」只能靠状态;状态是所有面板共用的,因此面板按 `scope` 记下自己打开的那条,确认时与本打开的条目核对,不一致就拒绝。
88
+ - **`/update` 不接受 `type`**:编辑已有记忆时类型只读展示,新建才给选择器。
89
+ - **保存只发差集**:`/update` 没有版本号校验,整份提交会把别处(工具、Web 面板)刚写进去的新值盖掉。面板在编辑页记住打开时的快照,只提交改动过的字段,`updated_at` 对不上就中止并让用户再看一眼。
90
+ - **`/create` 不接受 scope**:归属 Agent / 工作区两个输入只在编辑已有记忆时出现。
91
+ - **节点预算**:面板 120 节点 / 8 层 / 32 KiB,`data` 32 KiB。列表行只下发 `summary`(`content` 截 120 字),全文留在详情取,否则条目一多就超预算。
92
+ - **地志与事实都有闸门**:`/choro/*` 在 `chorographyEnabled` 关闭时回 `409 {error:"disabled"}`,`/profile-facts/run` 在 `profileFactsEnabled` 关闭时同样 `409`;两个读口(`/profile-facts`、`/choro/repos` 之外的模块/页)不受开关影响,面板要能展示历史数据。
93
+ - **事实的 op 形状是硬约定**:`add` 缺 `content` 会把 `"undefined"` 写进事实正文并从此每帧注入;面板在本地就拦「正文不能空」,不把脏 op 递过去。
94
+ - **重建骨架只到 T0**:`POST /choro/run` 是同步秒级回执,不带 T1 富化;面板文案要说明「语义段退回骨架」,否则用户会以为富化丢了。
95
+ - **`refresh` 会重建视图树,页签选择会丢**:顶层 `tabs` 的选中项是 App 的本地状态(视图协议没有绑定口),App 收到 `refresh` 重新拉一遍视图,树一重建就回到第一个页签。所以「只改一个绑定值」的动作(开关、档位下拉、外部访问开关、事实增改、地志翻页)不要申请重画,只推数据;只有真的改变了「哪些节点存在」的动作(进子页、换分区、换分组、翻页、搜索)才要 refresh。
96
+ - **`nestFlags` 不能把值压成布尔**:枚举型旗标(`chorographyInject` = light/full/off)以前被写成 `value === true`,下拉永远只拿到 true/false。视图绑定要原样透传。
97
+ - **不要用 `metric.caption` 放单位**:`caption` 固定渲染在数值下方一行;v2 的 metric 整串数值同一字号,所以单位拼进 value(`57 条`),App 侧只有自带模型卡才会拆小字单位。