# @xain_npm/dsh-client-ui-beep [English](README.md) | 中文 **dsh-beep** —— 面向 Web 界面的「AI 智能体心跳」声音化插件。它用三种程序化合成的 Web Audio 音色,以细微、不打扰的方式告诉你页面上各智能体正在做什么,无需盯着屏幕: > 本包是 [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) 仓库中 > `ui-beep` 插件的独立可发布分支(MIT)。它使用自带的 `tsconfig.json` 与 > `tsdown.config.ts` 构建(仓库内使用共享的 `clientBundle` 预设;本分支独立复刻了 > 相同的输出格式)。 | 音色 | 触发时机 | 声音 | |---|---|---| | **hum(低鸣)** | 任意会话**忙碌中**(工作正在进行)——「Deep diving…」(模型请求在途)、工具执行(运行代码、读取文件)、推理思考——**且**没有会话在等待你的输入、**且**当前会话未在流式输出可见内容。每 4 秒重复一次舒缓心跳;流式输出时暂停(由滴答声接管),有待处理交互时也暂停(提示音已提醒过你) | 柔和低频「lub-dub」心跳声(约 118 Hz + 92 Hz,约 500 ms) | | **tick(滴答)** | 当前会话正在流式输出可见内容 | 2 kHz 高频短促音(约 60 ms) | | **chime(提示音)** | 任意会话开始等待你的输入(审批 / 计划评审 / 提问)。未应答的交互会在 10 秒后再次提示,之后每 30 秒重复一次,直到应答 | 880 Hz + 1320 Hz 双音铃铛声(约 500 ms) | 映射关系源自 AgentPulse:智能体在工作 → 低沉的心跳声;有输出活动 → 轻快的滴答声;智能体在等你 → 清亮的提示音。 ## 工作原理 浏览器端观察三个与 React 无关的可观察数据面: - **`ctx.sessions.list`**(`ObservableSnapshot`):每个会话行的 `running` 标志。 - `running` 即**忙碌**信号:从提示词受理到工具执行、推理思考,整个回合都为 true。只要有**任意**会话在运行,低频**低鸣**就按固定间隔重复(默认 4 秒,可配置)——工作开始的第一拍立即响起(若页面加载时已有会话正在工作,也会立即响起)。仅当当前会话**正在活跃地**流式输出可见内容时低鸣暂停(由滴答声接管——「正在流式输出」指最近 1.5 秒内有文本增长,可配置);当**任意**会话有待处理交互时也**暂停**(提示音已提醒过你)。输出停止约 1.5 秒后低鸣即恢复——即使智能体仍在继续工作(如消息之后的工具调用)——交互清除后也会恢复。最后一个运行中的会话转为空闲后完全停止。这是**电平**判定:它表示「有东西正在工作」。音色本身是柔和的低频 lub-dub 心跳(两段 90–120 Hz 的缓起正弦起伏)——令人安心,而非催促。 - **`ctx.uiSession.pendingInteractions`**(`ObservableSnapshot`):每个会话的有效待处理交互(approval / plan-review / question)。某会话出现 0→1 边沿时播放**提示音**。判定只看**边沿**而非电平——持续等待的会话不会在每次刷新时重复提示。**未应答**的交互会在 10 秒后再次提示,之后每 30 秒重复一次(均可配置),直到被应答或会话消失;页面加载时已处于等待的交互同样启动提醒阶梯(但不会立即提示)。 - **当前会话的对话快照**(`uiConversation.binding(id).snapshot`,`ObservableSnapshot`):其通知器在每一帧组装完成时触发;chat 目标实时 `partial` 中的可见输出文本增长时触发**滴答声**(频率受渲染节奏约束,音频引擎自身的防抖再叠加硬性下限)。 所有音色均在代码内合成,带线性渐入渐出包络——无需素材文件,状态快速切换也不会产生爆音。每种音色有 50 ms 的最小防抖间隔。 ## 浏览器自动播放策略 浏览器会阻止未经用户手势的音频播放,因此引擎会在页面首次 `pointerdown`/`keydown` 时启动,在此之前一律静默不发声。音频不可用时不会抛出异常,也不会刷控制台。 ## 配置 cordis 行可接受 `config:` 对象(所有字段均可选): ```yaml - id: ui-beep name: '@xain_npm/dsh-client-ui-beep' config: volume: 0.5 # 主音量 0…1 enabled: true # false 时完全静音 heartbeatMs: 4000 # 忙碌心跳间隔(毫秒) pendingFirstRechimeMs: 10000 # 未应答交互的首次复响延迟(毫秒) pendingRechimeMs: 30000 # 未应答交互的后续复响间隔(毫秒) streamingPauseMs: 1500 # 最后一次文本增长后仍视为「流式输出」的时长(毫秒) ``` 行 `config:` 作为**组合基准**(composition base)注入持久的 `ui-beep` 用户设置段。 之后由 **设置 → 提示音** 页面接管实际值:一个启用开关、一个总音量,以及每个 模式各自的音量(流式输出提示音 / 工作提示音 / 等待输入提示音),均为 0–200% 并带试听按钮。100% 即 Web Audio 的标称满幅;超过 100% 的部分是留给用户自己的 余量——插件不做任何上限限制,拉高滑块的用户自己决定提示音有多响(超过满幅 可能削波)。默认值保持保守,不会吓到首次使用的用户。修改立即生效并持久化到 用户设置文档;用户覆盖始终优先于行配置。 输入框右下角(模型选择器旁边)还有一个**静音开关**:小喇叭按钮,点击切换 全部提示音静音(静音时喇叭带叉)。它与设置页的启用开关共用同一个持久化 `enabled` 字段,二者保持同步;静音会立即停止循环中的工作心跳,取消静音时 若 Agent 正在工作会立即响起一拍(无需等待下一个心跳间隔)。**工作结束时 也会响一声等待提示音**:最后一个忙碌会话转为空闲的瞬间,提示音提醒你 Agent 已完成、轮到你进行下一步。 ### 每个模式的自定义音频 每个模式都可以播放**用户提供的音频文件**,代替内置合成音。设置页每个模式 有「选择音频」按钮,点击打开全盘文件浏览器(从 `/` 或盘符根目录开始),列出 普通用户的文件夹和音频文件(`.mp3`、`.wav`、`.ogg`、`.flac`、`.m4a`、`.aac`、 `.opus`、`.webm`)——隐藏(dotfile)条目和系统目录会被跳过。所选**绝对路径** 存入设置文档——文件不会被上传或复制,磁盘上的文件可随意移动/替换。播放语义: - **tick / chime(输出/等待)** — 每次触发播放一次自定义文件。 - **hum(工作)** — Agent 忙碌时自定义文件**无缝循环**,因此文件自身长度 决定心跳节奏(文件越长 = 节拍越慢;换文件即可调整间隔)。 - **试听(Preview)** 按钮始终只播放一次自定义文件——即使对 hum 也一样, 试听不会循环播放。 - **没有路径、或路径对应的文件无法读取/解码**(缺失、移动、权限不足、格式 不支持)时,自动回退到内置音。点「恢复默认」清除路径。 Host 端通过两条 loopback、浏览器鉴权的路由(`GET /ui-beep/audio/:voice`、 `GET /ui-beep/browse`)提供文件——路径来自设置文档而非请求 URL,因此路由 无法被指向任意文件。浏览器每次获取并解码文件一次,之后缓存解码结果。 ## 模型体验 无。本包仅是浏览器端对已记录会话事实(运行/忙碌、流式输出、等待交互)的只读声音化;它只播放音频,不注册任何面向模型的内容。模型对自身工作的视图仍由产生这些事实的工具与宿主服务负责。 #### KV 缓存影响 无;本包从不组装或发送 provider 请求。 ## 已知限制与后续工作 - **声音仅限当前页面。** 只有 Web GUI 所在标签页(且已接收过手势启动)才会发声;插件不会跨标签页或宿主进程。 - **每个边沿只发一次音。** 会话进入等待交互时只提示一次;持续等待不会超时复响(macOS AgentPulse 的升级阶梯——30 秒复响、120 秒系统通知——留作后续工作)。 - **滴答声仅针对当前会话。** 后台会话(你未在查看的子智能体)的输出不会发声;只有聚焦会话的流式输出驱动滴答声。