# 更新日志

## v0.5.10

> `0.5.9` 未对外发布：打完内部标签后，在同一轮实机核对里又发现「导航项图标退回外壳默认齿轮」与「动态版一批 `const` 赋值导致工作区标记 / 语言绑定静默失效」两个问题，修完后直接并入本版，`v0.5.9` 标签已删除，避免留下一个没有对应发布的版本号。

**设置页改版 V3 + 工作区静音重写 + 界面语言跟随 DSH locale**。本版没有新增提醒类别，全部是「看得见的地方不好用 / 看着是中文其实是英文」这一层的问题：

- **设置页改版（V3）**：折叠分组 + 每事件一张卡片 + 双主题 token（浅色主题下不再白底白字）+ 图形化控件（开关 / 滑条 / 图标按钮）与自绘悬浮提示；动态版与静态版同一套布局与样式。音量滑条改为**常驻可见**——此前折叠在卡片里，被误认为「音量功能没了」（真机反馈）。原生 `<select>` 弹出层跟随主题（浅色下曾是白底白字，且浮层盖住整页）
- **工作区静音重写**：控制搬到设置页的「工作区静音」分组，侧栏只留**只读标记**。旧实现爬 DOM 抓工作区行、往 React 子树里插按钮，重渲染时标记被冲掉再补回（真机表现为「疯狂闪」）；现在标记渲染在 `body` 下的覆盖层（React 从不管理这些节点），只按工作区行的实际矩形**移动坐标**，定位在行内「…」操作按钮左侧，悬停时隐藏（悬停会展开行内操作、布局变化，标记不跟着乱跳）
- **语言跟随 DSH 的 `locale` 服务**，不再用 `navigator.language`——「英文界面 + 中文系统」时后者会把整页判成中文。同时修掉一批**在模块作用域就求值**的文案（事件名 / 分组名 / 音色名 / 默认音名 / 版本号）：它们在脚本加载瞬间被冻结成中文，切语言后永不更新；现改为 getter 或函数
- **设置分区 `label` 改为 thunk**：外壳每次投影都重新调用（`SlotLabel = string | (() => string)`），导航项文字因此跟着语言变；分区 id 固定为 `chime-alerts`（此前为「刷新导航文字」把 id 改成 `chime-zh` / `chime-en`，绕路且难排查）
- **诊断信息**：标签与值成对输出（旧实现两列错位），新增「语言」与「工作区静音标记」两行，语言行为 `已绑定 · <locale.active> · <实际取值>`
- **设置导航项图标恢复显示（真机发现）**：此前用 `.VOzbGW_navCell:nth-child(5)`（外壳混淆类名 + 硬编码序号）隐藏原图标并换成手绘铃铛。外壳一改，整条规则**一条都不命中**——导航项退回外壳默认齿轮，而第 5 项（Agent 预设）的图标完好，正好证明选择器整体失效。现改为不依赖类名与序号：找「文字恰好等于分区标签、且有直属 `svg`」的行，给它打 `data-chime-nav`，图标由 CSS `::before` 的**描边铃铛**遮罩绘制（与其它导航图标的线条风格一致，也不往 React 子树插节点，不会闪）
- **修复动态版「工作区静音标记」与「语言绑定」完全不工作**：一批本应可变的绑定被写成了 `const`——`for (const i = 0; i < n; i++)`、`const titleEl = null` 之后再赋值、`const overlayHost = null`、`const localeBound = false`。运行时必抛 `TypeError`，而它们都在 `try/catch` 里，于是**静默失效**：动态安装下侧栏从不显示静音标记；`locale` 订阅也没挂上（语言切换不刷新，且每次 800ms 同步都会重复订阅一次）。静态半用的是 `var`，不受影响——这正是它一直没被发现的原因
- 测试 323 项（宿主 124 / 动态客户端 105 / 静态客户端 94）。新增 `tools/fake-dom.mjs`（极简 DOM 桩，只支持客户端实际用到的 `tag` / `.class` / `[attr]` / `[attr="value"]` 形态），让「按 DOM 定位」这两条路径第一次进入自动化测试：导航行必须按文字被打上 `data-chime-nav`、其它分区不得被误标、样式不得再用 `nth-child`、反复同步必须幂等、英文界面按 `Sound Alerts` 也能定位、已静音工作区必须真的渲染出标记并贴在「…」按钮左侧；另新增英文环境下分区 `label` 返回英文、分区 id 固定、版本标注随语言变化

## v0.5.8

**适配 DSH 0.1.5-rc.1 与 0.1.7-rc.2 的契约变更**——此前多类提醒静默失效（不报错、不响，最容易被当成"插件坏了"）。全部按当前运行时的真实签名/类型核对后修正，并补上把旧契约钉死的回归测试。其中两条是用**只读动态探针**在真实 DSH 进程里实测发现的，会另记来源：

- **已知未修（记录待办，2026-09）**：工作区行内静音按钮尚未在真实浏览器验证。它按 `div[role="treeitem"][aria-expanded]` 取行、再用**整行 textContent 精确匹配工作区标题**，匹配失败即静默跳过（按钮不出现、无报错）。代码层面该 role/属性存在，但真实 DOM 未确认；现象待复现后再定改法，详见 README「已知限制」。

- **修复动态安装完全装不起来（实测发现）**：`lib/host.js` 里的 `const nodeIo = __nodeIo` 裸引用了一个**只由静态入口 `lib/index.js` 用 `new Function('ctx','harness','__nodeIo', …)` 注入**的标识符。动态安装（把该文件全文粘进 `cordis_define` 的 `code.host`）里这个标识符根本没有声明，于是 `cordis_run` 直接 `ReferenceError: __nodeIo is not defined`。而 README 方式 B 恰恰教用户粘这个文件——**动态安装路径自 v0.5.3 引入静态直通车起就是断的**，没人发现，因为没有测试覆盖"把 lib/host.js 当动态函数体求值"。改为 `typeof __nodeIo !== 'undefined'` 探测（对未声明标识符安全）。新增断言：把 `lib/host.js` 当**动态**函数体（第三参传 undefined）求值并 apply，不得抛错。

