Skip to content

Releases: TodayJin/dsh-trilogy

v0.9.9 — 记忆目录回退跟随 memoryDirName,文档与代码对齐

Choose a tag to compare

@TodayJin TodayJin released this 24 Oct 07:19

修复

  • registry 条目缺 memoryDir 时(手改过的、或旧 schema 写的),GET /trilogy/workspaces 曾把记忆目录解析成 <root>\memory\memory 并忽略 memoryDirName;/trilogy/clear 与状态接口的回退也忽略配置。三处回退现在统一按 cfg.memoryDirName 解析,硬编码的 cfgMemoryDirFallback() 已删除。
  • 新增 smoke 护栏:手写一条没有 memoryDir 的 registry 条目,再用 memoryDirName: "notes" 起第二个假 web 上下文,断言 /workspaces 报 root\notes、/clear 真正删的是 notes/PROJECT.md。

清理

  • 删除无人引用的 ARCHIVE_POINTER 常量。
  • README / DESIGN 与代码对齐:74 项 smoke、默认项目根 = 工作区本身(marker 才爬 .git)、PROJECT.md 第四节叫 现状、工具表补上 memory_search。

测试:112 项全绿(smoke 74 / client-render 9 / client-interact 29)。

v0.9.8 — 跟进 DSH 会话格式 v4:source 的 kind 自己报出生产者名

Choose a tag to compare

@TodayJin TodayJin released this 28 Sep 02:24

新版 DSH(0.1.7-rc.2)换了会话格式,旧写法会被直接拒收 —— 这次就是修这个。

出了什么事

DSH 0.1.7-rc.2 把会话读成 format v4。v4 废掉了 source 上那层 {kind:'plugin', plugin:<id>} 包装:dsh-session-format-v3-to-v4 的 source() 一见到它就抛

format v4 message requires a producer-owned source kind

而这个插件往会话里塞的每一样东西 —— 记忆块、更新通知、收尾提醒、收拢后的占位 —— 全是那个形状。在新宿主上,它们会在写进去的那一刻被拒。

改了什么

source 的 kind 自己报出生产者名:{kind:'plugin:trilogy', form:...}。

读的时候仍然认老形状(两个分支都收)。这不是客气:升级之前写下的会话里还躺着带 plugin 键的块,认不出来就会把同一份记忆再发一份,同一块内容在上下文里叠两层。

验证

  • 111 项全绿:73 宿主 + 9 渲染 + 29 交互
  • 新护栏红测过:把常量翻转回老形状,只有「injected messages carry a producer-owned source kind」这一条变红;其余用例都不读 kind,没有它这条回归会静默通过
  • 活体:本机新 DSH 上的真实会话(session.v4.jsonl.zstd)里解出 plugin:trilogy 的 source,SessionFormatError 0 次
  • 其余契约逐项核对,都没变:agent/pre-step、agent/turn-stopping、session/event、tools/result、defineTool + presentCall、createUserMessage、surfaceOp/sourceEventSeqs、dsh.bundle/dsh.client 清单字段、settings.section 与 conversation.input.left 两个座位及其 standard props、客户端模块加载器的静态表 —— 所以客户端半边不用改

注意

本插件在 profile 里装的是 link:,改完要重启 dsh web 才会生效。

v0.9.7

Choose a tag to compare

@TodayJin TodayJin released this 18 Sep 08:41

这个版本做了什么

面板里那条**「已清除」的工作区,现在可以从列表里拿掉**了。

registry 是索引,不是存储 —— 记忆文件被清掉的工作区故意还会留在列表里,好让你知道它曾经有记忆。对记录来说这是对的,对列表来说是错的:一直没有出口,所以列表只增不减(我自己的探针脚本就用五条死掉的 %TEMP% 记录把它撑大过一次)。

