Repository navigation
Releases: NightPainters/DSH-Ling
Release list
1.7.3 — 器灵在场 / 暂离
1.7.3
本版修的是「说了没做」这一类缺口,另把面板的一个模糊开关重做成一个清楚的状态。
面板只剩一个状态:器灵在场 / 暂离
- 旧的「启用人格与记忆注入」一个勾说不清两件事:关掉它会让人格段与记忆段一起消失,但树改动播报、时间锚、11 件记忆工具照旧在 —— 于是「完全原生助手」这句话也不成立。
- 现在只有一个状态:在场 / 暂离。暂离 = 人格与记忆都不再注入、记忆与图书馆的工具执行即拒、后台整理与成长停、离场期间的新会话不入库。她的一切都还在盘上,勾回来就回来 —— 暂时分别,回头见。
- 离场不走手术门(可逆的动作用可逆的门);改她是谁仍然走手术门。
- 面板会写明「上一段离场区间」,回来时说得清。
子代理不再拿到记忆工具
- 子代理此前会拿到 11 件记忆/图书馆工具;宿主
tools.restrict()只过滤继承层,遮不住 agent 自身层注册。 - 本版在注册期跳过(
reason: 'subagent-skip'),与「子代理不入记忆库」同一条界线。
离场闸分级
- 内容入库停:会话入库、深摘、导入、扫描、反馈成长。
- 档案整理照常:会话命名、打标签、图书馆整理。
- 记忆结构改动停:建枝、连边、矛盾裁决、主脉蒸馏、条目取舍、快照恢复。
设置读写地基
- 设置文件损坏或读失败 ⇒ 保守进入暂离,并把坏文件另存,不再静默恢复默认。
- 保存失败 ⇒ 运行态不再先行改变(回执 500 就是真的没改成)。
文档
- README 里「图书馆(1.7 工作区,未发布)」等 14 处过期状态陈述已清理。
- 致谢 @eltontse0-create(#2):这条反馈直接促成了面板改为「在场 / 暂离」。
校验
- 测试链 58 段全绿;发布前检查、合规检查、埋雷特征串清除核对均通过。
- 候选包 sha1
f3710fb4d71f66bd9d1fa32bd326d045c9cf5f7c
已知边界(如实记):away.windows 区间台账上限 200 段(超出后最早的区间会被丢弃);「补课」(把离场期间的会话读回来,并标明是听来的)排在 1.8。
1.7.2 · recall 回执降噪
1.7.2 · recall 回执降噪
上一版:1.7.1(2026-10-17)· 这一版没有新功能,只让
recall的每一次回执更短、更干净。
一句话
同样的信息,更少的话。 检索回执从「数据 + 三段固定文案」变成「数据 + 本次特有的警告」;被截断的片段不再留下半个 **;条目不再一屏刷同类目。
改了什么
| # | 改了什么 | 你会在哪里感觉到 |
|---|---|---|
| 1 | 截断后清理未闭合标记 —— ** / ` / [ / 【 数一遍奇偶,剥掉末尾没闭合的那个(不补) |
命中片段不再以半个标记结尾(以前读起来像断句错误) |
| 2 | 固定文案收进工具说明 —— 「下一步」「换词口径」不再每条回执印一遍 | 回执更短;模型照样知道下一步怎么走(系统面里写着) |
| 3 | 片段只剥成对标记 —— **粗**、`码`、[文字](地址) 只剥片段里的,回执自身格式保留 |
回执里的表格/多行不再有塌的风险 |
| 4 | 条目去重 + 同类折叠 —— 同会话同文本只留一条;同类目超 3 条折叠成一行并报数 | 一屏能看到真正不同的结论,而不是四条「记忆树」 |
| 5 | 回执总字数上限 4,000 —— 超了截断并报出「还有多少字没显示」 | 最长的回执从 5,322 字降到 4,000 以内,而且你能看到它被截了 |
| 6 | emoji 口径固定 —— 📌 ⚠️ ✓ ✅ ❌ 🟡 ★ 是状态符号:保留、不新增 |
界面观感不变 |
没有变的东西
- 「取原文」这一档不受 4,000 字上限约束 —— 它有自己的 12,000 字护栏与「还有 N 轮没给」的报数。降噪不该把"取原文"这件事本身弄废。
- 范围说明仍然印 —— 只是压成一行:你说得清"这次搜的是本机库全量还是主人自己的会话",这条不能因为瘦身而消失。
- 不剥离全部 Markdown、不去掉 emoji、不改历史会话文件(四条否决项照旧,理由见工作区
daily/recall输出过滤-修改清单.md§4)。
测试
- 新增
tests/recall-noise.test.mjs(M1/M2/M3/M5/M6 各一组断言,含「截断不得切出半个 emoji」)。 - 测试链 56 → 57 段;
recall-search里那条「回执要给下一步」的断言按新契约改写(它现在断言的是"不再印")。
升级须知
- 无需迁移、无需改配置。装上即生效(宿主重启后)。
- 如果你有脚本断言过回执里的「下一步」「换词试试」这两段文字,它们已经移到工具
description里 —— 这是本次唯一可能打破外部断言的地方,已在 CHANGELOG 写明。
已知取舍
- 同一类目超过 3 条时折叠而不是分页:折叠行只报条数、不报内容。要看全,请缩小查询词(这条与"M5 折叠不丢信息"的口径一致:丢的只是"一次看完",不是"看不到")。
1.7.1 — 安全加固:把「半个 emoji」的漏口全部堵上
1.7.1 — 安全加固:把「半个 emoji」的漏口全部堵上
这是一个补丁版,不含新功能。 1.7.0 上线后发现:那条会把半个 emoji 送进请求体的路径,当时只堵住了 recall 回执与会话行两条,注入面还有几处在裸切。本版把剩下全部补上,并顺手收口四项拖了很久的账。
一、注入面净化补全(本版最重要的一条)
症状:某次会话摘要里有一个 emoji 恰好被截断在中间 ⇒ 半个代理字符进了请求体 ⇒ 上游 JSON 解析直接失败(unexpected end of hex escape),而且毒留在上下文里,之后每一次请求都是 400,界面上看起来只是"这对话突然不能用了"。
1.7.0 已经堵住了 recall 回执(research/render 两个出口)与会话行;本版补上其余全部入口:
| 入口 | 补的位置 |
|---|---|
| L1 记忆行 | 三处截断(首句 / 行文 / 结论行) |
| 留痕与摘要 | 五处截断(改名留痕、事件摘要、提炼素材、建议文本、概览标题) |
| 深摘转写 | 两个来源(文件转写、库内原文转录) |
| 小助手摘要(本机模型) | 请求体里的 system / user 文本(这是独立于注入面的另一条出口) |
| 诞生仪式素材 | 短会话片段与素材拼装 |
| 快照成文出口 | 所有 part 拼完之后 |
| 注入面最终出口 | 正文与时间锚合流之后 |
- 截断长度契约没有变(仍是 UTF-16 单元口径、省略号仍占一位)—— 只换掉"切在了哪里",所以既有回执的长度与断行都跟以前一样。
- 检测口径:孤立的代理码位一律替换成
U+FFFD;成对的 emoji 原样保留。
二、记忆树:有向连边按方向删除
prerequisite(前置)/ supersede(取代)是有向的:A→B 和 B→A 是两条不同的边。以前删掉一条,会把反方向那条一起删掉,而回执只说"断开了" —— 树和回执对不上。
现在:有向类型只删同向;无向类型(related / contrast / example)仍按旧口径双向清理,那是历史重复行的兜底。
三、结论层:开场不再因为"只有结论"而整段消失
开场记忆段的闸门原先查的是「有没有会话概述」,而料早已换成结论层条目 ⇒ 一个只有结论层条目、没有概述的库,会静默丢掉整段记忆(面板看得见、注入看不见)。闸门改为让选料自己判空。
四、记忆导入:整包原子
导入记忆包改为在事务内执行:中途抛异常则整体回滚,不再留下"一部分已覆盖、一部分没覆盖"的半截库;回执如实回 errors: 1,不谎报行数。单行坏数据仍按旧口径只计数、不回滚整包。
五、客户端:一处"重试失效"、一处"面板说假话"
- 手术票:一次失败会把整页生命周期锁死(用户只看到"没反应",没有任何提示)。改为成功才锁定,失败可再试且有次数上限,不会变成无限刷屏。
- access 导入日志:迟到的响应不再覆盖新状态(与其他四个标签页一致的世代守卫) —— 以前快速切换来源时,面板可能显示旧数据而按钮已是新状态。
六、测试与 CI
recall的真机断言不再押在"此刻最新的那个会话"上:改为在最近 5 个会话里挑第一条真有可见对话轮的;全都不可断言时明说跳过而不是判红。以前只要此刻有子代理会话在写,这套测试就会时红时绿 —— 这是"本地全绿/CI 红"之外的第三种假信号。- CI 两条:按
package.json装依赖再逐段跑;library-sqlite的大小写断言改成"先探测文件系统能力、再按对应结果判",不再假定 Linux 一定是大小写敏感。
升级须知
- 库结构不变:记忆库 schema 仍是 v17,图书馆
library.db不变,开库不需要迁移。 - 依赖不变:运行期唯一依赖仍是
@deepseek-ai/schemastery ~3.18.4。 - 接口不变:工具签名、回执字段、配置键全部不变 —— 这是一次纯内部加固。
- 升级方式同 1.7.0:装新版本 → 重启宿主 → 新会话里首次注入即生效。
已知取舍(留给 1.7.2)
- 回执降噪(截断后的未闭合标记清理、固定文案瘦身、snippet 去 Markdown、回执字数上限)不在本版 —— 它们不影响安全,单独做一个版本更清楚。
- 有向连边删除目前按"同向"判:无向类型的历史重复行仍走双向清理;反向有向边不会因为删错向而被连带删除,但也不会被这条命令删掉(它是另一条边)。
1.6.2 · 检索档换快路 · 结果不再被「我刚说的话」占满
1.6.2
检索档换了一条快两个数量级的路 · 结果不再被"我刚说的话"占满 · 几处会误导人的地方修掉。
一句话:1.6.1 之后继续修。最值得说的三件:① 按内容找原文从"每次 20 秒以上"变成"20~50 毫秒"(改走本机库,不再依赖宿主那条例外慢的检索通道);② 搜索结果默认不再被"当前会话自己"占满 —— 此前搜什么都会先命中你几分钟前刚说的话,把真正要回溯的历史挤下去;③ 枝系数(复盘期给某个主题枝调的权重)真的开始生效了,此前它只存不用。
✅
package.json版本号为1.6.2。无 schema 变化(仍是 v17);⚠️ 多数改动在 host 侧 ⇒ 需重启宿主才生效。新增一份对外文档PHILOSOPHY.md(理念:为什么值得做、凭什么这样做)。
一、按内容找原文:换了一条快两个数量级的路
- 宿主那条检索通道每次都要做一遍全量对账 ⇒ 实测每次都超过 20 秒(重启后第一次也一样,并发还会互相排队)。现在 ① 档默认搜本机库内原文层(实测 20~50 毫秒);要看全量(覆盖全部事件、含子代理会话)显式传
hostSearch=true。 - 如实标注覆盖范围:本机库那份原文只含"真人消息 + 有正文的回复"(我自己的长回复中段搜不到)⇒ 回执里明写"原文来源:本机库内原文层",不说就会被读成"库里只有这些"。
- 原文层默认 5 轮(此前 10 轮):条目层是结论、原文层是下钻材料,后者一多就把前者挤出视野。
- 超时不再是挂死:宿主路有 20 秒上限,超时照样把条目层交出去并说明"没跑完" —— 此前实测等过 30 分钟,且当时没有任何中断入口。
二、搜索结果默认排除"当前会话自己"
- 症结:当前会话也在库里、而它的话最新 ⇒ 任何词都能命中它(实测搜"检索"时 10 条命中里 9 条是本会话),要回溯的历史被整页挤掉。而"我刚说过什么"根本不需要搜 —— 它就在上下文里。
- 现在:默认整条排除,并如实报出"另有 N 轮命中属于当前会话自己,已排除";要查自己刚说的传
includeSelf=true(那时它排到最后并标注「← 本会话」)。
三、记忆树:枝系数开始参与打分
branch.weight_scale从"只存与显示"变成真的参与检索排序:分数 = 基础分 × 血缘 × 矛盾 × 枝系数。一条结论的枝 = 它来源会话所在的枝。1.0= 零影响,0= 排到最后(不是禁用)。- 于是"复盘期给某个主题枝调权重"真的有行为后果了;此前它是面板上的装饰(README 的 U-10 正是针对这类"看起来能改、实际不做"的交互)。
- 实测现状:全库 20 条枝的系数都还是
1.0⇒ 这次上线不改变任何现有排序,变化只在你开始调之后发生。
四、写面与设置
- 两条写面补上手术门:
/persona/habits/scan与/persona/habits/reflect会写人格面(习惯候选),此前不受手术门管辖,而它们的确认侧/persona/habit是过门的 ⇒ 一条通路的两半不一致。现在四条形状一致(先解析请求体、再判门)。 - 删掉一个死开关:
updates.applyWhileRunning只有声明、没有消费者(而且它不在可写入白名单里,接上也开不了)⇒ 直接删,免得读的人以为改它能改行为。 DSH_HOME支持收尾:rawlog.js是最后一处仍走系统主目录的地方 ⇒ 现在与其它模块同源,自定义 home(隔离测试)下不会再读到真机目录。
五、文档与治理
- 新增
PHILOSOPHY.md:对外理念文档(目标是一种循环 · 复盘与"人在循环里" · 上下文是工作台、记忆库才是家 · 底层模型 ≠ 器灵 · 凭什么不越界)。随包发出,README 顶部与「文档」一节都有引用。 - 发布前检查加一条:对外文档(
README/CHANGELOG/DESIGN/ACCESS-DESIGN/package.json)里出现内部流程术语时会列出来提醒(警告级、不阻断发布)。 - 八项文档订正逐条落地:README 与代码打架的那句(段头要求 1.5.1 起已废弃)、写面回执形态、设置卡口径、两处数字(
daily0.3 /l1MaxItems10)、手术门覆盖条数统一为 14、安装四步的承诺口径、包名口径(@nightpainters/dsh-ling)。
六、结论层新增一类:纪律
- 条目现在有六类:承诺 / 决定 / 事实 / 偏好 / 关系 / 纪律。
- 纪律的判据三条(全中才进库):① 来源是主人授权的一句指令;② 作用对象是行为;③ 违反时能被某条判据红出来。⇒ 答不出"哪条判据会红"的只是一句话,退回收在别处。
- 它在记忆开场里的权重是 0.35 —— 高于"关系"、略低于"承诺":与承诺同族(都是说过的话),但承诺是对某件事的,纪律是对行为的。
1.6.1 · 修掉 14 处会影响实际使用的问题
1.6.1
修掉 14 处会影响实际使用的问题 —— 没有新功能,不改库结构。
一句话:本版是 1.6.0 的修复版。最值得装的三处:① 钉住的记忆不再被条数上限挤掉;② "只有摘要、没有原文"的记忆现在能忘了;③ 库被别的进程占用时,插件不再整个加载失败(此前 8 个工具会一起消失,界面上没有任何提示)。
另修好:飞书通道里你自己说的话不再被当成系统注入(找回 49 轮真话)、回灌网页端会话不再把它标成 DSH 会话、检索档不再给出错位的行号。
✅
package.json版本号为1.6.1。无新功能、无 schema 变化(仍是 v17);⚠️ 修复多落在 host 侧 ⇒ 需重启宿主才生效。
一、记忆开场
- 钉住的条目现在真的"必进":此前条数上限与字数预算先于钉住判定生效 ⇒ 钉住数超过上限时,多出来的会被挤掉(实测 9 条钉住 + 上限 3 ⇒ 只进 3 条)。现在两道闸门对钉住项豁免:同样条件下进 9 条。
- 落选记录的"来源"栏一直是空的:取错了列名。同批补齐外部面板需要的字段。
- 打分理由与真实公式不符:改动后统一为
(分项和) × 血缘 × 矛盾 ⇒ 总分。 - "热度"在留痕里被报成两种值(最高差 88%):改为报加权后的值。
二、原文与检索
- 检索档给出的"取原话"指引用错坐标系:它印的是事件行号,而取原文要的是对话轮号。实测 274 条里 **207 条(75.5%)**照做会报"序号超出范围"。现在改为"先看对话目录,再按目录里的
#号取"。 - 回灌会把网页端会话标成"DSH 会话":1,523 条网页端会话全是裸 id,回灌时被按 id 形状推成 DSH 来源 ⇒ 同一条出现两份摘要、双份记忆。现在按归档记录写入来源,并对不上时直接拒绝。
- 飞书通道里你自己的原话被当成"注入":这些句子在读档时被折进"(此处折叠 N 条注入消息…)"、目录里印成「(注入)」—— "你说过什么"整段消失。现在按会话通道区分:找回 49 轮真话,且不误收子代理收到的委托文字。
- 库被别的进程占用时,插件会整个加载失败:等待参数原先排在设置 WAL 模式之后 ⇒ 撞锁 23 毫秒就抛、连等都没等。现在挪到最前 + 对锁类错误做 3 次 × 2 秒有界重试;仍失败则只停用记忆功能(其它插件不受影响),并给出一条能照着做的排查提示。
三、记忆树与遗忘
- "只有摘要、没有原文"的记忆永远忘不掉(主脉记忆、导入条目):遗忘前的归档守卫要求"原文轮数 > 0",而这类条目的内容就在摘要里 ⇒ 判据改宽。同批修正"空归档"判据 —— 它原来把只有元数据空壳的会话也算成"有内容",于是真空归档会被放行,而遗忘标记一旦落下就永不召回。
- 改名后的枝名在三处不一致(回执 70 字 / 留痕 70 字 / 库里 60 字):统一上限常量,并把改名改成写后读回 —— 回执与留痕一律报库里的真值。
- 断边的回执可能是假的:连边可被反向重复写入(
A→B后再B→A落两行),而删除只删一条 ⇒ 回执说"断开了"、树里还连着。现在写入侧查反向(有向边不合并 —— "A 是 B 的前提"≠"B 是 A 的前提",渲染以→/↔区分),删除侧双向删。 - 传空权重会静默写成 0:0 在界面上是合法读数,看不出来是手滑 ⇒ 改为先判类型,空值一律拒。
- "被采用"的信号按请求窗口记账:没被真正交付的轮次也 +1 ⇒ 改用真正交付的区间。
- 范围限制如实标注:宿主没提供会话清单时,检索档开头写明「
⚠️ 没能限定范围 —— 结果可能含子代理或其它会话」,不再静默放行。
四、诊断
- 差分路的丢弃画像此前只在"一条都没写进去"时留一次记录 ⇒ 正常路径上丢了多少完全不可见。现在每轮覆盖写。
- 该画像与事件画像现在每次重启都清空(此前会跨重启残留,看着像"刚跑过")。
五、CI
- 一条测试断言写的是墙钟下界
dt >= 20(毫秒),CI 上实测 19 ms ⇒ 红。它断言的是定时器实现细节(毫秒级钟有粗粒度、定时器可提前一两毫秒),不是行为缺陷 ⇒ 下界放宽到只证明"确实等过、不是同步 reject",上界与"结局来自那道闸"由同组另两条钉住。
v1.5.3
1.5.3 — 导入不再被 4 MiB 卡死、手术门落在最后一层、原文来源从"猜"变成"记"
本版有三条主线:① 那份一千多条的历史导入时被 4 MiB 的上限整批打回(186 条、0 行入库),这一版把"总量限制"改成双限并让超限说人话;② 把"人格手术"做成一道真的门 —— 本机、票据、原句三关,且每次手术留一条链式留痕;③ 原文表第一次如实记录来源(不再靠"id 里有没有冒号"猜),未知来源会在前台说出来。另外停掉一条空转了 20 天的轮询。
安装前请确认
peerDependencies齐全(不合规的宿主会让插件整个不激活,线索只有一行)。
一、最该先看:导入的"4 MiB 整批打回"
一份 4,889,641 字节 / 186 条的历史,服务端上限是 4 MiB ⇒ 整批被拒,而客户端把 TCP 重置读成"批失败(网关)" —— 186 条,一行都没进。
- 客户端:按条数 × 字节双限切批(3 MiB / 200 条封顶,切点不切坏一条);
- 服务端:上限不再"读完就砍" —— 超限时先把 body 排空再回 413(带
limit与got两个真实读数),不再在同一拍里destroy让对面看见ECONNRESET; - 排空也有闸:排空最多收 64 MiB、闲置 30 秒即断(滑动计时);连第一个字节都不来的请求 30 秒回 408(此前要挂 300 秒);
- 单条就超限时,面板点名说清是哪一条(不再让人从 186 条里猜);
- "跳过了"必须说出来:某几轮原文没有替换(目标已有原文,见第二节)也进计数 —— 一路贯通 HTTP 回执 → 导入面板人话 → 持久账本,且为 0 时账本逐字不变。
二、折叠:不再悄悄覆盖已有原文;"同一段对话存两份"也堵了
导入第二份同源数据时会走"折叠"(并元数据、删副本)。旧行为会在目标已有原文时把它换掉 —— 静默、且不可逆。
- 现在:目标任一侧已有原文 ⇒ 不写、只计
· 原文未替换 N,宁少副本、不换真原文; - 命名空间裂缝:判断"有没有原文"原先只看写入目标(
dsweb裸 id),没把import:<id>一起算 ⇒ 某些顺序下同一段对话会在库里存两份(正是折叠要消灭的"召回两次")。现在判据扩成"候选链任一侧有原文";逐条比对的指纹也取两侧(否则已有副本仍会被算成"未替换"); - 真机库只读盘查:两侧都有 = 0(102 个会话:裸 id 87 /
import:侧 15)⇒ 无旧账,本版属纯预防。
三、"Host 是 loopback 就免检"在反代之后失效了 —— 伪装 loopback 现在会被抓
引入反向代理后,Host 头由客户端自报(nginx 原样透传),而 cookie 只校验名字(算法公开)⇒ 公网匿名请求可以拿到真实数据(实测:伪造 Host: 127.0.0.1:3080 + 自算 cookie ⇒ 200)。
- 新增
realPeerAddress():取X-Forwarded-For的最后一段(反代追加、客户端删不掉),回退X-Real-IP; - 凡"Host 声称 loopback,却带着非 loopback 的代理对端" ⇒
spoofed-loopback-host拒绝; ⚠️ 取首段会被预塞绕过(X-Forwarded-For: 127.0.0.1, <真实 IP>)—— 代码注释里已写明不许"优化"成首段;- 本机直连与经反代的正常远程访问都不受影响(后者根本不走 loopback 分支)。
四、人格手术门:不是挡主人的,是网络失守时最后失守的位置
封存(sealed)状态下,改人格/规矩/习惯的写面原来只有一处半过门。现在把门下沉为一处,覆盖全部 14 条能改人格的写面:
- 非本机(带代理头)⇒ 拒
surgery-local-only; - 每启动轮换的票据(
/surgery/ticket只发给"本机直连"的请求;重启即作废旧票); - 原句匹配;
- 并删掉一条绕过分支:旧代码里带
sealed:true的补丁可以跳过验句直接改库(实测证明它能生效)。
另外补了一句很关键的话:判据"是不是本机"必须看"有没有 User-Agent" —— 远端面板经宿主内中继进来时,UA 与代理头会被一起剥掉,请求看起来和本机一模一样;而本机浏览器永远带 UA。(代价:不带 UA 的本机脚本做不了手术 —— 有意如此。)
留痕:每次手术(含被拒的尝试)追加一条带哈希链的记录(prevLineHash 串上一条,改一行即断),并有一个只读事件面可查;已知历史缺口(早期版本建链失败的那 5 条)由基线标记如实表达为"含已知缺口",而不是让校验永远报错(永远报错的告警等于没有告警)。
承诺句:本机浏览器照旧看得到(不会因遗忘卡死),经代理的读面不再回发明文(/state · /persona/history · /export 一律只回 hasSeal)。
五、原文来源:从"推断"变成"记录",未知来源上前台说清楚
原文表此前没有来源列,"这条原文属于谁"只能靠 id 里有没有冒号猜 ⇒ 判据是"裸 id = 本机会话":将来若出现不带冒号的新来源名,物理上分不出来。
- 新增
source列(纯ALTER TABLE ADD COLUMN:不重建表、不重写行、幂等、真机库副本实测 10ms); - 写入侧如实记账;读取侧优先看列、列为空时回落旧判据 ⇒ 半迁移状态也不会出现两种行为;
- 不做历史回填:真机库当前没有任何"裸 id 但非本机"的行 ⇒ 回填收益 0、而写 5,000+ 行的风险非 0。回填能力已就位(幂等、只填空值、绝不覆盖非空、汇报条数),需要时显式触发;
- 免检通道(概述器/习惯生成器)从黑名单("排除
import:%")改成正向白名单("裸 id 才是本机会话"):黑名单把"没见过的"当自己人,白名单把它当外人并报警; - 未知来源在前台用人话说出来:
⚠ 有 N 个我没见过的原文来源:…(m 轮)…,共 X 轮原文 —— 已按外部内容处理:没被概述、也没进你的习惯统计(原文表里那个"会话数"仍含它们)。首次见到 …,最近见到 …。
(这句话的措辞被逐字推敲过:原先想写"没混进你的会话统计",查证后发现 rawSessionCount() 数的是全表去重 id、确实含外来来源 ⇒ 改成如实版,并加断言禁止那句假话出现。没有告警时字段整个不出现、前台完全静默。)
六、反馈通路:停掉空转了 20 天的轮询,改订宿主推送
插件原来每 60 秒读一次 storages/message_feedback.json;而该文件自 2026-09-11 起零写入(同目录另一个文件是当天 16:12)—— 而宿主早就提供 feedback/committed 推送,插件侧零订阅(代码注释里那句"平台无推送"是过时的假话,本版一并改掉)。
七、一批"小但会被误导"的修正
- 出厂默认值里不再夹带开发机的私有配置:访问日志的"设备 cookie 名"默认值原先是开发机在
remote-web-ui.cookieName里改过的名字 —— 这既是泄漏(会随包公开),也对别人是缺陷:别人机器上的 cookie 名不是它 ⇒ 访问日志里deviceCookie恒 false、设备维度静默失效。现在默认回到上游的dsh_pair,并支持 宿主配置 → 环境变量DSH_LING_DEVICE_COOKIE→ 中性默认 三级解析(改过 cookie 名的机器需显式声明一次)。 - 迁移不会再把"库只读 / 被写锁"变成"插件加载失败":
source列迁移里那笔诊断写入原先没有保护,遇到只读或被锁的库会把异常冒到插件加载 ⇒ 整个插件不激活。现在它被单独保护(只打一行 warn),迁移语义不变。(仍有一处已知的慢:库被写锁时启动会顿住约 49 秒;本轮有意未动那处锁等待语义。) - 同名不同语义:
TITLE_MAX_CHARS的58(生成上限)与60(显示兜底夹取)不是同一个东西 —— 硬统一会多切手改过的标题或改变 59–60 字输入的生成行为。只补注释 + 把不变式写成断言; - 预览与写路径的判据收敛到唯一一处(此前预览少一条
md5判据 ⇒ 存在"预览说追加、写路径判 same"的分化可能); - 归因不再冒充主人:旧代码拿"请求里有没有原话"反推"是谁写的",而面板发的请求本来就不带原话 ⇒ 会话内、不带原话的调用被记成
panel,并被填上「(面板直接写入)」。现在按请求形状判,会话内缺原话就如实标注; /state?sessionId=加了基数上界(只淘汰空壳会话,带读数者永不淘汰);?db=只读预言机收成白名单(越界回db-not-allowed并给出两条出路);POST_ONLY差集护栏:把当前 24 条"非 POST"端点逐条钉住 —— 将来新增写端点忘了进集合,测试会红(断言里写明"这是快照不是白名单");- 新增 CI(
.github/workflows/test.yml,Node 24):逐段跑、逐段记退出码、红段单列、段数自检。
测试:全链 39 段(npm test)全绿。
dsh-ling v1.5.2
1.5.2 — 轮询从 24ms 降到 0.5ms、活行改判、面板第一次看得见「那一行」
本版修的是看不见的那一层:面板每 3 秒问一次状态,此前每次都按「要显示给人看」的价钱算一遍;这一版把轮询路径与面板路径分开计价,顺带把「那一行为什么没变」从一句猜不到的话变成四行读数。另外补全了 peerDependencies —— 这道闸门会让不合规的插件整个不激活(线索只有一行,而且不是每条启动路径都会打)。
一、最该先看:3 秒一拍的轮询,此前每次都在付全价
面板要的是可读文本、快照读数、活行预览;而 3 秒一拍的轮询只想要几个计数。此前两条路走同一份重活 —— 每次都重算整表指纹、把记忆条目全量拉出来只为数个数、在算文本预览之前不做任何判断。
- 现在按是否要文本预览分档:面板那条路带上标记、轮询那条路从不带 ⇒ 轮询档在算预览之前就返回,只回几个轻字段(快照那一组的 5 个字段逐字未动);
- 记忆条数从「全量拉出来再取长度」改成一条聚合计数;
- 实测(本机当天
00:00–16:34的访问日志、取日志里的单次耗时字段;分界取13:13这一分钟、两侧都不算它 —— 切换就发生在这一分钟之内:13:13:08仍是 28 ms,13:13:16起已是 1~2 ms):该端点单次耗时均值 24.4 ms → 0.46 ms(n=51,573 → n=3,945;分界前 ≤2 ms 的比例是 0.02%,分界后 99.5%)。
这几个读数是某一天的运行日志,不是基准测试。你的会话数量、记忆库大小、面板开合次数都与这里不同,测到的数不会与它相同;判据是「均值掉到零头、绝大多数请求 ≤2 ms」,而不是复现某个具体数字。
二、指纹缓存:单看任何一个信号,都会漏掉一半写入
面板与状态接口一直靠「整库指纹」判断记忆变没变,代价是每次轮询重算一遍。这一版给它加了缓存,难的是缓存什么时候必须失效:
- 只用「本连接自己写了几次」⇒ 外部进程改库看不见;
- 只用「数据库旁边的预写日志文件变没变」⇒ 有人用别的日志模式改库时根本不产生那个文件,同样看不见;
- 所以缓存键由五维合成:本连接的写入计数 / 预写日志文件的大小 / 预写日志文件的修改时间 / 数据库自身的版本号 / 库句柄本身(最后这一维管「连接被关掉再开」)。任何一维变了就重算,全都一样就直接给上次的结果;
- 其中预写日志与数据库版本号这两类信号取不到时给哨兵值、不抛错(否则状态接口会 500);而「本连接的写入计数」那一维没有这层兜底 —— 它直接问数据库句柄,句柄已关就照常抛(由路由的兜底 catch 变成 500)。对外返回的指纹格式逐字不变(仍是全长摘要,面板只显示前 16 位)。
三、面板新增一块只读面:两条「那一行」+ 四类「为什么没变」
盘上其实有两条长得很像的「那一行」,一条讲过去的自己、一条讲这一轮的进展。此前面板两条都不显示、也不区分:
- 现在各自成行、各自标注:本会话在开场记忆里的那一条(只有标题),与本会话的活行(每轮重算);
- 「为什么没变」拆成四类看得见的读数(面板里逐行标着这一块),每条只印真读数:会话运行中冻结 / 已深摘冻结 / 内容确实没变 / 判据基准不可用。后端没带上来某个字段时明说「不做猜测」,不拿默认值冒充结论;
- 可以展开查看模型那一轮真正读到的注入面全文(纯读,对模型上下文零占用);
- 折叠状态存在渲染闭包里而不是 DOM 上 —— 面板每拍都会重建这一块,存 DOM 会被每一拍清掉。
四、活行档位:从「15 分钟且 10 轮」改为「轮次边界 + 内容真变了才追加」
旧档位是双门:距上次追加 ≥15 分钟且 ≥10 轮。它的问题不是太松或太紧,而是判据与"该不该更新"无关:一轮里发生了大事也可能被按下不追加,什么都没发生也可能到点就追加。
- 现在只有两条:① 只在新轮次的第一批考虑追加;② 内容真变了才追加;
- ②由三道同源判据把关,任一说"没变"就不追加。为什么非要第三道:那一行正文自带一个递增序号,拿整行去比,每试一次序号就 +1 ⇒ 恒判「变了」,闸等于没有;
- 「上一次追加过没有」的留痕附带累计追加次数,重启后判定照旧有效(不会「重启即多追加一条」);
- 旧档位的常量与判定函数保留导出(测试与只读探针仍引用),但探针里明确标出它已不在生效路径上。
五、侧栏「最近改动」不再静默截断
此前取数与渲染两处都写死 6 行,溢出时一个字都不说 —— 恰好改了 7 处时,看上去就是「少了一条」。
- 取数上限提到 100,渲染仍是 6 行;其后条件追加「…另有 N 笔改动(此处只列最近 6 笔)」;
- N 取服务端真计数(与同一块里那句「有 N 笔还没跟器灵说过」同数同界),不是「返回条数 − 6」—— 后者恒等于一个常数(列表恒满),与真实积压无关;
- 计数问不到时的退路:页面满就说「N 笔以上改动」(保留量级,不冒充确切数);
id读不出来的行当作已列出(宁可少报,不谎报);保存快照不算改动; - 零后端改动,硬刷页面即生效。
六、快照状态不再谎报
没有快照时,后端回的是一个「不存在」标记 —— 而它是真值,前端原先照样进「已定稿」分支,把它显示成「已定稿 0 字符」。
七、依赖声明:写紧了,插件会被跳过 —— 线索只有一行,且不是每条路径都打
宿主装载插件时会拿 peerDependencies 过一道 semver 闸门,判据是「宿主版本是否落在你声明的区间里」—— 不是"你的写法合规不合规"。写紧(^ / ~ / 钉死版本)会让宿主落在区间外;开区间同样可能落空(下限高于正在跑的宿主版本时)。落到区间外 ⇒ 整个包被丢进跳过列表、插件不激活。线索分两条路径:从命令行启动会往 stderr 打一行 skipping profile bundle … —— 容易被忽略,但不完全静默;而应用自持 profile 的那条装载路径一个字都不打 —— 那才是真正难查的一种,表象常常只有「它突然不见了」。
- 本版落 12 条
peerDependencies,范围一律开区间(闸门只读其中名字形如@deepseek-ai/dsh/@deepseek-ai/dsh-*的那些 ⇒ 12 条里实际进闸门的只有 11 条); - 这 11 条在本地跑的宿主版本上是通过的(隔离 profile 装完,跳过列表为空)—— 这一节讲的是别把它写坏,不是说本版已经踩在上面;这条证据覆盖到哪一层,见第十二节;
- 客户端注入声明由 5 项收窄为 3 项:删掉的两个包在宿主里没有客户端半边,列在那里是从模板继承来的死边(
⚠️ 那一列装的是包名,不是服务名); - 新增一条开发侧只读自检(不进发布包),把「宿主这道闸门会不会丢弃本插件」变成一条命令能查的事,退出码三态:放行 / 会被丢弃 / 无法判定(无法判定不算通过)。
八、「承担」:人格新增一栏职责
- 人格新增字段承担,注入面段头
[承担],排在「待我回应」与「设定」之间;引导写成陈述句(起草提示与面板都这么建议 —— 保存侧不校验句式); - 空值 ⇒ 整段不渲染(不留一个空段头);
- 诞生仪式的输出契约由 4 键扩成 5 键(新增职责候选):由模型从导入的历史里总结 1~3 条候选,每条 5~120 字、只写一句 —— 起草权归她,但与其它候选项一样只是草稿,终审与保存仍在人格中心;
- 措辞上禁止授权式写法(「我应当 / 我必须 / 我的任务 / 我的责任是 / 我被要求 / 请让我负责」):授权口吻会把她写成一个被授权者,而不是「她自己是什么」;起草提示与面板候选旁都配同款提醒;
- 该字段同步进了保存白名单 —— 白名单以外的键一律不收:会被丢弃,但如实回报被丢弃的键路径(写进日志与响应,不是静默吞掉)⇒「看着能填、保存后像没保存」这件事,在日志里查得到原因。
九、树的改动播报:以前会重播,也会漏播
树的改动会攒成一本账,定期播报给器灵(注入面里的那一段);面板侧栏的「最近改动」读的也是这本账。这本账靠一条「播到哪儿了」的水位推进,而此前这条水位有两个方向相反的毛病:
- 重播:水位按会话存 ⇒ 每开一个新会话都从零开始,最近几条又播一遍;
- 漏播:播报之后水位直接推到全表最新 ⇒ 一次播报就把中间一百多条标成「已读」,而它们从未示人。更麻烦的是这个错不会自己修正:水位一旦顶到最新,扫描的下界就一直卡在那里,那些没露过面的改动再也没机会出现。
现在三条水位并存:全局「已示人」水位(只推进到这次真正展示了的那条)+ 本会话增量水位 + 全表最大 id(这条有意不动 —— 它同时是复盘模式的退出判据,顺手改会让复盘模式退不出去)。超过时限的老改动单独用计数式处置:只报笔数、不展开明细,也不算已示人。
顺手修掉两个由此暴露的旧毛病:低报(笔数原先只在扫描窗口内累加 ⇒ 封顶在 120;本机实测那一次真库 395 条、超时限 368 条,注入面却报「其中 120 笔已超过 3 天」—— 低报 248)与饥饿(窗口被老账占满时,水位每轮只前进 120 行 ⇒ 明细永远渲染不出来,一条也看不到)。
十、测试与计数口径
npm test链登记 36 套,全绿;- 计数口径统一为「链里登记的段数」(不再数测试目录里的文件数),README 与发布门禁两边同口径;
- 门禁新增双向核对:链里登记的路径必须在源目录里存在;源目录下有测试文件却没登记进链 ⇒ 报红(它会被
npm test静默跳过,一条断言都不跑;跑器与被复用的辅助模块不计入这条判据)。
十一、升级提示与兼容
- 升级方式不变:profile 里换版本后重装,或按 README 的安装路径重装一次;包名与客户端注册 id 不要改(
@nightpainters/dsh-ling,两处必须逐字相等); - 若你改过
peerDependencies:别把范围写紧(^/~/ 钉死),也留意下限别高于你在跑的宿主版本 —— 两种情况都会让插件不激活,而线索只有命令行启动时的那一行 stderr(应用自持 profile 的装载路径一个字都不打); - 库结构自动迁移,无需手动操作;本版无破坏性数据变更;
- 本版大部分改动在宿主侧与前端侧,硬刷页面即生效;只有 /state 分档与依赖声明需要重启宿主。
十二、必须写明的未决事实
- 那 11 条
peerDependencies验的是本地打包产物(npm pack出来的 tarball + 隔离 profile 里安装),不是 registry 发布之后的安装。注册表上的解析结果、npm 客户端版本差异、以及「别人在他自己机器上装」这条路径,本版没有验; - 上面那 11 条在
peerDependenciesMeta里全部标着optional⇒ npm 本来就不会替宿主自动装它们 ⇒ 「跳过列表为空」这条证据只覆盖「声明的区间与正在跑的宿主版本相不相容」这一层(闸门读的正是这两样),不覆盖「这些包真被装上、且与宿主是同一份实现」—— 后者本版没有单独验; - 面板里那块只读面的实时性有两条已知残留:① 全局视图最快 30 秒才动一次(全局那条轮询被有意降到 30 秒,这是「不新开轮询」的必然结果,不是没修);② 面板开着期间,标题 / 人格 / L0 预览 / 记忆库 / L1 这几块都不自动刷新(面板自己那行分隔说明就是照这五项列的)—— 面板只在打开那一刻取一次,逐行标着「打开时快照」;切换会话也不刷,那几块仍停在旧会话那份(只有实时行会跟着变);
- 上面第一节的耗时数字来自本机一天的运行日志(含面板请求与轮询请求的混合流量,未按来源拆分),不是纯净的基准测试;
- 「…另有 N 笔改动(此处只列最近 6 笔)」的退路口径是「N 笔以上」:真计数问不到时只保证量级,不保证确切数。
dsh-ling v1.5.1
1.5.1 — 修复项落地、一处平台升级事故、安装路径变更
本版把 1.5.0 之后那轮内部代码审查得出的修复项逐条落地并单独验证;期间揪出一处「平台升级把事件改名,导致记忆与人格注入整段静默失效」的事故。包名同时变更 —— 升级前请先读最后一节。
一、最该先看:一个改名,让注入整段静默失效
DSH 0.1.7 把事件 agent/session-start 改名成 agent/created(载荷形状不变)。器灵监听的是旧名 ⇒ 在 0.1.7 上零命中 ⇒ 会话定稿从不发生 ⇒ 注入只送出时间锚,记忆正文与人格全部空白——无异常、无日志、无告警、库里零痕迹。
lifecycle.js两个名字都注册 + 3 秒短窗口去重(跨平台版本,不赌它保留哪一个);- 验收:
branch_log_wm.last392 → 395,注入正文回来; - 教训:平台契约不只是字段,还包括事件名 —— 凡
ctx.on('<事件名>'),平台升级后必须 grep 一遍这个名字还在不在。
二、会话历史:原先只看得到三成
会话文件名随平台版本变化(session.jsonl.zstd / session.v3.jsonl.zstd / session.v4.jsonl.zstd),器灵原先写死了第一种 ⇒ 在一次真实使用中只认得 53 个会话、漏掉 124 个(去重后共 177 个)—— 也就是说,原先只看得到约三成。
这三个数是修复当天的观察快照。会话目录会持续增长,你在自己机器上测到的数不会与它相同;判据是「候选数显著变大、解码失败为 0」,而不是复现某个具体数字。
- 改为按
session(.v\d+)?.jsonl.zstd取版本号最高的一代,同一会话只出一个候选,天然去重; - 候选数 53 → 177、解码失败 0 ⇒ 新旧格式内容形状完全兼容,不必做格式适配;
- 顺带修掉让这个缺陷静默两周的真病灶:「0 候选」与「这台机器没有历史」原先不可区分,现在显示
⚠+ 一行具体原因,并列出目录里实际见到的文件名。
三、本版修复的 8 个问题
- 补丁锚点多 cohort 化:恰好一个唯一命中才动手;歧义拒绝改动、漂移退出码 2(宁可不动,也不在核心代码里插错地方);
- 器灵记自己的端点:不再给核心打第三条补丁,改为在自家
guard上挂 observe(受控条件下从 0 行覆盖到 4 行); - 时间锚出口补
neutralizeMustache;回灌写入侧统一走rawSessionId(同一类缺陷在历史上第三次出现); - 主脉记忆采纳时备份后覆盖并落总变动日志;提炼素材排除已遗忘条目;
- 游离
.bak会卡死发布前检查 ⇒ 移入归档区;「↶ 撤回」状态改存 ref(原先被重渲染清空)。
四、rejection 不再误标 · 设备标识不再落凭据原文
rejection 原先从状态码反推(403 ⇒ untrusted-host),而器灵自己拦下的请求也进日志之后,7 种拒因里 6 种被误标。现在真因优先,读不到才退回旧判据 ⇒ 取值域从 3 类扩到 9 类(no-host / bad-host / untrusted-host / cross-site / origin-mismatch / no-cookie / empty-cookie / bad-cookie / method-not-allowed)。
- 日志里的
deviceId是设备凭据的原文 ⇒ 改为 sha256 不可逆摘要(取前 16 位十六进制):仍能回答「是不是同一台设备」,但不再持有可用的凭据。
五、归档 / 遗忘 / 回灌(此前是零测试覆盖的裸区)
- 新增
tests/forget-backfill.test.mjs(307 行 / 86 条断言):四段对账(行数守恒 / 召回 / 撤销 / 回灌往返 hash)+ import 源单独断言(裸 id 零出现,防跨源污染复发); - 回灌不再丢标题锁:底层
upsertOverview原先不带title_locked/title_by⇒ 硬删后回灌,「这是用户亲手定的名」这个状态会丢。现在改为调用方显式给了才写,概述器与批量改名不会顺手解锁; forget空归档守卫,两处 fail-closed:归档侧给出empty判据(0 轮原文 + 无 summary + 无 sessionMeta),数据层再加一道验盘闸门(无归档路径 ⇒ 拒;未声明轮数 ⇒ 直接数盘上的turns.jsonl;声明 0 ⇒ 拒)。旧行为下「条目还在、原文已被硬删」会拿到ok:true、打上永久遗忘标记,而回执谎称已归档。
六、工具面、主脉提炼、前端与注入面
- 工具回执改印全长 id:原先回执印短 id、输入侧却不认 ⇒ 工具会在
no-match上打转(提示语还在教「用概览行尾那 8 位」);link校验两端存在性(悬空边唯一的产生口,挡在写之前,并说清是哪一端);weight回执改回库里的值(原先请求 99、库里 2、回执说 99);非空枝删除把impact四个数字念出来;reparent缺parentId明确报错(不再静默挂主干);forget/backfill的source走白名单,且目标必须真的存在; - 提炼并发锁:
/vein/distill原先没有任何单实例锁,连点两次 = 两次模型调用 + 同一主脉两条候选;现在复用本仓既有的BATCH_RUN/batchEnter(与/conflicts/judge、/tree/autobuild/name同一套),抢锁在读 body 之前、finally放锁、并发第二次回中文busy; - 悬空候选不再谎报成功:删掉主脉后采纳悬空候选原先回
ok:true+ 一个已删枝的 id;现在采纳前做真实存在性门、写完再复核归属; - 锁的语义说清楚:用户手改过的名字不会被覆盖这一点现在明说(回执带
naming{title, titleBy, locked}),不再让人以为正文也被保住; - 三处吞错(断边 / 忽略候选 / 器灵概览):原先不看
r.ok、也没有.catch⇒ 后端明确回了失败,界面照样报「已断开 / 已忽略」。现在统一走一个共用判读器 + 中文原因; - 待审核候选全量渲染:原先标题写「待审核 20 条」、下面只画 6 张卡 ⇒ 14 条永不可达(前端硬截断 + 请求写死
limit=20);现在请求上限提到 100; - 判定循环的忙锁口径:原先固定 180s 时长上限,短于一轮判定的最坏耗时 ⇒ 到点后可能并发起第二个循环。现在改成「无进展容忍上限」,循环每轮续期;
- 审核卡片的 key:下标当 key + 非受控
defaultValue⇒ 列表缩短时可能「显示 A 的正文、采纳成 B 的记忆」;并脉 / 连边的下拉也是常量 key,而「并到」按钮读的正是那个 DOM 值 ⇒ 可能静默并错枝。一并改按条目身份立 key; - 删掉骗人的 Ctrl 多选:记忆中心的提示一直教「Ctrl+单击 = 加选/减选」,而那个染色选中集从无任何消费者 ⇒ 提示与实现一起删除,不再骗人;
- 注入面排版:规矩 / 习惯两个段头原先带着写给用户看的括号说明一起进了模型上下文(
[规矩](用户的指令,可直接追加)),而其中「可直接追加」读起来像一条授权指令;段与段之间没有空行;条目里的方括号原样拼入 —— 可段边界的判据恰恰是「行首是不是[」。现在段头只剩纯标签[规矩]/[习惯],底线:也统一成[底线](原先三个段头三种写法),段间空行分隔,规矩 / 习惯 / 底线三类条目里的方括号统一做全角转义,渲染出口再按同键去一次重(键的归一化只忽略空格与中英标点差异;只保留首次出现、顺序不变、仅在渲染期生效,不动存储); - 记忆树改动播报不再报假数:那句「…另有 N 笔改动」的 N,原先是两个写死上限的差值(拉取窗口减去展示条数),与真实剩余无关 —— 积压越多,它反而越固定地停在同一个数字上。现在改真实计数(一条走主键范围扫描的
COUNT,代价与积压量无关);
七、安装与分发(本版变了)
- 包名改为
@nightpainters/dsh-ling—— npm 上dsh-ling已被他人占用; - 加
publishConfig.access = "public"(scoped 包不显式声明会默认 restricted); - README 补两条安装路径:官方入口
dsh plugin --profile web add @nightpainters/dsh-ling(dsh plugin是转发给 profile 目录里的 pnpm,--profile必填、名字按你的安装来,常见是web),以及源码安装(pnpm add file:<路径>/dsh-ling); ⚠️ 注册 id 仍是dsh-ling,只有被 import 的模块说明符用新包名 —— 这是最容易配错、且配错就整片前端不激活的地方(客户端半边逐字比对「上溯到的package.json的name」与cordis.patch.yml里的name)。
八、升级提示与兼容
- 每次 DSH 升级后都要重跑补丁脚本(旧规则照旧适用):访问日志那两行补丁位于
node_modules内的宿主 core,DSH 升级会覆盖掉它。补丁缺失时插件启动会console.warn,宿主照常启动(那两行是 no-op);node tools/apply-access-log-patch.mjs支持--check/--revert,幂等可重放; - 包名迁移:profile 里登记的若是旧包名
dsh-ling,把那一行的name换成@nightpainters/dsh-ling,或直接用dsh plugin --profile web add @nightpainters/dsh-ling重装一次;id: dsh-ling不要改(改了前端整片不激活); - 库结构自动迁移,无需手动操作;本版无破坏性数据变更。
九、README 补了一节「本项目是什么形态」
最容易被人误解的两件事,这一版直接写在项目介绍之后:
- 它是本地运行的软件:跑在你自己的机器上;上游不提供任何在线服务、不运营服务端、不托管你的数据(作者不接触、不接收你的会话内容与记忆库);模型与 API Key 由使用者自行提供。分发只有两条路 —— GitHub 源码与 npm 包,不上架任何应用商店;
- 它不定义你与它的关系(定性归属);回答与记忆摘要由人工智能生成,可能存在错误,请自行判断(AI 生成标识);面向具备自行配置与运维能力的用户,不面向未成年人,也不提供在线陪伴服务(面向性)。
另外,全树统一了「定型门」的说法:原先 README、代码注释与设计文档里混用「仪式感 / 仪式」,现在一律写作庄重感,与代码里既有的「锁只造庄重」同口径 —— 语义零变化。它依然是庄重与自我提醒,不是安全边界;真正把门的是来源栅栏(配对 cookie 与本地令牌),不是那句承诺。
v1.4.1 — 修复与复盘守门
1.4.1 — 审计修复与复盘守门
本版是一次质量加固版本:把 1.4.0 红蓝对抗审计发现的中危项修完,并让「改动记忆树」变成一件必须被器灵看到的事。
一、红蓝对抗审计的中危项(27 条)
三路并行审计共记录约 40 条发现。1.4.0 发布前已修 8 条高危,本版修完其余中危项:
最值得记住的一条高危:宿主 WebRoute 契约里没有 method 字段,插件若不自行校验,任何 GET 请求都能执行写端点。已用 POST_ONLY 白名单(72 个写端点)封堵,非法方法回 405。
数据层
branchMembers两个查询改共用总预算(原先各吃一份 limit)- 新增
branchMemberCounts(),与展开用同一套谓词 resolveConflict加存在性检查tree_snapshot加kind('manual'|'auto')+ 迁移 + 最后一份自动快照拒删reparentBranch/setConvBranch加枝存在性校验
端点层
- 写 0 行 →
{ok:false,'no-match'},且不写假留痕 judge/name加模块级单实例锁 + 整批 deadlineapply校验parentId(跳过计数)与bucketId(去重)assign两侧存在性检查
前端
- 拖动源改用 ref 记录 —— 修好「撤回 0 项」
- 拖到会话行时解析其所属枝
- 失败不再假报成功
- 钩子清理 + busy 超时兜底
- 删除 140 行死代码与双真值
二、注入感知水位改为按会话
原实现的水位是全局键:一个会话消费过的改动,其它会话再也看不到;且「只重建快照」的路径也会消费水位 —— 结果是主人改的东西可能永远不被看到。
现在:渲染只读、投递才推进。
- 按会话水位 → 决定「这个会话看到什么」
- 全局水位 → 决定「是否还没跟器灵交代」
新增 10 条断言覆盖该语义,其中一条是判决性的:渲染期间写 kv 的次数 = 0。
三、复盘守门:改动必须让器灵看到
「互相复盘不应该被我绕过,动记忆树不能是随意的,应该是慎重严肃的。」
- 侧栏显示未同步笔数(金色警示块)
- 未同步时不允许退出复盘模式
- 归零的唯一途径是器灵真的开口(注入推进水位)
- 退出前实时查一次服务端,避免界面旧数字误拦
四、留痕显示修正
branch_log 的 before/after 按动作装不同东西(枝 id / 快照号 / 记忆条目 id),原先一律回退成「(已删枝 …)」——「保存了快照 #9」会被显示成「已删枝 #9」。现在按 id 形态分流,并补了 snapshot / restore / assign / delete 四个动作的中文措辞。
五、测试与兼容
- 29 套全部通过
- 库结构自动迁移,无需手动操作
- 首次运行本版时,「未同步笔数」会以当前最新留痕为起点(历史改动不计入),避免一次无意义的全量假警报
DSH-Ling-v1.2.0
1.2.0
记忆质量:网页端补摘要 · L1 摘要加权与命中记录 · L1 行压缩 · 归档语义收紧
一、归档语义收紧(不再自动删数据)
- 归档即停更:会话归档后不再刷新它的开场记忆条目,原文一律保留;该会话一旦有新对话即自动复活并重建条目;
- 取消自动清理 —— 归档不再删除任何概述;清理只保留人工入口:
node tools/clean-archived.mjs(默认只列清单,--write才动手,动手前自动VACUUM INTO备份),或在 DSH「设置 → 归档会话」里直接删; - 新增单测第 21 套(归档清理 · 人工)。
二、L1 质量:摘要加权 + 命中记录
- 新增
memory.summaryBonus(默认0.25):只有真摘要的行加分,标题 — N 条消息这类自动计数摘要不算; - L1 命中记录:首次定稿快照时记录命中,
hit_count从此有真实含义(此前恒为 0,热度公式里的"频次"腿长期失效);重建/失效/空闲刷新都不重复计数。
三、网页端历史补摘要(新)
- 新模块
lib/host/dsweb-summary.js+ CLItools/backfill-dsweb-summary.mjs(默认干跑;--list/--write/--only-hit/--min-turns/--all); - 界面:记忆中心 →「概览与数据」新增卡片 —— 候选预览 / 双引擎选择(本机小助手零成本 · 当前全局大模型)/ 进度轮询 / 重开面板自动续看;
- 绝不静默切换引擎:本机小助手探测失败时明确报错,由你显式改选,不替你花钱;
- 首轮实测:候选 469 条 → 466 条全部成功、零成本、20.4 分钟;
- 端点修复:本机小助手需走 Ollama 原生
/api/chat+think:false(OpenAI 兼容端点关不掉思考,会把 token 预算吃光导致正文为空); - 新增单测第 22 套(候选排序 / 正文组装与预算 / 清洗 / 探测失败路径 / 重试)。
四、L1 行压缩(两段结构 + 允许缺省)
- 有摘要 →
标题 —— 摘要第一句(第一句按中文标点切;超 110 字时硬截并补…); - 无摘要 → 只出标题,不截(信息量优先);
l1MaxLineChars(180)降级为"病态长标题兜底";新增配置memory.l1SummaryChars(默认 110);- 实测:同一记忆库下 L1 八条 1218 字 → 1046 字。
五、界面文案
- 聊天记录库路径缺失时的提示改为指路式(可在本卡片填,或先在上方「网页端历史增量扫描」栏填一次,路径会被复用)。
六、测试
- 新增第 21、22 套 → 共 22 套。