- **修正「需要授权」的检测方式：按形状识别，不按实参位置推断**。`session/event` 的**类型声明**是 `(this: Scoped<Session>, session, event)`，看起来 event 在第三个位置；但用运行时探针（`probe-1`，只读）实测，listener 实际只收到**两个**实参：`args[0]=Session`、`args[1]=SessionEvent`（`argCounts={"2":n}`、`eventAt=[1]`、`argTypes` 恒为 `["Session","SessionEvent"]`）。因此**位置推断是个陷阱**：声明 `(session, event)` 就拿到 Session（旧代码即如此，只因恰好参数个数吻合才「碰对」），声明 `(session, second, third)` 则第三个恒为 `undefined`、整个 handler 直接提前返回（本轮修复过程中真实踩到，一个 `if (event === undefined) return` 就把授权音重新关掉了）。现改为遍历实参、挑出「有 string `type` 且带 `data` 字段」的对象作为事件、「有 `id` + `snapshotEvents()`/`append()`」的对象作为 Session——两参/三参/更多布局都能工作，并由测试同时守住这两种布局
- 同时为 `agent/status` 的 `turn/end` 原因读取加两条兜底路径（`payload.session`、`agents.get(id).session`），避免再次因对象挂载位置变化而静默失效
- **运行契约的两处文档/实测差异**（记录备查，未改动代码）：① `session/event` 的 inspect 签名声称 3 个位置参数，实测 2 个；② 动态宿主半**没有 `process`/`Buffer`**（实测 `typeof process === 'undefined'`），而 `Builtin.listBuiltins` 文档称可用 `process.platform`——故动态安装下 `PLATFORM` 恒为 `null`，走 win32 兼容分支（本机 Windows 无影响，Linux/macOS 的**动态**安装不可靠，静态安装是真实 Node 模块不受影响）
- **修复「任务完成 / 子任务完成 / 其他打断」全灭**：旧代码读 `agent.session.events` 取最后一条 `turn/end` 的 `reason.kind`，但当前 `Session` 类**没有** `events` 成员（只有 `ownEvents()` / `snapshotEvents()` / `eventAt()`），`reason` 恒为 `undefined` 并在 `if (reason === undefined) return` 处直接退出。改为 `ownEvents()` 优先、`snapshotEvents()` 兜底（恢复/分叉会话的继承段），只回扫尾部 200 条
- **修复「目标受阻」永不触发**：Cordis 事件名是 `goal/changed`（带 s），payload 为 `{ agent, change }`（`GoalChanged = { operation, ref?, goal? }`）。旧代码监听的是 `goal/change`——那是**会话日志事件**的名字，不是 Cordis 事件，监听器根本没挂上。判定沿用 `operation === 'block'` 或 `change.goal.phase === 'blocked'`
- **修复静态安装下「需要授权 / Agent 提问 / 计划评审 / 插件授权」浏览器侧不响**：静态客户端原读 `row.pendingInteraction`，但当前 `SessionSummary` 没有该字段（它由 `dsh-client-ui-session` 单独维护，只经 slot 的 `useSessionPendingInteraction` prop 暴露给 React 组件），恒为 `undefined`。新增宿主 HTTP 端点 `/dsh-chime-alerts/events`，把宿主已检测到的这四类增量下发给浏览器，客户端每 1500ms 拉取播放（宿主入队范围随后续修复续有扩大，见下；最终只剩 `goalblocked` 仍由浏览器快照自检）
- `dsh.client.inject` 修正：移除当前依赖树中已无任何包引用的 `@deepseek-ai/dsh-client-runtime`，改列真正提供服务包 `sessions` ← `@deepseek-ai/dsh-api-session-controller`、`workspaces` ← `@deepseek-ai/dsh-api-workspace-controller`
- `lib/types/index.d.ts` 重写：旧声明导出的 `export default function` 与实现（`export const inject` / `export function apply`）不符，且 JSDoc 还写着 PowerShell 蜂鸣与不存在的 README 章节
- 工具链：`tools/chunk-src.mjs` 增加 `--verify`，`npm test` / `npm run check` 会校验 `.chunks/host.txt`、`.chunks/client.txt` 与源码一致（`.chunks/host.txt` 此前比 `lib/host.js` 落后约 26%，按 README 方式 B 粘贴会装到旧宿主）；新增 `npm run chunks`
- **修复静态安装下「后台任务完成 / 后台任务失败」永不响（同类静默失效，本次一并补上）**：DSH 0.1.7 起 `SessionListState` 已无 `jobsBySession`（作业列表迁到独立的 `jobs` 快照，见 `dsh-client-ui-jobs`），静态客户端旧自检分支的 `snap.jobsBySession || {}` 恒为空对象 → `jobdone`/`jobfail` 在浏览器侧永远不响。宿主半的 `jobs.onJobDone` 检测正常（这在 ≤ 0.1.7 成立；DSH 0.2.0 已删除该 API，另有适配见下一条），只是这两类被挡在 `CLIENT_PULL_KINDS` 之外；现把 `jobdone`/`jobfail` 加入该队列，并删除已失效的客户端快照分支，使 `/dsh-chime-alerts/events` 成为这两类的唯一来源（不会双响）
- **修复静态客户端「页面加载后第一响 / 从后台恢复后第一响」被静默吞掉（AudioContext 解锁竞态）**：浏览器自动播放策略下 `AudioContext` 初始为 `suspended`，而 `resume()` 是**异步**兑现的；旧实现同步查 `state` 后直接 `return null`，`playBrowser` 随即以 `false` 结束，提示音被丢弃且无重试、无队列。改为等 `resume()` 落地后再判定是否可播；仍未解锁则进入**有界等待队列**（同类只留最新一条、上限 8 条；超过 15 秒的旧条目不再补响，但最新一条在 120 秒内必定补响——见下方「解锁等待队列」条），在首次用户手势（`pointerdown`/`keydown`/`touchstart`，自动播放策略允许的时机）或上下文 `statechange` 恢复时补响。注：动态半 `lib/client.js` 本就有 `await resumePromise`（静态半当初移植时漏了这一句），故本次只改静态半
- **修复「等待授权的回合被误判为完成」→ 授权音与完成音同时响起（用户报告的「爆音」）**：静态客户端原以「`running` 由 true 变 false 且 `!row.pendingInteraction`」判定完成，而 `SessionSummary` 自 DSH 0.1.7 起**同样没有** `pendingInteraction`，该守卫恒真——一个因等待授权而结束的回合会被判成「完成」，与授权音叠在一起。宿主半的判定一直是正确的（`agent.inbox.hasPending` + `lastTurnEndReason`），只是 `complete`/`subcomplete`/`interrupt` 被挡在 `CLIENT_PULL_KINDS` 之外。现把这三类加入宿主队列，并删除客户端已失效的边沿判定（`prevRunning` 与 `rootOfSid` 一并移除），把判定权收归宿主，`/dsh-chime-alerts/events` 成为这三类的唯一来源（不会双响）；仍由浏览器快照自检的只剩 `goalblocked`（`projectionValues.goal` 仍在）
- **修复音频链路的硬削波（「爆音」的另一半）**：每条音符的 `gain` 原本直接接 `ac.destination`，整条链路没有任何余量——单个提示音峰值约 0.55，两个提示音碰在一起就超过 1.0 硬削波。新增**共享主总线**（`MASTER_GAIN = 0.85` → 内置 `DynamicsCompressorNode`（threshold -6 / knee 3 / ratio 12）→ destination），压缩器只作安全网、阈值卡在单个提示音峰值之上，**不参与音色**；环境缺少 `createDynamicsCompressor` 时自动退回直连。另新增 `nextFreeAt` **错开调度**：总线空闲仍立刻起音（零延迟行为不变），被上一声占着则顺延 `CHIME_GAP = 0.12s`，并发提示音不再同刻叠加
- **修复解锁等待队列会把用户正在等的那一声静默丢掉**：旧队列按「超过 15 秒一律 `continue`」处理——页面刚加载（`AudioContext` 处于 `suspended`）时排队的提示音，只要用户隔了 15 秒以上才做出首次手势（例如读完授权提示再点「授权」），就被无声丢弃，既无日志也无兜底。现保留「旧条目不再补响」以避免解锁瞬间炸出一串旧音，但**最新一条在 120 秒硬上限内必定补响**（新增 `PENDING_STALE_MS`）
- **修复宿主事件通道会被一次瞬时失败永久关闭**：`loadHostBeep()` 与 `pollHostEvents()` 的 `catch` 都把 `hostApiAvailable` 置 `false`，而 `pollHostEvents` 开头据此直接 `return`——启动瞬间端点尚未就绪、请求超时等任何一次瞬时失败，都会让 `approval`/`question`/`planreview`/`pluginapproval`/`jobdone`/`jobfail` 全部静默，直到刷新页面（这正是「有些时候不会出声」的随机性来源之一）。现改为**退避重试**：连续失败间隔 1.5s→3s→…上限 30s，成功一次立刻清零；连续 3 次失败才在设置页标记不可用，但通道仍按退避重试，绝不永久关闭
- **修复「宿主重启后本标签页彻底失声」——静态客户端漏了 boot 令牌守卫（用户实测发现）**：`pollHostEvents()` 只把「已播到第几条」记在内存里（`hostEventSeq`），且**从不读 `data.boot`**。宿主每次重启都会把 `seq` 计数清零，于是重启后本页仍在请求 `after=旧值`（例如 5），而新宿主的事件是 `seq` 1/2/3…——**过滤发生在宿主侧**（`ev.seq > after`），全部被挡掉；同一函数末尾的 `data.seq > hostEventSeq` 也修不回来（新 `seq` 只会更小）。结果：经宿主转发的**全部九类**提醒此后在这个标签页永久静默，只有刷新页面才能恢复。动态半 `lib/client.js` 一直有这道守卫（`res.boot !== myBoot → lastSeq = 0`），静态半当初移植时漏了——与本轮 `resumePromise` 漏写是同一类错误。实测现象：同一个页面手机一直有声音、电脑（长开的标签页）**完全没有声音**，刷新后立刻恢复；同时段宿主侧 `kinds` 与事件记录都正常，故与宿主无关。现补上 boot 令牌：换代即把游标归零并对齐到新宿主（不补响历史事件，与新开页面一致）
- **修复 DSH 0.2.0 下「后台任务」宿主蜂鸣依旧不响——`jobs` 服务换代契约的适配**：上面那条把 `jobdone`/`jobfail` 接进了 `/events`，前提是宿主半能用 `jobs.onJobDone` 检测到作业终止；但 `JobRegistry` 在 **0.2.0** 已删除该 API，改为 `jobs.subscribe(filter, listener)` 事件流（终止态 `{ type: 'settled', job, cause, awaited }`，`job.status ∈ completed | killed | failed`，`job.kind` 为 `bash`/`subagent`）。宿主半现在**三代契约都支持**：有 `onJobDone` 时走旧路（≤ 0.1.7）；否则挂事件流——先试 `jobs.subscribe`（部分 0.1.x 构建），再试 **`jobs.events.subscribe`**（0.2.0+ 的 `JobEvents`；注册表本身**没有** `subscribe`，事件流是它的 `events` 属性；desktop 内嵌 dsh 经 Service inspect 核对，并真机测出「只认 `jobs.subscribe` 就永不挂接」）；一律只认 `settled`、跳过 `kind === 'subagent'` 与非 `completed`/`failed` 的终止态，并用 `ctx.effect` 释放退订。缺这一段时 0.2.0 上宿主根本不会记录这两类，浏览器再转发也不会响
- **修复「每跑一条命令就响一次」——前台工具调用被当成后台作业**：最初判断用了 `settled.awaited`（「结算时是否有人在等」），但真机验证 `run_in_background` 的作业结算时同样为 `true`，只按它过滤会把真后台作业一起静默。最终改用**行为判据 `removed`**：前台 `bash`/`pwsh` 在 `wait()` 拿到结果后**立刻** `remove()`（工具源码里紧挨着），后台作业结算后仍留在可见列表。于是 `awaited` 的结算先挂 1.5s 确认，期间收到同 id 的 `removed` 就撤销；`awaited === false` 的结算直接提醒；同一作业 id 只认第一次结算（防重复投递）。真机验证：前台命令静音、后台作业只响一次
- **修复「后台任务失败」永远判不出来**：DSH 的契约是「非零退出码只**上报**、不算 failed」——`dsh-tool-bash` 的 `processOutcome()` 把退出码写进 `detail`（`exit code: N`），`status === 'failed'` 只在生产端自身崩掉时出现。原实现只认 `status === 'failed'`，于是 `exit 1` 的作业被当成 `jobdone`。现两种都认：`status === 'failed'` 或 `detail` 里的非零退出码
- **`/events` 新增 `jobLog` 诊断环**（最近 80 条原始作业事件：类型 / id / 状态 / `awaited` / `cause`）：真机排查时能直接看到「宿主到底收到了什么」，不用靠猜；浏览器忽略该字段
- **修复干净 clone 下 `npm test` 必然 `MODULE_NOT_FOUND`（打包缺陷）**：`.gitignore` 同时忽略了 `.chunks/` 与 `tools/chunk-src.mjs`，而 `package.json` 的 `test`/`check` 脚本依赖它们 → `git clone` 后 `npm test` 直接起不来，README 方式 B 让用户粘贴的 `.chunks/host.txt` 也不存在。现只把探针报告与临时产物继续忽略（`.chunks/*` + 两条 `!` 例外），把生成器 `tools/chunk-src.mjs` 与 `.chunks/host.txt`、`.chunks/client.txt` 纳入仓库
- 测试 292 项（宿主 122 / 客户端 96 / 静态客户端 74），新增契约回归：旧两参 `session/event` **不得**触发授权、仅带 `.events` 的 Session **不得**判完成、`ownEvents()` 为空时回退 `snapshotEvents()`、`/events` 转发九类且 `after` 增量正确、快照 `pendingInteraction`/`jobsBySession` 均不再触发、`/events` 必须下发 `jobdone`/`jobfail`（subagent 与 killed 跳过）、`suspended` 上下文在 `resume` 落地后仍须出声、`resume` 被拒时排队并在首次手势后补响、排队条目过期后不再补响；另新增两条「求值形态」回归：把 `lib/host.js` 分别当**动态**（无 `__nodeIo` 注入）与**静态**函数体求值都必须能 apply；本轮续增：`/events` 的 `kinds` 必须含 `complete`/`subcomplete`/`interrupt`（且不含 `goalblocked`）、快照 `running→false` 不得判定 `complete`/`subcomplete`、宿主 `/events` 的 `complete`/`subcomplete` 必须播放、并发提示音必须错开起音且音符 `gain` 不得直连 `destination`（须经主总线→压缩器）、排队条目超过新鲜窗口但未超硬上限仍须补响、宿主通道一次瞬时失败后必须重试并恢复播放、宿主重启（`boot` 换代、`seq` 归零）后游标必须跟着归零且新宿主 `seq` 从 1 重新计数仍须播放、**只有 `jobs.events.subscribe` 的 jobs 服务（0.2.0 实测契约）也必须挂接并记录 `jobdone`/`jobfail`（`registered`/`killed`/`subagent` 不计，`awaited === true` 的前台结算不计，重复 id 只认一次）**