出口刻意做得很窄:按钮只出现在「已清除」的行上,POST /trilogy/forget 只删 registry.json 里那一条键,磁盘上一个字不碰。在那个工作区再开一次会话,它会被重新登记 —— 正是这个可逆性,让这个动作只动索引成为安全的。

  • 宿主:第 12 个端点 POST /trilogy/forget,同一个 loopback 围栏内 —— 缺 root 400、不在 registry 404、成功 200 {forgotten, memoryDir}。
  • 界面:行的操作区是独立的 flex 项(长路径压不扁它),按钮只对宿主报「已清除」的行渲染。
  • 测试:宿主护栏走完 400/404/200/404 全序列,并断言事后磁盘上的记忆文件逐字节不变;交互套件的假宿主真的应答 /trilogy/forget,点击路径是端到端的。红测:改掉按钮类名 → 交互那条红;拿掉 registry 删除 → 宿主那条红。

三套合计 72 + 9 + 29 = 110 项全绿,行尾未破。


完整改动:v0.9.6...v0.9.7

v0.9.6 — 日志名额按区域分配,每个部分都有封顶

Choose a tag to compare

@TodayJin TodayJin released this 18 Sep 07:30

日志名额按区域分配,每个部分都有封顶;注入块头部从此常驻一行账目。

先说量到了什么

在真实工作区上量的,不是估的:

  • D:\Today\interface-adapter:旧的 64 KB 天花板下,日志一整段进不去 —— 一条都没有。footer 写着「预算连一条也装不下」,而且已经这样好几天了没人发现,因为账目只在丢东西时才出现。
  • 本工作区的 91 条日志横跨 4 个互不相干的主题(插件本体 / DSH 部署排障 / 第三方插件 / 网络环境)。「取最近 15 条」在这种日志上必然偏斜:最近 15 条里 8 条是插件开发、5 条是别的插件,一条网络相关的都没有 —— 而那唯一一条网络记录,恰好卡在切线上。

改了什么

总量:默认 64 KB → 160 KB,并按窗口封顶
生效值 = min(配置值, 窗口 × 16%)。固定字节数在两头都是错的:太小扛不住成熟工作区,太大等于给小窗口的模型塞一个吃掉它上下文的块。实测:1M 窗口给满,128K 窗口降到 20,480 并逐档降级。

第一级:按种类封顶(不是配额),余量给日志
PROJECT.md ≤ 35%、DECISIONS.md ≤ 20%,剩下全给日志 —— 某个文件用不到自己的份额时,余量流向日志而不是浪费。

PROJECT.md 再按它自己的五个小节分别封顶
这一步比整体封顶有效得多:实测 57 KB 的 PROJECT.md,整体封顶只降到 56 KB,按节封顶直接降到 18.1 KB(64 KB 预算下)/ 38.8 KB(160 KB 下)—— 小节点原样保留,只切超长的那一节,切处在注入文本里明说「本节已超出注入上限 N KB,请就地精简」。

第二级:日志名额按区域分配,三条规则

  1. 每个区域保底 1 条 —— 没有这条,聊到安静的区域就一条都看不到
  2. 其余按注意力权重 Σ 0.5^(条目年龄天数 / 14) —— 最近干得多的多拿
  3. 单个区域不超过公平份额的 2 倍 —— 没有这条,一个热门区域会吃掉全部日志

区域是条目上的标签,不是新文件
模型在 memory_checkpoint 的可选 area 里给名(零额外模型调用),插件只负责把它压粗:已有名字复用(大小写不敏感)→ 满 4 个之后新名字归 通用 → 留空也归 通用。宁可粗,不要碎。文件仍然是那三个 —— 每多一个文件就多一处「模型忘了更新」,而更新是这套设计唯一的命门。

旧日志行为完全不变:没有标签的条目全部落 通用(实测头部显示 区域:通用(97))。

头部常驻一行账目
区域:… / 本次注入:… / 文件实际:… —— 成熟工作区会先撞天花板,账目不能等到丢东西才出现。

验证

  • 108 项全绿:71 宿主 + 9 渲染 + 28 交互
  • 四条新护栏逐一红测:把「保底 1 条」「2 倍上限」「区域 ≤4」「按节封顶」分别退化,各自只让对应的那一条变红
  • 端到端(真实工作区脱敏副本):64 KB → PROJECT 57→18.1 KB、日志 15/89;160 KB → 38.8 KB、DECISIONS 20/20、日志 15/89
  • 重启后在真实宿主里实调:area 被接受、标题写成 ## 2026-09-18 · 插件开发、地图立刻变成 区域:通用(97) · 插件开发(1)

