# 0.1.19 — 更稳的收纳与拦截 ## 会拦下整轮压缩的问题 - **修(重要)**:外来检查点(宿主压缩留下的、没有我们的块头的摘要)现在按「出身 + 位置」分两种收纳。真检查点、且前面没有任何内容 → 登记成最深那一层,之后可以再折一次;其余情况一律当**原文**处理、跟着原文一起被压走。以前一律收进第一层,只要它前面还有原文,就会出现「压原文被它挡、压它被原文挡」的环形拒绝。 - **修(重要)**:日志里停着一个没有收尾的压缩事务时,插件在这一轮**一个节点都不写**,并直接把原因报在界面上。以前只有压缩工具自己会拒,而轮次标记与提醒照写不误,等于让这份日志越来越难修。 - **修**:收尾记录写不进去、但这次压缩的替换其实已经落地时,工具回执会明确说「这一层的块已经生效了,不要再压同一段」,同时把用量基线照常重设。以前这种情况回执只说"失败",模型很可能再压一次。 ## 提醒与代价 - **修**:「会话搜索」插件没装时不再叫醒。工具都停用着,叫醒只会让模型照地图撞墙。停用期间用量掉回线下的状态也会正确复位,重新启用后该报的照报。 ## 小口径 - **修**:地图里「可压原文合计太少」的阈值改成引用同一个常量,不再写死一个数字。 - **修**:`to` 的方向提示改成「只能调小 / 调大」,不再用"往前/往回"这种容易两边理解的词。 - **修**:注释与默认值对齐(子代理默认参与)。 ## 文档 - 公开设计说明补充「定位的可靠边界」:插件保证的是"要么切在真实的轮次边界上、要么拒绝",不是"任何情况都能算出正确轮号"。 ## 测试 - 304 项(`blocks` 94 / `callfold` 22 / `compress` 20 / `compress-e2e` 55 / `config` 15 / `map` 12 / `prestep` 53 / `scope` 7 / `usage` 8 / `imageprice` 18)。 # 0.1.18 — 压缩恢复正常(重要修复) ## 会拦下整轮压缩的问题 - **修(严重)**:修掉一个让 `compress` **每次都失败**的缺陷。上一次改动在写入路径里少传了一个参数,于是每一层在预演时就会抛错,被捕获后回成一句普通的拒绝理由——看起来像"这一层压不了",实际上是功能整体失效。0.1.16、0.1.17 都受影响,请升级。 - **修**:压缩前先检查上一次压缩有没有收尾。如果某个会话的上一次压缩写了一半、连补偿的收尾记录也没写进去,继续对话会让这条会话下次打开时加载失败。现在遇到这种状态直接拒绝再压,并提示立刻新建会话。 - **修**:原文区里有一个"空节点"时,成本不再被当成"量不出来"。以前这种情况会绕过最小成本门槛,导致地图一直说可压、模型压一次被拒一次。 ## 小口径 - **修**:地图的成本缓存按"区间"区分。以前只按节点数缓存,两段长度相同、位置不同的原文会互相串数,地图和 `compress` 的判断口径不一致。 ## 文档 - 公开设计说明补充:插件只读宿主给的表层列表,不写、不改任何表层存储;压缩写回时由宿主校验区间。 ## 测试 - 240 项(`blocks` 89 / `callfold` 22 / `compress` 20 / `config` 15 / `map` 12 / `prestep` 49 / `scope` 7 / `usage` 8 / `imageprice` 18)。 # 0.1.17 — 来路不明的块不再当底座 ## 会改变行为的问题 - **修**:宿主把整段历史压成一个检查点之后,插件不再只看来源标记就把它当成"盖住整段历史的底座"。只有日志里查得到那次压缩事务(`compactionId` 对得上)的检查点才升到最底层 L3;查不到的一律按普通块收在 L1,并在日志里记一条。来路不明的内容是什么没人知道,不能拿它当底座。 - **修**:地图会说明"这个块出身没核实",模型能看到为什么这一版压不动,而不是对着一个不动的上界瞎猜。 ## 小口径 - **修**:日志写入按作用域共用同一个入口。以前两个实例(热重载、或者别的组件)同时判断滚动,会互相把正在写的文件改名。 - **修**:地图只在真要用日志时才建目录,插件加载不再有副作用。 - **改**:压缩事务的记录不缓存、每次现扫——只有表层里真有"没有块头的检查点"时才走这条路,扫一遍 50k 事件约 0.24ms,比维护一个会失效的缓存便宜得多。 ## 文档 - 公开设计说明补「定位的可靠边界」:保证的是"要么切在真正的整轮边界上、要么拒绝",不是"任何情况下都算得出正确轮号"。六种例外逐一写明,并说明来历不明的检查点为什么只当普通块。 ## 测试 - 232 项(`blocks` 83 / `callfold` 22 / `compress` 20 / `config` 15 / `map` 12 / `prestep` 49 / `scope` 5 / `usage` 8 / `imageprice` 18)。 # 0.1.16 — 缺轮能压了,提醒不再抢跑 ## 会拦下整轮压缩的问题 - **修**:空会话的第一轮不再往表层写任何节点。以前轮次标记可能抢在系统头前面,成为这个会话的第一个事件——写入时一切正常,关闭重开 DSH 后这个会话会永久加载失败(`system/message requires a protected first surface head`)。 - **修**:轮次大纲的两个来源都拿不到时不再判成损坏。以前每轮都报红色错误、连带吞掉用户刚发的那条消息。 - **修**:一次压缩恰好是这轮最后一个动作时,收尾前会先补一次折叠。以前那次折叠会被永久放弃,摘要的参数副本此后每轮重复发出。 ## 定位与轮号 - **修**:`keepTurns` 改成保住「最近 N 个**已结束**的轮」。旧算式把当前轮算进了可压范围,`keepTurns=2` 实际只保住 1 个已结束的轮。 - **修**:宿主的轮次大纲投影没挂上、又碰上日志里有重号的 `turn/start` 时,先把重号按轮次去重——不再把 fork 出来的会话判成损坏、每轮报红。 - **修**:轮次记录里缺了某一轮(`fork`、迁移、异常重试都会留下)时,先用我们自己写的〔轮次标记〕把它补回来;补不上也允许跨过缺口切一刀——切点仍落在下一个已知边界上,缺掉的那一轮顺着前一轮的标签一起进块(块头的轮号会粗一档)。 - **修**:〔轮次标记〕与已知边界对不上时只记备注、不再报红。宿主压缩本身就会让标记里写死的轮号与旧边界错位,报红等于让正常会话停摆。 - **修**:只认「来源标记」判断宿主检查点,不再靠正文开头那句英文前导语去猜——用户自己粘一句同样的话不会被误当外来块。 - **修**:宿主把整个历史拍平成一个检查点时,把它登记成 L3 层,`l3` 仍能继续压它、金字塔还能重新长出来。 - **修**:空节点占着的事件号也算进块头的事件号范围,取回线索不再指向一条空事件。 - **修**:折叠的返回节点查找范围从 12 扩到 24,长工具结果不再错过折叠。 ## 提醒与代价 - **修**:提醒已经投递、还没被下一轮读到时不再结算增长基线,避免门槛被提前重置。 - **修**:静默子代理正常收尾时也会清掉超窗口计数;以前它自愈一次后再超一次,会被误判成「连着两轮」并停掉它的自动续作。 - **修**:超出窗口分两次说。第一次只写中性提示;连着两轮都超出才停下自动续作。 - **修**:同一条助手消息里同时发两个 `compress` 直接拒绝——一次只发一个。 ## 图片计价 - **修**:图片按当前路由声明的真实价计价。宿主返回的是 `{ visualTokens, text }` 对象,以前只认数字,等于每张图都按 800 算。 - **修**:同一个口径同时用于地图的可压合计、叫醒门槛与压缩收益校验,不再一个按真价、一个按 800。 - **修**:暂时拿不到价格(模型服务还没装配好)不写缓存,下次自动重试;只有「这条路由确实不供应图片价」才记住。 - **修**:宿主已经把图离线化成文本的,不再重复计价。 - **修**:拿不到图片价时会写一条日志(适配器报错、张数对不上),不再静默退回 800;地图也会区分「可压的原文太少」和「成本量不出来」两种情况,压缩回执会写明这次跨过了哪些缺失的轮次。 ## 地图与许可证 - **修**:原文区那一行会说明「轮次记录里缺了第 N 轮」以及它的影响(压到它前一轮时会把它的内容一起压进块)。 - **修**:许可证指纹不再包含系统头节点。宿主重写系统提示词不会再让手上的地图假性过期。 ## 小口径 - **修**:`to` 只收字符串;`keepTurns` 的小数在运行时出口截断。 - **修**:日志目录在 Windows 上遇到超长路径时,改用扩展路径再试一次。 # 0.1.15 — 定位更稳,代价更小 ## 会拦下整轮压缩的问题 - **修**:一次压缩写到一半被宿主拒绝时,原先可能留下一张层级颠倒的表,此后每次压缩前都报错。现在按「先写最深的层、最后写原文」落盘,中途失败留下的是一批空层;写完还会再核对一次表层,回执里写明哪几层已经落地。 - **修**:空轮、空消息、空的系统节点会让某一层永远显示「有内容但压不动」。现在这类节点不算原文,也不占层级。 - **修**:同一条助手消息里同时发两个 `compress`,现在直接拒绝——一次只发一个。 - **修**:失败的回执以前可能被折叠误判成「已经成功」,连带把失败原因和地图一起折掉;现在不会了。 ## 定位与轮号 - **修**:轮号定位改以对话里实际写着的「〔轮次标记〕」为准,宿主的大纲只做交叉校验,两者对不上时报错。压过一轮之后,不会再因为标记与大纲错位而报红色错误、停用这个对话。 - **修**:可压上界改成「表层上真有原文的最后一轮」为止,不再把正在进行的轮或空轮当成边界。 - **修**:宿主丢弃「轮号回退」的轮次事件时,定位改以日志为准(投影与日志取并集)。 ## 地图与许可证 - **修**:地图过期只看三层块有没有被真正换过。宿主重写提示词、剪枝、折叠都不会再让手上的地图作废,连续压两次不用来回重看地图。 - **修**:地图里「原文区还剩多少」与提醒的门槛用同一个分母,不再一个说能压、另一个说不能。 - **修**:地图会明说「不足 300 token,不值得为它开一轮」,而不是笼统地写「可压」。 ## 提醒与代价 - **修**:原文区的成本估算只在真要发提醒的那一轮做一次,平时每轮不再空算一遍。 - **修**:压缩之后占用明显下降时,同一轮里不会再发一条基于压缩前快照的「某层已超限」。 - **修**:「结构上可压」与「值得把模型叫醒」分开判断;拿不到正文时只提示、不叫醒。 ## 小口径 - **修**:`to` 只收字符串。数字参数以前会被隐式转成 `"3"`,现在直接拒绝。 - **修**:`keepTurns` 取整,写入与展示一致。 - **修**:折叠注记用自己的来源标记,界面上显示为插件卡片,判定也按它认。 - **修**:可用输入直接取宿主的预算值,与宿主显示的占用一致。 ## 其它 - 不再硬依赖宿主的轮次投影服务;缺这个可选服务也能启动。 - 每个会话的用量读数分别缓存,两个助手同时跑不会互相清掉。 - 测试:八个文件共 182 项,全绿。 # 0.1.14 — 折叠只折还没发出去的那一对 - **修**:压缩成功后的折叠可能去改写一条已经跟着请求发出去过的节点,导致从那里往后的前缀缓存全部重算。现在折叠只在「这一对还是表层的最后两个节点」时发生;错过那一步(并行了、被别的写入顶走、那一轮异常收场)就不再折,调用记录原样留着。 - **修**:`/context-status` 不再把设计性不折(并行调用、被顶走、定位不到)显示成「折叠失败」;失败(代码异常,可能下次成功)与跳过(结构性,不再重试)分两行显示。 - **修**:关掉 `foldCallRecord` 期间不再登记待折叠项,并清空已有队列——避免重新打开时把一批旧节点折掉。 - **测试**:调整 2 条、新增 3 条断言,覆盖「一对后面还有节点 → 一个字节都不动」、开关关闭时清空队列、并行调用只记跳过。 # 0.1.13 — 文档与仓库整理 本版不改运行行为,只整理文档与仓库: - 设计说明合并成一份连贯文档:分层模型、四个键、写哪四条记录、地图、提醒、配置、安装与限制。 - README 精简:只留定位、工作方式、安装、配置、边界与限制。 - 仓库只保留使用者需要的文档。 - 代码与 0.1.12 相同。 # 0.1.12 — 修安装模板的配置缩进 - **致命(0.1.11 引入)**:`dsh.bundle.patch.yml` 里 `notifySubagents` 少缩进一层、落到了配置根层,而插件对未知顶层键是拒绝启动 → 照模板装的人一装就起不来(报"配置有 1 处不对,插件拒绝启动")。 - 模板已改回 `notice` 组;同时加**就地迁移**:老配置里根层的 `notifySubagents` 会自动挪进 `notice`,不用手动改。 - 新增测试:直接解析模板文件的缩进层级,顶层键与 `notice` 子键都必须在允许清单里——上一版那种错层现在会被当场逮到(之前只测内存对象,抓不到模板错误)。 测试:blocks 49 / compress 18 / callfold 19 / config 14 / map 10 / prestep 40 / scope 5 / usage 8。 # 0.1.11 — 修复一批会导致压缩失效或报错的问题 本版修掉的问题: - **致命**:`l0` 收益门槛用了 `messageTokens()` 却没导入,异常被吞成 `Infinity`,门槛两版都没生效(模型仍会被叫醒、压一次被拒一次)。 - **致命**:大纲判据曾把宿主"每轮第 1 步还没有节点"当成损坏,抛错前宿主已取走用户消息 → 每条消息都被吞。现在只认缺轮/乱序。 - **严重**:压缩成功后的回执地图读到压缩前的占用(按步缓存跨进了工具执行期)。 - **严重**:配置热更新的过滤写反,改 `segment`/`title`/`budget` 不重建,提示词里的数字停在旧值。 - **严重**:宿主自身的 `cordis-host-runner` 消息会被当成"用户开口了",解开叫醒闸门。 - **严重**:带地图的失败回执在模型读到之前就被折叠成一句话。 - **一般**:块尾取回线索写成了块写入时的节点号;图片台账合并时张数会虚减(100 张变 6 张);子代理默认不参与导致它误读已压缩内容;关掉子代理通知时连回执折叠也一起停;`keepTurns=0` 的说明写成"多压一轮"(真相是一轮都压不动)。 其它:提醒/折叠注记带身份哨兵(`〔Ebbing Context 插件注入〕`),轮次标记靠 kind 认;`l0` 门槛 300 token(插件自研估算器);用量按步缓存;`notifySubagents` 默认 true。 测试:blocks 49 / compress 18 / callfold 19 / config 14 / map 10 / prestep 40 / scope 5 / usage 8。 # 变更记录 本文件只记“对外可见的变化”。规范里每一条规则为什么这么定,见 [`docs/SPEC.md`](docs/SPEC.md)。 ## [0.1.10] — 2026-10-31 修提醒门控的两条边界。都不改压缩本身,只改「该不该开口」与一句话的措辞。 ### 变更 - **环境原因不再连坐提醒**:`l0` 压不动有两种原因——内容(只剩 `keepTurns` 保住的轮、或只剩每层最后一块)与环境(轮次大纲损坏、拿不到轮号)。以前两种都算「浅层压不动」,于是 `l1`/`l2` 的区超限提醒被一起拦下,而地图上明明写着「`l1`/`l2` 的 `to` 不受影响」——等于要人等到大纲自己修好。现在只有内容原因才拦(`st.envBlocked` 取代 `st.zoneHas`)。 - **去掉空层豁免**:块只能由下一层压出来、每层最后一块又永不压,所以「上层有块、下层为空」不可能出现;真出现了按压不动处理。原来那条豁免防的是一个不可能的状态。 - **措辞统一**:轮次大纲相关的拒绝与地图提示一律说「轮次大纲损坏」,并说明 `l1`/`l2` 照常可用。 ### 说明 - 轮次大纲**能自愈的部分早就自愈了**:宿主的 `turnOutline` 投影坏了会改用日志里的 `turn/start` 重建(`turnsFromLog`)。只有日志本身缺 `turn/start` 或乱序才真的修不了——那种情况下真相不在日志里,只能明说损坏并降级到 `l1`/`l2`。 - 测试:`prestep` 27 → 28 项(新增「大纲坏了但 `L2` 压得动 → 照报」;原来那条「浅层是空的照样报」改为「不报」,因为它描述的是不可能出现的表层)。 ## [0.1.9] — 2026-10-31 修 0.1.8 里一个会“永久半额”的后果:增长门槛被砍半之后,只靠压缩才恢复原额,模型一直不压就一直按半个门槛催。 ### 变更 - **结算后退回原额**:增长提醒发出、模型那一轮没压,收尾结算时除了按 `notice.forgivePct` 退回额度,**同时把门槛放回原额**。砍半只是“压不动时早点给一次机会”的临时手段,不该让一次压不动的历史把此后每一轮都变成半个门槛。 - 成功压缩那条路不变:`resetGrowthBaseline` 仍然把基线、门槛、指示灯、待结算全部清空。 ### 说明 - 行为差异只出现在“砍半 → 报了一次 → 模型没压”之后:以前下一轮仍按半个门槛判,现在要重新攒满原门槛。 ## [0.1.8] — 2026-10-31 提醒的触发语义按讨论结果重做:**越线才报一次**,并且只在真压得动的时候开口。 ### 变更 - **到线提醒改成越线触发**:越过 75% 那条线时报一条、记住;停在线上不重复催,掉到线下(比如压完之后)再越线才会再报一次。以前是只要在线上就每轮一条。 - **区超限要连浅层一起看**:某层越线要报,光这一层压得动还不够——比它浅、且**还有内容**的层也必须都压得动。浅层已经空了不算拦(压缩过的会话只剩块就是这种形状),否则深层会被永久拖黑。反过来(深层满、浅层没满)照报浅层,地图上写着深层不可压,模型不会硬去压它。 - **增长触发压不动就砍半**:又长了 `growthTokens` 但一层都压不动时,不发提醒,把门槛砍到原额的一半、基线前移到当前量——再长半个门槛就重新判一次,不会把这条触发饿死。 - **新增配置 `notice.forgivePct`(默认 100)**:增长提醒发出后,模型还有一整轮决定压不压;到那轮收尾仍没压,就按这个比例把这段增长退回额度。100 = 全额退回(重新攒满一个门槛)、50 = 退一半、0 = 一分不退。 - **任何原因触发的压缩都清空增长状态**:基线对齐当前用量、门槛回原额、指示灯熄灭、待结算清空——省掉一堆边界情况。 - 面板:`triggerPct` 的说明补上「越线才报一次」;增长阈值那条补上「砍半」;新增「不压时退回多少额度」控件;「使用注意」文案不变。 ### 说明 - 配置叶子从 15 个变成 16 个(`notice` 组 3 个)。profile 的 YAML 里没写 `forgivePct` 时用默认 100,不会报错。 - 已知代价:模型如果既不压、也不长,可能长时间安静——这是设计上要的(不在线上反复催);「还在长」由增长那条负责。 ## [0.1.7] — 2026-10-31 提醒(那条把模型叫醒的自动消息)改成**只在这时候真压得动才发**,并且明确允许"这一轮不压"。 ### 变更 - **区超限只在压得动时才报**:某层越过设计容量、但那一层已经压不动了(只剩最后一块;或原文区的轮次判不出来),就完全不发这条提醒。此前会照报,模型只能撞一次"最后一块不能压"的拒绝——每轮白烧一次请求和一次失败调用。 - **纯增长触发要有一层压得动**:自上次提醒之后又长了足够多,但 L0/L1/L2/L3 一层都压不了时,不发。到线那条(占用 ≥ 75%)不受影响——它还承担"告诉用户这一版压完了"的职责。 - **提醒里说清"可以不压"**:正文现在包含两层意思——要压掉的那几轮里如果还有没做完的事(目标没收尾、todo 没打完、还在验证、等用户拍板),或者内容马上还要用,就留着别动,**这一轮允许什么都不压**;压完只回一两句(压了哪几层、省了多少、现在占用多少),不复述摘要。决定不压也回一句为什么,不要沉默。 ## [0.1.6] — 2026-10-31 修一条**每次压缩成功都会出现**的错误回执:压缩其实成功了,界面上与模型看到的却是「压缩时出错了:reminded is not defined」。 ### 修复 - 负责压缩的工具函数是模块级函数,成功分支里却引用了另一个作用域里的局部变量(`reminded`,记"上一次提醒时的用量")。JS 只在这个分支真的被执行时才报错,而那时压缩的四个事件**已经写进会话表层**——于是压缩生效了、模型却收到失败回执,还可能照它重试一遍。 - 现在把这个变量作为参数传进去。另外这一条会连带影响两件事:成功回执里的刷新地图与"下一次不用重取地图"的许可证照常发出;调用记录也照常被折叠成一条短注(此前失败分支跳过了它)。 - 新增静态检查 `test/scope.test.mjs`:`apply()` 里声明的局部名不许出现在模块级函数体里。同类问题(写的时候不报错、跑到那一步才炸)已经出现过两次,用扫描兜住。 - `l1` / `l2` 的参数说明删掉「前 N 块」这种要模型自己猜的写法,改成「最老的一块或几块,到 `to` 指定的那块为止」。 0.1.0–0.1.5 都带这个问题,请升级。 ## [0.1.5] — 2026-10-30 提醒(叫醒模型去压缩的那条)不再往队列里叠加。 宿主每轮只认领**一条** `next-turn` 消息(`dsh-agent-loop` 的 `claim` 里是 `mutate("next-turn", 0, 1, …)`)。上一轮如果以异常收场,提醒可能没人认领、一直挂在队列里;此时再投递一条会排成两行,要多花一轮才轮到后面那条。 现在投递前先看队列:**还挂着我们的提醒就就地替换**(`agent.inbox.replace(id, 新消息)`,内容换成这次的新读数),没有才 `followup` 追加。队列里始终只有一条。 ## [0.1.4] — 2026-10-30 提醒(那条把模型叫醒去压缩的自动消息)在界面上看起来像「用户自己发起的请求」,模型也会把它当成用户说的话。三处修好: - **首行自报家门**:正文第一行固定为「〔Ebbing Context 自动提醒 · 插件注入,不是用户说的话〕」。 - **来源标记换成宿主登记过的 `cordis-host-runner`**:Client 的 turn-trigger 卡片会把标题显示成「插件状态更新」并配插件图标;此前自造的 kind 落到默认分支,显示成「收到执行请求」。 - **地图里不再出现「前 N 块」**:有可压范围就直接写真实区间(如「压 L1 区最老的 1–3 块」「可写 to=1–3」),压不动时写「一段」而不是留一个要模型自己猜的 N。 另外说明一处容易被误解的行为:提醒不会挤掉用户正在写的草稿,也不会覆盖用户运行中排队发出的消息——草稿在浏览器本地,用户的排队消息走 inbox 的 `next-step`,提醒走 `next-turn`,宿主认领时先取走整个 `next-step` 再取一条 `next-turn`。 ## [0.1.3] — 2026-10-30 修一个**每轮都会触发**的收尾崩溃:模型答完之后,界面报「本轮运行失败 next is not a function」。 ### 修复 - 插件在 `agent/turn-stopping` 上注册的监听器写成了 `(payload, next) => { … return next() }`。这个事件是宿主用 `dispatch.serial(...)` 派发的,监听器**只收到载荷、没有 `next`**,于是收尾阶段抛 `TypeError: next is not a function`,宿主把整轮判定为失败——回复其实已经生成、用户也看得见,但这一轮被标红。 - 只有 `waterfall` 事件(`agent/pre-step`、`agent/request-error`)才给 `next`。现在 turn-stopping 的监听器不再接收也不再调用 `next`。 - 新增回归断言:按宿主的真实派发形状(只传一个参数)调用 turn-stopping handler。之前所有测试都手动传了 `next`,所以这个问题一路漏到了真机。 - 0.1.0 / 0.1.1 / 0.1.2 都带这个问题,请升级到 0.1.3。 ## [0.1.2] — 2026-10-30 把「剪大工具返回」退回宿主原本的语义:**只在压缩前剪一次**,不再每步请求前剪。 ### 变更 - 0.1.1 曾让插件在每次 `agent/pre-step` 扫一眼尾部节点、发现超大工具返回就剪。这偏离了宿主的设计——宿主把 `toolResultPruner` 当作**压缩的前置步骤**(`compaction-basic` 只在 pressure 越过阈值与 context-overflow 两处调用)。两个具体问题:剪枝是无条件的,而取回是有条件的(没装会话搜索时 `compress` / `context_map` 会停用,剪枝却照剪,被削掉的中间段不一定找得回);而且它会在模型正要引用刚读到的内容时把内容削掉。 - 现在只有 `compress` 执行时剪一次,与宿主同构。`compress` 的开关 `pruneFirst` 保留,含义回到「压缩前先剪」。 - 新增回归断言:`pre-step` 里一次都不许调 pruner,防止这个行为再被挂回去。 ### 需要知道的取舍 关掉宿主自动压缩后,单个巨型工具返回会原样留到模型决定压缩(兜底是 75% 提醒线)。这是有意的:宁可多花一点 token,也不在任意时刻改写模型正在读的内容。 ## [0.1.1] — 2026-10-30 修一个会让**整轮请求**失败的参数 schema 错误——首次真机运行暴露出来的第一个问题。 ### 修复 - `compress` 的嵌套块参数(`l0` / `l1` / `l2` / `l3`)原来在属性上写 `required: true`。宿主的工具描述转换只处理顶层属性,嵌套层原样发给 provider;OpenAI 兼容线只接受数组形式,于是整轮失败: `Invalid schema for function 'compress': true is not of type "array"`。 现在块对象自身声明 `required: ["title","summary"]`(`l0`/`l1`/`l2` 另加 `"to"`)。 - 新增递归断言:参数树里任何 `required` 都必须是数组(`test/prestep.test.mjs`)。以后再往嵌套层加参数,这条断言会先失败。 ## [0.1.0] — 2026-10-30 首个版本。**尚未在真机上安装运行过**,请按 README 的步骤自行验证。 ### 能做什么 - 两个工具:`context_map`(当前分层结构与占用)、`compress`(执行一次压缩);一条只读命令 `/context-status`。 - 四区三层:`原文 → L1 块 → L2 块 → L3 块`。`compress` 的键就是要压的那一层,产物自动升一层;`l3` 是唯一不写 `to` 的键。 - 级联:压深层时,比它浅且还能压的层必须同批给完,否则整批拒绝(只断一次前缀缓存)。 - 零台账:层级、块身份、轮号全部从会话表层现算,块的身份写在正文首行 `〔L1|T16–T19|ev …〕标题`。 - 提醒(不代劳):占用到 `triggerPct`、自上次提醒后又增长、某层超出设计配额时,在**一轮收尾**用 `agent.followup()` 把模型叫醒并附一张完整地图;压不压由模型决定,插件从不自动压缩。 - 超限只报一次话、不催 `/compact`,也不自动重试。 - 压缩成功后折叠调用记录:把「调用 + 返回」两条换成一条短注(含每键结果与地图),避免摘要正文在参数里再存一份。并行调用时降级为“折地图、留每个键的结果”,并提醒模型以后单独发。 - 失败回执只折地图、错误原因逐字保留。 ### 安全边界 - 写入失败必补 `compaction/end`,避免会话此后被宿主判为“压缩进行中”而永久拒绝压缩。 - 会话搜索缺失时拒绝执行(`requireSessionQuery`,默认开)。 - 空节点、缺轮次大纲、某层不足两块、最后一块、`keepTurns` 保护圈等情形都有明确拒绝文案,不静默改数据。 - 只改 `tool/result` 的正文内容,或用一对「调用 + 返回」整段替换成一条用户消息——不做单节点删除,不留孤儿。 ### 发行准备(尚未上架) - 包名 `dsh-ebbing-context`,`publishConfig.access: public`;`private` 已去掉,`prepublishOnly` 会先跑语法检查与全部测试。 - `npm run pack` 生成 `dist/dsh-ebbing-context-0.1.0.tgz`;打 `v*` 标签时 CI 自动打包并挂到 Release,配了 `NPM_TOKEN` 才顺带 `npm publish`。 - 目前**没有发到 npm、也没有建正式 Release**:等真机验证过再上架。