Repository navigation
Releases: Kanadego/dsh-heartbeat
Release list
v2.0.1
v2.0.1 · 2026-10-25
界面说起英语了。设置卡、状态卡与报告面板里的文案全部改走 Harness 的 locale 服务——Harness 切成英文,心跳的界面跟着变成英文;而中文界面,一个字都没有动。
新增:客户端界面双语
以前心跳的界面文案硬编码在 client.js 里,Harness 切到英文时,插件这部分仍然是满屏中文。现在:
- 157 条文案收进
heartbeat命名空间,中文 / 英文各一套; - 语言切换即时生效,不用重启、也不用刷新页面;
- 设置行、状态卡、素材池、用户画像、周报、迁移——界面上能看到的都在里面。
字符串之外,有几个拼接碎片顺手改成了模板:像「共 3 / 6 字符(在预算内)」这种句子,以前是由七八段字符串拼出来的,现在是一整句模板加几个占位符。
保持不变:中文逐字未改
这一版只搬字符串,渲染结果与 v2.0.0 完全一致:
- 中文文案一个字都没有改;
- 设置行的标签仍然是函数,所以切语言能触发重渲染(写成预求值的字符串就会丢掉这个能力);
- 素材池、报告面板里的状态符号(
☑/☐)拆成了符号加文案,两种语言下都还是原来的样子。
不受影响:数据与宿主侧
- 数据目录、配置项、RPC 接口都没有动,升级不需要任何迁移;
- 心跳报告正文(引擎室那份)仍然是原来的中文口径——宿主侧定时生成时没有请求上下文可读,「界面语言该不该影响报告语言」是另一个问题,另行处理;
- 插件声明支持的最低宿主版本不变:
locale服务从 DSH 0.1.1 起就有(已逐个版本验过),万一取不到就退回内置中文,不会影响界面启动。
致谢
由 @miztizm 提出(issue #3)并实现(PR #5)。
验证
- 全量测试 378 条通过;
tsc --noEmit零错误;node --check client.js通过;- CI 额外核对了
dist与源码一致。
安装/升级方式不变(npm 或 GitHub release)。
v2.0.0
v2.0.0 · 2026-10-17
两个新功能(心事匣、摘抄本)+ 第二次全量代码审查(22 项主发现 + 一节低优先级观察)的修复与结构整改。
审查报告与逐条实验证据见 CODE-REVIEW-2026-10-17.md。
心事匣:跳与跳之间的那道缝
心跳以前每一拍都是「重新认识自己一遍」——上一拍想到一半的事、咽回去的那句话,没有地方放。这一版给它一格便签。
它是一格,不是队列。 新念头盖旧念头,由每拍主动决定:想留就照原样再写一遍,想说新的才换掉。存储恒定,不随拍数增长;一条心事连着好几拍都没被盖掉,这件事本身就是它一直被想起的证据。
投递素材包里带上它。 引擎室的反刍备料包里没有这块——只有投递给目标会话的素材包会附带三行:
(你上跳留下的心事:那句没说完的话)
(留于3 跳前,已搁 2 小时 15 分,期间被叫醒过 1 次;还惦记就调 notepad 照原样再写一遍,想换新的直接写,什么都不做它下一跳就没了)
(照原样再写一遍算保留,它的年龄不会重算)
生命周期靠两个序号判定,不靠时间戳——时间戳会被新消息重置,算不出「搁了几拍」:
- 放掉的条件是「被读过的那一拍,比最后一次触碰还晚」(严格大于)。本拍才写下或才保留的便签从没被读过,不能被刚送出它的那次投递吃掉;
- 启动那一拍不加序号、也不丢东西。DSH 每次启动都会先跑一拍,如果它算数,「搁了几拍」会被重启污染;
- 同文本再写一遍 = 保留(
leftAt/ 留于第几拍 / 被叫醒的起点原样留着,只记「这拍碰过它」);写别的文本 = 换新的,年龄从头算。
注册成一个模型工具 notepad(write / read / clear),走和账本工具同一条路:注册在插件根上下文 → 落在全局工具层,日常会话里的 agent 看得见;引擎室被自己的预设遮住,看不到。
数据落 data/notepad.json(加密,和别的敏感文件同一套)。读写出错只记一条 notepad_read_failed / notepad_write_failed 留痕,绝不让一格便签把整拍弄挂。
摘抄本:她自己的笔记本
心事匣管的是「这句话要带到下一拍」,账本管的是「这件事要做」。剩下一类东西一直没有地方放:看见就想留下来的句子——不为做什么,只是喜欢。这一版给她一本摘抄本。
它是一本,不是缓存。 只增不减,除非她主动抹掉;没有状态、不会过期、不参与任何判定,写进去就是留着。
明文落盘(data/excerpts.md)。敏感数据默认加密,这本和账本一样走明文,理由也是同一个:一本谁也翻不开的笔记本不叫笔记本(明文里当然还有游标、计数、日志——但那是机器状态,没有人要读)。设置卡里加了「一键打开摘抄本」,她抄下的那句你随时能翻、也能自己添两行;认不出的行在改写时原样留着,手编不会把她的东西弄乱。
注册成一个模型工具 excerpt(add / list / remove),和账本、心事匣同一条路:注册在插件根上下文 → 落在全局工具层,日常会话里的 agent 想到什么就当场抄下,不用谁下命令;引擎室被自己的预设遮住,看不到。
三本东西的分工也写进了工具说明,免得她自己拿错:
| 本子 | 记什么 | 结局 |
|---|---|---|
账本 data/ledger.md(明文) |
待办与承诺,你和 agent 共享 | 做完勾掉,条目仍留在文件里 |
心事匣 data/notepad.json(加密) |
留给下一拍自己的一句引子 | 一格,会被覆盖;不保留就下一拍清掉 |
摘抄本 data/excerpts.md(明文) |
想长期留着的句子和小事 | 只增不减,除非她主动抹掉 |
加密换代:AES-256-GCM + DPAPI 保管的数据密钥
数据文件以前逐个交给 PowerShell 调 DPAPI 加解密——每读写一个文件就起一次进程,单次最长等 30 秒;全量测试里有 97 秒花在这上面。
现在:数据用 AES-256-GCM 整文件加密(含认证,改一个字节就解不开),数据密钥由 Windows DPAPI(CurrentUser)保管在 data/vault.key,进程启动解一次、之后不再走 PowerShell。旧版 1.x 的 DPAPI 容器(KHBV1)照旧能读,新写的一律是 KHBV2:
- 旧数据不会变成死数据——读路径两代都认;
- 旧容器平时就会自己换代:任何一次写入都落成新格式,每一跳的维护相再顺带换掉长期没动过的一个;
vault status报告各代数量,vault upgrade一次全换; vault.key存在但解不开时直接报错,绝不另生成一把(否则盘上旧数据全成垃圾);- 加密文件解密失败时抛错而不是返回「空」——避免调用方把
null当空库覆盖掉真实数据。
⚠️ 回退警告:KHBV2只有 2.0 及以后认识。升级之后不要再退回 1.x;要退回就趁早——跑过vault upgrade、或让数据写过一轮之后,就回不去了。
副产物:全量测试从 97 秒降到 12 秒。
遗忘与销毁的边界
profile wipe 以前只删 profile.json / profile_inbox.jsonl / profile_journal.jsonl,而画像的 journal 快照、归档分片、迁移备份都还在盘上——下一次重放会把画像原样长回来,用户以为「清掉了」。
wipe现在是真遗忘:live 三个文件 + 快照 + 归档分片 + 迁移备份与导入暂存残留一起删(仍不动设定、账本、周报、日志);burn的清单补上了迁移备份族(*.bak-migrate-<时间戳>)与导入暂存残留(*.incoming-<时间戳>),任意子目录层级都认。
迁移:整包全或无
导入迁移包时,以前会跳过不合格的文件、把其余部分导进去,结果可能是半个数据库。现在:
- 只有白名单里的名字能落地(
ledger.md、画像与 journal 归档、weekly/*.json等),路径越界或名字不在名单上的直接判整包失败; - 单个容器有预算上限(5000 个文件 / 64MB),超了在解密前就拒绝;
- 暂存阶段只要有一个文件被拒,整包不导入,已暂存的一并清掉,本地数据原样不动;
- RPC 侧不再把带
error的结果当成功返回。
记忆的正确性:流水事件化 + 证据由代码生成
画像的 journal 以前记的是「模型这一轮给的操作」,重放时用一套更宽松的规则重新解读——于是重启前后的画像可能不一样,老化(易变条目到期、低活跃标记)也从不入流水,快照会把偏差固化。
现在 journal 记的是「规范化之后的最终事实」:
- 每轮记录本轮写入/变更条目的完整终态,重放就是按 id 原样回放——实时路径与重放路径完全一致,老化也成为可重放的事件;
- 旧的 v1 流水照旧能读,新旧混在同一个文件里也逐行判定;
- 读不出来的坏行被计数暴露(
badRecords),不再静默跳过。
证据由代码生成:模型只能引用本批次观察的编号(o1、o2…),引用位置、时间、来源种类(chat / screen / browse / hand / ledger)一律由代码填。此前模型可以自造引用、或自称来源是「手写」,把一条新记忆直接钉到信心上限 1.0。
其余记忆层修复:
- 观察不再丢(合并期间到达的观察):一轮合并只清掉它真正读过的观察,不再整文件清空;
- 跨进程锁失败关闭:等不到锁就跳过这次写入并留痕
inbox_locked,不再「等 3 秒没等到就照写」;陈旧锁要同时满足「够老」与「持有进程确实不在了」才被回收; - 被拒的更新不再改正文:信心升级校验失败时不再留下已经写进去的新正文。
执行取消链
超时以前只是「不再等待」——原任务继续跑、继续写盘;看门狗到点只清了「正在跳」的标记,旧的阶段仍在后面兑现。
现在 AbortController 贯穿每一跳的所有阶段:模型调用、PowerShell 探测、视觉识别子进程、投递前的复查都接同一条取消链;超时、看门狗与插件卸载会真正取消它们,审计留 beat_aborted(带阶段与原因)。
画面与投递
- 画面不认旧图:本跳没截到图(采集失败)时,取消本跳的画面资格并烧掉盘上旧图。新增
screen.capture印章(只存本跳时间戳),印章、screen.json与截图三者对得上才解密使用——以前只要文件在就解密,可能把上一跳的画面当成现在的; - 投递按说话时刻记账:写入前完整复查(静默时段、每日上限、冷却、忙窗、是否正在打字),复查条件变化也不再影响账——话已经在对方那里了,拒绝只说明条件变了,绝不表示「她没说话」。
RPC 通道失败关闭
浏览器数据通道的鉴权依赖宿主提供的闸门。宿主没提供时,以前只拒 6 个破坏性端点,等于把画像摘要、分区、素材池、周报正文、迁移导出全向 loopback 敞开。
现在整个通道失败关闭:只放行 status / config.get / sessions.list / bindings.get / weekly.list 五个低敏只读端点,其余拒绝并留痕 rpc_host_gate_missing;端点表改为无原型对象,constructor、__proto__、toString 这类名字不再能穿透。
引擎室的工具面:复用路径漏网(已修)
引擎室的工具面靠一条白名单收窄:闲逛相唯一需要的 web_search,加上装了 billion-context 这类插件时的压缩三件套。这条白名单原先只在插件自己拉起 agent 时钉(setup 钩子),而 ensureAgent 还有第三条获取路径——取到已存在且活着的 agent(agent_reuse_live,会话正开着时就走这条)——那条路径不跑 setup,于是插件重启后的每一跳都带着全局层全量在跑。2026-10-17 实测:模型看到的是 62 个工具(shell、邮件、撤销、qqbot 全在里面),而不是应有的 9 个,那一跳的决策轮还因此撞了 240 秒超时。
修法是把白名单那段抽成 pinEngineRoomTools(deps, agentCtx, notes),复用分支也调一次——宿主 agent 对象本身带 agent.ctx,不需要重建 agent。结果写进同一行 tool_policy,前缀 reuse。 当日 22:05 实测撞上这条路径并确认:agent_reuse_live 那一跳写出 reuse restrict=ok,同跳送给模型的工具面正好是 9 个(此前同类场景是 62 个)。
节律配置:重启不再被默认值掀掉
timeInjectMin / statusbar / idleMode / tokenSaver 这四个字段原先在 plugin 配置 schema 里带默认值(statusbar 是 false)。问题在于启动顺序:插件先读 ui.json(卡片写的),随后 applySettingsOverrides 又拿解析后的配置盖了一遍——而带默认值的配置永远有值,分不出「用户明确设成 false」和「用户没设」。于是每次重载,卡片里的选择都会被默认值改回去。实测症状:在卡片里把「状态栏」打开,重启 DSH 后它自己变回关。
现在这四个字段不再写默认值:未设 → 用 ui.json → 再落到出厂值(statusbar 出厂关,v1.9.3 起的口径不变)。配置里真填了值仍然优先,factory policy < user policy < ui.json < Config fields 这条顺序不变。
投递目标不再以裸 agent 发言
目标会话没活着时,投递会先 resume 一个临时 agent 替它说话。那次 resume 原先没有 setup,于是 agent 是裸的:不加入任何预设,工具按进程全局层解析,呈现模式退回 native。跑起来的效果是——那一轮的工具面完全不是这个会话平时的样子(实测:从 1 个 run_code 变成 61 个,投递结束再变回来,GUI 因此在对话中间弹了一次工具变更提示),而且那一轮调不到 run_code。
现在会从会话自己的事件流里读出它加入过哪个预设(agent-preset/selected),把同一个挂回去再发言;会话从没记过预设、宿主没有预设服务、或挂载被拒,都退回原来的行为并写一行 deliver_target_preset 留痕。
表述与预算
- 「判『别说』时一个 token 都不花」过宽——闲逛相在闸门之前触发。文档改为「之后的决策与表达两相不再调用模型」;
- 账本块与摘要加了长度上限,超预算的条目整条跳过,不再从句子中间切断;
- 闲逛搜索的年份不再写死 2026。
工程卫生
- 所有外部输入(模型输出、流水读入、迁移包、RPC 载荷)统一用 schemastery 校验形状,未知字段与缺失必填都被拒;
- 原子写先
fsync再改名;加密临时窗口的崩溃残渣在首次使用时清理; - CI 增加构建检查、
dist一致性检查与浏览器端client.js语法检查——检查当场发现跟踪的dist早已过期(npm 会发出与源码不符的包),已重建; - 新增 38 条回归测试(审查修复部分)。
一并带上:v1.9.3 的内容
v1.9.3 改完了但从未单独发版,所以它那批改动会随 2.0.0 一起到用户手上:
- 素材包可以留白:判断从「挑哪条说」变成「要不要开口」,决定沉默时正文留空,只记一次「这轮没开口」与原因;
- 摘要预算分区:送进引擎室的画像摘要总预算 3200 → 6000 字符,tact / topic / wander 各有上限,超预算整条跳过;
- 出厂默认更省:心跳间隔 60 分钟、每日开口上限 10、状态栏默认关闭(已保存的设置不受影响);
- 节省 token 模式能唤醒:暂停期间每 3 分钟探一次闸门,绑定会话出现新消息立刻恢复;
- 周报保留期数:
policy.weekly.keepReports(默认 3); - 设置卡重做:画像四个分区 + 只读的「引擎室输入」视图、素材池四个按钮(含归档),列表都加了滚动区。
升级提示
- 数据目录与旧版本一致,升级不需要迁移任何东西;
- 旧容器照常可读,想换成新容器跑一次
vault upgrade即可(可选); - 升级后不要退回 1.x;
- 安装/升级方式不变(npm 或 GitHub release)。
验证
- 全量测试 378 条通过(v1.9.3 的 304 条 → 审查修复后 344 条 → 心事匣 17 条 → 摘抄本 14 条 → 复用白名单 2 条 → 投递预设 1 条 → 378 条);
tsc --noEmit零错误;- 宿主版本兼容性与 v1.9.3 相同。
v1.9.3
v1.9.3 · 2026-10-17
让心跳学会闭嘴,也让它记得更久。这一版给素材包加上「留白」——决定不说的时候,正文就是空的,不再为了凑一句话去挑一条不相干的素材;同时把摘要预算拆成分区、给周报加上保留期数,并把设置卡片整个重做了一遍。
新增:素材包可以留白
以前每一轮心跳都必须挑一条素材开口,挑不出来也得硬挑,结果就是偶尔冒出一句和当下毫不相干的话。
现在素材包的语义变了:模型的判断从「挑哪条」变成「要不要开口」。决定沉默时,正文直接留空,只在账上记一笔「这轮没开口」以及原因——不花冤枉的输出 token,也不制造噪声。
留白是否生效取决于模型的指令遵循能力。如果想更稳,建议在系统提示词或长期记忆里补一条对应规则;模型照做了效果就很好,不照做时仍会退回旧行为。
新增:摘要预算分区
摘要(digest)的字符预算从 3200 提到 6000,并拆成三个分区各有上限:tact(分寸)/ topic(话题)/ wander(闲逛)。超预算的条目整条跳过,不再从句中截断——截一半的句子比没有更糟。
新增:周报保留期数
policy.weekly.keepReports 默认保留最近 3 期周报,超出的自动清理并留痕(weekly_pruned)。以前周报会一直堆在数据目录里。
修复:节省 token 模式下叫不醒
启用节省 token 模式后,心跳靠定时探测等待用户回来。原来的探测间隔太长且没有恢复钩子,用户回来了心跳还挂着。现在:
- 每 3 分钟探一次;
- 绑定的会话一有新消息立刻恢复心跳;
- 状态栏在此期间显示「你不在,心跳挂起」。
调整:出厂默认值
新装的默认节奏变了,升级后不会自动改你已经保存的设置:
- 心跳间隔 60 分钟;
- 每日表达上限 10 次;
- 状态栏默认关闭。
想保持旧行为,去设置卡里手动调回去即可。
界面:设置卡片重做
- 用户画像拆成四个分区(兴趣 / 进行中 / 沟通偏好 / 心理),另加一个只读的「引擎室输入」视图,用来核对哪几条画像真的进了引擎室;
- 素材池改成四个按钮:话题素材 / 聊天素材 / 话题·归档 / 聊天·归档。归档不再单独占一块,点进归档条目按钮会变成「恢复」;
- 引擎室输入视图和各列表都加了滚动区,条目多了不再把卡片撑长。
升级提示
- 数据目录与旧版本一致,升级不需要迁移任何东西;
- 出厂默认值只影响新装,已保存的设置原样保留;
- 安装/升级方式不变(npm 或 GitHub release)。
验证
- 全量测试 304 条通过(v1.9.2 的 300 条 → 304 条);
tsc --noEmit零错误;- 宿主版本兼容性与 v1.9.2 相同。
v1.9.2
v1.9.2 · 2026-10-10
把用户数据从包目录里搬出来,并修掉一个会让整个 DSH 起不来的预设声明。这一版没有新功能,两条主线都指向同一件事:升级之后,数据还在,宿主还能起得来。
修复:心跳模式预设改为运行时注册
以前心跳模式的预设在包里的 cordis.patch.yml 里声明宿主包 @deepseek-ai/dsh-agent-preset。这要求 profile 自己能解析到这个包,于是有两种死法:
- profile 解析不到 → 整个插件树加载失败,DSH 起不来;
- 手工把包补进依赖 → 宿主的装载机制出现两份实例,之后任何会话都无法恢复(报
prompt section "deployment:persona-prefix" is already registered)。
现在预设定义活在插件自己内部,加载时经宿主 API 运行时注册——由声明方自己提供定义正是这个接口的用途。宿主没有这个接口时只记一条审计日志,绝不让宿主挂掉;老宿主仍走原本的自装路径。
修复:会话恢复失败不再无限重试
旧格式会话迁移被拒时属于永久性失败,以前和网络抖动一视同仁地退避重试,结果引擎室被反复重试拖住、永远空转。现在这类失败单独识别:当场重建会话并把正身指过去;真正暂时性的错误仍照旧退避。
修复:数据目录默认移出包外
默认数据目录以前是包内的 data/,而它在 node_modules 底下——dsh plugin add、pnpm add、以及每次升级替换包目录,都会连带删掉画像、素材池与正身记录(原本只有显式 burn 才会销毁)。
- 默认位置改为
<DSH_HOME>/heartbeat-data(一般是~/.dsh/heartbeat-data),在包外,重装 / 升级 / 卸载都不碰。 - 想换位置:插件的
dataDir配置项,或环境变量HEARTBEAT_DATA_DIR。 - 老数据自动搬迁:v1.9.1 及更早装在包内的
data/,升级后首次启动自动搬出去——先备份、再逐文件校验(文件清单、大小、sha256 全部一致),全部通过才删原件与备份;任何一步失败都原地不动并留痕。 - 显式配过
dataDir或HEARTBEAT_DATA_DIR的一律不搬——你自己钉过位置,插件不擅自改。
升级提示
- 不需要手工迁移任何数据,装上即可;如果你原来用的是默认位置,首次启动会自动搬迁。
- 想确认搬迁结果,看日志里有没有
data_dir_relocated(成功)或data_dir_relocate_failed(失败并原地保留)。 - 安装/升级方式不变(npm 或 GitHub release)。
验证
- 全量测试 300 条通过(v1.9.1 的 282 条 → 300 条,净增 18 条);
tsc --noEmit零错误;npm run build成功;- CI(Windows runner:
npm ci→ 类型检查 → 测试)通过。
v1.9.1
v1.9.1 · 2026-10-07
一次全量代码审查,86 项修复:没有新功能,也不改用法——改的是代码结构与一批可靠性问题。其中三条最值得知道:心跳卡死有了看门狗兜底、说了话不会再丢账、观察游标换成了不会错位的记法。
修复:心跳卡死不再需要重启
某一跳如果卡在模型调用上,插件以前会一直以为自己在跳、之后再也不动,只有重启才好。现在超过 45 分钟强制收尾并记 beat_watchdog,下一拍照常(不补跳)。同一类问题还修了三处:agents.create/resume 超时后不再留在后台跑、决策轮等待空闲的超时也会真正取消、心跳 agent 失活时能自愈重建。
修复:投递成功就一定落账
投递成功后如果碰上超时或异常,以前会漏记这一笔,连带冷却与每日上限都算错。现在投递成功无条件落账,超时的另记一条 spoke_late——这是新增的锚点,用来分辨「真的晚了一点」和「根本没送到」。
修复:观察游标改用宿主绝对序号
游标以前记的是「第几条事件」,会话一旦被压缩、或事件被重写,游标就会指错地方,表现为漏读或反复重读同一段。现在按宿主的绝对序号记;升级后旧格式游标自动丢弃并全量重扫一遍,会留一条 cursor_format_discarded——看到它一次是正常的。
修复:素材不再「说出口就消失」
聊天素材(画像合并时从对话里挑出的话题)以前被说出口就当场退场,常常话刚说完上下文就断了。现在至少活到下一次回收,有新证据还会继续留池。同组还修了「恢复出来的冷板凳素材下一拍又被归档」和「新证据豁免条件恒真、规则退化成按次数算」两个问题。
修复:周报的画像写入统计
周报统计「本周画像写了几条」时只读活跃 journal,快照轮转之后这个数字会静默归零。现在同时读归档与活跃两份。
修复:跨夜浏览窗口
配置里写的跨夜窗口(比如 22:00–02:00)以前判定永远为假——配了等于没配,且毫无提示。现在与静默时段的判定口径一致。
改进:画像合并能看到 psy 分区
此前模型看不见 psy 条目,却能往里写;写进去因为没有编号就再也改不动,还可能被重复写入。现在四个分区的条目字段都进合并视图(字段范围不变:编号 / 分区 / 主题 / 子主题 / 内容 / 置信度;观察引语仍最多一句,仍不送对话原文)。这是有意放宽原先「psy 不出库」的口径。
改进:表达被拦下时留下痕迹
素材包发出去了、目标却没说话时,日志里以前一片沉默。现在记 expression_dropped(带丢弃原因),连续三次另记一条 expression_dropped_streak。
改进:数据通道多了道自检
浏览器端的数据通道依赖宿主提供的门禁(Host / Origin 校验 + 会话 secret)。宿主没提供时,删除 / 迁移 / 改配置这类端点现在直接拒绝并留痕(rpc_host_gate_missing),只读端点照常——而不是默默裸奔。
改进:配置里「写了但没生效」的开关全接上
config/interests.json 的 _schedule(每日闲逛场次、每场焦点数、每焦点最多几条)和 config/busy-rules.json 的四个阈值,以前只有一部分真在读。现在全部读。
改进:作息峰值不再被噪声顶掉
「近期活跃时段」现在要同时过两道门(强度阈值 + 相对最高值的占比),指数衰减的残渣也会归零——不会把一次偶发熬夜报成你的作息。
改进:焚毁与迁移更稳
- 焚毁清单补全:v1.9.0 新增的运行时文件(报账、偏好、游标、画像快照、周报、journal 归档)以前不在清单里,现在补齐。
- 迁移改成两阶段:先全部暂存、再统一切换,中途失败会回滚,不再留下半套记忆。
- 审计写入不再掀翻整轮:审计落盘失败以前会让整轮画像合并中断,现在只记一条警告。
工程卫生
- 补了类型声明(
types字段 + 构建产出.d.ts); - 清了重复的锁文件(只留 npm),构建统一走 npm 的扁平依赖布局;
- 加了 CI(Windows runner:
npm ci→ 类型检查 → 测试); - 新增 47 个回归测试(编排器、RPC 通道、会话绑定),测试总数到 282。
升级提示
- 不需要迁移任何数据,装上升级即可。
- 首次心跳若出现
cursor_format_discarded,那是旧游标被丢弃重扫,正常现象,只会出现一次。 - psy 分区的出库口径有放宽(见上);如果你不希望 psy 参与画像合并,可在设置里关掉 psy 分区。
- 安装/升级方式不变(npm 或 GitHub release)。
验证
- 全量测试 282 条通过(v1.9.0 的 223 条 → 282 条,净增 59 条);
tsc --noEmit零错误;npm run build成功,产物含.d.ts类型声明。
v1.9.0
v1.9.0 · 2026-10-06
素材报账 · 话题偏好加权 · 素材池审计:素材投递的归账从「事后拿文本兜底匹配」换成目标会话的 agent 主动报账,偏好统计与周报由此有了可信来源;素材池的一举一动也第一次有了完整的审计痕迹。
新功能:素材报账(seed_report 工具)
每次素材投递的包里都带一个本次投递编号(形如 d1a2b3c4)。目标会话的 agent 收到后,无论说不说话都要报一次账:
spoken=material+seed_ids:说了素材里的内容——填实际用到的素材编号(可多条);spoken=heartfelt:说了不带素材的话(真心话或短应答);spoken=silent+reason:没说话,原因三选一(不想说话 / 素材不搭 / 在忙或刚聊过);profile_ids:把真的当成话题聊了的画像条目编号填进来(闲着模式的素材包里才有)。
设计取向是宁可少记,绝不误记:编号对不上就往少的方向降级(素材多等一跳没关系,误记会污染偏好统计)。报账落 data/seed_report.jsonl(DPAPI 加密,环形保留最近 400 条);工具全局注册(日常会话可用),心跳自己的轮次看不到它——报账说明写在素材包里。config seedReportTool 可关。没等到报账的投递由引擎室兜底归账,并写一条 report_missing 审计。
新功能:话题偏好加权
报账(含兜底)折成每个话题的「投递 / 被采用」计数,闲逛挑焦点时按平滑后的采纳率加权,叠在原有的轮换新近度之上——接得住的话题多露脸,接不住的慢慢退后。防锁死四条同时生效:
- 权重夹在 0.5–2 之间:最偏好最多翻倍,最冷门最少减半;
- 拉普拉斯平滑:没数据的话题停在中性,不是零分;
- 计数按 30 天半衰期衰减:过去的热度会淡;
- 挑焦点另有 14 天硬地板:长期没被碰过的话题优先轮换,不会被高权重话题永远压住。
统计落 data/preference.json(DPAPI)。
新功能:素材池审计
素材池的每一次变动都写进 data/logs/heartbeat.jsonl:
seed_added——新增或合并进池,记下是新增还是并进已有行、并掉了几条同话题旧行;seed_retired——淘汰,记下原因(TTL / 消费退休 / 冷板凳 / 容量挤出 / 已合并)与经手方(投递 / 维护 / 手工);seed_archive_trimmed——归档区被裁剪的条数与编号;seed_restored/seed_deleted——卡片上的恢复与删除。
只记编号与元数据,不记素材正文:那本日志是明文,正文来自对话,不该落到明文里。代价是被裁掉的素材事后只剩编号——这是明确的设计取舍。审计出口由调用方注入,池子保持零依赖,审计写失败绝不会影响池子写入。
改进:归档区上限
素材归档区超过 seeds.archiveCap(默认 50)时按最老优先裁掉,不再无限增长;CLI seeds gc 会打印这次裁剪的条数。
改进:周报
- 新增「投递报账」一节:本周报账次数与
material/heartfelt/silent分布、没用素材的原因计数;没等到报账的投递单独报一行,并且不计入偏好统计——免得「没记账」被算成「发了没人要」。 - 周报起点锚定:从没生成过周报的库在安装时打一个起点,满 7 天才出第一期。此前
lastGeneratedAt = 0让首次维护跳立刻出报告,与说明文档写的「装好一周后出第一期」不符。
改进:迁移容器 KDF 可演进
- 容器的 scrypt 强度
N现在由容器自己携带,导入按容器的N派生密钥,并做范围校验(2^12 – 2^16)与显式maxmem。以前写死N,哪天调强就会把旧导出误报成「密码错误」。 - 非法/损坏的容器报「kdf 参数无效」,不再和口令错误混为一谈。
- 迁移打包清单加入
seed_report.jsonl与preference.json。 - 导入的文件路径校验提前到路径解析之前,不再依赖守卫模块兜住。
修复
- 报账编号清洗:模型写
[s3]、"s3"这类带括号引号的编号,此前会让整条核对失败、投递被记成「没人用」——编号来自素材包里- [s3] …那一行,很容易带上方括号。现在统一剥掉括号引号再核对。 - 报账写盘超时:5s 抬到 15s。整文件 DPAPI 读写在慢盘上实测 0.7–2.7s,原来的 5s 偏紧。
- 闲置兜底素材改用画像里的真实条目编号,不再是占位编号。
- 偏好统计加载时丢弃损坏条目(
NaN计数会让整个排序失效)。
升级提示
- 升级后旧数据无需迁移,
seed_report.jsonl/preference.json缺省即为空,第一次投递后自然生成。 - 归档区超过 50 条的库,第一次跑维护相时会裁到 50(
seed_archive_trimmed会记下裁了哪些)。 - 安装/升级方式不变(npm 或 GitHub release)。
验证
- 全量测试 223 条通过(v1.8.0 的 194 条 → 223 条,净增 29 条);
tsc --noEmit零错误;- 0.2.0-rc.2 宿主实机验收:报账工具注册与调用、审计落盘、归档区裁剪、周报模板新段落。
v1.8.0
v1.8.0 · 2026-10-05
三个新功能 + 三个实用改进:周报、账本工具、journal 快照基点、旧引擎室自动归档、换机迁移(CLI + GUI)、引擎室开放压缩工具。
新功能:周报
每 7 天自动生成一份周报,只报道这周干了些什么:
- 触发:维护相检查距上次周报 ≥7 天即生成(装好一周后出第一期);
- 内容(纯代码收集,全部来自现有存储):本周表达与静默计数及静默主因、画像新增条目、账本新增/已解决/长期挂起事项、素材池新增与已消费、尚未聊过的素材、活跃高峰时段;
- 成文:引擎室一次模型调用写成平实中性的叙述——以「心跳」(插件后台)署名,绝不以任何会话 agent 的身份自称;动作归属与数据来源一致(闲逛是后台干的就写后台干的,会话里说的话才是 agent 说的)。模型调用失败降级为代码模板拼装,绝不空手;
- 存储:报告含画像/账本内容,整篇 DPAPI 加密落盘
data/weekly/; - 送达:toast 提示"周报已生成"(静默时段不弹),设置卡新增「周报」分区可翻阅历史各期;
- CLI:
weekly list/weekly show [file]; - 开关:policy
weekly.enabled(默认开)。
新功能:账本工具
把共享账本注册成模型工具,日常会话里的 agent 可以自己记账:
- 会话中提到计划、待办、承诺、完成某事时,agent 主动调用
ledger工具登记/勾掉,不再需要你明确说"记一下"; - 三个动作:
add(登记)/list(查未完成)/done(勾掉),后端与心跳维护相、CLI 操作的是同一份data/ledger.md; - 全局注册(所有 agent 可见),但心跳引擎室的工具面被预设钉死在
web_search,不受影响; - 心跳自己记账后,周报的"账本"一节也从单方面观察变成你们共同维护的记录;
- 开关:插件 config
ledgerTool(默认开)。 - 配套小改:账本条目被勾掉时日期改记为完成日(原记创建日),周报才能数出"本周已解决"。
改进:旧引擎室会话自动归档
引擎室会话上下文涨过阈值自动轮换(v1.7.4)后,旧会话原样留在会话列表里,日积月累会攒出一片无人认领的会话。现在轮换成功后会通过宿主的 workspaceRegistry.archiveSession 把旧正身自动归档:侧栏不再显示,会话文件原样保留、随时可解档,工作区归属不动。policy heartbeat.archiveRotatedHome(默认开)。
改进:换机迁移工具
加密绑死本机本账户(设计如此),代价是换电脑/重装系统时画像与素材带不走。新增 CLI:
node dist/cli/index.js migrate export # 打包记忆文件为口令加密容器(口令走 stdin 两行)
node dist/cli/index.js migrate import <file> # 在新机器恢复(口令走 stdin 一行)- 容器为 AES-256-GCM + scrypt,除口令持有者外无人能读,可安全走网盘/聊天工具传输;
- 画像三件套、账本、素材池、表达记录、浏览流、观察游标、作息聚合、全部周报一并打包;
gate.json(本机运行时计数)与屏幕快照(一次性瞬态)不随行; - 导入时按原样恢复加密位,用新机器的 DPAPI 重新加密;覆盖现有文件前自动备份
.bak-migrate-*。
新功能:journal 快照基点
画像 journal 是无限追加的,重放成本随时间线性涨。现在维护相在 journal 超过 5000 条时自动折叠:全部历史折叠进一个 DPAPI 快照(profile_snapshot.json),原始记录移入带时间戳的归档分片(profile_journal.archive-*.jsonl),live journal 从零开始。verify / rebuild 自动改为「快照基点 → 归档分片 → live journal」的重放路径,跨快照依然正确,无需任何额外操作。CLI:profile snapshot [--force]。迁移打包清单也已包含快照与归档分片。
新功能:换机迁移 GUI
迁移不再只有 CLI——设置卡新增「换机迁移」分区:设两遍口令点「导出迁移包」生成容器(落在 data/exports/),新机器上填容器路径 + 口令点「导入」即可。口令只在当次调用中出现,不落盘、不进日志。
改进:引擎室开放压缩工具(billion-context 兼容)
billion-context(bili)的压缩机制靠模型自己调用 compress 等工具,而引擎室的工具白名单把全局工具全部遮住了——压缩永远无法发生,引擎室上下文迟早还会涨爆。现在设置引擎室工具面时会探测全局层:compress / decompress / acp_status 存在即自动放行(search_context 不自动放行——dsh-acp 注册过同名工具,见历史事故记录)。也可以用插件 config 的 extraTools(逗号分隔)手工追加其他全局工具名,同样只放行真实存在的。
注意:bili 需要装在心跳所在的 profile(
dsh plugin --profile desktop add billion-context)才会注册工具;bili 未装或代理未启动时引擎室白名单自动回落为仅web_search,行为与之前一致。bili 在引擎室 agent 创建之后才注册工具的话,下次轮换/重启时自动生效。
其他
- 素材深度可调:观察相每条消息收入画像收件箱的字数上限/每跳条数从写死(80 字/10 条)改为 policy 可调:
observe.maxChars(20–2000)/observe.perBeat(1–100),默认值不变。 - 焚毁、卸载语义不变;
data/weekly/属运行时数据,在焚毁范围内。
验证
- 全量测试 194 条通过(新增周报 5、账本工具 5、迁移 2、配置 3、归档判定 1、快照 4、白名单 1);
- 0.2.0-rc.2 宿主实机验收:周报生成与卡片展示、账本工具在会话中可见可用、迁移导出/导入、轮换归档。
v1.7.4
v1.7.4
修复引擎室会话上下文涨过模型的上下文窗口后导致合并、反刍、投递全部失败,且宿主的自动压缩也救不了的 bug。现在心跳会自动轮换引擎室会话:事件数超阈值、或某轮因上下文溢出失败时,自动新建一个干净的引擎室会话接替,旧会话原样留在会话列表里(画像、账本、素材池都在插件数据目录,不受影响)。
问题
引擎室(心跳正身)会话只进不出:每跳都往里追加时间注入、观察摘要、合并裁决提示词(其中合并提示词每次携带完整画像条目,体积最大)。累积越过所用模型的上下文窗口后:
- 合并(consolidation)、反刍决策(decision)每一轮都以
CONTEXT_WINDOW_EXCEEDED失败; - 宿主的自动压缩救不回来——历史大到压缩请求本身也放不进窗口;
- 心跳就此哑火:不合并、不反刍、不投递,观察持续堆积,且每个会话都正常,只有引擎室静默坏死。
实机样本:正身会话 turn 256 / 3221 事件 / 3.6MB,合并与反刍双双溢出(2026-09-30)。
修复方式:正身会话自动轮换
双触发,任一命中即在下一跳开始时轮换:
- 主动:引擎室事件数达到
HOME_ROTATE_EVENT_COUNT(默认 3000); - 被动:任一引擎室轮次的终态错误匹配上下文溢出(
CONTEXT_WINDOW_EXCEEDED/ "context overflow",兼容各 provider 措辞)。
轮换动作:新建一个干净的正身会话(挂同样的预设与工具白名单),绑定写回 gate.json,旧会话原样保留在会话列表(不再有心跳写入)。画像、账本、素材池、绑定关系全部在插件 data/ 目录,轮换零损失。审计留痕:home_rotate_start / home_rotate_done / home_rotate_failed。
同版新增:会话绑定列表过滤
设置卡「会话绑定」的会话列表现在自动隐藏已归档的会话和子代理会话,只剩你真实可见的会话。判定依据是宿主的会话注册表(工作区成员 − 归档名单),注册表读不到时自动退回旧行为(全部显示)。心跳正身始终显示。
说明
- 升级后重启 DSH 生效;已撑爆的正身会话会在重启后的第一跳自动被轮换掉,无需手动清理。
- 轮换后引擎室历史清零属预期行为——那是心跳的工作现场,不是记忆;记忆在画像与素材池里。
安装/升级方式不变(npm 或 GitHub release)。
v1.7.3
v1.7.3
修复心跳进行中拖住宿主关闭的问题。
问题
此前桌面端更新若恰逢一跳(心跳循环)正在跑,更新器的优雅收尾会等不到心跳结束而超时中止,报「未能安全停止任务」(Host did not complete graceful task teardown)。
根因:宿主关闭时心跳的定时器会被正常清理,但已经跑到一半的那一跳不会被中断——其中最长的模型调用有 240 秒预算,宿主在宽限期内等不到它结束。
修复方式
- 一跳启动时注册 agent 取消钩子(复用孤儿轮次的既有 cancel 语义),结束时清除;
- 编排器 disposer 清理定时器后调用该钩子,取消进行中的一跳,宿主的活动任务立即结束;
- 审计日志新增
orchestrator_disposed的beatCancelled标记,可查证是否发生过关机取消。
说明
- 升级后重启 DSH 即可,无需手动操作。
安装/升级方式不变(npm 或 GitHub release)。
v1.7.2
v1.7.2
声明适配 DSH 0.2.0-rc.1,无行为改动。
变更
0.2.0-rc.1 发布后,宿主按插件声明的 peerDependencies 做兼容性检查时会把本插件整包跳过:^0.1.x 的 caret 范围上限是 <0.2.0-0,把 0.2.0 的预发布版也排除了。本版把版本范围加入 ^0.2.0-rc.1:
@deepseek-ai/dsh-llm(peerDependencies)@deepseek-ai/dsh-settings(dependencies)
老宿主(0.1.2-rc.1 / 0.1.5-rc.2 / 0.1.7-rc.x)不受影响,0.2.0 正式版也在覆盖内。
适配依据
对 0.1.7-rc.2 ↔ 0.2.0-rc.1 逐包源码比对,心跳依赖的全部宿主接口运行时代码零变化,无需行为改动:
dsh-llm:createUserMessage与自有消息来源标记(byte 级一致)dsh-agent/dsh-session:agent/inbox/spliced、turn/end事件原样保留dsh-app-boot:profile 加载与 patch 校验机制不变dsh-client-modules:客户端脚本投递机制不变dsh-settings:API 不变(心跳本就带三代 API 兼容层)
说明
- 升级后重启 DSH 即可。
⚠️ 0.2.0生态提示:dshmarket、soul-md 等多数第三方插件在0.2.0-rc.1上会被同机制跳过,升级宿主前先确认插件已发适配版。
安装/升级方式不变(npm 或 GitHub release)。