## v0.5.7

- **文档重构**：安装指南改为「静态安装（npm，推荐）→ 动态插件（免安装）」排序；补充 CLI 偶发漏注册 bundle 层时的两处手动检查（`dependencies` + `dsh.profile.bundles`）；删除「前身 dsh-chime / dsh-sound-alerts」历史行；版本号全链路对齐（package.json / 设置页版本标注 / 测试断言）
- 发布到 npm：`dsh-chime-alerts@0.5.7`（latest），npm 页面 README 与仓库同步

## v0.5.6

- **修复设置页「切换宿主蜂鸣开关后卡住、开关不再响应」（卡顿根因）**：静态客户端 `useVersion` 的订阅回调写成 `pair[1](pair[0] + 1)`——闭包捕获初值 0，永远 `setState(1)`，React 第一次重渲染后把所有后续 bump 全部吞掉。表现：页面第一次交互后 UI 不再更新——开关点不动（实际已写盘，宿主会响，但界面不反映）、选音量/静音等控件看起来失效。改为函数式更新 `setState(x => x + 1)`（与动态版一致的写法）
- 修复后宿主蜂鸣开关可反复切换、每事件宿主音行随开关实时展开/收起
- 测试 +2（回归：bump 触发 setState / setState 必须为函数式更新 `x => x + 1`——旧实现会在此测试失败），宿主 85 项、客户端 97 项、静态客户端 39 项、合计 221 项

## v0.5.5

- **静态设置页补上「每事件宿主音」配置（宿主蜂鸣开关展开宿主音行）**：动态版一直有——宿主蜂鸣开启后每个事件展开第二行「宿主音」（宿主音下拉 / 独立静音键 / 宿主试听），静态版此前只有总开关，选不了每个事件的宿主音。现在与动态版 UI 对齐：
  - 宿主 HTTP `/sysget` 回传 `hostSounds`/`hostMuted`，客户端启动时读回
  - 新增 `/dsh-chime-alerts/sysbeep`（POST `{kind}`）宿主试听端点——直接播该事件当前配置的宿主音（不依赖宿主蜂鸣总开关）
  - 宿主音选项按平台（win32 系统 wav / linux freedesktop 主题音 / darwin 系统音），选择即写盘持久化；`capBeep=false` 时整行禁用
