@modusensus/dsh-mneme 0.7.27 → 0.7.29

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.en.md CHANGED
@@ -174,6 +174,7 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
174
174
 
175
175
  | Version | Highlights |
176
176
  |------|------|
177
+ | **v0.7.28** | Connectivity test triple fix: the `POST /api/dsh-mneme/test-model` minimal call raised `maxTokens` 16→1024 — a thinking model's reasoning alone can exhaust a 16-token budget, leaving `reply` always empty ("actually answered ok, not just connected" never held for thinking models; billed on actual usage, manual button, no amplification cost); connectivity test state is now per-route (dream/sleep each hold their own) — previously a single shared state made both groups show "testing/result" and disable each other when either button was clicked; the panel's mount-time draft now includes `sleepProvider`/`sleepModel` — values were always saved server-side, but after a remount (tab switch / re-enter) the dropdowns showed "follow default route" as if the setting had been reset; the saved values restore without re-entering them; 733 tests green |
177
178
  | **v0.7.27** | v0.7.26 endpoint backfill: the v0.7.26 release tree did not include the `fix/dream-effort-trap` branch, so the `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` endpoints announced in its CHANGELOG/Release were absent from the package (the panel's cascading dropdown + connectivity test would 404); this release rebases and merges the branch (PR #100), landing the endpoints — panel connectivity test now works; 733 tests green |
178
179
  | **v0.7.26** | Consolidation `UNSUPPORTED_REASONING_EFFORT` root-cause fix + dream/sleep model connectivity test: root-caused the **defaultEffort trap** — the harness injects `reasoning.defaultEffort` when effort is omitted, so if that default tier itself is unsupported, retrying without effort never helps (whatever effort is sent gets rejected); fix: new `resolveDreamEffort` proactively queries `ctx.llm.resolveModelInfo()` for the model's supported effort tiers and sends a supported one — an unsupported configured tier auto-falls back to the model's default/first supported tier, and the field is omitted when the model declares no reasoning capability. Settings panel adds selection hints on 6 dream/sleep model fields (guiding toward non-thinking models so they don't burn the token budget on reasoning). New `GET /api/dsh-mneme/llm-providers` (host-side provider/model discovery; keys never touch the plugin side) + `POST /api/dsh-mneme/test-model` (connectivity test; empty body resolves via the consolidation route and returns `modelId`); 732 tests green |
179
180
  | **v0.7.25** | Tool-compat hardening + `memory_get`/render content preview + consolidation-model guide: new `memory_get` tool (8th model tool, read a single memory's full body by id) + `memory_search`/`memory_list` render now embeds title/metadata/body preview (the model reads actual memory content even when the host only forwards render text) + consolidation-model selection guide (config comments/README classification, non-thinking vs thinking). Fixed `memory_get` execute nested inside output crashing with `userExecute is not a function` (previous tests only counted tool names, never executed it) + tool de-dup on live patch reload + Standalone API port-collision retry (EADDRINUSE) + client inject declaration alignment + better-sidebar hardening; 718 tests green |
@@ -234,8 +235,12 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
234
235
  | v0.7.20 | ✅ Done | Heat restore + phase-two frontend + better-sidebar fix | Heat model fully restored (issue #87, backported from v0.7.10: power-law decay + TYPE_DECAY + sleep heat-combined dual protection + entity heat projection) + acceptance checklist landed (heatEnabled default OFF / feature_flags 31 keys / lightMode linkage / sleep demotion audit / updated_at⊥last_accessed_at contract) + phase-two frontend (/list heat projection, HeatBadge three-tier, order=heat page-local sort) + better-sidebar fix (issue #88: inner dynamic sub-plugin); 685 tests green |
235
236
  | v0.7.21 | ✅ Done | effort-fallback stream fix | Catch-based effort fallback was dead code on the stream path (dsh-llm rc.1 turns adapter failures into a terminal error finish chunk instead of a throw) → `streamText` captures the finish-chunk cause (`describeStreamFailure`) + `withEffortFallback` gains a `getStreamError` accessor (retries without effort when rejected) + `runAuditedLlm` supports `spec.streamError` (real cause in audit); 688 tests green |
236
237
  | v0.7.22 | ✅ Done | skipInvalid tolerant-validation restore (issue #89) + autoDream throttle | Restored v0.6.9 skipInvalid dual-track structure (lost in the v0.7.11 rewrite): `dreamSkipInvalid` skips individual invalid decisions + applies the valid subset + marks the run degraded; `allowCrossTypeMerge` explicitly relaxes cross-type merging; weak models (qwen3.8-flash) with jittery schema compliance no longer fail the whole batch. New `dreamMinIntervalMinutes` (0–10080, default 0) minimum trigger interval — failed/degraded runs also consume it. Strict mode / sleep path unchanged; feature_flags whitelist 34 keys; 696 tests green |
238
+ | v0.7.23 | ✅ Done | "Consolidation keeps failing" root fix + empty-body part 2 + skipInvalid splice fix | A legal empty decision array `[]` no longer fails the whole run (CONSOLIDATION_PROMPT explicitly allows no output when nothing needs changing, yet `validateDecisions` hard-rejected it as non-empty — model-agnostic, ChatGPT/Claude hit the same trap; fix: empty array short-circuits to `ok:true` no-op instead of tripping the implicit-keep coverage check); `dreamMaxTokens` default 8192→32768 (thinking models burn the whole budget on reasoning); skipInvalid splice residue bug (length equality ≠ content equality, skipped decisions leaked into apply/audit); 712 tests green |
239
+ | v0.7.24 | ✅ Done | DSH Desktop plugin-tree load crash fix (v0.7.23 regression) | cordis 4's ctx is a Proxy — accessing a property not declared in `inject` throws `cannot get property "webServer" without inject` (not `undefined`), and removing webServer from inject made Desktop crash on restart; fix: restored webServer to inject (cordis applies the plugin only after the host service is ready) + apply/register guard switched to `ctx.reflect.get` (inject-free read, returns `undefined` when absent, never throws); verified with a real cordis + dsh-host-webserver plugin (API routes 200 / unknown path 404 / headless silently inactive); 714 tests green |
237
240
  | v0.7.25 | ✅ Done | Tool-compat hardening + content preview + consolidation-model guide | New `memory_get` tool (8th model tool, full-body read by id) + `memory_search`/`memory_list` render embeds title/metadata/body preview + consolidation-model selection guide; fixed `memory_get` execute nested inside output (`userExecute is not a function`) + tool de-dup on live patch reload + Standalone API port-collision retry + client inject alignment + better-sidebar hardening; 718 tests green |
238
241
  | **v0.7.26** | ✅ Done | Consolidation effort-trap root fix + LLM connectivity test | `UNSUPPORTED_REASONING_EFFORT` root cause (defaultEffort trap: harness injects `reasoning.defaultEffort` when effort is omitted, so an unsupported default can never be retried) → `resolveDreamEffort` proactively queries `ctx.llm.resolveModelInfo()` for the model's supported effort tiers (unsupported configured tier falls back to the model's default/first supported tier; field omitted when the model declares no reasoning capability); settings panel adds selection hints on 6 dream/sleep model fields; new `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` (connectivity test; empty body resolves via the consolidation route; keys never touch the plugin side); 732 tests green |
242
+ | **v0.7.27** | ✅ Done | v0.7.26 endpoint backfill | The v0.7.26 release tree did not include the `fix/dream-effort-trap` branch, so the `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` endpoints announced in its CHANGELOG/Release were absent from the package (the panel's cascading dropdown + connectivity test would 404); this release rebases and merges the branch (PR #100), landing the endpoints — panel connectivity test now works; 733 tests green |
243
+ | **v0.7.28** | ✅ Done | Connectivity test triple fix | `POST /api/dsh-mneme/test-model` minimal call `maxTokens` 16→1024 (a thinking model's reasoning alone can exhaust a 16-token budget, leaving `reply` always empty); connectivity-test state now per-route (dream/sleep each hold their own — buttons no longer cross-disable); mount-time draft now includes `sleepProvider`/`sleepModel` (values were always saved server-side, but after a remount the dropdowns showed "follow default route" as if reset); 733 tests green |
239
244
  | **v0.8.0** | 🚧 Planned (late Sep) | Graph enhancement | Interest-drift visualization + scope isolation (issue #17) + cross-workspace sharing |
240
245
 
241
246
  > All new capabilities ship as **toggleable features** (enabled/disabled via configuration), conservatively on by default and never breaking existing behavior. The `failure_memories` table and the autoDream decision engine have already paved the way for future reflective growth.
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-744%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.29** | 实体抽取路由契约修复 + 面板控件 + 帮助与反馈:`streamEntityText` 抽取为可测试导出的 `createEntityStreamAdapter`——显式 `provider`/`model` 优先、缺省兜底读默认路由选择;实体抽取思考强度 `entityExtractionReasoning` 被模型拒绝时自动去 effort 重试(与 autoDream/sleep 同一降级策略);设置面板新增实体抽取 `provider`/`model`/思考强度三控件;面板底部新增「帮助与反馈」卡片(GitHub issue 预填 + mailto `work@modusensus.space` + 浏览已知问题)+ 配套 `GET /api/dsh-mneme/info`;744 测试全绿 |
219
+ | **v0.7.28** | 连通性测试三连修:`POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024——思考模型的推理过程足以耗尽 16 token 预算致 `reply` 恒为空(「真的答了 ok 而非只报通」对思考模型不成立;按实际用量计费,手动按钮无放大成本);连通性测试状态按巩固/睡眠路由各持一份——此前共享单份,点任一「测试连通性」按钮两组同显「测试中/结果」且互相禁用;面板挂载草稿补 `sleepProvider`/`sleepModel`——后端一直保存正常,但重挂载(切页签/退出重进)后下拉恒显「跟随默认路由」,看起来像设置被重置,补键后正常回填(历史已存值无需重填);733 测试全绿 |
218
220
  | **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 测试全绿 |
219
221
  | **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 测试全绿 |
220
222
  | **v0.7.25** | 工具兼容性加固 + `memory_get`/render 内容预览 + 巩固模型引导:新增 `memory_get` 工具(第 8 个模型工具,按 id 读单条记忆全文)+ `memory_search`/`memory_list` render 嵌入标题/元数据/正文预览(宿主只透传 render 文本时模型也能直接读到记忆内容)+ 巩固模型选型引导(config 注释/README 分类声明,非思考 vs 思考模型差异);修复 `memory_get` execute 嵌套进 output 的崩溃(`userExecute is not a function`,此前测试只数工具名从未执行该工具而掩盖)+ 工具重复注册去重(live patch reload)+ Standalone API 端口冲突重试(EADDRINUSE)+ client inject 声明对齐 + better-sidebar 集成加固;718 测试全绿 |
@@ -290,9 +292,9 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
290
292
  | **v0.7.3** | ✅ 完成 | issue #38 新功能 | 左下角入口按钮可选开关 `showSidebarTrigger`(默认开,settings-over-config);Web 面板设置一键关闭,与 dsh-cost-meter 等 footer 插件冲突可隐藏按钮、记忆库标签不受影响;776 测试全绿 |
291
293
  | **v0.7.4** | ✅ 完成 | issue #40 + #41 修复 | 注入边界 run-based 花括号转义(`{{a}}`→`{\{a\}\}`、奇数连续如 `{{{a}}}` 不残留字面,`escapePromptVariables` 默认开)+ 记忆窗口顶栏左对齐、关闭按钮避开宿主窗口控制按钮区;782 测试全绿 |
292
294
  | **v0.7.5** | ✅ 完成 | 分层记忆类型 + 总览视图 | 借鉴 meow-memory 分层概念、贴合单表架构:新增 `user`(用户画像)/`fact`(原子事实)类型,注入/镜像/梦境/质量过滤全链路打通;Web 面板「总览」视图(分层卡片 + 用户画像卡 + 类型分布 + 近 7 天趋势);`/api/dsh-mneme/stats` 端点;kimi-k2.7-code 复验;790 测试全绿 |
295
+ | **v0.7.6** | ✅ 完成 | issue #48 修复 | 四工具统一 `service.resolveMemoryId`:截断/前缀短 id 也能精确操作(精确命中优先、唯一前缀解析、多命中拒绝并列出候选、`memory_delete` 未命中幂等返回 + `logger.warn` 留痕);Web bundle `client.js` 改 src 正源;801 测试全绿 |
293
296
  | **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 测试全绿 |
294
297
  | **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 测试全绿 |
295
- | **v0.7.6** | ✅ 完成 | issue #48 修复 | 四工具统一 `service.resolveMemoryId`:截断/前缀短 id 也能精确操作(精确命中优先、唯一前缀解析、多命中拒绝并列出候选、`memory_delete` 未命中幂等返回 + `logger.warn` 留痕);Web bundle `client.js` 改 src 正源;801 测试全绿 |
296
298
  | **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 测试全绿 |
297
299
  | **v0.7.10** | ✅ 完成 | Web 面板体验升级 | 记忆类型色点体系 + 图谱画布平移/缩放(0.5x–3x 光标锚定)+ 设置页分区重排 + 侧边栏同标签冲突修复 + 详情 meta 精排 + 只读端点 `GET /api/dsh-mneme/entities`;815 测试全绿 |
298
300
  | **v0.7.11** | ✅ 完成 | 记忆库面板改版 | 按月分页 + 无限滚动 + 搜索全局化 + 30s 静默刷新 + 状态子页四卡 + 删除两步确认 + 重要性过滤芯片 + 内联 Lucide 图标;bundle 默认开实体抽取;修复 issue#72 图谱节点裁剪 + issue#59 autoSummarize 垫片;595 测试全绿 |
@@ -305,11 +307,14 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
305
307
  | **v0.7.18** | ✅ 完成 | 生态第一步 + 查询收敛 | better-sidebar 软集成(inject 声明 + optional peer `dsh-better-sidebar` + registerTab 复用四视图,未装安全跳过;窄容器 `@container` 适配)+ `/list?deposited=only` 沉淀视图(receipt_chain ∪ source=dream)+ 记忆库沉淀/已归档筛选 chip + 状态页仪表盘化(统计 + 查看全部跳转预置筛选)+ 抽屉归档记忆「恢复」;667 测试全绿 |
306
308
  | **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 测试全绿 |
307
309
  | **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 测试全绿 |
308
- | **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 测试全绿 |
309
- | **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 测试全绿 |
310
- | **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 测试全绿 |
311
- | **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 测试全绿 |
312
310
  | **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 测试全绿 |
311
+ | **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 测试全绿 |
312
+ | **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 测试全绿 |
313
+ | **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 测试全绿 |
314
+ | **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 测试全绿 |
315
+ | **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 测试全绿 |
316
+ | **v0.7.28** | ✅ 完成 | 连通性测试三连修 | `POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024(思考模型推理过程即可耗尽 16 token 预算,`reply` 恒空);连通性测试状态按巩固/睡眠路由各持一份(按钮不再串扰);面板挂载草稿补 `sleepProvider`/`sleepModel`(重挂载后睡眠路由不再显示回「跟随默认路由」,值一直有存);733 测试全绿 |
317
+ | **v0.7.29** | ✅ 完成 | 实体抽取路由契约修复 + 面板控件 + 反馈入口 | 实体抽取 LLM 路由契约修复(#108/#109):`createEntityStreamAdapter` 显式 provider/model 优先 + 兜底默认路由 + effort 拒绝自动去重试;设置面板新增实体抽取 provider/model/思考强度三控件;「帮助与反馈」卡片(GitHub issue 预填 + mailto + 浏览已知问题)+ `GET /api/dsh-mneme/info`;744 测试全绿 |
313
318
  | **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ 跨 workspace 记忆共享 |
314
319
 
315
320
  > 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`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",
@@ -296,7 +306,10 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
296
306
  provider,
297
307
  model,
298
308
  purpose: "dsh-mneme-connectivity-test",
299
- maxTokens: 16,
309
+ // 给思考模型留出「思考完还有正文」的空间:16 会被 reasoning 全部吃掉,
310
+ // reply 恒为空,「真的答了 ok」对思考模型不成立。按实际用量计费,手动
311
+ // 按钮无放大风险。
312
+ maxTokens: 1024,
300
313
  ...(reasoningEffort && reasoningEffort !== "none" ? { reasoningEffort } : {}),
301
314
  messages: [{ role: "system", content: [{ type: "text", text: "Reply with exactly one word: ok" }] }]
302
315
  })) {
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": "结果重排",
@@ -528,6 +542,12 @@ window.__ModuleLoader__.load({
528
542
  "memory.settings.apiTokenPlaceholder": "Empty = no auth (default)",
529
543
  "memory.settings.apiTokenSave": "Save Token",
530
544
  "memory.settings.apiTokenSaved": "Token saved",
545
+ "memory.settings.feedback.title": "Help & feedback",
546
+ "memory.settings.feedback.desc": "Stuck? Search the existing issues, or tell us what happened:",
547
+ "memory.settings.feedback.newIssue": "Open a GitHub issue (environment info prefilled)",
548
+ "memory.settings.feedback.email": "Email feedback (work@modusensus.space)",
549
+ "memory.settings.feedback.browse": "Browse known issues",
550
+ "memory.settings.feedback.hint": "Search for an existing issue first — it saves a duplicate.",
531
551
  "memory.settings.mode.title": "Runtime mode",
532
552
  "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
553
  "memory.settings.mode.light": "Light",
@@ -565,6 +585,14 @@ window.__ModuleLoader__.load({
565
585
  "memory.features.hotMemoryEnabled.hint": "Carry the last few turns verbatim; never written to long-term memory",
566
586
  "memory.features.entityExtractionEnabled": "Entity extraction",
567
587
  "memory.features.entityExtractionEnabled.hint": "Extract people / projects / concepts so the graph grows by itself",
588
+ "memory.features.entityExtractionProvider": "Entity extraction provider",
589
+ "memory.features.entityExtractionModel": "Entity extraction model",
590
+ "memory.features.entityExtractionReasoning": "Entity extraction reasoning",
591
+ "memory.features.entityExtractionReasoning.none": "Follow default",
592
+ "memory.features.entityExtractionReasoning.low": "Low",
593
+ "memory.features.entityExtractionReasoning.medium": "Medium",
594
+ "memory.features.entityExtractionReasoning.high": "High",
595
+ "memory.features.entityExtractionModelHint": "Provider / model empty = follow the main conversation model; reasoning none = provider default",
568
596
  "memory.features.codingRetrospect": "Coding retrospection",
569
597
  "memory.features.codingRetrospect.hint": "Distill pitfalls, constraints and rejected solutions from full transcripts (tools and errors included)",
570
598
  "memory.features.rerankEnabled": "Reranking",
@@ -950,6 +978,7 @@ window.__ModuleLoader__.load({
950
978
  ".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
979
  // 字符串开关的输入框(provider/model 等)与其子块容器
952
980
  ".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}",
981
+ ".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
982
  ".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
983
  ".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
984
  ".mneme-featsub .mneme-featnum{padding:6px 0;border-bottom:none}",
@@ -1592,8 +1621,10 @@ window.__ModuleLoader__.load({
1592
1621
  const FEATURE_ADVANCED_BOOLS = ["hybridInject", "selectiveInjectEnabled", "adaptiveThresholdEnabled", "reflectionUpdateEnabled", "reflectionFailureTracking", "conflictFreezeEnabled", "trustEpistemicWeighting"];
1593
1622
  // 字符串键(blur/Enter 提交,空串合法 = 跟随默认):巩固模型与语义
1594
1623
  // 检索路线。embedProvider 是枚举,用下拉单独渲染。
1595
- const FEATURE_STRINGS = ["dreamProvider", "dreamModel", "localEmbedModel", "ollamaBaseUrl", "ollamaModel"];
1624
+ const FEATURE_STRINGS = ["dreamProvider", "dreamModel", "sleepProvider", "sleepModel", "entityExtractionProvider", "entityExtractionModel", "localEmbedModel", "ollamaBaseUrl", "ollamaModel"];
1596
1625
  const EMBED_PROVIDERS = ["openai", "local", "ollama"];
1626
+ // 实体抽取思考强度(issue #109):与后端 FEATURE_FLAG_ENUMS 枚举对齐。
1627
+ const ENTITY_REASONING = ["none", "low", "medium", "high"];
1597
1628
 
1598
1629
  function FeatureRow({ name, hint, on, disabled, onToggle }) {
1599
1630
  return h("div", { className: "mneme-featrow" },
@@ -1623,8 +1654,10 @@ window.__ModuleLoader__.load({
1623
1654
  // 巩固/睡眠模型路由下拉的数据源:GET /llm-providers 探测(云端 v0.7.26+
1624
1655
  // 的插件侧端点)。null = 端点不可用(404/失败)→ 回退纯文本输入,不挡旧后端。
1625
1656
  const [routes, setRoutes] = useState(null);
1626
- // 连通性测试结果:{ running: true } | { ok, ms, detail }
1627
- const [testState, setTestState] = useState(null);
1657
+ // 连通性测试结果(巩固/睡眠/实体抽取各一份,互不串扰):{ running: true } | { ok, ms, detail }
1658
+ const [dreamTest, setDreamTest] = useState(null);
1659
+ const [sleepTest, setSleepTest] = useState(null);
1660
+ const [entityTest, setEntityTest] = useState(null);
1628
1661
 
1629
1662
  useEffect(() => {
1630
1663
  let cancelled = false;
@@ -1700,7 +1733,7 @@ window.__ModuleLoader__.load({
1700
1733
  // 连通性测试:POST /test-model,空 provider/model = 按巩固路由解析
1701
1734
  // (agent 默认)。durationMs 优先后端值,缺失时客户端兜底计时;成功行
1702
1735
  // 附模型的实际回复(reply,后端截 100 字符)——「真的答了 ok」而非只报通。
1703
- const runModelTest = async (provider, model) => {
1736
+ const runModelTest = async (provider, model, setTestState) => {
1704
1737
  setTestState({ running: true });
1705
1738
  const started = Date.now();
1706
1739
  try {
@@ -1733,7 +1766,7 @@ window.__ModuleLoader__.load({
1733
1766
  // 适配器,用户不会选到不存在的模型)+ 连通性测试。空值 = 跟随默认
1734
1767
  // 路由;改动即提交(与 embedProvider 下拉一致)。当前值不在枚举里时
1735
1768
  // 保留为额外选项,避免静默改值。
1736
- const routeSelects = (providerKey, modelKey) => {
1769
+ const routeSelects = (providerKey, modelKey, testState, setTestState) => {
1737
1770
  const curP = strs[providerKey] ?? "";
1738
1771
  const curM = strs[modelKey] ?? "";
1739
1772
  const entries = Array.isArray(routes) ? routes : [];
@@ -1769,7 +1802,7 @@ window.__ModuleLoader__.load({
1769
1802
  h("button", {
1770
1803
  type: "button", className: "mneme-btn",
1771
1804
  disabled: busy || (testState && testState.running),
1772
- onClick: () => runModelTest(curP, curM)
1805
+ onClick: () => runModelTest(curP, curM, setTestState)
1773
1806
  }, t((testState && testState.running) ? "memory.features.modelTesting" : "memory.features.modelTest")),
1774
1807
  testState && !testState.running && h("div", { className: "mneme-featsubhint" },
1775
1808
  (testState.ok ? "✓ " + t("memory.features.modelTestOk") : "✗ " + t("memory.features.modelTestFail"))
@@ -1812,7 +1845,7 @@ window.__ModuleLoader__.load({
1812
1845
  // 可用时用级联下拉 + 连通性测试;旧后端(端点 404)回退纯文本输入。
1813
1846
  const dreamSub = eff.autoDream && h("div", { className: "mneme-featsub" },
1814
1847
  Array.isArray(routes)
1815
- ? routeSelects("dreamProvider", "dreamModel")
1848
+ ? routeSelects("dreamProvider", "dreamModel", dreamTest, setDreamTest)
1816
1849
  : h(react.Fragment, null, strRow("dreamProvider"), strRow("dreamModel")),
1817
1850
  h("div", { className: "mneme-featsubhint" }, t("memory.features.dreamModelHint"))
1818
1851
  );
@@ -1820,10 +1853,30 @@ window.__ModuleLoader__.load({
1820
1853
  // 睡眠模型:sleepModeEnabled 开着才展开(sleepProvider/sleepModel 随本版
1821
1854
  // 进白名单;下拉与测试复用同一 /llm-providers 数据源)。
1822
1855
  const sleepSub = eff.sleepModeEnabled && Array.isArray(routes) && h("div", { className: "mneme-featsub" },
1823
- routeSelects("sleepProvider", "sleepModel"),
1856
+ routeSelects("sleepProvider", "sleepModel", sleepTest, setSleepTest),
1824
1857
  h("div", { className: "mneme-featsubhint" }, t("memory.features.sleepModelHint"))
1825
1858
  );
1826
1859
 
1860
+ // 实体抽取路由与思考强度(issue #109):entityExtractionEnabled 开着才
1861
+ // 展开。provider/model 级联下拉 + 连通性测试(同巩固/睡眠),reasoning
1862
+ // 枚举即时提交;旧后端(/llm-providers 404)回退纯文本输入。
1863
+ const entitySub = eff.entityExtractionEnabled && h("div", { className: "mneme-featsub" },
1864
+ Array.isArray(routes)
1865
+ ? routeSelects("entityExtractionProvider", "entityExtractionModel", entityTest, setEntityTest)
1866
+ : h(react.Fragment, null, strRow("entityExtractionProvider"), strRow("entityExtractionModel")),
1867
+ h("div", { className: "mneme-featnum" },
1868
+ h("span", { className: "mneme-featnumlabel" }, t("memory.features.entityExtractionReasoning")),
1869
+ h("select", {
1870
+ className: "mneme-select",
1871
+ value: eff.entityExtractionReasoning || "none",
1872
+ disabled: busy,
1873
+ onChange: (e) => put({ entityExtractionReasoning: e.target.value })
1874
+ },
1875
+ ENTITY_REASONING.map((r) => h("option", { key: r, value: r }, t(`memory.features.entityExtractionReasoning.${r}`))))
1876
+ ),
1877
+ h("div", { className: "mneme-featsubhint" }, t("memory.features.entityExtractionModelHint"))
1878
+ );
1879
+
1827
1880
  if (error && !state) return h("section", { className: "mneme-set-card" },
1828
1881
  h("div", { className: "mneme-set-title" }, t("memory.features.title")),
1829
1882
  h("div", { className: "mneme-set-hint" }, error));
@@ -1840,7 +1893,7 @@ window.__ModuleLoader__.load({
1840
1893
  FEATURE_GROUPS.map((g) => h(react.Fragment, { key: g.key },
1841
1894
  h("div", { className: "mneme-featgroup" }, t(`memory.features.${g.key}`)),
1842
1895
  g.items.map(boolRow),
1843
- g.key === "group.enhance" && embedSub,
1896
+ g.key === "group.enhance" && h(react.Fragment, null, embedSub, entitySub),
1844
1897
  g.key === "group.dream" && h(react.Fragment, null, dreamSub, sleepSub)
1845
1898
  )),
1846
1899
  h("div", { className: "mneme-featgroup" },
@@ -1866,6 +1919,18 @@ window.__ModuleLoader__.load({
1866
1919
  return `${s.slice(0, 5)}${"•".repeat(10)}${s.slice(-4)}`;
1867
1920
  }
1868
1921
 
1922
+ // 反馈预填用的平台标签:从 userAgent 提一个粗粒度 OS 名,够定位问题即可,
1923
+ // 不引第三方解析库。浏览器/Electron webview 里都能取到。
1924
+ const platformLabel = () => {
1925
+ const ua = (typeof navigator !== "undefined" && navigator.userAgent) || "";
1926
+ if (/Windows NT/.test(ua)) return "Windows";
1927
+ if (/Mac OS X/.test(ua)) return "macOS";
1928
+ if (/Android/.test(ua)) return "Android";
1929
+ if (/iPhone|iPad/.test(ua)) return "iOS";
1930
+ if (/Linux/.test(ua)) return "Linux";
1931
+ return "Unknown";
1932
+ };
1933
+
1869
1934
  function SettingsContent({ t }) {
1870
1935
  const [profile, setProfile] = react.useState("");
1871
1936
  const [rules, setRules] = react.useState([]);
@@ -1882,6 +1947,8 @@ window.__ModuleLoader__.load({
1882
1947
  (typeof window !== "undefined" && window.localStorage) ? window.localStorage.getItem("dsh-mneme-api-token") || "" : ""
1883
1948
  );
1884
1949
  const [apiTokenSaved, setApiTokenSaved] = react.useState(false);
1950
+ // 反馈入口的插件版本(GET /info)。失败保持 "unknown",链接仍可用。
1951
+ const [pkgVersion, setPkgVersion] = react.useState("unknown");
1885
1952
  // 运行模式 — light vs standard; null = still loading. The card keeps
1886
1953
  // its own busy/saved/error state so it never blocks the others.
1887
1954
  const [mode, setMode] = react.useState(null);
@@ -1917,6 +1984,27 @@ window.__ModuleLoader__.load({
1917
1984
 
1918
1985
  react.useEffect(() => { load(); }, [load]);
1919
1986
 
1987
+ // 帮助与反馈卡片的版本号:独立小请求,失败不影响其它卡片。
1988
+ react.useEffect(() => {
1989
+ let cancelled = false;
1990
+ apiFetch("/api/dsh-mneme/info")
1991
+ .then((res) => (res.ok ? res.json() : null))
1992
+ .then((j) => { if (!cancelled && j && typeof j.version === "string") setPkgVersion(j.version); })
1993
+ .catch(() => {});
1994
+ return () => { cancelled = true; };
1995
+ }, []);
1996
+
1997
+ // 反馈链接预填:环境信息(插件版本 + 平台)+ 问题描述骨架。邮件主题带
1998
+ // 版本/平台方便归档,正文同款模板;纯链接零后端成本。
1999
+ const feedbackEnv = `**插件版本**: ${pkgVersion}\n**平台**: ${platformLabel()}\n`;
2000
+ const feedbackBody = feedbackEnv + "**问题描述**:\n- 期望行为:\n- 实际行为:\n- 复现步骤:\n";
2001
+ const issueHref = "https://github.com/modusensus/dsh-mneme/issues/new?title="
2002
+ + encodeURIComponent("[dsh-mneme] 问题反馈")
2003
+ + "&body=" + encodeURIComponent(feedbackBody);
2004
+ const mailHref = "mailto:work@modusensus.space?subject="
2005
+ + encodeURIComponent(`[dsh-mneme 反馈] ${pkgVersion} / ${platformLabel()}`)
2006
+ + "&body=" + encodeURIComponent(feedbackBody);
2007
+
1920
2008
  // Runtime mode + external API — two independent fetches: one failing
1921
2009
  // endpoint only errors its own card, never the other one.
1922
2010
  react.useEffect(() => {
@@ -2317,6 +2405,19 @@ window.__ModuleLoader__.load({
2317
2405
  h("button", { className: "mneme-btn", onClick: saveToken }, t("memory.settings.apiTokenSave"))
2318
2406
  ),
2319
2407
  apiTokenSaved && h("div", { style: { marginTop: 8 } }, h("span", { className: "mneme-saved" }, t("memory.settings.apiTokenSaved")))
2408
+ ),
2409
+ // 帮助与反馈 — 反馈问题三入口(新建 issue 预填 / 邮件 / 浏览已知问题)。
2410
+ // 纯前端链接零后端成本;插件版本来自 /info,平台取 userAgent。GitHub
2411
+ // 仓库当前没有 issue 模板,故用 issues/new?title=&body= 直接预填。
2412
+ h("section", { className: "mneme-set-sec" },
2413
+ h("div", { className: "mneme-set-title" }, t("memory.settings.feedback.title")),
2414
+ h("div", { className: "mneme-set-desc" }, t("memory.settings.feedback.desc")),
2415
+ h("div", { style: { display: "flex", flexDirection: "column", gap: 2 } },
2416
+ h("a", { className: "mneme-feed-link", href: issueHref, target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.newIssue")),
2417
+ h("a", { className: "mneme-feed-link", href: mailHref }, t("memory.settings.feedback.email")),
2418
+ h("a", { className: "mneme-feed-link", href: "https://github.com/modusensus/dsh-mneme/issues", target: "_blank", rel: "noopener noreferrer" }, t("memory.settings.feedback.browse"))
2419
+ ),
2420
+ h("div", { className: "mneme-featsubhint", style: { marginTop: 8 } }, t("memory.settings.feedback.hint"))
2320
2421
  )
2321
2422
  );
2322
2423
  }
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),
@@ -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) {
package/lib/index.js CHANGED
@@ -35,6 +35,57 @@ export { Config };
35
35
  // value, so a `function apply` disposer would never run on unload. An arrow
36
36
  // has no prototype, is called normally, and its returned disposer is collected
37
37
  // and run by the fiber on unload.
38
+ // Entity-extraction LLM adapter (issue #108/#109): maps the extractor's
39
+ // options (provider/model override + reasoningEffort) onto a real dsh-llm
40
+ // stream route and retries once without the effort when the first attempt is
41
+ // rejected. Extracted from apply() so the effort-fallback branch is
42
+ // unit-testable; the extractor only ever sees a callLLM(messages, options)
43
+ // => Promise<string>. The route always carries a real provider/model (dsh-llm
44
+ // GenerateOptions requires both) — never a bare stream.
45
+ export function createEntityStreamAdapter({ llm, agentDefaultModel, logger }) {
46
+ return async function streamEntityText(messages, options = {}) {
47
+ let route = {};
48
+ if (options.provider) route.provider = options.provider;
49
+ if (options.model) route.model = options.model;
50
+ if (!route.provider || !route.model) {
51
+ try {
52
+ const sel = agentDefaultModel?.currentSelection?.();
53
+ if (sel?.provider && sel?.model) {
54
+ route.provider ??= sel.provider;
55
+ route.model ??= sel.model;
56
+ }
57
+ } catch { /* fall through to whatever route we already have */ }
58
+ }
59
+ const effort = options.reasoningEffort;
60
+ const tryStream = (withEffort) => {
61
+ let text = "";
62
+ return (async () => {
63
+ for await (const chunk of llm.stream({
64
+ ...route,
65
+ maxTokens: 4096,
66
+ ...(withEffort && effort ? { reasoningEffort: effort } : {}),
67
+ messages
68
+ })) {
69
+ if (chunk.type === "text-delta" && typeof chunk.text === "string") text += chunk.text;
70
+ if (chunk.type === "finish" && (chunk.reason?.kind === "error" || chunk.reason?.kind === "aborted")) return undefined;
71
+ }
72
+ return text;
73
+ })().catch((err) => {
74
+ logger?.warn?.(`[dsh-mneme] entity extraction llm stream failed: ${String(err)}`);
75
+ return undefined;
76
+ });
77
+ };
78
+ let text = await tryStream(true);
79
+ if (text === undefined && effort) {
80
+ // Mirror dream's effort fallback: a provider rejecting the reasoning
81
+ // effort must not sink the whole extraction — retry once without it.
82
+ logger?.warn?.(`[dsh-mneme] entity extraction: reasoningEffort "${effort}" rejected, retrying without it`);
83
+ text = await tryStream(false);
84
+ }
85
+ return text;
86
+ };
87
+ }
88
+
38
89
  export const apply = (ctx, config) => {
39
90
  const rawCfg = Config(config);
40
91
 
@@ -320,36 +371,27 @@ export const apply = (ctx, config) => {
320
371
  // expects, reusing the same ctx.llm.stream consumption pattern as dream.js.
321
372
  // Explicit opt-in only (entityExtractionEnabled defaults to false); any LLM
322
373
  // failure degrades inside the extractor to { ok:false }, never a write error.
323
- if (cfg.entityExtractionEnabled && ctx.llm) {
324
- const streamEntityText = async (messages, options = {}) => {
325
- let route = null;
326
- if (options.model) {
327
- route = { model: options.model };
328
- } else {
329
- try {
330
- const sel = ctx.agentDefaultModel?.currentSelection?.();
331
- if (sel?.provider && sel?.model) route = sel;
332
- } catch { /* fall through to no route */ }
333
- }
334
- let text = "";
335
- for await (const chunk of ctx.llm.stream({
336
- ...(route ?? {}),
337
- purpose: "entity-extract",
338
- maxTokens: 4096,
339
- messages
340
- })) {
341
- if (chunk.type === "text-delta" && typeof chunk.text === "string") text += chunk.text;
342
- if (chunk.type === "finish" && (chunk.reason?.kind === "error" || chunk.reason?.kind === "aborted")) return undefined;
343
- }
344
- return text;
345
- };
346
- service.setEntityExtractor((memory) =>
347
- extractEntities(memory, { store, config: cfg, callLLM: streamEntityText, logger: ctx.logger })
348
- .catch((err) => {
349
- ctx.logger?.warn?.(`[dsh-mneme] entity extraction failed: ${String(err)}`);
350
- return { ok: false, error: String(err) };
351
- })
352
- );
374
+ if (cfg.entityExtractionEnabled) {
375
+ if (!ctx.llm) {
376
+ // Issue #108: an enabled-but-unwired extractor failed silently before —
377
+ // zero entities, zero llm_audit_logs, no log line anywhere. Make the
378
+ // missing dependency visible so a user can tell "extractor not installed"
379
+ // from "extraction failed".
380
+ ctx.logger?.warn?.("[dsh-mneme] entityExtractionEnabled=true but ctx.llm unavailable — entity extractor NOT installed");
381
+ } else {
382
+ const streamEntityText = createEntityStreamAdapter({
383
+ llm: ctx.llm,
384
+ agentDefaultModel: ctx.agentDefaultModel,
385
+ logger: ctx.logger
386
+ });
387
+ service.setEntityExtractor((memory) =>
388
+ extractEntities(memory, { store, config: cfg, callLLM: streamEntityText, logger: ctx.logger })
389
+ .catch((err) => {
390
+ ctx.logger?.warn?.(`[dsh-mneme] entity extraction failed: ${String(err)}`);
391
+ return { ok: false, error: String(err) };
392
+ })
393
+ );
394
+ }
353
395
  }
354
396
 
355
397
  const disposers = [];