@modusensus/dsh-mneme 0.7.31 → 0.8.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 (93) hide show
  1. package/README.md +65 -27
  2. package/lib/api.js +139 -6
  3. package/lib/client.js +369 -32
  4. package/lib/commands.js +36 -5
  5. package/lib/config.js +101 -14
  6. package/lib/dream/decisions.js +147 -14
  7. package/lib/dream/sleep.js +84 -34
  8. package/lib/dream.js +250 -60
  9. package/lib/embedding.js +22 -2
  10. package/lib/index.js +68 -37
  11. package/lib/inject.js +54 -13
  12. package/lib/lang.js +168 -0
  13. package/lib/local-embedder.js +23 -9
  14. package/lib/mirror.js +17 -14
  15. package/lib/reranker.js +23 -8
  16. package/lib/runtime/adopt.js +176 -0
  17. package/lib/runtime/closure.js +172 -0
  18. package/lib/runtime/download.js +247 -0
  19. package/lib/runtime/layout.js +235 -0
  20. package/lib/runtime/loader.js +249 -0
  21. package/lib/runtime/provision.js +237 -0
  22. package/lib/runtime/tarball.js +130 -0
  23. package/lib/runtime/verify.js +191 -0
  24. package/lib/scope.js +83 -0
  25. package/lib/service.js +374 -55
  26. package/lib/settings.js +43 -2
  27. package/lib/store.js +126 -13
  28. package/lib/summarize.js +108 -35
  29. package/lib/tools.js +205 -12
  30. package/package.json +7 -2
  31. package/runtime-manifest.json +596 -0
  32. package/scripts/build-runtime-manifest.mjs +217 -0
  33. package/scripts/mneme-runtime.mjs +277 -0
  34. package/src/api.js +139 -6
  35. package/src/commands.js +36 -5
  36. package/src/config.js +101 -14
  37. package/src/dream/decisions.js +147 -14
  38. package/src/dream/sleep.js +84 -34
  39. package/src/dream.js +250 -60
  40. package/src/embedding.js +22 -2
  41. package/src/index.js +68 -37
  42. package/src/inject.js +54 -13
  43. package/src/lang.js +168 -0
  44. package/src/local-embedder.js +23 -9
  45. package/src/mirror.js +17 -14
  46. package/src/reranker.js +23 -8
  47. package/src/runtime/adopt.js +176 -0
  48. package/src/runtime/closure.js +172 -0
  49. package/src/runtime/download.js +247 -0
  50. package/src/runtime/layout.js +235 -0
  51. package/src/runtime/loader.js +249 -0
  52. package/src/runtime/provision.js +237 -0
  53. package/src/runtime/tarball.js +130 -0
  54. package/src/runtime/verify.js +191 -0
  55. package/src/scope.js +83 -0
  56. package/src/service.js +374 -55
  57. package/src/settings.js +43 -2
  58. package/src/store.js +126 -13
  59. package/src/summarize.js +108 -35
  60. package/src/tools.js +205 -12
  61. package/test/api.test.js +175 -9
  62. package/test/client.test.js +152 -10
  63. package/test/commands.test.js +40 -3
  64. package/test/config.test.js +17 -1
  65. package/test/dream-candidate-hybrid.test.js +281 -0
  66. package/test/dream.test.js +172 -0
  67. package/test/embedding-ready.test.js +196 -0
  68. package/test/helpers/runtime-source.js +60 -0
  69. package/test/helpers/tar-builder.js +80 -0
  70. package/test/inject.test.js +130 -1
  71. package/test/lang.test.js +87 -0
  72. package/test/quality-filter.test.js +68 -0
  73. package/test/reasoning-effort.test.js +106 -15
  74. package/test/reindex-backfill.test.js +62 -0
  75. package/test/runtime-adopt.test.js +206 -0
  76. package/test/runtime-cli.test.js +89 -0
  77. package/test/runtime-closure.test.js +193 -0
  78. package/test/runtime-download.test.js +302 -0
  79. package/test/runtime-layout.test.js +163 -0
  80. package/test/runtime-loader.test.js +237 -0
  81. package/test/runtime-manifest.test.js +158 -0
  82. package/test/runtime-provision.test.js +101 -0
  83. package/test/runtime-tarball.test.js +82 -0
  84. package/test/runtime-verify.test.js +214 -0
  85. package/test/scope-retrieval.test.js +197 -0
  86. package/test/scope-storage.test.js +247 -0
  87. package/test/scope-strict.test.js +165 -0
  88. package/test/service.test.js +76 -0
  89. package/test/sleep-action-set.test.js +422 -0
  90. package/test/store.test.js +14 -0
  91. package/test/summarize.test.js +224 -0
  92. package/test/tools.test.js +30 -5
  93. package/test/webServer-optional.test.js +34 -1
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-745%20passed-success)](https://github.com/modusensus/dsh-mneme)
8
+ [![tests](https://img.shields.io/badge/tests-988%20passed-success)](https://github.com/modusensus/dsh-mneme)
9
9
  [![CI](https://img.shields.io/github/actions/workflow/status/modusensus/dsh-mneme/ci.yml)](https://github.com/modusensus/dsh-mneme/actions)
10
10
  [![node](https://img.shields.io/badge/node-24%2B-blue)](https://nodejs.org)
11
11
  [![npm downloads](https://img.shields.io/npm/dm/@modusensus/dsh-mneme?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
@@ -13,15 +13,15 @@
13
13
 
14
14
  > 给 DeepSeek Harness 的跨会话记忆插件:让 Agent 记住你、记住项目、自动整理记忆。**Mneme**(Μνήμη)——希腊记忆女神 Mnemosyne 之名,掌管记忆与梦境,正如 autoDream 在后台巩固记忆。
15
15
 
16
- `dsh-mneme` 是一个 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 插件,为 Agent 提供持久的跨会话记忆能力。它借鉴了 Claude 的 **Dream 机制** 与 cc-haha / Claude Code 的 **autoDream** 实现思路——不仅**存储**记忆,还会**自动巩固**(去重、合并、冲突裁决、摘要生成),让记忆库越用越精炼。
16
+ `dsh-mneme` 是一个 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 插件,为 Agent 提供持久的跨会话记忆能力。它借鉴了 Claude 的 **Dream 机制** 与 cc-haha / Claude Code 的 **autoDream** 记忆巩固功能——不仅**存储**记忆,还会**自动巩固**(去重、合并、冲突裁决、摘要生成),让记忆库越用越精炼。完整溯源与致谢见文末「[🙏 致谢](#-致谢)」。
17
17
 
18
18
  ## ⏱️ 30 秒理解
19
19
 
20
- **一句话**:给 Agent 装上跨会话记忆——记住你、记住项目,并在后台自动整理,越用越懂你。
20
+ **一句话**:给 Agent 装上跨会话记忆——记住你、记住项目,并在后台自动整理,越用越懂你;**v0.8.0 起**记忆可按 agent 与工作区隔离(`scopeEnabled`),多 Agent / 多项目互不串台。
21
21
 
22
22
  | ① 写入 | ② 存储 | ③ 进化 |
23
23
  |--------|--------|--------|
24
- | 对话中模型主动记录(`memory_save`);会话结束自动提炼(`autoSummarize`) | SQLite 主库 + 人类可编辑 Markdown 镜像;实体 / 属性 / 时间轴三层结构化 | 新会话自动注入相关记忆;autoDream 后台去重 / 合并 / 归档,记忆库自我精炼 |
24
+ | 对话中模型主动记录(`memory_save`,可带敏感度与事件时间);会话结束自动提炼(`autoSummarize`) | SQLite 主库 + 人类可编辑 Markdown 镜像;实体 / 属性 / 时间轴三层结构化;记忆带 agent / workspace / 敏感度 / 发生时间标注 | 新会话自动注入相关记忆(按当前作用域加权,strictScope 开启后硬隔离);autoDream 后台去重 / 合并 / 归档,冲突冻结待人工裁决 |
25
25
 
26
26
  ```bash
27
27
  # 30 秒上手
@@ -49,18 +49,19 @@ dsh web
49
49
  - 逐 type 记录 `committed / failed / pending` 回执,健康端点区分 `ok / degraded / unknown`
50
50
  - 状态写失败不静默:同步失败落日志并留债务,重启自动收敛
51
51
 
52
- ### 模型工具(8 个)
52
+ ### 模型工具(9 个)
53
53
 
54
54
  | 工具 | 功能 |
55
55
  |------|------|
56
- | `memory_save` | 记录一条记忆(自动按标题去重合并) |
57
- | `memory_search` | 全文搜索(中文子串友好,可启用向量语义搜索) |
58
- | `memory_list` | 按类型分页列出(`include_archived=true` 可查看已归档) |
56
+ | `memory_save` | 记录一条记忆(自动按标题去重合并;v0.8.0 起去重键含 agent/workspace/scope 作用域,可选 `sensitivity` / `occurred_at` 参数) |
57
+ | `memory_search` | 全文搜索(中文子串友好,可启用向量语义搜索;可选 `occurred_from` / `occurred_to` 按事件发生时间过滤) |
58
+ | `memory_list` | 按类型分页列出(`include_archived=true` 可查看已归档;可选 `occurred_from` / `occurred_to` 时间过滤) |
59
59
  | `memory_get` | 读取单条记忆完整正文(v0.7.25,按 id;memory_search / memory_list 命中后读全文) |
60
60
  | `memory_update` | 修改已有记忆 |
61
61
  | `memory_delete` | 删除记忆(按记忆 ID 精确删除) |
62
62
  | `memory_forget` | 抑制注入(降权不删除,可恢复) |
63
63
  | `memory_archive` | 归档/恢复记忆(v0.2.5;归档后隐藏于列表/搜索/注入/整理,`archived=false` 可恢复) |
64
+ | `memory_runtime` | 本地推理运行时的状态 / 供给 / 校验(v0.7.32,自管运行时三档取件) |
64
65
 
65
66
  ### 自动注入 + 会话摘要
66
67
 
@@ -69,11 +70,13 @@ dsh web
69
70
 
70
71
  ### autoDream 自动记忆整理 🧠
71
72
 
73
+ > **溯源**:autoDream 的理念源自 Claude Code 的 Auto Dream 记忆巩固功能(Anthropic,Memory 2.0 的后台整理子代理),其「离线巩固」思路与 UC Berkeley & Letta 的 *Sleep-time Compute* 论文([arXiv:2504.13171](https://arxiv.org/abs/2504.13171))一脉相承。dsh-mneme 将其引入 DSH 插件生态,并发展为自己的实现——K-Means++ 聚类预分组、类型化决策清单与可回放的 sha256 摘要审计链,详见文末「[🙏 致谢](#-致谢)」。
74
+
72
75
  - **触发**:记忆数 > 10 或总字符 > 5000 时,异步自动触发(不阻塞写入)
73
76
  - **决策清单式整理**:LLM 输出 `keep` / `merge` / `archive` / `conflict` / `update` 决策清单,服务端校验后逐条应用
74
77
  - `merge`:合并主题相近的条目,保留信息最完整者
75
78
  - `archive`:归档过时/冗余条目(可恢复,不物理删除)
76
- - `conflict`:裁决矛盾信息,胜者保留、败者归档并追加溯源注释
79
+ - `conflict`:裁决矛盾信息,胜者保留、败者归档并追加溯源注释;`conflictFreezeEnabled`(默认关)开启后矛盾对不自动裁决,改为冻结进**冲突队列**(状态页并排对比,人工选保留方,#166)
77
80
  - `update`(v0.2.1):直接修正单条记忆的过时/错误内容(单 id / 必须实际变化 / 非 summary / 24h 保护 / 每次 ≤2)
78
81
  - **失败追踪(v0.2.1)**:用户纠正记忆时写入 `failure_memories` 表(旧值/新值),为后续自进化积累数据
79
82
  - **摘要生成**:整理后生成"记忆库总览"(单一实例),作为下次会话的优先注入
@@ -90,14 +93,14 @@ dsh web
90
93
  | 中等(1 万-5 万字) | `65536` |
91
94
  | 大型(5 万字以上) | `131072`(上限) |
92
95
 
93
- > 若使用**思考型模型**(如 deepseek-v4-flash / DeepSeek-R1 类),模型可能把全部预算花在 reasoning 上导致正文为空(日志出现 `no json array in llm output`)。处理顺序:① 把 `dreamReasoningEffort` 设为 `low` 显式压低思考(v0.7.26+ 若模型不支持该档位,`resolveDreamEffort` 会自动换用模型支持的默认/首个档位或省略字段,拒绝原因会记入 llm_audit);② 重试走模型默认思考行为后正文仍为空的,调大 `dreamMaxTokens`(reasoning 与正文共享该预算)或配置 `dreamProvider`/`dreamModel` 指向非思考模型。sleep 侧对应 `sleepReasoningEffort`。
96
+ > 若使用**思考型模型**(如 deepseek-v4-flash / DeepSeek-R1 类),模型可能把全部预算花在 reasoning 上导致正文为空(日志出现 `no json array in llm output`)。#135 修复后:未配置 `dreamReasoningEffort` 时会**自动取模型支持的最低档**发流(不再省略字段让模型自带默认档——v4-flash 系默认 high——顶上烧光预算),默认配置即生效;显式 `none` = 不发送字段用服务商默认,`off`/`low`/`medium`/`high` 原样传递,模型不支持的档位自动换用(拒绝原因记入 llm_audit)。正文仍为空时再调大 `dreamMaxTokens`(默认已抬至 131072)或配置 `dreamProvider`/`dreamModel` 指向非思考模型。sleep 侧对应 `sleepReasoningEffort`。
94
97
 
95
98
  **巩固模型分类声明**(settings panel「巩固模型」= `dreamProvider`/`dreamModel`,睡眠侧对应 `sleepProvider`/`sleepModel`):
96
99
 
97
100
  | 模型类别 | 例子 | 说明 |
98
101
  |---------|------|------|
99
102
  | **非思考模型(推荐)** | glm-5-2 类等 | 无 reasoning 声明;即使配了 effort 被 harness 拒绝,fallback 去掉字段重试即成功。空体风险最低 |
100
- | **思考模型(需实测)** | deepseek-v4-flash-ga 等 v4-flash-ga 系 | 默认开推理,可能烧光 token 预算返回空体;部分型号(如 v4-flash-ga)在 harness 侧被声明为**不接受任何 reasoning effort**(去掉 effort 时 harness 的 `defaultEffort` 会顶上来再次拒绝)。v0.7.26+ 已根治:`resolveDreamEffort` 发流前探测模型支持的档位,不支持的配置档位自动换用模型默认/首个支持档位,声明无 reasoning 能力的型号则省略字段。选用时建议配 `dreamReasoningEffort` 实测,不行就换非思考模型 |
103
+ | **思考模型(需实测)** | deepseek-v4-flash-ga 等 v4-flash-ga 系 | 默认开推理,可能烧光 token 预算返回空体;部分型号(如 v4-flash-ga)在 harness 侧被声明为**不接受任何 reasoning effort**(去掉 effort 时 harness 的 `defaultEffort` 会顶上来再次拒绝)。v0.7.26+ 已根治:`resolveDreamEffort` 发流前探测模型支持的档位,不支持的配置档位自动换用模型默认/首个支持档位,声明无 reasoning 能力的型号则省略字段。选用时建议配 `dreamReasoningEffort` 实测(未配置时自动取最低档),不行就换非思考模型 |
101
104
 
102
105
  ### Sleep Mode 系统级睡眠 💤(v0.4.0,opt-in)
103
106
 
@@ -123,7 +126,7 @@ dsh web
123
126
 
124
127
  - **用户画像**:一段自由文本描述用户自己(角色、背景、偏好),**每轮注入**到系统提示,让 Agent 始终遵循
125
128
  - **规则**:Agent 必须遵守的行为规则列表(如"回答先给结论"),同样每轮注入
126
- - **自定义指令**:注册斜杠命令(`/名称`),触发时把用户定义的指令内容交给 Agent。命令持久化到 SQLite,启动时自动注册到 DSH 命令表,增删实时生效
129
+ - **自定义指令**:注册斜杠命令(`/名称 [补充说明]`),触发时把用户定义的指令内容作为一条用户消息提交给当前 Agent,命令后缀会作为补充说明一并提交;界面仅显示提交回执(宿主不支持 followup 提交时退回旧行为,指令原文会显示在界面,便于复制后手动发送)。命令持久化到 SQLite,启动时自动注册到 DSH 命令表,增删实时生效
127
130
 
128
131
  > 画像与规则通过独立的 `[用户设置]` 注入区块(优先级高于记忆库),即使记忆为空也会注入。
129
132
 
@@ -153,9 +156,19 @@ v0.2 起新增**完全离线的语义记忆引擎**(本地模型 + 精排 +
153
156
  - **Rerank 精排**:`Xenova/bge-reranker-base` 对召回候选交叉编码精排,提升 Top-K 准确率
154
157
  - **autoDream 语义增强**:对记忆向量聚类(`clusterMemories`),自动发现主题相近 / 疑似矛盾的记忆,巩固更精准
155
158
  - **搜索流水线**:混合召回(关键词 + 向量)→ Rerank → Top-K
159
+ - **自管运行时**:本地推理的依赖闭包(`@huggingface/transformers` + `onnxruntime-node` + `sharp`,从宿主收编时本机实测 49 个包 / 约 393MB)可收编到 `~/.dsh/mneme/runtime/`,与 profile 的依赖图解耦——由于 profile 是所有插件共用的依赖图,这份闭包留在此前的位置会让「安装任何插件」都替它重走一遍整条依赖链;收编优先硬链接,同盘时几乎不额外占盘。`@huggingface/transformers` 已从 `dependencies` 降为**可选 peer**,因此安装插件不再携带它;正在使用本地嵌入的用户请先按文档收编,否则升级后本地嵌入不可用(读写信道不受影响)
160
+ - **取回运行时的三条来源**:① 收编本机已有(零网络、同盘硬链接)→ ② 本地 `.tgz` 目录(`runtimeTarballDir`,某个包网络下不到时用)→ ③ npm registry(`runtimeMirror` 可换镜像),按随包发布的 `runtime-manifest.json` 逐个取并**先校验 sha512 再落盘**(win32-x64 实测约 33 个包 / 2142 个文件 / 数百 MB;清单平台无关,一份覆盖 win32/darwin/linux × x64/arm64;剔除 `onnxruntime-web`——Node 构建从不 import 它)。面板「向量索引」卡片在不就绪时会给出这段代价说明并提供一个按钮;不想开面板也可以让 agent 用 `memory_runtime` 工具(`status` / `provision` / `verify`)代做
156
161
 
157
162
  配置只需在 `cordis.patch.yml` 里设置 `embedProvider`(默认 `openai`,保持 v0.1 行为;改为 `local` 即离线)。升级无需迁移数据。
158
163
 
164
+ ```bash
165
+ node scripts/mneme-runtime.mjs status # 运行时在哪、来源、结构是否完整(不加载模型)
166
+ node scripts/mneme-runtime.mjs adopt --from ~/.dsh/profiles/<profile>/node_modules
167
+ node scripts/mneme-runtime.mjs verify # 真加载运行时并跑一次推理
168
+ ```
169
+
170
+ > 三个命令的退出码为 `0` 健康 / `1` 不健康 / `2` 用法错误,便于脚本 gate;`--json` 输出机器可读结果。运行时不可用时不会崩——检索降级为关键词/BM25,读写不受影响。详见 [本地模型部署指南 §2.5](docs/LOCAL_MODEL.md)。
171
+
159
172
  ### 实体结构化记忆(Entity Gene)🧬
160
173
 
161
174
  v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带时间轴的属性**、**实体间关系**,让搜索从"字面关键词"升级为"按实体/属性精确召回"。
@@ -176,9 +189,9 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
176
189
 
177
190
  - `score ≥ 60`:正常存储
178
191
  - `30 ≤ score < 60`:`quality_score` 落库,注入排序改为按 `importance × quality/100` 降权(degraded)
179
- - `score < 30`:归档并标记 `low_quality`——仍可显式搜索召回,只是**永不自动注入**
192
+ - `score < 30`:归档并标记 `low_quality`——仍可显式搜索召回,只是**永不自动注入**。**例外(#135)**:`importance ≥ memoryQualityFilter.exemptImportance`(默认 4)的记忆只降权不归档——评分与信号标签照常落库,归档决定不再静默越过用户标注的重要性
180
193
 
181
- `memoryQualityFilter.enabled` 可整体关闭,`archiveThreshold` / `degradeThreshold` / `minContentLength` 可调。
194
+ `memoryQualityFilter.enabled` 可整体关闭,`archiveThreshold` / `degradeThreshold` / `minContentLength` / `exemptImportance`(1=全部豁免,5=仅最重要豁免)可调。此外更新记忆(`memory_update` 等)不会抹掉系统信号标签(`low_quality` / `duplicate` / `meta` / `self_referential` / `repetitive` / `short_content`)——它们是「为什么被降权/归档」的审计线索,与用户自传标签并集保留(#135)。
182
195
 
183
196
  ### LLM 消耗审计 📊(v0.4.6,默认开)
184
197
 
@@ -215,7 +228,8 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
215
228
 
216
229
  | 版本 | 亮点 |
217
230
  |------|------|
218
- | **v0.7.31** | peerDependencies 宿主版本声明修复(#121):原 `^0.1.0-rc.6` 按 node-semver 预发布元组规则不匹配 `0.1.5-rc.1` 等中间预发布版本(当前 dsh 用户安装会 ERESOLVE),也违反 awesome-dsh-plugin 的 peer-range 预发布分支规范;改显式三段式预发布分支 `>=0.1.0-rc.6 <0.2.0 \|\| >=0.1.5-rc.0 <0.3.0 \|\| >=0.2.0-rc.0 <0.3.0`——覆盖全部已发布 0.1.x(含 `0.1.5-rc.1`)、0.2 预发布留显式分支、0.3+ 待验证后放开;745 测试全绿 |
231
+ | **v0.8.0** | 作用域隔离(issue #17 批次 A)+ 冲突集中处理 + 斜杠命令提交 Agent + 注入转义回归修复:记忆按 agent 与工作区双维标注(agent_scope / workspace_scope / sensitivity / occurred_at 四列可空),检索按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见),opt-in `strictScope` 硬过滤贯通检索/注入/列表/单取(fail-closed);`scopeEnabled` / `strictScope` 默认关、面板「作用域隔离」组可启停;去重键扩展含作用域三元——跨作用域同标题不再物理合并;`occurred_at` 事件发生时间 + `occurred_from/to` 过滤贯通搜索与列表;冲突冻结(conflictFreezeEnabled)新增人工出口——状态页冲突队列并排对比 + 保留 A/B/仅标记(#166);斜杠命令经 agent.followup 真正提交模型(#152);恢复 v0.7.4 的注入花括号转义 + hot memory 跳过 reasoning——`{{.Server.Version}}` 类文本不再卡死会话(#165);988 测试全绿 |
232
+ | **v0.7.32** | 冲突动作集扩展 + 运行时自管化 + 记忆/提示语言 + 候选集 hybrid 档 + 一批修复:`sleepActionSet` 新增 `full` 档(supersede/differentiate/update/merge/conflict/keep 六分支,默认 `conflict` 零行为变化)——supersede 赢家正文干净、输家归档附「已被取代」注记,differentiate 双留+差异注记;sleep 校验接入 `dreamSkipInvalid` 宽容策略(默认 true,一票否决变宽容,严格可关);运行时三档取件(收编/本地 .tgz/registry)+ transformers 转可选 peer(#131);`memory.language`(zh/en)提示/注入/镜像语言可选(#124);summarize 节流 + 产出上限 + 同会话去重(#127);inject 正文读取修复(#129)、#135 五连修(前缀解析/effort 最低档/importance 豁免/ready 语义/覆盖度降级告警)、boot 回填指纹短路修复(#128)、degraded 轮跳过明细落库(#104);916 测试全绿、总覆盖 92.7%(runtime 96%、embedding 100%) |
219
233
  | **v0.7.30** | 状态页向量卡修复(issue #118):改读开放的 `/semantic`——不再吃 `/vector-config` 的 401「加载失败」,ollama/local 模式不再恒显「未启用」;新增「初始化中(embedder 不可达,正在重试)」与「已索引 N / M 条」回填进度显示;legacy OpenAI 兼容 embedder「Object · 0D」显示修复(embedder 显式 `name: "OpenAI"` + 维度回退 `index.dimension`);「已索引 N / M」分子分母同口径(`embeddedCount` 剔除归档/遗忘,与 `count()` 默认过滤对齐);embedder init 失败有界重试(共 5 次,不再一次性永久降级,unload 清理定时器)+ `OllamaEmbedder.ready` 生命周期位 + `/semantic` 新增 `ready` 字段;745 测试全绿 |
220
234
  | **v0.7.29** | 实体抽取路由契约修复 + 面板控件 + 帮助与反馈:`streamEntityText` 抽取为可测试导出的 `createEntityStreamAdapter`——显式 `provider`/`model` 优先、缺省兜底读默认路由选择;实体抽取思考强度 `entityExtractionReasoning` 被模型拒绝时自动去 effort 重试(与 autoDream/sleep 同一降级策略);设置面板新增实体抽取 `provider`/`model`/思考强度三控件;面板底部新增「帮助与反馈」卡片(GitHub issue 预填 + mailto `work@modusensus.space` + 浏览已知问题)+ 配套 `GET /api/dsh-mneme/info`;744 测试全绿 |
221
235
  | **v0.7.28** | 连通性测试三连修:`POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024——思考模型的推理过程足以耗尽 16 token 预算致 `reply` 恒为空(「真的答了 ok 而非只报通」对思考模型不成立;按实际用量计费,手动按钮无放大成本);连通性测试状态按巩固/睡眠路由各持一份——此前共享单份,点任一「测试连通性」按钮两组同显「测试中/结果」且互相禁用;面板挂载草稿补 `sleepProvider`/`sleepModel`——后端一直保存正常,但重挂载(切页签/退出重进)后下拉恒显「跟随默认路由」,看起来像设置被重置,补键后正常回填(历史已存值无需重填);733 测试全绿 |
@@ -319,7 +333,8 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
319
333
  | **v0.7.29** | ✅ 完成 | 实体抽取路由契约修复 + 面板控件 + 反馈入口 | 实体抽取 LLM 路由契约修复(#108/#109):`createEntityStreamAdapter` 显式 provider/model 优先 + 兜底默认路由 + effort 拒绝自动去重试;设置面板新增实体抽取 provider/model/思考强度三控件;「帮助与反馈」卡片(GitHub issue 预填 + mailto + 浏览已知问题)+ `GET /api/dsh-mneme/info`;744 测试全绿 |
320
334
  | **v0.7.30** | ✅ 完成 | 状态页向量卡修复 + embedder 有界重试 | 卡片改读 `/semantic`(不再 401「加载失败」、ollama/local 不再恒显「未启用」)+「初始化中 / 已索引 N / M 条」显示(issue #118);legacy OpenAI 兼容 embedder 显式 `name: "OpenAI"` + 维度回退 `index.dimension`(「Object · 0D」修复);`embeddedCount` 对齐 `count()` 默认口径、剔除归档/遗忘(「已索引 269 / 36」修复);embedder init 失败 5 次有界重试不再一次性永久降级 + `OllamaEmbedder.ready` + `/semantic` `ready` 字段;745 测试全绿 |
321
335
  | **v0.7.31** | ✅ 完成 | peerDependencies 宿主版本声明修复 | 原 `^0.1.0-rc.6` 按 node-semver 预发布元组规则不匹配 `0.1.5-rc.1` 等中间预发布版本(当前 dsh 用户安装 ERESOLVE),也违反 awesome-dsh-plugin 的 peer-range 预发布分支规范;改显式三段式预发布分支(覆盖 0.1.0-rc.6 至 0.1.5-rc.1 全部已发布 0.1.x 含预发布 + 0.2 预发布留显式分支 + 0.3+ 待验证后放开);745 测试全绿 |
322
- | **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ 跨 workspace 记忆共享 |
336
+ | **v0.7.32** | ✅ 完成 | sleep 冲突动作集扩展 + 运行时自管化 + 候选集 hybrid 档 + 一批修复 | `sleepActionSet` 新增 `full` 档(supersede/differentiate/update/merge/conflict/keep 六分支,默认 `conflict` 零行为变化)+ sleep 校验接入 `dreamSkipInvalid` 宽容策略 + 被跳决策带 phase 落库 `dream_runs.skipped`;运行时三档取件 + transformers 转可选 peer(#131);`memory.language` 提示/注入/镜像语言可选(#124);候选集向量驱动 hybrid 档(#125,PR #147);summarize 节流 + 产出上限 + 同会话去重(#127);inject 正文读取修复(#129)+ #135 五连修 + boot 回填指纹短路修复(#128)+ degraded 轮跳过明细落库(#104);runtime 覆盖统计归一 + 补齐(总覆盖 92.7%);916 测试全绿 |
337
+ | **v0.8.0** | ✅ 完成 | 作用域隔离 + 冲突队列 | 按 agent 与工作区双维隔离记忆(标注 / 检索加权 / opt-in strictScope 四路硬过滤,issue #17 批次 A);冻结冲突新增人工确认队列(状态页并排对比,#166);斜杠命令真正提交 Agent(#152);注入转义回归恢复 + hot memory 跳过 reasoning(#162)。图谱增强(兴趣漂移可视化)、跨 workspace 记忆共享仍在规划 |
323
338
 
324
339
  > 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
325
340
 
@@ -364,6 +379,7 @@ dsh web
364
379
  name: '@modusensus/dsh-mneme'
365
380
  config:
366
381
  memoryDir: ~/.dsh/memory
382
+ language: zh
367
383
  autoInject: true
368
384
  autoSummarize: true
369
385
  maxInjectedItems: 5
@@ -379,6 +395,7 @@ dsh web
379
395
  | 键 | 默认值 | 说明 |
380
396
  |----|--------|------|
381
397
  | `memoryDir` | `~/.dsh/memory` | 记忆存储目录(SQLite + Markdown) |
398
+ | `language` | `zh` | 记忆语言:生成的记忆条目、注入标题与后台 LLM 提示词所用语言(`zh` / `en`) |
382
399
  | `autoInject` | `true` | 会话启动自动注入记忆 |
383
400
  | `autoSummarize` | `true` | 会话结束自动提炼摘要 |
384
401
  | `summarizeProvider` / `summarizeModel` | 空 | 摘要的 LLM 路由覆盖(空=使用当前会话模型);推荐轻量模型节省主模型 token |
@@ -391,6 +408,9 @@ dsh web
391
408
  | `dreamProvider` / `dreamModel` | 空 | dream 的 LLM 路由覆盖(显式配置优先于 agent 默认模型;留空则回退到 agent 默认模型) |
392
409
  | `dreamMaxTokens` | `32768` | dream LLM 调用最大 token 数(上限 131072;思考型模型的 reasoning 与正文共享该预算,正文为空时优先调大,见下方调优指南) |
393
410
  | `dreamReasoningEffort` | `none` | dream LLM 推理强度透传:`low` / `medium` / `high` / `none`(`none`=不传该字段,沿用模型默认;思考型模型(如 deepseek-v4-flash)想压低思考可设 `low`;v0.7.26+ 模型不支持配置档位时自动换用其支持的默认/首个档位,无 reasoning 能力的型号省略字段) |
411
+ | `dreamCandidateMode` | `window` | dream 候选集构造:`window`(只取最近 `dreamMaxSnapshotSize` 条)/ `hybrid`(在此基础上并入向量翻出的高相似组)。纯时间窗口下,实测 45 对「双方活跃且 sim≥0.85」里 0 对能同时进窗口——该合并的一对几乎永远碰不到面。**已知边界**:dream 侧动作集仍是五分支(keep / merge / archive / update / conflict),hybrid 翻出的互补型 / 演进型对在 dream 里没有 `differentiate` / `supersede` 出口;正确出口在 sleep 侧(`sleepActionSet: full`)|
412
+ | `dreamCandidateMax` | `0` | hybrid 的候选总量上限;`0` = 复用 `dreamMaxSnapshotSize`。候选总量与库总量解耦,输入成本不随库增长 |
413
+ | `dreamCandidateMinSim` | `0.85` | hybrid 判「高相似」的阈值(与 sleep 的 normal 档对齐,两个模块共用同一个「高相似」定义) |
394
414
  | `apiToken` | 空 | 可选 API 鉴权 token;设置后写操作与密钥接口要求 `Authorization: Bearer <apiToken>` |
395
415
  | `embedProvider` | `openai` | 语义后端:`openai`(默认,兼容 v0.1)/ `local`(ONNX 离线)/ `ollama` |
396
416
  | `localEmbedModel` | `Xenova/bge-small-zh-v1.5` | 本地 ONNX embedding 模型 |
@@ -422,7 +442,7 @@ dsh web
422
442
  | `entitySearchEnabled` | `true` | `entity:` / `attr:` 前缀搜索开关 |
423
443
  | `trustEpistemicWeighting` | `false` | 记忆可信度加权(v0.4.5,opt-in 默认关):记忆按来源分级 `observation`> `inferred` > `subjective`,开启后检索排序优先高可信记忆、注入对 observation 标注 `[verified]`、dream merge/conflict 偏向高可信一方;关闭时 `epistemic_status` 仅随保存落库、不参与行为 |
424
444
  | `evalPersistTestResults` | `false` | 检索评估落库(v0.4.5,opt-in 默认关):开启后 `evaluateRetrieval` 把 precision/recall/mrr 快照写入 `recall_evals`;默认关时仅返回调用方不落库。生产 `searchMemories` 审计始终走 `recall_runs`,无条件不触碰 `recall_evals` |
425
- | `autoReindexOnBoot` | `true` | 存量记忆缺 embedding 时,向量已配置则启动后延迟后台按批次限速自动回填重建(设为 `false` 仅手动重建) |
445
+ | `autoReindexOnBoot` | `true` | 存量记忆缺 embedding 时,向量已配置则启动后延迟后台按批次限速自动回填重建(设为 `false` 仅手动重建)。只补**活跃**记忆(归档/遗忘行不参与召回,#128);模型指纹一致时照常补缺失行、不再短路(#128) |
426
446
  | `hybridInject` | `true` | 注入语义召回优先(v0.4.6,Bug4):`injectCandidates` 带非空 query 时先走向量索引语义召回候选,规则筛选补足/去重;空 query / 无向量回退旧逻辑 |
427
447
  | `bm25SearchEnabled` | `true` | BM25 稀疏第三路召回(v0.5.0):ASCII 词元 + CJK bigram,IDF 加权,散词/ID/代码片段查询不再依赖子串命中 |
428
448
  | `adaptiveThresholdEnabled` | `true` | 自适应相似度阈值(v0.5.0):按查询形态动态截断(前缀 0.5 / 短查询 0.7 / 长查询 0.6 / 头部分差大放宽 0.5),显式传 `threshold` 走旧行为 |
@@ -438,8 +458,12 @@ dsh web
438
458
  | `selectiveInjectEnabled` | `true` | 选择性注入(v0.5.0):query 向量可用时注入候选按主题相似度重排,替代固定规则序 |
439
459
  | `searchSemanticDedup` | `false` | 搜索时语义去重(v0.5.0,激进选项默认关):embedding 余弦 ≥0.95 近重复行在 Rerank 前丢弃 |
440
460
  | `searchSemanticDedupThreshold` | `0.95` | 语义去重相似度阈值(v0.5.0,默认 0.95,范围 0.5-1.0):`searchSemanticDedup=true` 时生效,调整可防小模型误折叠 |
441
- | `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10}` | 记忆质量过滤(v0.4.6,默认开):写库前启发式打分 0-100,元记忆词汇/自指/过短/重复/近似重复扣分;≥60 正常存储,30-60 降权(注入排序按 importance×quality/100),<30 归档标记 `low_quality`(显式搜索仍可召回,永不自动注入) |
461
+ | `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10, exemptImportance:4}` | 记忆质量过滤(v0.4.6,默认开):写库前启发式打分 0-100,元记忆词汇/自指/过短/重复/近似重复扣分;≥60 正常存储,30-60 降权(注入排序按 importance×quality/100),<30 归档标记 `low_quality`(显式搜索仍可召回,永不自动注入;`exemptImportance` 豁免:importance ≥ 该值只降权不归档,#135) |
442
462
  | `llmAudit` | `{enabled:true, retentionDays:90}` | LLM 消耗审计(v0.4.6,默认开):每次后台 LLM 调用(autoDream/autoSummarize)写 `llm_audit_logs`(tokens/duration/status/source);失败记 error 不阻塞;只读 API `/api/dsh-mneme/semantic/llm-audit` + `/llm-audit/stats` |
463
+ | `scopeEnabled` | `false` | 作用域隔离总开关(v0.8.0,issue #17):memory_save 按会话身份写入 agent / workspace 标注(agentPreset / 工作区路径,registry 反查取不到回退 header.cwd,再取不到 NULL);去重键扩展含作用域三元——跨作用域同标题不再物理合并;检索按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见)。也走 feature_flags 白名单(面板可启停) |
464
+ | `strictScope` | `false` | 作用域硬过滤(v0.8.0,依赖 `scopeEnabled`):检索 / 注入 / 列表 / 单取四路过滤,带他者作用域的记忆完全不可见;会话身份解析不到时 fail-closed 只见未标注行。关闭时为软隔离(降权保留可见)。也走 feature_flags 白名单 |
465
+ | `conflictFreezeEnabled` | `false` | 冲突冻结(v0.4.4):dream 发现矛盾对不自动裁决,冻结进冲突队列;状态页「冲突队列」支持并排对比与人工确认(保留 A / 保留 B / 仅标记已处理,v0.8.0) |
466
+ | `escapePromptVariables` | `true` | 注入文本花括号转义(v0.8.0 恢复,issue #162):注入边界把 `{{...}}` 转义,防止 hot memory / 记忆原文里的 Go template / Vue 语法触发宿主 interpolate 抛错卡死会话(v0.7.4 曾修复、v0.7.11 误删) |
443
467
 
444
468
  > 🔐 **API 安全**:DSH 无内置鉴权且默认仅监听 `127.0.0.1`。插件 API 默认开放(便于 Web 面板即装即用)。如需防护(如局域网暴露),在配置中设置 `apiToken`:写操作(画像/规则/命令)与密钥端点(`vector-config`、`vector-reindex`)需携带 `Authorization: Bearer <token>`(前端设置面板可填入同一 token),只读的 `list` / `search` / `semantic` 保持开放。`/api/dsh-mneme/vector-config` 返回的 `apiKey` 已掩码(`sk-***…`),存储仍保留明文供调用;前端回传空或掩码值表示"不改 key"。
445
469
 
@@ -536,25 +560,29 @@ dsh-mneme config show # 查看当前配置(toke
536
560
 
537
561
  ```
538
562
  src/
539
- ├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema 迁移)
563
+ ├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema 迁移、scope 四列)
540
564
  ├── mirror.js # Markdown 镜像(渲染/解析,人工优先)
541
- ├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子)
565
+ ├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子、scope 加权/硬过滤、冲突队列)
566
+ ├── scope.js # 作用域解析(agentPreset + workspace registry 反查,issue #17)
542
567
  ├── config.js # schemastery 配置 schema
543
- ├── tools.js # 8 个模型工具(defineTool)
544
- ├── inject.js # systemPrompt.context 动态注入
568
+ ├── tools.js # 9 个模型工具(defineTool)
569
+ ├── inject.js # systemPrompt.context 动态注入(含花括号转义)
545
570
  ├── summarize.js # 会话结束 LLM 摘要
546
571
  ├── dream.js # autoDream 调度 + runDream(LLM 决策 + 摘要)
547
572
  ├── dream/decisions.js# 决策校验(fail-safe)+ 决策应用
573
+ ├── dream/clustering.js # 候选集聚类(k-means++ seeding)
574
+ ├── dream/sleep.js # Sleep Mode 分层压缩
548
575
  ├── entities/extractor.js # 实体抽取器(v0.3.0:LLM JSON 抽取 + 去重 + fail-safe)
549
576
  ├── search/bm25.js # BM25 稀疏召回(v0.5.0:分词 + IDF 索引)
550
577
  ├── search/adaptive.js# 自适应相似度阈值(v0.5.0)
551
- ├── hot-memory.js # 会话级短期热记忆(v0.5.0:滚动轮次 + token 预算)
578
+ ├── hot-memory.js # 会话级短期热记忆(v0.5.0:滚动轮次 + token 预算;注入剥离 reasoning)
579
+ ├── runtime/ # 自管本地推理运行时(收编 / 本地 .tgz / registry 三档取件 + 校验)
552
580
  ├── embedding.js # OpenAI 兼容 embeddings 客户端 + 向量检索
553
- ├── api.js # HTTP 路由(Web 面板数据通道)
581
+ ├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
554
582
  └── index.js # 插件接线
555
583
  lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
556
- test/ # 714 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
557
- scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js 召回基准
584
+ test/ # 988 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
585
+ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js 召回基准 · release-prep.mjs 发布准备
558
586
  ```
559
587
 
560
588
  ## 🧪 开发
@@ -562,7 +590,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
562
590
  ```bash
563
591
  cd dsh-mneme
564
592
  npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
565
- npm test # 运行 714 个测试
593
+ npm test # 运行 988 个测试
566
594
  npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
567
595
  npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
568
596
  ```
@@ -580,6 +608,16 @@ npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动
580
608
  - [本地模型部署指南](docs/LOCAL_MODEL.md)
581
609
  - [从 v0.1 升级说明](docs/MIGRATION.md)
582
610
 
611
+ ## 🙏 致谢
612
+
613
+ autoDream 的理念溯源(理念借鉴、实现原创):
614
+
615
+ - **[Claude Code 的 Auto Dream](https://code.claude.com/docs/en/memory)**(Anthropic,Memory 2.0):理念源头——会话间隙由后台子代理整理记忆文件(去重、修矛盾、清衰减)。
616
+ - **[Sleep-time Compute: Beyond Inference Scaling at Test-time](https://arxiv.org/abs/2504.13171)**(UC Berkeley & Letta,arXiv:2504.13171):Auto Dream「离线巩固」思想背后的学术脉络。
617
+ - **cc-haha**:早期实现思路的参照之一。
618
+
619
+ 在上述工作之上,dsh-mneme 做了自己的工程发展:K-Means++ 聚类预分组(`src/dream/clustering.js`)、类型化决策清单(keep / merge / archive / conflict / update,及 sleep 侧 supersede / differentiate)与可回放的 sha256 摘要审计链(`dream_runs` / `receipt_chain`)。如有遗漏的灵感来源,欢迎提 issue 指出。
620
+
583
621
  ## 📜 License
584
622
 
585
623
  MIT
package/lib/api.js CHANGED
@@ -3,8 +3,11 @@ import { readFileSync } from "node:fs";
3
3
  import { timingSafeEqual, randomBytes } from "node:crypto";
4
4
  import { FEATURE_FLAG_SPEC } from "./settings.js";
5
5
  import { TYPE_FILE, renderMirrorText, parseHumanEdits } from "./mirror.js";
6
+ import { langOf } from "./lang.js";
6
7
  import { computeHeat } from "./heat.js";
7
8
  import { describeStreamFailure, resolveRoute } from "./dream.js";
9
+ import { describeLocalRuntime, publicRuntimeStatus } from "./runtime/loader.js";
10
+ import { hostModulesDir, provisionRuntime } from "./runtime/provision.js";
8
11
 
9
12
  // headers:少数端点(/export 附件下载)需要追加 Content-Disposition 等响应头。
10
13
  function sendJson(res, status, payload, headers = {}) {
@@ -113,6 +116,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
113
116
  const flagKeys = [
114
117
  ...FEATURE_FLAG_SPEC.booleans,
115
118
  ...Object.keys(FEATURE_FLAG_SPEC.ints),
119
+ ...Object.keys(FEATURE_FLAG_SPEC.numbers),
116
120
  ...FEATURE_FLAG_SPEC.strings,
117
121
  ...FEATURE_FLAG_SPEC.urls,
118
122
  ...Object.keys(FEATURE_FLAG_SPEC.enums)
@@ -604,18 +608,87 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
604
608
  handler(req, res) {
605
609
  try {
606
610
  const stats = semantic?.vectorIndex?.getStats?.() ?? null;
611
+ // #118: expose readiness so the status card can tell "initializing /
612
+ // unreachable" from "disabled". An embedder carrying no `ready` prop at
613
+ // all (test doubles, third-party adapters) is assumed usable.
614
+ // issue #135: 但「可用」和「配了」是两件事 —— legacy OpenAI embedder
615
+ // 此前恒报 ready,于是 baseUrl/apiKey/model 全空时状态卡依然是绿的
616
+ // ("绿色假阳性")。下面把两者分开,并附上索引覆盖度。
617
+ const ready = embedder ? ("ready" in embedder ? embedder.ready === true : true) : null;
618
+ // local/ollama 的配置来自 settings 兜底,没有「未配置」态:它们的
619
+ // ready=false 表示「还在初始化」,不是「没配」。
620
+ const configured = embedder ? (typeof embedder.configured === "boolean" ? embedder.configured : true) : null;
621
+ const embedded = Number(stats?.embeddedCount ?? 0);
622
+ const total = Number(stats?.totalCount ?? 0);
607
623
  sendJson(res, 200, {
608
- // #118: expose readiness so the status card can tell "initializing /
609
- // unreachable" from "disabled". Legacy OpenAI embedder has no `ready`
610
- // prop and is immediately usable, so treat that as ready.
611
- ready: embedder ? ("ready" in embedder ? embedder.ready === true : true) : null,
624
+ ready,
625
+ configured,
626
+ // 机器可读的原因;null = 一切正常。面板按它选文案,不用猜布尔组合。
627
+ reason: !embedder ? "no-embedder"
628
+ : configured === false ? "not-configured"
629
+ : ready !== true ? "initializing"
630
+ : null,
631
+ // 空库时 ratio 必须是 null 而非 0 —— 0 会被读成「一条都没嵌上」。
632
+ coverage: { embedded, total, ratio: total > 0 ? embedded / total : null },
633
+ // 配了、库里有东西可嵌、却一条都没嵌上 = 真的降级(本机此前长期
634
+ // 处于 ready:true + embeddedCount:0,面板却显示正常)。
635
+ degraded: configured === true && total > 0 && embedded === 0,
612
636
  // Explicit display name first: the legacy OpenAI-compatible embedder
613
637
  // is an object literal, so constructor.name would be "Object".
614
638
  embedProvider: embedder ? (embedder.name ?? embedder.constructor?.name ?? "unknown") : null,
615
639
  modelHash: embedder?.modelHash ?? null,
616
640
  dimension: embedder?.dimension ?? null,
617
641
  reranker: semantic?.reranker ? "ready" : null,
618
- index: stats
642
+ index: stats,
643
+ // issue #131 / PR-A:自管运行时的状态。无论当前 provider 是不是 local
644
+ // 都报 —— 面板需要区分「已收编但没启用」和「要用本地却没收编」。
645
+ // 这里只做结构检查(不跑推理),失败降级成状态对象,不会让本端点 500。
646
+ // 必须经 publicRuntimeStatus 投影:本端点是免鉴权的,绝对路径与原始错误
647
+ // 文本不能出去(CWE-200)。完整诊断走本机 CLI 的 status。
648
+ localRuntime: publicRuntimeStatus(describeLocalRuntime({ runtimeDir: config?.runtimeDir ?? "" }))
649
+ });
650
+ } catch {
651
+ sendJson(res, 500, { error: "internal" });
652
+ }
653
+ }
654
+ });
655
+
656
+ // --- 一键取回自管运行时(issue #131 / PR-C:三档来源)-------------------------
657
+ // 写路径(requireAuth):它会往 ~/.dsh/mneme/runtime/ 落一份依赖闭包。
658
+ //
659
+ // 第一档源固定取本 profile 的 node_modules(由插件自身路径推出),不接受客户端传源目录 ——
660
+ // 「从任意目录往运行时目录里搬东西」没有必要成为对外能力。
661
+ //
662
+ // 一律回 200:请求本身合法,成不成看 body 里的 ok/status/reason。让 HTTP 码只表达
663
+ // 「有没有鉴权/路径对不对」,结论交给 ok —— 面板要能显示「收编了但闭包缺件」这种
664
+ // 中间状态,用一个 4xx 概括会把信息压掉。
665
+ register({
666
+ kind: "exact",
667
+ path: "/api/dsh-mneme/runtime/provision",
668
+ handler(req, res) {
669
+ try {
670
+ if (req.method !== "POST") {
671
+ sendJson(res, 405, { error: "method-not-allowed" });
672
+ return;
673
+ }
674
+ if (!requireAuth(req, res, apiToken)) return;
675
+ return readBody(req).then(async (text) => {
676
+ const body = parseBody(text);
677
+ const runtimeDir = config?.runtimeDir ?? "";
678
+ // 三档来源依次尝试(收编本机 node_modules → 本地 .tgz 目录 → registry)。
679
+ // 所以这个端点会**联网**:它可能拉一份几十到上百 MB 的闭包。
680
+ const result = await provisionRuntime({
681
+ hostModulesDir: hostModulesDir(import.meta.url),
682
+ runtimeDir,
683
+ localTarballDir: config?.runtimeTarballDir ?? "",
684
+ mirror: config?.runtimeMirror ?? "",
685
+ overwrite: body?.overwrite === true
686
+ });
687
+ // 顺手带回最新状态,面板不必再打一次 /semantic(且它走的是免鉴权投影)。
688
+ sendJson(res, 200, {
689
+ ...result,
690
+ localRuntime: publicRuntimeStatus(describeLocalRuntime({ runtimeDir }))
691
+ });
619
692
  });
620
693
  } catch {
621
694
  sendJson(res, 500, { error: "internal" });
@@ -853,7 +926,7 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
853
926
  }
854
927
  const sections = [];
855
928
  for (const type of Object.keys(TYPE_FILE)) {
856
- if (byType[type]?.length) sections.push(renderMirrorText(type, byType[type]));
929
+ if (byType[type]?.length) sections.push(renderMirrorText(type, byType[type], langOf(config)));
857
930
  }
858
931
  sendAttachment(res, 200, "text/markdown; charset=utf-8", `dsh-mneme-export-${stamp}.md`, sections.join("\n"));
859
932
  } catch {
@@ -941,6 +1014,66 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
941
1014
  }
942
1015
  });
943
1016
 
1017
+ // --- 冲突集中处理(v0.8.0):队列 + 人工确认 --------------------------------
1018
+ // GET /conflicts:未解决冲突 + 双方当前内容(此前面板只有计数与徽章,
1019
+ // resolveConflictPending 在存储层躺着没有任何调用方)。POST /conflicts/resolve:
1020
+ // {id, winner: "a"|"b"|null, apply}——盖章审计,apply 时按 dream 非冻结
1021
+ // conflict 路径同款处置(service.resolveConflictPending 内部复用
1022
+ // applyDecisions:CAS + 事务 + 注记 + 幂等)。认证与其他写路由一致。
1023
+ register({
1024
+ kind: "exact",
1025
+ path: "/api/dsh-mneme/conflicts",
1026
+ handler(req, res) {
1027
+ try {
1028
+ if (req.method !== "GET") {
1029
+ sendJson(res, 404, { error: "not-found" });
1030
+ return;
1031
+ }
1032
+ if (!requireAuth(req, res, apiToken)) return;
1033
+ const url = new URL(req.url, "http://localhost");
1034
+ const limit = Math.min(200, Math.max(1, Number(url.searchParams.get("limit") ?? 50) || 50));
1035
+ const offset = Math.max(0, Number(url.searchParams.get("offset") ?? 0) || 0);
1036
+ const items = service.listConflictQueue({ limit, offset });
1037
+ sendJson(res, 200, { items, total: service.countConflictPending?.() ?? items.length });
1038
+ } catch {
1039
+ sendJson(res, 500, { error: "internal" });
1040
+ }
1041
+ }
1042
+ });
1043
+
1044
+ register({
1045
+ kind: "exact",
1046
+ path: "/api/dsh-mneme/conflicts/resolve",
1047
+ handler(req, res) {
1048
+ try {
1049
+ if (req.method !== "POST") {
1050
+ sendJson(res, 404, { error: "not-found" });
1051
+ return;
1052
+ }
1053
+ if (!requireAuth(req, res, apiToken)) return;
1054
+ return readBody(req).then((text) => {
1055
+ let body = {};
1056
+ try { body = JSON.parse(text || "{}"); } catch { /* 坏 JSON → 走缺参分支 */ }
1057
+ const id = typeof body.id === "string" ? body.id.trim() : "";
1058
+ if (!id) {
1059
+ sendJson(res, 400, { error: "missing-id" });
1060
+ return;
1061
+ }
1062
+ const winner = body.winner === "a" || body.winner === "b" ? body.winner : null;
1063
+ const apply = body.apply !== false;
1064
+ const result = service.resolveConflictPending(id, { winner, apply });
1065
+ if (!result) {
1066
+ sendJson(res, 404, { error: "not-found" });
1067
+ return;
1068
+ }
1069
+ sendJson(res, 200, { ok: true, conflict: result });
1070
+ }).catch(() => sendJson(res, 500, { error: "internal" }));
1071
+ } catch {
1072
+ sendJson(res, 500, { error: "internal" });
1073
+ }
1074
+ }
1075
+ });
1076
+
944
1077
  // --- health: mirror sync state (F-NEW-03 / v0.3.6) ---
945
1078
  // Auth-gated; only returns a sanitized error code (never raw last_error which
946
1079
  // may leak paths/token-like strings/internal hosts). On state read failure it