- **设置页底部标注版本（v0.5.5）**：一眼确认页面加载的是哪个版本的代码，排查「页面还是旧代码」类问题时不用再猜
- 测试 +11（host：sysbeep 端点注册 / POST 200 / 触发 wscript / 播当前宿主音 / 未知种类 400 / GET 405 / sysget 回传 hostSounds+hostMuted；client-web：10 行宿主音展开 / 下拉 / 静音键 / 试听按钮 / 选择写盘 / 静音写盘 / 试听调 sysbeep / 版本标注），宿主 85 项、客户端 97 项、静态客户端 37 项、合计 219 项

## v0.5.4

- **蜂鸣 vbs 走 nodeIo 直通车（静态安装「开关开了也没宿主音」的最后一块）**：`writeBeepVbs` 此前仍经沙箱 fs 写 vbs 到 `%DSH_HOME%\plugins\dsh-chime-alerts`——工作区外写入要先吃一次 `FS_SANDBOX_DENIED` 再靠 `danger-full-access` 重试成功，链路脆弱；现在静态宿主半与设置文件同通道（`node:fs` 直通车），一步到位，宿主蜂鸣路径不再依赖沙箱 fs
- **jobs 服务延迟挂接**：真实 web 树中 jobs 服务可能晚于 chime 条目挂载，apply 期 `ctx.get('jobs')` 取不到 → `onJobDone` 永久失联，静态安装的 jobdone/jobfail 宿主蜂鸣不响；改为缺位时经 `internal/service` 延迟挂接（与 webServer 端点同形态，挂上即撤监听）
- **客户端 HTTP 8s 超时兜底**：`hostFetch` 加 AbortController 超时——端点异常挂起时设置页绝不滞留僵尸请求
- 测试 +5（host：静态 vbs 经 nodeIo 直写 DSH 目录 / spawn wscript / jobs 未就绪暂不挂接 / 服务出现后延迟挂接 / 挂接后 jobdone 正常记录），宿主 79 项、客户端 97 项、静态客户端 29 项、合计 205 项

## v0.5.3

- **静态安装恢复宿主蜂鸣（修复「没宿主响铃了」）**：静态环境没有客户端-宿主 RPC 桥（harness 为 undefined）,此前 `sysget`/`sysset` 无人调用 → 磁盘上保存的 `hostBeep` 永远不生效、`alwaysBeep` 恒为 false。v0.5.3 补齐：
  - **启动自动读盘**：宿主半 apply 时直接读取 `settings.json` 恢复 `hostBeep`/`hostSounds`/`hostMuted`（磁盘设置成为真源）
  - **静态存储直通车（nodeIo）**：静态宿主半经 `lib/index.js` 注入的 `node:fs` 直通车读写 `%DSH_HOME%\plugins\dsh-chime-alerts`——会话沙箱只允许写工作区根，绕过它对用户数据目录的写入限制（与 dsh-liquid-glass-balance-card 写 `$DSH_HOME\storages` 同形态）；动态模式仍走会话沙箱 fs，互不干扰
  - **webServer HTTP 端点**：在 DSH webServer 上注册 `/dsh-chime-alerts/sysget`（GET）与 `/dsh-chime-alerts/sysset`（POST）,静态设置页直接读写宿主设置；注册采用服务延迟监听（`internal/service`）,解决 webServer 在 chime 条目之后挂载的时序问题;仅无 harness 时注册,动态桥共存不受影响
  - **惰性解析 fs/subprocess**：真实 web 树的激活顺序里 fs/subprocess 晚于 chime 条目,apply 时的 `ctx.get` 拿不到 → 改为用时解析,宿主蜂鸣能力检测与设置读写不再依赖挂载时序
- **静态设置页加回「宿主蜂鸣」开关**：之前静态版刻意隐藏了宿主蜂鸣 UI（因无通道不生效）,现在有 HTTP 端点后恢复——点击即写宿主设置并持久化(`%USERPROFILE%\.dsh\plugins\dsh-chime-alerts\dsh-chime-alerts-settings.json`),启动时从 `/sysget` 回读开关状态
- 测试 +14（host：无 harness 注册端点/路径正确/sysget 200/自动读盘恢复 hostBeep/nodeIo 存储根 DSH 目录/sysset 200/sysset ok/HTTP 写盘/写后读回一致性；client-web：宿主蜂鸣开关存在/启动读 /sysget/开关可点击/切换写 /sysset）,宿主 74 项、客户端 97 项、静态客户端 27 项、合计 198 项

## v0.5.2

- **修复静态客户端启动失败（`cannot get property "timer" without inject`）**：`lib/client.web.js` 的插件只声明了 `inject: ['slots', 'sessions', 'workspaces']`,但 `apply()` 使用了 `ctx.interval`/`ctx.timeout`（mixin 访问器内部解析 `ctx['timer']`,未声明时宿主客户端 loader 会整体报错,网页端停止加载「Failed to load plugins / dsh-chime-alerts」）；补上 `inject: ['timer', ...]`,与动态版 `lib/client.js` 的声明一致
- 测试 +4（断言静态客户端 `inject` 声明 timer/slots/sessions/workspaces——覆盖本回归,任何后续把 timer 从 inject 里删掉的改动都会测试失败）
- 这也是之前「重启后仍失败、条目 id 每次变化」的真正原因：变的是**网页端** loader 条目 id（浏览器 boot 阶段为每个 client bundle 建随机 id 条目）,而非宿主补丁条目（`id: chime` 固定）

## v0.5.1

- **修复静态宿主半加载失败**：`lib/index.js` 原用「默认导出函数」形式包装 host.js,Cordis loader 只识别顶层命名导出 `inject`/`apply`,导致 `inject: ['timer']` 不被读取,静态安装报 `cannot get property "timer" without inject`（截图「Failed to load plugins」）；改为标准 Cordis 插件导出（`export const inject: ['timer']` + `export function apply`）,静态安装恢复正常

## v0.5.0

- **静态安装开箱即响 + 网页端完整（修复 v0.4.6 静态安装不响的问题）**：v0.4.6 以 npm/独立目录方式安装时只有宿主半——无浏览器声音、无设置页、宿主蜂鸣默认关，等于装了不响；v0.5.0 提供完整静态客户端，静态安装后浏览器合成音**默认开启直接响**，设置页/工作区静音/网页通知全部可用，零手动配置
- **静态化完成（npm 安装可双端工作）**：新增 `lib/client.web.js`——以 DSH 标准经典脚本封套（`window.__ModuleLoader__.load`，与社区验证的 dsh-plugin-notify-sound 同形态）实现客户端半，**不再依赖动态桥**，直接订阅 `sessions`/`workspaces` 客户端运行时快照自行检测事件
- 事件检测对照（静态版）：`complete`（running→false）/ `subcomplete`（subagent 会话按 parentId 链归属根）/ `jobdone`、`jobfail`（jobsBySession 终态，subagent job 跳过）/ `approval`、`question`、`planreview`（pendingInteraction 上升沿）/ `goalblocked`（goal 投影进入 blocked）；**「插件授权」无快照信号**（由宿主半 `tools/result` 检测 + 宿主蜂鸣兜底）、**「其他打断」并入「任务完成」**（快照无 reason 字段）
- 配置：浏览器 localStorage（键 `dsh-chime-alerts-v1`，与动态版同键，**既有设置自动继承**）；自定义音频以 data URL 存 localStorage（≤3MB，替代动态版宿主磁盘音频库）；静态版设置页隐藏宿主蜂鸣相关 UI（宿主蜂鸣需手动编辑 `%USERPROFILE%\.dsh\plugins\dsh-chime-alerts\dsh-chime-alerts-settings.json`）
- `package.json` 新增 `exports["./client"]` + `dsh.client: { platform: "web", inject: [...] }`；`npm run check` 增加 client.web.js 校验
- 测试 +23（新增 `tools/test-client-web.mjs`：ModuleLoader 封套、八类快照检测、静音、节流、设置页、localStorage 继承、音频库），宿主 64 项、客户端 97 项、静态客户端 23 项、合计 184 项
- **已知差异（静态 vs 动态）**：无「插件授权」浏览器音、无「其他打断」区分、无宿主音配置 UI、自定义音频上限 3MB；更新静态插件后需重启 DSH

