@modusensus/dsh-mneme 0.7.18 → 0.7.20
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 +5 -3
- package/README.md +11 -17
- package/cordis.patch.yml +5 -0
- package/lib/api.js +17 -3
- package/lib/client.js +154 -37
- package/lib/config.js +22 -1
- package/lib/dream/sleep.js +14 -1
- package/lib/heat.js +136 -0
- package/lib/service.js +24 -7
- package/lib/settings.js +1 -0
- package/package.json +2 -3
- package/src/api.js +17 -3
- package/src/config.js +22 -1
- package/src/dream/sleep.js +14 -1
- package/src/heat.js +136 -0
- package/src/service.js +24 -7
- package/src/settings.js +1 -0
- package/test/api.test.js +33 -5
- package/test/client.test.js +70 -13
- package/test/heat.test.js +148 -0
- package/test/recall-layer.test.js +15 -4
- package/test/sleep-heat.test.js +125 -0
- package/test/sleep.test.js +12 -4
- package/test/updated-at-semantics.test.js +113 -0
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@ English | [简体中文](README.md)
|
|
|
7
7
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
10
|
-
[](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
|
|
|
@@ -162,10 +162,11 @@ 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.
|
|
165
|
+
> ⚠️ **Archival note**: pre-v0.7.12 rows below record experimental features later removed in the **v0.7.11** rewrite (Wiki-Link, tag system/directory/tag-boost, user/fact layered types, prefix-id resolution, /stats and /directory endpoints). v0.7.11 and v0.7.12 shipped the same day; historical docs credited v0.7.12, corrected here to v0.7.11. Unrecorded removals in the same rewrite: session lifecycle (`session_disposed_at` soft-hide), provenance birth-session tracking (`session_id`), and the decision-field normalizer (`normalizeDecisions`, superseded by strict prompt schema + `extractJsonArray`). They are version history only — **not current capability**. (The heat model was fully restored in v0.7.20 from v0.7.10, see below.) Current features are what this README's body and the [config table](#-configuration) describe.
|
|
166
166
|
|
|
167
167
|
| Version | Highlights |
|
|
168
168
|
|------|------|
|
|
169
|
+
| **v0.7.20** | Heat model restored (issue #87): v0.7.0 self-evolving memory back (`src/heat.js` power-law decay `H=1/(1+λΔt)^α` + per-type half-lives), sleep demotion dual protection (cold time-window + heat<0.05 + importance<5), touchRecalled gating back on `heatEnabled`, entity heat projection (ego node heat → size/opacity), recall_runs recording on by default; **default OFF** (matches v0.7.12 behavior out of the box) + feature_flags whitelist rollback switch + lightMode linkage + sleep demotion audit counts exposed (workbench can show "N demoted") + phase-two frontend (/list heat projection, HeatBadge three-tier badge, self-gated status heat-distribution card, order=heat page-local sort, all self-gated); better-sidebar fix (issue #88: soft integration moved to an inner dynamic sub-plugin, no more startup failure without bs); 685 tests green |
|
|
169
170
|
| **v0.7.18** | Ecosystem step one + query convergence: better-sidebar soft integration (inject declaration + optional peer `dsh-better-sidebar` + registerTab reusing the four views, safe skip when absent; narrow-container `@container` adaptation) + `/list?deposited=only` view (receipt_chain ∪ source=dream) + deposited/archived filter chips in the library + status-page dashboard (server totals + view-all jumps) + drawer restore for archived memories; 667 tests green |
|
|
170
171
|
| **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 |
|
|
171
172
|
| **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 |
|
|
@@ -211,7 +212,7 @@ Every **background LLM call** (autoDream consolidation + summary, autoSummarize
|
|
|
211
212
|
| 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 |
|
|
212
213
|
| 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 |
|
|
213
214
|
| 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 |
|
|
214
|
-
| v0.7.13–0.7.
|
|
215
|
+
| v0.7.13–0.7.20 | ✅ Done | Distillation, security, desktop, dream fix, panel polish, ecosystem, heat restore | 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; better-sidebar soft integration (inner dynamic sub-plugin fix, issue #88) + deposited/archived library views; heat model restored (issue #87) with phase-two frontend + archival audit (issue #87); 685 tests green |
|
|
215
216
|
| **v0.8.0** | 🚧 Planned (late Sep) | Graph enhancement | Interest-drift visualization + scope isolation (issue #17) + cross-workspace sharing |
|
|
216
217
|
|
|
217
218
|
> 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.
|
|
@@ -317,6 +318,7 @@ It works out of the box with the defaults. To adjust, override in `~/.dsh/profil
|
|
|
317
318
|
| `evalPersistTestResults` | `false` | Retrieval evaluation persistence (v0.4.5, opt-in, off by default): when enabled, `evaluateRetrieval` writes precision/recall/mrr snapshots into `recall_evals`; when off it only returns them to the caller without persisting. Production `searchMemories` auditing always goes through `recall_runs`, unconditionally never touching `recall_evals` |
|
|
318
319
|
| `autoReindexOnBoot` | `true` | When existing memories lack embeddings and vectors are configured, a delayed, rate-limited background backfill rebuild starts after boot (set to `false` for manual rebuild only) |
|
|
319
320
|
| `hybridInject` | `true` | Semantic-recall-first injection (v0.4.6, Bug4): when `injectCandidates` receives a non-empty query, it first recalls candidates via the vector index semantically, then fills in/deduplicates with rule-based filtering; empty query / no vectors falls back to the old logic |
|
|
321
|
+
| `heatEnabled` | `false` | Heat-model master switch (v0.7.0 / restored in v0.7.20, **off by default** — v0.7.12+ users expect no-heat behavior): when on, provides heat fields / sleep heat-combined demotion protection / frontend heat projection without changing recall ranking; when off, skips all heat computation and touch, and sleep demotion reverts to pure time-tiering. Also on the feature_flags whitelist (panel-toggleable rollback switch); light-mode preset forces it off |
|
|
320
322
|
| `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10}` | Memory quality filter (v0.4.6, on by default): heuristic 0-100 scoring before write; meta-memory vocabulary/self-reference/overly short/duplicate/near-duplicate content deduct points; ≥60 stored normally, 30-60 down-weighted (injection ranked by importance×quality/100), <30 archived and flagged `low_quality` (still recallable via explicit search, never auto-injected) |
|
|
321
323
|
| `llmAudit` | `{enabled:true, retentionDays:90}` | LLM usage audit (v0.4.6, on by default): every background LLM call (autoDream/autoSummarize) writes `llm_audit_logs` (tokens/duration/status/source); failures are recorded as error without blocking; read-only APIs `/api/dsh-mneme/semantic/llm-audit` + `/llm-audit/stats` |
|
|
322
324
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
9
9
|
[](https://github.com/modusensus/dsh-mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -34,7 +34,7 @@ dsh web
|
|
|
34
34
|
- 不是向量数据库——语义搜索是可选增强,默认零额外依赖
|
|
35
35
|
- 不替代会话日志——它存的是「值得跨会话记住的」精炼知识
|
|
36
36
|
- 不改变模型本身——进化的是记忆库与每次注入的上下文
|
|
37
|
-
- 删对话 ≠
|
|
37
|
+
- 删对话 ≠ 删记忆——记忆是跨会话的全局知识(v0.7.11 起不再与会话绑定),删除会话不影响已存入的记忆
|
|
38
38
|
|
|
39
39
|
## ✨ 功能
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ dsh web
|
|
|
42
42
|
|
|
43
43
|
- **SQLite 主存储**:`~/.dsh/memory/memory.db`,`node:sqlite` 内置,零原生依赖
|
|
44
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.
|
|
45
|
+
- **9 种记忆类型**:`preference` 偏好 / `project` 项目 / `decision` 决策 / `history` 历史 / `summary` 会话总览 / `pattern` 模式 + 编码记忆三型 `rejected_solution` 被否方案 / `pitfall` 踩坑 / `constraint` 约束(v0.7.13 起,`codingRetrospect` 默认关;v0.7.11 近重写已收窄删除 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` 上界,负数/溢出拒绝
|
|
@@ -107,15 +107,6 @@ dsh web
|
|
|
107
107
|
|
|
108
108
|
> 配置详见 `docs/SLEEP.md`;迁移说明见 `docs/MIGRATION.md`。
|
|
109
109
|
|
|
110
|
-
### 会话生命周期:把对话当存档点 💾(v0.6.0,opt-in)
|
|
111
|
-
|
|
112
|
-
**默认关闭**(`sessionLifecycleEnabled: false`,保持旧行为)。开启后,会话被删除/销毁(DSH `session/disposed` 事件)时,自动把该会话内出生(`session_id` 溯源)的记忆**软隐藏**——不再出现在检索/注入/列表/整理,但**不删除**,随时可恢复:
|
|
113
|
-
|
|
114
|
-
- **与 `archived` 正交**:`archived` 是用户/AI 主动"长期保留但安静",`session_disposed_at` 是会话删除被动隔离,两者互不覆盖。恢复会话绝不复活你手动归档的记忆
|
|
115
|
-
- **全局记忆免疫**:存量无 `session_id` 的记忆视为全局,永不参与会话清理
|
|
116
|
-
- **幂等 + 熔断**:dispose/restore 状态守卫幂等(重复调用 no-op);事件回调内部异常 catch 住,不抛进 DSH 会话清理流程
|
|
117
|
-
- **恢复**:整会话 `service.restoreBySession(sessionId)` 一键还原;`service.listBySession(sessionId, { includeDisposed: true })` 可查看当前隐藏了哪些(DTO 带 `disposed` 标记)
|
|
118
|
-
|
|
119
110
|
官方设置面板 → 「记忆库设置」→「记忆」标签:按类型浏览、全文搜索;启用向量搜索后可用「语义」切换做向量召回。
|
|
120
111
|
|
|
121
112
|
### 用户设置(画像 / 规则)与自定义指令 ⚙️
|
|
@@ -212,10 +203,11 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
212
203
|
|
|
213
204
|
## 🆕 最近版本亮点
|
|
214
205
|
|
|
215
|
-
> ⚠️ **历史存档提示**:下方及路线图表中 v0.7.12 之前的早期条目记录的实验功能(
|
|
206
|
+
> ⚠️ **历史存档提示**:下方及路线图表中 v0.7.12 之前的早期条目记录的实验功能(Wiki-Link、tag 系统/目录/tag 加权、user/fact 分层类型、前缀 id 解析、/stats 与 /directory 端点等)已在 **v0.7.11** 近重写时移除(v0.7.11 与 v0.7.12 同日发布,历史文档多标 v0.7.12,此处勘误归一到 v0.7.11),仅作版本历史存档,**不代表当前能力**。同期未记载的移除另有:会话生命周期(`session_disposed_at` 软隐藏)、provenance 出生会话溯源(`session_id`)、决策字段归一化(normalizeDecisions,已由提示词硬规范取代)。(heat 热度模型已于 v0.7.20 从 v0.7.10 完整找回回归,见下方 [v0.7.20](#recent-version-highlights)。)当前特性以本 README 正文与 [配置表](#-配置) 为准。
|
|
216
207
|
|
|
217
208
|
| 版本 | 亮点 |
|
|
218
209
|
|------|------|
|
|
210
|
+
| **v0.7.20** | heat 热度模型回归(issue #87):找回 v0.7.0 自进化记忆(`src/heat.js` 幂律衰减 `H=1/(1+λΔt)^α` + per-type 差异化半衰期)、sleep 降级热联合双保护(时间窗冷 + heat<0.05 + importance<5)、touchRecalled 门控改回 `heatEnabled`、实体热投影(ego 节点 heat → 前端大小/明暗)、recall_runs 默认记录;**默认关**(默认=与 v0.7.12 行为一致)+ feature_flags 白名单回滚开关 + lightMode 联动 + sleep 降级审计计数暴露(工作动态可展示"降级 N 条")+ 阶段二前端(/list heat 投影、HeatBadge 三档徽章、状态页热度分布卡、order=heat 页内排序,全部自门控);better-sidebar 修复(issue #88:软集成改内层动态子插件,无 bs 环境不再启动失败);685 测试全绿 |
|
|
219
211
|
| **v0.7.18** | 生态第一步 + 查询收敛:better-sidebar 软集成(inject 声明 + optional peer `dsh-better-sidebar` + registerTab 复用四视图,未装安全跳过;窄容器 `@container` 适配)+ `/list?deposited=only` 沉淀视图(receipt_chain ∪ source=dream)+ 记忆库沉淀/已归档筛选 chip + 状态页仪表盘化(统计 + 查看全部跳转预置筛选)+ 抽屉归档记忆「恢复」;667 测试全绿 |
|
|
220
212
|
| **v0.7.17** | 面板体验细化:侧边栏入口持续对齐宿主(MutationObserver 同步「新会话」类名 + `width:100%` + 交还原生居中,皮肤异步改写不再失配)+ 重要性星级换 Lucide 星形(`ImportanceStars` 实心/空心组件,替换文本 ★)+ 工具栏下拉层级修复(z-index 提到容器,导出/导入菜单不再被吸顶月份头遮挡);664 测试全绿 |
|
|
221
213
|
| **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 测试全绿 |
|
|
@@ -347,8 +339,6 @@ dsh web
|
|
|
347
339
|
dreamThresholdCount: 10
|
|
348
340
|
dreamThresholdChars: 5000
|
|
349
341
|
dreamDelayMs: 2000
|
|
350
|
-
# 会话生命周期(v0.6.0,默认关):会话被删时软隐藏其记忆,可恢复
|
|
351
|
-
# sessionLifecycleEnabled: false
|
|
352
342
|
```
|
|
353
343
|
|
|
354
344
|
## ⚙️ 配置
|
|
@@ -404,6 +394,12 @@ dsh web
|
|
|
404
394
|
| `bm25SearchEnabled` | `true` | BM25 稀疏第三路召回(v0.5.0):ASCII 词元 + CJK bigram,IDF 加权,散词/ID/代码片段查询不再依赖子串命中 |
|
|
405
395
|
| `adaptiveThresholdEnabled` | `true` | 自适应相似度阈值(v0.5.0):按查询形态动态截断(前缀 0.5 / 短查询 0.7 / 长查询 0.6 / 头部分差大放宽 0.5),显式传 `threshold` 走旧行为 |
|
|
406
396
|
| `hotMemoryEnabled` | `true` | 会话级短期热记忆总开关(v0.5.0):关闭后热记忆块不再注入(长期召回不受影响) |
|
|
397
|
+
| `heatEnabled` | `false` | 热度模型总开关(v0.7.0 / v0.7.20 回归,**默认关**——v0.7.12 起用户已习惯无 heat 行为):开启后提供热度字段 / sleep 热联合降级保护 / 前端热度投影,不改变召回排序;关闭则跳过 heat 计算与热度触达,sleep 降级退回纯时间分层。也走 feature_flags 白名单(面板可启停=回滚开关),lightMode 预设强制关 |
|
|
398
|
+
| `heatGlobalAlpha` | `1.2` | 幂律形状参数 α(`H=1/(1+λΔt)^α`),越大衰减越快 |
|
|
399
|
+
| `heatTypeDecay` | 内置 TYPE_DECAY | per-type 衰减因子 λ;λ=0 的类型免疫(preference/pattern/summary 热度恒 1.0,sleep 永不降级) |
|
|
400
|
+
| `sleepHeatThreshold` | `0.05` | sleep 降级联合判定热度下限:heat<该值 **且** importance<5 才允许降级 |
|
|
401
|
+
| `recallRecordDefault` | `true` | recall_runs 记录默认开(显式传 `recordRecall:false` 的调用方不受影响) |
|
|
402
|
+
| `recallRetentionDays` | `90` | recall_runs 滚动清理保留天数 |
|
|
407
403
|
| `hotMemoryRounds` | `5` | 会话级短期热记忆轮次(v0.5.0):最近 N 轮对话滚动注入,从会话事件日志无状态重建、不落库 |
|
|
408
404
|
| `hotMemoryMaxTokens` | `2000` | 热记忆 token 预算(v0.5.0,200-32000),超出滚动截断 |
|
|
409
405
|
| `selectiveInjectEnabled` | `true` | 选择性注入(v0.5.0):query 向量可用时注入候选按主题相似度重排,替代固定规则序 |
|
|
@@ -411,8 +407,6 @@ dsh web
|
|
|
411
407
|
| `searchSemanticDedupThreshold` | `0.95` | 语义去重相似度阈值(v0.5.0,默认 0.95,范围 0.5-1.0):`searchSemanticDedup=true` 时生效,调整可防小模型误折叠 |
|
|
412
408
|
| `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10}` | 记忆质量过滤(v0.4.6,默认开):写库前启发式打分 0-100,元记忆词汇/自指/过短/重复/近似重复扣分;≥60 正常存储,30-60 降权(注入排序按 importance×quality/100),<30 归档标记 `low_quality`(显式搜索仍可召回,永不自动注入) |
|
|
413
409
|
| `llmAudit` | `{enabled:true, retentionDays:90}` | LLM 消耗审计(v0.4.6,默认开):每次后台 LLM 调用(autoDream/autoSummarize)写 `llm_audit_logs`(tokens/duration/status/source);失败记 error 不阻塞;只读 API `/api/dsh-mneme/semantic/llm-audit` + `/llm-audit/stats` |
|
|
414
|
-
| `sessionLifecycleEnabled` | `false` | 会话生命周期(v0.6.0,默认关):开启后会话被删除/销毁时自动把该会话出生的记忆软隐藏(`session_disposed_at`,与 `archived` 正交、可恢复);存量无 `session_id` 的记忆永不参与清理 |
|
|
415
|
-
| `showSidebarTrigger` | `true` | 侧边栏底部(左下角)记忆入口按钮(issue #38,默认开):与其他插件(如 dsh-cost-meter)抢占同一 footer slot 导致 UI 冲突时可关掉;仅隐藏按钮,记忆库仍可通过顶部「记忆库」标签访问。Web 面板「设置」里有对应开关 |
|
|
416
410
|
|
|
417
411
|
> 🔐 **API 安全**:DSH 无内置鉴权且默认仅监听 `127.0.0.1`。插件 API 默认开放(便于 Web 面板即装即用)。如需防护(如局域网暴露),在配置中设置 `apiToken`:写操作(画像/规则/命令)与密钥端点(`vector-config`、`vector-reindex`)需携带 `Authorization: Bearer <token>`(前端设置面板可填入同一 token),只读的 `list` / `search` / `semantic` 保持开放。`/api/dsh-mneme/vector-config` 返回的 `apiKey` 已掩码(`sk-***…`),存储仍保留明文供调用;前端回传空或掩码值表示"不改 key"。
|
|
418
412
|
|
package/cordis.patch.yml
CHANGED
|
@@ -13,4 +13,9 @@
|
|
|
13
13
|
dreamThresholdCount: 10
|
|
14
14
|
dreamThresholdChars: 5000
|
|
15
15
|
dreamDelayMs: 2000
|
|
16
|
+
# 巩固默认压低推理:思考型模型(如方舟 v4 系)会把 dreamMaxTokens
|
|
17
|
+
# 预算花在推理上、正文空体 → "no json array in llm output"。low 让
|
|
18
|
+
# provider 封顶推理;拒绝 effort 字段的 provider 由 runAuditedLlm
|
|
19
|
+
# 自动去掉重试,行为安全(v0.7.16 机制)。
|
|
20
|
+
dreamReasoningEffort: low
|
|
16
21
|
entityExtractionEnabled: true
|
package/lib/api.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { timingSafeEqual, randomBytes } from "node:crypto";
|
|
4
4
|
import { FEATURE_FLAG_SPEC } from "./settings.js";
|
|
5
5
|
import { TYPE_FILE, renderMirrorText, parseHumanEdits } from "./mirror.js";
|
|
6
|
+
import { computeHeat } from "./heat.js";
|
|
6
7
|
|
|
7
8
|
// headers:少数端点(/export 附件下载)需要追加 Content-Disposition 等响应头。
|
|
8
9
|
function sendJson(res, status, payload, headers = {}) {
|
|
@@ -185,10 +186,14 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
185
186
|
const rows = service.list({ type, limit, offset, order, minImportance, source, updatedFrom, updatedTo, onlyArchived, depositedOnly });
|
|
186
187
|
// 面板行在 wire DTO 之上补 archived/quality_score——模型工具的输出
|
|
187
188
|
// schema 严格复用 toApiList,扩展只发生在 HTTP 层。
|
|
189
|
+
// heat 投影(阶段二前端数据源):仅 heatEnabled=true 时下发逐条热度
|
|
190
|
+
// (heat.js 纯函数,λ=0 免疫类型恒 1.0);字段缺省时前端徽章自动隐藏。
|
|
191
|
+
const heatOn = config?.heatEnabled === true;
|
|
188
192
|
const items = service.toApiList(rows).map((m, i) => ({
|
|
189
193
|
...m,
|
|
190
194
|
archived: rows[i].archived === true || rows[i].archived === 1,
|
|
191
|
-
quality_score: rows[i].quality_score ?? null
|
|
195
|
+
quality_score: rows[i].quality_score ?? null,
|
|
196
|
+
...(heatOn ? { heat: computeHeat(rows[i], Date.now(), config ?? {}) } : {})
|
|
192
197
|
}));
|
|
193
198
|
// Total honors the same filters as the rows, or the pager's
|
|
194
199
|
// has-more math breaks whenever minImportance/source/updated-at
|
|
@@ -590,7 +595,10 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
590
595
|
name: n.name,
|
|
591
596
|
type: n.type ?? null,
|
|
592
597
|
mention_count: n.mention_count ?? 1,
|
|
593
|
-
distance: n.distance
|
|
598
|
+
distance: n.distance,
|
|
599
|
+
// v0.7.0 实体热投影:实体热 = 关联记忆 heat 聚合(max),前端据此
|
|
600
|
+
// 缩放节点大小/明暗。heatEnabled=false 时 entityHeat 返回 null。
|
|
601
|
+
heat: service.entityHeat?.(n.id) ?? null
|
|
594
602
|
})),
|
|
595
603
|
edges: [...edgeMap.values()].map((e) => ({
|
|
596
604
|
id: e.id,
|
|
@@ -779,7 +787,13 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
779
787
|
status: r.status,
|
|
780
788
|
provider: r.provider ?? null,
|
|
781
789
|
model: r.model ?? null,
|
|
782
|
-
error: r.error ?? null
|
|
790
|
+
error: r.error ?? null,
|
|
791
|
+
run_type: r.run_type ?? "auto",
|
|
792
|
+
// sleep 审计:heat/时间分层降级决策计数(工作动态可展示"降级 N 条")。
|
|
793
|
+
// 数据来自 runSleep 写入的 decisions.demotion({demoted,archived} 数组)。
|
|
794
|
+
demotion: r.decisions?.demotion
|
|
795
|
+
? { demoted: (r.decisions.demotion.demoted ?? []).length, archived: (r.decisions.demotion.archived ?? []).length }
|
|
796
|
+
: null
|
|
783
797
|
}));
|
|
784
798
|
const pendingConflicts = service.countConflictPending?.() ?? 0;
|
|
785
799
|
const ids = new Set();
|
package/lib/client.js
CHANGED
|
@@ -55,7 +55,8 @@ window.__ModuleLoader__.load({
|
|
|
55
55
|
copy: [["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }], ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }]],
|
|
56
56
|
check: [["path", { d: "M20 6 9 17l-5-5" }]],
|
|
57
57
|
inbox: [["polyline", { points: "22 12 16 12 14 15 10 15 8 12 2 12" }], ["path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" }]],
|
|
58
|
-
activity: [["path", { d: "M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" }]]
|
|
58
|
+
activity: [["path", { d: "M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" }]],
|
|
59
|
+
flame: [["path", { d: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" }]]
|
|
59
60
|
};
|
|
60
61
|
const Icon = ({ name, size = 16, className }) => {
|
|
61
62
|
const parts = ICON_PATHS[name];
|
|
@@ -92,6 +93,18 @@ window.__ModuleLoader__.load({
|
|
|
92
93
|
}))));
|
|
93
94
|
};
|
|
94
95
|
|
|
96
|
+
// 热度徽章(阶段二):flame 图标 + 整数百分比,三档配色(热/温/冷)。
|
|
97
|
+
// /list 仅在 heatEnabled=true 时下发 heat 字段——缺省即不渲染,开关关闭
|
|
98
|
+
// 时徽章全站自动消失,前端无需感知开关状态。
|
|
99
|
+
const HeatBadge = ({ value, size = 12 }) => {
|
|
100
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
101
|
+
const pct = Math.round(Math.min(1, Math.max(0, value)) * 100);
|
|
102
|
+
const tier = pct >= 66 ? "hot" : pct >= 33 ? "warm" : "cold";
|
|
103
|
+
return h("span", { className: `mneme-heat mneme-heat--${tier}`, title: `${pct}%` },
|
|
104
|
+
h(Icon, { name: "flame", size, className: "mneme-heatico" }),
|
|
105
|
+
h("span", { className: "mneme-heatpct" }, `${pct}%`));
|
|
106
|
+
};
|
|
107
|
+
|
|
95
108
|
// Unified API fetcher: attaches the optional apiToken (set in the settings
|
|
96
109
|
// view, persisted in localStorage) as a Bearer header. When no token has
|
|
97
110
|
// been configured the header is omitted and the API stays open (default).
|
|
@@ -105,7 +118,11 @@ window.__ModuleLoader__.load({
|
|
|
105
118
|
return fetch(path, { ...opts, headers });
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
|
|
121
|
+
// ⚠️ 'betterSidebar' 绝不能进模块级 inject(issue #88 实锤):loader 对
|
|
122
|
+
// inject 声明的服务是硬等待——未安装 better-sidebar 的环境里整个 entry
|
|
123
|
+
// 永远 pending("1 entry did not activate" → Failed to load plugins)。
|
|
124
|
+
// 软依赖靠内层动态子插件探测(见 apply 内 better-sidebar 挂载块)。
|
|
125
|
+
const inject = ["slots", "locale"];
|
|
109
126
|
|
|
110
127
|
const NS = "memory";
|
|
111
128
|
|
|
@@ -174,7 +191,11 @@ window.__ModuleLoader__.load({
|
|
|
174
191
|
"memory.explorer.updated": "更新",
|
|
175
192
|
"memory.explorer.tags": "标签",
|
|
176
193
|
"memory.explorer.importance": "重要性",
|
|
194
|
+
"memory.explorer.heat": "热度",
|
|
177
195
|
"memory.explorer.sourceFilter": "来源",
|
|
196
|
+
"memory.explorer.sort": "排序",
|
|
197
|
+
"memory.explorer.heatSort": "热度优先",
|
|
198
|
+
"memory.explorer.heatSortHint": "对已加载条目按热度降序(页内排序,非全局序;时间树保持时间序)",
|
|
178
199
|
"memory.explorer.filterDeposited": "沉淀",
|
|
179
200
|
"memory.explorer.filterDepositedHint": "只看 autoDream 巩固/更新过的记忆",
|
|
180
201
|
"memory.explorer.filterArchived": "已归档",
|
|
@@ -362,6 +383,8 @@ window.__ModuleLoader__.load({
|
|
|
362
383
|
"memory.status.consolidated": "沉淀的记忆",
|
|
363
384
|
"memory.status.consolidatedEmpty": "autoDream / autoSummarize 沉淀的记忆会出现在这里",
|
|
364
385
|
"memory.status.archivedMemories": "已归档的记忆",
|
|
386
|
+
"memory.status.heatDistribution": "热度分布",
|
|
387
|
+
"memory.status.heatHint": "热门(≥66%){hot} · 温热(33–66%){warm} · 冷却(<33%){cold},样本为最近 {sample} 条",
|
|
365
388
|
"memory.status.viewAll": "查看全部",
|
|
366
389
|
"memory.status.depositedCount": "沉淀的记忆({n})",
|
|
367
390
|
"memory.status.archivedCount": "已归档的记忆({n})",
|
|
@@ -448,7 +471,11 @@ window.__ModuleLoader__.load({
|
|
|
448
471
|
"memory.explorer.updated": "Updated",
|
|
449
472
|
"memory.explorer.tags": "Tags",
|
|
450
473
|
"memory.explorer.importance": "Importance",
|
|
474
|
+
"memory.explorer.heat": "Heat",
|
|
451
475
|
"memory.explorer.sourceFilter": "Source",
|
|
476
|
+
"memory.explorer.sort": "Sort",
|
|
477
|
+
"memory.explorer.heatSort": "By heat",
|
|
478
|
+
"memory.explorer.heatSortHint": "Sort loaded items by heat, in-page (not global; the month tree keeps chronological order)",
|
|
452
479
|
"memory.explorer.filterDeposited": "Deposited",
|
|
453
480
|
"memory.explorer.filterDepositedHint": "Only memories autoDream consolidated or updated",
|
|
454
481
|
"memory.explorer.filterArchived": "Archived",
|
|
@@ -636,6 +663,8 @@ window.__ModuleLoader__.load({
|
|
|
636
663
|
"memory.status.consolidated": "Deposited memories",
|
|
637
664
|
"memory.status.consolidatedEmpty": "Memories deposited by autoDream / autoSummarize appear here",
|
|
638
665
|
"memory.status.archivedMemories": "Archived memories",
|
|
666
|
+
"memory.status.heatDistribution": "Heat distribution",
|
|
667
|
+
"memory.status.heatHint": "hot (≥66%) {hot} · warm (33–66%) {warm} · cooled (<33%) {cold}, sampled from the latest {sample}",
|
|
639
668
|
"memory.status.viewAll": "View all",
|
|
640
669
|
"memory.status.depositedCount": "Deposited memories ({n})",
|
|
641
670
|
"memory.status.archivedCount": "Archived memories ({n})",
|
|
@@ -871,6 +900,12 @@ window.__ModuleLoader__.load({
|
|
|
871
900
|
// wrapper display:contents 隐身,按钮成为侧边栏弹性布局的直接子元素;
|
|
872
901
|
// 盒模型/间距/收起态 rail 几何全部继承宿主,我们只覆盖配色为次级观感。
|
|
873
902
|
".mneme-stars{display:inline-flex;align-items:center;gap:2px}",
|
|
903
|
+
// 热度徽章三档:热(橙)/温(次级文字)/冷(弱化文字),冷档藏百分比只留图标。
|
|
904
|
+
".mneme-heat{display:inline-flex;align-items:center;gap:2px;font-size:11px;line-height:14px;font-variant-numeric:tabular-nums}",
|
|
905
|
+
".mneme-heat--hot{color:var(--dsw-alias-state-warning,#d97706)}",
|
|
906
|
+
".mneme-heat--warm{color:var(--dsw-alias-label-secondary)}",
|
|
907
|
+
".mneme-heat--cold{color:var(--dsw-alias-label-tertiary)}",
|
|
908
|
+
".mneme-heat--cold .mneme-heatpct{display:none}",
|
|
874
909
|
// width:100% 与「新会话」同宽(同一弹性父容器);文案对齐交给复制来的
|
|
875
910
|
// 原生类,不再用 flex:1/text-align:left 覆盖成左对齐。
|
|
876
911
|
".mneme-topentry{display:contents}",
|
|
@@ -1049,7 +1084,16 @@ window.__ModuleLoader__.load({
|
|
|
1049
1084
|
|
|
1050
1085
|
function nodeRadius(n) {
|
|
1051
1086
|
// mention_count → area-ish growth, clamped so hubs stay legible.
|
|
1052
|
-
|
|
1087
|
+
const base = 7 + Math.min(20, Math.max(1, n.mention_count ?? 1)) * 0.55;
|
|
1088
|
+
// heat projection (0-1 or null): hot entities render slightly larger.
|
|
1089
|
+
if (n.heat == null) return base;
|
|
1090
|
+
return base + (Math.max(0, Math.min(1, n.heat)) - 0.5) * 4;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function nodeOpacity(n) {
|
|
1094
|
+
// heat projection: hot entities are bright (opacity 1), cold fade to 0.4.
|
|
1095
|
+
if (n.heat == null) return 1;
|
|
1096
|
+
return 0.4 + 0.6 * Math.max(0, Math.min(1, n.heat));
|
|
1053
1097
|
}
|
|
1054
1098
|
|
|
1055
1099
|
// Deterministic golden-angle spiral: no two nodes start overlapping, and
|
|
@@ -1498,7 +1542,7 @@ window.__ModuleLoader__.load({
|
|
|
1498
1542
|
onClick: () => onNodeClick(n),
|
|
1499
1543
|
"data-node": n.name
|
|
1500
1544
|
},
|
|
1501
|
-
h("circle", { r: nodeRadius(n), fill: typeColor(n.type) }),
|
|
1545
|
+
h("circle", { r: nodeRadius(n), fill: typeColor(n.type), fillOpacity: nodeOpacity(n) }),
|
|
1502
1546
|
h("text", { className: "mneme-glabel", y: nodeRadius(n) + 13 }, n.name)
|
|
1503
1547
|
))
|
|
1504
1548
|
)
|
|
@@ -2533,6 +2577,45 @@ window.__ModuleLoader__.load({
|
|
|
2533
2577
|
);
|
|
2534
2578
|
}
|
|
2535
2579
|
|
|
2580
|
+
// 热度分布卡(阶段二):采样最近 200 条的 heat 值做三档分布。自门控——
|
|
2581
|
+
// /list 不下发 heat(heatEnabled=false)时整卡不渲染,前端不感知开关。
|
|
2582
|
+
function HeatStatusCard({ t }) {
|
|
2583
|
+
const [state, setState] = useState({ loading: true, off: false, hot: 0, warm: 0, cold: 0, sample: 0 });
|
|
2584
|
+
useEffect(() => {
|
|
2585
|
+
let cancelled = false;
|
|
2586
|
+
apiFetch("/api/dsh-mneme/list?limit=200&order=chrono")
|
|
2587
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2588
|
+
.then((d) => {
|
|
2589
|
+
if (cancelled) return;
|
|
2590
|
+
const items = d.items || [];
|
|
2591
|
+
if (!items.length || typeof items[0].heat !== "number") {
|
|
2592
|
+
setState({ loading: false, off: true, hot: 0, warm: 0, cold: 0, sample: 0 });
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
let hot = 0, warm = 0, cold = 0;
|
|
2596
|
+
for (const m of items) {
|
|
2597
|
+
if (m.heat >= 0.66) hot++; else if (m.heat >= 0.33) warm++; else cold++;
|
|
2598
|
+
}
|
|
2599
|
+
setState({ loading: false, off: false, hot, warm, cold, sample: items.length });
|
|
2600
|
+
})
|
|
2601
|
+
.catch(() => { if (!cancelled) setState({ loading: false, off: true, hot: 0, warm: 0, cold: 0, sample: 0 }); });
|
|
2602
|
+
return () => { cancelled = true; };
|
|
2603
|
+
}, []);
|
|
2604
|
+
if (state.off) return null;
|
|
2605
|
+
return h(StatusCard, {
|
|
2606
|
+
t,
|
|
2607
|
+
title: t("memory.status.heatDistribution"),
|
|
2608
|
+
loading: state.loading,
|
|
2609
|
+
error: false,
|
|
2610
|
+
num: `${state.hot}`,
|
|
2611
|
+
cap: t("memory.status.heatHint")
|
|
2612
|
+
.replace("{hot}", String(state.hot))
|
|
2613
|
+
.replace("{warm}", String(state.warm))
|
|
2614
|
+
.replace("{cold}", String(state.cold))
|
|
2615
|
+
.replace("{sample}", String(state.sample))
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2536
2619
|
function StatusPanel({ t, onBrowse }) {
|
|
2537
2620
|
return h("div", { className: "mneme-status" },
|
|
2538
2621
|
h("div", { className: "mneme-statusgrid" },
|
|
@@ -2540,7 +2623,8 @@ window.__ModuleLoader__.load({
|
|
|
2540
2623
|
h(EntitiesStatusCard, { t }),
|
|
2541
2624
|
h(VectorStatusCard, { t }),
|
|
2542
2625
|
h(LlmStatusCard, { t }),
|
|
2543
|
-
h(DreamStatusCards, { t })
|
|
2626
|
+
h(DreamStatusCards, { t }),
|
|
2627
|
+
h(HeatStatusCard, { t })
|
|
2544
2628
|
),
|
|
2545
2629
|
h(WorkbenchSection, { t, onBrowse })
|
|
2546
2630
|
);
|
|
@@ -2629,6 +2713,9 @@ window.__ModuleLoader__.load({
|
|
|
2629
2713
|
onChange: (e) => setImportance(Number(e.target.value))
|
|
2630
2714
|
}, [1, 2, 3, 4, 5].map((n) => h("option", { key: n, value: n }, "★".repeat(n))))
|
|
2631
2715
|
: h(ImportanceStars, { className: "mneme-dmetaval", value: memory.importance || 0 }),
|
|
2716
|
+
memory.heat != null && h(react.Fragment, null,
|
|
2717
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.heat")),
|
|
2718
|
+
h("span", { className: "mneme-dmetaval" }, h(HeatBadge, { value: memory.heat, size: 13 }))),
|
|
2632
2719
|
memory.source && h(react.Fragment, null,
|
|
2633
2720
|
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.source")),
|
|
2634
2721
|
h("span", { className: "mneme-dmetaval", title: memory.source }, memory.source)),
|
|
@@ -2798,6 +2885,7 @@ window.__ModuleLoader__.load({
|
|
|
2798
2885
|
// 方案 A:沉淀/归档筛选 chip——状态页「查看全部」也会带着它们跳转过来。
|
|
2799
2886
|
const [depositedOnly, setDepositedOnly] = useState(false); // 只看 autoDream 巩固
|
|
2800
2887
|
const [archivedOnly, setArchivedOnly] = useState(false); // 只看已归档
|
|
2888
|
+
const [heatSort, setHeatSort] = useState(false); // 卡片页内热度降序(阶段二补口)
|
|
2801
2889
|
const [pendingIds, setPendingIds] = useState(() => new Set());
|
|
2802
2890
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
2803
2891
|
const [importOpen, setImportOpen] = useState(false);
|
|
@@ -2967,6 +3055,13 @@ window.__ModuleLoader__.load({
|
|
|
2967
3055
|
// the grouping follows the sort order instead of re-sorting buckets.
|
|
2968
3056
|
const sorted = [...visible].sort((a, b) =>
|
|
2969
3057
|
new Date(b.updated_at || b.created_at || 0) - new Date(a.updated_at || a.created_at || 0));
|
|
3058
|
+
// heatSort(阶段二补口,order=heat 的前端实现):热度是运行时投影、
|
|
3059
|
+
// 无存储序,SQL 排不了;页内对已加载条目降序(非全局序),时间树
|
|
3060
|
+
// 仍走 chrono 不受影响。开关仅在 heat 字段在场上时出现(自门控)。
|
|
3061
|
+
const heatAvailable = visible.some((m) => typeof m.heat === "number");
|
|
3062
|
+
const gridItems = heatSort
|
|
3063
|
+
? [...sorted].sort((a, b) => (b.heat ?? 0) - (a.heat ?? 0))
|
|
3064
|
+
: sorted;
|
|
2970
3065
|
const months = [];
|
|
2971
3066
|
let curMonth = null, curDay = null;
|
|
2972
3067
|
for (const m of sorted) {
|
|
@@ -3231,6 +3326,19 @@ window.__ModuleLoader__.load({
|
|
|
3231
3326
|
onClick: () => setArchivedOnly(!archivedOnly)
|
|
3232
3327
|
}, t("memory.explorer.filterArchived"))
|
|
3233
3328
|
),
|
|
3329
|
+
heatAvailable && h(react.Fragment, null,
|
|
3330
|
+
h("div", { className: "mneme-xcolhead" }, t("memory.explorer.sort")),
|
|
3331
|
+
h("div", { className: "mneme-xrow" },
|
|
3332
|
+
h("button", {
|
|
3333
|
+
className: heatSort ? "mneme-chip mneme-active" : "mneme-chip",
|
|
3334
|
+
title: t("memory.explorer.heatSortHint"),
|
|
3335
|
+
onClick: () => {
|
|
3336
|
+
setHeatSort(!heatSort);
|
|
3337
|
+
if (!heatSort) switchViewMode("cards"); // 排序只作用于卡片网格
|
|
3338
|
+
}
|
|
3339
|
+
}, t("memory.explorer.heatSort"))
|
|
3340
|
+
)
|
|
3341
|
+
),
|
|
3234
3342
|
h("div", { className: "mneme-xcolhead" }, t("memory.explorer.types")),
|
|
3235
3343
|
h("button", {
|
|
3236
3344
|
className: type === "all" ? "mneme-xtype mneme-active" : "mneme-xtype",
|
|
@@ -3255,7 +3363,7 @@ window.__ModuleLoader__.load({
|
|
|
3255
3363
|
? h("div", { className: "mneme-xempty", style: { gridColumn: "1 / -1" } },
|
|
3256
3364
|
h(Icon, { name: "inbox", size: 20, className: "mneme-xemptyico" }),
|
|
3257
3365
|
t("memory.explorer.empty"))
|
|
3258
|
-
:
|
|
3366
|
+
: gridItems.map((m) => h("button", {
|
|
3259
3367
|
key: m.id,
|
|
3260
3368
|
type: "button",
|
|
3261
3369
|
className: m.id === selectedId ? "mneme-card mneme-active" : "mneme-card",
|
|
@@ -3271,6 +3379,7 @@ window.__ModuleLoader__.load({
|
|
|
3271
3379
|
h("div", { className: "mneme-cardexcerpt" }, m.content || ""),
|
|
3272
3380
|
h("div", { className: "mneme-cardfoot" },
|
|
3273
3381
|
h(ImportanceStars, { value: m.importance || 0, size: 12 }),
|
|
3382
|
+
h(HeatBadge, { value: m.heat }),
|
|
3274
3383
|
m.source && h("span", { className: "mneme-cardsrc", title: m.source }, m.source)
|
|
3275
3384
|
)
|
|
3276
3385
|
)),
|
|
@@ -3480,39 +3589,47 @@ window.__ModuleLoader__.load({
|
|
|
3480
3589
|
));
|
|
3481
3590
|
});
|
|
3482
3591
|
|
|
3483
|
-
// --- better-sidebar 生态 tab
|
|
3484
|
-
//
|
|
3485
|
-
//
|
|
3486
|
-
//
|
|
3487
|
-
//
|
|
3488
|
-
//
|
|
3489
|
-
//
|
|
3490
|
-
//
|
|
3592
|
+
// --- better-sidebar 生态 tab(可选软依赖)---
|
|
3593
|
+
// ⚠️ 平台事实(issue #88 实测 + dsh-server-deck 同款方案):模块级
|
|
3594
|
+
// inject 声明 betterSidebar 是硬等待——未安装 bs 的环境整个 entry
|
|
3595
|
+
// pending("1 entry did not activate" → Failed to load plugins),
|
|
3596
|
+
// v0.7.18 即因此对无 bs 用户启动失败。正确双模式(server-deck 验证
|
|
3597
|
+
// 过):外层入口零 inject 立即激活(独立模式保底);tab 注册挂在内层
|
|
3598
|
+
// 动态子插件 `ctx.plugin({ inject: ['betterSidebar'] })`,由 cordis
|
|
3599
|
+
// 原生等待服务——bs 未装时该内层 fiber 永远 INACTIVE,静默无害。
|
|
3600
|
+
// 兄弟上下文取未声明属性会直接抛错("cannot get property without
|
|
3601
|
+
// inject"),轮询探测方案不可用。
|
|
3491
3602
|
ctx.effect(() => {
|
|
3492
|
-
let
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3603
|
+
let dead = false;
|
|
3604
|
+
try {
|
|
3605
|
+
ctx.plugin?.({
|
|
3606
|
+
name: "dsh-mneme:bs-tab",
|
|
3607
|
+
inject: ["betterSidebar"],
|
|
3608
|
+
apply: (bsCtx) => {
|
|
3609
|
+
bsCtx.effect(() => {
|
|
3610
|
+
if (dead) return;
|
|
3611
|
+
const reg = bsCtx.betterSidebar;
|
|
3612
|
+
if (!reg || typeof reg.registerTab !== "function") return;
|
|
3613
|
+
try {
|
|
3614
|
+
reg.registerTab({
|
|
3615
|
+
id: "dsh-mneme:memory",
|
|
3616
|
+
title: () => t("memory.view.label"),
|
|
3617
|
+
icon: (size) => h(IconArchiveOutline20, { size }),
|
|
3618
|
+
order: 60,
|
|
3619
|
+
component: () => h(MemoryExplorer, { t })
|
|
3620
|
+
});
|
|
3621
|
+
} catch (err) {
|
|
3622
|
+
// id 重复等注册失败不应拖垮其余功能,留一条线索即可
|
|
3623
|
+
console.error("[dsh-mneme] better-sidebar registerTab failed:", err);
|
|
3624
|
+
}
|
|
3625
|
+
}, "dsh-mneme: better-sidebar tab");
|
|
3509
3626
|
}
|
|
3510
|
-
}
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
return () =>
|
|
3515
|
-
}, "dsh-mneme: better-sidebar
|
|
3627
|
+
});
|
|
3628
|
+
} catch (err) {
|
|
3629
|
+
console.error("[dsh-mneme] better-sidebar mount failed:", err);
|
|
3630
|
+
}
|
|
3631
|
+
return () => { dead = true; };
|
|
3632
|
+
}, "dsh-mneme: better-sidebar mount");
|
|
3516
3633
|
}
|
|
3517
3634
|
|
|
3518
3635
|
exports.apply = apply;
|
package/lib/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { TYPE_DECAY_DEFAULTS } from "./heat.js";
|
|
2
3
|
|
|
3
4
|
export const Config = z.object({
|
|
4
5
|
memoryDir: z.string().default("~/.dsh/memory"),
|
|
@@ -301,6 +302,24 @@ export const Config = z.object({
|
|
|
301
302
|
// reaches any service; a persisted panel_mode="light" (settings kv) counts
|
|
302
303
|
// as lightMode=true too and wins over the bundle config.
|
|
303
304
|
lightMode: z.boolean().default(false),
|
|
305
|
+
|
|
306
|
+
// --- heat: v0.7.0 self-evolution (heat + interest drift) ----------------
|
|
307
|
+
// 总开关,默认关(v0.7.12+ 用户已习惯无 heat 行为,默认开=全员行为变更)。
|
|
308
|
+
// 开启后:提供热度字段 / sleep 降级联合判定保护 / 前端热度投影,不改变
|
|
309
|
+
// 召回排序。关闭则跳过所有 heat 计算与热度触达,sleep 降级退回纯时间分层。
|
|
310
|
+
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
|
|
311
|
+
heatEnabled: z.boolean().default(false),
|
|
312
|
+
// 幂律形状参数 α(heat = 1/(1+λΔt)^α),越大衰减越快。
|
|
313
|
+
heatGlobalAlpha: z.number().min(0.1).max(5).default(1.2),
|
|
314
|
+
// per-type 衰减因子 λ;λ=0 的类型免疫(热度恒 1.0,sleep 永不降级)。
|
|
315
|
+
// 未知类型走默认 0.002。dict 的键为 type 字符串、值为数字 λ。
|
|
316
|
+
heatTypeDecay: z.dict(z.number(), z.string()).default({ ...TYPE_DECAY_DEFAULTS }),
|
|
317
|
+
// sleep 降级联合判定的热度下限:heat < 该值 且 importance<5 才允许降级。
|
|
318
|
+
sleepHeatThreshold: z.number().min(0).max(1).default(0.05),
|
|
319
|
+
// recordRecall 默认值(recall_runs 记录默认开;显式传 false 的调用方不受影响)。
|
|
320
|
+
recallRecordDefault: z.boolean().default(true),
|
|
321
|
+
// recall_runs 滚动清理保留天数。
|
|
322
|
+
recallRetentionDays: z.natural().min(1).max(3650).default(90),
|
|
304
323
|
});
|
|
305
324
|
|
|
306
325
|
// Fields forced to false by the light-mode preset. Everything not listed here
|
|
@@ -315,7 +334,9 @@ const LIGHT_MODE_OFF = [
|
|
|
315
334
|
"hybridInject",
|
|
316
335
|
"searchSemanticDedup",
|
|
317
336
|
"selectiveInjectEnabled",
|
|
318
|
-
"bm25SearchEnabled"
|
|
337
|
+
"bm25SearchEnabled",
|
|
338
|
+
// 轻量模式不开热计算(heat 属于重型增强;关掉后 sleep 降级也退回纯时间分层)。
|
|
339
|
+
"heatEnabled"
|
|
319
340
|
];
|
|
320
341
|
|
|
321
342
|
/**
|
package/lib/dream/sleep.js
CHANGED
|
@@ -18,6 +18,7 @@ import { randomUUID, createHash } from "node:crypto";
|
|
|
18
18
|
import { validateDecisions, applyDecisions } from "./decisions.js";
|
|
19
19
|
import { findPotentialConflicts } from "./clustering.js";
|
|
20
20
|
import { buildReceipt, withEffortFallback } from "../dream.js";
|
|
21
|
+
import { computeHeat } from "../heat.js";
|
|
21
22
|
|
|
22
23
|
const SUMMARY_MAX = 120;
|
|
23
24
|
// Conflict similarity threshold per strictness level (v0.4.0):
|
|
@@ -242,10 +243,22 @@ function phaseDemotion(service, config, logger, runId, signal = null) {
|
|
|
242
243
|
for (const m of service.all()) {
|
|
243
244
|
if (signal?.aborted) break;
|
|
244
245
|
if (m.archived || m.forgotten) continue;
|
|
245
|
-
const ref = m.last_accessed_at ?? m.
|
|
246
|
+
const ref = m.last_accessed_at ?? m.created_at;
|
|
246
247
|
if (!ref) continue;
|
|
247
248
|
const t = new Date(ref).getTime();
|
|
248
249
|
if (Number.isNaN(t)) continue;
|
|
250
|
+
// v0.7.0 热联合判定(仅 heatEnabled 时启用;默认关则退回纯时间分层,
|
|
251
|
+
// 与 v0.7.12 行为一致):时间窗之外再加两道保护闸——热度低于
|
|
252
|
+
// sleepHeatThreshold 且 importance<5 才允许降级。λ=0 的免疫类型 heat 恒
|
|
253
|
+
// 1.0 天然豁免(preference/pattern/summary 永不因 sleep 降级);importance
|
|
254
|
+
// ≥5 的紧要记忆无论多冷都保留。`冷但重要` 与 `热但低值` 均不满足条件。
|
|
255
|
+
const heatOn = config.heatEnabled !== false;
|
|
256
|
+
if (heatOn) {
|
|
257
|
+
const heat = computeHeat(m, Date.now(), config);
|
|
258
|
+
const heatProtected = heat >= (config.sleepHeatThreshold ?? 0.05);
|
|
259
|
+
const important = (m.importance ?? 0) >= 5;
|
|
260
|
+
if (heatProtected || important) continue;
|
|
261
|
+
}
|
|
249
262
|
if (t < compressCut) {
|
|
250
263
|
service.setArchived(m.id, true);
|
|
251
264
|
archived.push(m.id);
|