注意

area 是新增的工具参数。改动宿主插件后需要重启 dsh web 才会生效(否则调用会被参数校验拒掉)。

v0.9.5 — 收尾提醒不再多出一个回合

Choose a tag to compare

@TodayJin TodayJin released this 18 Sep 04:10

修复:收尾兜底提醒不再「自己回答自己」,也不会再开一个回合。

症状

对话结束之后插件注入一段提醒,DSH 又输出一段话;而很多「以回合末条为准」的功能(产物行、回合导航、预览)把那句「记下了」当成本回合的输出,真正的回答被折叠到回合中间,要展开再滚动才看得见。

根因

兜底提醒用的是 agent.steer。按 dsh-agent 的接口定义,steer 是「送到最近一步,空闲驱动会开一个回合」—— 它把已经结束的回合续下去,模型对提醒的回复就成了这一回合最后一条助手消息。

两条错路都试过并实测否掉

做法 实测结果
agent.steer 续出一步,模型的回复成为回合末条 ❌
agent.send(msg, "next-turn", false) 上一回合确实干净地 turn/end 了,但 wakeup: false 只挡住「唤醒睡着的驱动」 —— 驱动仍会把它当活儿排干,日志是 turn/end 紧跟又一个 turn/start ❌

现在的做法

欠着,不投递:turn-stopping 只置一个待办标记,由下一次 agent/pre-step 把它追加进那一步。

于是它和用户的下一条消息在同一步被模型看到 —— 上一回合保持自己的末条、不为它开新回合、提醒本身一条没少。真机日志:

assistant/message          ← 真正的回答
step/end
turn/end  reason=completed ← 回合到此结束,后面没有任何 step/start
…(用户下一条消息)…
user/message  src=user
user/message  src=trilogy-nudge   ← 同一步、同一次请求
request/header

文案随投递时机改对

同一段文本里还有两处因改时机而变得不对的地方,一并修掉:

  • 开头 这一轮结束前 → 上一轮的收尾检查(提醒现在是下一轮才到,旧措辞描述的时机已经过去)
  • 路由表 这一轮做了什么 → 上一轮做了什么
  • 末句 …就直说,然后停 → …就直说;记完或跳过之后,继续处理本轮用户的消息 —— 旧版提醒独占一步,说「停」没问题;现在它和用户的新消息在同一步,再说「停」会让模型把用户这一轮的任务也停掉

验证

  • 护栏:新检查断言 steer 与 send 都不得被使用,且提醒必须随下一步的消息到达;把实现退回 steer 立刻红
  • 三套测试 67 宿主 + 9 渲染 + 28 交互 全绿
  • 真机验收通过(见上面的日志片段)

代价(诚实说明):若会话再无下一回合,这条提醒就不会出现 —— 保底弱了一点,换来的是本回合的收尾永远是真正的回答。想彻底关掉用 nudgeOnTurnEnd: false。

v0.9.4 - 更新只追加通知,不再改写历史

Choose a tag to compare

@TodayJin TodayJin released this 14 Sep 10:11

Fixed

  • 更新不再改写历史。 以前每次记忆变化都会把历史里那份记忆块原地替换掉。实测(7012 次请求):携带这种替换的请求只有 90 次(1.3%),却吃掉全部全价输入的 58%(24.25M / 42M),平均每次 279k,而普通请求只有 3k —— 贵 93 倍。 根因是 prompt cache 是前缀缓存:在历史中间改写一条消息,会让它之后的全部内容失效;而宿主还会因此开一条新的请求序列(requestSurfaceGeneration !== surface.replaceGeneration),连系统提示词一起重写 —— 改写一次,整段上下文付两遍。
  • 现在的做法:整块一个会话只发一次,之后不再动它;记忆再变时只在末尾追加一行 ~200 字节的提示(「记忆已更新,需要时用 memory_read」)。只有在 surface 上已经没有活块时(会话第一步,或块被压缩掉)才重新发整块。
  • 恢复的会话不再被误报。 新进程没有 per-session 摘要,所以它的第一步看起来「像变了」—— 以前会因此发一条假的「记忆已更新」。现在改为比较 surface 上那份块的实际文本与新渲染的文本,而不是假定「新进程 = 新内容」。
  • 读不出 surface 时按「已经发过」处理。 当成「没有块」会每次变化都追加一整块,把这版修复的意义抵消掉。
  • 存量收拢保留,但只在多于一份时触发,且只能逐条单节点替换 —— 一个大 range 会被 assertProvenance 要求列出其中所有被遮蔽的节点,而旧块之间夹着 assistant / tool 消息,那等于把对话一起删掉。