## v0.4.6

- **「插件授权」细分为独立事件（第十个事件）**：动态 Cordis 插件等待批准（`cordis_run` 返回 awaiting user approval）不再复用「需要授权」，改为设置页「需要人介入时」分组下的独立「插件授权」项——独立开关、独立声音（默认「门铃 · 短叮咚」，与「需要授权」的慢叮咚区分）、独立音量、独立宿主音与独立静音键
- 宿主 `tools/result` 命中后记录 `pluginapproval` 事件；三平台宿主默认音与「需要授权」一致（UAC.wav / dialog-warning / Ping.aiff）
- 测试 +3（client：10 张卡片 / 渲染「插件授权」行 / 静音键 20 个；host 断言改为 pluginapproval），宿主 64 项、客户端 97 项、合计 161 项

## v0.4.5

- **修复 v0.4.4 动态插件授权声音的漏检**：`tools/result` 的真实运行时文本可能嵌在多层嵌套里（`content[].content[].text`，与会话日志形状一致），单层解析会漏掉；改为**深度收集**所有文本字段后再匹配 `awaiting user approval`
- 测试 +1（嵌套结构命中 awaiting approval），宿主 64 项、客户端 94 项、合计 158 项

## v0.4.4

- **动态插件授权也有声音**：实测发现 cordis 插件（`cordis_run`）的授权**不经过 DSH approval 服务**（不会产生 `approval/asked` 会话事件，会话日志中全部授权事件均为工具授权），原「需要授权」声音收不到它；现新增宿主监听 `tools/result`——`cordis_run` 返回文本含 `awaiting user approval` 时按「需要授权」事件发声（复用该事件的开关/音量/宿主音配置）
- 测试 +2（host：cordis_run 等待授权 → approval；其他工具结果不触发），宿主 63 项、客户端 94 项、合计 157 项

## v0.4.3

- **文档（仅 README，代码无改动）**：删除「声音一览」表格，新增「默认声音」说明——**默认声音全部为浏览器 Web Audio 合成音，实时生成、不附带任何音频文件，无版权、无许可证负担**；宿主系统音效（Windows wav / freedesktop / macOS）属操作系统自带，受系统许可条款约束，且宿主蜂鸣默认关闭、可选开启

## v0.4.2

- **每事件独立静音宿主音**：宿主蜂鸣开启时第二行新增宿主静音键（只静音该事件的宿主系统音，与主行静音键互不影响——主行静音键只关浏览器音）；`hostMuted` 存宿主 settings.json，宿主 `record()` 时跳过静音事件
- **「桌面通知」改名为「网页通知」**：该通知由浏览器 Notification API 弹出（显示在系统通知中心但本质是网页权限），改名避免误解为宿主通知
- 测试 +10（host +5：hostMuted 存读/静音事件不响/取消恢复；client +5：改名/第二行静音键渲染与保存/主行不受影响），宿主 61 项、客户端 94 项、合计 155 项

## v0.4.1

- **宿主能力检测**：`sysget` 新增 `capBeep` 字段——宿主端探测响铃能力（win32 查 `wscript.exe` 是否在 PATH、linux 探测 canberra/paplay、darwin 探测 afplay；无 subprocess 服务时 false），结果缓存
- **不支持时控件变灰**：`capBeep=false` 时宿主蜂鸣开关、宿主音下拉、宿主试听按钮全部禁用（不可点击 + 变灰样式）；浏览器无 Notification API 时桌面通知开关禁用；`capBeep` 未知（如 sysget 未返回）时不禁用，避免误伤
- 测试 +10（host +5：win32/无 subprocess/linux 无播放器/darwin capBeep；client +5：宿主控件禁用/通知开关禁用/不受影响场景），宿主 56 项、客户端 89 项、合计 145 项

## v0.4.0

- **跨平台宿主蜂鸣**：系统音按平台分发——Windows 保持 `wscript`+WMP 播系统 wav；**Linux** 探测 `canberra-gtk-play`（无则回退 `paplay`）播 freedesktop 主题音（`/usr/share/sounds/freedesktop/stereo/*.oga`）；**macOS** 用 `afplay` 播 `/System/Library/Sounds` 系统音
- **宿主音选项按平台**：设置页第二行下拉——Windows 19 种系统 wav / Linux 8 种 freedesktop 主题音 / macOS 11 种系统音;每平台各有语义对应的九事件默认映射(如 Linux 门铃→dialog-warning、坠落→dialog-error)
- **存储 fallback 平台化**：workspaceRoot 落在系统目录时,Windows 用 `%USERPROFILE%\.dsh\plugins\dsh-chime-alerts`(原逻辑),Linux/macOS 用 `$HOME/.dsh/plugins/dsh-chime-alerts`(经 `sh -c 'printf %s "$HOME"'` 解析)
- `sysget` 返回 `platform` 字段,客户端据此渲染宿主音列表
- 测试 +14(host +9:linux canberra/paplay 回退/自定义音/存储 fallback/darwin afplay;client +5:linux 选项渲染与保存),宿主 51 项、客户端 84 项、合计 135 项

## v0.3.22

- **设置页精简**：去掉页尾的大段说明文字（分组 / 每行元素 / 双开关 / 存储位置等长篇解释），只保留本地存储位置一行，界面更简洁
- **推送准备**：`package.json` 的 `repository.url` / `bugs.url` 占位符替换为真实仓库地址（`nienieai/dsh-chime-alerts`），新增 `homepage` 字段

## v0.3.21

- **任务完成的宿主音默认改为系统通知**（`Windows Notify System Generic.wav`，替代钟琴 chimes——完成音用系统通知更贴语义）

## v0.3.20

- **默认选项显示声音名称**：每个事件声音下拉的「默认」项显示该事件实际会响的声音名（如任务完成 → 「凯旋 · 渐强琶音」），不再写「默认（本事件专属音）」
- **宿主蜂鸣开启时每行变两行**：第二行为「宿主音」下拉（19 种系统 wav 可选，如 UAC/邮件/日历/惊叹/错误等）+「宿主试听」按钮；浏览器试听与宿主试听完全分离——主行「试听」只播浏览器合成音，宿主试听只播系统音（不受宿主蜂鸣开关影响）
- **宿主音每事件可配置**：宿主端 `hostSounds` 存 settings.json（跟随 DSH 目录），`sysget/sysset` 支持读写；未配置事件用默认映射
- **宿主响铃默认值重新映射**（更贴语义）：提问→Windows Ding、评审→Windows Default、打断→Windows Notify Email（原提问用 Email、评审用 Calendar 不符合语义）
- 测试 +8（host +4：hostSounds 存读/自定义 wav 蜂鸣/未知拒绝/默认不冗余；client +4：默认名/双行渲染/宿主试听 sysbeep/浏览器试听分离/下拉保存），宿主 42 项、客户端 79 项、合计 121 项

## v0.3.19

- **音量回调消除杂音**：基音 0.55→0.45、泛音 0.26→0.22（大音量下 sine+triangle+泛音叠加导致轻微失真）
- **子任务单独低调**：subcomplete 事件音量 ×0.6（单声轻叩不再偏响）
- **每个声音起名**：声音下拉显示名字——凯旋（任务完成）/ 轻叩（子任务）/ 涟漪（后台完成）/ 门铃（授权）/ 询问（提问）/ 落定（评审）/ 困顿（受阻）/ 惊停（打断）/ 坠落（失败）
- 其余维度（1~4 音、跳跃音阶、时值节奏、渐强渐弱、音色变化、尾音）保持 v0.3.18
- 测试 +1（子任务低调）、更新音量断言,宿主 37 项、客户端 71 项、合计 108 项

