# 更新日志 / Changelog 本文件记录所有值得注意的变更。版本号遵循 [SemVer](https://semver.org/lang/zh-CN/):`MAJOR.MINOR.PATCH`。 **版本号只有一个来源:`package.json`。** 运行时可在两处确认当前加载的是哪一版: - 设置 → 避峰岗,标题下方的 `版本 vX.Y.Z`; - `debug.log` 中每次加载插件时的那一行:`apply: vX.Y.Z, enabled=…`。 (`DSH_HOME/plugins/dsh-peak-valley-router/debug.log`) ## 版本策略(pre-1.0) 当前处于 **0.x**:功能与界面已可用,但**路由语义、设置页布局、内部接缝(`agent/request` / `internal/get`)仍可能在小版本之间调整**。 `policy.json` / `state.json` 的字段保持向后兼容:新增字段只追加,旧文件无需迁移;历史上写出的嵌套 `peak.model` 写法仍然可读。 **1.0 的条件**:路由语义与配置格式冻结,并经过若干个版本的实际使用。在那之前的破坏性变更会写在下面。 ## [0.11.0] - 2026-10-15 **显式的「全局解耦」小开关**:把「这个会话跟不跟随全局」变成一件可见、可控的事。 ### 语义(明确下来) - **全局 = 总闸**:切换它对所有「跟随中」的会话一次性生效(扫一遍)。 - **局部单独改过的会话不再被全局切换带走**——两个方向都不跟随;要重新跟随就显式接回。 - **解耦 / 接回只切换连接关系**,不改变「此刻是否生效」。 ### 新增 - composer 输入区左侧新增第二个小开关「解耦」(两个圆环相接=跟随全局,分开=已解耦): - 跟随中 → 点一下**解耦**:把当前有效值固定为本会话的独立值,可见状态不变,此后全局切换不再影响它; - 已解耦 → 点一下**接回**:清除独立值,重新跟随全局。 - 会话列表小灯对已解耦的会话多一圈淡环,提示文案也区分「跟随全局 / 已解耦」。 - 侧边栏的全局开关提示里显示「· N 个已解耦」——否则解耦过的会话在全局侧完全不可见。 - 第一个按钮的右键「恢复跟随全局」保留,作为接回的快捷方式。 ### 工程 - `scripts/verify.mjs` 的文案 key 校验此前只匹配 `t('key')`,漏掉带参数的 `t('key', { … })`;已修正。这次 `decoupledCount` 的命名不一致(客户端叫 `decoupledCount`、文案表写成 `decoupleCount`)正是被放宽后的正则与 copy 测试一起抓出来的。 - 新增断言:已解耦的会话在全局**两个方向**翻转时都不跟随,接回后重新跟随。 ## [0.10.2] - 2026-10-14 独立复核(对抗性)报出的缺陷修复。复核的 9 项必查全部通过,以下是我据此改掉的真问题。 ### 修复 - **整数型会话 id 不再被提前淘汰**:JS 对象会把整数型键排到最前并按升序枚举,所以「删除后重新赋值」**不会**把它移到末尾——`"0"` 这种 id 刚设置完就会被当成最旧条目淘汰,覆盖**静默消失**(与 0.10.1 修的 `__proto__` 属于同一类静默丢失)。内存里的覆盖表改用 `Map`(插入顺序对所有键形状都精确);落盘仍是 `{ id: boolean }` 对象,旧文件与客户端契约不变。补了针对整数型 id 的回归断言(旧的容量测试只用 `s${i}`,抓不到)。 - **非字符串 `sessionId` 不再静默变成全局开关**:`toggle { sessionId: 12345, enabled: false }` 过去会走进"没有会话 id"的分支,把**所有**会话的默认路由关掉,而且回包里没有 `sessionId` 字段,调用方根本看不出走了哪个分支。现在「存在但不是非空字符串」一律返回 `bad-request`,并记一行日志。 - **`toggle` 的两种参数形状语义统一**:按会话的路径在不传 `enabled` 时会翻转当前有效值,而全局路径过去是**静默 no-op**。现在全局路径同样翻转。 - **视觉接管不再闪烁**:挂载/卸载与取值更新拆成两个 effect。此前每次切换都会先注销(清掉注入的样式与 `html` 属性)再重新注册,模型选择器的置灰会闪一下。 - **首次加载不再先显示错误状态**:第一次 `rpc.get` 返回前不渲染开关(此前会先显示"未启用"再翻成真实状态)。读失败仍会渲染并带 `!` 标记。 ### 工程 - `npm run verify` 改为**只读**:产物新鲜度检查现在把客户端**构建到临时文件**再比对,不再重写仓库里的 `lib/client.js`;比对前统一换行符,CRLF 检出不再误报"产物陈旧"。 - 版本一致性守卫扩展到文档:`README.md` 与 `SKILL.md` 的「当前版本」行必须与 `package.json` 一致。此前 README 停在 `v0.9.0` 而 package.json/CHANGELOG/tag 都已是 0.10.x,旧守卫只核对 CHANGELOG,所以一直是绿的。 - 新增随机化差分属性测试(`npm run fuzz`,可传种子):200 次随机操作 × 12 个探针 id(含 `__proto__`、`constructor`、超长与 Unicode id),期望值独立地从 `state.json` 重算。 ### 文档 - README 补齐行首小灯的**四条**隐藏条件(归档 / 空白 / 正在运行 / 有未读),此前只写了两条。 - 新增「已知限制」一节:跨窗口与多进程不同步(后写覆盖整张 `sessions` 表,无合并)、目标模型不在目录时静默回落(小灯反映的是开关意图而非此刻必生效)、并发点击在写盘期间被禁用且不报错。 ## [0.10.1] - 2026-10-14 ### 修复 - 覆盖表改用 null-prototype 映射:会话 id 若是 `__proto__` 这类特殊键,在普通对象上赋值会被原型 setter 静默吞掉——覆盖看起来存下了、却永不生效。node 端与客户端两处都改,并加了回归断言。 ## [0.10.0] - 2026-10-14 **每会话独立的避峰岗状态**,并在会话列表上用一个小蓝灯标出哪些会话开着。 ### 新增 - 开关从「全局」扩展为「全局默认 + 每会话覆盖」:没有覆盖的会话跟随全局默认,单独设置过的会话以自己的状态为准。`state.json` 里 `enabled` 是默认值,`sessions` 是每会话覆盖表;**没有 `sessions` 的旧文件行为完全不变**。 - 会话列表行首小灯:注册在 `sidebar.session.row.leading`,仅当该会话的有效状态为开时出现(小圆点 + 柔和外发光 + 极缓慢呼吸,尊重 `prefers-reduced-motion`)。 - 会话内开关(composer 输入区左侧)改为操作**当前会话**;**右键**可清除覆盖、恢复为跟随全局默认。侧边栏底部的开关仍然是全局默认。 - 覆盖表有上限(500 条,按最近写入淘汰),长期使用不会无限增长。 ### 变更 - `get` RPC 增加 `sessions`(每会话覆盖表)。 - `toggle` RPC 支持 `{ enabled, sessionId }` 与 `{ sessionId, inherit: true }`。 - 模型选择器的置灰改为跟随**当前会话**的有效状态(会话内入口优先),不再只看全局开关。 - 日志里的接管/决策行带上会话 id,便于核对是哪个会话被路由。 ### 说明与已知限制 - 会话 id 取自 `agent.session.header.id`(`session.id` 同值)。读不到时按**全局默认**处理,不会静默关闭路由。 - 行首位置由宿主决定:会话**正在运行(有状态点)或有未读标记**时,宿主不渲染该插槽,因此小灯在这些时刻不显示。这是宿主的条件渲染,插件不去改它。 ## [0.9.0] - 2026-10-14 首个公开版本(V0.9)。 ### 功能 - 按时间路由:策略 = 生效星期 + 峰时窗口;命中峰时用副模型,否则用主模型。 - 高级模型支援(升维兜底):所有策略都未命中时使用。 - 设置页三档下拉:Provider / 模型 / 思考档;候选全部来自 harness 运行时的注册表,**API key 与订阅登录同源**,没有硬编码名单。 - 左侧「启用避峰岗」开关(侧边栏入口 + 会话输入区备用入口),按控件**自身容器宽度**自适应。 - 开关打开时,策略对**模型与思考档**均有决定权:会话里已显式选定的模型同样会被接管。 - 模型级兜底:策略目标不在该 Provider 目录中时保留原模型,绝不阻塞请求;目录刷新全程在后台,请求路径不等待网络。 - 版本可在运行时确认(设置页与 `debug.log`)。 ### 修复(0.9.0 定版前修掉) - **副模型形状错配**:服务端曾把副模型写成嵌套的 `peak.model = {provider, model, …}`,而设置页与 `lib/text.js` 默认定义读的是扁平字段 `peak.provider` / `peak.model`。后果是每次 get/set 往返后副模型下拉变空,且保存时被静默替换成主模型。现在输出统一为扁平;旧的嵌套写法仍能被读入,并在下次保存时改写为扁平。 - **思考档不生效**:模型相同但思考档不同时曾被判定为"无需改写",导致策略里配的思考档失效。现在思考档不同同样按策略改写。 - **加载安全性**:非法配置不再让插件加载失败,而是回退到默认定义并留一行日志。 - **请求路径零阻塞**:过期的目录刷新曾发生在请求路径上(同步发起 `listModels`),已移出到下一个宏任务。 - **事件作用域**:`llm/adapters-updated` 补上 `global`,避免依赖作用域默认行为。 - **界面文案**:英文界面下星期显示中文(硬编码)已移入文案表;服务端校验报错中文化并保留「第几条策略」;保存后给出明确的成功/失败反馈。 ### 工程 - `lib/text.js` 是**界面文案与默认定义的唯一维护文件**;客户端产物 `lib/client.js` 由 `src/build-client.mjs` 生成,不手工编辑。 - 测试:`npm run verify` = 契约自检 + 副模型往返回归 + 文案与本地化检查。