Tests

  • smoke 的假宿主现在真的模拟 session 的 append / replace 语义并统计改写次数。新增三条护栏:更新绝不改写历史(replaced() === 0)、占位和通知不算活块、通知不会触发收拢。
  • 顺带修好一条已经失去牙的旧护栏:「块被压缩掉后重注入」原来只断言消息数为 1 —— 而一条通知也是 1 条。现在它会检查 form。
  • 三条都实测会红。三套件 67 + 9 + 28 = 104 项全绿。

Docs

  • README / DESIGN 的注入说明与测试计数已同步;PROJECT.md 的注入语义小节记录了「为什么更新必须是追加」,以及这次成本是怎么量出来的(解会话日志里的 usage,其中 inputTokens 是未命中缓存、按原价计费的那部分)。

真实环境已验:重启后 miss = 0k(修复前携带替换的请求平均 279k)、0 次 replace、live surface 仍是 1 份整块。

67 宿主 + 9 渲染 + 28 交互,全部通过。

v0.9.3 - 注入原地替换(不再按次累积)+ 面板三处修复

Choose a tag to compare

@TodayJin TodayJin released this 13 Sep 02:28

Fixed

  • 注入不再按次累积。 以前记忆内容一变,就往会话里追加一整份全量快照 —— 一个真实会话攒到了 18 份,占其 surface 约 40%。根因:宿主 agent/pre-step 的 messages 不是会话历史,loop 用 inbox.claim(...) 建输入与 next() 的默认值,然后把返回的每一条都 session.append(..., {surfaceOp:"append"}) 追加。在那里返回消息只能「加」、不可能「换」。
  • 改成原地替换:把新块以 surfaceOp: {op:"replace", startSeq, endSeq} + sourceEventSeqs 直接写到 session 上,会话里始终只有一份。替换被宿主拒绝时退回追加,最坏不比上一版差。
  • 顺手收拢存量:注入时检查 live surface 上自己所有的块,最新那条换成新块,其余每一条各发一次单节点 replace,换成一行的 ~30 字节占位(form: "trilogy-superseded",因此不会被再认成活块)。实测某会话 22 份 → 1 份 + 21 行占位,回收约 680 KB。
  • 面板:「移除 Memory 段」后编辑器不再残留旧文本。 bootAction 只刷新了徽章、没刷新 instruction,整份编辑器打开时拿到的仍是删除前的文本 —— 而照它保存会把刚删掉的段原样写回去。
  • 面板:指令文件的编辑框不再顶出卡片。 插件样式表从没设过 box-sizing,而宿主是逐组件重置、没有全局重置,于是编辑框的 width:100% 把 padding 与 border 加在外面,比卡片宽出 22px。一条作用域内的重置同时修掉编辑框、左栏筛选框与工作区行。
  • 面板:归档页签的「导出 / 导入」回到同一排。 两者原本是同一条换行行里的独立兄弟元素,归档时的长提示把「导入」挤到了第二行;现在它们是一个 flex:0 0 auto 的整体,由提示先让出宽度。

Tests

  • smoke 的假宿主现在真的模拟 session 的 append / replace 语义,并且按 loop 的方式把每步的消息落盘。不做这件事,「追加」和「替换」在测试里看不出区别 —— 这正是上一版改动测试全绿、生产环境完全没生效的原因。
  • 两条新护栏:原地替换(短路替换即红:a replacement must go to the session, not the step list)与收拢旧副本(短路收拢即红:exactly one full block may survive, saw 5),后者同时钉住占位必须 < 400 字节,因为收拢的全部意义就是腾空间。
  • 渲染套件钉住 box-sizing 重置与按钮分组;交互套件钉住「编辑器显示的是现在的文件」与「归档页签两按钮同行」。
  • 收拢只能用逐条单节点 replace:assertProvenance 要求 sourceEventSeqs 列出区间内所有被遮蔽的节点,而旧块之间夹着 assistant / tool 消息 —— 一个大 range 会把这段对话一起删掉。这条已在真实会话上验证(21 条收拢全是 startSeq === endSeq)。

