内容
# 🧠 memory-eternal — 给 AI 装「第二大脑」
[](https://hol.org/registry/plugins/eternalnight996%2Fmemory-eternal)
<!-- 语言 / Language:本文件是**默认**(简体中文)README;英文镜像见 README.en.md。
两份是**独立文件**、标题层级一一对应;改一处请同步另一处(新增一节要两边都加)。
npm / DSH 插件市场默认展示本文件(中文)。 -->
<p align="center"><b>简体中文(默认)</b> · <a href="README.en.md">English</a></p>
<p align="center">
<img src="https://img.shields.io/badge/DeepSeek%20Harness-plugin-3B82F6" alt="DSH plugin" />
<img src="https://img.shields.io/npm/v/memory-eternal" alt="npm version" />
<img src="https://img.shields.io/github/stars/EternalNight996/memory-eternal?style=flat" alt="GitHub stars" />
<img src="https://img.shields.io/github/license/EternalNight996/memory-eternal" alt="license" />
<a href="https://dsh.market/"><img src="https://raw.githubusercontent.com/2BingLing/dsh-market/master/assets/readme/badge-listed-zh.svg" alt="DSH Market 收录" /></a>
</p>
<p align="center">
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/cards.png" width="900" alt="知识卡列表:对话结束自动沉淀成卡,可检索 / 可审核 / 可追溯来源" />
</p>
> **对话结束自动沉淀成知识卡,跨会话不失忆;召回只取相关小块,省 token 少噪音。**
> 全自研、零第三方记忆框架、不改 DSH 源码;一个记忆库所有 Agent 共享,SQLite 持久存储,零外部依赖。
<p align="center"><strong>⭐ 如果它帮你少解释了一遍上下文,点个 Star 就是最大的支持</strong><br/><sub>DSH 一条命令:<code>dsh plugin --profile web add memory-eternal</code></sub></p>
<p align="center">
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/graph.png" width="32%" alt="知识图谱:897 节点 / 4257 连线,按类型着色、支持跨库聚合与导出" />
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/health.png" width="32%" alt="健康概览:知识库 / 备份 / 自动沉淀 / 版本,一眼看清有没有静默坏掉的东西" />
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit.png" width="32%" alt="审核中心:AI 写的卡先审后入,可批量批准 / 驳回 / 回收" />
</p>
<p align="center"><sub>▲ 知识卡列表 · 知识图谱 · 健康概览 · 审核中心 —— 全部是真实运行截图(1280×860)</sub></p>
---
## ✨ 它到底解决什么问题
| 你遇到的 | 以前的做法 | 用了之后 |
| --- | --- | --- |
| 聊了 3 天的项目,新会话全忘 | 手动翻历史 / 重新解释一遍 | 每轮结束自动沉淀成卡,开工前 `memory_recall` 一句就回来 |
| 记忆塞进 prompt,token 爆掉 | 全量粘贴、越聊越贵 | 只召回相关的一小块(可配上限),省 token 也少噪音 |
| AI 写的东西不敢直接信 | 无法追溯、无法回滚 | 卡片带来源 / 时间 / 状态,先审后入,删除进回收站 |
- **看得见**:知识卡 / 图谱 / 健康概览 / 沉淀日志,四层可视化;
- **控得住**:审核模式(全部人工 / 仅高风险)、去重与矛盾扫描、批量合并、每日备份与一键恢复;
- **带得走**:知识库是本地 Markdown + SQLite,可 git 管理、可导出,不锁在插件里。
---
## 🚀 五分钟上手
### 🟦 DeepSeek Harness(DSH)—— 重点
**装**(官方桌面版走插件市场,`dsh web` 用一条 CLI):
```bash
# ① 官方 DeepSeek Harness 桌面版(推荐):设置 → 插件 → 搜 memory-eternal 安装
# 等价 CLI(该壳的 profile 名就是 desktop):
dsh plugin --profile desktop add memory-eternal
# ② dsh web(浏览器端):
dsh plugin --profile web add memory-eternal
# 或在 profile(pnpm workspace)里直接更新。注意用 pnpm,npm install 会报 EUNSUPPORTEDPROTOCOL
cd ~/.dsh/profiles/web && pnpm add memory-eternal@latest
# 刚发新版时 @latest 可能被 pnpm 的最小发布年龄门槛/元数据缓存挡住(静默停在旧版):
# 直接指定版本号最快,pnpm 会自动把它加进 workspace 的 minimumReleaseAgeExclude
cd ~/.dsh/profiles/web && pnpm add memory-eternal@0.9.6
# 验版本(应等于上面装的号),然后重启对应宿主生效
node -e "console.log(require('memory-eternal/package.json').version)"
```
#### 🖥️ 桌面版支持策略(先看这条)
- **优先、且只保证支持官方 DeepSeek Harness 桌面版**(官方 Electron 发行版,profile 名 `desktop`)。v0.9.4 → v0.9.6 三轮修复全部围绕它:0.1.7 设置服务换血、`schemastery 3.18.4` 的 volatile 活引用、以及窗口标题栏避让(`data-windows-titlebar`,避免全屏浮层压住最小化/最大化/关闭)。
- **作者自研的 `dsh-desktop`(`dsh-desktop-shell` / `dsh-ui-*` / `dsh-plugin-marketplace` 那一套)已停止维护**,不再作为支持目标 —— 官方桌面版已覆盖同样能力,继续维护两条壳只会分裂兼容性。注意它的 profile 依赖里还钉着已从 npm 撤下的 `dsh-desktop-shell@0.3.0`,会让 `pnpm install` 直接失败;**建议迁到官方桌面版,并从 profile 里删掉这套依赖**。
- 兼容矩阵(均已实测):官方桌面版 `desktop` profile(schemastery **3.18.4**)+ `dsh web` 的 `web` profile(schemastery **3.18.1**),两条路径共用同一份代码。
**重启宿主**后,三样东西立即生效:
| 效果 | 在哪看 |
|---|---|
| 自动沉淀知识卡 | 每轮对话结束自动发生,无需操作 |
| `memory_recall` 工具 | Agent 需要历史时自动调用 |
| 图形化界面 | 侧边栏底部「记忆」按钮 / 设置 → 记忆 |
**入口**:侧边栏底部「记忆」按钮 → 记忆库(左栏含 知识卡 / 知识图谱 / 用量 / **审核中心** / 回收中心 / **记忆配置**);「DSH 设置 → 记忆」= 纯配置页。
**界面语言**:全套 UI 中英双语,跟随 **DSH 设置 → 语言** 实时切换(含知识卡 / 知识图谱 / 审核中心 / 新建卡模板);浏览器直接访问 web 时跟随浏览器语言。
**改配置**:记忆库左栏「记忆配置」(或 DSH 设置 → 记忆)→ DSH 记忆配置 / 成本控制 / 自动审核配置 / 服务自管理,点「保存配置」即写入。`autoWebMode`/`watchdogAutoSpawn` 的改动需重启 DSH 生效;注意**已常驻的 watchdog 不会被配置改动自动停掉**,要停止或换新版请执行 `dsh-memory stop` / `dsh-memory restart`(见「服务自管理」)。
### 🟨 Claude Code
```bash
npm i -g memory-eternal # 装 CLI + MCP(写 ~/.claude.json mcpServers.memory)
dsh-memory connect claude # 写 ~/.claude/settings.json 的 SessionEnd hook → 会话结束自动沉淀
```
装完即用:会话里说「recall 一下数据库选型」→ 自动检索记忆;会话结束 → 自动沉淀进统一 `~/.dsh/memory-vault`(新卡 `pending` 待审核)。
### 🟧 Codex CLI / Cursor
```bash
npm i -g memory-eternal # 装完自动写 Codex config.toml / Cursor mcp.json 的 mcpServers.memory
dsh-memory connect codex # 写用户级 ~/.codex/hooks.json 的 Stop hook → 会话结束自动沉淀(含 Codex Desktop)
dsh-memory connect cursor # 写 ~/.cursor/hooks.json 的 stop/sessionEnd hook → 自动沉淀
```
重启工具 → MCP 已在列表,会话里直接:`用 memory_recall 查一下项目历史决策`;会话结束自动沉淀进统一 `~/.dsh/memory-vault`(新卡 `pending` 待审核)。
> **三种 agent 同一套库**:全部写入 `~/.dsh/memory-vault`,每卡 `submittedBy` 区分作者(DeepSeek Harness / claude-code / codex / cursor),主库只显已审核、审核中心管新卡。
> **原生插件(可选,平台 Marketplace)**:仓库含 `.claude-plugin` / `.codex-plugin` / `.cursor-plugin` 清单,可 `claude /plugin marketplace add EternalNight996/memory-eternal` + `/plugin install`、`codex plugin marketplace add EternalNight996/memory-eternal` + `codex plugin add`、Cursor Settings→Plugins。`connect` 走用户级 hooks.json(更稳,不依赖 Marketplace 审核;Codex Desktop 也走这条)。
> **只保留本插件(卸载其它记忆插件,如 agentmemory)**:`dsh-memory` 只写自己的键(`mcpServers.memory` / 含 `capture.mjs` 的 hooks),不依赖、也不冲突任何其它记忆插件。卸载其它插件需在其自身配置层删除对应条目(如 Codex 的 `[marketplaces.*]` 与 `hooks.state.*`、其 `hooks.json` 事件、`~/.agentmemory` 数据目录)。
### 🟩 浏览器(不依赖任何 Agent)
```bash
dsh-memory open # 起 web + 开浏览器(默认 http://127.0.0.1:7999)
```
统计 / 搜索 / 知识卡(增删改合并导入导出)/ 知识图谱,全在此。与 DSH 内嵌页同一份 UI,数据同步。
> **zcode(智谱)**:暂无原生 MCP,经社区 [zcode-open-bridge](https://github.com/tizerluo/zcode-open-bridge) 转 MCP 或用 CLI。
---
## 📖 命令速查
```bash
dsh-memory recall "数据库选型" # 检索
dsh-memory capture "重要结论..." # 手动沉淀(- 读 stdin)
dsh-memory sweep ~/.claude/projects # 挖掘已有会话记录
dsh-memory setup [--dry-run] # 重跑/预览自动挂载(幂等)
dsh-memory connect <claude|codex|cursor> # 写会话结束自动沉淀 hook(用户级 hooks.json,含 Codex Desktop)
dsh-memory mcp # MCP stdio(挂任意 MCP 客户端)
dsh-memory serve [--port 7999] # 前台跑 web
dsh-memory open # ensure web 存活 + 开浏览器
dsh-memory watchdog [--port 7799] # 看门狗保活 web(独立进程)
dsh-memory status [--json] # 看常驻 watchdog:pid / 端口 / 版本 / 是否存活
# 以及**端口上真正在服务的版本**(锁里的版本号只是 watchdog 自述)
dsh-memory stop [--port 7799] # 停止常驻 watchdog(配置改动不会自动停;连它拉起的 web 一起停)
# 停完会再问一次端口占用者,仍被占着就明确告警
dsh-memory restart [--port 7799] # 停止并重起 watchdog(升级后换新版)
# 按端口占用者兜底收掉锁里没登记的旧 web,起完自检端口上服务的版本
dsh-memory audit list [--status pending|rejected|approved|deleted|all] [--limit N] [--json]
# 列出卡片;总数不受 --limit 影响,--limit 只限制显示条数(默认全部)
# all = 审核队列(pending + rejected),不含 approved / deleted
dsh-memory audit approve <卡片路径...> # 人工批量批准
dsh-memory audit reject <卡片路径...> --reason "..." # 人工批量驳回
```
单独装(不发 DSH)时 `dsh-memory` 命令来自 `npm i -g`。
---
## ⚙️ 服务自管理(白话)
**三个概念**,别搞混:
- **web server 怎么保活**(`autoWebMode`)→ `init`=DSH 启动时拉一次(默认);`interval`=DSH 进程内定时探活自动拉起(0 额外内存);`manual`=全手动只从 `dsh-memory open` 起。
- **看门狗进程**(`watchdogAutoSpawn`,默认开)→ 一个**独立** node 进程,DSH 退出了它也能拉起 web(约 +47 MB 内存)。只在要 7×24 保活时开。
- **版本漂移自愈**(`autoRestartOnDrift`,默认开)→ 常驻 web **比 DSH 活得久**:升级只换磁盘文件,端口上那个进程仍跑着启动时加载的旧代码(「启动 dsh 后运行中还是旧版本」就是这么来的)。开着它,DSH 激活时会**问端口上真正服务的版本**,与磁盘不一致就用新代码重启常驻实例;关掉则只告警,需要时在「插件信息」点**重启常驻实例**。
- **自动挂载 MCP**(`autoMcpSetup`,默认关)→ 是否自动把 MCP 写进 Claude Code/Codex/Cursor 配置。关 = 不碰你本机配置文件,需要时手动 `dsh-memory setup`。
**改这些**:记忆库左栏「记忆配置」(或 DSH 设置 → 记忆)→ 表格里改,点「保存配置」;`autoWebMode`/`watchdogAutoSpawn` 需重启 DSH 生效。
> ⚠️ **同端口不会因配置改动而自动替换(#19)**:看门狗是**独立进程且故意不杀**(多会话共用一个),所以
> 关闭 `watchdogAutoSpawn` **不会**停掉已经在跑的那个,改 `webCheckIntervalMs`/`webMaxRestart` 也不会生效。
> 要停止 / 换新版请显式执行 `dsh-memory stop [--port N]` 与 `dsh-memory restart [--port N]`(`stop` 会**连它拉起的 web 一起停**,不留占端口的孤儿);
> `dsh-memory status` 会显示常驻实例的 pid、端口、启动时间与**版本是否落后于本机安装的包**,并单独报出**端口上真正服务的版本**
> (锁文件里的版本号只是 watchdog 自述,端口上跑旧代码时它不会变)。
>
> ⚠️ **升级后请以「端口实际服务版本」为准(#23)**:`restart` 现在不只信锁里登记的 web 子进程,
> 还会按**端口占用者**兜底清理(锁里没登记的旧 web 也会被识别并收掉,非本插件的进程只告警不动手),
> 并在起完之后自检「端口上服务的版本 = 本机版本」——对不上就以非 0 退出码报失败,不再打印「已启动」就完事。
>
> ✅ **「更新了却还是旧版本」现在有两条出路(#19/#23)**:① 默认开的 `autoRestartOnDrift` 在 DSH 启动时
> **自动**替换旧常驻实例;② 面板「插件信息」里发现版本漂移时给一个**🔄 重启常驻实例**按钮(服务端即
> `dsh-memory restart --port N` 的同一实现:收旧实例 → 抢锁 → 拉起新 web → 自检端口版本)。
> 注意「运行中」那一格指的是**服务本页的进程**:独立 Web 页里就是常驻 web,DSH 设置页里是宿主 ——
> 面板会同时列出**常驻实例**的版本,两者不同时一眼能看出该重启谁。
**MCP 是协议不是常驻服务**:agent 开会话才 spawn,用完即退,没有「开机自启」一说。
### 三种部署强度
| 场景 | 配置 | 内存 |
|---|---|---|
| 个人开发(推荐) | `autoWebMode=init` + **手动**把 `watchdogAutoSpawn` 关掉(默认是**开**,见下方配置表) | web 47 MB |
| 常驻 7×24 | `watchdogAutoSpawn=on` | web + watchdog 47+47 MB |
| 真正开机自启(无 DSH) | Windows 计划任务跑 `dsh-memory watchdog --port 7799 --interval 5000 --max-restart 10` | 同上 |
---
## ⚙️ 记忆配置(大白话)
> 所有配置都在 **记忆库左栏「记忆配置」**(或 DSH 设置 → 记忆)里改,点「保存配置」生效。下图就是配置页全貌(含插件信息 / Agent MCP 挂载状态 / 自动审核配置):
<p align="center">
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/config.png" width="880" alt="记忆配置页:蒸馏 provider / model 下拉、跟随会话、仅试首选、超时与输出上限" />
</p>
### 一、最常用
| 配置项 | 默认 | 大白话说明 |
|---|---|---|
| 自动沉淀 | 开 | 每轮聊完自动把有用的内容存成知识卡 |
| 自动召回 | 开 | AI 需要历史时自动帮你查记忆 |
| 记忆库目录 | `~/.dsh/memory-vault` | 记忆存哪,纯 Markdown 可 git 管理 |
#### 多库(按项目隔离记忆)
配置里还有两个字段(写在 `memory-eternal-config.json`,或设置面板同步过来的共享配置):
```json
{
"vaultProfiles": [{ "name": "work", "path": "D:/vaults/work" }],
"activeVault": "work"
}
```
解析顺序:`MEMORY_VAULT_DIR` 环境变量 → `activeVault` 命中 `vaultProfiles[].name` 的 `path` → `vaultDir` → 默认 `~/.dsh/memory-vault`。
**v0.9.8 起,CLI / MCP / hooks / 独立 web / sweep 与 DSH 宿主共用同一套顺序**:以前只有宿主认 `activeVault`,切库后终端与 hooks 仍往默认库写,记忆会被劈成两份。
> 说明:配置页目前还没有 `vaultProfiles` / `activeVault` 的可视化表单(要手工写配置文件),按 workspace 自动选库也还没做。
### 二、省钱包(重要)
| 配置项 | 默认 | 大白话说明 |
|---|---|---|
| **蒸馏知识卡** | 开 | 把对话**压缩**成精炼知识卡(要调 AI,花钱)。**关掉 = 存原文**,一分钱不花 |
| **语义去重喂 AI** | 开 | 判断新内容是不是重复(要调 AI)。**关掉 = 用简单去重**,省一次 AI 调用 |
| 蒸馏输出上限 | 900 | 压缩一次最多写多少字,越大越准越费钱 |
| 召回相关性阈值 | 2 | 检索要「多像」才返回,越大越准但漏得越多(越省) |
| 捕获最小长度 | 200 | 对话太短不存,避免闲聊浪费 |
| 日配额 | 60 | 一天最多存几张,防 AI 烧钱 |
### 三、服务怎么跑
| 配置项 | 默认 | 大白话说明 |
|---|---|---|
| 保活模式 `autoWebMode` | init | `init`=DSH 启动时开一次 web;`interval`=定时检查挂了自动重启;`manual`=全靠手动 |
| 看门狗 `watchdogAutoSpawn` | 开 | 后台一个**独立进程**保证 web 不死(+47 MB 内存)。个人用可关——但关闭后**既有实例不会自动停**,需 `dsh-memory stop`(#19) |
| 漂移自愈 `autoRestartOnDrift` | 开 | DSH 激活时发现**端口上服务的版本 ≠ 磁盘版本**(升级后常驻 web 还是旧代码)就用新代码重启常驻实例。关掉只告警,仍可在「插件信息」点**🔄 重启常驻实例**(#19/#23) |
| 自动挂载 MCP `autoMcpSetup` | 关 | **让 Claude Code / Codex / Cursor 也能用你的记忆库**。开=自动配好它们;关=不碰你电脑配置,手动跑 `dsh-memory setup` |
| 凭证提示 `secretHint` | 空 | 填一句自己的约定(例如「需要 API key/token 时先查记忆里的密钥目录」),会**注入每个会话的 systemPrompt**。记忆库里只存**目录**(名字/位置/取用方式),密钥的**值**不入库 —— 见下方「密钥怎么放」 |
> 🔑 **密钥怎么放(`secretHint` 的用法)**:把**值**留在本机凭据里(环境变量 / 密钥文件,权限收紧),
> 记忆库里只留一张**目录卡**(有哪些键、放在哪、干什么用、轮换状态),再用 `secretHint` 让 agent
> 每次会话都想到去查这张卡。为什么值不能入库:卡片 `summary` 就是正文开头,**召回默认就带 130 字**
> (`recallSummaryLen`),值一入库就随每次相关召回进入模型上下文;`/export`、备份、多机同步也会带上。
> `dsh-memory setup` 会在 Claude Code 里注册一个 `PreToolUse` 守卫:直接读
> `.credentials.yaml` / `.env` / `*-token` 会被拒绝并指路到目录卡。
> 💰 **想省钱**:把「蒸馏知识卡」关掉、调低「蒸馏输出上限」、调高「召回相关性阈值」。
### 🎯 一键推荐配置(按场景点一下)
配置页顶部有 **🟢 A 轻量省心 / 💰 B 极致省钱 / ⭐ C 高质量** 三个按钮,点一下自动填好对应值,再点保存即可:
| 方案 | 场景 | 保活 | 看门狗 | 蒸馏 | 蒸馏上限 | 召回阈值 | 内存 | LLM 成本 |
|---|---|---|---|---|---|---|---|---|
| 🟢 **A 轻量省心** | 个人开发(推荐)| init | 关 | 开 | 2000 | 2 | ~47 MB | 正常 |
| 💰 **B 极致省钱** | 预算敏感/多 Agent | init | 关 | **关** | 500 | 3 | ~47 MB | **近 0** |
| ⭐ **C 高质量** | 长项目/团队 | interval | **开** | 开 | 1200 | 1 | ~94 MB | 高 |
---
## 🔔 自动沉淀日志 & 异常提示
自动沉淀是后台静默管线:一旦坏了,以前只能表现为「卡不再增加」,看不出原因。v0.9.0 起它会自己说话。
- **自动沉淀日志**:设置 → 记忆 → 用量/今日,最近 100 条运行轨迹:`🚀 启动` / `👂 会话接入(用的事件接口 + 事件数)` / `✅ 新建卡` / `➕ 追加更新` / `⏭ 跳过(原因)` / `❌ 失败(原因)`。
- **异常主动提示**:出现失败即亮红——记忆库页面顶部红条,同时把「自动沉淀异常 + 原因」注入 Agent 上下文,Agent 会在回复第一句提醒你,不需要你自己去翻日志。
- **停滞兜底**:**本次运行至今一次收尾事件都没收到**、且最近 20 分钟内持续有轮次开始(例如 DSH 改了事件名/作用域),5 分钟巡检记一条 `warn` —— 不再冒充红色「自动沉淀异常」,正在进行的**长回合也不会被误判**(一轮对话中途 `inbox/claimed` 会多次触发,收尾要等回合真的结束)。
- **接口自适应**:自动识别 DSH 会话事件接口(`ownEvents()` → `snapshotEvents()` → `events`),DSH 升级换 API 不会再静默断掉。
---
## 🛡️ 审核中心 & 回收中心
新卡默认进**审核中心**(`pending`),由你确认后才入主库;驳回的进「已驳回」,可恢复或删除进回收站。命中免审条件(审核模式=全部免审 / 免审智能体 / 免审类型)的新卡直接入库;回收站软删卡 30 天内可恢复,超期自动永久删除。
> **v0.3.0 起:审核守卫下沉到数据库层。** 所有写入路径(自动沉淀 / MCP / UI 新建 / 导入)强制经过 `enforceAudit()`,调用方无法绕过。`setCardStatus()` 是唯一审核操作入口,每次变更写入 `audit_log` 审计日志(who/what/when/why),不可篡改。
> **v0.10.0 起:升级为「两套存储」——未审核内容物理隔离。**
> `cards`(**主库 / 正常区**)物理上**只存 approved**;`quarantine`(**隔离区 / 异常区**)存 `pending` / `rejected` / `deleted`。
> 意义:召回、检索、知识图谱、去重池、导出这些读路径**查主库即安全**——不再依赖「每处 SQL 都记得写 `WHERE status='approved'`」。
> (v0.9 审计实测漏过三处:去重池会把新知识追加进待审卡、`readCard` 可按 path 直读未审核正文、`/card` 接口无状态校验。)
> 审核流转 = **跨表搬家**:批准 → 搬进主库,驳回/软删 → 搬进隔离区;搬家在一个事务里完成,更新记录与审计日志跟着走。
> 升级说明:首次启动会自动把存量非 approved 卡搬进隔离区(**不删除任何内容**,都在同一个 `.db` 文件里)。
> 若回滚到旧版本,审核队列会显示为空(旧代码看不到隔离区表),升级回来即恢复。
<p align="center">
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/screen/audit-center.png" width="880" alt="审核中心" />
</p>
- **待审核 / 已驳回** 双页签,按类型 / 日期 / 智能体筛选,全选后一键「批准 / 驳回 / 删除进回收站」。
- 审核规则在「记忆配置 → 自动审核配置」:`审核模式`(全部要审 / 全部免审)+ `免审智能体` + `免审类型` + `回收保留天数`。
- **Agent 不能代你审批(v0.9.1 起)**:只要审核生效,插件就往 Agent 的上下文里注入红线——新卡只能停在「待审核」,并明确禁止调用 `approve`/`reject` 接口或任何等价方式绕过;判断权始终在你手里。
### 🥇 为什么审核系统更靠谱(对比其它记忆产品)
多数记忆产品(mem0 / Zep / agentmemory…)对话一结束就**自动全量入库**,好坏不论——噪音、错误事实、敏感内容一起进库,之后又被召回,**污染上下文、放大幻觉**。
memory-eternal 走**人机协同审核**,只让可信内容进主库:
| 维度 | 其它记忆产品 | memory-eternal 审核系统 |
|---|---|---|
| 入库 | 自动全量,无把关 | 新卡先进**审核中心(pending)**,人工批准才入主库 |
| 质量 | 未过滤噪音/错误 | 只保留你确认过的卡 → 召回更准、噪声更低 |
| 可信 | 无来源/审核追溯 | 每卡带 `submittedBy` 作者 + `pending/approved/rejected` 审核状态 |
| 免打扰 | — | 免审智能体 / 免审类型命中 → 可信卡直接入库,零等待 |
| 容错 | 删了就没 | 回收站软删,30 天内可恢复 |
---
## 🧬 为什么全自研
市面上记忆方案多,但大多依赖第三方框架 / 动不动起 MCP 服务 / 记忆锁私有库。本插件把骨架自己搭,**零第三方运行时依赖**,逻辑逐行可读:
| 模块 | 自研实现 | 替代什么 |
|---|---|---|
| 去重 | 词法 Jaccard bigram(0.62)+ 语义去重 | 防重复卡 |
| 检索 | CJK 感知:中文整词 + 字符 bigram | 无需全文搜索引擎 |
| 图谱 | 力导向 + `[[wikilink]]`/共享标签连边 | 知识关联一眼看清 |
| 存储 | **SQLite**(`node:sqlite` 内置,零依赖)| 不锁库、分表存储、SQL 查询 |
| 审核 | **数据库层审核守卫** + 审计日志 | 杜绝越权,所有写入强制走审核 |
> 与热门项目同向(总结→存储→按需召回),但定位不同:**本地、自研、零依赖、可读可控**。如果你已在用 mem0/Zep 等,也能把它当「本地持久记忆底座」叠加使用。
---
## 🛠 开发 / 测试
```bash
npm i
npm test # 单元测试:vault 去重/检索/图谱 + capture 管线 + API 形状
npm run build # 构建 lib/client.js(DSH 内嵌)+ web/app.js(独立 web bundle)
```
> 📄 **README 是中英两份独立文件**:`README.md`(简体中文,**默认**,npm / 插件市场展示这一份)
> 与 `README.en.md`(English)。两份标题层级一一对应,**改一处要同步另一处**(新增一节两边都加)。
---
## 🐞 反馈问题
插件左栏有 **🐞 反馈异常** 入口:填完问题描述后可以
- **在 GitHub 提交(已预填)** —— 标题 / 正文 / 诊断信息 / `bug` 标签都填好,再点一次 **Submit** 即提交;
- **复制给 AI 的提示词** —— 粘进 AI 对话框,让它用 `gh` 查重后建 issue 并回报链接。
> **为什么不能「点一下自动提交」**:GitHub 不允许匿名创建 issue;插件里内置 token 会被反编译提取;OAuth / 自建中转需要服务器。所以实现为「预填 + 一次点击」。
> 诊断信息会**自动脱敏**(home 目录 → `~`,`sk-` / `ghp_` / `github_pat_` / `api_key:` 等 → `***`),但公开提交前仍建议自己扫一眼。
如果你拿不到插件界面,也可以直接把下面这段贴给 AI(把「我的问题描述」换成你的问题即可):
```text
我在使用 memory-eternal(DeepSeek Harness 的记忆插件)时遇到了问题,请你帮我把这个问题提交到 GitHub。
仓库:https://github.com/EternalNight996/memory-eternal
请按这个流程做:
1. 先跑 `gh issue list --repo EternalNight996/memory-eternal --state open --limit 50`,看是否已有相同问题:
- 已有 → 用 `gh issue comment <编号> --body-file <文件>` 补充现象,不要重复开新 issue;
- 没有 → 继续第 2 步。
2. 把正文写成文件,然后 `gh issue create --repo EternalNight996/memory-eternal --title "[Bug] <一句话概括>" --body-file <文件>`;正文用这个结构:
现象 / 复现步骤 / 期望行为 / 实际行为 / 环境(插件版本 / DSH 版本 / 操作系统 / Node)/ 诊断信息。
3. 如果 `gh` 没登录或不可用,不要尝试提交,改为输出一条「标题与正文都已预填」的 GitHub 新建 issue 链接给我,我自己点提交。
4. 提交成功后,把 issue 链接发给我。
注意:我的问题描述请原样照录,不要替我美化或省略细节;诊断信息原样放进「诊断信息」一节。
我的问题描述:
<在这里写你的问题>
诊断信息:
<把「反馈异常」弹窗里复制的诊断信息粘到这里>
```
### 环境自检(贴问题时附上更快定位)
```bash
dsh plugin list # 插件版本
node -v # Node 版本
curl http://127.0.0.1:7999/memory-eternal/api/web-info # 独立 web 是否活着
```
---
## 📋 更新日志
> **发布规则(2026-09-30 起)**:正式版一律**三端同步发布** —— `npm publish` + **GitHub Release** + **Gitee Release**,并保证 `vX.Y.Z` tag 在三端一致。桌面版 profile 依赖 `github:EternalNight996/memory-eternal`、`dsh web` 走 npm 版本号,少任何一端都会出现「npm 上是新版、桌面版还是旧的」。统一入口:`npm run release:dry`(预演)→ `npm run release`(正式)。详见 [PUBLISH.md](./PUBLISH.md)。
>
> **版本撤回说明**:**v0.9.16 – v0.9.22 已全部撤回**(问题期间版本:git tag 已删除、npm 已标记 deprecated,请使用 **v0.9.23**)。**v0.9.14 已 deprecated**(客户端渲染改动降低手感,已回退;其服务器侧算法优化并入 **v0.9.15**,渲染与 v0.9.13 完全一致)。**v0.9.0 – v0.9.5 均已在 npm 标记 deprecated;v0.9.1 / v0.9.2 / v0.9.3 / v0.9.4 的 release tag 已从 git 移除**(v0.9.5 只是被取代、tag 保留)—— v0.9.1/v0.9.2 带记忆页白屏缺陷,v0.9.0 沉淀告警误报刷屏,v0.9.3 不支持 DSH v0.1.7-rc.2(升级后插件整体挂不上:设置服务换血 + 客户端 `settingsScope` 消失),v0.9.4 在官方桌面版(schemastery 3.18.4 的 volatile 活引用)下抛 `cfg.vaultDir.trim is not a function`、插件整体挂不上,**v0.9.5 的全屏浮层会盖住桌面版的窗口控制面板(右上角「×」压在窗口「关闭」上,点一下会退出整个桌面壳)**。请一律使用 **v0.9.6+**(`npm i memory-eternal@latest`)。
> **DSH 兼容性**:`>=0.1.5-alpha.2 <0.3.0-0`(**v0.10.1 起放宽**:旧的上界 `<0.2.0` 在 semver 上会把 DSH `0.2.0-rc.x` 排除在外 —— DSH 的插件守卫用 `includePrerelease:true` 判定,实际仍能装上,但声明与事实不一致;现在明确覆盖整个 0.1/0.2 线,且 `<0.3.0-0` 仍然挡住 0.3.0 及其预发布。**已在官方桌面版 DSH 0.2.0-rc.2 上实测运行**:插件挂载、记忆页、自动沉淀、审核中心、导入导出均正常)。**v0.9.4 起适配 DSH v0.1.7-rc.2**(该版本把设置服务换成纯表单 API,并移除了 `@deepseek-ai/dsh-client-runtime` 与 `settingsScope`);**v0.9.5 起兼容 schemastery ≥3.18.4 的 volatile 活引用**(官方桌面版 profile 即此形态,web profile 仍是 3.18.1,两者都支持);**v0.9.6 起全屏浮层自动让开桌面壳的窗口标题栏**(不再压住最小化/最大化/关闭)。0.1.5 系列仍走旧的 `settings.register` 路径,两条路径都保留。
| 版本 | 日期 | 关键改动 |
|---|---|---|
| **v0.10.54** | 2026-10-20 | **本地大模型蒸馏更稳(#48)+ README 焕新与最新截图**。① **#48**:本地 30B 模型冷启动要 40–60 秒,而蒸馏超时**硬编码 45 秒** → 每次冷启动必报 ABORTED;现在超时可配 **captureTimeoutMs**(默认仍 45s,本地推理建议 120000+),且**超时后自动用更长超时重试同一候选一次**(不直接换模型)。② **#48 的第二种形态**:模型不给 max-tokens finish 时,截断只表现为「JSON 未闭合」→ 新增独立错误码 **TRUNCATED_OUTPUT**,与 MAX_TOKENS 一起走「加倍输出预算重试同一候选」的阶梯。③ 配置页新增「蒸馏超时(毫秒)」输入项。④ **README 焕新**:全部换成真实运行截图(知识卡 / 知识图谱 897 节点 4257 连线 / 健康概览 / 审核中心 / 配置页)+ 新增「它到底解决什么问题」对照表 + 更清晰的卖点与 Star 引导(中英同步)。npm test 共 **440 项** |
| **v0.10.53** | 2026-10-20 | **健康面板(可靠性线):把看不见的状态集中一处**。在「用量/今日」顶部新增「🩺 健康概览」:① **知识库**(journal 模式 / 可写 / 最后一次体检时间,异常时给 doctor 指引);② **备份**(数量 + 最近一次多久前,>7 天标黄、0 备份标红);③ **自动沉淀**(最近一次成功/失败与原因);④ **版本**(磁盘 / 常驻 / 宿主;未检查时给「开始检查」按钮,因为 align 要联网比 npm 不能自动跑)。数据来自既有接口:/overview 新增 **backups 摘要**(count/newestAt/最近 5 个文件名),加上 /capture-log 与 /align。**顺带修一个白屏 bug**:健康卡最初误用了 ConfigPanel 作用域的 tf() → 点开「用量/今日」整页白屏;同时给 panel-check 补了两道闸 —— **页面存活(textLen<20 判空白页)** 与 **Runtime.exceptionThrown 捕获**,因为「白屏没有横向溢出」原本会让检查通过。新增 tests/health-panel.test.mjs(4 项)。npm test 共 **437 项** |
<details>
<summary>更早的更新日志(v0.10.52 及以前 · 共 79 版,点开查看)</summary>
| 版本 | 日期 | 关键改动 |
|---|---|---|
| **v0.10.52** | 2026-10-19 | **诊断包脱敏补漏(隐私修复,建议尽快升级)**。0.10.51 的「一键诊断包」有个漏点:doctor/align --json 与 meta.json 里的家目录是 **JSON 转义形态**(C:\\Users\\x),而我只替换了原样/正斜杠两种写法 → 包里仍能 grep 到家目录明文。现在三种写法(原样、正斜杠、JSON 转义)都替换成 <home>,并在测试里加了一条「JSON 转义形态不能漏」的断言;本机重新生成诊断包实测:**无家目录明文、无 sk- 密钥**。**如果你已经用 0.10.51 生成过诊断包并分享出去,建议重新生成一次再分享。** npm test 共 **433 项** |
| **v0.10.51** | 2026-10-19 | **一键诊断包 dsh-memory support**(可靠性线;#47 里用户贴的现场被 GitHub 截断,我拿不到完整信息,这功能就是为这类沟通做的)。一条命令生成 **zip**:meta(版本矩阵/平台/node/vault)/ doctor / align / status / 看门狗锁 / 宿主心跳 / 共享配置 / 沉淀日志尾巴(默认 200 行)+ README;写入前**统一脱敏**(家目录→<home>,sk-/ghp_/github_pat/Bearer/配置里 key 类字段→***)。实现为**零依赖**:自写 store/deflate ZIP 写入器(lib/zip.js,Windows 资源管理器可直接打开,实测 Expand-Archive 解压正常)。受限环境可加 --no-spawn 跳过子进程;--out 指定输出、--tail N 控制日志条数。新增 tests/support-bundle.test.mjs(4 项:脱敏、zip 结构、包内不含明文密钥、CLI 接线)。npm test 共 **433 项** |
| **v0.10.50** | 2026-10-19 | **「模型不守 JSON」不再每轮白烧调用(#47 衍生改进)**。① 把「模型回了散文、输出里根本没有 JSON 对象」从笼统的 UNPARSEABLE_OUTPUT 里拆出来,给独立错误码 **MODEL_NOT_JSON**,文案直接说清「该模型可能不遵守结构化输出」。② 新增 `lib/model-strikes.js`:记下「哪个 provider/model 最近不守格式」(24h 窗口、最多 50 条),宿主选路时把它们**排到候选末尾**(不删除;全部命中过则按原顺序兜底,绝不没候选),并留一条「蒸馏候选已调整」日志。③ 效果:像 stepfun/step-api 这种会把蒸馏当聊天回的模型,第二轮起就不再优先尝试,省掉每次的无效调用。新增 `tests/model-strikes.test.mjs`(4 项:散文识别 / 排序稳定性 / 记录窗口与裁剪 / 接线守卫)。npm test 共 **429 项** |
| **v0.10.49** | 2026-10-19 | **桌面版里「无法选择蒸馏模型」修复(#47):独立页把 /llm-routes 与 /llm-test 代理给宿主 + 文案补一个状态**。根因:桌面版的「记忆」弹窗是 iframe 里的**独立页**,它没有 DSH 的 llm 服务 → /llm-routes 恒失败 → provider/model 只能手填。现在独立页读宿主心跳(<config>.host.json 里的端口,仅 127.0.0.1、心跳 120s 内有效),把这两个路由**转发给宿主**执行并标记 proxied;宿主不在线时给明确原因(面板直接显示,不再静默回退成手填)。② 版本卡状态矩阵补 restartRepair:**版本已对但没人保活**时提示「重新托管并保活」,不再出现「换成 v0.10.48(当前 v0.10.48)」这种自相矛盾的文案(截图实测)。npm test 共 **425 项** |
| **v0.10.48** | 2026-10-19 | **界面验收手段 + 状态穷举(方案一落地)**。① **新增 `npm run panel-check`**(scripts/panel-check.mjs,零依赖):用本机 Chrome headless + CDP 打开面板,逐屏断言**没有横向溢出**并截图(列表 / 阅读器 / 配置 / 反馈弹窗)。它第一次跑就抓到两个真 bug:卡片标题里的无断点长串(`(begin/chunk/probe/complete/abort)`)溢出 45px;**反馈弹窗 textarea 溢出 22px**(width:100% + padding 在 content-box 下)—— 正是用户报的「排版不对 / 滚动条异常」。② 修复:`.mc-cardrow h4` 断词、`.me-overlay input/textarea/select` 统一 border-box。③ **版本卡判定抽成纯函数** `src/client/version-state.js`:`versionCardState(...)` 返回 `{showButton, emphasis, warn, hint}`,组件只做 key→文案;新增表驱动测试**穷举 324 种组合**,并守住不变式「只要拿到版本信息,按钮永远显示」—— 0.10.45「第三种状态导致按钮消失」从结构上不可能再发生。npm test 共 **420 项** |
| **v0.10.47** | 2026-10-19 | **面板版本探针自动复查 + 「常驻未运行」文案修正(用户反馈:重启后面板一直显示「常驻实例 未运行」)**。① 现场其实**是活的**(status 显示 7999 在听、服务 v0.10.46),但版本探针只在面板挂载时跑一次 —— 恰好撞上重启窗口就会被缓存成「未运行」并一直显示。现在**每 60 秒静默复查**(不点亮「检查更新」按钮)+ **切回页面立刻复查**(visibilitychange),重启后 6 秒本来就会复查一次。② 文案:常驻实例真没跑时,原来那句会拼出「当前 v」(空值);现在改走 vcRestartStart:**「点击会用当前磁盘版本 v{disk} 把常驻实例拉起来(不用重启 DSH)」**。npm test 共 **408 项** |
| **v0.10.46** | 2026-10-19 | **「重启常驻实例」按钮改为**永远显示** + 修正「谁旧」的文案(用户第二次反馈按钮消失)**。上一版把按钮放进「常驻实例需要重启」的分支,而实际状态是**常驻实例已是新版、服务本页的进程(DSH 宿主)还是旧版** → 落进宿主提示分支、按钮又没了;更糟的是警告文案写着「点重启常驻实例即可」,而重启常驻根本不会改变宿主提供给你的本页版本。现在:① 按钮**只依赖 vc 存在**,任何状态都在;② 三种状态各给准确说明 —— 常驻旧(点了就换新版,不用重启 DSH)/ 宿主旧(重启常驻**不影响本页**,请重启 DSH)/ 全部对齐(卡住时可强制重启);③ 警告文案拆出 vcHostProcessStale,明确「服务本页的是宿主进程」。npm test 共 **406 项** |
| **v0.10.45** | 2026-10-19 | **修复「重启常驻实例」按钮消失 + 对话框排版/滚动条异常(用户截图反馈)**。① **按钮常驻**:它原来被包在「有异常才渲染」的警告块里 —— 版本一对齐整块消失,用户再也找不到(#28 也抱怨过)。现在操作行**独立渲染**:异常时是修复入口,一切正常时也保留按钮并说明「通常无需重启,卡住时可强制重启」。② **滚动条异常**:阅读器最多 8 个头部按钮,而 0.10.42 给按钮组设的是 nowrap → 在 max-width:760px 的对话框里**横向溢出**;现在按钮自身仍不缩不换行、但**按钮组整体允许换行**(掉到第二行也不变形)。③ **排版**:.me-dialog / .me-dialog-body 改 border-box 并隐藏正文横向滚动(阅读器的 width:100% + padding 不再撑破容器);诊断/反馈的 <pre> 长路径加 word-break(远程 agent 的路径没有断点,以前必横向滚);大标题 modal 的 h2 加省略号。新增 tests/ui-overflow.test.mjs(4 项守卫)。npm test 共 **406 项** |
| **v0.10.44** | 2026-10-19 | **界面体验(第二批):标签点击即筛选 + 搜索命中片段**。① **标签筛选**:列表/阅读器里的标签芯片现在可点,点了立即按该标签筛(与搜索 / 类型 / 状态**可叠加**);工具栏出现可清除的「🏷 标签 ✕」。服务端 /cards?tag= 走 SQL 精确匹配 —— 按「JSON 数组里带引号的完整标签」用 instr 匹配,**不会把 ab 命中成 a**;countCards 与 sort=title 两条分支同步生效。② **搜索命中片段**:命中正文时,列表多显示一段**关键词附近的片段并高亮**(折叠空白、两端省略),一眼看出「为什么命中」。③ 新增 src/client/snippet.js(纯函数,Node 可单测)+ tests/tag-filter.test.mjs(3 项)。npm test 共 **402 项** |
| **v0.10.43** | 2026-10-19 | **界面体验(第一批 · 读卡)**。① **复制**:阅读器新增「📋 复制正文」「🔗 复制路径」,复用带兜底的剪贴板工具,1.5s 内显示「已复制」(不再点了没反应);② **阅读进度**:头部下沿 2px 细条随正文滚动推进,短卡直接 100%;③ **j/k 翻页**:阅读器内 j 下一张、k 上一张(基于当前排序后的列表),**方向键仍留给正文滚动**,输入框内不触发;④ **阅读宽度**:窄/中/宽三档(720/900/1180px)一键切换并记住偏好;⑤ 延续 0.10.42 的头部不挤压修复。新增 tests/card-reader-ux.test.mjs(4 项守卫)。npm test 共 **399 项** |
| **v0.10.42** | 2026-10-19 | **卡片头部不再被长标题挤压(用户截图反馈)**。旧布局是 space-between,而 h3 没有 min-width:0(长标题压不动),按钮组也没有 flex:0 0 auto / nowrap → 按钮被挤成竖排(「收/起」「关/闭」)。现在:h3 可收缩并**单行省略**(悬停显示完整标题)、按钮组 flex:0 0 auto + nowrap(永不被压扁)、头部允许换行(极窄时整组按钮掉到第二行,仍逐个可点);阅读器与导出图谱两个头部统一走 .me-head-actions。新增 tests/card-head-actions.test.mjs(3 项守卫)。npm test 共 **395 项** |
| **v0.10.41** | 2026-10-19 | **知识图谱初始视图缩小两个度**。图谱是 canvas + sim.zoom,滚轮每格 ×1.1;按「两个度」= ×(1/1.1)² ≈ **0.826** 处理 —— 初始 fit()(首屏、布局稳定后、以及「重置视图」)现在都在「刚好铺满」的基础上再外扩两格,四周留出拖拽/框选余量;滚轮与初始视图**共用同一个 ZOOM_STEP 常量**,以后调步长不会两边漂;原有限幅(0.35–1.8)保留,小图不会被缩到看不见。新增 tests/graph-initial-zoom.test.mjs(3 项源码守卫,含系数数值)。npm test 共 **392 项** |
| **v0.10.40** | 2026-10-19 | **#45 后续(阻塞级):枚举超时不再被当成「端口空闲」+ macOS 红绿灯遮挡(#46)**。① 报告者实测:Get-NetTCPConnection 在该机要 **4.1–61.4 秒**(TCP 表大),而 takeover 只给 2.5s → 超时 → 空输出 → null → 「端口已空出」→ 新看门狗绑不上 7999、漂到 8001,**旧 web 永远活着、0.10.38 的修复永远轮不到执行**。修法:Windows 改**先走 netstat -ano**(实测 1.8s 且直接给 pid,本机复测 **403ms** 拿到 pid+命令行),PowerShell 只作兜底;portFree 升级成**三态**(free / occupied / **unknown**),枚举超时=未知 → **中止接管**并写明确日志(不再漂移);status 把「响应超时」与「没有服务」分开报;探测路径不再漏 socket。② **#46**:macOS 桌面版的窗口按钮是 Electron **原生 traffic light**(titleBarStyle:hiddenInset),任何 z-index/portal 都盖不住 —— 插件改为**跟随宿主安全区** --dsh-frame-overlay-top(macOS 68px,原生全屏宿主降到 20px),浮层顶边恒 68px、遮罩仍铺满全屏,不再被「关闭」按钮压住。npm test 共 **389 项** |
| **v0.10.39** | 2026-10-17 | **接线自愈:codex 的 TOML 按段解析 + 单引号/转义兼容 + 启动期只读体检(#44 第②④条)**。① 修了一个真 bug:setup / align 解析 ~/.codex/config.toml 时**没有限定 [mcp_servers.memory] 段**,会抓到别的 server 的第一个 command =(本机实测读成了 MCP_DOCKER 的 docker.exe);而且只认双引号,而 codex 自己写出来是单引号 → **既读不到也改不掉,却照样打印「已更新」**。现在读写共用纯函数(按段 → 单/双引号 → 反转义),**改完校验真的变了**,幂等(已是目标形态不算改动),改不了就如实报 section unparseable。② 新增启动期**只读**体检 staleWiringCheck():MCP/hooks 里若写着**已不存在**的 node 路径、或条目与当前版本不一致,就在自动沉淀日志里留一条 warn(dsh-memory setup 可修)—— 没人会天天跑 align,静默失效要变成可见。③ 本机验证:align 三端接线全部 ✓(修前 codex 一直报「node 路径与当前不一致」)。npm test 共 **379 项** |
| **v0.10.38** | 2026-10-17 | **接管成功即释放 claim(修 0.10.35 的副作用)+ #45 真机 A/B 验证数据**。0.10.35 的 claim 在「助手变成常驻看门狗」这条路径上永远不会被释放(那个进程不退出),于是接管完成后 60 秒内的下一次 restart 会被自己挡成 in-flight(本机实测踩到)。现在 bootWatchdog() 之后立刻 releaseReplaceClaim() —— 互斥窗口严格等于「接管进行中」。**#45 真机验证(同一台机、同一组 20 次 /version-check)**:0.10.36 → 9 成功 / **11 拒连**,TIME_WAIT 24→**1976**(+1952 ≈ 每请求 100 条连接 = 递归深度),首帧 2389ms;0.10.37 → **20/20 成功**,TIME_WAIT 1969→**1971**(+2),18–34ms。npm test 共 **376 项** |
| **v0.10.37** | 2026-10-17 | **修 #45(我 0.10.36 引入的严重回归:/version-check 自我探测递归被重试放大)+ #44(node 路径硬编码 / 僵尸看门狗误判)+ #43(跟随会话的官方字段)**。① **#45**:selfPort() 就是本进程自己的端口,而 /version-check 会用 probeServedVersion **再向它发一条 /version-check** → 处理器 → 自己 → 处理器…… 深度只受超时约束、每层各开一条连接 → listen backlog 打满 → **间歇 ECONNREFUSED(17–37ms)**、TIME_WAIT 静置 40 秒涨 4402、served 反成空串;0.10.36 的「重试 3 次」把它**乘以 3**。现在自探一律**直接作答**(probedSelf:true,不发 HTTP),/restart-self 同一道闸门;/version-check 也支持 ?port= 问别的端口。② **#44**:nodeBinary() 缓存命中前**验存在性**,execPath 失效(brew 升级删掉旧目录)时回退 PATH —— 长命 watchdog 从此自愈;inspectResident 新增 portServing,decideResidentAction 遇「登记说 watchdog 活着、端口却没人应答」判 restart(watchdog-port-dead)而**不再 delegate**(那正是「沉淀静默停摆、日志只写 delegated」的现场);status 打印实际用的 node 与存在性。③ **#43**:sessionRouteFrom() 改为 async,按官方字段优先取 session.requestHeader().config → agent.options → 旧嗅探兜底。npm test 共 **375 项** |
| **v0.10.36** | 2026-10-16 | **把两种新状态接到所有入口(align / doctor)+ 修一处测试卫生**。① align 新增一条结论:**常驻实例由宿主托管**(锁不可写 → 看门狗路径被绕过,改由宿主停旧起新并保活)—— **warn 级**,明确写「实例是有人管的,不是孤儿;锁恢复可写后自动交还看门狗」(align.ok 不受影响,warn 不算失败);② dsh-memory doctor 增印一行 **常驻实例托管:**(看门狗 / 宿主托管 / 不可替换(环境限制)),配合已有的 journal 模式、写探针、锁可写性、备份清单,一次看清这台机器处于哪种状态;③ **测试卫生**:/restart-self 的 alreadyCurrent 用例依赖**开发者本机真实的锁文件**(我本机锁里只剩一条 endedAt 记录,于是它不再判 alreadyCurrent、用例翻红)—— 把「读锁」做成可注入(deps.readWatchdogLock),用例自己注入一条活登记,从此与开发机状态无关。npm test 共 **371 项** |
| **v0.10.35** | 2026-10-16 | **接管助手单飞互斥(#30 第六轮实测:宿主 tick 与 CLI 各拉一个 --replace 互相踩)**。报告者观察到失败的 restart 期间同端口上**同时存在两个互不相识的接管助手**(父 = DSH 宿主 / 父 = CLI),谁都不写锁、还互相收对方的 web —— 现场更难复盘。修法:新增 **claim 文件**(memory-eternal-replace.claim,与看门狗锁同目录)—— ① **助手开工前独占抢 claim**,抢不到就**直接退出**(把接管让给已经在跑的那个),并在退出时释放;② **restartResident 发现接管进行中**(持有者活着且 <60s)就**不再拉第二个助手**,返回 stage「in-flight」并说明是谁在跑、多久前启动;③ **失败开放**:受限环境写不了 claim 时**照常接管**(不能让权限问题把接管彻底堵死),但把原因写进返回值 / 日志。测试覆盖:先到先得 / 活持有者让位 / 过期与死 pid 可接管 / 只清自己的 claim / restartResident 的 in-flight 分支。npm test 共 **370 项** |
| **v0.10.34** | 2026-10-16 | **治本:锁不可写时由「宿主」自己托管常驻实例(采纳 #30 报告者第四点建议)**。他的诊断:那台机器上「登记」恰好落在**唯一写不了锁的那个进程**上 → 自动替换被永久拒绝。修法:① 宿主的 `autoWebMode=interval` 保活循环**不再只探存活**,还探**端口上服务的版本** —— 落后且锁可写 → 仍交给看门狗 / 漂移路径(宿主不抢);落后且**锁不可写** → **宿主自己接管**(`ensureWebServer({forceTakeover:true})`:只收掉**自家** web、在**同端口**起新实例、由宿主保活);② 新增可测试的 `takeoverOwnWeb()`(安全闸门:只对 `looksLikeOurWeb` 为真的占用者下手,别人的进程一个都不动);③ 「宿主托管」记成状态(`recordHostManaged`)→ `/version-check` 报 `resident.managedBy='host'`,面板显示「**宿主托管**」而不是「未托管 / 孤儿」;看门狗重新登记后自动清除。`npm test` 共 **366 项** |
| **v0.10.33** | 2026-10-15 | **蒸馏供应商:动态下拉 + 跟随当前会话(尽力)+ 仅试首选 + 测试按钮(issue #42 第一刀)**。① **动态下拉**:新增只读接口 `/llm-routes`(宿主侧的 listProviders/listModels),配置页的「蒸馏 provider / model」从**手填编号**变成**下拉选择**(取不到时自动回退成文本输入);② **跟随当前会话**(他的最高优先级):新增配置 `captureFollowSession`,开启后宿主蒸馏**先尝试**用这一轮会话的 provider/model(`sessionRouteFrom()` 按 `agent / session / session.route / session.options` 逐个嗅探),**读不到就回退显式配置并记一条日志**(DSH 未保证会话对象暴露 route —— 这条日志就是以后补适配的依据,不猜);后台 / 无会话任务(CLI、watchdog、hooks)天然只能回退配置;③ **仅试首选 + 测试按钮**:`captureFirstOnly` 让选路只取第一个候选、失败即停(避免「先打官方 → 白烧一次调用 → 再轮换」);`POST /llm-test` 用真实调用路径打一次小请求,回显 **provider / model / 耗时 / 候选数 / 错误码**,也是 ①② 的验证手段。④ 顺带修一处**探测抖动**:`probeServedVersion()` 现在退避重试 3 次(报告者实测「同一时刻 version-check 200、2 秒后 status 说没有服务」),单发探测不再被重启窗口/瞬时拒绝骗到。`npm test` 共 **363 项** |
| **v0.10.32** | 2026-10-15 | **「常驻实例不可替换」变成长期状态,而不是每次开机重试(#30 第五轮复测:A: 盘的稳态)**。报告者实测 5 天 44 次「版本漂移」,机制是**每一步都按设计工作、合起来永远换不掉**:升级 → 端口上仍是上一代 → 宿主探测漂移 → 拉起 `--replace` 助手 → 助手前置探到 `lock.writable=false` → **正确地拒绝**(0.10.29 的设计,零破坏)→ 旧实例继续存活 → 下次开机再报一次。修法:① 新增 `lib/resident-state.js`:把「不可替换(环境限制)」**记成状态**(原因 / 锁路径 / 端口 / 版本 / 次数,7 天有效);② 宿主在重启前**先探锁可写性**,不可写就**不再重试注定失败的替换**,只记一条 —— **首次 `warn`**、之后每次开机降级为 `boot` 级「已知状态」(报告者原话:不想每次开机都看到一条 warn 永久持续);③ 措辞与「漂移」显式分开:**「常驻实例不可替换(环境限制)」** + 出路(修权限后 restart,或 `dsh-memory watchdog --port N` 前台观察);④ 面板 / `/version-check` / `align` 都能看到这个状态(`resident.replaceBlocked`;`align` 里是一条 `warn` 级结论,环境修好后自动清除)。`npm test` 共 **359 项** |
| **v0.10.31** | 2026-10-15 | **`restart` 在「端口枚举很慢」的机器上 100% 失败的根因修复(#30 第三/四轮复测,报告者定位到行)**。机制:`--replace` 助手的顺序是「清场 → 端口空了才写锁 → 启动看门狗」,而清场里 `Get-NetTCPConnection` 实测要 **4–13.6 秒**且不受 8 秒预算约束 → 助手**永远走不到写锁那一步** → 锁里什么都没有 → `waitForTakeover`(要求「锁里出现新的活看门狗」)**必然超时** → CLI 与面板按钮在这类机器上 100% 报「(无响应)」,**与版本号无关**。修法:① **清场失败也必须留下登记**(`acquireWatchdogLock({force:true})`),现场不再出现「端口有 web、锁里空白」;② **`acquireWatchdogLock` 支持 `force`**(`--replace` 的语义就是替换同端口那个,不许让位 —— 否则助手在清场没跑完时先退出);③ 0.10.30 已给枚举执行器加超时(`defaultExec` 真掐断子进程、`findPortListener({timeoutMs})` 逐层传递、清场给 ≤2.5s 有界值);④ CLI 的 `restart` 结果**真的写进自动沉淀日志**(提示里一直这么说,但 CLI 路径过去不写)。他还实测到「两个接管助手互相赛跑」(宿主 tick + CLI 各拉一个)—— 登记 + force 之后这种赛跑的危害大幅降低(后到者接管登记,而不是双双留空)。`npm test` 共 **355 项** |
| **v0.10.30** | 2026-10-15 | **两处「让整页不可用」的修复:畸形 tags 黑屏(#41)+ 接管链路的两个真病灶(#30 第三/四轮复测)**。① **#41**:`/write` 传字符串 `tags`(如 `"[\"a\",\"b\"]"`)被 `writeCard` 直接 `JSON.stringify` → **双重编码**落库 → 读出来仍是字符串 → 前端 `card.tags.slice(...).map` 抛错 → **React 根组件挂载失败、整页黑屏**(重启 DSH / 插件 / 常驻实例都无效,因为坏数据一直在库里)。现在 `normalizeTags()` 是**唯一入口**:数组 / JSON 字符串 / 逗号串 / **双重编码的旧数据**都收敛成数组,写路径与**所有读路径**都走它(既有坏数据不必用户手工修);渲染层再加兜底(坏 tags 最多少显示几个标签,不许把整棵树带走)。② **#30**:报告者在我 0.10.29 里又抓出两处 —— `restoreWatchdogLock` 的「活登记」判定**少了 `!w.endedAt`**(PID 复用/正在退出时,写回会被放弃);**Windows 端口枚举没有超时**(实测 `Get-NetTCPConnection` 无条件要 4–7.5 秒、一次 13.6 秒,而接管清场的 8 秒预算根本管不住它 → 决策基于过期数据)。现在 `defaultExec` 支持超时并真的掐断子进程、`findPortListener({timeoutMs})` 逐层传下去、接管清场给有界值(≤2.5s)。新增 `tests/tags-normalize.test.mjs`(4 项)+ `watchdog-restart` 2 项回归。`npm test` 共 **354 项** |
| **v0.10.29** | 2026-10-14 | **失败的接管不再破坏现场(issue #30 复测的关键发现)**。报告者在 0.10.26 上实测:`restart` 失败后**锁文件被删除**、端口上还是同一个 web —— 现场从此看起来像「从来没有过常驻实例」,只剩一个孤儿。四处修复:① **释放锁不再删文件**(`releaseWatchdogLock` 留一条带 `endedAt` 的「已结束」记录,`status` 会显示「已退出(陈旧记录)」;这种槽**永不阻挡**新实例,pid 复用也不怕);② **失败的 restart 把替换前的登记写回去**(`restoreWatchdogLock`,只在本端口没有活着的登记时才写);③ **枚举不到占用者时不再误判「端口已空」**——`clearResidentForTakeover` 用 **TCP 复核**(受限环境里进程查询被拒会返回空,旧实现据此认为端口空出 → 新 web 绑不上 → 自检「(无响应)」+ 锁被清空),还在监听就中止接管且**一个登记都不动**;④ **锁写不进去时前置拒绝接管**(写不进锁 → 新实例无法登记 → 只会留孤儿;直接报错并给前台观察的出路)。另外 `restart` 失败信息补上「**端口上现在是谁**」+「锁有没有写回」,`status` 把「端口探测」与「锁登记」**分段标注**(报告者指出旧输出两者并列会误导)。`npm test` 共 **348 项** |
| **v0.10.28** | 2026-10-14 | **新增「版本对齐检查」:把「插件迭代后旧接口 / 旧接线还在」一次查清**。插件升级只换**磁盘文件**,两类「旧」不会自己消失:① **运行时** —— DSH 宿主进程内存里注册的事件订阅(`agent/turn-stopping`、`agent/inbox/claimed`…)来自它**启动时加载的那份代码**,不重启 DSH 就会一直用旧接口(那条「轮次收尾事件疑似失效」多半就是它:本机实测宿主 **v0.10.8** / 磁盘 v0.10.27,而且该告警文案里的「累计 135/75」本身就证明跑的是 ≥0.10.20 之前的旧判据);② **持久接线** —— 插件写进 `~/.claude.json`、`~/.claude/settings.json`、`~/.codex/config.toml`、`~/.cursor/mcp.json` 的 MCP 与 hooks 条目含**旧安装路径 / 旧 node 路径 / 旧参数**,升级后不会自动改写。新增 `lib/align.js` + `GET /align` + `dsh-memory align [--json] [--fix]` + 面板「用量/今日 → 🔧 版本对齐检查」:逐条给结论与**可执行动作**(`完全退出并重开 DSH` / `dsh-memory restart --port N` / `dsh-memory setup` 重写接线 / 更新插件),`--fix` 只重写插件自己的键(不动数据与配置)。新增 `tests/align.test.mjs`(6 项,含「同一 node 但条目是旧写法」的措辞准确性守卫)。`npm test` 共 **345 项** |
| **v0.10.27** | 2026-10-13 | **「损坏时从备份重建」从引导升级成可执行(issue #31 的现实出路)**。WAL 头一旦损坏,代码修不了**数据** —— 上一版只能告诉你「有哪些备份、按什么顺序替换」,现在多了 `dsh-memory doctor --restore latest\|备份名`:**不带 `--yes` 只演练**(一个文件都不动,先告诉你备份里有几张卡、会不会覆盖可写库);执行时四道闸门:① **校验备份**(只读打开 + `PRAGMA quick_check` + 数卡片);② 当前库**此刻**完好时要求显式 `--force`(用**当场探测**判,不信缓存的体检结论);③ 检测到常驻看门狗还活着就**中止**(避免边写边换,除非 `--force`);④ 恢复后**复核**(重新打开 + 写探针 + 卡片数)。原现场**一律改名 `*.corrupt-<时间戳>` 留证,绝不删除**(库 + `-wal` + `-shm` 一起)。新增 `tests/db-restore.test.mjs`(4 项:坏库重建 / 完好库需 force 且演练不动任何文件 / 拒绝危险来源 / 体检结论刷新)。`npm test` 共 **339 项** |
| **v0.10.26** | 2026-10-13 | **撞锁改用「应用层异步重试」,而不是 `busy_timeout`(issue #31/#33 实测对症)**。本机对照实验:外部进程持写事务时我方立刻拿到 `database is locked`,而且**对方被强杀之后仍然锁着** —— 老实现没有忙等,一撞锁就永久失败(在 A: 盘那类受限 / 被外部占用的现场,表现出来就是「整个记忆库坏了」)。评审已否决 `PRAGMA busy_timeout`(`node:sqlite` 的 `DatabaseSync` 是**同步 API**,忙等会阻塞宿主事件循环、把整个 DSH 卡住),所以改成新增 `withDbRetry()`:撞锁时**让出事件循环睡一小会儿再试**(线性退避,最多 3 次),只用**写路由**;「响应已发出」时显式禁止重试(`e.noRetry`,避免重复写),非锁类错误(数据 / 语法)只试一次并原样抛。测试覆盖:重试到成功 / 非忙错误只试一次 / `noRetry` 生效 / 试满仍失败 / 源码守卫(不许偷偷加 `busy_timeout`)。`npm test` 共 **335 项** |
| **v0.10.25** | 2026-10-13 | **「判不出来」不再冒充「未托管」+ 库不可写时的统一 UX(issue #33 第③层收口)**。① **托管状态变三态**:`/version-check` 增报 `resident.managedKnown`、`lock:{path,writable,error}` 与 `pid` —— 受限环境(沙箱禁写 / ACL / 非系统盘)写不进看门狗锁文件时,面板不再显示「未托管」(那会让用户以为实例坏了、反复重启),改为「**托管状态未知**:锁文件写不进去 → 无法判断;保活通常仍然有效」,并同时显示真实原因与路径;`writeWatchdogLock` 的失败也不再全静默(每个进程留一条可查的痕)。② **库不可写时统一 UX**:写路由(delete / merge / card / refine-apply / optimize-execute / …)在入口**短路径失败**(503 + `DB_UNAVAILABLE` + 恢复指引),没枚举到的写路径由最外层兜底把 SQLite 原文翻译成同一套答复;面板**跨标签顶部**明示「记忆库当前不可写 —— 卡片读写会失败,但**配置仍可保存**」。③ `dsh-memory doctor` 增报看门狗锁可写性(受限环境一眼可见)。`npm test` 共 **334 项** |
| **v0.10.24** | 2026-10-13 | **「同目标、不同做法」也能归到一起了(issue #36 第三块,闭环)**。前两版解决了「冗余合并」与「矛盾判别」,但最后一块 —— 同一目的、不同工作流/代码 —— 按正文相似度**根本抓不到**(「用 7z 压缩」「用 tar.gz 分发」「npm 包瘦身」字面差很远、目标却一样)。做法:**先让模型给每张卡抽一句「这张卡解决什么问题」,再按目标聚类** —— 目标抽取结果缓存在 `refine-goals.json`(同一张卡只抽一次,第二次扫描 0 次调用),聚类用并查集;命中后按「同一目标 N 张卡」成组给出,动作二选一:**都保留**(记两两决策,下次不再提示)或**合并这一组**(走现有 /merge,新卡进审核队列)。*全程只读,唯一写入是目标缓存*。入口:面板「整理 → 🎯 同目标不同做法」+ API `/refine-goals`、`/refine-goals-keep`;CLI `dsh-memory refine --goals --keep --path A --path B`(聚类本身需要模型,在面板跑)。`npm test` 共 **332 项** |
| **v0.10.23** | 2026-10-13 | **存量「注入垃圾卡」可一键清理(issue #36 第二刀)**。上一版修掉了**新生成**的注入垃圾卡(技能目录 / `<system-reminder>` 被当 user 消息 → 剥标签后仍被当知识沉淀),但**存量还在**:在真实库里一跑就找出 **32 张**「和对话毫无关系」的卡(`- \`code-review\`: Review the changes…` 这种目录行、`用户system-reminder-you-are-teammate-c…` 这种碎片)。新增 `findInjectedCards()`(只**提议**、不动数据;判据:含注入标签 / 技能目录条目占比 ≥30% / 剥离注入后只剩 <200 字 / 标题是碎片)与 `purgeInjected()`(**走回收站、可还原**,逐个报告失败)。入口:面板「整理 → 🧹 注入垃圾卡」(扫描存量 / 单张回收 / 全部回收)+ CLI `dsh-memory refine --junk [--purge --path P] [--json]`;API `/refine-junk`、`/refine-junk-purge`。`npm test` 共 **331 项** |
| **v0.10.22** | 2026-10-13 | **「矛盾扫描」:相似可以合并,矛盾必须人判断(issue #36 第一刀)**。现状是 `/optimize` 只按相似度(≥0.55)**盲合并** —— 而两张语义相近、结论互斥的卡相似度同样很高(实测 0.93),盲合并会把冲突悄悄「和解」掉,且 `mergeCards` 会**永久删除**源卡。新增 `lib/refine.js` + 客户端「整理 → 🧭 矛盾扫描」:① **宽召回**(≥0.30,上限放到 1.0,特意把近重复那一段纳入 —— 那正是盲合并最危险的区间);② 模型只判「重复 / 补充 / 矛盾 / 无关」并给一句冲突点 + 建议保留谁;③ **人拍板**:保留 A / 保留 B / 都保留 / 合并;**扫描绝不写库**,`keepA|keepB` 只把让位那张送**回收站**(可还原);④ 决策记进 `refine-decisions.json`,同一对不再重复打扰;⑤ **安全联锁**:被判为矛盾的对**绝不参与一键优化**的盲合并。另修一处冗余来源:技能目录(`- \`skill-name\`: …`)这类注入文本不再被抓成垃圾卡(实测库里已有这种卡)。API:`/refine`、`/refine-scan`、`/refine-apply`;CLI:`dsh-memory refine`(列候选不调模型,`--apply` 落裁决)。`npm test` 共 **329 项** |
| **v0.10.21** | 2026-10-13 | **「库不可用」从「静默运行」变成「看得清」(issue #33,按评审裁剪范围)**。① **打开即体检**:老实现执行完 `PRAGMA journal_mode=WAL` 就当成功(这条 PRAGMA 失败时**不抛错**,只把真实模式当一行返回),文件只读 / 目录不可写时插件会一路静默跑下去;现在打开后**读回真实 journal 模式 + 真写一次探针**(建表→删表,写主库而非 temp 库),结论记进 `dbStatus()`,不可写时立即把原因与排查方向打到日志。② **`/overview` 降级返回**:库/目录不可用时不再吐 500(500 会被探针、面板、脚本读成「这个端口坏了 / 不是我们的服务」,历史上正是重启风暴与端口漂移的入口),改成 200 + `degraded:true` + `dbError` + `db:{journalMode,writable,error}`,并保住 `vaultDir` / `version` 身份字段 —— 「这是我们、但库有问题」从此可以被分开表达。③ **新增 `dsh-memory doctor`**:库健康自检(真实 journal 模式 / 写探针 / 备份清单 / 恢复引导),带 `--json` 可脚本化;不可写或疑似损坏时给出**只引导、不动数据**的步骤(先停写者 → 复制现场留证 → 查备份 → 替换 → 复核)。**刻意不做**(评审结论,已写成测试守卫防止顺手加回):`busy_timeout`(`DatabaseSync` 是同步 API,撞锁会阻塞宿主事件循环)与 WAL→DELETE 自动降级(会**永久**改变库模式,且 rollback journal 并发更差、可能反过来加剧锁竞争)。`npm test` 共 **322 项** |
| **v0.10.20** | 2026-10-12 | **「库不可用 → 看门狗自杀自拉 → 端口漂到 8006」三层现场一起修(issue #31 / #33,AkiMei0721 实测)**。① **`/overview` 非 200 ≠ 端口死了**:`/overview` 会碰数据库,库只读 / WAL 损坏时它返回 500,而看门狗据此判「离线」→ 走 `spawnWeb()`(开头先 SIGTERM 掉自己的上一个 child)→ **自杀自拉、端口从 7999 一路漂到 8006(6 个实例)**;新增 `decideTickAction()` 纯决策:只要 **TCP 还能连**就视为存活(`api-degraded-but-listening`,并重置重启计数),新实例的就绪判定同样接受「已监听但 `/overview` 非 200」。② **版本探测改走 `/version-check`**(不碰库;拿不到才回落 `/overview`)—— 病根是「端口上明明活着、版本却拿不到」,于是 verify 报「(无响应)」、`status` 甚至误判成「v0.10.4 之前的旧版」。③ **受限沙箱里不再崩**:默认 `stdio:'pipe'` 会在 `spawn()` 时**同步抛 EPERM**(老实现只在 `'error'` 事件里兜底,根本没机会执行)→ `dsh-memory watchdog --reap` 直接崩、进程枚举永远空名单;现在 `try/catch` 包住 spawn,并**用临时文件接 stdout**、完全不碰命名管道。④ 顺带给 stall 判据加「最短运行时长(30 分钟)」:刚落进程必然 0 收尾,不该据此报「收尾事件失效」(实测重启 1.5 分钟后就误报过)。新增 `tests/issue-fixes-8.test.mjs`(4 项)+ `tests/stall.test.mjs` 的 too-early 用例。`npm test` 共 **318 项** |
| **v0.10.19** | 2026-10-12 | **`UNSUPPORTED_REASONING_EFFORT` 不再让整条蒸馏熔断(issue #34)**。现场(zafir-zhong 等):很多**网关**在能力自述里说支持 reasoning effort,真把 `reasoningEffort: 'off'` 发出去才回 `UNSUPPORTED_REASONING_EFFORT`(实测:workbuddy/hy3、FallbacksChain/Auto、new-api-resp/gpt-6-luna),于是整个候选被判失败、换下一个 provider —— 用户的自动沉淀直接停摆(#37 / #38 / #40 的诊断信息里都能看到这条 FAIL)。既有设计只覆盖「provider **自述**不支持 → 不传该字段」(#15),缺了运行时这一半。修法:新增进程内记忆 `markEffortUnsupported()\/isEffortUnsupported()` —— 首次被拒时**不带该字段重试一次**(同一候选、同一 provider),并记住这个 `provider/model`,此后同一进程内它第一次请求就不再带 effort;记忆**按 provider/model 分开**,不误伤别的 provider。回归守卫加在 `tests/capture-route.test.mjs`(第一次带 → 被拒 → 第二次不带并拿到卡 → 后续只发一次 → 别的 provider 不受影响)。`npm test` 共 **313 项** |
| **v0.10.18** | 2026-10-12 | **「保存配置」被无条件拒绝的修复(issue #35)**。现场(Dzifxazy):全新安装、什么都不动、直接点保存 → **HTTP 400「字段不能为空:secretHint」**,整单被拒,用户改的数字与开关**一个都存不进去**;文案还会把排查方向误导到「某个数字格子没填」。根因:宿主侧的前置校验把**任何空字符串**都当成「数字没填」,只豁免一份硬编码名单(vaultDir / captureProvider / captureModel / recallEmbedding / activeVault)—— 而 `secretHint` 的默认值**就是**空串(`z.string().default('')`)又不在名单里;面板提交的是整张表单,于是每次保存必然命中。修法:新增 `emptyNumericFields(patch, Config.dict)`,**按 schema 的 `type === 'number'` 判定**「哪些键被清空」,彻底删掉硬编码名单(以后新增可空字符串字段不需要谁记得去补)。新增 `tests/config-validate.test.mjs`(2 项:空串只对 number 字段报「没填」——里面放了一个「不在任何名单里」的新字符串字段做回归钉子;源码守卫不许退回名单)。`npm test` 共 **312 项** |
| **v0.10.17** | 2026-10-12 | **「常驻实例在服务、却没人托管」这类状态自相矛盾的根治(issue #28)**。现场:用户升级到 0.10.16 后常驻实例没起来,面板**既没有重启按钮也没有任何提示**(横幅只在 `vc.stale` 时渲染,而宿主与端口版本号一致时 `stale` 为假),只看到一条「自动沉淀异常」;本机复现出另一半:`stop` 之后端口上**还活着**、锁里却空了、`status` 说「没有登记的看门狗」—— 因为端口上那个 web 是宿主 `ensureWeb()` 直接拉起的(锁里没有 `webPid`),`stop` 只杀了看门狗。五处一起修:① **`/version-check` 报出 `resident.managed`**(锁里有活着的看门狗才算被托管)—— 「在服务」≠「被托管」;② **面板**:常驻实例**没在跑**或**没被托管**时都显示重启按钮与专门文案(「未运行」/「未托管」),不再只认「版本落后」这一种;③ **`/restart-self`**:`alreadyCurrent` 必须同时满足「版本一致 **且** 被托管」,否则照常替换(孤儿 web 版本再新也得重新托管);④ **显式 `dsh-memory stop` 把端口腾出来**(`forcePortOccupant: true`,闸门不变:只终止 `looksLikeOurWeb` 认得出是自家的进程);⑤ **自检改为「新实例接管」**:`restartResident` 不再只看「端口服着期望版本」(升级前后版本号常常相同、旧实例本就是这个版本 → 自检会被**旧实例**骗过,实测打印「自检通过(等待 0s)」后紧接着 `status` 说没有登记的看门狗),新增 `waitForTakeover()`:必须「版本一致 **且** 锁里出现一个不在替换前 pid 集合里的活看门狗」才算成功,失败原因也据此分离成「版本不对」与「没人接管」。本机同一现场实测:替换前 `41672/17432` → 替换后 `28136/31976`,`restart` 等待 13s 后自检通过,**紧接着的 `status` 立刻显示新看门狗已登记**(不再矛盾)。`npm test` 共 **310 项** |
| **v0.10.16** | 2026-10-09 | **`--reap` 的输出不再「明明收了却说没收」**(alario-tang 反馈,macOS + 0.10.13 实测)。现场:`kill -9` 掉 watchdog 造出 `PPID=1` 的孤儿 web 后跑 `dsh-memory watchdog --reap --port 7999`,端口确实被释放、web 确实被收掉,屏幕上却写着「**扫描到 1 个 watchdog 进程,清理孤儿 0 个**」—— 因为 0.10.13 让 `scanned` 含了 web、而 `webKilled` 一个字都没打印。修法:新增纯函数 `formatReapSummary()` 统一口径(扫描数拆成 `watchdog A / web B`、清理项按类分别列出 pid),`reapStaleWatchdogs` 返回值补 `scannedWatchdogs` / `scannedWebs`(`scanned` 仍是两者之和,兼容旧调用方),CLI 改用它。测试补 2 项(收了 web 时不许再说「清理孤儿 0 个」/ 扫描计数必须拆开),并在 `tests/watchdog-orphan-web.test.mjs` 头部记下复现要点:**这类孤儿只能用 `kill -9` 造 —— SIGTERM 会走 watchdog 的优雅退出、把它拉起的 web 一起停掉**(与 #19 里「Windows 上 SIGTERM 即无条件终止」是同一枚硬币的两面)。`npm test` 共 **308 项** |
| **v0.10.15** | 2026-10-09 | **三处「升级留下的尾巴」**。① **推荐方案 A 不再把刚省掉的调用装回来**:v0.10.12 把「蒸馏输出上限」默认提到 4000(省掉「先撞 2000、再以 4000 重跑同一候选」那次白烧),但面板里「一键推荐配置 A(轻量)」仍写死 `captureMaxTokens: 2000` —— 用户点一下就又把重试装回来;现已与默认对齐,并加源码守卫(`tests/csrf-origin.test.mjs` 断言 planA 的上限 === 默认值)。② **README 的「停滞兜底」描述与新判据对齐**(中英两份):旧文案写「15 分钟收不到收尾事件就报」,现在是「**本次运行至今 0 收尾** + 最近 20 分钟内持续有轮次开始 → 记一条 `warn`」,并写明正在进行的**长回合不会被误判**。③ 顺带核实日志里另一种措辞 `轮次收尾事件未触发(agent/turn-stopping 没到)` 最后出现于 **2026-09-24**,属于旧判据的历史残留,现行代码里已无这条路径(无需修复)。`npm test` 共 **306 项** |
| **v0.10.14** | 2026-10-09 | **「自动沉淀异常:轮次收尾事件疑似失效」误报根治**。现场:几十分钟的长回合里,`agent/inbox/claimed` 在**一轮对话中途**也会多次触发,而 `turn-stopping` 要等回合真的结束才来 —— 于是 20 分钟窗口必然「开始 ≥3、收尾 0」,判据把**还在进行中**的轮次当成「事件被改名」,直接把红警报推给用户(本会话实测那条「累计 39/18」就是这么来的)。修法:加一条硬前提 —— 「事件被改名/移除」是**进程生命周期**属性,不是窗口属性:本次运行里只要收到过哪怕一次收尾,通路就是好的,窗口内 0 收尾只说明当前轮次还没结束(`evaluateStall` 新增 `everStopped`,并在返回值里给出 `reason: wiring-proven | no-stop-ever`);只有「本次运行至今 **0** 收尾 + 窗口内持续开始」才报警。同时把这条**推断**从 `fail` 降级为 `warn` —— 不再由它把沉淀健康状态翻红、更不冒充「自动沉淀异常」(真故障会由写卡失败自己报红)。`tests/stall.test.mjs` 新增 3 项锁住新旧行为(长回合不报 / 真改名要报 / `everStopped` 可注入)。`npm test` 共 **305 项** |
| **v0.10.13** | 2026-10-09 | **孤儿 `web.js` 终于收得掉了(#11「待做 1」的 POSIX 分支)**。现场(alario-tang,macOS arm64):watchdog 先死 + 锁文件丢失 → 端口 7999 上留着一个 `PPID=1` 的旧 web;`stop`/`restart` 读锁槽找不到目标、`watchdog --reap` 只认 `watchdog.js`、面板按钮又走同一条链路 → 漂移只能报不能自愈,用户被迫手动 `kill`。查出**两个具体缺口**:① **POSIX 侧端口占用者只拿到进程名** —— `lsof -Fpcn` 的 `c` 字段是 `node` 而不是命令行,而「要不要收」的判据(`looksLikeOurWeb`)要求命令行含 `memory-eternal` 或本机 `web.js` 绝对路径 → 判据恒假 → `stopWatchdogs` / `clearResidentForTakeover` 的兜底循环**一次都不杀**(Windows 走 `Get-CimInstance` 拿完整命令行,所以本机从未暴露);现在补一次 `ps -p <pid> -o args=`,拿不到才退回旧行为(宁可不杀、也不误杀),并让 `platform` 可注入以便单测。② **`--reap` 扩到自家 web**:新增 `listWebs` 分支,把「同端口 + 是自家 web + **托管者已死**(该槽 watchdog 不存活)+ 不在保护集合(keepPid / 当前 pid / 父进程 / 带 `--reap` 的同类)」的 web 一并回收;**托管者还活着的 web 一律不动**,别的端口、别人的 `web.js` 一个不杀。新增 `tests/watchdog-orphan-web.test.mjs`(5 项),并把两条只谈 watchdog 的旧用例显式声明 `listWebs: []`(否则 reap 会去列本机真实进程表)。`npm test` 共 **302 项** |
| **v0.10.12** | 2026-10-09 | **两件「不显眼但每天都在扣钱 / 留洞」的事**。① **蒸馏输出上限默认 2000 → 4000(= 硬顶)**:日志实测高频出现「输出撞到 maxTokens=2000 上限 → 以 4000 重试同一候选」,每一次都是**白烧一次 LLM 调用**(先撞顶、再拿 4000 重跑同一个候选)。maxTokens 只是上限、按实际生成量计费,所以默认直接放到硬顶:同样的轮次一次调用就够;`maxTokenLadder` 仍保留给**显式**把 `captureMaxTokens` 调小的用户。② **插件 API 加跨站写入闸门**:`/memory-eternal/api/*` 无鉴权,本机任意网页都能 POST `/config`、`/audit/approve`、`/write`… 新增 `crossOriginError()` —— 状态变更请求(非 GET/HEAD/OPTIONS)必须与请求自身的 `Host` **同源**:浏览器跨站请求一定带 `Origin`(与 Host 不同源即 403 `CROSS_ORIGIN_BLOCKED`);同源页面(DSH 设置页/记忆页、独立页)与局域网访问都同源不受影响;没有 Origin 的 curl / CLI / MCP 照常放行;`app://` 这类桌面壳自定义 scheme 也放行以免误伤。宿主自己的拦截路由(`/config` POST、`/restart-self`、`/mcp/action`…)与共享 api 入口**都**装了这道闸门。新增 `tests/csrf-origin.test.mjs`(4 项:判定矩阵含局域网 / 无 Origin / null / app://、跨站 POST 被 403 且不进业务逻辑、两处入口都在、默认上限=硬顶且无 ladder 重试)。`npm test` 共 **297 项** |
| **v0.10.11** | 2026-10-09 | **蒸馏输出解析再补一层容错:字符串里的裸引号(`Expected ',' or '}' after property value`)**。现场:`fail` 日志里 `UNPARSEABLE_OUTPUT` 占大头(最近 600 条里 103 条),最近一条带全了现场 —— `JSON.parse 原始报错:Expected ',' or '}' after property value in JSON at position 1913`,摘录里能看到正文写着 `运行 "npm test" 时…`:模型把引号当正文直接写进字符串,于是它被当成字符串结束符。这与 #24 修过的「裸控制字符」是**不同**的一类(控制字符在字符串内非法,引号只是需要转义)。修法:新增 `repairUnescapedQuotes()` —— 一个 `"` 后面(跳过空白)紧跟 `:` `,` `}` `]` 或文本结尾才算字符串结束,否则当正文补 `\"`;它**只作为最后一个候选**参与解析,且必须仍通过卡片体检(标题合格 + 正文 ≥20 字)才被接受 —— 宁可不修,也不写坏卡。回归守卫 `tests/capture-json-repair.test.mjs`(4 项:合法 JSON 恒等变换、裸引号现场、控制字符与裸引号叠加、截断仍如实失败)。`npm test` 共 **293 项** |
| **v0.10.10** | 2026-10-09 | **审核中心能看正文了 + 就地批准/驳回(issue #27)**。现场(thinrflbtlm):审核中心过去只列「标题 / 类型 / 来源 / 严重度 / 原因」,**看不到正文**;而 v0.10.0 之后待审卡物理隔离在 `quarantine`、知识卡界面只列已审核卡 —— 于是「不人工确认就没法审」,审核形同虚设。① 每行新增 ▸/▾(点标题也行)**就地展开 frontmatter + 正文**,用与卡片详情**同一套 Markdown 渲染**(`renderMd` + `splitFrontmatter`),不是裸文本;② 正文**按需拉取**(`GET /card?path=…&status=pending|rejected`,服务端只放行确实在审核队列里的 path,未知 path 仍然 403 `CARD_NOT_APPROVED`),列表接口不塞整库正文;③ 展开块里直接给 **✓ 批准 / ✕ 驳回**(回收站页签是 ✓ 恢复 / 🗑 删除),批量勾选与外层工具条保持原样;④ 批/驳后自动收起并刷新,中英双语词条齐全。新增 `tests/issue-fixes-6.test.mjs`(2 项:待审正文读得到且批准后离开队列、源码守卫含「按需拉取」与「越权拒绝码」)。`npm test` 共 **289 项** |
| **v0.10.9** | 2026-10-09 | **升级期端口漂移把记忆页打成「拒绝连接」的根治(issue #29)**。现场:旧包遗留的 detached 实例占着 7999 且不自报版本 → 宿主 `ensureWebServer()` 认不出它、向后漂到 8000 并写进 `web-info`;而插件的自愈随后把 8000 上的实例当野实例收掉 → `web-info` 永久悬空,客户端 iframe 指向死端口(首次能进、刷新即「拒绝连接 127.0.0.1」)。① **记忆库页改走 host 同源壳**:宿主新增 `/memory-eternal/ui/app`(与配置页 `/ui/config` 同一份 index.html,其中 `<script src="app.js">` 相对路径正好解析到 `/ui/app.js`),iframe 地址从此与「常驻实例监听哪个端口」**彻底解耦**(旧宿主 404 时仍回落 `web-info`)。② **`/web-info` 实时收敛**:每次读之前先探「配置端口 → 记录端口」,谁在服务就用谁,都不通就如实 `alive:false`(不再把死地址当活的);自愈替换成功后与 interval 保活探到活时都就地回写。③ **不再盲目漂端口**:新增 `looksLikeOurWeb()`(宽松识别「自家但不自报版本」的占用者:/overview、/web-info、/budget 任一 ok:true 即认),疑似自家时先给自愈留 6s 接管窗口,等不到才漂并留日志。④ **体检退避重试**:`inspectResident()` 一次探针打空不再直接判「旧版不自报版本」(那个结论会触发误替换,每次启动都白起一次实例),退避重试后才下结论。新增 `tests/issue-fixes-5.test.mjs`(4 项:宽松识别真/假阳性、等自愈接管不漂端口、宿主与客户端源码守卫)。`npm test` 共 **287 项** |
| **v0.10.8** | 2026-10-08 | **独立 Web 端保存「点一下即生效」:把应用 pending 的上下文从插件回调搬到宿主 HTTP 请求(方案 A,实测驱动)**。现场:独立页保存 → 宿主连续重试 5 次后 `dropped`,报错恒为 `HMR transactions cannot be nested`(0.10.7 只修掉了它冒充「自动沉淀异常」与升级期未知键,同步本身没修)。**实测定位**(同一宿主、同一个 `settings.update`):从插件**回调**(`setInterval` / `fs.watch`)里调 → 抛守卫错且**一个字节都没提交**(只含宿主认识的键、值真的变了,同样失败);从插件 **HTTP 请求处理器**里调 → **真的提交**(`recallSummaryLen` 130→132→130,`revision` 4→6)。所以修法不是换 API 而是换**调用上下文**:① 宿主新增 `POST /memory-eternal/api/drain-pending`,在**请求处理器**里应用 pending(两道闸门:只接受本机来源 + 心跳里的一次性令牌,防本机任意页面乱写);② 宿主把自己 web server 的**监听端口 + 令牌**写进心跳(`webServer.port` 是服务公开的 getter),独立端保存后据此**主动叫醒宿主**,拿到真实结果再回给界面(新增 `savedApplied`「已保存并生效」);③ 叫醒失败(旧宿主没这条路由 404 / 端口对不上 / 超时)**不算错误** —— 回落到 0.10.5 的等待路径与「去 DSH 设置里改」的指路。新增 `tests/host-drain.test.mjs`(6 项:回环判定 / 守卫三件套 / 端到端叫醒 / 失败如实回报 / 404 回落 / 源码守卫)+ `tests/e2e-host-drain.mjs`(2 项:**真起进程**的端到端「独立页保存 → 叫醒宿主 → 宿主应用 → 回报已生效」,以及旧宿主心跳(无端口/令牌)时回落成「排队等待」)。`npm test` 共 **283 项** |
| **v0.10.7** | 2026-10-08 | **「启动 dsh 后运行中还是旧版本」根治:版本漂移自愈 + 一键重启常驻实例(#19 / #23 的正面解法)**。现场:升级 npm 包只换了磁盘文件,**常驻 web(默认 7999)比 DSH 宿主活得久** —— 端口上那个进程仍在跑「启动时加载进内存」的旧代码;而宿主激活时的策略是「同端口已有活着的 watchdog 就 `delegated`(不再 spawn、更不替换)」,于是**重启多少次 DSH 都不会换掉它**(实测本机:宿主 10:19 启动已是新版、设置页同源显示 0.10.6,而 7999 上的 web 是 08:15 启动的 0.10.0,`/config` 与 `/version-check` 各报一个版本)。① **启动自愈(`autoRestartOnDrift`,默认开)**:激活时先 `inspectResident()` 体检 —— 版本一律**问端口**(锁里的 `pkgVersion` 只是 watchdog 自述,旧实例写的还是空串,根本看不出版本漂移),再 `decideResidentAction()` 决策:一致 → `delegate`(保持 #19 的如实口径)、漂移 → `restart`、读不到本机版本 → 绝不乱动、刚替换过 → 防抖不重来(配置改动会让 effect 重跑)。**「更新运行中的程序」的正确形态由此定为「用新代码重启那个常驻进程」,而不是热替换内存里的模块**:ESM 模块缓存 + 闭包状态(settings / vault 路由 / SSE hub / `fs.watch` / 已监听端口)没有安全热换的余地,缓存失效重 import 只会得到第二份模块图(双实例、双监听、双写)。② **替换机制统一到一处**:新增 `restartResident()` = spawn 一个 detached 的 `watchdog.js --replace` 助手(收旧 watchdog / 旧 web → **等端口真的空出来** → 抢锁 → 拉起新 web),再自检「端口上真正服务的版本 = 本机版本」(#23 建议 2);端口没空出来就**中止接管**、不退让到 `port+1`(退让实例正是孤儿 web 的诞生地)。`dsh-memory restart`、宿主启动自愈、面板按钮现在共用这一份实现。助手是 detached + `stdio:ignore`(stderr 没人看),所以接管结论**必须**写进「自动沉淀日志」—— 否则「重启了却没生效」又会变成一条无迹可查的静默失败。③ **面板一键修复**:新增 `POST /memory-eternal/api/restart-self`,三条硬约束 —— 本进程很可能**就是**要被替换的旧 web,所以**先回响应再调度**替换;常驻实例已经是最新就回 `alreadyCurrent`(宿主面板点这个按钮时最常见,绝不为一个健康实例白折腾一次停机);同一时刻只允许一个替换在跑。④ **面板语义消歧**:「运行中」改为「**运行中(服务本页的进程)**」(独立 Web 页里它是常驻 web,DSH 设置页里是宿主 —— 这正是两个面板数字「打架」的根源),并新增「**常驻实例 vX**」徽标:两者不同时一眼看出该重启谁;漂移横幅给按钮 + 等价命令,若常驻实例已新而本页仍旧,则明说「需重启桌面版 / DSH」,不再给一个点了也没用的按钮。⑤ **`updateAvailable` 改按版本比较**:`latest !== onDisk` 在本地跑未发布版本时会**反过来劝你装回旧版**(磁盘 0.10.7 / npm 0.10.6),现在用 `compareVersions`(含预发布语义 `rc.2 < rc.10`)。⑥ 顺带清掉客户端词典里重复的 `saving` 键(esbuild 每次构建都在报警告)。新增 `tests/watchdog-restart.test.mjs`(17 项:决策矩阵 / 体检问端口 / 替换自检 / 接管前清场不误杀 / 路由先响应后替换 + 并发闸门 / 版本比较)。⑦ **README 改为中英两份独立文件**:`README.md` 是**默认**的简体中文(npm 与 DSH 插件市场展示这一份),`README.en.md` 是英文镜像;顶部加语言切换与「改一处要同步另一处」的说明,并补齐英文版缺失的「环境自检」小节;新增 `tests/readme-i18n.test.mjs` 锁住**结构 1:1**(标题层级序列 + 代码块数量 + 互链 + 默认语言标记),防止以后只改一份。⑧ **`secretHint` 凭证提示 + 凭据读取守卫**:填一句自己的约定就会被注入每个会话的 systemPrompt,让 agent 需要 API key / token 时**先查记忆里的「密钥目录」卡**(记忆库只存目录:名字/位置/取用方式,**值留在本机凭据库**);`dsh-memory setup` 同时注册 Claude Code 的 `PreToolUse` 守卫(拒读 `.credentials.yaml` / `.env` / `*-token` 并指路到加载器),并会刷新仍指向旧安装副本的 hook 路径。⑨ **发布前又抓到三个实测现场并修掉**:① 面板「重启常驻实例」按钮在**服务本页的进程**是旧版时也显示 —— 点了必然 404(旧宿主没有这条路由),用户只看到一句「未知接口」;现在按钮只在「端口上那个常驻实例落后于磁盘」时出现,404/405 还会给一条「重启桌面版 / DSH 或执行 dsh-memory restart」的指路。② 独立页(新版)提交整张表单、宿主还是旧版时:宿主按白名单丢掉它不认识的键(如 `secretHint`)→ 回读校验必然对不上 → 连试 5 次 `dropped`,用户只看到「HMR transactions cannot be nested」;现在应用前按宿主 schema **过滤未知键**(宿主升级后自然生效),并把「配置同步失败」与「自动沉淀异常」彻底分开(`config-warn` / `config-fail`,不再翻红沉淀健康状态)。③ `autoRestartOnDrift` 在宿主快照里没有这个键时按**默认开**渲染(实测现场:0.10.6 宿主 + 0.10.7 面板 → 复选框空着,而宿主实际按开处理,面板与真实行为不一致)。新增 `tests/issue-fixes-4.test.mjs`。`npm test` 共 **274 项** |
| **v0.10.6** | 2026-10-07 | **孤儿 web 有了回收机制 + 共享配置同步失败不再静默**。① **孤儿 web**:web 子进程是 detached 启动的,watchdog 被硬终止时(Windows 上 `process.kill(pid,'SIGTERM')` 即无条件终止)它的退出处理器不执行;若这个 web 当初因为目标端口被占而**退让到 port+1…**,登记它的锁条目又随 watchdog 一起消失 —— 于是 8001/8002 这类端口上会常驻没人认领的 web(实测本机就躺着两个,其中一个已跑了 20 小时,而 `status` 只探配置端口,永远发现不了)。现在 `startWatchdog` 启动时、以及 `stop`/`restart`(`checkPort` 路径)都会扫**退让窗口** `[port+1, port+9]` 并收掉它们;三道门缺一不可(端口在窗口内 / 命令行**确实是本插件的 web** / pid 不在锁里任何 watchdog 的 `pid` 或 `webPid` 上),**别人的 `node web.js` 一个都不杀**。另外 `spawnWeb` 发现目标端口上已有自家 web 时**不再重复拉起**(退让实例的主要来源),只交给 tick 探活。② **`syncConfigFile()` 不再静默吞错**:它写的是独立页 / hooks / MCP 共用的读源,写不进去就表现为「配置改了但他们看不到」——旧实现是 `catch { /* 静默 */ }`(实测到过一次:共享文件 mtime 停在激活前,而宿主里的值已经变了)。现在失败会进 stderr 与「自动沉淀日志」的 warn(同一原因只报一次,不刷屏);因为 `logCapture` 定义在 `apply()` 更靠后的位置,日志刻意**延迟一拍**再写,避开 `const` 的 TDZ。新增 `tests/watchdog-orphans.test.mjs`(7 项),`npm test` 共 **245 项** |
| **v0.10.5** | 2026-10-07 | **独立 Web 端的配置保存:不再空头承诺,也不再要求「必须装了 DSH」**。① **宿主无关的直写路径(#21)**:独立端保存后若 800ms 内没人消费 pending 文件、**且**本机没有活着的 DSH 宿主(心跳文件 `memory-eternal-config.host.json` 不新鲜、或里面的 pid 已死),就直接把改动**原子合并**写进共享配置、清掉 pending,响应回 `pendingOutcome: applied-direct` —— 于是 Codex / Claude Code / Cursor、以及只跑 `dsh-memory serve` 的用户,保存当场生效(此前那条路只会写「待应用」等一个**根本不存在**的宿主,界面上那句「下次启动生效」永远不会兑现)。宿主活着时**绝不**直写:共享配置是宿主的派生镜像,绕过它会被下一次 `syncConfigFile()` 盖回去(有专门的反向测试守着这条边界)。② **失败要指路,不只是重复报错(#21)**:识别「宿主守卫类」报错 —— `HMR transactions cannot be nested`(Cordis HMR 宿主)与 `root.events.emit is unavailable from a plugin activation`(dsh-tui 一类)是同一族缺陷的两种措辞 —— 并把「此宿主不允许插件从自己的回调上下文写配置」与「去哪改」(**DSH 设置 → 记忆**,或 profile 的配置补丁层)写进放弃时的报错、诊断信息与独立页提示;`POST /config` 同时回 `hostGuard` / `hint` 字段。③ **界面不再一律弹「已保存」(#21)**:客户端开始消费 `pendingOutcome` —— `queued` / `failed` 用**非成功样式**并说明「尚未确认生效」,`applied-direct` 明确写「本机没有 DSH 宿主,已直接写入共享配置」,只有真生效才显示「已保存」。④ 顺带修掉一个会让宿主/测试**无法退出**的清理竞态:心跳定时器与 pending 文件监听都是在异步 `import()` 之后才建立,若在它 resolve 之前就 dispose,回调就再没人清理(心跳定时器还额外 `unref()`)—— `tests/settings-compat.test.mjs` 用假 ctx 跑 `apply()` 时正是被它挂住的。新增 `tests/issue-fixes-3.test.mjs`(9 项),`npm test` 共 **238 项** |
| **v0.10.4** | 2026-10-07 | **四个「看着正常、实际静默错位」的 issue 集中修复(#21 / #22 / #23 / #24)**。① **宿主守卫误报的保存不再被当成失败(#21)**:dsh-tui 一类宿主的能力守卫会在 `settings.update` **内部**的 `describe()` 上抛 `root.events.emit is unavailable from a plugin activation`,而写入其实已经提交 —— 旧实现只认「有没有抛错」,会把**已生效**的改动重试 5 次后标成 `dropped`、日志里还报失败。新增 `applyPatchVerified`(抛错后**回读确认**,判据 `patchApplied` 浅比较,命中则记一行日志按成功处理),drain 与独立 Web 端共用这条路径;独立 Web 的 `POST /config` 也不再无条件回「一切正常」:等最多 800ms 回读 pending 文件,回 `pendingOutcome: applied / failed / queued` 并给对应措辞(`waitPendingOutcome`)。② **`/cards` 的 status 不再静默错位(#22)**:`status=deleted` 现在真的列**回收站**(与 `/recycle/list` 的数字对得上),未知取值显式回 `unknownStatus: true` 并回显 `appliedStatus` / `requestedStatus`(回落 approved 的老行为保留,但不再沉默)。③ **升级后以「端口上真正服务的版本」为准(#23)**:`/overview` 新增**当前服务进程**加载的 `version`;`dsh-memory status` 单独报「端口实际服务版本」,对不上就直说「升级尚未生效」(锁里的版本号只是 watchdog 自述;端口上是 **v0.10.4 之前**的旧 web 时探不到版本 —— 它的 `/overview` 还没有 `version` 字段 —— 这时会明说「是本插件的 web 但不会自报版本,即旧版」,而不是误报「没有服务在响应」);`stop` 停完再问一次端口占用者、仍被占就告警;`restart` 不再只信锁里登记的 webPid —— 按**端口占用者**兜底收掉锁里没登记的旧 web(先用命令行确认**确实是本插件的 web**(包名 `memory-eternal`,或本插件自己的 `lib/web.js` 绝对路径)才动手,别人的 `node web.js` 只告警不动手 —— 收紧这条判据是刻意的:误杀别人占着该端口的进程,比不杀更糟),端口没空出来就中止重启,起完自检「端口服务版本 = 本机版本」,对不上以非 0 退出(不再打印「已启动」就完事)。④ **蒸馏解析不再把「字符串里的裸控制字符」误判成无法解析(#24)**:模型把 markdown 正文的换行写成真实 U+000A(括号闭合、`looksTruncatedJson` 为 false)时,旧实现必然 `JSON.parse` 失败 → 整卡退成原文卡;新增 `repairJsonControlChars` **只在字符串内部**把裸控制字符转成合法转义(`\n` `\r` `\t` `\b` `\f`,其余 `\u00XX`;字符串外的换行是合法 JSON 空白,一律不动),候选串顺序为「原样 → 截 `{ … }` → 各自的已修复版」;解析失败的消息不再吞掉 `JSON.parse` 原始报错,现场摘要改为**首尾各留一段**(`describeOutputExcerpt`,旧的 `slice(0,160)` 恰好把「被切在 `"body":` 半截处」显示得像输出断了);撞输出上限的重试从「只翻倍一次」改为 `maxTokenLadder` **一路翻倍到 schema 上限**(1200 → 2400 → 4000,不再止步 2400)。新增 `tests/issue-fixes-2.test.mjs`(21 项),`npm test` 共 **229 项** |
| **v0.10.3** | 2026-10-06 | **修复 `audit list` 的计数语义(#20)**。① 总数与 `--limit` **解耦**:`共 N 张` 取**匹配总数**(`countCards`),`--limit` 只限制显示条数,截断时明确写出 `显示前 M 张 … 还有 X 张未显示`。② **默认不再静默截断**(不给 `--limit`、或 `--limit 0` = 全部显示)。③ `--json` 拆分为 `total`(匹配总数)/ `returned`(本次返回)/ `count`(= total 的兼容别名;0.10.2 里它误等于 returned)/ `limit`(null = 全部)/ `hasMore`。④ `--status all` 的语义写进用法与 README:= 审核队列(pending + rejected),**不含**已出队的 approved 与回收中心的 deleted;`approved` 可显式查询。⑤ **同源修复 MCP 的 `memory_audit_list`**:它同样存在「默认 20 静默截断 + 把截断条数报成待处理数」,现改为报匹配总数 + 未显示提示,上限 100 → 500。 |
| **v0.10.2** | 2026-10-06 | **五个存量 issue 集中修复(#15 / #16 / #17 / #18 / #19)**。① **按项目选库在宿主侧终于生效(#15-1)**:宿主过去拿进程 cwd 去匹配 `vaultProfiles[].match.workspace`,而那是「启动 dsh 的目录」——现在从 `session.header.cwd` 取**会话自己的工作区**(capture 与 memory_recall 两条路径都传),CLI/MCP/hooks 行为不变。② **蒸馏不再选错 provider(#15-2)**:声明 `supportsReasoningEffort:false` 的候选不再被传 `reasoningEffort`(能力未知一律按支持处理),并把它们排到支持者之后但**不删除**,避免兜底全无。③ **输出截断不再伪装成解析失败(#18)**:新增独立错误码 `MAX_TOKENS`(含实际上限值),撞上限自动以双倍上限重试同一候选;兜底上报改为**始终以第一候选为主因** + 其余错误按码汇总(不再被最后一个候选覆盖);默认 `captureMaxTokens` 900 → **2000**;失败兜底的原文卡加 `distill-failed` 标签便于过滤。④ **独立 Web 端保存配置不再静默丢失(#16)**:drain 报错不再被 `catch {}` 吞掉(进 stderr + 自动沉淀日志,5 分钟去重节流),共享配置 `memory-eternal-config.json` 改为 **tmp + rename 原子写**(原先非原子写会在截断窗口内被独立 web / MCP 读到半截内容 → JSON.parse 失败 → 设置静默回落成默认值),重试耗尽后**保留 pending 文件**并标注 `dropped:true` + `lastError`(不再删除用户改动),失败状态进 `/diagnostics` 与 `/config` 响应。⑤ **审核中心有了 CLI(#17)**:`dsh-memory audit list/approve/reject`(多路径批量、`--json` 便于 jq 筛选、复用同一套 `setCardStatus` 守卫与 `audit_log`);MCP 侧只新增**只读**的 `memory_audit_list`,不暴露 approve/reject。⑥ **看门狗有了生命周期(#19)**:新增 `dsh-memory status/stop/restart`,锁文件写入 `pkgVersion`(status 会提示「常驻实例是旧版」),宿主日志按抢锁结果如实打印 `spawned` / `delegated to existing pid`(不再每次都谎报 spawned),关闭 `watchdogAutoSpawn` 时明确提示既有实例仍在跑;并且 `dsh-memory stop` 会**连同该 watchdog 拉起的 web 一起停**(Windows 上 `process.kill(pid,'SIGTERM')` 是无条件终止,watchdog 的退出处理器根本不会执行 —— 旧写法会在 stop 后留下一个占着端口的孤儿 web;web 子进程 pid 现在记在锁里,杀之前还会用进程命令行确认确实是 `web.js`,防 pid 复用误杀);README 修正「默认值自相矛盾」与「重启即可关闭」的错误承诺。 |
| **v0.10.1** | 2026-10-02 | **修复「导出 OK、导入 0 张」**。根因:客户端「导出JSON」写出的是**裸数组**,而 `/import` 只读 `payload.cards` —— 裸数组被静默当成空备份(`ok:true` / `imported:0` / `skipped:0`),UI 只显示「导入完成:0」,看不出是格式不匹配。① `/import` 现在同时接受**裸数组**(v0.10.0 及以前的备份)与**信封对象** `{format, formatVersion, exportedAt, count, cards}`;形状不认识 → **400 + 人话原因**,空文件 → `warning`,坏 JSON → 原有的解析失败提示;② 导出改为**带信封**(老版本只认 `payload.cards`,信封对老版本也互通)并保留每张卡的 `status` / `store`;③ 导入去重基线改为**导入前快照**(`writeCard` 新增 `dedupAgainst`)—— 否则同一份备份里互相近似的卡会在写入过程中互相判重、被自己人吞掉一批(备份是真相源);④ 导入应答补 `total` / `skipped` / `quarantined` / `failed[]`(含每条未导入原因),提示语改为「导入完成:N 张 / 文件共 M 张 · 待审核 K · 跳过 D」,一张都没进来时按**失败样式**提示;⑤ 新增 `tests/import-roundtrip.test.mjs`(6 项:裸数组 / 信封 / 导出→导入全量往返 / 重复导入如实报重复 / 坏形状 / 审核守卫不可绕过)。⑥ **装坏了也要说人话(issue #14)**:从插件市场装出来的副本可能缺 `web/` 静态资源,那时侧边栏「记忆」只会弹一页 `{"ok":false,"error":"ENOENT …"}` —— 现在缺 `index.html` 但 `app.js` 还在就用**内置外壳**把界面拉起来(自救),缺 `app.js` 则回一段把诊断画进 `#root` 的 JS + 一页含「缺哪个文件 / 绝对路径 / 包版本 / 怎么重装」的说明;两种缺失都写进自动沉淀日志(`app.js` → `fail` 健康态亮红、只缺 `index.html` → `warn`),宿主启动时也自检一次(判据与措辞由新增的 `lib/web-assets.js` 统一,附 `tests/web-assets.test.mjs` 6 项)。⑦ 顺带修 `appendCaptureLog` 在 `DSH_HOME` 目录不存在时静默失败(诊断信息连一条都留不下)。⑧ **DSH 兼容范围放宽到 `>=0.1.5-alpha.2 <0.3.0-0`**:旧上界 `<0.2.0` 按 semver 会把 DSH `0.2.0-rc.x` 排除在外(DSH 守卫用 `includePrerelease:true`,实际装得上,但声明与事实不符)——现在覆盖整个 0.1/0.2 线,`<0.3.0-0` 仍挡住 0.3.0 及其预发布;`dsh.compatibility` 同步补 `"0.2.0-rc.2": "compatible"`(本机在官方桌面版 0.2.0-rc.2 上实测运行)。⑨ **修三个第三方插件清单长期非法 JSON**:`.claude-plugin` / `.codex-plugin` / `.cursor-plugin` 的 `description` 收尾引号被双重编码吃掉(最后改动停在 v0.9.23),DSH 不走这三个文件所以一直没暴露,但 Claude Code / Codex / Cursor 侧安装**必然解析失败**;现已重写为合法 UTF-8 JSON、`version` 跟到 0.10.1,并新增**随包体检守卫** `tests/manifests.test.mjs`(3 项:随包 JSON 全部可解析 / 三清单 `name`+`version` 与 package.json 一致 / 随包文本无编码事故乱码)。实测用户 **661 张真实备份(裸数组、3.9MB):修复前导入 0 张 → 修复后 661 张**(654 进主库 + 7 进隔离区)。`npm test` 共 **187 项** |
| **v0.10.0** | 2026-09-30 | **两套存储:未审核内容物理隔离**。① `cards`(主库)只存 `approved`,新增 `quarantine`(隔离区,含 `quarantined_at` / `quarantine_reason`)存 `pending`/`rejected`/`deleted`;② 召回/检索/图谱/去重/导出等读路径**不再需要状态条件**(查主库即安全),根治 v0.9 审计出的三处绕过审核漏洞(去重池把新知识写进待审卡、`readCard` 直读未审核正文、`/card` 无状态校验);③ 审核流转改为**跨表搬家**(事务内完成,id 由新增的 `card_sequence` 单一发号器分配,`card_updates` 随卡改绑);④ 首次启动**自动迁移**存量非 approved 卡(不删内容,幂等);⑤ 新增 `checkMainStoreInvariant()` 不变量体检与 `tests/two-store.test.mjs`(15 条);⑥ 入料噪声闸门:剥离 DSH 运行时注入(环境快照 / team 广播 / teammate 原文 / 工具说明),挡住碎片卡与糊标题;⑦ 修 `settings-compat` 在 Windows 上的原生崩溃(`fs.watch` 父目录 → 改监听文件) |
| **v0.9.23** | 2026-09-30 | **保存不再闪烁**。保存成功后客户端会立刻重拉 `/config`,而宿主 volatile 回流是滞后的 → 输入框会「先退回原配置、再跳回修改后的值」。现在客户端把「已保存但宿主未回显」的字段做**本地叠加**(新增 `src/client/config-merge.js` 纯函数 + 4 项单测),宿主回显一致后自动摘除,之后仍跟随宿主权威值;重置表单会清空叠加层。`npm test` 共 **139 项** |
| **v0.9.22** | 2026-09-30 | **配置准即时同步(SSE 推送 + 文件监听)**。① 新增 SSE 推送中心 `lib/sse.js`:DSH 内嵌宿主与独立 Web 宿主都暴露 `/memory-eternal/api/events`,配置一变更就推给所有已打开的页面,客户端订阅后自动重载 —— **正在编辑时不覆盖你的输入**(dirty 标记)。② 独立页保存 → DSH 应用:从 5 秒轮询升级为 `fs.watch` **毫秒级**触发(5 秒轮询保留为兜底)。③ 独立页监听共享配置文件:DSH 端一保存,独立页立刻同步。新增 `tests/sse.test.mjs`(5 项),`npm test` 共 **135 项** |
| **v0.9.21** | 2026-09-30 | **保存成功不再被误读为失败 + 配置页排版修复**。① 保存成功一律用**成功样式**提示(原来「已写入;宿主尚未回流」被弹成红色失败样式,用户以为保存失败),措辞改为「已写入配置文件;宿主仍是旧版,重启 DSH 后即以新值为准」;② 配置页字段网格列宽 150/160px → **200/220px**、间距加大、长中文标签允许换行,勾选框与说明文字顶端对齐,字段不再互相挤压。 |
| **v0.9.20** | 2026-09-30 | 独立 Web 页补**取值范围校验**:`/config` 镜像 Config schema 的 min/max(如 `recycleRetentionDays` 1–3650、`webPort` 1–65535),越界直接 **400** 并说明「需要 1–3650,当前 0」。至此「数字框清空 / 越界」这条最常见的保存失败链路在**客户端 + 独立页 + 宿主**三层都被拦住并给出字段级原因。 |
| **v0.9.19** | 2026-09-30 | **「保存失败」不再无信息量(根因:数字框清空=0 被 schema 拒绝,宿主回空 500)**。复现与实测:越界值 / 清空数字框 / 类型错误 → DSH 宿主返回 **HTTP 500 + 空响应体** → 客户端 `r.json()` 抛错 → 只能显示通用「保存失败」。修复:① **数字输入框清空不再静默变成 0**(JS 里空串转数字得 0,而 `recycleRetentionDays` 的 schema 是 `min(1)`,直接失败);② 保存前按 schema 范围**逐字段自检**,指名报错(例如「recycleRetentionDays」需要 1–3650 之间的数字,当前 0);③ 响应体兜底解析:非 JSON / 空响应体时显示 HTTP 状态码与原始片段,而不是「保存失败」;④ 宿主 POST 外层兜底,**绝不返回空响应体**,并对空字符串数值字段前置返回 400 + 字段名;⑤ 独立页 /config 增加类型与范围校验(拒绝空数值、类型不符),避免写入非法「待应用」值;⑥ `drainPendingConfig` 增加失败计数,连续 5 次失败后放弃并删除(不再每 5 秒无限重试)。`npm test` 共 **130 项** |
| **v0.9.18** | 2026-09-30 | **独立 Web 页终于能保存配置 + 只读状态不再「毫无反应」**。根因(issue #12 的另一半):独立 web server 进程**没有 DSH 的 settings 服务**,它的 `/config` 过去固定返回 `writable:false`,客户端 `save()` 第一行就 `if (readonly) return`,按钮还是 `disabled` —— 所以点保存**一点反应都没有**,与插件版本无关。修复:① 新增共享文件协议 `lib/config-sync.js`:独立页保存 → 原子写 `memory-eternal-config.pending.json` → **DSH 端激活时与每 5 秒轮询应用**(`settings.update(patch, undefined)` 跳过乐观并发校验),成功后删除,失败保留重试;DSH 没运行时改动留在文件里,下次启动生效。② 独立页 `/config` 返回 `writable:true, viaPending:true`,页面顶部显示蓝色说明「保存会写入待应用文件,由 DSH 自动同步」。③ **只读时按钮不再禁用**:点击必弹出明确原因(`🔒 只读` + 指引「桌面版 → 设置 → 记忆」),彻底消灭「按了没反应」。新增 `tests/config-sync.test.mjs`(4 项:读写往返、损坏内容容错、drain 成功即删/失败保留、clear 幂等)。`npm test` 共 **125 项** |
| **v0.9.17** | 2026-09-30 | **「更新了却还是旧界面」根治**。① 构建期把插件版本注入客户端 bundle,配置页新增「**页面脚本 vX**」徽标;② 当**页面脚本版本 ≠ 磁盘版本**时醒目提示「⚠ 当前页面仍在运行旧版脚本(页面 v{page} / 磁盘 v{disk}):**按 Ctrl+F5 刷新页面即生效**」;当「运行中(宿主加载)」落后于磁盘时,提示语也改为「先刷新页面,宿主半未更新才需重启桌面版 / DSH」。起因:issue #12「保存配置没有任何反馈」的真实原因之一,就是页面里跑的还是更新前加载的旧 JS —— 宿主已设 `Cache-Control: no-store`,但**已打开的页面不会自动重新加载脚本**,不刷新永远是旧的。另:独立 web 服务(`:7999`,9/29 启动的旧进程)已重启到新版并验证 `loaded=onDisk=latest` |
| **v0.9.16** | 2026-09-30 | **修复配置保存不生效(issue #12)+ 配置项全覆盖**。① **保存链路**:宿主 `dsh-settings.write()` 要求「面板持有的 revision 与 describe **完全一致**」,revision 一过期就抛 `SettingsConflictError` 导致保存失败 —— 现在遇到冲突会自动取最新 revision **重试一次**(提示里标 ♻)。② **宿主 volatile 回流滞后**:写成功后把改动叠加在本地视图(面板重开立刻是新值),等宿主快照追上再自动摘除;宿主 POST 新增**写入回读校验**,未回流时返回 `pending` 并提示「已写入,重启 DSH 后以配置文件为准」。③ **保存即时反馈**(你此前反馈过):保存按钮旁直接显示 ⏳ 保存中 / ✅ 已保存 / ⚠ 错误,并标明是否重试/是否回流;按钮文案修正(原来误显示「导出中…」)。④ **配置全覆盖**:补齐 7 个以前只能手改配置文件的字段 —— **总开关 `enabled`**、单库目录 `vaultDir`、蒸馏路由 `captureProvider` / `captureModel`、沉淀冷却 `captureCooldownMs`、语义召回 `recallEmbedding`、会话预算 `sessionBudgetChars`;宿主 `/config` 补 6 项、独立 web `/config` 补 9 项。⑤ 新增**永久守卫测试**:`tests/config-coverage.test.mjs` 保证每个 Config 字段都被「宿主 API / 独立 web API / 配置页」三处覆盖(漏一个就红),`tests/settings-save.test.mjs`(5 项)钉死 revision 重试、回流兜底与「错误不吞」。`npm test` 共 **121 项** |
| **v0.9.15** | 2026-09-29 | **只带服务器侧图谱提速(渲染与 v0.9.13 完全一致)**。① **建图落盘缓存**:按指纹缓存 nodes+edges 到 `<vault>/memory-eternal-graph-cache.json`(原子写、指纹失效、超 8MB 不缓存、写失败只影响下次重建),596 卡实测冷启动 **422ms → 11ms**;② **bigram 哈希化 + 稀有 gram 计数排序**:`bigramHashSet()` 把 bigram 压成 32 位整数入 `Set<number>`(**写卡去重仍用字符串版,判定语义不变**),稀有倒排改按 df 计数排序(202 → **54ms**);③ **MinHash + LSH 候选生成**(48 维签名 / 24 band,与稀有倒排取并集后**精确校验**):jaccard 调用 15,148 → 6,797,相似度段 210 → 130ms。全量冷建图 **600 → 422ms**,边数与 v0.9.13 一致(tag 2777 / similar 4)。**客户端零改动**:不做精灵/静态层/骨架降级(v0.9.14 那三项因影响手感已回退并 deprecated)。新增 `lib/minhash.js` + `tests/minhash.test.mjs`(4 项),`npm test` 共 **108 项** |
| **v0.9.13** | 2026-09-28 | **按用户反馈修 7 项**。① **版本跟踪**:配置页「插件信息」同时显示**运行中(宿主加载)/ 磁盘安装 / npm 最新**三个版本;磁盘比运行中新时给橙色警告(「磁盘已安装 X,但当前进程仍加载 Y —— 重启后才生效」),并新增「🔄 检查更新」(服务端查 npm,10 分钟缓存 + 4s 超时)。② **复制失败**:记忆弹窗在 iframe 里、默认没有 clipboard-write 权限,`navigator.clipboard.writeText` 必然被拒 —— 现在先试 Clipboard API,失败退 `document.execCommand('copy')`,再失败就把内容摊进只读文本框并给「全选」;iframe 加 `allow="clipboard-write; clipboard-read"`。③ **保存没有提示**:保存结果显示在保存按钮旁(⏳ 保存中… / ✅ 已保存 / ⚠ 错误),文案从错误的「导出中…」改为「保存中…」。④ **回收中心**:恢复 / 彻底删除 / 清空都有成功与失败提示,空库给说明与禁用态。⑤ **通用按键反馈**:审核中心的批准 / 驳回 / 删除原先 `catch{}` 吞掉一切,现逐条统计成功失败并提示;未选卡片也提示。⑥ **图谱缩放卡顿**:新增 `src/client/graph-lod.js` —— **视口剔除**(屏幕外节点/边不画)+ **标签预算**(≤120 全画 / ≤300 画 160 / ≤600 画 90 / 更多画 60,优先焦点、悬停、搜索命中、高度数)+ **文本宽度缓存** + **大图关阴影**。⑦ 新增 `tests/graph-lod.test.mjs`(5 项),`npm test` 共 **108 项** |
| **v0.9.12** | 2026-09-28 | **新增「反馈异常」入口(左栏 🐞)**:弹窗里填完问题描述后可 ① **一键打开 GitHub 预填 issue**(标题 / 正文 / 诊断信息 / `bug` 标签都已填好,再点一次 Submit 即提交),或 ② **复制一段给 AI 的提示词**(贴进对话框,让 AI 先用 `gh issue list` 查重、再建 issue 并回报链接)。**为什么不做「点一下直接提交」**:GitHub 不允许匿名创建 issue;插件内置 token 会被反编译提取;OAuth / 中转需要自建服务 —— 所以走「预填 + 一次点击」。诊断信息由新接口 `/memory-eternal/api/diagnostics` 生成并**自动脱敏**(home 目录 → `~`,`sk-` / `ghp_` / `github_pat_` / `api_key:` 等 → `***`,截断到 2400 字符),issue 链接用**二分截断**保证不超长(`URLSearchParams` 会把中文膨胀约 9 倍)。新增 `lib/feedback.js`(纯函数层)与 `tests/feedback.test.mjs`(4 项:脱敏 / 诊断字段与限长 / 预填链接编码与截断 / 中英双语提示词),`npm test` 共 **103 项** |
| **v0.9.11** | 2026-09-28 | **修复 `watchdog --reap` 把自己杀掉**:直接运行 `node lib/watchdog.js --reap` 时,调用者自己的命令行同样含 `watchdog.js` + 同端口、且不在锁里 —— reap 于是把**自己** SIGTERM 了,表现为「一条输出都没有 + 退出码 1」(走 `dsh-memory watchdog --reap` 时因命令行不含 `watchdog.js` 而侥幸没事,所以只在直跑时暴露)。现在保护集合固定包含 `keepPid` / 当前 pid / 父进程 pid,并跳过命令行带 `--reap` 的其它进程;输出改成 `fs.writeSync(2, …)` 同步写,不再被 `process.exit` 截断。回归测试补「自己与父进程都不得被杀」(用真实空转子进程当靶子,不再用假 pid)。`npm test` **98 项** |
| **v0.9.10** | 2026-09-28 | **多库与看门狗收尾 + 图谱帧优化**。① **多库(#10)**:`vaultProfiles` / `activeVault` 终于有了配置页表单(增删库、填目录、选激活库,并显示当前生效目录 / 来源 / workspace);vault 解析抽成 `lib/vault-resolve.js`,宿主与 CLI / MCP / hooks / 独立 web / sweep **共用同一份**,新增 `vaultProfiles[].match.workspace` **按项目目录自动选库**(最长前缀优先、opt-in,默认仍是单库);`memory_recall` 增加 `scope` 参数(库名 / 路径前缀 / `all` 跨库聚合),跨库结果标注来源库。② **watchdog(#11)**:锁改为**按端口分槽**(多端口 / 多 vault 并存不再互相覆盖),新增 `dsh-memory watchdog --reap` 回收升级前遗留的孤儿进程(只清同端口、且不在锁里的;锁中活跃实例与用户手动起的都不动)。③ **图谱帧时间**:边改为按「颜色 + 透明度 + 线宽」分桶后用 **Path2D 一次描边**(2.7k 条边从 2.7k 次绘制调用降到几个桶),节点径向渐变改为**按 颜色\|半径 缓存 + translate 复用**(593 次/帧 → 首帧之后基本不再创建)。④ 新增 `tests/vault-resolve.test.mjs`(7 项)、看门狗测试扩到 7 项;`npm test` 共 **98 项** |
| **v0.9.9** | 2026-09-28 | **修复侧边栏「记忆」入口被挤出可视区(issue #2)**:槽位宿主有两种形态 —— 壳自己的横向行容器 `.footerActions`,以及被其他插件(`dsh-diff-approval` / `dsh-footer-order` 等)改成 `flex-direction: column` 的 `div[data-slot="sidebar.footer.action"]`。旧 CSS 对两者都套了 `flex-wrap: wrap` + `.me-footer { flex: 1 1 100% }`:纵向容器里 wrap 的语义是「换列」、`basis: 100%` 指的是**高度 100%**,叠加后按钮被挤进右侧溢出列、裁切到只剩右边缘一条缝。现在 `flex-wrap` 只留给横向 `.footerActions`,`.me-footer` 改用方向无关的 `flex: 0 0 auto`,并新增 SSR 回归断言。另:README 双语新增「交流群 & 打赏」(群二维码 + 微信收款码 / 赞赏码),新增 `SPONSOR.md` 与 `.github/FUNDING.yml`(FUNDING 只支持文字链接,故 custom 第一项指向 SPONSOR.md 页面直接展示二维码) |
| **v0.9.8** | 2026-09-28 | **集中解决存量问题**。① **采集可信性**(issue #3 / #4):蒸馏路由不再写死 `providers[0]` —— 新增 `captureProvider` / `captureModel` 配置,并按注册顺序**依次重试**;**流末尾 `finish` 块里的适配器错误**(缺凭证 `MISSING_CREDENTIAL`、限流等)现在成为**可见失败**(沉淀日志写明 code/message + 健康态亮红 + 兜底原文卡注明原因),不再笼统记成「蒸馏无输出」;停滞探测从「累积计数差额」改为**滑动窗口**(20 分钟内持续开始且零收尾才告警),误报与漏报一并修掉。② **独立进程 vault 一致性**(dsh-memory-eternal#5):CLI / MCP / hooks / 独立 web / sweep 与宿主共用同一套优先级(`MEMORY_VAULT_DIR` → `activeVault` → `vaultDir` → 默认库),切库后不再数据分裂。③ **看门狗单例锁**(dsh-memory-eternal#6):同端口已有活着的 watchdog 时新进程直接退出并清理陈旧 pid 记录,止住「每次重启多一个孤儿进程」。④ **图谱性能**:标签边改「共享标签数 Top-K」(593 卡实测 **24,193 → 2,763** 条边)、相似度改**稀有 bigram 倒排候选**(冷启动 **948 → ~520ms**,warm 2-4ms)、payload **3,531KB → 579KB**;客户端 dim 判定改**邻接 Set**(每帧 **52ms → <1ms**)。⑤ 新增 5 个测试文件(路由/finish、停滞窗口、vault 解析、看门狗锁、图谱性能基线),`npm test` **85 项** |
| **v0.9.7** | 2026-09-28 | **打开卡片默认全部展开(可收起)+ 正文排版重做**:卡片阅读器不再默认折成 300px,而是**打开即读全文**,长卡可一键收起(折叠后底部给「展开 ⌄」提示)。frontmatter 从正文里抽出来,改成 kind / 审核状态 / 标签 / 时间 / 来源的**彩色 chips**(kind 沿用图谱配色),不再把一段 YAML 当正文露出来。正文排版换成新方案(`src/client/markdown.js`,宿主与独立 web 共用同一套):小标题按语义自动配 **emoji + 颜色分组**(结论 ✅绿 / 风险 ⚠️琥珀 / 根因 🔍蓝 / 方案 🛠️紫 / 示例 💡粉),连续 `- ` 合成真正的 `<ul>`、`1.` 合成带圆形序号的 `<ol>`、`- [x]` 变复选框、`>` 引用升级成带 emoji 的 callout、`---` 变分隔线、行首「短键:值」渲染成对齐样式、`**加粗**` 加荧光笔下划线;围栏代码整段保护,内部 `#` / `-` 不再被误排版。**新增 7 项排版单测**(含 XSS 转义、链接协议白名单、散文冒号不得误判成 key/value)。另含 PR #8 移植:Electron 宿主下的子进程 node 解析(`lib/node-bin.js`,修「独立 web server 拉不起来 → 侧边栏弹窗白屏」)与 `webServer` 就绪后再注册 API 路由(修「设置页永远加载中」) |
| **v0.9.6** | 2026-09-28 | **修复官方桌面版全屏浮层挡住窗口控制面板(点「关闭」会退出整个桌面壳)**:桌面壳把「最小化 / 最大化 / 关闭」画在页面顶部,并按 DSH 约定在 `<html>` 上打 `data-windows-titlebar` + 给出 `--dsh-windows-titlebar-height`(已在官方桌面版 `app.asar` 里确认这两个标记确实存在);记忆库全屏浮层原本是 `inset:0` + 内联 `100vh`,整条盖住标题栏,而我们右上角的「×」恰好压在窗口「关闭」上 —— 用户一点就直接退出整个桌面壳。现全屏态改用 `.me-modal-full` 类(不再写死内联 `100vh`:内联样式无法被 CSS 覆盖),并在 `[data-windows-titlebar]` 下把 `.me-overlay-top` / `.me-overlay` / `.me-modal-full` 一律下移 `--dsh-windows-titlebar-height`、高度改为 `calc(100vh - 该高度)`;浏览器里没有该属性时偏移 0px,行为与之前完全一致。导出图谱的全屏预览同步从 `100vh` 改为 `100%`,避免在缩短后的浮层里溢出。新增 1 项渲染回归断言(三处让位规则必须出现在打包产物中) |
| **v0.9.5** | 2026-09-28 | **修复官方桌面版(schemastery 3.18.4)下插件整体挂不上**:桌面版 profile 解析到的是 **schemastery 3.18.4**,它会把标了 `volatile` 的 Config 字段解析成 cosmokit 的**活引用**(`{ get(): snapshot }`,品牌 `Symbol.for('cosmokit.volatile.write')`),而 0.9.4 的业务代码把字段当普通值用 —— `cfg.vaultDir.trim()` 当场抛 `TypeError: cfg.vaultDir.trim is not a function`,插件在 `apply` 里就崩(桌面版实测报错原文)。现按 cosmokit 协议统一**深解引用**(引用取快照、数组/对象递归展开),`settings.get()` 对外只给普通值;每次读取都重新解,loader 原地提交的 volatile 热更新照样立刻可见,`JSON.stringify` 写共享配置也不再变成 `{}`。新增 2 项回归(3.18.4 真实引用形态 + 嵌套数组/对象),并用**桌面版真实模块解析布局**(schemastery 3.18.4 + 真 cosmokit)跑通 `apply` 验证 |
| **v0.9.4** | 2026-09-28 | **适配 DSH v0.1.7-rc.2(修复「升级后插件整体失联」)**:① **设置服务换血** —— 0.1.7 的 `ctx.settings` 只剩表单 API(`describe/update/replace/mutate/configure`),不再有 `register()`;旧代码 `ctx.settings.register(...)` 直接抛 `TypeError`,插件根本挂不上(记忆页消失、自动沉淀停摆)。现改为跨版本兼容层:旧版仍走 `register`,新版读 loader 传进来的**活配置引用**、订阅 `loader/volatile-update` 做热更新、写回走 `settings.update(条目 id, patch, revision)`(revision 冲突映射 HTTP 409),并注册 `configure({ auto:false })` 免得宿主再生成一张重复的表单页。② **字段 volatile 标记改为构造后统一打**:不再链式写 `.volatile()` —— 该方法 schemastery ≥3.18.4 才有,而插件在 profile 层实际解析到的是 **3.18.1**,链式调用会在 `import` 期抛 `volatile is not a function`,把插件整个打挂。③ **客户端去死引用**:`inject` 移除已随 `@deepseek-ai/dsh-client-runtime` 一起消失的 `settingsScope`(0.1.7 客户端服务表里已无此服务,插件会永远等待服务而永不激活 → 记忆页与侧边栏按钮全部不见);`dsh.client.inject` 同步去掉不存在的包名。④ 新增 **`tests/settings-compat.test.mjs`**(8 项:0.1.7 形状挂载 / 活引用读值 / volatile 热更新 / 写回与 revision 冲突 / 老版本 `register` 路径 / 字段 volatile 标记),已并入 `npm test`。已在**真实 DSH v0.1.7-rc.2** 上启动验证:宿主插件正常挂载(配置落盘 `memory-eternal-config.json`)、客户端 bundle 正常进入 boot graph(`plugins/??memory-eternal/client.js`) |
| **v0.9.3** | 2026-09-12 | **修复记忆页白屏("没画面")**:v0.9.1 把「滚动续拉」的 `useEffect` 写在了 `loadCards` 定义**之前**——React 在渲染期求值依赖数组,触发 TDZ(`Cannot access 'loadCards' before initialization`),**整个记忆页一渲染就崩**;现已移到定义之后。**新增渲染冒烟测试**(`tests/ssr.test.mjs`:真跑打包产物、把卡片/设置/用量/图谱四个入口各渲染一遍,已并入 `npm test`)——「构建全绿、页面白屏」这类 bug 以后发不出去 |
| **v0.9.2** | 2026-09-12 | **日志可见性 + 误报修复**:自动沉淀日志**落盘持久化**(`~/.dsh/memory-eternal-capture.jsonl`,最多 500 条)——独立 Web 页不再显示「日志仅在 DSH 宿主内可用」而是直接能看到记录,宿主重启后历史也不丢(启动时读回);修复**停滞探测误报刷屏**(轮次计数对不上其实是常态:子代理轮次、被取消的轮次都不发 `turn-stopping`;现在要求「有轮次在跑 **且** 15 分钟无任何沉淀活动」才报一次,活动恢复即自动撤销,不再每 10 分钟刷一条把面板挤满、健康状态永久亮红);蒸馏无输出/JSON 解析失败时**改为原文卡兜底**,内容不再白丢 |
| **v0.9.1** | 2026-09-12 | **性能与体验批次**:记忆页按视图**懒加载**(打开「设置」不再顺带拉 250KB 卡片 + 890KB 图谱,首屏只需约 0.5KB);知识图谱生成 **3232ms → 43ms**(bigram 集合只算一次 + 数学上界剪枝无损跳过 + 标签倒排),并加**指纹精确失效**的服务端缓存(写卡/审核/删卡立即重建,无脏读);全站 **brotli q5**(图谱 877KB → **54KB**,比 gzip 再小 34%;卡片 246KB → 83KB → br 20KB),大响应体同时压 br + gzip 取更小者,保证任何响应都不比 gzip 差;`/cards` 支持**真分页**(offset/limit/total + 服务端标题排序,跨页全局有序),首屏只拉 100 条、滚到底续拉;「用量/今日」的 Agent 卡清单改为服务端署名过滤(30 条);**审核红线**:插件在 Agent 上下文注入「禁止调用 approve/reject 代用户审批」;watchdog **回收备用端口的多余实例**(实测清掉一个常驻 76MB 的重复 web 进程) |
| **v0.9.0** | 2026-09-12 | **修复自动沉淀静默失效(一连 6 天不写卡)**:DSH 升级后会话事件接口由 `events` 改为 `ownEvents()/snapshotEvents()`,监听器读不到事件后静默返回、不留任何痕迹 —— 现改为三级自适应取法(`ownEvents()` → `snapshotEvents()` → `events`)+ 每会话接入留痕;日配额改为**只统计真实写卡**(此前「不值得保存」的判定也照样扣配额,40 次即耗尽 24h 配额)、`captureCooldownMs` 冷却真正生效、单次沉淀输入截尾 20000 字;新增**自动沉淀日志**面板与**异常主动提示**(页面红条 + 注入 Agent 上下文 + 轮次收尾停滞巡检);移除「每日回顾」自动简报(定时器 / `/todayBrief` 接口 / UI 按钮) |
| **v0.8.0** | 2026-09-06 | **全套 UI i18n**:跟随 DSH 系统语言实时切换(中/英),覆盖知识卡 / 知识图谱 / 审核中心 / 配置面板 / 新建卡模板;Windows 测试收尾 SQLite 连接 EBUSY 修复 |
| **v0.3.1** | 2026-09-03 | 知识图谱按需加载(点击时 fetch,关闭时注销) |
| **v0.3.0** | 2026-09-03 | **SQLite 化存储层**(`node:sqlite` 零依赖)+ 数据库层审核守卫 `enforceAudit()` + `audit_log` 审计日志 + 自动从 .md 迁移 |
</details>
| v0.2.0 | 2026-09-03 | recall 含正文修复(`search()` 返回 excerpt 字段) |
| v0.1.2 | 2026-09-03 | 审核守卫加固(`writeCard` 默认 `pending`,`mergeCards` 走审核) |
| v0.1.1 | 2026-09-03 | recall 含正文修复(excerpt 字段 + body 逻辑修正) |
| v0.1.0 | 2026-08-31 | 首发:自动沉淀 + 自动召回 + 图形化知识库 + 知识图谱 + 审核中心 + 回收中心 |
</details>
---
## 💬 交流群 & 打赏
**遇到问题、想提需求、或者想聊聊 DSH 插件开发 —— 欢迎进群:**
<img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/group-qr.jpg" width="260" alt="DeepSeek Harness 交流群二维码" />
**如果这个插件帮你省了时间,可以请作者喝杯咖啡(两个码任选其一):**
| 微信收款码 | 微信赞赏码 |
|---|---|
| <img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/wechat-pay.jpg" width="230" alt="微信收款码" /> | <img src="https://raw.githubusercontent.com/EternalNight996/memory-eternal/main/assets/support/wechat-reward.jpg" width="230" alt="微信赞赏码" /> |
> 打赏完全自愿,不影响任何功能;插件本体始终开源免费(MIT)。
> 仓库页的 **Sponsor** 按钮下拉里选 custom 第一项,会落到 [SPONSOR.md](SPONSOR.md)(同样是这一页的码)。
---
## 📄 License
MIT
---
> **让 AI 真正记住你:对话自动沉淀,知识随手可查。** ⭐ 觉得有用就点个 Star,Let's make AI not forget.
>
> English README: [README.en.md](README.en.md)
连接信息
猜你喜欢
暂无推荐服务器