## v0.3.18

- **后台任务低调不打扰**：后台任务完成/失败(vol 0.5,约 -6dB)明显轻于前台事件
- **整体音量再加大**：基音峰值 0.45→0.55(泛音 0.26 保持)
- **每音独立音量因子**：渐强/渐弱表情——任务完成渐强(0.6→1)、后台完成渐弱(1→0.55)、授权渐强、评审渐弱、失败渐弱;音型数据升级为 `[频率, 起始, 时长, 波形, 音量]`,事件可带整体 `vol`
- **音色变化**：音符可在 sine/triangle 间切换(如完成第三音、后台完成尾音、提问尾音用 triangle 提亮)
- 保持 1~4 音、连续/跳跃音阶、相同/不同间隔、相同/不同时长、尾音长短(v0.3.16/17)
- 测试 +3(音量 0.55 / 渐强与音色 / 后台低调),宿主 37 项、客户端 70 项、合计 107 项

## v0.3.17

- **每个音符时值/间隔各不相同(节奏化)**：不再等间隔——任务完成「短短短长」琶音(尾音 0.8s)、后台完成「短短长」、授权「慢叮咚」、打断「长音后突停短音」、失败「快速坠落+长叹息」、受阻「重复低音+再下行」
- **纠正不符合语义的声音**：授权从「叮咚×2 大跳」改为慢「叮—咚」单遍高→低(请人来处理)；提问从三连跳跃改为上扬双音(疑问句末尾上扬)；打断从下行双音改为长音后戛然而止(被打断感)；受阻改为重复低音卡住感
- 音符数 1~4 声与跳跃音型保持(v0.3.16),音量 0.45/0.26 与尾音余韵保持
- 测试 +1(时值节奏:complete 尾音更长)、更新音型断言,宿主 37 项、客户端 67 项、合计 104 项

## v0.3.16

- **九种音改为 1~4 声不等**（不再全是四声）：任务完成 4 声上行琶音 523→659→784→1047Hz、子任务完成 1 声轻叮 784Hz、后台完成 3 声跳进上行 392→523→784Hz、授权 4 声叮咚大跳×2 1047→659→1047→659Hz、提问 3 声跳跃上行 523→698→988Hz（增四度大跳）、评审 3 声跳跃下行 988→698→523Hz、受阻 3 声低音下行 392→330→262Hz、打断 2 声下行 698→523Hz、失败 3 声大跳坠落 1047→698→494Hz
- **音调使用跳跃音型**（如 1313 / 1427 式跳进，不再连续级进），每声 0.1–0.2s 间隔、尾音 0.35–0.7s 长衰减；音量保持 v0.3.15 的 0.45/0.26
- 声音下拉文案同步更新（单声轻叮 / 叮咚大跳×2 / 跳跃上行 / 坠落三连 等）
- 测试更新（音符数 1–4 与跳跃断言），宿主 37 项、客户端 66 项、合计 103 项

## v0.3.15

- **九种音全部扩展为四音符**(更饱满):上行琶音=完成/积极(任务完成 C5-E5-G5-C6、子任务 E5-G5-B5-D6、后台完成 D5-F5-A5-D6),叮咚×2 门铃=授权,四连上行=提问,四连下行=评审,低音慢四连下行=目标受阻,快四连下行=打断/失败
- **音量提升**:基音增益 0.32→0.45、泛音 0.22→0.26(约 +40%)
- **保留尾音**:0.25s 自然余韵(铃感)机制不变,尾音音符加长至 0.4–0.7s 长衰减
- 声音下拉文案同步更新(完成音 · 上行琶音 / 授权音 · 叮咚门铃×2 等)
- 测试 +2(四音符×8 振荡器 / 音量 0.45)、更新音型断言,宿主 37 项、客户端 66 项、合计 103 项

## v0.3.14

- **九种合成音统一风格**：全部改为 sine 基音 + sine 2 倍频泛音（原受阻/打断/失败用刺耳 triangle）。音型语法统一——**上行=完成/积极**（任务完成两音、子任务单音、后台完成两音），**高→低双音门铃=需要授权**，**三连上行=提问**，**三连下行=评审结束**，**低音慢三连下行=目标受阻**（392→349→262Hz），**快两音下行=打断**（523→392Hz），**快三连下行=失败**（494→440→392Hz）
- 声音下拉文案同步更新（受阻音 · 低音三连 / 打断音 · 快下行双响 / 失败音 · 快三连下行）
- 测试 +6（九种音全 sine、方向语义：goalblocked 低音区 / interrupt、jobfail、question、planreview、approval 音型），宿主 37 项、客户端 61 项、合计 98 项

## v0.3.13

- **存储位置跟随 DSH 目录**：DSH 以管理员身份启动时进程 cwd=System32，导致沙箱 `workspaceRoot` 落在系统目录、插件数据写进 `C:\Windows\System32`。现检测到系统目录时自动改用 **DSH 数据目录** `%USERPROFILE%\.dsh\plugins\dsh-chime-alerts`（经 `cmd /c echo %USERPROFILE%` 解析用户主目录，失败则回退原路径）；普通工作区场景行为不变
- **旧数据自动迁移**：`workspaceRoot` 系统目录里已写入的 `dsh-chime-alerts-settings.json` / 音频清单 / 音频文件在首次启动时自动复制到新位置（旧文件保留，可手动删除）
- 测试 +5（系统目录→DSH 目录 / cmd 解析 / sysget 路径 / 旧数据迁移 / 普通工作区不变），宿主 37 项、客户端 55 项、合计 92 项

## v0.3.12

- **存储落盘加固**：写入被 workspace-write 沙箱拒绝（`FS_SANDBOX_DENIED`）时，自动以插件自身数据文件身份带 `danger-full-access` 策略重试一次（正常路径仍先走沙箱）——解决 `workspaceRoot` 回退到 DSH 进程目录导致设置/音频/蜂鸣脚本写不进磁盘的问题
- 测试 +2（沙箱拒绝重试 / 正常路径不传 policy），宿主 32 项、客户端 55 项、合计 87 项

## v0.3.11

- **双独立开关**：原「系统级声音」四档下拉改为两个独立开关——**网页响铃**（浏览器合成音，设置存浏览器 localStorage）与**宿主蜂鸣**（系统音效，页面关闭也响，设置存本地磁盘）；旧 `sysMode`/`alwaysBeep` 自动迁移（auto/both/always→宿主蜂鸣开；off→关），不再有"播放失败自动兜底系统音"
- **音频库共享**：导入的自定义音频存入共享库，**所有事件的声音下拉都能选**（`custom:<id>`，按需从磁盘加载）；`saveaudio` 返回库 id、新增 `loadall` 列出库
- 测试更新（+7：双开关/迁移×2/库加载/库选项/失败不兜底），宿主 30 项、客户端 55 项、合计 85 项

## v0.3.10

- **修复本地存储从未落盘**：DSH 沙箱（workspace-write 模式）只允许写工作区根/平台临时区，此前写入目标（DSH 进程工作目录）在工作区外，全部被 `FS_SANDBOX_DENIED` 拒绝——sysMode 存不住、自定义音频存不上、vbs 蜂鸣脚本写不出（静默无声）。现改为以 `sandboxPolicy.workspaceRoot`（工作区根）为存储基准，设置/音频/蜂鸣脚本全部正常落盘
- 测试不变（28 宿主 + 50 客户端 = 78 项）

## v0.3.9

