@modusensus/dsh-mneme 0.7.15 → 0.7.17

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
@@ -7,7 +7,7 @@ English | [简体中文](README.md)
7
7
  [![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
8
8
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
9
9
  [![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
10
- [![tests](https://img.shields.io/badge/tests-645%20passed-success)](https://github.com/modusensus/dsh-mneme)
10
+ [![tests](https://img.shields.io/badge/tests-664%20passed-success)](https://github.com/modusensus/dsh-mneme)
11
11
 
12
12
  > A cross-session memory plugin for DeepSeek Harness: it lets the Agent remember you, remember your projects, and organize memories automatically. **Mneme** (Μνήμη) — named after Mnemosyne, the Greek goddess of memory who presides over memory and dreams, just as autoDream consolidates memories in the background.
13
13
 
@@ -19,7 +19,7 @@ English | [简体中文](README.md)
19
19
 
20
20
  - **SQLite primary storage**: `~/.dsh/memory/memory.db`, built-in `node:sqlite`, zero native dependencies
21
21
  - **Markdown mirror**: `preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md` — human-readable and hand-editable (**manual edits take priority** when merged back into the store)
22
- - **4+1 memory types**: `preference` / `project` / `decision` / `history` / `summary`
22
+ - **9 memory types**: `preference` / `project` / `decision` / `history` / `summary` / `pattern` + coding-retrospect types `rejected_solution` / `pitfall` / `constraint` (v0.7.13, `codingRetrospect` opt-in; the `user`/`fact` types were dropped in the v0.7.12 rewrite)
23
23
  - **Mirror sync state machine (v0.3.6+)**: the mirror is strongly consistent with the primary store, with sync debt modeled via `generation` (desired round) / `applied_generation` (applied round)
24
24
  - Business write operations **atomically increment** the desired generation **within their own transaction** — even a crash after COMMIT but before rendering recovers on restart from the durable debt, never silently skipped (v0.3.8)
25
25
  - `generation` is incremented with atomic SQLite statements — zero loss under multi-process concurrency; a `CHECK` upper bound rejects negative values/overflow
@@ -53,16 +53,16 @@ English | [简体中文](README.md)
53
53
  - `update` (v0.2.1): directly corrects outdated/incorrect content of a single memory (single id / must actually change / not `summary` / 24h protection / ≤2 per run)
54
54
  - **Failure tracking (v0.2.1)**: when the user corrects a memory, it is written to the `failure_memories` table (old value/new value), accumulating data for future self-evolution
55
55
  - **Summary generation**: after consolidation, a "memory store overview" (single instance) is generated, injected with priority in the next session
56
- - **Fail-safe**: illegal LLM output (unknown id / invalid action / cross-type merge / out-of-range importance) rejects the entire decision list — the memory store is never corrupted
56
+ - **Fail-safe**: "individually invalid" decisions (unknown id / invalid action / cross-type merge / out-of-range importance, Issue #26) are skipped and the valid subset applied — the run is marked `degraded` and the memory store is never corrupted; only global errors (coverage shortfall / update overflow) reject the whole list
57
57
  - **Adjudication audit**: every run writes to the `dream_runs` audit table (input snapshot sha256 digest + full input snapshot + decision list + per-id disposition + receipt), replayable offline; merge / conflict / update are applied idempotently — replays and concurrent duplicate runs have no cumulative side effects; `update` records a `_before` snapshot
58
58
 
59
59
  #### dreamMaxTokens Tuning Guide
60
60
 
61
- The default `4096` covers ordinary memory stores. When the **memory volume is large** (tens of thousands of characters or more), the decision list and summary may exceed the default budget; scale it up by size:
61
+ The default `8192` covers ordinary memory stores. When the **memory volume is large** (tens of thousands of characters or more), the decision list and summary may exceed the default budget; scale it up by size:
62
62
 
63
63
  | Memory store size | Recommended `dreamMaxTokens` |
64
64
  |-----------|----------------------|
65
- | Ordinary (<10k chars) | `4096` (default) |
65
+ | Ordinary (<10k chars) | `8192` (default) |
66
66
  | Medium (10k–50k chars) | `65536` |
67
67
  | Large (>50k chars) | `131072` (cap) |
68
68
 
@@ -162,8 +162,24 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
162
162
 
163
163
  ## 🆕 Recent Release Highlights
164
164
 
165
+ > ⚠️ **Archival note**: pre-v0.7.12 rows below record experimental features later removed in the v0.7.12 rewrite (heat model, Wiki-Link, tag system/directory/tag-boost, user/fact layered types, prefix-id resolution, /stats and /directory endpoints). They are version history only — **not current capability**. Current features are what this README's body and the [config table](#-configuration) describe.
166
+
165
167
  | Version | Highlights |
166
168
  |------|------|
169
+ | **v0.7.17** | Panel polish: sidebar entry continuously aligned with the host (MutationObserver syncs the New-Session class + `width:100%` + native centering restored, resilient to async skin rewrites) + importance rendered as Lucide star glyphs (`ImportanceStars` filled/hollow component replacing text ★) + toolbar dropdown stacking fix (z-index moved to the container; export/import menu no longer painted under the sticky month header); 664 tests green |
170
+ | **v0.7.16** | Fixed autoDream empty-body failures on thinking models (`no json array in llm output`): restored config-first routing (settings "consolidation model" wins, Issue #25) + reasoningEffort auto-retry without effort on rejection + honest `llm_audit` error on parse failure; backfilled API-route tests (/delete, /entities, /external-api) + lib runtime smoke; 662 tests green |
171
+ | **v0.7.15** | Desktop adaptation: library panel redesign + 30-key feature-flag UI (features API) + status dashboard + import/export (mirror-isomorphic md golden loop) + token masking by default; 645 tests green |
172
+ | **v0.7.14** | Security fix (CWE-200): distillation no longer collects private `reasoning` blocks — only public `text`; 617 tests green |
173
+ | **v0.7.13** | Coding-memory distillation `codingRetrospect` (opt-in: full-transcript atomic memories, 3 new types) + 429 governor (global serial queue + exponential backoff); 616 tests green |
174
+ | **v0.7.12** | Near-rewrite: inlined panel replaced by a pure HTTP API (127.0.0.1:8790 Bearer auth) + standalone zero-dep CLI `dsh-mneme` + lightMode; memory TYPES narrowed 8→6 (user/fact dropped) |
175
+ | **v0.7.11** | Library panel redesign: monthly pagination + infinite scroll + global search + 30s silent refresh + two-step delete + issues #72/#59 fixes; 595 tests green |
176
+ | **v0.7.10** | Web panel UX: memory-type color dots + graph canvas pan/zoom + settings re-grouping + sidebar tab-conflict fix + read-only `/entities` endpoint; 815 tests green |
177
+ | **v0.7.9** | Issue #65 fix: the snapshotEvents shim only landed in src/, never the npm-loaded lib/ — synced lib + pre-publish src↔lib consistency gate (check-sync.js) + lib smoke tests; 815 tests green |
178
+ | **v0.7.8** | DSH 0.1.2-rc.1 compatibility (issues #58 #59): `Session.events` → `snapshotEvents()` shim; autoSummarize & hot-context injection restored; 812 tests green |
179
+ | **v0.7.5** | Layered memory types (user/fact) + Overview view + stats endpoint; 790 tests green |
180
+ | **v0.7.0** | Self-evolving memory: heat power-law decay + per-type half-life + sleep dual-protection + entity heat projection; 757 tests green |
181
+ | **v0.6.0** | Session lifecycle: `session_disposed_at` soft-hide (orthogonal to archived, recoverable) + `memory_delete` description delete; 628 tests green |
182
+ | **v0.5.0** | Recall fusion & memory graph: BM25 three-way recall fusion + ego-graph API + zero-dep SVG force-directed graph + hot memory; 593 tests green |
167
183
  | **v0.4.2** | autoSummarize custom model: the `summarizeProvider`/`summarizeModel` config options let you independently designate a lightweight model (e.g. qwen3.6-plus) for session summaries, saving main-model tokens; 473 tests green |
168
184
  | **v0.4.0** | System-level Sleep Mode: idle-triggered four-phase deep maintenance (conflict resolution / archival demotion / pattern discovery / relation completion), interruptible, serially safe, fail-safe; tiered compression releases cold memories; 471 tests green |
169
185
  | **v0.3.9** | Fixed 4 FAILs from the third-party audit: CAS made atomic within the same transaction, mirror degraded-receipt passthrough, per-type physical terminal-state convergence, strict integer validation for generation and stabilized concurrent initialization |
@@ -187,7 +203,15 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
187
203
  | **v0.4.5** | ✅ Done | Epistemic trust + recall eval | Memory credibility grading `trustEpistemicWeighting` (observation>inferred>subjective: retrieval ranking favors high-credibility memories, injection tags `[verified]`, dream merge/conflict favors the more credible side; opt-in, off by default) + retrieval evaluation `evaluateRetrieval` persisted to `recall_evals` (`evalPersistTestResults` opt-in, off by default; production retrieval always goes through `recall_runs`, unconditionally isolated); 518 tests green |
188
204
  | **v0.4.6** | ✅ Done | 8 fixes (vector pipeline + injection/quality/audit) | Vector pipeline fixes (embedSingle adaptation / `autoReindexOnBoot` backfill of existing data / `vector_meta` metadata) + injection semantic recall `hybridInject` + same-title append `content_history` + injection length caps (300 per item / 1500 per block) + memory quality filter `memoryQualityFilter` + LLM usage audit `llmAudit` (table + instrumentation + read-only APIs); 553 tests green |
189
205
  | **v0.4.7** | ✅ Done | Idempotent schema migrations | When the same db is opened concurrently, the `PRAGMA table_info` check and ALTER are non-atomic and may repeat `ADD COLUMN`, failing with a duplicate column name; switched to an `addColumn` helper that swallows the race (try/catch), unifying all 12 migration sites |
190
- | **v0.5.0+** | 🚀 Long-term | Self-evolving memory | Interest drift tracking + cross-workspace memory sharing (pending DSH support) |
206
+ | v0.5.0 | Done | Recall fusion & memory graph | BM25 three-way recall fusion + ego-graph API + zero-dep SVG force-directed graph + hot memory + recall benchmark; 593 tests green |
207
+ | v0.6.0 | ✅ Done | Session lifecycle | `session_disposed_at` soft-hide (orthogonal to archived, recoverable) + `memory_delete` description delete + event circuit-breaker; 628 tests green |
208
+ | v0.6.x | ✅ Done | Panel enhancements + fixes | 7 further 0.6.x releases: Wiki-Link/tag/directory experiments (later removed in v0.7.12), #25/#26 fixes, allowCrossTypeMerge, ID-exposure hardening, version-sync discipline; up to 735 tests green |
209
+ | v0.7.0 | ✅ Done | Self-evolving memory | heat power-law decay + per-type half-life + sleep dual-protection + entity heat projection + recall_runs marking + 90-day cleanup; 757 tests green (heat model later removed in v0.7.12) |
210
+ | v0.7.1–0.7.8 | ✅ Done | Issue fixes + graph backfill | tags↔entity_attrs bridge, inline-confirm delete, sidebar trigger toggle, brace escaping, user/fact layered types + stats endpoint (later removed), prefix-id resolution (later removed), sleep batch entity extraction, snapshotEvents() DSH compat; 764→812 tests green |
211
+ | v0.7.9 | ✅ Done | lib-sync gate | Issue #65: src-only shim silently killed the shipped lib — synced lib + pre-publish src↔lib consistency check + lib smoke tests; 815 tests green |
212
+ | v0.7.10–0.7.12 | ✅ Done | Panel redesign + near-rewrite | UX upgrades (color dots, pan/zoom, pagination, global search), then near-rewrite: inlined panel → pure HTTP API + standalone CLI + lightMode; TYPES narrowed 8→6 |
213
+ | v0.7.13–0.7.17 | ✅ Done | Distillation, security, desktop, dream fix, panel polish | codingRetrospect + 429 governor; private reasoning blocks dropped (CWE-200); desktop panel redesign + feature flags; autoDream thinking-model empty-body fix + backfilled tests; sidebar entry alignment + star glyphs + menu stacking polish; 664 tests green |
214
+ | **v0.8.0** | 🚧 Planned (late Sep) | Graph enhancement | Interest-drift visualization + scope isolation (issue #17) + cross-workspace sharing |
191
215
 
192
216
  > 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.
193
217
 
@@ -256,8 +280,8 @@ It works out of the box with the defaults. To adjust, override in `~/.dsh/profil
256
280
  | `dreamThresholdCount` | `10` | Memory count threshold that triggers consolidation |
257
281
  | `dreamThresholdChars` | `5000` | Total character threshold that triggers consolidation |
258
282
  | `dreamDelayMs` | `2000` | Asynchronous consolidation delay (debounce) |
259
- | `dreamProvider` / `dreamModel` | empty | LLM route fallback for dream (defaults to the agent's default model) |
260
- | `dreamMaxTokens` | `4096` | Maximum tokens per dream LLM call (cap 131072; increase for large memory stores — see the tuning guide below) |
283
+ | `dreamProvider` / `dreamModel` | empty | Explicit dream LLM route config wins over the agent's default model (config-first, v0.7.16); left empty, the agent's default model is used |
284
+ | `dreamMaxTokens` | `8192` | Maximum tokens per dream LLM call (cap 131072; increase for large memory stores — see the tuning guide below) |
261
285
  | `dreamReasoningEffort` | `none` | Reasoning-effort passthrough for the dream LLM: `low` / `medium` / `high` / `none` (`none` = omit the field and use the model default; set `low` when a reasoning model exhausts its budget on reasoning and produces an empty body) |
262
286
  | `apiToken` | empty | Optional API auth token; once set, write operations and key endpoints require `Authorization: Bearer <apiToken>` |
263
287
  | `embedProvider` | `openai` | Semantic backend: `openai` (default, v0.1-compatible) / `local` (ONNX offline) / `ollama` |
@@ -405,7 +429,7 @@ src/
405
429
  lib/
406
430
  ├── client.js # Web 面板(手写 ModuleLoader bundle)
407
431
  └── *.js # src 的同步分发产物
408
- test/ # 645 node:test 测试(含审计与三轴线压测不变量)
432
+ test/ # 662 node:test tests (audit + three-axis stress invariants)
409
433
  scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步
410
434
  ```
411
435
 
@@ -414,7 +438,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
414
438
  ```bash
415
439
  cd dsh-mneme
416
440
  npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
417
- npm test # 运行 450 个测试
441
+ npm test # 运行 662 个测试
418
442
  npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
419
443
  npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
420
444
  ```
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-645%20passed-success)](https://github.com/modusensus/dsh-mneme)
8
+ [![tests](https://img.shields.io/badge/tests-664%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)
@@ -41,8 +41,8 @@ dsh web
41
41
  ### 记忆存储(SQLite + Markdown 镜像)
42
42
 
43
43
  - **SQLite 主存储**:`~/.dsh/memory/memory.db`,`node:sqlite` 内置,零原生依赖
44
- - **Markdown 镜像**:`preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md` / `user.md` / `facts.md`,人类可读、可手工编辑(**人工修改优先**合并回库)
45
- - **8 种记忆类型**:用户可存 6 种(`preference` 偏好 / `project` 项目 / `decision` 决策 / `history` 历史 / `user` 用户画像 / `fact` 原子事实)+ AI 自动生成 2 种(`summary` 会话总览 / `pattern` 模式)(v0.7.5 起新增 user/fact 两个轻量分层,单表 `type` 字段扩展,不动表结构)
44
+ - **Markdown 镜像**:`preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md`,人类可读、可手工编辑(**人工修改优先**合并回库)
45
+ - **9 种记忆类型**:`preference` 偏好 / `project` 项目 / `decision` 决策 / `history` 历史 / `summary` 会话总览 / `pattern` 模式 + 编码记忆三型 `rejected_solution` 被否方案 / `pitfall` 踩坑 / `constraint` 约束(v0.7.13 起,`codingRetrospect` 默认关;v0.7.12 重写已收窄删除 user/fact 两型)
46
46
  - **镜像同步状态机(v0.3.6+)**:mirror 与主库强一致,用 `generation`(期望轮次)/ `applied_generation`(已应用轮次)建模同步债务
47
47
  - 业务写操作在**自身事务内原子递增** desired generation——崩溃在 COMMIT 后、渲染前,重启也能凭 durable 债务恢复,绝不静默跳过(v0.3.8)
48
48
  - `generation` 用 SQLite 原子语句递增,多进程并发零丢失;带 `CHECK` 上界,负数/溢出拒绝
@@ -57,7 +57,7 @@ dsh web
57
57
  | `memory_search` | 全文搜索(中文子串友好,可启用向量语义搜索) |
58
58
  | `memory_list` | 按类型分页列出(`include_archived=true` 可查看已归档) |
59
59
  | `memory_update` | 修改已有记忆 |
60
- | `memory_delete` | 删除记忆(v0.6.0 支持 `query` 按描述删除,不只能靠记忆 ID) |
60
+ | `memory_delete` | 删除记忆(按记忆 ID 精确删除) |
61
61
  | `memory_forget` | 抑制注入(降权不删除,可恢复) |
62
62
  | `memory_archive` | 归档/恢复记忆(v0.2.5;归档后隐藏于列表/搜索/注入/整理,`archived=false` 可恢复) |
63
63
 
@@ -76,20 +76,20 @@ dsh web
76
76
  - `update`(v0.2.1):直接修正单条记忆的过时/错误内容(单 id / 必须实际变化 / 非 summary / 24h 保护 / 每次 ≤2)
77
77
  - **失败追踪(v0.2.1)**:用户纠正记忆时写入 `failure_memories` 表(旧值/新值),为后续自进化积累数据
78
78
  - **摘要生成**:整理后生成"记忆库总览"(单一实例),作为下次会话的优先注入
79
- - **Fail-safe**:非法 LLM 输出(未知 id / 非法 action / 越界 importance 等)拒绝整单,绝不破坏记忆库;跨类型合并这类"单条非法"决策(Issue #26)默认跳过并应用合法子集(run 记为 `degraded`),`dreamSkipInvalid:false` 可恢复旧的整单拒绝
79
+ - **Fail-safe**:非法 LLM 输出(未知 id / 非法 action / 越界 importance / 跨类型合并等"单条非法"决策,Issue #26)默认跳过该条并应用合法子集(run 记为 `degraded`),绝不破坏记忆库
80
80
  - **裁决审计**:每次运行写入 `dream_runs` 审计表(输入快照 sha256 digest + 完整输入快照 + 决策清单 + 逐 id 去向 + receipt),可离线回放;merge / conflict / update 幂等应用,重放/并发重复执行无累积副作用;update 记录 `_before` 快照
81
81
 
82
82
  #### dreamMaxTokens 调优指南
83
83
 
84
- 默认 `4096` 已覆盖常规记忆库。当**记忆量大**(数万字符以上)时,决策清单与摘要可能超过默认预算,建议按规模调大:
84
+ 默认 `8192` 已覆盖常规记忆库。当**记忆量大**(数万字符以上)时,决策清单与摘要可能超过默认预算,建议按规模调大:
85
85
 
86
86
  | 记忆库规模 | 建议 `dreamMaxTokens` |
87
87
  |-----------|----------------------|
88
- | 常规(<1 万字) | `4096`(默认) |
88
+ | 常规(<1 万字) | `8192`(默认) |
89
89
  | 中等(1 万-5 万字) | `65536` |
90
90
  | 大型(5 万字以上) | `131072`(上限) |
91
91
 
92
- > 若使用**思考型模型**(如 deepseek-v4-flash / DeepSeek-R1 类),模型可能把全部预算花在 reasoning 上导致正文为空(日志出现 `no json array in llm output`)。此时把 `dreamReasoningEffort` 设为 `off` 显式关闭思考(实测 8192 token 预算不再被推理吃光,输出恢复正常);sleep 侧对应 `sleepReasoningEffort`。默认 `none` 不传该字段,完全沿用模型自身默认,行为与旧版本一致。
92
+ > 若使用**思考型模型**(如 deepseek-v4-flash / DeepSeek-R1 类),模型可能把全部预算花在 reasoning 上导致正文为空(日志出现 `no json array in llm output`,v0.7.16 已修复)。此时把 `dreamReasoningEffort` 设为 `low` 显式压低思考(`none` 默认不传该字段,完全沿用模型自身默认,行为与旧版本一致);即使被方舟拒绝该参数,v0.7.16 起也会自动去掉重试一次。sleep 侧对应 `sleepReasoningEffort`。
93
93
 
94
94
  ### Sleep Mode 系统级睡眠 💤(v0.4.0,opt-in)
95
95
 
@@ -116,22 +116,6 @@ dsh web
116
116
  - **幂等 + 熔断**:dispose/restore 状态守卫幂等(重复调用 no-op);事件回调内部异常 catch 住,不抛进 DSH 会话清理流程
117
117
  - **恢复**:整会话 `service.restoreBySession(sessionId)` 一键还原;`service.listBySession(sessionId, { includeDisposed: true })` 可查看当前隐藏了哪些(DTO 带 `disposed` 标记)
118
118
 
119
- ### Wiki-Link 双向链接 🔗(v0.6.1,opt-in)
120
-
121
- **默认关闭**(`wikiLinkEnabled: false`)。开启后,记忆正文支持 `[[target]]` / `[[显示|target]]` 双括号链接语法:保存时自动解析并记录跨记忆 `links_to` 关系(partial 唯一索引只对 links_to 去重),记忆详情旁出现反向链接面板,点击可跳转来源记忆。提供只读 API:backlinks / forward-links / wikilink-resolve(输出脱敏)。
122
-
123
- ### Tag 系统 🏷️(v0.6.2,opt-in)
124
-
125
- `autoTagEnabled` 默认关。开启后:正文 `#标签` 即时标记(规则 `[a-zA-Z0-9_一-龥-]+`,≤20 字符,非法/超长自动丢弃);autoDream 整理后 LLM 自动打 1-3 个标签(`autoTagMaxPerRun=10` 频控,fail-safe);tags 存 `entity_attrs`;`tag:` 搜索前缀可与关键词/`entity:`/`attr:` 组合;mirror 顶部渲染 `#tag` 行;记忆面板标签可点击(过滤)、添加、移除(`manualTagEnabled` 默认开)。存储与搜索能力始终可用,`autoTagEnabled` 只控制自动打标。
126
-
127
- ### 目录视图 📁(v0.6.3)
128
-
129
- 记忆面板新增「目录」视图:以 Tag 为轴心的一级手风琴文件夹,无标签记忆自动沉底到「无标签」兜底组;组内按重要性/时间双降序;点击条目直达详情页。数据由 `GET /api/dsh-mneme/directory` 提供(只读、排除 disposed/archived/forgotten)。
130
-
131
- ### Tag 加权召回 ⚖️(v0.6.4,opt-in)
132
-
133
- `tagBoostEnabled` 默认关。开启后,三路召回合并出候选后做 tag 加权重排:候选记忆 tags 与 Query 提取 tags(`#xxx` + 已知 tag 列表)交集 → 分数 `×1.15`;与当前 Session 热记忆 tags 交集 → `×1.08`(可叠加,上限 1.0)。关闭时行为与旧版完全一致,可用 `evaluateRetrieval` 开关对比调优。
134
-
135
119
  官方设置面板 → 「记忆库设置」→「记忆」标签:按类型浏览、全文搜索;启用向量搜索后可用「语义」切换做向量召回。
136
120
 
137
121
  ### 用户设置(画像 / 规则)与自定义指令 ⚙️
@@ -226,21 +210,14 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
226
210
  - **选择性注入**:query 向量可用时注入候选按主题相似度重排(`selectiveInjectEnabled` 可关);**搜索时语义去重**为激进选项(`searchSemanticDedup=true` 显式开启,近重复行 Rerank 前丢弃)
227
211
  - **召回基准**(`scripts/benchmark-recall.js`):标准查询集驱动,计算 Recall@5 与 MRR,`legacy`(三特性全关)vs `fused`(默认配置)双跑对比
228
212
 
229
- ### 自进化记忆 🌡️(v0.7.0,默认保守开启)
230
-
231
- 让记忆库从"存得准、召得回"进化为会自我衰减、识别兴趣漂移的智能体:
232
-
233
- - **heat 热度模型**(`src/heat.js`):类遗忘曲线幂律衰减 `H = 1/(1+λ·Δt)^α`,per-type 差异化半衰期(TYPE_DECAY):preference/pattern/summary 免疫(λ=0,热度恒 1.0),project 慢衰减(λ=0.0008),decision 中速(λ=0.002),history 较快(λ=0.006);全局参数 `heatGlobalAlpha`(默认 1.2) 控制衰减速度
234
- - **sleep 热联合双保护**:降级需同时满足"冷"(heat < `sleepHeatThreshold` 0.05)+"非紧要"(importance < 5)+"非免疫类型"三重条件——冷但重要(如遗忘的高价值决策)与热但低值(如刚访问的闲聊)都受到保护,免疫类型(preference 等)永不降级
235
- - **updated_at 语义修正**:合并/更新刷新的 `updated_at` 不再计为访问(`last_accessed_at` 独立追踪),杜绝 autoDream 合并动作伪装成"刚被召回";触达数据采集(touchRecalled)由 `heatEnabled` 门控
236
- - **recall_runs 数据前提**:检索记录默认开启(`recallRecordDefault: true`),candidates 打 `injected` 标记区分"被召回"(false)与"被注入上下文"(true)两个消耗强度;注入场景也记账(mode="inject");90 天滚动清理(`recallRetentionDays`)防膨胀
237
- - **实体热投影**(ego-graph API + 前端):`entityHeat` 取关联记忆 heat 的 max 值,API 返回 `heat` 字段;前端节点大小/明暗随热度变化(`nodeRadius`/`fillOpacity`),兴趣漂移在图谱上可见
238
- - **配置**:`heatEnabled`(默认 true)、`heatTypeDecay`(per-type λ)、`heatGlobalAlpha`(1.2)、`sleepHeatThreshold`(0.05)、`recallRecordDefault`(true)、`recallRetentionDays`(90)
239
-
240
213
  ## 🆕 最近版本亮点
241
214
 
215
+ > ⚠️ **历史存档提示**:下方及路线图表中 v0.7.12 之前的早期条目记录的实验功能(heat 热度模型、Wiki-Link、tag 系统/目录/tag 加权、user/fact 分层类型、前缀 id 解析、/stats 与 /directory 端点等)已在 v0.7.12 近重写时移除,仅作版本历史存档,**不代表当前能力**。当前特性以本 README 正文与 [配置表](#-配置) 为准。
216
+
242
217
  | 版本 | 亮点 |
243
218
  |------|------|
219
+ | **v0.7.17** | 面板体验细化:侧边栏入口持续对齐宿主(MutationObserver 同步「新会话」类名 + `width:100%` + 交还原生居中,皮肤异步改写不再失配)+ 重要性星级换 Lucide 星形(`ImportanceStars` 实心/空心组件,替换文本 ★)+ 工具栏下拉层级修复(z-index 提到容器,导出/导入菜单不再被吸顶月份头遮挡);664 测试全绿 |
220
+ | **v0.7.16** | 修复 autoDream 在 thinking 模型上空体 failed(`no json array in llm output`):恢复 config-first 路由(设置面板「巩固模型」生效,Issue #25)+ reasoningEffort 被拒自动去掉重试一次 + 解析失败如实记 llm_audit error 并带原始输出头日志;补测 API 路由空白(/delete、/entities、/external-api)+ lib 运行时冒烟(src↔lib 一致性);662 测试全绿 |
244
221
  | **v0.7.15** | 桌面端适配:记忆库面板重设计(撤对话 tab → 居中非全屏 sheet、卡片网格/时间线双视图、右侧详情抽屉、编辑/归档/关联实体)+ 侧边栏入口上移工作区上方(借用宿主原生类名对齐、收起态零位移)+ 功能开关 30 键上 UI(features API,巩固模型与 embedding 提供方可配)+ 状态页工作台(巩固卡/工作动态流/沉淀记忆/归档恢复)+ 导入导出(镜像同构 md 黄金闭环)+ Token 面板默认遮蔽;645 测试全绿 |
245
222
  | **v0.7.14** | 安全修复(CWE-200):蒸馏不再把助手 `reasoning` 私有推理块送进蒸馏上下文(只采公开 `text`,防止记忆沉淀私有思考链);617 测试全绿 |
246
223
  | **v0.7.13** | 编码记忆蒸馏(`codingRetrospect` 默认关):完整转录(用户+助手回答+工具调用/结果+代码执行)提炼原子记忆,新增 `rejected_solution`/`pitfall`/`constraint` 三类型,编码任务 `codingBoostFactor` 加权(cap 5)+ 智能调速器(蒸馏全局串行队列 + 429 指数退避自动重试)+ 原子记忆语义保留(宁可拆多条不合并丢细节,`distillMaxChars` 默认 24000)+ 修复 v0.7.12 CI 回归(恢复 c8 覆盖率);616 测试全绿 |
@@ -309,7 +286,14 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
309
286
  | **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 测试全绿 |
310
287
  | **v0.7.6** | ✅ 完成 | issue #48 修复 | 四工具统一 `service.resolveMemoryId`:截断/前缀短 id 也能精确操作(精确命中优先、唯一前缀解析、多命中拒绝并列出候选、`memory_delete` 未命中幂等返回 + `logger.warn` 留痕);Web bundle `client.js` 改 src 正源;801 测试全绿 |
311
288
  | **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 测试全绿 |
312
- | **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ workspace 记忆共享 + 更多 heat 信号 |
289
+ | **v0.7.10** | 完成 | Web 面板体验升级 | 记忆类型色点体系 + 图谱画布平移/缩放(0.5x–3x 光标锚定)+ 设置页分区重排 + 侧边栏同标签冲突修复 + 详情 meta 精排 + 只读端点 `GET /api/dsh-mneme/entities`;815 测试全绿 |
290
+ | **v0.7.11** | ✅ 完成 | 记忆库面板改版 | 按月分页 + 无限滚动 + 搜索全局化 + 30s 静默刷新 + 状态子页四卡 + 删除两步确认 + 重要性过滤芯片 + 内联 Lucide 图标;bundle 默认开实体抽取;修复 issue#72 图谱节点裁剪 + issue#59 autoSummarize 垫片;595 测试全绿 |
291
+ | **v0.7.12** | ✅ 完成 | 近重写:纯 HTTP API + CLI | 内置面板 client.js 删除改纯 HTTP API(`127.0.0.1:8790` Bearer 鉴权,api.js/api-standalone.js)+ 独立 CLI `dsh-mneme`(bin/cli.mjs 零依赖)+ 轻量模式 lightMode + 设置面板新卡片;store TYPES 收窄 8→6(删 user/fact)、distill VALID →4、saveWithDedupe 改单参 |
292
+ | **v0.7.13** | ✅ 完成 | 编码记忆蒸馏 + 调速器 | `codingRetrospect`(默认关:完整转录提炼原子记忆,新增 rejected_solution/pitfall/constraint 三类型)+ 智能调速器(蒸馏全局串行队列 + 429 指数退避重试)+ 语义保留(distillMaxChars 默认 24000);616 测试全绿 |
293
+ | **v0.7.14** | ✅ 完成 | 安全修复(CWE-200) | 蒸馏不再采集私有推理块:`collectMessages` 只采公开 text,防止记忆库沉淀模型私有思考链;617 测试全绿 |
294
+ | **v0.7.15** | ✅ 完成 | 桌面端适配 | 记忆库面板重设计 + 功能开关 30 键 UI(features API)+ 状态页工作台 + 导入导出(镜像同构 md 黄金闭环)+ Token 面板默认遮蔽;645 测试全绿 |
295
+ | **v0.7.16** | ✅ 完成 | autoDream thinking 模型空体修复 + 补测 | 恢复 config-first 路由(设置面板「巩固模型」生效,Issue #25)+ reasoningEffort 被拒自动去掉重试 + 解析失败如实记 llm_audit error;补测 API 路由空白(/delete、/entities、/external-api)+ lib 运行时冒烟;662 测试全绿 |
296
+ | **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ 跨 workspace 记忆共享 |
313
297
 
314
298
  > 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
315
299
 
@@ -382,9 +366,7 @@ dsh web
382
366
  | `dreamDelayMs` | `2000` | 整理异步延迟(去抖) |
383
367
  | `dreamProvider` / `dreamModel` | 空 | dream 的 LLM 路由覆盖(显式配置优先于 agent 默认模型;留空则回退到 agent 默认模型) |
384
368
  | `dreamMaxTokens` | `8192` | dream LLM 调用最大 token 数(上限 131072;大记忆量建议调大,见下方调优指南) |
385
- | `dreamReasoningEffort` | `none` | dream LLM 推理强度透传:`off` / `low` / `medium` / `high` / `none`(`off`=显式关闭思考,思考型模型(如 deepseek-v4-flash)必须用它,否则推理会烧光 token 预算导致正文为空;`none`=不传该字段,沿用模型默认) |
386
- | `dreamSkipInvalid` | `true` | 跳过非法决策(Issue #26,默认开):跨类型 merge 等"单条非法"决策不再整单拒绝,而是跳过该决策、应用合法子集,run 记为 `degraded`(`applied>0`,autoTag 照常触发);设为 `false` 恢复旧的"任意非法即整单拒绝"(`applied=0`)。防洗白语义不变——显式覆盖率不足 / update 超量等全局错误仍整单拒绝 |
387
- | `allowCrossTypeMerge` | `false` | 允许跨类型合并(Issue #26,默认关):类型有语义边界(`preference` 注入权重更高、`decision`/`project` 注入上下文不同),跨类型合并默认视为非法并被跳过;显式开启后放宽该检查,跨类型 merge 可被应用,类型边界由用户自行承担 |
369
+ | `dreamReasoningEffort` | `none` | dream LLM 推理强度透传:`low` / `medium` / `high` / `none`(`none`=不传该字段,沿用模型默认;思考型模型(如 deepseek-v4-flash)想压低思考可设 `low`;被方舟拒绝该参数时 v0.7.16 起自动去掉重试一次) |
388
370
  | `apiToken` | 空 | 可选 API 鉴权 token;设置后写操作与密钥接口要求 `Authorization: Bearer <apiToken>` |
389
371
  | `embedProvider` | `openai` | 语义后端:`openai`(默认,兼容 v0.1)/ `local`(ONNX 离线)/ `ollama` |
390
372
  | `localEmbedModel` | `Xenova/bge-small-zh-v1.5` | 本地 ONNX embedding 模型 |
@@ -541,10 +523,9 @@ src/
541
523
  ├── hot-memory.js # 会话级短期热记忆(v0.5.0:滚动轮次 + token 预算)
542
524
  ├── embedding.js # OpenAI 兼容 embeddings 客户端 + 向量检索
543
525
  ├── api.js # HTTP 路由(Web 面板数据通道)
544
- ├── client.js # Web 面板 bundle(ModuleLoader 自注册;v0.7.6 起 src 正源)
545
526
  └── index.js # 插件接线
546
- lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性,无手写例外)
547
- test/ # 645 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
527
+ lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
528
+ test/ # 662 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
548
529
  scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js 召回基准
549
530
  ```
550
531
 
@@ -553,14 +534,14 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
553
534
  ```bash
554
535
  cd dsh-mneme
555
536
  npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
556
- npm test # 运行 645 个测试
537
+ npm test # 运行 662 个测试
557
538
  npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
558
539
  npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
559
540
  ```
560
541
 
561
542
  > 压测(`npm run stress`)三条轴线:**长会话检索**(Recall@k、陈旧残留率)、**冲突裁决**(可重放仲裁集:审计快照 hash + receipt + 幂等回放)、**多 Agent 并发**(丢更新、重复合并、事务/崩溃恢复)。每次 autoDream 运行都会写入审计表 `dream_runs`(输入快照 digest + 决策清单 + 逐 id 去向 + receipt),让高通过率下也能定位静默错误。
562
543
 
563
- > `lib/` 是 `src/` 的同步分发产物(`npm run sync`):v0.7.6 起全部由 src 复制而来,**不再有任何手写 lib 文件**——Web 面板 bundle 也以 `src/client.js` 为唯一正源。
544
+ > `lib/` 是 `src/` 的同步分发产物(`npm run sync`):除 **`lib/client.js`**(Web 面板 bundle)为 lib 独有的手写文件、sync check-sync 均不触碰外,其余全部由 src 复制而来。改 src 必 `npm run sync` 并提交 lib,否则发布产物静默失效(issue #65 教训)。
564
545
 
565
546
  ## 📄 设计文档
566
547
 
package/lib/client.js CHANGED
@@ -74,6 +74,24 @@ window.__ModuleLoader__.load({
74
74
  }, parts.map(([tag, attrs], i) => h(tag, { key: i, ...attrs })));
75
75
  };
76
76
 
77
+ // Lucide v1.42 star path(morphicons 官方配套的数据包)。morphicons 本体
78
+ // 是 ESM-only 的变形动画引擎、插件运行时不允许 require 第三方库,故按
79
+ // 既有惯例内联图标数据静态渲染;实心/空心由 fill 区分,空心降透明度。
80
+ const STAR_PATH_D = "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z";
81
+ const ImportanceStars = ({ value = 0, size = 13, className }) => {
82
+ const filled = Math.min(5, Math.max(0, Math.round(value || 0)));
83
+ return h("span", { className: `mneme-stars${className ? ` ${className}` : ""}`, role: "img", "aria-label": `${filled}/5` },
84
+ [0, 1, 2, 3, 4].map((i) => h("svg", { key: i, width: size, height: size, viewBox: "0 0 24 24", "aria-hidden": "true" },
85
+ h("path", {
86
+ d: STAR_PATH_D,
87
+ fill: i < filled ? "currentColor" : "none",
88
+ stroke: "currentColor",
89
+ strokeWidth: 1.6,
90
+ strokeLinejoin: "round",
91
+ opacity: i < filled ? 1 : 0.35
92
+ }))));
93
+ };
94
+
77
95
  // Unified API fetcher: attaches the optional apiToken (set in the settings
78
96
  // view, persisted in localStorage) as a Bearer header. When no token has
79
97
  // been configured the header is omitted and the API stays open (default).
@@ -695,7 +713,9 @@ window.__ModuleLoader__.load({
695
713
  ".mneme-vtab:hover{color:var(--dsw-alias-label-primary)}",
696
714
  ".mneme-vtab.mneme-active{color:var(--dsw-alias-state-business-primary)}",
697
715
  ".mneme-vtab.mneme-active::after{content:\"\";position:absolute;left:10px;right:10px;bottom:-1px;height:2.5px;border-radius:2px;background:var(--dsw-alias-state-business-primary)}",
698
- ".mneme-xtools{position:absolute;right:14px;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:8px;padding:0}",
716
+ // transform 会自建层叠上下文:层级必须给在容器上——3 高于吸顶月份头(2)
717
+ // 低于详情抽屉(6),下拉菜单/导入对话框随容器整体上浮。
718
+ ".mneme-xtools{position:absolute;right:14px;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:8px;padding:0;z-index:3}",
699
719
  ".mneme-xcount{flex:none;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
700
720
  // --- three-column browse layout: hairline separators, no outer box ---
701
721
  ".mneme-xmain{flex:1;min-height:0;display:flex;flex-direction:row;overflow:hidden}",
@@ -824,10 +844,13 @@ window.__ModuleLoader__.load({
824
844
  // --- 侧边栏顶部入口:借宿主「新会话」按钮的原生类名对齐 ---
825
845
  // wrapper display:contents 隐身,按钮成为侧边栏弹性布局的直接子元素;
826
846
  // 盒模型/间距/收起态 rail 几何全部继承宿主,我们只覆盖配色为次级观感。
847
+ ".mneme-stars{display:inline-flex;align-items:center;gap:2px}",
848
+ // width:100% 与「新会话」同宽(同一弹性父容器);文案对齐交给复制来的
849
+ // 原生类,不再用 flex:1/text-align:left 覆盖成左对齐。
827
850
  ".mneme-topentry{display:contents}",
828
- ".mneme-topentry-native{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}",
851
+ ".mneme-topentry-native{width:100%;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}",
829
852
  ".mneme-topentry-native:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
830
- ".mneme-topentry-native .mneme-topentry-label{flex:1;min-width:0;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
853
+ ".mneme-topentry-native .mneme-topentry-label{white-space:nowrap}",
831
854
  // --- 功能开关:一行一开关,Claude 式安静排版 ---
832
855
  ".mneme-featgroup{flex:none;font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary);margin:16px 0 2px}",
833
856
  ".mneme-featrow{display:flex;align-items:center;gap:14px;padding:11px 2px;border-bottom:1px solid var(--dsw-alias-border-l1)}",
@@ -2560,7 +2583,7 @@ window.__ModuleLoader__.load({
2560
2583
  value: importance,
2561
2584
  onChange: (e) => setImportance(Number(e.target.value))
2562
2585
  }, [1, 2, 3, 4, 5].map((n) => h("option", { key: n, value: n }, "★".repeat(n))))
2563
- : h("span", { className: "mneme-dmetaval" }, "★".repeat(Math.min(5, Math.max(0, memory.importance || 0)))),
2586
+ : h(ImportanceStars, { className: "mneme-dmetaval", value: memory.importance || 0 }),
2564
2587
  memory.source && h(react.Fragment, null,
2565
2588
  h("span", { className: "mneme-dmetakey" }, t("memory.explorer.source")),
2566
2589
  h("span", { className: "mneme-dmetaval", title: memory.source }, memory.source)),
@@ -3160,7 +3183,7 @@ window.__ModuleLoader__.load({
3160
3183
  h("div", { className: "mneme-cardtitle" }, m.title || (m.content || "").slice(0, 60)),
3161
3184
  h("div", { className: "mneme-cardexcerpt" }, m.content || ""),
3162
3185
  h("div", { className: "mneme-cardfoot" },
3163
- h("span", null, "★".repeat(Math.min(5, Math.max(0, m.importance || 0)))),
3186
+ h(ImportanceStars, { value: m.importance || 0, size: 12 }),
3164
3187
  m.source && h("span", { className: "mneme-cardsrc", title: m.source }, m.source)
3165
3188
  )
3166
3189
  )),
@@ -3287,19 +3310,34 @@ window.__ModuleLoader__.load({
3287
3310
  const [nativeCls, setNativeCls] = useState("");
3288
3311
  useEffect(() => {
3289
3312
  if (!reactDom || typeof document === "undefined") return undefined;
3290
- let tries = 0, timer = null, created = null;
3313
+ let tries = 0, timer = null, created = null, mo = null;
3314
+ const findNative = (region) =>
3315
+ region.parentElement.querySelector('[class*="newSession"]')
3316
+ || region.previousElementSibling;
3291
3317
  const attempt = () => {
3292
3318
  const region = document.querySelector('[class*="regionArea"]');
3293
3319
  if (region && region.parentElement) {
3294
3320
  // 「新会话」按钮与 regionArea 同级且紧邻其前,借它的类名获得
3295
3321
  // 与原生侧边栏项完全一致的盒模型(含收起态 rail 几何)。
3296
- const ns = region.parentElement.querySelector('[class*="newSession"]')
3297
- || region.previousElementSibling;
3322
+ // 宿主/皮肤会异步改写按钮类名,用 MutationObserver 持续同步,
3323
+ // 否则 portal 按钮会停留在捕获时刻的旧类上(宽度/对齐失配)。
3298
3324
  created = document.createElement("div");
3299
3325
  created.dataset.pluginEntry = "@modusensus/dsh-mneme";
3300
3326
  region.parentElement.insertBefore(created, region);
3301
3327
  setHost(created);
3302
- setNativeCls(ns?.className || "");
3328
+ setNativeCls(findNative(region)?.className || "");
3329
+ mo = new MutationObserver(() => {
3330
+ const cur = document.querySelector('[class*="regionArea"]');
3331
+ if (!cur || !cur.parentElement) return;
3332
+ const cls = findNative(cur)?.className || "";
3333
+ setNativeCls((prev) => (prev === cls ? prev : cls));
3334
+ });
3335
+ mo.observe(region.parentElement, {
3336
+ attributes: true,
3337
+ attributeFilter: ["class"],
3338
+ childList: true,
3339
+ subtree: true
3340
+ });
3303
3341
  return;
3304
3342
  }
3305
3343
  if (++tries > 40) return; // 放弃 portal,footer 回退保持可用
@@ -3308,6 +3346,7 @@ window.__ModuleLoader__.load({
3308
3346
  attempt();
3309
3347
  return () => {
3310
3348
  clearTimeout(timer);
3349
+ if (mo) mo.disconnect();
3311
3350
  if (created && created.parentElement) created.parentElement.removeChild(created);
3312
3351
  };
3313
3352
  }, []);
@@ -17,7 +17,7 @@
17
17
  import { randomUUID, createHash } from "node:crypto";
18
18
  import { validateDecisions, applyDecisions } from "./decisions.js";
19
19
  import { findPotentialConflicts } from "./clustering.js";
20
- import { buildReceipt } from "../dream.js";
20
+ import { buildReceipt, withEffortFallback } from "../dream.js";
21
21
 
22
22
  const SUMMARY_MAX = 120;
23
23
  // Conflict similarity threshold per strictness level (v0.4.0):
@@ -72,16 +72,18 @@ async function streamText(ctx, options) {
72
72
  return text;
73
73
  }
74
74
 
75
- /** LLM route: agent default model first, then sleepProvider/Model, then the
76
- * dream route as a shared fallback. Sleep can pin a cheaper model for its
77
- * bulk passes without disturbing the dream route. */
75
+ /** LLM route (Issue #25): explicit sleepProvider/Model wins, then the dream
76
+ * route as a shared explicit fallback, then the agent default model. Sleep
77
+ * can pin a cheaper model for its bulk passes without disturbing the dream
78
+ * route. Explicit config first — otherwise the config routes are dead code
79
+ * whenever agentDefaultModel resolves (see resolveRoute in dream.js). */
78
80
  function resolveSleepRoute(ctx, config, logger) {
81
+ if (config.sleepProvider && config.sleepModel) return { provider: config.sleepProvider, model: config.sleepModel };
82
+ if (config.dreamProvider && config.dreamModel) return { provider: config.dreamProvider, model: config.dreamModel };
79
83
  try {
80
84
  const sel = ctx?.agentDefaultModel?.currentSelection?.();
81
85
  if (sel?.provider && sel?.model) return { provider: sel.provider, model: sel.model };
82
- } catch { /* fall through to config route */ }
83
- if (config.sleepProvider && config.sleepModel) return { provider: config.sleepProvider, model: config.sleepModel };
84
- if (config.dreamProvider && config.dreamModel) return { provider: config.dreamProvider, model: config.dreamModel };
86
+ } catch { /* fall through to warn */ }
85
87
  logger?.warn?.("dsh-mneme sleep: no llm route available");
86
88
  return undefined;
87
89
  }
@@ -182,19 +184,19 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
182
184
  const listText = selected.map((p) =>
183
185
  `候选冲突:\nid=${p.a.id} | type=${p.a.type} | title=${p.a.title}\n${p.a.content}\n---\nid=${p.b.id} | type=${p.b.type} | title=${p.b.title}\n${p.b.content}\n(相似度 ${p.similarity.toFixed(2)})`
184
186
  ).join("\n\n");
185
- const text = await streamText(ctx, {
187
+ const sleepEffort = config.sleepReasoningEffort && config.sleepReasoningEffort !== "none" ? config.sleepReasoningEffort : null;
188
+ const runConflict = (withEffort) => streamText(ctx, {
186
189
  provider: route.provider,
187
190
  model: route.model,
188
191
  purpose: "sleep-conflict",
189
192
  maxTokens: 2048,
190
- ...(config.sleepReasoningEffort && config.sleepReasoningEffort !== "none"
191
- ? { reasoningEffort: config.sleepReasoningEffort }
192
- : {}),
193
+ ...(withEffort && sleepEffort ? { reasoningEffort: sleepEffort } : {}),
193
194
  messages: [
194
195
  { role: "system", content: [{ type: "text", text: CONFLICT_PROMPT }] },
195
196
  { role: "user", content: [{ type: "text", text: listText }] }
196
197
  ]
197
198
  });
199
+ const text = await withEffortFallback(ctx, sleepEffort, () => runConflict(true), () => runConflict(false));
198
200
  if (text === undefined) return { status: "failed", error: "llm failed" };
199
201
  const decisions = parseJsonArray(text);
200
202
  if (!decisions) return { status: "failed", error: "invalid decisions json" };
@@ -282,19 +284,19 @@ async function phasePatterns(ctx, service, config, logger, runId, signal = null)
282
284
  .map((m) => `id=${m.id} | type=${m.type} | importance=${m.importance} | updated=${m.updated_at} | title=${m.title} | content=${m.content}`)
283
285
  .join("\n");
284
286
  const maxPatterns = config.sleepMaxPatternPerRun ?? 3;
285
- const text = await streamText(ctx, {
287
+ const sleepEffort = config.sleepReasoningEffort && config.sleepReasoningEffort !== "none" ? config.sleepReasoningEffort : null;
288
+ const runPattern = (withEffort) => streamText(ctx, {
286
289
  provider: route.provider,
287
290
  model: route.model,
288
291
  purpose: "sleep-pattern",
289
292
  maxTokens: 2048,
290
- ...(config.sleepReasoningEffort && config.sleepReasoningEffort !== "none"
291
- ? { reasoningEffort: config.sleepReasoningEffort }
292
- : {}),
293
+ ...(withEffort && sleepEffort ? { reasoningEffort: sleepEffort } : {}),
293
294
  messages: [
294
295
  { role: "system", content: [{ type: "text", text: PATTERN_PROMPT.replace("N", String(maxPatterns)) }] },
295
296
  { role: "user", content: [{ type: "text", text: listText }] }
296
297
  ]
297
298
  });
299
+ const text = await withEffortFallback(ctx, sleepEffort, () => runPattern(true), () => runPattern(false));
298
300
  if (text === undefined) return { status: "failed", error: "llm failed" };
299
301
  const decisions = parseJsonArray(text);
300
302
  if (!decisions || decisions.length === 0) return { status: "skipped", reason: "no patterns found" };