# 更新日志 本文件记录 `dsh-greet-signoff` 的每个发布版本。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/), 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。 ## [1.22.0] - 2026-09-20 > 发哥说:「给上下文、开场语、收尾分别做单独的开关,(苹果滑动开关)」。 > 这一版在设置页最顶上放了一排 **iOS 样式的滑动开关**,三个功能各管各的 —— > 想要干净的输入框就关上下文卡,想只留开场不留收尾(或反过来)就单独关一行。 > 宿主半有改动(提示段按开关生成)→ **需要重启一次 DSH**;关掉/打开都在设置页里拨,拨完自动保存。 ### 新增 - **三个独立总开关(苹果滑动开关)**:设置页最顶上新分区「总开关」,三格并排,每格一个 iOS 风格的滑动开关 (51×31 轨道 / 27 滑块 / 20px 位移,`cubic-bezier(.4,0,.2,1)` 过渡;深色皮肤用 iOS 深色档 #39393d / #30d158): - 🚦 **上下文卡**:关掉后输入框上方那整块都不出现 —— 进度条、六格读数、上下文构成明细、到线红底横幅、 标签页标题的 🚨 前缀一起消失;纯页面显示,不影响模型。实现上走一层门组件 `GreetDockGate` (开关一拨就整块挂上/卸下),**不是**在 `GreetDock` 里 early return —— 后者会让 React 的 hook 数量前后不一致,直接报错。 - 👑 **开场语** / 🏁 **收尾语**:关掉的那一行不再写进系统提示段,模型之后就不写它了; 文案、样式、文案池、场景快照都留着,随时开回来。 - 开关的真身是 ``(键盘 Tab / 空格可用、屏幕阅读器认得出), 轨道与滑块是 CSS 画的两层 span;系统开了「减少动态效果」时过渡一并停掉。 - **配置字段 `switches`**:`{ greeting, signOff, contextBar }`,缺字段 = 开。宿主半与浏览器半**只认明确写成 `false` 为关**,字符串 / `0` / 手改坏的值一律回落成开 —— 老配置升级后功能不会凭空消失。 - **诊断区认得「被开关关掉」这种状态**:上下文档位关着时,自检不再报「进度条未挂载(卡片没出现)」, 而是直说「已按总开关关闭,在设置页拨回来即可」。 ### 变更 - **配置与附件默认跟着插件目录走**:数据目录从写死的 `$DSH_HOME` 改成三级解析—— `DSH_GREET_SIGNOFF_HOME` 环境变量 → **插件目录下的 `config/`** → 旧的 `$DSH_HOME`(兜底)。 插件搬到哪个盘哪个目录,配置就跟到哪;`config/` 已加进 `.gitignore`,不进版本库。 旧位置有配置、插件目录里还没有时行为与 1.21.0 完全一致(老用户不受影响)。 这次搬家:`D:\plugins\dsh-greet-signoff` → `E:\dsh-greet-signoff\dsh-greet-signoff`。 ### 其他 - 单测 87 → 93 项:新增总开关清洗(缺字段 / 坏值 / 老配置一律为开)、`ruleTextWith` 按开关生成 (关一行只少一行、两个都关整段渲染成空串、关掉的行连文案池都不碰)、以及一条源码契约 (总开关区在跳转条第一位、三个 `masterSwitch` 都在、`role="switch"` / 滑块位移 / 门组件都在)。 - `client.js` 380.5 KB,仍在 400 KB 门禁内。 ## [1.21.0] - 2026-09-20 > 发哥在桌面端(Chrome app 窗口)说「这个快捷方式不显示上下文的横幅」—— > 无头 CDP 抓到真凶:`GreetDock` 在**拿不到上下文读数**的客户端里裸读 `occupancy.capacity`, > 抛 `TypeError: Cannot read properties of null (reading 'capacity')`, > 整个 `conversation.input.dock` 槽位崩成一个空占位(`data-slot-error`)→ 进度条和红底横幅一起消失。 > **纯浏览器半修复 → 刷新页面即生效**;宿主半只是版本号跟着走(不重启只会多一行版本提示)。 ### 修复 - **没有 `contextPressure` 读数时不再把整条进度条炸掉**:KPI 的 `limitText` 与占用格 `occupancyTip` 里的 `occupancy.capacity` 改成 `hasReading ? occupancy.capacity : null/—`。此前只要宿主投影还没送到 (新开的窗口 / 桌面端 app 窗口 / 刚连上的客户端),`occupancy` 就是 `null`,一读就崩, 连带把「到线红底横幅」一起拖下水——所以表现是"有的窗口有进度条、有的窗口什么都没有"。 ## [1.20.0] - 2026-09-20 > 发哥指着进度条上一直是空白的那两格问「实测速率 / 到线约还有,怎么老是不显示?」—— > 这一版把它们从"只认本页亲眼看过的采样"改成**宿主半按本机请求记录算**,并在提示里写清数据是谁算的。 > 宿主半(`index.mjs`)有改动 → **需要重启一次 DSH**;浏览器半刷新页面即生效。 ### 修复 - **「实测速率 / 到线约还有几轮」刚打开会话时不再空白**(三个根因一起修): 1. **改由宿主半算**:新增纯函数 `parsePace()`,从会话投影的 `contextTimeline.requests[]` (每条带 `time / turn / step / prompt`)算「观察窗口内首末两条的 prompt 差 ÷ 时间差」, 随 `/api/greet-signoff/context` 的 `pace` 字段返回 —— 开页即有数、换标签页不丢、切会话各自独立。 浏览器半新增纯函数 `pickPace()`:**宿主优先**,宿主给不了(老版本宿主 / 投影缺失 / 这条会话还没有请求记录)才退回本页 45 分钟采样,最后才是跃升点斜率兜底。 2. **解开"相邻两次跃升只差几十毫秒"的死结**:token 占用只在每轮请求结束后更新,一个 5 秒刻度里 可能挤进好几步(实测相邻跃升只差 56~88 ms),旧的 `rateFromJumps` 只认"最近两次",跨度恒不达标 → 兜底永远是 null。现在从最后一次跃升**往前找第一个跨度 ≥ 20 秒的点**,增量取这段区间内各次跃升之和。 3. **每轮涨量改成"轮末之差"**:轮内 prompt 会因为工具结果被裁而下降(实测同一轮 796967 → 785012), 旧实现按"每步涨量"取均值,把「约还能聊 N 轮」和「上一轮 ↑X 万」都算小了。 现在只取每轮的**最后一个点**做相邻轮差值,负数(压缩 / 裁剪)直接跳过。 - **"上一轮 ↑X.X 万"在饱和会话里也能出数**:上下文被 DSH 裁剪之后,轮末 prompt 可能比轮首还低 (实测同一轮 796967 → 785012),这时"轮末之差"是负数。现在负数时退回 "轮内峰值相对上一轮末的涨幅",仍为负才不显示;响应里同时给出 `lastTurnSteps`(最后一轮走了几步)。 - **只有一个轮次时直说原因**:全新会话第一轮还没结束时,"每轮涨量"本来就无从算起, 提示里会写明「每轮涨量要等这一轮结束(这条会话现在只有 1 轮)」,不再让人以为功能坏了。 - **速率读数写清来源**:悬停提示里明说这个数是「按本机记录算:这个会话最近 45 分钟的请求」/ 「按本页采样算」/「按本页跃升算」;长时间没聊(窗口内记录不足两条)时退到最后两次请求并注明。 纯函数 `paceSourceText()` 产出文案。 ### 其他 - 单测 78 → 87 项:新增 `parsePace`(窗口速率 / 轮末涨量 / 峰值兜底 / 脏数据 / 时间倒挂 / 窗口外回落)、 `pickPace`(宿主优先 / 混合来源 / 脏输入)、新版 `rateFromJumps`、`paceSourceText`, 外加一条锁住"进度条三格都从 pickPace 走、旧开关已删"的源码契约检查。 - `scripts/verify-manifest.mjs` 体积阈值 380KB → 400KB(本版 client.js 370.9KB,新增的全是核心诊断逻辑)。 - **文档重写**:README 补齐 1.11–1.20 的全部能力(预算口径与四档预算模式、六格读数、上下文构成明细、花费台账与对账、 交接包 `HANDOFF.md`、场景与文案池、活跃时长与实测速率的算法说明、长尾选项展开/收起),并把已知限制按"读数滞后一轮 / 新页面未知 / 宿主半旧版降级 / HANDOFF 覆盖"逐条写清。 - **预览图重拍**:`docs/preview-chat.png`(对话页 + 六格读数 + 红底预算横幅)与 `docs/preview-settings.png` (设置页全部分区)按当前版本的真实类名与 CSS 规则重新渲染;旧图里的导航条还是三格、没有横幅。 ## [1.19.0] - 2026-09-20 > 发哥一句「按照方案全部执行」。这一版把「提醒该换会话」这条链子补完整:**从"看见红线"到"把活交接出去"不再断在半路**, > 同时让花费读数可校准、多标签不再重复计时、页面切走时不再空转。 > 宿主半有改动(`index.mjs`)→ **需要重启一次 DSH**;浏览器半刷新页面即生效。 > > 2026-09-20 补完:原计划"留待下个版本"的两项(① 占用格的采样口径说明 ② 长尾选项「展开 / 收起」三处统一) > 已在本版一并做完 —— 本版当时尚未发布(没 push、没打 tag),所以直接并进 1.19.0,不再另起小版本号; > 这两处都只动浏览器半,刷新页面即生效。 ### 新增 - **进度条显示「上一轮 ↑X.X 万」**:以前只有"token/分钟"这种匀速度量,看不出是哪一轮把上下文顶炸的; 现在 `.gs-dock-time` 行末尾直接给"上一轮涨了多少",涨幅 ≥ 5 万 tok 时变警示色(深浅主题各一套色)。 纯函数 `lastJumpRise` 只认"账本里 ≥2 次轮次跃升",数据不够就不显示,不猜。 - **交接摘要改走宿主接口落盘(省一次模型往返)**:以前点「总结要点」是把一段提示词塞进输入框、让模型自己 用 write 工具写 `HANDOFF.md` —— 要花一轮完整请求,还得赌模型听话。现在改成:提示词让模型只输出 `<<>>…<<>>` 包起来的摘要,前端观察整个 `document.body`(900ms 静默去抖、流式未写完先跳过、 同一段只落盘一次),抓到手就直接调宿主的 `POST /api/greet-signoff/handoff` 写文件并提示"已写入"。 接口失败自动退回复制到剪贴板。**宿主半那个接口从 v1.14.0 起就存在,但浏览器半一直没调用过 —— 这次接上了。** - **多标签不再重复计时**:同一会话开两个标签时,两边各自累加活跃时长会让数字翻倍。现在每个标签有随机 `tabId`,靠 `gs.signoff.lock.` 锁(12 秒有效期)+ `BroadcastChannel` 辅助选出"记账的那个", 非主标签只读不写。 - **版本错配直接写在进度条旁边**:宿主半版本与浏览器半版本不一致时,`.gs-dock-note` 行直接提示 「⚠️ …不一致:重启一次 DSH 才会全部生效」,不用再翻设置页自检。 - **花费单价可配置 + 与台账对账**:宿主半 `/cost` 支持 `?priceIn=&priceCache=&priceOut=`(元/百万 token, 非法值自动回落内置的 1 / 0.02 / 4),响应新增 `pricing`(本次生效价来自自定义还是内置)与 `reconcile` (算得金额 vs 台账 `ledgerCostCNY` 的差额比例);本会话、今日、近 N 天、Top5 全部使用同一份单价。 设置页新增「计价口径」三项输入与「恢复默认」,并显示对账差额(> 20% 给警示色)—— DSH 调价后不会再静默算错。 - **按历史峰值建议预算档位(只提示,绝不自动改)**:新账本 `gs.signoff.peaks` 记最近 30 次会话的占用峰值, 纯函数 `suggestBudget` 取 P90 给出建议的黄线/红线(样本 < 5 条不给建议);设置页显示建议 +「采纳」按钮, 到线横幅在"实际占用明显超过红线"时追加一句建议。 - **数据源拿不到时直说**:宿主半没响应、或本会话还没有投影文件时,进度条下方明确提示 「拿不到上下文明细:宿主半没响应或本会话还没有投影(重启一次 DSH 试试)」,不再只显示一个「—」。 ### 优化 - **页面隐藏时暂停心跳**:标签页切到后台时跳过心跳的记账与重算,回到前台立即补算一次;隐藏时段不计入活跃时长。 - **「上下文占用」格补上采样口径说明**(原"本轮未实施"第①项,已做完):这一格以前只有一个数字, 看着像随手估的。现在鼠标停上去就说清楚三件事 —— ① 它是"**发下一条消息时预计要喂进去多少 token**", 不是屏幕上现在有多少字;② 数 = 上一次请求真正喂进去的 prompt(`pressureTokens`)+ 那次采样之后界面上新增的部分; ③ **只在每轮请求结束后刷新一次,天然滞后一轮**。预算档下顺带写出当前黄线 / 红线与本次读数, 超过红线还会提示"建议开新会话";切回"占模型窗口"口径则换成另一套说法。纯函数 `occupancyTip` 产出文案。 - **长尾选项的"展开 / 收起"三处统一**(原"本轮未实施"第②项,已做完):以前配色旁边挂一个「全部 37」按钮, 动效与车型是下拉末尾一条「▾ 显示全部(N 项)」,而且**展开之后就收不回来**(只能去设置页底部那个开关切)。 现在配色 / 动效 / 车型共用 `moreOptionLabel` 一套文案与同一个开关值:收起时「▾ 显示全部(N 套)」、 展开时「▴ 只看常用(M 套)」,从同一个下拉里就能收回精选;配色那个独立按钮删掉了 (净减一处重复控件,行为只多不少)。 - 单测:`moreOptionLabel`(量词 / 缺省 / NaN 兜底)、`moreOption` + `isMoreOptionValue`(`__more__` / `__less__` 与 正常选项不混淆、精选表里不许混进保留值)、`occupancyTip`(预算档 / 窗口档两套文案、空参与脏值不炸)、 `dockKpiCells` 把说明挂到占用格自己的 `title` 上(没传仍是空串,老调用方行为不变)。本轮共 78 项全绿。 ### 其他 - **体积门禁 340KB → 380KB**(`scripts/verify-manifest.mjs`):这一版超限的主因是核心功能(读数、交接、互斥、对账), 不是长尾选项 —— 实测 40 种动效 + 32 个图标 + 37 套配色 + 形状/填充/阴影 + 风格预设全部加起来只占 client.js 的 5.9%, 门禁继续只用来防"长尾膨胀"。 - **被评估后否决的改动**:原本计划把长尾表拆成独立 JSON 懒加载 —— 实测只能省 ≤4.2KB(1.2%),却要引入 注入时序、旧配置回显、首屏竞态三类风险,判定不值得,不做。 ## [1.18.0] - 2026-09-20 > 发哥三连问:「已聊时长别把上次的会话也记进来」「实测速率和到线约还有上一轮就没显示」 > 「截图里日常跟跟随默认档是不是重复了」。三件事都查实了,改动全在浏览器半(刷新页面即生效)。 ### 修复 - **已聊时长不再串会话、不再吞断档**:以前直接拿宿主半给的 `session.header.createdAt` 相减; 而宿主半在"不知道你在看哪条会话"时会退回它自己记的上一个会话,于是把**上一次**的创建时间 递过来 —— 新会话就顶着上次的已聊时长。现在两级修:① 只有当宿主半答的 `sessionId` 与当前 会话 id 一致时才采信它的时间(不一致一律不用);② 已聊时长改成按会话 id 各自记账的 **活跃时长**:每 5 秒心跳累加,两次心跳间隔超过 5 分钟算断档、不计入;恢复旧会话时从 当前时刻重新起算,绝不把隔夜的断档算进来。账本存 localStorage,刷新页面接着累。 - **实测速率 / 到线约还有不再长时间显示 "—"**:① 速率门槛从 60 秒放宽到 20 秒(聊得快时 两轮间隔常常不到一分钟);② 时间轴采样不够时,退回"最近两次轮次跃升"的斜率兜底; ③ 采样账本(samples / jumps / jumpTimes)按会话 id 落盘,刷新页面立刻恢复,不用重新等一轮。 说明:DSH 的 token 占用本身只在**一轮请求结束后**才更新,所以这两格仍是"一轮跳一次" 而不是秒级实时 —— 但不再出现"上一轮什么都没有"。 - **预算档菜单不再自相矛盾("日常" vs "跟随默认档")**:查实确实重复 —— 当前档就是默认档时, "日常"那一行被高亮、菜单最后一行又写"跟随默认档 · 当前就是这个",说的是同一件事。 现在:当前生效档只在**它自己那一行**标「✓ 来源 · 当前」(来源 = 本会话临时档 / 来自默认档 / 内置默认);「跟随默认档」只在**本会话设过临时档**时才作为还原操作出现,没设就不出现。 ### 测试 - 新增纯函数用例:`activeElapsed`(首次种子 / 断档不累加 / 连续心跳累加 / 旧会话不从创建时间起算)、 `rateFromJumps`(跨度不足返回 null / 正常给斜率)、`tokensPerMinute` 的 `minSpanMs` 参数。 ## [1.17.1] - 2026-09-20 > 发哥反馈:「箭头左右两侧还是没有跟输入框一样,有时候一样有时候不一样。」 > 只改浏览器半(刷新页面即生效):修"与输入框对齐"这件事的触发条件 —— 以前只有加载时量一次, > 之后 hero↔对话态切换、侧栏收放、会话加载完这类**只改位置不改尺寸**的变化全都漏掉。 ### 修复 - **对齐观察者原来是失效的**:`ResizeObserver` 观察的是 `el.parentElement`,那是宿主插槽层 (`display:contents`,宽高恒为 0),永远不会回调 —— 等于没观察者。现在改为观察真正会变尺寸的 composer 容器 / 输入框卡片 / 它们的父层 / 进度条自己。 - **补上 MutationObserver**(composer 容器子树:子节点、class、内联 style,触发合并到一帧): 位置变了但尺寸没变的情况也能立刻重测。 - **输入框selector放宽**:DSH 现在是 `contenteditable` 富文本(实测 textarea 数量为 0), 以前那条只认 `textarea` / `[contenteditable="true"]` 的路径会量不到;现在认 `textarea, [contenteditable], [role="textbox"]`。 - **量歪保护**:两条边加起来超过容器一半、或卡片比容器还宽,一律判为量歪并丢弃, 保留上一次的正确值 —— 宁可不动,也不会把进度条挤成一条。 - **挂载后补测**从 2 次(300ms / 1200ms)加到 3 次(+2600ms),覆盖会话内容异步长出来的阶段。 ### 实测(无头 Chrome,实时页面) - 基线:`rowPadL=16px`、进度条卡片 `left=493` = 输入框卡片 `left=493`(对齐)。 - 用内联样式把输入框卡片右移 40px(**不触发任何 resize 事件**)→ 进度条自动跟到 `rowPadL=36px`、`left=513` = 卡片 `513`(对齐);撤掉内联样式 → 回到 `16px / 493`(对齐)。 修之前这条路径不会重测,这正是"有时候一样有时候不一样"的来源。 ## [1.17.0] - 2026-09-20 > 发哥要求:「到线约还有多少轮,也移动到实测速率后面,样式也一样。」 > 本版只改浏览器半(刷新页面即生效,不用重启 DSH):KPI 那一排从五格变六格,时间行只剩「上次读数 X 前」。 ### 新增 - KPI 读数排新增第六格 **「到线约还有」**:显示 `N 轮`(玫红系,与状态色绿/黄/红和前面五格都不撞色), 位置就在「实测速率」右边,样式与其它格完全一致(上面 11px 小标签 + 下面同色淡底数字胶囊)。 轮数没算出来时写 `—`;设置里关掉「时间显示」时这一格不出现(沿用原开关语义)。 - 列数跟随格数(4 / 5 / 6 列),六格时 ≤1080px 退回 3 列、≤620px 退回 2 列。 - 单测 1 条(`test/client-pure.test.mjs`):六格顺序、玫红与淡底、暗色档、缺数据写 `—`、老调用方三格不变。 ### 变更 - 进度条下的时间行不再显示「到线约还有 N 轮」(已搬进 KPI 排),只剩「上次读数 X 前」;没内容整行不渲染。 - 顺带修一处旧口径:以前只有"实测速率还没算出来"时才显示轮数,现在只要算得出轮数就一直显示(就在速率右边)。 ## [1.16.0] - 2026-09-20 > 发哥要求:「把『日常』『实测』这两个功能放到(上下文占用那一排)里面,同一排、同样的样式。」 > 本版只改浏览器半(刷新页面即生效,不用重启 DSH):KPI 那一排从三格变成五格。 ### 新增 - KPI 读数排新增第四格 **「预算档」**:显示当前档位(日常 / 大任务 / 省着聊 / 自定义), 样式与占用 / 花费 / 时长完全一致(上面 11px 小标签 + 下面同色淡底数字胶囊,紫色系); 这一格是**可点的**,点它弹出四档 + 「跟随默认档」的菜单(菜单改为挂在格子里向上弹, 不再是"fixed 不带坐标"——那种写法滚动后会跟着视口漂)。 - KPI 读数排新增第五格 **「实测速率」**:`~X/分`(青色系,避开状态色绿,免得和「上下文占用」混淆), 数据不足时写 `—`;它原来只是时间行里的一截小字,现在与其它读数平级。 设置里关掉「时间显示」时这一格不出现(沿用原开关语义)。 - 列数跟随格数(4 / 5 列),窄窗口由 media query 退回 3 列 / 2 列,避免"万/分"被挤成省略号。 - 单测补 1 条(`test/client-pure.test.mjs`):五格顺序、颜色、暗色档、`本会话`后缀、 以及"老调用方不给 showMode/showRate 时仍是最初三格"的向后兼容。 ### 变更 - 紧凑 / 纯文字形态下没有 KPI 排,仍保留原来那个 🎯 小胶囊,保证任何形态都够得着换档。 - 时间行不再重复渲染速率,只留「到线还有几轮」与「上次读数 X 前」。 ## [1.15.3] - 2026-09-20 > 发哥反馈:「截图蓝色框内的进度条线没有颜色,把(这些 token 是谁占用的)文字删除。」 > 本版只改浏览器半(刷新页面即生效):构成明细的每行进度条有自己的颜色,标题也短了。 ### 修复(红卡里那排白条) - 到线是红底白字的卡,而「上下文构成」的填充色此前被写成 `currentColor`(= 纯白), 红底上一排白条分不出哪块是哪块,看着就像"没有颜色"。 - 现在每行按行号取 `PART_COLORS` 里一个固定色(`--gs-part-color`):琥珀/天蓝/绿/粉/紫/青…, 浅底与红底(#d93026)上都够亮、够分得清;没到线时也不再跟着进度条色带一起变红变绿。 ### 变更(标题瘦身) - 展开按钮从「▸ 上下文构成(这些 token 是谁占的)」改成「▸ 上下文构成」; 那句解释挪到 `title` 悬停提示里,红卡里的加粗标题不再长得像一整句话。 ### 验证 - `node --check client.js` 通过;`node --test` 59 项全通过。 - 无头 Chrome 渲染同款红卡 + 同款 CSS:6 行填充色分别为 rgb(245,158,11) / rgb(14,165,233) / rgb(34,197,94) / rgb(236,72,153) / rgb(139,92,246) / rgb(6,182,212),无一行是白色。 ## [1.15.2] - 2026-09-20 > 发哥反馈:「这些内容要放在同一个胶囊型里面,宽度也要一致,不要什么都自己另起一方, > 很不美观又占位置。」本版把进度条区所有零件收进**同一个胶囊容器**(浏览器半,刷新页面即生效)。 ### 变更(一个胶囊容器装下全部零件) - 新增统一容器 `.gs-dock-stack`:三格读数(占用/花费/时长)、进度条、时间行、🎯 预算胶囊、 到线横幅、上下文构成、档位建议、提示文字**全部**装进同一张卡 —— 共用一条边框、一个底色、 一条左右边界;不再各自另起一方。 - 三格读数不再是自己一张卡:去掉边框/底色/圆角,只留一条细分隔线把它与进度条分区。 - 到线横幅也不再挂在容器外面:它现在是同一个胶囊里的一块(红底/黄底仍按线色)。 - 宽度一致性由容器统一保证:容器内容区与输入框**左右边界分毫不差**(缩进仍来自 `composerInsets()`)。 - 🎯 档位菜单改 `position:fixed`(卡片内定位会被圆角卡边界裁掉);打开时恒定在标题行正上方, 并自动夹在视口内。 ### 验证 - `node --test` 59 项全通过;`node scripts/verify-manifest.mjs` 通过。 - 无头 Chrome 实测:容器内容区 841px == 三格/进度条/预算胶囊宽度 841px,三者 left/right 完全一致; 三格 `border-width:0`、背景透明;档位菜单 `position:fixed`、完整可见。 ## [1.15.1] - 2026-09-20 > 发行版级的小修:把到线横幅下面**各自成行的两条提示并进横幅本身**,三块信息共用一张卡 > (浏览器半,刷新页面即生效)。 ### 变更(一张卡,不再上下三条) - 到线/超线时,「上下文构成(这些 token 是谁占的)」与「💡 档位建议」不再挂在横幅**外面**各占一行, 而是并进同一张横幅卡:卡内用一条细分隔线接在标题行下面,底色/文字色继承卡片(红底=白字)。 - 横幅改为纵向两层结构(`.gs-dock-banner` > `.gs-dock-banner-top` + 两个附加块), 标题行与按钮行仍是一行;卡内构成明细的进度条底色改用半透明白,红底上看得清。 - 卡内展开构成明细时停掉红卡的呼吸闪动(`.gs-dock-banner-open`)—— 一闪一闪没法逐行读数。 - 只有**没到线**时(没有卡片可依附),两条提示才恢复成原来的轻量小字各占一行。 ## [1.15.0] - 2026-09-20 > 这一版只改**输入框上方这块进度条长什么样**(浏览器半,刷新页面即生效): > 按发哥选定的「方案 7 · KPI 三格 + 贯通线」重排,配色取「A 语义状态色」+「C 数字胶囊底」。 ### 变更(方案 7:KPI 三格 + 贯通线) - **三格读数**:占用 / 花费 / 时长做成等宽三格(数字对齐成列),三格与输入框**同宽同左右边界** (缩进仍由 `composerInsets()` 实测得出,随窗口与 hero→对话态切换实时同步)。 这三格取代了原来"三行灰字 + 一条粗条"的排布;下方贯通线只保留状态色实色填充。 - 三格拿不到数据的格子一律写 `—`,绝不写 0(写 0 看起来像坏了)。 ### 变更(配色 A:语义状态色) - **占用的数字颜色 = 状态本身**:安全=绿(配色方案第一色)、到黄线=黄、到红线=红, 不用比大小就知道"还能不能聊";进度线填充色跟着状态走(实色,不再走整条渐变; 未走到的部分仍保留淡色色带,越往后越危险)。配色方案(设置页那 37 套)依旧生效,用的是每套的三色。 - **花费固定财神金、时长固定蓝**;深色主题下自动换亮一档(`#b8860b → #e6b84d`、`#2563eb → #7aa2ff`), 跟着方案 C 的深浅判定(`html[data-gs-dark]`)走,dark-only 皮肤也能看清。 ### 变更(配色 C:数字胶囊底) - 每个数字后面垫一层**同色淡底胶囊**(`tintOf()` 现算 `rgba(...)`:浅色 0.14 / 深色 0.24), 颜色仍按语义走;底色用 rgba 而非固定色,所以占用那格换成任何配色方案都不会撞色。 ### 变更(信息不再重复) - 原来那一行"⏱ 已聊 X · 本条会话 ≈¥Y"搬进了三格,进度条下只留三格里没有的两个数: **实测速率**(~X tok/分)与**读数新鲜度**(上次读数 X 前);两者都没有就整行不渲染,不留空行。 - 「紧凑」与「纯文字」两种形态未动(纯文字形态照旧把读数压在一行里)。 ### 新增(测试) - 新增纯函数 `dockKpiCells()`(三格读数与配色)与 `tintOf()`(同色淡底),并补 2 组单测: 三档状态色、深浅两档、坏值/空值一律 `—`、非十六进制颜色不炸。总用例 59 项全过。 ## [1.14.0] - 2026-09-20 > 这一版的主线:**把"还剩多少"变成"花了多少钱、钱花在哪、接下来该干什么"**, > 同时把设置页里那些"选项比需求多一个数量级"的地方收一收。 ### 新增(花费看得见:这条会话花了多少钱) - **为什么加**:进度条一直在说"用了 24.9 万 token",可 token 是个抽象单位 —— 看完还是不知道这值多少钱, 而单价早就标定过(未命中 ¥1/M、缓存命中 ¥0.02/M、输出 ¥4/M)。现在进度条那行直接写 `≈¥0.42`, 悬停提示里也有一份;设置页新增「花费与上下文」分区,给出今天 / 近 7 天 / 最贵几条会话的台账。 - 数据来自本机 `dsh-usage/usage-ledger.json`,**不联网**;宿主半是旧版就整段显示"读不到",不影响进度条本体。 - 顺手把这条反直觉的事实摆在设置页里:**输出价是缓存读的 200 倍** —— 少让我啰嗦,比省那点上下文更省钱。 ### 新增(上下文构成明细:这些 token 是谁占的) - **为什么加**:只说"用了多少",用户没法动手;能砍的只有占大头的几块(系统提示词、工具定义、记忆注入、 助手历史、工具结果)。现在进度条下面多一行「▸ 上下文构成(这些 token 是谁占的)」,点开是一列带占比条 的明细,按 token 降序,前几名就是能砍的地方。 - 数据由宿主半读本机上下文投影(`GET /api/greet-signoff/context` → 本机 `session_projcache`),默认收起、按需展开。 ### 新增(档位建议 + 到线系统通知) - **档位建议**:到线时不再只是报红,还会按客观计数说一句"这条会话已经超线 2.3 倍、还在涨 —— 是个没干完 的大任务就切「大任务」",按钮就在旁边。反向也管:挂着「大任务」档却只用到 45% 以下,会提醒可以降回 「日常」。**只提示,不自动改档**(换档是用户的决定),设置页可关。 - **系统通知**:页面在后台、标签页看不见时,到线可以发一条浏览器通知。设置页「花费与上下文 → 系统通知」 打开,会先向浏览器申请权限;拿不到权限就如实说"没打开",不留一个假开关。 ### 新增(交接包:换会话时把摘要落盘) - **为什么加**:以前点「总结要点」只是把"写摘要"这句话发出去 —— 摘要仍旧留在即将被换掉的那条会话里, 新会话开局还是空的,"提醒 → 换会话"这条链断在最后一步。 - 现在这句话多要一步:**要求把摘要用 write 工具写进工作区根目录的 `HANDOFF.md`**(宿主半的提示段里同时 约定:工作区存在 `HANDOFF.md` 时,开场先读它)。摘要从"对话里的文字"变成"新会话能直接拿到的文件"。 ### 变更(换档入口:从"点一下轮转"改成"点开选") - **为什么改**:旧版点一下胶囊在 `日常 → 大任务 → 省着聊 → 跟随默认` 里轮转 —— 轮到哪一档、下一步变成 什么、当前算"本会话临时档"还是"跟随默认",全得靠猜。现在点开就是一个列表:四档各自的线都写着,当前档 高亮,并注明它是哪来的。三个入口(胶囊 / 横幅按钮 / 设置页)都还在,走的仍是同一个 `applyBudgetMode`。 ### 变更(精简:选项少一点,决定快一点) - **长尾选项默认收起**:40 种动效 / 37 套配色 / 30 种车型,默认只列 6~8 个精选,下拉末尾挂一条 「▾ 显示全部(N 项)」;也可以去设置页「选项范围 → 全部」一次性放开。当前正在用的那一项**永远保留**, 不会因为收起而显示成别的档。 - **旧文案兼容 30 条 → 8 条**(换过固定行才需要的一次性兜底,30 条纯属用不到)。 - **「大小 + 朝向」合并成「尺寸」**(小 / 中 / 大):朝向固定"车头向右",天生朝左的车型自动镜像 —— 多一个"原样"开关只是多一格设置、多一个没意义的选择。 - **诊断分区默认隐藏**:它对日常使用是纯噪音;排障时在「花费与上下文 → 诊断」点一下就能显示。 ### 删除(不再预测"还能聊多久") - **为什么删**:那是拿最近 45 分钟的采样外推出来的,而 token 读数本身还要等上一轮请求结束才更新 —— 猜偏一次,这个数就再也没人信了。现在时间行只留两个"确实发生过"的数:**已聊多久** + **实测 ~1.2k/分**; 「约还能聊 N 轮」只在速率算不出来时作为兜底出现。 ### 新增(测试) - 单测 34 → **39**(浏览器半):长尾选项裁剪(精选 / 保留当前值 / 不共享引用)、金额写法(分、角、元、坏值)、 旧文案 30 → 8 的上限校验,外加一条 v1.14.0 源码级契约测试(花费 / 明细 / 档位菜单 / 诊断开关 / 通知都在)。 - 宿主半:新增上下文投影解析、花费聚合、交接落盘三组纯函数单测。 ## [1.13.0] - 2026-09-20 ### 新增(预算模式 = 「大任务模式」:三档预设 + 自定义) - **为什么加**:预算线只有一组手填的数字时,日常小任务(7.5 万 tok 就该换会话)和一次大改造 (动辄十几万 tok)用的是同一条线 —— 大任务下会一直报红、提醒就变成了噪音。所以给它一个"档位"。 - **三档预设 + 自定义**:`日常` 7.5 万/11 万(默认)· `大任务` 15 万/20 万 · `省着聊` 5 万/7.5 万 · `自定义`(用设置页手填的两个数)。 - **优先级**:本会话临时档 > 全局默认档 > 内置默认。临时档存 `localStorage` 的 `gs.signoff.mode.`,默认档存在 `gs.signoff.ui.budgetMode`。 - **三个入口**: 1. **进度条行里的 `🎯` 小胶囊**(主入口,任何形态都在):点一下按 `日常 → 大任务 → 省着聊 → 跟随默认` 轮转,只改**本会话**;切完在进度条下方那行提示里 回一句"现在生效的两条线是多少"。 2. **到线横幅上的按钮**:红线/黄线横幅里新增「切大任务」(一键抬到 15 万/20 万)和 「跟随默认预算」(清掉本会话临时档)—— 正好放在"要不要开新会话"这个决策点上。 3. **设置页**「进度条外观 → 预算模式」:四个档位按钮,改的是**全局默认档**。 - 还没进会话(hero 态、拿不到 sessionId)时点胶囊会改默认档,免得点了没反应。 - 原来那两项输入框改名为「黄线(自定义档)/ 红线(自定义档)」,语义不变(选「自定义」时才生效)。 - 纯浏览器半改动:刷新页面即生效,不需要重启 DSH。 ### 新增(测试) - 新增 2 组纯函数单测(档位解析优先级 / 胶囊轮转顺序),并把 `resolveBudgetMode`、 `normalizeBudgetMode`、`budgetModeLabel`、`budgetModeHint`、`nextBudgetMode` 挂到 `__test`。 - 新增 1 组源码级防误删测试:三个入口的标签(`切大任务` / `跟随默认预算` / `gs-mode-pill`)必须还在。 ## [1.12.0] - 2026-09-20 ### 变更(进度条口径:从"占模型窗口"改成"占你自己的预算") - **为什么改**:原来那条进度条的分母是**模型窗口(本机 100 万 tok)**,所以一条已经 24.9 万 tok (每轮都要重读一遍、早该换会话)的会话,界面上只显示 **24%**,看着像很安全 —— 这个数回答的是 "离塞满模型窗口还有多远",不是"离你该换会话还有多远"。 - **新的默认口径 = 预算口径**:进度条 **100% = 你自己设的「必须换会话」线**(默认 **11 万 tok**), 黄线默认 **7.5 万 tok**。超了会显示"超 N 倍",条子封顶在 100% 并整条变红呼吸。 - 想回到旧口径:设置页「进度条外观 → 口径」切「占模型窗口」即可(两套随时可切)。 ### 新增 - **一眼就知道该换会话(不用读数字)**: - 到黄线/红线时,进度条下方弹**大横幅**:红底白字 + 呼吸的 `🚨 该开新会话了`, 副标题写明「已 ~24.9 万 tok(预算 11 万,超 2.3 倍)· 先点「总结要点」再开新会话」。 - **浏览器标签页标题**同时加前缀(黄线 `⚠️`、红线 `🚨`)→ 扫一眼标签就知道,不用盯着页面。 - 「总结要点 / 开新会话」两个按钮直接放进横幅里,顺手就能点。 - 设置页「进度条外观」新增三项:**口径**(预算 / 模型窗口)、**黄线**(默认 75000 tok)、 **红线**(默认 110000 tok),外加一段说明。改动即时生效、存浏览器本地。 - token 数改用中文直观写法:`248930 → 24.9 万`、`110000 → 11 万`。 - 进度条那行文字在预算口径下变成 `已用 ~24.9 万 / 11 万 tok · 超 2.3 倍 · 已聊 32 分`, 悬停提示里同时给出"占模型窗口 24%"(两个数都在,但主看预算)。 ### 变更(其他) - 单测 35 → **37**:新增预算口径读数(安全 / 黄线 / 红线 / 超线 / 无读数 / 红线填反的兜底)与中文 token 写法。 ## [1.11.0] - 2026-09-20 ### 新增 - **进度条的时间功能(真的在走,不是摆设)**:输入框上方那条上下文进度条现在多一行 `⏱ 已聊 42 分 · 预计还能聊 ~35 分 · 实测 ~1.2k/分`,并且**每 5 秒自己重算一次**(页面在后台时不折腾): - **已聊多久**:宿主半新增 `GET /api/greet-signoff/session?sessionId=`,把会话的 `session.header.createdAt`(durable 创建时间,浏览器半看不到)交给页面半;拿不到时退回 「这个浏览器第一次看到该会话的时刻」(存 localStorage,刷新/重启不丢)。 - **还能聊多久**:按最近 45 分钟的**读数采样**算实测消耗速率(token/分钟)再推剩余时间; 采样跨度不足 1 分钟、或读数没有净增长时**宁可不给数字**,退回原来的「约还能聊 N 轮」估算。 - **读数新鲜度**:token 读数只在上一次请求结束后才更新,所以读数超过 2 分钟没变时会标一句 「上次读数 X 前」——看着时间在走、百分比不动时不会再误以为进度条坏了。 - 时间行只出现在「完整」形态;「紧凑 / 纯文字」形态把时间并进原来那一行文字里。 设置页「进度条外观」多了「时间显示」开关(默认开)。 ### 修复 - **深色皮肤下固定行颜色认成浅色(方案 C 落地)**:以前只用 `body[data-ds-dark-theme]` 判深浅, 而 dark-only 皮肤(如 astral-choir / remiel-starlit / ice-princess)只改 CSS 变量、 在 `:root` 钉 `color-scheme: dark`,**不加**那个属性 → 深色档颜色(`colorDark`)永远不生效, 深橙字压在暗底上看不清。现在判定改成四条证据任一成立即算深色: ① `body[data-ds-dark-theme]`;② 根元素 computed `color-scheme` 含 `dark`; ③ 系统偏好 `prefers-color-scheme: dark`;④ 页面底色亮度 < 0.5(兜底)。 结果写到 ``,对话里那两行的深色档 CSS 同时挂 `body[data-ds-dark-theme]` 与 `html[data-gs-dark="1"]` 两条选择器(旧主题照旧生效)。 监听 html/body 属性变化 + 系统偏好变化 + 8 秒兜底对表,**换皮肤即时重算**,不用刷新页面。 - 设置页「诊断 → 健康自检」新增两行:**深浅判定**(把四条证据的实测值都列出来)与 **进度条时间行**(是否显示、当前文字),排"颜色不对 / 时间没出现"时不用猜。 ### 变更 - 单测 30 → **35**:新增深浅判定四条证据、CSS 颜色解析与亮度、`chatCss` 双选择器、 时长/时刻格式化、消耗速率与剩余时间估算。 ## [1.10.0] - 2026-09-18 ### 新增 - **按工作区自动换文案**:宿主半从会话的 `Session.header.cwd` 取当前工作目录,按**目录前缀**匹配绑定表 (不区分大小写、斜杠随便写,多条命中取**最长**前缀)。优先级明确:**工作区绑定 > 文案池 > 固定文案**; 某一行没填就自动回落下一级。设置页「场景」分区里有绑定表(路径 + 该项目专用的开场/收尾), 还能点「填当前工作区」从页面里把路径猜出来填上。自检接口也支持 `?cwd=<路径>` 直接验证命中情况。 - **一键外观改成小样卡网格**:16 套外观不再是按钮列表,而是一张张卡片 —— 每张卡把**当前文案**按该预设 直接渲染出来(形状/填充/边框/阴影/颜色一眼可见),当前生效的那套高亮显示。 - **常用色板**:底色与边色各加一排常用色小方块,点一下即设好颜色并自动启用「自定义底色 / 边框」。 - **一键复制到另一边**:把当前行的**文案**或**外观**整包复制到另一行(两个按钮分开,互不干扰)。 - **图片支持拖拽上传**:把图片/GIF 直接拖进虚线框即可载入(拖进来时有虚线框反馈)。 - **撤销上次保存**:底部操作条新增「撤销上次保存」,一键回到上一次保存之前的那份配置(没保存过时置灰)。 ### 修复 - **`section()` 只吃一个子元素(真 bug)**:多传的内容会被静默当成 `options` 丢掉 —— 「外观样式」分区里的**形状 / 填充 / 圆角 / 内边距 / 底色 / 边框 / 边色 / 阴影**整块**从未渲染出来过** (1.4.0 把设置页改成折叠卡片布局时就埋下了)。现在 `section()` 支持可变数量的子元素, 只有当最后一项是"普通对象"(不是 React 元素)时才当作 options。 实测修复后这些控件与 12 个色板小方块全部回来了。 ### 变更 - 单测 27 → **30**:新增按工作区绑定的清洗/匹配/优先级、`lineStyleSource`(复制外观不带走文案与图片)。 ## [1.9.0] - 2026-09-18 ### 新增 - **场景:一键整包切换(工作 / 生活 / 深夜)**。设置页多了一个「场景」分区: 每个场景就是**保存那一刻的整份设置快照**(文案、字体、外观、阈值、文案池), 点场景名=整包切过去,`✎`=用当前设置覆盖它,`✕`=删除;最多 8 个。 坏数据(id 不合法、重复、空名)一律在清洗时丢掉,绝不因为一个场景写坏而让整份配置读不出来; 场景内部不嵌套 `scenes` 自身(宿主半 `normalizeCore` / 浏览器半 `normalizeCore` 分离),彻底避免自我递归。 - **阈值提醒条 + 一键处理**:上下文占用到黄/红阈值时,输入框上方的进度条下面多出一行提醒,右边两个按钮: **「总结要点」**把一段"交接摘要"要求交到你手上(能直接写进输入框就写;写不进去就复制到剪贴板; 连剪贴板也不可用就把整句话显示出来 —— 全程不自动发送,免得误触); **「开新会话」**沿用原来的一键开新会话。 - **调试钩子 `window.__dshGreetSignoff`**:版本、连接自愈计数、贴样式器统计、输入框定位与写入、 阈值判断结果都挂在上面。出问题时在浏览器控制台敲一下就看得见,也是本仓库自动化验证的抓手。 ### 修复 - **输入框定位错了整整一个版本**:DSH 现在的输入框是 `[data-slot="conversation.composer"]` 里的 `[role="textbox"]`(contenteditable),页面上**并不存在** `[data-dsh-part="composer-input"]` 这个更早版本的钩子。 受影响的是连接自愈的草稿判断("有草稿就不自动重载")——它此前恒为 `false`,等于这道保护没生效; 现在改成"钩子 → 插槽 → 可见的最后一个可编辑元素"逐级兜底。 - **「总结要点」改成回读校验 + 三级兜底**:实测 DSH 的输入框是 React 完全受控的 contenteditable, 直接写入会被立刻回滚(回读校验返回 false),所以只有真的写进去了才提示"已写进输入框", 否则退到剪贴板/直接显示。 ### 变更 - 单测 25 → **27**:新增 `contextAlert`(阈值判断)与 `sanitizeScenes`(场景清洗、防嵌套、上限截断)。 - 设置页分区 7 → **8**(新增「场景」),顶部快速跳转条同步多一个胶囊。 ## [1.8.0] - 2026-09-18 ### 新增 - **本轮信息变量:`{model}` `{count}` `{elapsed}` `{tokens}`**。宿主半挂在 `llm/stream` 瀑布上**只读统计** (不改请求、不改 chunk):这一次调用跑了多久、用了多少 token、哪个模型、本会话第几次调用。 于是固定行可以写成 `✅ 以上,本轮 {elapsed} · {tokens} · 第 {count} 次`,或者把 `{model}` 写进开场语。 **语义说明**:`{elapsed}` 与 `{tokens}` 报的是**上一次**调用的数值 —— 本轮的数字在回复之前根本还不知道; `{count}` 把每次模型调用算一轮(含工具往返),重启后重新计数。 - **页面侧的"通配符匹配"**:这些值只有服务端知道,页面不可能自己算出同一个数。所以浏览器半把它们编译成 通配符正则(`^以上,本轮 .+? · .+?$`)来匹配 —— 数值换成多少、换成 `820ms` 还是 `12.4s`,样式都贴得上, 三种匹配模式(逐字/宽松/近似)都生效。 - **健康自检按钮**:设置页「诊断」区点「跑一遍自检」,一次问到底并逐项给结论: 配置读取、固定文案与文案池状态、贴样式器运行次数与命中行数、进度条是否挂载、连接自愈计数、 **宿主半与浏览器半版本是否一致**(不一致就提示需要重启)、**规则文本能不能生成**、 **会话识别是否精确命中**与上一轮统计。 - **新增自检接口**:`GET /api/greet-signoff/rule-text` 返回当前这一刻模型会看到的规则文本(运行时变量已解析)、 会话识别结果与上一轮统计;`GET /api/greet-signoff` 增加 `hostVersion` 字段。 ### 变更 - 单测从 18 个扩到 **25 个**,并新增宿主半单测文件 `test/host-pure.test.mjs`(文案池挑句、运行时变量解析、 耗时/用量格式化、规则文本拼装、pool 字段清洗);`npm test` 现在一次跑两个文件。 ## [1.7.0] - 2026-09-18 ### 新增 - **文案池:开场语 / 收尾语可以"每轮换一句"**。设置页「文案」分区里多了一块「文案池」: 一行一句(最多 20 句,每句 200 字),开启后由宿主半在**每次组装提示词时挑一句** —— 选「随机挑一句」或「按顺序轮换」(轮换游标只在进程内累计,重启后从头开始,够用即可)。 开关是两行共用的,池子内容各存各的:只想让开场语轮换、收尾照旧固定也没问题; 某一行的池子为空时,那一行自动回落到固定文案。池子里的句子同样支持全部动态变量 (`{date}` `{weekday}` `{daypart}` `{time}` …),并且**页面样式对池子里每一句都生效** (浏览器半会把池子里的每一句都编译成待匹配行)。旁边还有「把上面的文案加进池子」一键搬过去。 - **设置页顶部快速跳转条**:文案 / 字体 / 外观 / 提醒 / 进度条 / 旧文案 / 诊断 七个胶囊, 点一下=展开该分区并滚过去(卡片带 `scroll-margin-top`,不会被吸顶预览挡住), 右边还有「全部展开 / 全部收起」一键搞定。折叠状态照旧记在浏览器本地。 ### 修复 - **底部吸底操作条不再压住最后一块内容**:面板底部留白从 58px 提到 74px(操作条 45px + 余量), 滚到底时「进度条外观 / 诊断」这类末段分区的控件完整可见、可点。 ### 说明 - **「大屏两栏」这条没做**:实测 DSH 设置弹窗的面板宽度固定 **549px**(滚动容器 597px), 拆成两栏后每栏不到 300px,编辑控件会挤成一团,反而更难用。所以这一版改成了上面这套 "快速跳转 + 全部展开/收起 + 更紧凑的单列 + 吸顶预览",效果比硬拆两栏好。 ## [1.6.3] - 2026-09-18 ### 新增 - **连接自愈(重启后页面不再永远转圈)**:`dsh web` 每次启动都换访问令牌,已经打开的页面手里那份握手就永远续不上了, 界面上会一直停在**自动重连中**(官方文案本身就是让人手动重开)。现在插件带一个看门狗: 每 5 秒看一眼页面是不是在显示连接异常,**连续 30 秒**都异常、且探活 `GET /` 仍是 200(服务端是通的、只是这个页面的握手废了)、 且输入框里没有未发送的草稿时,自动重载一次页面 —— 重载会用 cookie 重新握手,页面自己就活了。 边界与安全阀:服务真挂了(探活失败)不动作,避免刷屏;页面登录态失效(401/403)也不动作(重载只会换一张错误页); 本标签页每滚动 1 小时最多自动重载 3 次、两次之间至少隔 60 秒;有草稿一律让路(宁可多等,也不弄丢你正在写的东西)。 设置页"诊断"区新增一行「连接自愈:检查 N 次 · 看到异常 N 次 · 已自动重载 N 次」方便自查。 ## [1.6.2] - 2026-09-18 ### 修复 - **「导入配置」「选择图片 / GIF」里的文字贴顶、不垂直居中**:这两个"按钮"其实是 `