- **系统蜂鸣改走 wscript + 系统 wav，彻底避开 PowerShell**：安全软件（如火绒「隐藏执行PowerShell」防护）会拦截 `node → 隐藏 powershell.exe` 链路并弹窗；改为宿主用 fs 写临时 `.vbs`，由 `wscript.exe` + WMP 播放 Windows 自带音效，脚本播放后自删。九类事件映射不同系统音色（任务完成 chimes / 子任务 ding / 后台完成 Notify System / 授权 UAC / 提问 Notify Email / 评审 Notify Calendar / 受阻 Exclamation / 打断 Ding / 失败 Error），实测不被安全软件拦截、无窗口闪现
- 测试 +2（wscript argv / vbs 内容），宿主 28 项、客户端 50 项、合计 78 项

## v0.3.8

- **移除「声音范围」**：固定监听所有会话（范围控制交给侧栏工作区静音按钮），设置页去掉该卡片，会话级过滤逻辑与 SessionIdCapture 一并删除
- **系统音模式存本地磁盘**：`sysMode`（自动/同时/常驻/关闭）经宿主 `fs` 服务写入 DSH 数据目录（`dsh-chime-alerts-settings.json`），不再存浏览器；旧 localStorage 值自动迁移一次；「常驻」模式仍由宿主即时系统蜂鸣（页面关闭也响）
- **自定义音频存本地磁盘**：上传音频以 base64 存宿主磁盘（`dsh-chime-alerts-audio-<kind>.b64` + manifest.json），插件/浏览器重载后自动恢复；移除 Cache Storage 与 localStorage 文件名存储，换浏览器/清缓存不再丢
- 设置页底部显示本地存储位置，写入失败时显示提示
- 测试 +15（宿主 +9：fs 读写/非法值/常驻联动；客户端 +6：范围移除/本地位置/sysset/loadaudio），宿主 26 项、客户端 50 项、合计 76 项

## v0.3.7

- **中英双语界面**：设置页、事件标签、声音下拉、桌面通知等全部用户可见文案按浏览器语言自动切换（`navigator.language` 以 zh 开头显示中文，其余显示英文；无浏览器环境默认中文），中文用户界面不变
- **文档**：README 注明本插件由 AI Agent 工具辅助开发
- 测试 +5（英文文案断言：分区标题 / 试听按钮 / 分组标题 / 自定义声音选项 / 通知标题），客户端共 44 项、合计 61 项

## v0.3.6

多 Agent 审计驱动的优化批次：

- **修复常驻模式双蜂鸣**（审计发现）：`sysMode='always'` 时宿主 `record()` 已自动蜂鸣，客户端不再重复调用 `sysbeep`——每个事件只响一次系统音
- **修复自定义音频静默降级**（审计发现）：`sound='custom'` 但音频缺失（旧版重启丢失）时降级为本事件默认音，不再静默变系统蜂鸣
- **新增桌面通知**：事件发生时弹浏览器系统通知（`Notification`，同类事件 tag 合并为一条，静音不重复响铃）；设置页新增「桌面通知」开关，首次开启/试听时请求权限
- **多标签页防双响**：localStorage 领导锁（token+时间戳，8s 超时接管），非 leader tab 只消费事件不播放
- **自定义音频持久化**：上传改为 `Blob` + Cache Storage 存储（文件名存 localStorage），插件/浏览器重载后自动恢复 objectURL；旧版"重启即丢"成为历史
- **工作区静音改按 `workspaceId` 键控**（审计建议）：旧版存标题，工作区改名/重名会失效/误伤；自动迁移旧数据
- **文档修正**（审计发现）：README「八套内置→九套」、`jobs.onJobDone` 补后台完成、REGISTRIES「八声→九声」、测试注释八类→九类
- 测试 +6（always 不重复蜂鸣 / 领导锁 / 通知×2 / muted 迁移×2），客户端共 39 项、合计 56 项

## v0.3.5

- **更名 `dsh-chime` → `dsh-chime-alerts`**（市场名称冲突）：目录、npm 包名、cordis.patch.yml 的 name、静态入口导出名、文档全部更新；存储键改为 `dsh-chime-alerts-v1`，旧键 `dsh-chime-v1` / `dsh-sound-alerts-v1` 自动迁移，设置不丢
- README 同步修正为九事件 / 四档系统音 / 49 项测试

## v0.3.4

- **下拉框样式统一**（用户反馈：下拉框样式与设置页其他控件不一致）：去掉浏览器原生下拉箭头（`appearance:none`），自绘 chevron 图标，控件固定 30px 高；系统级声音选项缩短为短文案（自动 / 同时 / 常驻 / 关闭系统音）
- **设置页对齐修复**：事件行名称不换行（`white-space:nowrap`，最小宽度 88px）；下拉框/试听按钮/音量控件统一高度；「系统级声音」由纵向 label 改为与「声音范围」一致的卡片行

## v0.3.3

- **静音标志常驻显示**（用户反馈：静音后要鼠标移到工作区标题才看得见）：已静音的工作区行旁，橙色斜杠喇叭标志**一直可见**（无背景/边框的轻量图标样式），点击即可恢复声音；未静音的工作区维持悬停显示；官方 ⋮/+ 按钮不受影响（仍按悬停显示）

## v0.3.2

- **合并「宿主常驻蜂鸣」与「系统级声音」**（用户反馈：两个控件重复）：删除独立开关，系统级声音下拉改为四档——**自动**（浏览器音为主，失败时系统音兜底，默认）/ **同时**（浏览器音 + 系统音）/ **常驻**（宿主检测到事件立即系统蜂鸣，页面关闭也能响）/ **关闭系统音**；旧设置里 `alwaysBeep=true` 自动迁移为「常驻」
- 测试 +3（always 模式播放/同步/迁移），客户端共 32 项

## v0.3.1

- **设置页按三组分类**：主要通知（任务完成 / 子任务完成 / 后台任务完成）、其他通知（后台任务失败 / 其他打断）、需要人介入时（需要授权 / Agent 提问 / 计划评审 / 目标受阻），每组一个小标题
- **新增「后台任务完成」事件**（`jobdone`）：`jobs.onJobDone` status=`completed` 且非 subagent 作业（子代理完成已有子任务音，跳过避免双响）；浏览器音 = 五度上行双音（D5→A5），系统蜂鸣 = 659→988 上行双音；默认开启
- 测试 +3（host：bash completed → jobdone / subagent 不重复；client：三分类标题），宿主 17 项、客户端 29 项

## v0.3.0

- **合成音改柔**（用户反馈：原音偏硬）：每个音符改为「基音 + 2 倍频泛音（低音量正弦）」双层结构（铃感），起音缩短到 12ms、峰值音量略降，衰减带 0.25s 自然余韵再收尾（不再硬切）；预警类（受阻/打断/失败）由 sawtooth/square 换为 triangle，刺耳感下降
- 测试同步（complete 断言 2 音符 × 基音+泛音 = 4 振荡器），客户端共 27 项；系统级兜底蜂鸣（PowerShell 控制台音，无包络可调）保持原样

## v0.2.9

- **设置页事件改为单行布局，不再折叠**：从左到右 = 名称 / 声音下拉 / **静音图标键**（响铃=开启、斜杠喇叭橙色=静音，替代原开关）/ 音量条 / 音量数值 / 试听；静音的行整体变暗
- 测试 +4（每行静音键 / 音量数值 / 默认静音态 / 点击切换），客户端共 25 项

## v0.2.8

- **自定义声音并入「声音」下拉列表**：列表新增「自定义声音…」选项，选中即弹出上传窗（上传/替换一个入口）；上传完成后选项显示为「自定义声音 · 文件名」
- **删掉外置按钮**：移除事件卡里单独的「上传音频替换…」和「清除」按钮；想换文件时先选其他声音、再选回「自定义声音…」即可重新弹出上传窗（取消上传则回退到原选择；已上传过的文件取消时切回自定义声音）
- 测试 +4（下拉含自定义选项 / 文案 / 预设选择生效 / 外置按钮已移除），客户端共 21 项

## v0.2.7

- 设置页事件卡片**去掉描述文案**（如「Agent 完整结束一轮任务（回合正常完成）」），只保留标题 + 开关，更紧凑

## v0.2.6

