@modusensus/dsh-mneme 0.7.28 → 0.7.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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-714%20passed-success)](https://github.com/modusensus/dsh-mneme)
8
+ [![tests](https://img.shields.io/badge/tests-745%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)
@@ -215,6 +215,8 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
215
215
 
216
216
  | 版本 | 亮点 |
217
217
  |------|------|
218
+ | **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 测试全绿 |
219
+ | **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 测试全绿 |
218
220
  | **v0.7.28** | 连通性测试三连修:`POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024——思考模型的推理过程足以耗尽 16 token 预算致 `reply` 恒为空(「真的答了 ok 而非只报通」对思考模型不成立;按实际用量计费,手动按钮无放大成本);连通性测试状态按巩固/睡眠路由各持一份——此前共享单份,点任一「测试连通性」按钮两组同显「测试中/结果」且互相禁用;面板挂载草稿补 `sleepProvider`/`sleepModel`——后端一直保存正常,但重挂载(切页签/退出重进)后下拉恒显「跟随默认路由」,看起来像设置被重置,补键后正常回填(历史已存值无需重填);733 测试全绿 |
219
221
  | **v0.7.27** | v0.7.26 端点遗漏补齐:v0.7.26 发版树未含 `fix/dream-effort-trap` 分支,CHANGELOG/Release 宣告的 `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` 两个端点实际不在包内(面板「级联下拉 + 测试连通性」会 404);本版将分支 rebase 后合入(PR #100),端点落地,面板连通性测试可用;733 测试全绿 |
220
222
  | **v0.7.26** | 记忆巩固 `UNSUPPORTED_REASONING_EFFORT` 根治 + 巩固/睡眠模型连通性测试:根治 **defaultEffort 陷阱**——harness 在 effort 省略时注入 `reasoning.defaultEffort`,若该默认档位本身不被模型支持,则去 effort 重试也无济于事(换什么 effort 都会被拒绝);修复:新增 `resolveDreamEffort` 经 `ctx.llm.resolveModelInfo()` 主动探测模型支持的档位再发送——配置档位不被支持时自动换用模型支持的默认/首个档位、模型声明无 reasoning 能力则省略字段;设置面板巩固/睡眠 6 字段补选型提示(引导非思考模型,避免再踩思考模型烧光 token 预算);新增 `GET /api/dsh-mneme/llm-providers`(宿主侧 provider/model 发现,密钥不经插件侧)+ `POST /api/dsh-mneme/test-model`(模型连通性测试,空 body 按巩固路由解析返回 modelId);732 测试全绿 |
@@ -291,9 +293,9 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
291
293
  | **v0.7.3** | ✅ 完成 | issue #38 新功能 | 左下角入口按钮可选开关 `showSidebarTrigger`(默认开,settings-over-config);Web 面板设置一键关闭,与 dsh-cost-meter 等 footer 插件冲突可隐藏按钮、记忆库标签不受影响;776 测试全绿 |
292
294
  | **v0.7.4** | ✅ 完成 | issue #40 + #41 修复 | 注入边界 run-based 花括号转义(`{{a}}`→`{\{a\}\}`、奇数连续如 `{{{a}}}` 不残留字面,`escapePromptVariables` 默认开)+ 记忆窗口顶栏左对齐、关闭按钮避开宿主窗口控制按钮区;782 测试全绿 |
293
295
  | **v0.7.5** | ✅ 完成 | 分层记忆类型 + 总览视图 | 借鉴 meow-memory 分层概念、贴合单表架构:新增 `user`(用户画像)/`fact`(原子事实)类型,注入/镜像/梦境/质量过滤全链路打通;Web 面板「总览」视图(分层卡片 + 用户画像卡 + 类型分布 + 近 7 天趋势);`/api/dsh-mneme/stats` 端点;kimi-k2.7-code 复验;790 测试全绿 |
296
+ | **v0.7.6** | ✅ 完成 | issue #48 修复 | 四工具统一 `service.resolveMemoryId`:截断/前缀短 id 也能精确操作(精确命中优先、唯一前缀解析、多命中拒绝并列出候选、`memory_delete` 未命中幂等返回 + `logger.warn` 留痕);Web bundle `client.js` 改 src 正源;801 测试全绿 |
294
297
  | **v0.7.7** | ✅ 完成 | issue #23 图谱回填 | sleep 批量实体抽取 phase:默认关 `sleepEntityExtractionEnabled`,按最老优先、SQL LIMIT/OFFSET 分页(下沉为有界查询,不再整表扫描)批量抽取未打标记忆的实体(修复 issue #23 实体图谱空白);`pending_extracted_at` 幂等防重、成功/失败清除,metadata 合并不覆盖;node:sqlite 兼容修复(pluck→all+map、`forgotten=0` 查询条件);810 测试全绿 |
295
298
  | **v0.7.8** | ✅ 完成 | DSH 0.1.2-rc.1 兼容(issues #58 #59) | 官方移除 `Session.events` 属性、改用 `snapshotEvents()` 方法后 autoSummarize 与 hot-context 注入失效;改为兼容垫片 `session.snapshotEvents?.() ?? session.events`,新旧 DSH 通吃,老版本不受影响;新增 2 个回归用例;812 测试全绿 |
296
- | **v0.7.6** | ✅ 完成 | issue #48 修复 | 四工具统一 `service.resolveMemoryId`:截断/前缀短 id 也能精确操作(精确命中优先、唯一前缀解析、多命中拒绝并列出候选、`memory_delete` 未命中幂等返回 + `logger.warn` 留痕);Web bundle `client.js` 改 src 正源;801 测试全绿 |
297
299
  | **v0.7.9** | ✅ 完成 | issue #65 修复 | v0.7.8 的 snapshotEvents 适配未同步 lib/(npm 实际加载 lib/)导致发布产物静默失效;补齐 lib 三处垫片 + 发布前 src↔lib 一致性校验(`scripts/check-sync.js`,root prepack 调用,漂移 exit 1)+ `test/lib-smoke.test.js` 从 lib 导入复跑 + 一致性断言;815 测试全绿 |
298
300
  | **v0.7.10** | ✅ 完成 | Web 面板体验升级 | 记忆类型色点体系 + 图谱画布平移/缩放(0.5x–3x 光标锚定)+ 设置页分区重排 + 侧边栏同标签冲突修复 + 详情 meta 精排 + 只读端点 `GET /api/dsh-mneme/entities`;815 测试全绿 |
299
301
  | **v0.7.11** | ✅ 完成 | 记忆库面板改版 | 按月分页 + 无限滚动 + 搜索全局化 + 30s 静默刷新 + 状态子页四卡 + 删除两步确认 + 重要性过滤芯片 + 内联 Lucide 图标;bundle 默认开实体抽取;修复 issue#72 图谱节点裁剪 + issue#59 autoSummarize 垫片;595 测试全绿 |
@@ -306,11 +308,15 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
306
308
  | **v0.7.18** | ✅ 完成 | 生态第一步 + 查询收敛 | better-sidebar 软集成(inject 声明 + optional peer `dsh-better-sidebar` + registerTab 复用四视图,未装安全跳过;窄容器 `@container` 适配)+ `/list?deposited=only` 沉淀视图(receipt_chain ∪ source=dream)+ 记忆库沉淀/已归档筛选 chip + 状态页仪表盘化(统计 + 查看全部跳转预置筛选)+ 抽屉归档记忆「恢复」;667 测试全绿 |
307
309
  | **v0.7.20** | ✅ 完成 | heat 回归 + 阶段二前端 + better-sidebar 修复 | heat 热度模型完整找回(issue #87,v0.7.10 移植:幂律衰减 + TYPE_DECAY + sleep 热联合双保护 + 实体热投影)+ 验收清单落地(heatEnabled 默认关 / feature_flags 31 键 / lightMode 联动 / sleep 降级审计暴露 / updated_at⊥last_accessed_at 契约)+ 阶段二前端(/list heat 投影、HeatBadge 三档、order=heat 页内排序)+ better-sidebar 修复(issue #88:内层动态子插件);685 测试全绿 |
308
310
  | **v0.7.21** | ✅ 完成 | effort 回退流式修复 | autoDream/sleep 的 catch 式 effort 回退在流式路径是死代码(dsh-llm rc.1 把 adapter 异常转成终态 error finish chunk 不再抛出)→ `streamText` 捕获 finish-chunk 失败原因(`describeStreamFailure` 归一化)+ `withEffortFallback` 增加 `getStreamError` 访问器(effort 被拒去重试)+ `runAuditedLlm` 支持 `spec.streamError`(audit 记真实原因);688 测试全绿 |
309
- | **v0.7.26** | ✅ 完成 | 巩固 effort 陷阱根治 + LLM 连通性测试 | 记忆巩固 `UNSUPPORTED_REASONING_EFFORT` 根治(defaultEffort 陷阱:harness 省略 effort 时注入 `reasoning.defaultEffort`,默认档位不被模型支持则任何重试无效)→ 新增 `resolveDreamEffort` 经 `ctx.llm.resolveModelInfo()` 探测模型支持的档位(不支持的配置档位自动换用模型支持的默认/首个档位,无 reasoning 能力则省略字段);设置面板巩固/睡眠 6 字段补选型提示(引导非思考模型);新增 `GET /api/dsh-mneme/llm-providers`(宿主侧 provider/model 发现)+ `POST /api/dsh-mneme/test-model`(模型连通性测试,空 body 按巩固路由解析,密钥不经插件侧);732 测试全绿 |
310
- | **v0.7.25** | ✅ 完成 | 工具兼容性加固 + 内容预览 + 巩固模型引导 | 新增 `memory_get` 工具(第 8 个模型工具,按 id 读单条记忆全文)+ `memory_search`/`memory_list` render 嵌入标题/元数据/正文预览(宿主只透传 render 文本时模型也能读到内容)+ 巩固模型选型引导(config 注释/README 分类声明);修复 memory_get execute 嵌套 output 的崩溃(userExecute is not a function)+ 工具重复注册去重(live patch reload)+ Standalone API 端口冲突重试 + client inject 声明对齐 + better-sidebar 集成加固;718 测试全绿 |
311
- | **v0.7.24** | ✅ 完成 | 桌面端崩溃紧急修复 | v0.7.23 把 webServer 移出 inject 致 cordis Proxy 抛 `cannot get property "webServer" without inject`(未注入属性直接访问抛错而非 undefined),桌面端重启插件树加载失败;修复:恢复 webServer 到 inject(cordis 等宿主就绪再 apply)+ apply/register 守卫改 `ctx.reflect.get`(免 inject 读取、未提供返回 undefined 不抛错,未来 headless 移出 inject 也安全);真实 cordis + dsh-host-webserver 实测 API 路由 200 / 未知路径 404 / headless 静默不激活;714 测试全绿 |
312
- | **v0.7.23** | ✅ 完成 | 记忆沉淀「反复失败」根治 + 空体第二段 + skipInvalid splice 修复 | consolidation 合法空数组 `[]` no-op(CONSOLIDATION_PROMPT 允许无问题无需输出;此前 validateDecisions 硬判 non-empty → 整单 failed,模型无关、ChatGPT/Claude 同样踩中;修复:空数组显式短路 ok,不再触发隐式 keep 覆盖率误判);`dreamMaxTokens` 默认 8192→32768(思考模型推理烧光预算根治余量);skipInvalid splice 残留 bug(长度相等≠内容一致,被跳决策残留进 apply);712 测试全绿 |
313
311
  | **v0.7.22** | ✅ 完成 | skipInvalid 宽容校验回归(issue #89)+ autoDream 节流 | 恢复 v0.6.9 的 skipInvalid 双轨结构(v0.7.11 重写丢失):`dreamSkipInvalid` 单条非法决策跳过 + 合法子集应用 + run 记 degraded,`allowCrossTypeMerge` 显式放宽跨类型合并;弱模型(qwen3.8-flash)决策合规抖动不再整单拒绝;新增 `dreamMinIntervalMinutes`(0-10080,默认 0=不限)最小触发间隔,失败/degraded run 也占用间隔;严格模式/sleep 路径行为不变;feature_flags 白名单 34 键;696 测试全绿 |
312
+ | **v0.7.23** | ✅ 完成 | 记忆沉淀「反复失败」根治 + 空体第二段 + skipInvalid splice 修复 | consolidation 合法空数组 `[]` no-op(CONSOLIDATION_PROMPT 允许无问题无需输出;此前 validateDecisions 硬判 non-empty → 整单 failed,模型无关、ChatGPT/Claude 同样踩中;修复:空数组显式短路 ok,不再触发隐式 keep 覆盖率误判);`dreamMaxTokens` 默认 8192→32768(思考模型推理烧光预算根治余量);skipInvalid splice 残留 bug(长度相等≠内容一致,被跳决策残留进 apply);712 测试全绿 |
313
+ | **v0.7.24** | ✅ 完成 | 桌面端崩溃紧急修复 | v0.7.23 把 webServer 移出 inject 致 cordis Proxy 抛 `cannot get property "webServer" without inject`(未注入属性直接访问抛错而非 undefined),桌面端重启插件树加载失败;修复:恢复 webServer 到 inject(cordis 等宿主就绪再 apply)+ apply/register 守卫改 `ctx.reflect.get`(免 inject 读取、未提供返回 undefined 不抛错,未来 headless 移出 inject 也安全);真实 cordis + dsh-host-webserver 实测 API 路由 200 / 未知路径 404 / headless 静默不激活;714 测试全绿 |
314
+ | **v0.7.25** | ✅ 完成 | 工具兼容性加固 + 内容预览 + 巩固模型引导 | 新增 `memory_get` 工具(第 8 个模型工具,按 id 读单条记忆全文)+ `memory_search`/`memory_list` render 嵌入标题/元数据/正文预览(宿主只透传 render 文本时模型也能读到内容)+ 巩固模型选型引导(config 注释/README 分类声明);修复 memory_get execute 嵌套 output 的崩溃(userExecute is not a function)+ 工具重复注册去重(live patch reload)+ Standalone API 端口冲突重试 + client inject 声明对齐 + better-sidebar 集成加固;718 测试全绿 |
315
+ | **v0.7.26** | ✅ 完成 | 巩固 effort 陷阱根治 + LLM 连通性测试 | 记忆巩固 `UNSUPPORTED_REASONING_EFFORT` 根治(defaultEffort 陷阱:harness 省略 effort 时注入 `reasoning.defaultEffort`,默认档位不被模型支持则任何重试无效)→ 新增 `resolveDreamEffort` 经 `ctx.llm.resolveModelInfo()` 探测模型支持的档位(不支持的配置档位自动换用模型支持的默认/首个档位,无 reasoning 能力则省略字段);设置面板巩固/睡眠 6 字段补选型提示(引导非思考模型);新增 `GET /api/dsh-mneme/llm-providers`(宿主侧 provider/model 发现)+ `POST /api/dsh-mneme/test-model`(模型连通性测试,空 body 按巩固路由解析,密钥不经插件侧);732 测试全绿 |
316
+ | **v0.7.27** | ✅ 完成 | v0.7.26 端点遗漏补齐 | v0.7.26 发版树未含 `fix/dream-effort-trap` 分支:CHANGELOG/Release 宣告的 `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` 两个端点实际不在包内(面板「级联下拉 + 测试连通性」会 404);本版将分支 rebase 合入(PR #100),端点落地、面板连通性测试可用;733 测试全绿 |
317
+ | **v0.7.28** | ✅ 完成 | 连通性测试三连修 | `POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024(思考模型推理过程即可耗尽 16 token 预算,`reply` 恒空);连通性测试状态按巩固/睡眠路由各持一份(按钮不再串扰);面板挂载草稿补 `sleepProvider`/`sleepModel`(重挂载后睡眠路由不再显示回「跟随默认路由」,值一直有存);733 测试全绿 |
318
+ | **v0.7.29** | ✅ 完成 | 实体抽取路由契约修复 + 面板控件 + 反馈入口 | 实体抽取 LLM 路由契约修复(#108/#109):`createEntityStreamAdapter` 显式 provider/model 优先 + 兜底默认路由 + effort 拒绝自动去重试;设置面板新增实体抽取 provider/model/思考强度三控件;「帮助与反馈」卡片(GitHub issue 预填 + mailto + 浏览已知问题)+ `GET /api/dsh-mneme/info`;744 测试全绿 |
319
+ | **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 测试全绿 |
314
320
  | **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ 跨 workspace 记忆共享 |
315
321
 
316
322
  > 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
package/lib/api.js CHANGED
@@ -161,6 +161,16 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
161
161
  }
162
162
  });
163
163
 
164
+ // 反馈入口预填用的插件版本(面板「帮助与反馈」卡片拉取)。读取失败(打包
165
+ // 环境)返回 "unknown",链接照常可用,纯展示信息不拦截。
166
+ register({
167
+ kind: "exact",
168
+ path: "/api/dsh-mneme/info",
169
+ handler(req, res) {
170
+ sendJson(res, 200, { version: PACKAGE_VERSION });
171
+ }
172
+ });
173
+
164
174
  register({
165
175
  kind: "exact",
166
176
  path: "/api/dsh-mneme/list",
@@ -595,7 +605,13 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
595
605
  try {
596
606
  const stats = semantic?.vectorIndex?.getStats?.() ?? null;
597
607
  sendJson(res, 200, {
598
- embedProvider: embedder ? (embedder.constructor?.name ?? "unknown") : null,
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,
612
+ // Explicit display name first: the legacy OpenAI-compatible embedder
613
+ // is an object literal, so constructor.name would be "Object".
614
+ embedProvider: embedder ? (embedder.name ?? embedder.constructor?.name ?? "unknown") : null,
599
615
  modelHash: embedder?.modelHash ?? null,
600
616
  dimension: embedder?.dimension ?? null,
601
617
  reranker: semantic?.reranker ? "ready" : null,
package/lib/client.js CHANGED
@@ -239,6 +239,12 @@ window.__ModuleLoader__.load({
239
239
  "memory.settings.apiTokenPlaceholder": "留空 = 不鉴权(默认)",
240
240
  "memory.settings.apiTokenSave": "保存 Token",
241
241
  "memory.settings.apiTokenSaved": "Token 已保存",
242
+ "memory.settings.feedback.title": "帮助与反馈",
243
+ "memory.settings.feedback.desc": "遇到问题?先看看仓库已有的 issue,或直接把情况反馈过来:",
244
+ "memory.settings.feedback.newIssue": "GitHub 新建 issue(自动带上环境信息)",
245
+ "memory.settings.feedback.email": "邮件反馈(work@modusensus.space)",
246
+ "memory.settings.feedback.browse": "浏览仓库已知问题",
247
+ "memory.settings.feedback.hint": "反馈前先搜搜是否已有相同问题,能省一份重复的 issue~",
242
248
  "memory.settings.mode.title": "运行模式",
243
249
  "memory.settings.mode.desc": "轻量模式只保留核心的记忆读写与自动注入(关闭 autoDream 巩固、实体抽取、语义搜索等高级功能),适合只想「记住偏好」的轻量使用;标准模式开启全部功能。",
244
250
  "memory.settings.mode.light": "轻量",
@@ -276,6 +282,14 @@ window.__ModuleLoader__.load({
276
282
  "memory.features.hotMemoryEnabled.hint": "最近几轮对话原文随注入携带,不写入长期记忆",
277
283
  "memory.features.entityExtractionEnabled": "实体抽取",
278
284
  "memory.features.entityExtractionEnabled.hint": "从记忆中提取人物/项目/概念,图谱随之生长",
285
+ "memory.features.entityExtractionProvider": "实体抽取 Provider",
286
+ "memory.features.entityExtractionModel": "实体抽取模型",
287
+ "memory.features.entityExtractionReasoning": "实体抽取思考强度",
288
+ "memory.features.entityExtractionReasoning.none": "跟随默认",
289
+ "memory.features.entityExtractionReasoning.low": "低",
290
+ "memory.features.entityExtractionReasoning.medium": "中",
291
+ "memory.features.entityExtractionReasoning.high": "高",
292
+ "memory.features.entityExtractionModelHint": "Provider/模型留空 = 跟随主对话模型;思考强度 none = 服务商默认",
279
293
  "memory.features.codingRetrospect": "编码记忆蒸馏",
280
294
  "memory.features.codingRetrospect.hint": "用完整转录(含工具调用与报错)提炼踩坑、约束与被否决方案",
281
295
  "memory.features.rerankEnabled": "结果重排",
@@ -410,8 +424,10 @@ window.__ModuleLoader__.load({
410
424
  "memory.status.memories": "记忆总数",
411
425
  "memory.status.entities": "实体",
412
426
  "memory.status.vector": "向量索引",
413
- "memory.status.vectorOn": "已启用",
414
427
  "memory.status.vectorOff": "未启用",
428
+ "memory.status.vectorInit": "初始化中",
429
+ "memory.status.vectorInitHint": "embedder 不可达,正在重试",
430
+ "memory.status.vectorIndexed": "已索引 {n} / {m} 条",
415
431
  "memory.status.llm": "LLM 消耗",
416
432
  "memory.status.llmCalls": "近 7 天 · {n} 次调用",
417
433
  "memory.status.error": "加载失败"
@@ -528,6 +544,12 @@ window.__ModuleLoader__.load({
528
544
  "memory.settings.apiTokenPlaceholder": "Empty = no auth (default)",
529
545
  "memory.settings.apiTokenSave": "Save Token",
530
546
  "memory.settings.apiTokenSaved": "Token saved",
547
+ "memory.settings.feedback.title": "Help & feedback",
548
+ "memory.settings.feedback.desc": "Stuck? Search the existing issues, or tell us what happened:",
549
+ "memory.settings.feedback.newIssue": "Open a GitHub issue (environment info prefilled)",
550
+ "memory.settings.feedback.email": "Email feedback (work@modusensus.space)",
551
+ "memory.settings.feedback.browse": "Browse known issues",
552
+ "memory.settings.feedback.hint": "Search for an existing issue first — it saves a duplicate.",
531
553
  "memory.settings.mode.title": "Runtime mode",
532
554
  "memory.settings.mode.desc": "Light mode keeps only the core memory read/write and auto-injection (autoDream consolidation, entity extraction and semantic search are off) — for light use where you just want preferences remembered. Standard mode enables everything.",
533
555
  "memory.settings.mode.light": "Light",
@@ -565,6 +587,14 @@ window.__ModuleLoader__.load({
565
587
  "memory.features.hotMemoryEnabled.hint": "Carry the last few turns verbatim; never written to long-term memory",
566
588
  "memory.features.entityExtractionEnabled": "Entity extraction",
567
589
  "memory.features.entityExtractionEnabled.hint": "Extract people / projects / concepts so the graph grows by itself",
590
+ "memory.features.entityExtractionProvider": "Entity extraction provider",
591
+ "memory.features.entityExtractionModel": "Entity extraction model",
592
+ "memory.features.entityExtractionReasoning": "Entity extraction reasoning",
593
+ "memory.features.entityExtractionReasoning.none": "Follow default",
594
+ "memory.features.entityExtractionReasoning.low": "Low",
595
+ "memory.features.entityExtractionReasoning.medium": "Medium",
596
+ "memory.features.entityExtractionReasoning.high": "High",
597
+ "memory.features.entityExtractionModelHint": "Provider / model empty = follow the main conversation model; reasoning none = provider default",
568
598
  "memory.features.codingRetrospect": "Coding retrospection",
569
599
  "memory.features.codingRetrospect.hint": "Distill pitfalls, constraints and rejected solutions from full transcripts (tools and errors included)",
570
600
  "memory.features.rerankEnabled": "Reranking",
@@ -699,8 +729,10 @@ window.__ModuleLoader__.load({
699
729
  "memory.status.memories": "Total memories",
700
730
  "memory.status.entities": "Entities",
701
731
  "memory.status.vector": "Vector index",
702
- "memory.status.vectorOn": "Enabled",
703
732
  "memory.status.vectorOff": "Disabled",
733
+ "memory.status.vectorInit": "Initializing",
734
+ "memory.status.vectorInitHint": "embedder unreachable, retrying",
735
+ "memory.status.vectorIndexed": "Indexed {n} / {m} items",
704
736
  "memory.status.llm": "LLM Usage",
705
737
  "memory.status.llmCalls": "Last 7 days · {n} calls",
706
738
  "memory.status.error": "Failed to load"
@@ -950,6 +982,7 @@ window.__ModuleLoader__.load({
950
982
  ".mneme-numinput:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
951
983
  // 字符串开关的输入框(provider/model 等)与其子块容器
952
984
  ".mneme-strinput{box-sizing:border-box;width:240px;max-width:60%;height:30px;padding:0 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;outline:none;text-align:left;transition:border-color .12s,box-shadow .12s}",
985
+ ".mneme-feed-link{display:block;color:var(--dsw-alias-brand,var(--dsw-alias-label-primary));text-decoration:none;font-size:13px;line-height:20px;padding:4px 2px;border-radius:6px;transition:opacity .12s}.mneme-feed-link:hover{opacity:.8;text-decoration:underline}",
953
986
  ".mneme-strinput:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
954
987
  ".mneme-featsub{margin:2px 0 8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;display:flex;flex-direction:column;gap:6px}",
955
988
  ".mneme-featsub .mneme-featnum{padding:6px 0;border-bottom:none}",
@@ -1592,8 +1625,10 @@ window.__ModuleLoader__.load({
1592
1625
  const FEATURE_ADVANCED_BOOLS = ["hybridInject", "selectiveInjectEnabled", "adaptiveThresholdEnabled", "reflectionUpdateEnabled", "reflectionFailureTracking", "conflictFreezeEnabled", "trustEpistemicWeighting"];
1593
1626
  // 字符串键(blur/Enter 提交,空串合法 = 跟随默认):巩固模型与语义
1594
1627
  // 检索路线。embedProvider 是枚举,用下拉单独渲染。
1595
- const FEATURE_STRINGS = ["dreamProvider", "dreamModel", "sleepProvider", "sleepModel", "localEmbedModel", "ollamaBaseUrl", "ollamaModel"];
1628
+ const FEATURE_STRINGS = ["dreamProvider", "dreamModel", "sleepProvider", "sleepModel", "entityExtractionProvider", "entityExtractionModel", "localEmbedModel", "ollamaBaseUrl", "ollamaModel"];
1596
1629
  const EMBED_PROVIDERS = ["openai", "local", "ollama"];
1630
+ // 实体抽取思考强度(issue #109):与后端 FEATURE_FLAG_ENUMS 枚举对齐。
1631
+ const ENTITY_REASONING = ["none", "low", "medium", "high"];
1597
1632
 
1598
1633
  function FeatureRow({ name, hint, on, disabled, onToggle }) {
1599
1634
  return h("div", { className: "mneme-featrow" },
@@ -1623,9 +1658,10 @@ window.__ModuleLoader__.load({
1623
1658
  // 巩固/睡眠模型路由下拉的数据源:GET /llm-providers 探测(云端 v0.7.26+
1624
1659
  // 的插件侧端点)。null = 端点不可用(404/失败)→ 回退纯文本输入,不挡旧后端。
1625
1660
  const [routes, setRoutes] = useState(null);
1626
- // 连通性测试结果(巩固/睡眠各一份,互不串扰):{ running: true } | { ok, ms, detail }
1661
+ // 连通性测试结果(巩固/睡眠/实体抽取各一份,互不串扰):{ running: true } | { ok, ms, detail }
1627
1662
  const [dreamTest, setDreamTest] = useState(null);
1628
1663
  const [sleepTest, setSleepTest] = useState(null);
1664
+ const [entityTest, setEntityTest] = useState(null);
1629
1665
 
1630
1666
  useEffect(() => {
1631
1667
  let cancelled = false;
@@ -1825,6 +1861,26 @@ window.__ModuleLoader__.load({
1825
1861
  h("div", { className: "mneme-featsubhint" }, t("memory.features.sleepModelHint"))
1826
1862
  );
1827
1863
 
1864
+ // 实体抽取路由与思考强度(issue #109):entityExtractionEnabled 开着才
1865
+ // 展开。provider/model 级联下拉 + 连通性测试(同巩固/睡眠),reasoning
1866
+ // 枚举即时提交;旧后端(/llm-providers 404)回退纯文本输入。
1867
+ const entitySub = eff.entityExtractionEnabled && h("div", { className: "mneme-featsub" },
1868
+ Array.isArray(routes)
1869
+ ? routeSelects("entityExtractionProvider", "entityExtractionModel", entityTest, setEntityTest)
1870
+ : h(react.Fragment, null, strRow("entityExtractionProvider"), strRow("entityExtractionModel")),
1871
+ h("div", { className: "mneme-featnum" },
1872
+ h("span", { className: "mneme-featnumlabel" }, t("memory.features.entityExtractionReasoning")),
1873
+ h("select", {
1874
+ className: "mneme-select",
1875
+ value: eff.entityExtractionReasoning || "none",
1876
+ disabled: busy,
1877
+ onChange: (e) => put({ entityExtractionReasoning: e.target.value })
1878
+ },
1879
+ ENTITY_REASONING.map((r) => h("option", { key: r, value: r }, t(`memory.features.entityExtractionReasoning.${r}`))))
1880
+ ),
1881
+ h("div", { className: "mneme-featsubhint" }, t("memory.features.entityExtractionModelHint"))
1882
+ );
1883
+
1828
1884
  if (error && !state) return h("section", { className: "mneme-set-card" },
1829
1885
  h("div", { className: "mneme-set-title" }, t("memory.features.title")),
1830
1886
  h("div", { className: "mneme-set-hint" }, error));
@@ -1841,7 +1897,7 @@ window.__ModuleLoader__.load({
1841
1897
  FEATURE_GROUPS.map((g) => h(react.Fragment, { key: g.key },
1842
1898
  h("div", { className: "mneme-featgroup" }, t(`memory.features.${g.key}`)),
1843
1899
  g.items.map(boolRow),
1844
- g.key === "group.enhance" && embedSub,
1900
+ g.key === "group.enhance" && h(react.Fragment, null, embedSub, entitySub),
1845
1901
  g.key === "group.dream" && h(react.Fragment, null, dreamSub, sleepSub)
1846
1902
  )),
1847
1903
  h("div", { className: "mneme-featgroup" },
@@ -1867,6 +1923,18 @@ window.__ModuleLoader__.load({
1867
1923
  return `${s.slice(0, 5)}${"•".repeat(10)}${s.slice(-4)}`;
1868
1924
  }
1869
1925
 
1926
+ // 反馈预填用的平台标签:从 userAgent 提一个粗粒度 OS 名,够定位问题即可,
1927
+ // 不引第三方解析库。浏览器/Electron webview 里都能取到。
1928
+ const platformLabel = () => {
1929
+ const ua = (typeof navigator !== "undefined" && navigator.userAgent) || "";
1930
+ if (/Windows NT/.test(ua)) return "Windows";
1931
+ if (/Mac OS X/.test(ua)) return "macOS";
1932
+ if (/Android/.test(ua)) return "Android";
1933
+ if (/iPhone|iPad/.test(ua)) return "iOS";
1934
+ if (/Linux/.test(ua)) return "Linux";
1935
+ return "Unknown";
1936
+ };
1937
+
1870
1938
  function SettingsContent({ t }) {
1871
1939
  const [profile, setProfile] = react.useState("");
1872
1940
  const [rules, setRules] = react.useState([]);
@@ -1883,6 +1951,8 @@ window.__ModuleLoader__.load({
1883
1951
  (typeof window !== "undefined" && window.localStorage) ? window.localStorage.getItem("dsh-mneme-api-token") || "" : ""
1884
1952
  );
1885
1953
  const [apiTokenSaved, setApiTokenSaved] = react.useState(false);
1954
+ // 反馈入口的插件版本(GET /info)。失败保持 "unknown",链接仍可用。
1955
+ const [pkgVersion, setPkgVersion] = react.useState("unknown");
1886
1956
  // 运行模式 — light vs standard; null = still loading. The card keeps
1887
1957
  // its own busy/saved/error state so it never blocks the others.
1888
1958
  const [mode, setMode] = react.useState(null);
@@ -1918,6 +1988,27 @@ window.__ModuleLoader__.load({
1918
1988
 
1919
1989
  react.useEffect(() => { load(); }, [load]);
1920
1990
 
1991
+ // 帮助与反馈卡片的版本号:独立小请求,失败不影响其它卡片。
1992
+ react.useEffect(() => {
1993
+ let cancelled = false;
1994
+ apiFetch("/api/dsh-mneme/info")
1995
+ .then((res) => (res.ok ? res.json() : null))
1996
+ .then((j) => { if (!cancelled && j && typeof j.version === "string") setPkgVersion(j.version); })
1997
+ .catch(() => {});
1998
+ return () => { cancelled = true; };
1999
+ }, []);
2000
+
2001
+ // 反馈链接预填:环境信息(插件版本 + 平台)+ 问题描述骨架。邮件主题带
2002
+ // 版本/平台方便归档,正文同款模板;纯链接零后端成本。
2003
+ const feedbackEnv = `**插件版本**: ${pkgVersion}\n**平台**: ${platformLabel()}\n`;
2004
+ const feedbackBody = feedbackEnv + "**问题描述**:\n- 期望行为:\n- 实际行为:\n- 复现步骤:\n";
2005
+ const issueHref = "https://github.com/modusensus/dsh-mneme/issues/new?title="
2006
+ + encodeURIComponent("[dsh-mneme] 问题反馈")
2007
+ + "&body=" + encodeURIComponent(feedbackBody);
2008
+ const mailHref = "mailto:work@modusensus.space?subject="
2009
+ + encodeURIComponent(`[dsh-mneme 反馈] ${pkgVersion} / ${platformLabel()}`)
2010
+ + "&body=" + encodeURIComponent(feedbackBody);
2011
+
1921
2012
  // Runtime mode + external API — two independent fetches: one failing
1922
2013
  // endpoint only errors its own card, never the other one.
1923
2014
  react.useEffect(() => {
@@ -2318,6 +2409,19 @@ window.__ModuleLoader__.load({
2318
2409
  h("button", { className: "mneme-btn", onClick: saveToken }, t("memory.settings.apiTokenSave"))
2319
2410
  ),
2320
2411
  apiTokenSaved && h("div", { style: { marginTop: 8 } }, h("span", { className: "mneme-saved" }, t("memory.settings.apiTokenSaved")))
2412
+ ),
2413
+ // 帮助与反馈 — 反馈问题三入口(新建 issue 预填 / 邮件 / 浏览已知问题)。
2414
+ // 纯前端链接零后端成本;插件版本来自 /info,平台取 userAgent。GitHub
2415
+ // 仓库当前没有 issue 模板,故用 issues/new?title=&body= 直接预填。
2416
+ h("section", { className: "mneme-set-sec" },
2417
+ h("div", { className: "mneme-set-title" }, t("memory.settings.feedback.title")),
2418
+ h("div", { className: "mneme-set-desc" }, t("memory.settings.feedback.desc")),
2419
+ h("div", { style: { display: "flex", flexDirection: "column", gap: 2 } },
2420
+ h("a", { className: "mneme-feed-link", href: issueHref, target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.newIssue")),
2421
+ h("a", { className: "mneme-feed-link", href: mailHref }, t("memory.settings.feedback.email")),
2422
+ h("a", { className: "mneme-feed-link", href: "https://github.com/modusensus/dsh-mneme/issues", target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.browse"))
2423
+ ),
2424
+ h("div", { className: "mneme-featsubhint", style: { marginTop: 8 } }, t("memory.settings.feedback.hint"))
2321
2425
  )
2322
2426
  );
2323
2427
  }
@@ -2483,22 +2587,49 @@ window.__ModuleLoader__.load({
2483
2587
 
2484
2588
  // 向量索引 — whether semantic recall is switched on.
2485
2589
  function VectorStatusCard({ t }) {
2486
- const [state, setState] = useState({ loading: true, error: false, enabled: false });
2590
+ const [state, setState] = useState({ loading: true, error: false, provider: null, ready: null, dimension: 0, embedded: 0, total: 0 });
2487
2591
  useEffect(() => {
2488
2592
  let cancelled = false;
2489
- apiFetch("/api/dsh-mneme/vector-config")
2593
+ // #118: /vector-config is secret-bearing (401 without a stored token →
2594
+ // "加载失败") and its `enabled` only reflects the OpenAI-compat external
2595
+ // service, so ollama/local mode always showed "未启用". /semantic is open
2596
+ // and covers every embedder provider + index stats.
2597
+ apiFetch("/api/dsh-mneme/semantic")
2490
2598
  .then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
2491
- .then((j) => { if (!cancelled) setState({ loading: false, error: false, enabled: !!j.config?.enabled }); })
2492
- .catch(() => { if (!cancelled) setState({ loading: false, error: true, enabled: false }); });
2599
+ .then((j) => {
2600
+ if (cancelled) return;
2601
+ setState({
2602
+ loading: false,
2603
+ error: false,
2604
+ provider: j?.embedProvider || null,
2605
+ ready: j?.ready ?? null,
2606
+ // Legacy OpenAI embedder exposes dimension only after its first
2607
+ // successful embed — fall back to the index stats' dimension.
2608
+ dimension: Number(j?.dimension ?? j?.index?.dimension ?? 0),
2609
+ embedded: Number(j?.index?.embeddedCount ?? 0),
2610
+ total: Number(j?.index?.totalCount ?? 0)
2611
+ });
2612
+ })
2613
+ .catch(() => { if (!cancelled) setState({ loading: false, error: true, provider: null, ready: null, dimension: 0, embedded: 0 }); });
2493
2614
  return () => { cancelled = true; };
2494
2615
  }, []);
2616
+ const off = !state.provider;
2617
+ const pending = !off && state.ready !== true;
2618
+ const num = off
2619
+ ? t("memory.status.vectorOff")
2620
+ : pending
2621
+ ? t("memory.status.vectorInit")
2622
+ : `${state.provider.replace(/Embedder$/, "")} · ${state.dimension}D`;
2623
+ const cap = pending
2624
+ ? t("memory.status.vectorInitHint")
2625
+ : off ? "" : t("memory.status.vectorIndexed").replace("{n}", state.embedded).replace("{m}", state.total);
2495
2626
  return h(StatusCard, {
2496
2627
  t,
2497
2628
  title: t("memory.status.vector"),
2498
2629
  loading: state.loading,
2499
2630
  error: state.error,
2500
- num: state.enabled ? t("memory.status.vectorOn") : t("memory.status.vectorOff"),
2501
- cap: ""
2631
+ num,
2632
+ cap
2502
2633
  });
2503
2634
  }
2504
2635
 
package/lib/config.js CHANGED
@@ -178,6 +178,19 @@ export const Config = z.object({
178
178
  searchSemanticDedup: z.boolean().default(false),
179
179
  searchSemanticDedupThreshold: z.number().min(0.5).max(1).default(0.95),
180
180
 
181
+ // Recall fusion recipe (plan #1): how the keyword/vector/BM25 ranked lists
182
+ // are combined into the final ranking. `blend` (default) is the legacy
183
+ // behavior — weighted sum for vector/hybrid, union backfill for auto —
184
+ // unchanged. `rrf` (Reciprocal Rank Fusion) and `minmax` (min-max normalized
185
+ // weighted sum) are rank/scale-aware recipes that fix the unit mismatch the
186
+ // issue describes (raw cosine vs keyword score vs normalized IDF are added
187
+ // directly). Off by default so existing behavior holds exactly.
188
+ recallFusion: z.union([z.const("blend"), z.const("rrf"), z.const("minmax")]).default("blend"),
189
+ // Attach a `signals` object { keyword, vector, bm25, final } to each search
190
+ // result for transparency/debugging (plan #2). Default off; when on it only
191
+ // decorates the returned rows, never changes the ranking.
192
+ signalTransparency: z.boolean().default(false),
193
+
181
194
  // --- semantic: rerank layer (v0.2) --------------------------------------
182
195
  // Opt-in by default (item ⑥): the local cross-encoder pulls in onnxruntime
183
196
  // (transformers.js) at init, so a bare install must not load it. Only an
@@ -207,9 +220,23 @@ export const Config = z.object({
207
220
  // The storage layer (entities/entity_attrs/entity_relations tables + CRUD)
208
221
  // is always available regardless of this flag.
209
222
  entityExtractionEnabled: z.boolean().default(false),
223
+ // Optional provider override for entity extraction; empty = use the caller's
224
+ // default provider/model. Combined with entityExtractionModel — provider
225
+ // without model (or vice versa) falls through to the caller default.
226
+ entityExtractionProvider: z.string().default(""),
210
227
  // Optional model override for entity extraction; empty = use the caller's
211
228
  // default provider/model.
212
229
  entityExtractionModel: z.string().default(""),
230
+ // Reasoning effort for entity extraction (issue #109), mirrors
231
+ // dreamReasoningEffort: 'none' (default) omits the field / provider default;
232
+ // low/medium/high passed through. A provider that rejects the effort retries
233
+ // once without it, so opting in is safe to experiment with.
234
+ entityExtractionReasoning: z.union([
235
+ z.const("low"),
236
+ z.const("medium"),
237
+ z.const("high"),
238
+ z.const("none")
239
+ ]).default("none"),
213
240
  // Cap on entities per extraction pass and attributes per entity.
214
241
  entityExtractionMaxEntities: z.natural().min(1).max(20).default(10),
215
242
  entityExtractionMaxAttrs: z.natural().min(1).max(50).default(20),
package/lib/embedding.js CHANGED
@@ -97,6 +97,9 @@ export function createEmbedder({ store, settings, logger, vectorIndex }) {
97
97
  }
98
98
 
99
99
  return {
100
+ // Display name for /semantic: a literal's constructor.name is "Object",
101
+ // which the status card would render verbatim.
102
+ name: "OpenAI",
100
103
  /** Fire-and-forget re-embed of a memory after any write. */
101
104
  schedule(memory) {
102
105
  if (!memory?.id) return;
@@ -146,16 +146,23 @@ export async function extractEntities(memory, { store, config, callLLM, logger }
146
146
  return { ok: false, error: "Invalid memory: missing content" };
147
147
  }
148
148
 
149
- const model = config.entityExtractionModel || null;
150
149
  const systemPrompt = buildSystemPrompt(config);
151
150
  const userText = buildUserMessage(memory.content);
152
-
151
+
153
152
  const messages = [
154
153
  { role: "system", content: [{ type: "text", text: systemPrompt }] },
155
154
  { role: "user", content: [{ type: "text", text: userText }] }
156
155
  ];
157
-
158
- const options = model ? { model } : {};
156
+
157
+ // Issue #109: optional provider/model override + reasoning effort are
158
+ // passed through to callLLM's options; the index.js adapter maps them onto
159
+ // the route (or falls back to the caller's default model). Empty provider/
160
+ // model both mean "use the caller default".
161
+ const options = {};
162
+ if (config.entityExtractionProvider) options.provider = config.entityExtractionProvider;
163
+ if (config.entityExtractionModel) options.model = config.entityExtractionModel;
164
+ const reasoning = config.entityExtractionReasoning;
165
+ if (reasoning && reasoning !== "none") options.reasoningEffort = reasoning;
159
166
  const llmResponse = await callLLM(messages, options);
160
167
 
161
168
  if (!llmResponse) {
@@ -219,7 +226,7 @@ export async function extractEntities(memory, { store, config, callLLM, logger }
219
226
  to_entity: toId,
220
227
  relation_type: rel.type,
221
228
  memory_id: memory.id,
222
- metadata: { model: model || "default" }
229
+ metadata: { model: options.model || "default" }
223
230
  });
224
231
  savedRelations.push(saved);
225
232
  } catch (err) {