Docs

  • README / DESIGN 写清注入路径,包括为什么替换不能走 pre-step 的消息表;测试计数 62 → 64、7 → 9、26 → 28。

64 宿主 + 9 渲染 + 28 交互,全部通过。

v0.9.2 - 归档跨轮不再倒序

Choose a tag to compare

@TodayJin TodayJin released this 12 Sep 15:09

Fixed

  • 归档跨轮顺序。 ARCHIVE_HEADER 写着「最新在最上,形状和 SESSIONS.md 一样」,但每轮从活日志搬出来的批次是追加到归档正文末尾的:第一轮看着没问题,从第二轮起归档变成「老批次在前、新批次在后」(后搬的批次在时间上更新),设置页「归档」页签的排列也跟着乱。
  • 修法按你选的 (a):用宿主侧那套认围栏的 splitEntries 拆出归档的头部与正文,写成 头部 + 本轮搬出的批次 + 原有条目。

Tests

  • 新增「连搬两轮」的护栏:整份归档必须全局最新在最上,头部不能重复也不能被顶下去,指针要把两轮的条数累计报出来。实测会红 —— 换回追加顺序时它报 ["first 3","first 4","first 5","second 3",…]。

Docs

  • README 的「归档与恢复」写明归档也是最新在最上;测试计数 61 → 62。

62 宿主 + 7 渲染 + 26 交互,全部通过。

v0.9.1 - 面板里记忆文件只读,「重新读取」挪进页签条

Choose a tag to compare

@TodayJin TodayJin released this 12 Sep 14:56

Changed

  • 三个记忆文件在面板里变成只读。 现状 / 决策 / 日志 页签不再提供「编辑」,手改记忆文件的那条路径整体删掉(编辑器、保存 / 取消、编辑中,未保存 的提示、editing/draft 状态),不留死代码。写入的正路是会话里的 memory_checkpoint,或者导入记忆包。
  • 「重新读取」挪进页签条,就在 归档 左边 —— 它重新读取的正是这些页签选中的那个文件。
  • 归档本来就只读,现在四个页签一视同仁。
  • 顺带修掉一个连带问题:页签切换过去会关掉打开着的指令文件编辑器(旧代码里两个编辑器互斥)。互斥关系没了之后再那样做,只会静默丢掉没保存的草稿,所以切页签不再动指令编辑器。

Unchanged

  • AGENTS.md 的「编辑整份文件」照旧保留,它是界面里唯一的编辑器。
  • 宿主 POST /save 的白名单没变 —— 只是界面不再提供记忆文件的入口。

61 宿主 + 7 渲染 + 26 交互 = 94 项,全部通过。两条新护栏都实测会红:把「重新读取」的插入点从 归档 前改成 现状 前 → 页签顺序测试红;在工具栏塞回一个「编辑」按钮 → 只读测试红。

v0.9.0 - 删掉跨工作区总览

Choose a tag to compare

@TodayJin TodayJin released this 12 Sep 14:44

Removed

  • 「全部工作区」页签。 用户反馈没用。界面删掉之后 GET /trilogy/overview 就是死代码,所以端点一并删除 —— 端点从 11 条减为 10 条。
  • 客户端一并清掉:页签按钮、它的内容分支、overview 状态与 loadOverview、以及三条 .dsh-pm-overview-* 样式。归档分支在三级三元里上移一级并重新缩进,不留悬空层。

Docs

  • 设置页功能表里的那一行、端点数量与清单、以及测试计数。
  • 顺手校准了测试计数 —— 它们从 v0.7.0 起就没跟上(README 还写着 59/6/27)。

61 宿主 + 7 渲染 + 26 交互 = 94 项,全部通过。