- 设置页：**关闭的事件卡片收起**，只显示标题行 + 开关（声音/音量/试听/上传控件仅在开启时显示），页面更清爽（如默认关闭的「子任务完成」只占一行）

## v0.2.5

- **悬停静音键时保持官方按钮显示**（用户反馈：光标移到静音键上后官方 ⋮/+ 消失）：光标悬停本按钮时给工作区行加 `snd-keep-open` 类，样式表用 `.YDXeBa_projectRow.snd-keep-open.snd-keep-open .YDXeBa_rowActions{display:inline-flex}`（双类提升优先级）复刻外壳的 :hover 规则；离开按钮/行延迟隐藏时同步移除类
- 已知依赖新增：外壳哈希类名 `YDXeBa_*`（外壳升级可能失效，见 README 已知限制）

## v0.2.4

- **修复悬停循环闪烁**（用户实测：鼠标在静音键上移动时按钮和官方 + 按钮不停闪）：按钮挂 body（不在行内），鼠标一移上按钮就触发行 mouseleave → 立即隐藏 → 光标又回行上 → 再显示，循环闪烁。改为**悬停意图协议**：行/按钮任一悬停即显示，离开行延迟 250ms 隐藏、离开按钮延迟 200ms 隐藏（给光标跨过按钮留时间），隐藏定时器在卸载/换行时一并清理

## v0.2.3

- **修复双喇叭**（用户实测：静音后出现两个喇叭重叠）：同页可能存在多个客户端实例（频繁 stop/run 时旧实例泄漏）各自挂按钮。新增**单例所有权协议**：body 上的 owner 标记（token+时间戳），只有属主实例创建按钮，属主每次扫描刷新时间戳、死后 5s 被接管；扫描时清理行内残留（旧版注入的）与 body 上非本实例的按钮；工作区服务不可用时不再注入按钮（防误挂未分组行）

## v0.2.2

- **修复工作区静音按钮状态丢失**：按钮原来注入在工作区行内（React 树中），悬停状态变化触发 React 重渲染会抹掉注入节点，点击落空/状态不持久。改为**挂在 `document.body`（React 树外，永不被抹除）**，fixed 定位贴到工作区行右侧（官方 ⋮/+ 左侧 62px），悬停显示/移开隐藏（与官方按钮行为一致），侧栏滚动/窗口变化即时重新定位，插件卸载时清理
- 按钮加背景/边框/阴影（浮层观感），避免悬停时与官方按钮混淆

## v0.2.1

- **静音按钮改到工作区条目旁**（用户澄清「工作区的静音」）：侧栏每个工作区行（如 K230）的 ⋮/+ 旁注入喇叭按钮，**按工作区独立静音**（事件 sessionId → 工作区映射，该工作区的完成/子任务/打断等全部静音；设置页总开关不受影响）
- 移除 v0.2.0 的侧栏底部（sidebar.footer.action）全局静音按钮
- 实现方式：外壳没有工作区行内插槽 → DOM 注入，锚定 `div[role="treeitem"][aria-expanded]`（会话行无此属性），React 重渲染清掉注入节点后由 1200ms 周期扫描自动重挂
- 静音状态持久化（localStorage 的 `muted` 数组）；测试 +3（被静音工作区不响 / 未静音正常响 / 未知会话不误静音），客户端共 17 项

## v0.2.0

- **新增三类事件声音**：
  - `planreview` 计划评审：`tools/execute` 的 `exit_plan_mode`（三连下行 sine）
  - `goalblocked` 目标受阻：会话日志 `goal/change` operation=block（锯齿警报 sawtooth）
  - `jobfail` 后台任务失败：`jobs.onJobDone` status=failed，跳过 subagent 作业避免与打断音双响（短促双响 square）
- **侧栏快捷静音按钮**：注册 `sidebar.footer.action` 插槽（设置旁全局一个），扬声器图标一键静音/恢复，状态与设置页总开关联动并即时持久化
- **仓库结构整理**（对齐社区 npm bundle 插件惯例）：
  - `src/` → `lib/`（host.js / client.js / index.js / types/index.d.ts）
  - 新增 `cordis.patch.yml`（`dsh.bundle.patch` 补丁层，`dsh plugin add` 可装宿主半）
  - `package.json`：exports（types+default）、publishConfig、files、scripts、peerDependencies（`@deepseek-ai/cordis`）、`dsh.bundle.patch` 声明
- **自动化测试**：`tools/test-host.mjs`（15 项）+ `tools/test-client.mjs`（16 项）+ `tools/syntax-check.mjs`，Node 直接跑（`npm test` / `npm run check`），无需浏览器/DSH
- 动态插件重装为 `chime-1/pkg-1`（DSH 进程重启后 sound-2 已消失）

## v0.1.0（开源重命名）

- 项目更名 `dsh-sound-alerts` → **dsh-chime**，搭建开源仓库骨架（v0.2.0 起 `src/` 已整理为 `lib/`）：
  - `lib/host.js` / `lib/client.js`：双端源码（动态插件函数体，粘贴即装）
  - `lib/index.js`：静态宿主入口（npm 包 `main`，与 host.js 同一份逻辑）
  - `package.json`：koishi 元数据、exports、files 等发布字段
  - `docs/REGISTRIES.md`：社区市场（WhaleHub / dsh-market / dshfind / awesome 列表）上架指南
- 客户端存储键迁移：`dsh-sound-alerts-v1` → `dsh-chime-v1`（旧键自动迁移一次，设置不丢）
- 设置分区 id：`sound-alerts` → `chime`
- 宿主半增加 harness 守卫：静态环境（无动态桥）下 handlers 静默不注册，事件记录与系统蜂鸣仍工作

## 动态插件历史（sound-2，pkg-8 → pkg-17）

- **pkg-8**（初版）：四事件 + 设置页 + 混合发声；授权用 `approval/request` 瀑布监听
- **pkg-9**：开关底色修复（关=灰 `#7d8698`、开=蓝 `#3b82f6`，白球旋钮）
- **pkg-10**：授权检测改用会话日志 `approval/asked`（修复“授权音听不到”）；节流从全局改为按种类独立
- **pkg-12**：新增「子任务完成」独立一类（子代理映射回主会话、逐个响）；新增「声音范围」（仅当前会话/所有会话）与「宿主常驻蜂鸣」（页面关闭也能系统级响）；节流细化为种类+来源 agent 独立
- **pkg-13**：默认监听所有会话（scope=all）；子任务音默认关闭；设置入口带声音图标；描述文案随默认值更新
- **pkg-15**：修复子代理完成音不响——origin/原因/归属从防抖定时器内改为 idle 事件同步捕获（一次性子代理在 800ms 防抖内被销毁导致归属映射落空），归属不明时兜底用子代理自身会话 id；新增设置页诊断面板（后于 pkg-17 移除）。实测 2 子代理 = 2 声叮 ✅
- **pkg-16**：设置导航图标改为单一扬声器 SVG（mask + currentColor，随主题变色），去掉齿轮与 emoji——外壳的 navIcon 按 id 写死（仅 models/agent-presets/plugins 有专属图标，其余回退齿轮，无图标注册 API），故用 CSS 隐藏第 5 个导航项的默认 svg 并在 label::before 上绘制扬声器；`nth-child(5)` 依赖当前分区排序（general/models/plugins/agent-presets/声音提醒）
- **pkg-17**（动态插件最终版）：移除诊断面板与 dump 处理器（修复其 JSON 序列化报错，同时不再需要）；系统音选项短文案：自动 / 同时 / 关闭系统音；默认满音量（volume=1）；设置持久化到浏览器 localStorage（key `dsh-sound-alerts-v1`，按 origin 存储；自定义音频文件过大不入库）。✅ 五种声音全部真实事件实测通过：提问（提问工具）/ 完成（回合结束）/ 子任务（后台子代理×2）/ 打断（手动 Stop）/ 授权（真实审批弹窗）
