# 变更记录 本文件记录每个**打过标签**的版本。 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/), 版本号遵循[语义化版本](https://semver.org/lang/zh-CN/)。 **怎么钉版本**(避免"装到一半上游改了"): ```sh dsh plugin --profile <你的profile名> add github:eighteentang/dsh-plugin-memory-lite#v1.8.0 ``` > ⚠ **1.5.0 / 1.6.0 / 1.7.0 没有标签,而且是故意不补的**(2026-10-13 核实): > 这三个版本号的实现落在同一个提交(`db92c6e`)里,而那棵树的 `package.json`、 > `lib/tools.js`、`CHANGELOG.md` **带 UTF-8 BOM** —— **BOM 会让插件启动即崩** > (`JSON.parse` 直接抛;2026-10-12 真机实测过,修复见 1.5.0 之后的 `5117640`)。 > 给它们打标签 = 发布一个**装不上**的版本,所以直接 **v1.4.0 → v1.8.0**。 > 1.5~1.7 的功能(人设/规范两个文本框、改成插件自己的文件、用户级跨工作区库) > **全部包含在 v1.8.0 里**。 > > 这是仓库的 pre-push 钩子(`check-plugin-clean.mjs`)拦下来的 —— 它对新推的每棵树都查 > BOM / 凭据 / 本机路径 / 不该进包的文件。**它是这道闸门,不是障碍。** --- ## [1.8.0] - 2026-10-13 ### 新增:备份/恢复 + 迁移前备份**闸门**(阻塞式) 原来迁移前的那次备份是 `void backupBeforeMigration(...)` —— **既不阻塞、也无声**: 备份失败了迁移照样跑。而此刻正要迁移的库,恰恰是"迁移出问题就回不去"的那个。 - **`lib/backup.js`(新模块)**:创建备份(SQLite `VACUUM INTO` 语义,失败不留 `.partial` 半成品)、 校验备份文件、列出备份、保留数量轮转(默认 7)、恢复(就地/指定目录,`force` 需显式)。 - **闸门**:迁移前**同步**执行备份;**备份失败 = 拦住迁移**(`openMemory-BLOCKED-no-backup`), 并写 `pre-migration-backup {ok:false, reason}` 到加载日志。 `autoBackup !== true` 时闸门放行,但**记下原因**(用户显式选择不备份 ≠ 静默跳过)。 - 备份目录默认 `$DSH_HOME/memory-lite/backups/`,日志 `$DSH_HOME/memory-lite/backup.log`。 ### 修复:脱敏漏了两类凭据 —— 而敏感分类器正好依赖它 敏感分类从"关键词猜"改成**看精确信号**:写入时 `redactSecrets` 会把真凭据换成 `[REDACTED:xxx]`,正文里出现这个标记 = **真的含过**凭据(比"提到密钥这个词"准得多)。 暴露出的问题是:分类器依赖写入侧先脱敏,**写入侧漏了,分类器就跟着漏**。 验收实测漏报的两类,已补进 `SECRET_PATTERNS`: - **PEM 私钥块**(`-----BEGIN ... PRIVATE KEY-----`)—— 原来一条都拦不住; - **截断的私钥**(只有 BEGIN 头,复制一半/日志被截/模型输出被切)—— 半个私钥照样是秘密; - 另补 `password: xxx` 这类**无特征前缀**的赋值写法。 **判据**:脱敏是分类的前置条件,两者必须覆盖**同一批形态**。 ### 新增:检索算法版本号 + 反馈闭环接线 - `RECALL_ALGO_VERSION = 'v4.1'`(`lib/config.js`):**任何会改变召回结果的改动都要 +1**。 没有它,反馈数据只能笼统堆在一起,而检索改过之后旧反馈的含义已经变了。 - 反馈能关联到**具体记忆与排序**(`index.js` 接线),这是"用真实使用效果评判检索改动"的前提。 ### 实测(A/B 对照,59 例固定测试集,跑在库快照上) | 指标 | 前 | 后 | |---|---|---| | precision@3 | 0.177 | **0.287** | | recall@3 | 0.407 | **0.500** | | MRR | 0.409 | **0.494** | | 不相关注入率 | 0.823 | **0.713** | | 负样本泄漏率 | 0.688 | **0.375** | | 平均候选 / 平均注入 | 5.29 / 2.10 | **3.22 / 1.59** | | 敏感泄漏(9 例) | 0 | 0 | **⚠ 口径提醒**:`zeroHitRate` 0.102 → 0.203 看着像变差,其实是把两类混在一个数里了 —— **负样本**零命中 5/16 → **10/16(好事:不再乱注入)**,**正样本**零命中 1/43 → **2/43(真差 1 条)**。 别用这个聚合数下结论。 ### 界面(记忆控制面板) - 重排成 **3 块**:人设与规范(每人设一个保存按钮)· 一行控制条 · 列表。 - 视觉全部走 **DSH 主题 token**(0 个硬编码色值,深浅主题自动跟随); 列表从"每行一个描边卡片"改成"一个容器 + 发丝分隔线 + 斑马纹 + 悬停高亮"。 - 构建标记仍在(控制台一行 + 根元素 `data-build`,界面上不再占位)。 ### 校验器 - 常规套件 **319 项**(`tools/verify/`),本轮新增/加强 **150 项**验收(`tools/eval/`, 不进仓库):备份恢复、恢复失败路径、迁移闸门、敏感安全、修复验收、面板渲染。 - **注意**:断言全绿 ≠ 验收完成。备份/恢复目前只有**脚本级**验证, **还没在真机上恢复过一次真实库**。 --- ## [1.7.0] - 2026-10-12 ### 新增:用户级(跨工作区)记忆库 **解决的问题**:记忆库是"一个工作区一个库",于是"用户级长期记忆跨工作区生效"这件事 **没有归属** —— 身份、职业、语言、跨项目约定这类"换个项目还需要"的信息, 写进某个工作区就只有那个项目能召回。此前只有手动跨库检索(`memory_search(scope="all")`)能用, **自动召回与写入都不跨**。 现在: - **库**:`$DSH_HOME/memory-lite/global/memory.db`(沿用 `dirForWorkspace(null)` 的现成路径)。 它**不进注册表**(注册表是"工作区库"的清单,混进去面板会多出一个像工作区的条目), 但会在面板与 `scope="all"` 里显式出现,标 `kind: "user"`、显示名「用户级(跨工作区)」。 - **写**:显式指定,**不猜** —— `memory_note(scope="user")`、面板路由 `POST /api/save` 带 `scope:"user"`。 按 `id` 的工具(`memory_read` / `memory_update` / `memory_forget`)**自动认库**: 主库里没有那个 id 就回退到用户级库,所以拿着从用户级库搜出来的 id 直接改也能改到。 - **读(召回)**:每轮召回时「当前工作区库 + 用户级库」**一起搜、按分数合并**; 同一条记忆用**内容指纹**判重(两个库的 id 不同,用 id 判重会让同一条出现两遍); 同分时工作区优先("项目里的事"比"用户级的事"更具体)。 - **安全阀**:合并后**不额外扩容** —— 名额仍由 `injectMaxItems` 统一控制, 所以用户级记忆写多少条都不会撑爆每轮提示词。 压缩后的「补回常驻层」也把用户级库算进来(否则压缩一次,用户级记忆就再也回不来)。 - **不进常驻索引段**(与用户确认过的取舍):索引段只有 4096 B,容量留给工作区条目。 - **记账**:注入日志行记在工作区库(会话在哪就记在哪); 用户级条目的 `inject_count` 落在**它自己那个库**(新增 `storage.bumpInjected()`), 否则面板上它永远是"未用过"。 ### 变更 - `memory_search` 的 `scope` 增加 `user`(原来只有 `workspace` / `all`); `all` 现在也包含用户级库;输出措辞从「跨 N 个工作区」改为「跨 N 个库」「↳ 来自:」。 - `POST /api/save` 的"不带 dir 就落到唯一那个库"在**多库**时明确拒绝 —— 本来就是这个设计,只是 V1.7 起了第二个库后才真的会走到这个分支。 ### 修复:设置页列表重排 用户报「有些标签占了 2 行,有些一行」。**根因不是标签"天生两行"**:列表原来是 flex 行, 而 45 条里有 **40 条没有标题**(回落到"正文前 24 字",长度不可控),长文本不收缩, 把标签挤得**自己断行**。改成 **grid**: - 列模板固定 `52px / 84px / minmax(0,1fr) / 30px / 40px / 88px` —— 列宽由整表统一决定, 于是**行行等高(32px)、列列对齐**; - 标题与项目名一律 `minWidth:0` + 省略号 + 不换行,**悬停看全文**; - 标签加 `whiteSpace:nowrap` + `flex:0 0 auto`(药丸不再自我断行、也不被标题挤扁); - 「用过 N 次」简化为 **`3×`**(悬停显示"被自动注入过 3 次";未用过的显示暗色 `–`); - 「敏感」独立成一个 30px 窄列(没有敏感内容也渲染空格子,保证列不错位); - 加斑马纹,长列表好扫行。 ### 校验器 - 246 → **268 项全绿**(V1 144 / V2 52 / 存储 34 / 客户端 21 / 元数据 17)。 - **再加 15 项列表布局断言**(客户端 21 → 36):起因就是"标签占两行"这类**只有肉眼能发现的回归**。 样式本身人眼才能看,但"会不会换行"是由几个具体属性决定的,那几个属性机器能验: 每行都是 grid 且用同一个列模板 · 行高固定 32 · `boxSizing:border-box` · 行裁剪溢出 · **每行格子数一致(6 格,列不会错位)** · 没有 null 格子 · 标题列 `minWidth:0`+省略号+nowrap · **无标题回落 ≤24 字且不含换行** · 标签 `nowrap` 且不伸缩 · 项目列截断 · 敏感列始终占位 · 状态下拉三态齐全。 为此把行渲染抽成纯函数 `renderRow`,并通过 `__test` 暴露给校验器 —— 渲染函数里 `useState` 拿不到列表数据,**这条渲染路径以前从未被校验器碰到过**。 - 新增 22 项,最关键的一条是全链路真机同款断言: **「在另一个工作区(空库)开一个会话,召回必须取到用户级库那条」**, 并顺带断言那个工作区自己的库是空的(排除"其实是本地库命中"的歧义)。 另有:只写用户级库不串工作区、`inject_count` 落在自己的库、索引段不含用户级条目、 面板快照标 `kind:user`、`/save` 的 scope 分支,以及合并函数的三条规则 (判重 / 排序 / 同分优先级 / 单库异常不影响另一个库)。 --- ## [1.6.0] - 2026-10-12 ### 修复(一次返工:把上一版强加给用户的约束去掉) - **「人设设定」「工作规范与行为准则」不再写你的 `AGENTS.md`,也不再要求保留任何标题。** 1.5.0 把这两块存进 `$DSH_HOME/AGENTS.md`,并用我加的 `### 身份` / `### 核心原则` 当锚点, 于是"标题行不能删"变成了对用户的约束(用户原话:**「这些###的标题不能去掉吗?不合理。」** —— 他是对的:那些标题只是我写文件时顺手加的 markdown 结构,跟内容无关)。 现在内容存在插件自己的两个文件里,**文件内容就是文本框内容**(零解析、零锚点、零格式要求): ``` $DSH_HOME/memory-lite/persona/persona.md ← 人设设定 $DSH_HOME/memory-lite/persona/rules.md ← 工作规范与行为准则 ``` 插件把这两份渲染成一个 **system prompt 段落**(`memory-lite:persona`), order 紧跟**框架自己的人设段**(`DEPLOYMENT_PERSONA_PREFIX` = 0 → 我们用 1)—— 于是"每轮必到"仍然成立,而且靠的是框架的段落机制,不是我发明的锚点。 保存后下一轮立刻生效,不用重启。 - **迁移脚本** `tools/migrate/move-persona-to-plugin.mjs`:把 AGENTS.md 里那一节搬进插件并从 文件中删除(先写插件文件、再改 AGENTS.md、改前备份;默认 dry-run)。 作者本人的文件:15090 → 11787 字节。 ### 取舍(已与用户确认) 内容现在**随插件走**:插件停用/卸载,人设与准则就不生效了。放在 `AGENTS.md` 里的好处是 "不依赖任何插件" —— 这是上一版唯一的优点,作为交换去掉了格式约束。 ### 校验器 - 227 → 241 → **246 项全绿**(V1 124 / V2 50 / 存储 34 / 客户端 21 / 元数据 17)。 - 人设相关断言**全部重写**,其中两条直接钉住这次的返工点: - **「正文逐字节原样落盘(没有任何标题要求)」** —— 写一段连一个 `#` 都没有的文本,落盘必须一模一样; - **「没有偷偷加任何 markdown 标题」**。 另新增:段落注册与位置(`memory-lite:persona` order 必须是 1、排在记忆索引 5000 之前)、 换行/制表符/尾部空格原样保留、备份落在 `backup/` 子目录、段落渲染(两份都空 → 不注入)。 删掉的断言:原先那一整类"AGENTS.md 锚点 / 字节手术"的测试 —— **因为不再碰用户的文件了**。 --- ## [1.5.0] - 2026-10-12 ### 新增 - **面板顶部多了两个文本框:「人设设定」与「工作规范与行为准则」**。 它们读写的是**宿主全局指令文件 `$DSH_HOME/AGENTS.md`** —— 不是记忆库。 为什么不在记忆库里:这两块必须**每轮生效**,而记忆库是按关键词召回的, 规范类的东西"这轮恰好没命中"就等于**静默失效**(你那条「改动前先给我看」能每次都拦住我, 是因为它在 system prompt 里,不是因为被召回)。 - 分组按**小节名**:人设 = `### 身份 / ### 性格 / ### 信任`; 规范 = `### 核心原则 / ### 长期协作`(这两组在文件里是交错的,所以不能按位置切一刀)。 - 写回是**外科手术**:只替换这几节,其余字节逐一不动(校验器有一条断言专门钉这件事: "原样保存后文件字节完全一致");写前备份 `AGENTS.md.bak-<时间戳>`;先写临时文件再 rename。 - 结构不符就**拒写**:缺小节标题 / 混入另一组的标题 / 出现不认识的标题 / 空内容。 - 带 mtime 的乐观并发:文件被别处改过时拒绝这次写入,提示先「重新读取」。 ### 变更 - **移除** 1.4.0 刚加进面板的「+ 新增」与「编辑」单条记忆的表单。理由:长期记忆绝大多数 来自每轮自动沉淀与整理,手写是少数情况;而且想手动记一条,最自然的入口是**在对话里说** (还能带上当时的上下文)。面板的定位收窄为「设定 + 查看 + 改状态」。 后端 `POST /api/save` 路由保留(有断言),只是前端不再暴露入口。 - **长期记忆列表改成一行一条**:层级 · 标题 · 状态 · 用过几次 + 状态下拉。 **正文不在前端展示**(据用户 2026-10-12 的决定:35 条摊开正文太冗余)。 想看某条全文,可以在对话里问,或直接打开库文件。 注意:迁移来的条目大多没有标题,列表会回落到"正文前 24 字"。 ### 校验器 - 205 → 227 → **241 项全绿**(V1 119 / V2 50 / 存储 34 / 客户端 21 / 元数据 17)。 - 新增:persona 的分组、读取边界、**原样写回字节不变**、改一节不动别处、 四类坏输入被拒、备份生成、`GET/POST /persona` 的真实 handler 调用(含 mtime 冲突拒绝); 客户端新增"已接上 `/persona`"与"不再调用 `/save`"两条契约断言。 --- ## [1.4.0] - 2026-10-12 ### 新增 - **设置页「记忆控制」可以直接新增 / 编辑记忆了**(在这之前它只能看、搜、筛选、改状态 —— 想写一条只能去对话里说)。新增内容: - 工具栏「+ 新增」,每条记忆上的「编辑」; - 表单字段:层级 / 内容 / 标题 / 关键词 / 重要性 / 项目 / 子类; - 新路由 `POST /dsh-memory-lite/api/save`(新增与编辑共用,带 `id` 就是编辑)。 - **校验与对话里完全同一套**:抽出了 `normalizeEntryInput()`,工具(`memory_note`)与面板 都走它 —— 否则会出现"对话里写不进去的、面板里能写进去",而且没人会去逐处比对。 它与 `memory_note` 有两处**有意的差异**:关键词可选(面板是人手输入), 以及接受逗号/顿号/空格分隔的关键词串。 内容落库前同样过 `redactSecrets()`;凭据类内容会被标记为敏感、不参与注入。 ### 设计上的两条硬约束 - **写哪个库必须由调用方显式指定**(`dir`)。面板没有 agent 上下文,"推断当前工作区" 在冷启动时必然落回 `$DSH_HOME/memory-lite/global` 那个空库(V2 真机踩过:面板显示 0 条)。 所以多库环境下没带 `dir` 直接拒绝,**不猜**。 - **编辑语义保留**:`title` 不传 = 不改动这一列,传空串 = 清空。 ### 校验器 - 170 → 197 → 205 → **227 项全绿**。 - 新增两类断言: - `verify-v1` 里给假 ctx 补上 `inject` + `webServer` 桩,把 **`/save` 的真实 handler 抓出来直接调**:新增成功、空内容被拒、非法层级兜底、`project` 缺项目名被拒、 越界重要性被夹取、编辑走更新分支、改完 **FTS 已同步**(新关键词命中 / 旧关键词无残影)。 (之前假 ctx 没有 `inject`,整块 HTTP 路由注册不上,面板相关的代码完全测不到。) - `verify-client` 新增**两端契约**断言:客户端 `api('…')` 调的每个路径, 服务端必须注册了对应路由 —— 这类"两端各写一份字符串"的错在本项目发生过。 --- ## [1.3.0] - 2026-10-11 ### 修复 - **日志层的"累积式膨胀"**(这是本版最重要的一条) 实测:10 轮对话写出了 **804 KB**(WAL 另有 1.4 MB),最大一条 **104,405 字**; 相邻两条日志里 **6/9 对**满足"后一条完整包含前一条" —— 也就是整段历史被反复落库。 后果不只是占地方:蒸馏的输入被重复内容主导,日志层也失去了"流水账"的意义。 根因是**水位失准**:`seenSeq` 是**进程内存里的 Map**,插件一重启就归零; 压缩后会话快照重新物化时事件编号也可能变小。两种情况都让"本轮新增"退化成"整段历史"。 三条修法分工明确(缺一条都不够): 1. **水位持久化**到 `meta` 表(`watermark:`),重启后不再从头开始; 2. **重启守卫**:`extractTurnText()` 多返回一个 `maxSeqAll`(事件里真实存在的最大 seq, 不受水位过滤影响)。发现编号变小就丢弃水位重来 —— 没有这条守卫,水位会**静默跳过** 整轮内容(丢数据比重复严重得多); 3. **内容去重**:新文本若**包含**上一条,只存多出来的那段。水位负责**省事**, 内容比对负责**正确**。 > 只认**包含**这种确定关系,不做"最长公共子串"这类模糊匹配 —— 一旦切错就是静默丢内容。 > 切不动就原样存:**宁可重复,不可丢失**。 - **采集侧不再把系统样板写进日志**。日志一条 = 一整轮,其中大量内容是宿主以 `` 注入的用户全局 `AGENTS.md` 等样板。现在落盘前先清洗 (实现与蒸馏侧**共用一份**:`lib/text.js`)。 最小长度闸门也移到清洗**之后** —— 否则量的只是样板长度,一轮全是样板也会过闸。 ### 变更 - 日志内容不再是"逐字原样":**先剔系统样板、再去重与前文重复的部分**才落盘。 已有数据可以用 `tools/migrate/clean-log-boilerplate.mjs` 按同规则重算一遍 (dry-run 默认;`--apply` 前会先 `VACUUM INTO` 备份,备份失败则中止)。 在作者本人的库上:**649,870 字 → 192,451 字(保留 29.6%),10 行里 6 行被判为重复**。 --- ## [1.2.0] - 2026-10-11 这一版的三项都不是"想到什么做什么",而是**先量了真库**再定(量法与数字见 `AGENTS.md` 的「V3 实测依据」一节)。量的时候把库**拷一份到临时目录**再跑 —— `search()` 会写 `hit_count`,直接在真库上量会把面板的「用过 N 次」灌水。 ### 修复 - **蒸馏看到的其实是同一段系统样板** 日志一条 = 一整轮,里面夹着宿主以 `` 注入的用户全局 `AGENTS.md`。 而蒸馏原本只取**前 1500 字** —— 实测 10 条真实日志里**有 7 条的"前 1500 字"逐字相同**, 最大的 4 条(7~10 万字)只能看到 **1.4%~2%**。也就是说:模型拿到的不是"这一轮发生了什么", 而是同一段样板,蒸馏出来的条目自然只能反映那段样板。 现在 `pickLogExcerpt()` **先剔样板再截断**:整块剔除 `` / `` (含未闭合的)、逐行剔运行时样板(`Runtime context.` / `Elapsed since …` / `Browser time zone …`), 再把重心放到**尾部**(头 40% + 中略标记 + 尾 60% —— 结论在尾部,提问在头部)。 总字符:649,870 → 450,701(降到 69.4%)。 ### 新增 - **召回观测(`recall_runs` 表)**:**每一轮**召回尝试都落一行,**包括 0 命中的那种**。 以前回答不了"召回到底命中多少轮" —— 日志层存的是一整轮文本,拿不到"当时那句 query", 于是离线既复现不了、也验收不了任何检索改动。现在配 `tools/diag/diag-recall-quality.mjs` 可以在库副本上**逐条重放**,给出零命中率与和历史记录的差异。 query 落库前过 `redactSecrets()` 并截到 500 字;只保留最近 500 行(观测数据不该让库膨胀)。 ### 变更 - **常驻索引段每条只列 20 字**(原为 48 字)。实测依据:33 条真实记忆时索引段已占 3431/4096 B(83.8%),**每行 123 B**(30/33 条没有标题,回落到"正文前 48 字", 中文 1 字 = 3 B)→ 只装得下约 33 条,再多就会**静默**从 system prompt 里消失。 截到 20 字后每行约 83 B,约能装 49 条(+48%)。 ⚠ **不要用"给旧条目回填标题"来解决这件事** —— 回填的标题同样会被截断,额外容量是 0; 要调容量就调 `INDEX_TITLE_CHARS`。 ### 开发期修掉的两个"校验器假绿" - **假 ctx 覆盖了同名事件的多个处理函数**:`verify-v1` 原本用 `Map<事件, 函数>` 存处理函数, 而 `apply()` 对 `agent/pre-step` 注册了**两个**(召回、压缩后补回),后一个把前一个覆盖 —— 于是"pre-step 只追加不改写"实际测的是**补回**分支,**每轮都在跑的召回分支从未被检验过**。 现在改成数组并按真实瀑布语义串起来跑,召回路径的断言才真正生效。 - **校验器污染真实注册表**:`apply()` 会往 `$DSH_HOME/memory-lite/workspaces.json` 登记工作区库, 而校验器用的是临时目录 —— 每跑一次就留下一条指向已删除路径的记录。现在校验器先把 `DSH_HOME` 指向临时目录再加载 `index.js`。 --- ## [1.1.0] - 2026-10-11 首个版本。 ### 功能 - **两层记忆** - **日志层**:每轮对话结束把内容原样追加到本地 SQLite(**零模型调用**), 每会话上限约 200 条,溢出**先写归档文件再删库内行**(不静默丢) - **长期条目层**:把日志蒸馏成记忆条目。**默认手动触发**(点「立即整理」), 可开 `autoDistill` 让它空闲时自己整理(默认关,避免没打招呼就花额度) - **注入**(每轮自动,零成本) - 恒定索引段放进 system prompt(字节稳定,护前缀缓存) - 命中的记忆作为**独立消息追加在尾部**,不改写已有历史 - 同一会话已注入过的不重复注入;**压缩后自动补回**常驻记忆 - **检索**:FTS5 + LIKE 双路,精确子串独占最高层,其余按 基分 × 重要性 × 时效 排序 - **跨工作区检索**:`memory_search` 传 `scope="all"` 可搜所有已知工作区,结果标注来源 - **注入可见性**:逐轮记录注入了哪些条目;面板显示「用过 N 次(最近一次)」 - **七层语义**:`soul` / `user` / `rules` / `fact` / `lesson` / `topic` / `project`(含子类) - **设置页「记忆控制」**:查看、搜索、按层级/状态筛选、改状态、手动整理 - **六个模型工具**:`memory_note` / `memory_search` / `memory_read` / `memory_update` / `memory_forget`(**删除走人工审批**)/ `memory_stats` - **本地优先**:不联网、不上传;库文件 0600 + WAL ### 安全 - **敏感内容双防线**:写入时单点脱敏(密钥类正则)+ `sensitive` 在**写入时**判定并落列, 注入侧只读列(fail-closed —— 注入路径上再判断等于把 fail-closed 变成 fail-open) - **删除走审批**:`memory_forget` 声明为破坏性工具,转人工确认 ### 修复(开发期实测发现的问题) - **蒸馏调用没给 `provider` → `NO_ADAPTER`**:`ctx.llm.stream()` 的 `provider` **不能省** (框架没有全局默认提供方;参照 `dsh-compaction-basic`,它用的是自己配置的 `summarizationProvider` / `summarizationModel`)。现在显式配置优先, 否则从 `ctx.llm.listProviders()` / `listModels()` 发现候选并**逐个尝试** (`NO_ADAPTER` / 凭据类失败就换下一个),成功的那对会记进 `distill_runs.note` - **正常结束被误判成错误**:`{type:'finish', reason:'stop'}` 的 `reason` 是**字符串**, 第一版把它也当失败 → 每次整理都报「模型流错误:stop」。 现在只有对象形态的 `reason` / `failure`(带 kind/code)才算失败 - **前端吞掉真实错误**:失败时界面只显示「整理未完成:error」 (代码优先显示 `reason`,而它恒为 `"error"`),真正的 `message` 被丢掉。现在显示完整原因 - **蒸馏提示词构造的 TDZ**:`const known = ... ${known.map(...)}` —— 在变量自己的初始化 表达式里引用它,命中暂时性死区直接抛 `Cannot access 'known' before initialization`。 **只在「已有记忆标题非空」时触发**,空库第一次整理反而正常 - **`ctx.llm.stream()` 用法**:真实签名是 `stream({ messages: [...] })` 且返回**异步可迭代**, 必须 `for await` 消费 `text-delta` 分片。早期写成 `{system, prompt}` + `await`, 于是"调用成功但产出为空(raw 长度 0)" - **蒸馏输入无上限**:实测送进去 14.7 万字(单条 7.4 万)导致模型零产出。 现在单条截断 1500 字、总量 ≤20000 字,**超预算的流水留到下次**而不是标记成已处理 - **面板读错库**:面板没有 agent 上下文,任何"推断当前工作区"的方案在冷启动时都会落回 空的全局回退库 → 显示 0 条。改为**发现式定位**(注册表 + 扫描 + 库内 meta), 并列多个库供选择 - **注册表 BOM**:PowerShell 写 JSON 会带 BOM,`JSON.parse` 直接抛,而当时的 catch 静默返回空数组 → 面板永远 0 条。现在容错 BOM 且读失败会记日志 - **中文检索**:FTS5 的 `unicode61` 把连续 CJK 当一个整体 token, 必须传完整连续串;LIKE 回退也不能拿整串匹配(要求相邻必 0 行) - **客户端 ctx 是声明门控代理**:未 `inject` 就取属性会抛 `cannot get property "slots" without inject` - **插件卡片没名字没图标**:元数据在 `locale/en.json` 的 `meta` 里, **不在 `package.json` 的 `meta`**;且 locale 受 `exports` 门控(缺了会静默略过) ### 已知限制 - 检索是**关键词级**的,不是语义向量检索 —— 换个说法可能搜不到 - 每个工作区一个库,默认只搜当前工作区(跨库要显式 `scope="all"`) - 空闲自动整理依赖宿主进程活着(客户端关着时不会整理) --- [1.1.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.1.0 [1.2.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.2.0 [1.3.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.3.0 [1.4.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.4.0 [1.5.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.5.0 [1.6.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.6.0 [1.7.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.7.0 [1.8.0]: https://github.com/eighteentang/dsh-plugin-memory-lite/releases/tag/v1.8.0