Skip to content

dsh-client-ui-beep

Verified

@xain_npm/dsh-client-ui-beep · v0.6.1 · MIT · Web UI

Web agent-heartbeat sonification for DeepSeek Harness: a calm lub-dub hum while agents work, ticks on streaming output, chime when a session awaits your input

Install

dsh plugin add @xain_npm/dsh-client-ui-beep

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Readme

@xain_npm/dsh-client-ui-beep

English | 中文

dsh-beep —— 面向 Web 界面的「AI 智能体心跳」声音化插件。它用三种程序化合成的 Web Audio 音色,以细微、不打扰的方式告诉你页面上各智能体正在做什么,无需盯着屏幕:

要求 DSH ≥ 0.1.7。 该版本用「profile 配置表单」取代了 settingsScope 服务 (及其背后的 settings.yaml)——插件的设置就是它自己的 cordis Config。 0.5.0+ 已改用这套 seam(浏览器端 configForms、Host 端 .volatile() Config schema); 0.4.x 及更早版本在 0.1.7+ 上无法激活。

该要求已写入 peerDependencies(插件运行时真正读取的 DSH 包 + cordis), 因此 Harness 版本不兼容时会在安装阶段报 peer 警告,而不是插件静默激活失败。 (浏览器模块表的 seed——dsh-client-store、dsh-client-ui-slots、 dsh-client-ui-primitives——刻意不声明:它们由 web app 在运行时注入, 在 profile 的 node_modules 里解析不到。)

⚠️ semver 预发布版本的坑。 ^0.1.7-rc.2 能匹配 0.1.7-rc.x 与正式版 0.1.7,但匹配不上未来的 0.1.8-rc.x:semver 规定范围里的预发布版本 只能匹配 major.minor.patch 元组相同的版本。DSH 每次发布新的预发布元组时, 这个范围都需要相应放宽。

本包是 deepseek-harness 仓库中 ui-beep 插件的独立可发布分支(MIT)。它使用自带的 tsconfig.json 与 tsdown.config.ts 构建(仓库内使用共享的 clientBundle 预设;本分支独立复刻了 相同的输出格式)。

安装与使用

npm install @xain_npm/dsh-client-ui-beep
# 或:pnpm add / yarn add

然后在你的 harness cordis.yml(或 cordis.patch.yml 覆盖层)里挂载为 web client 行:

- id: ui-beep
  name: '@xain_npm/dsh-client-ui-beep'
  config:
    enabled: true        # 每浏览器的默认值(各浏览器各自保存自己的设置)
    masterVolume: 0.4    # 主音量 0…2(100% = Web Audio 标称满幅)

开发

npm install
npm run build   # tsc + tsdown → lib/
npm test        # vitest(66 个用例)

发布

npm login                 # 你的 npm 账号
npm version minor         # 升版本(见下方「版本历史」)
npm publish               # prepublishOnly 会先跑 build + test

音色

音色 触发时机 声音
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.uiSession.sessionStatus —— DSH 0.1.7 引入的统一「每会话状态」数据面(它取代了原先分开的 sessions.list + uiSession.pendingInteractions)。它是一个 SessionId → { running, pendingInteraction } 的 Map,一次订阅同时驱动心跳与提示音。
    • running 即忙碌信号:从提示词受理到工具执行、推理思考,整个回合都为 true。只要有任意会话在运行,低频低鸣就按固定间隔重复(默认 4 秒)——工作开始的第一拍立即响起(若页面加载时已有会话正在工作,也会立即响起)。仅当当前会话正在活跃地流式输出可见内容时低鸣暂停(由滴答声接管——「正在流式输出」指最近 1.5 秒内有文本增长);当任意会话有待处理交互时也暂停(提示音已提醒过你)。输出停止约 1.5 秒后低鸣即恢复——即使智能体仍在继续工作(如消息之后的工具调用)——交互清除后也会恢复。最后一个运行中的会话转为空闲后完全停止。这是电平判定:它表示「有东西正在工作」。音色本身是柔和的低频 lub-dub 心跳(两段 90–120 Hz 的缓起正弦起伏)——令人安心,而非催促。
    • pendingInteraction 驱动提示音:某会话出现 0→1 边沿时播放。判定只看边沿而非电平——持续等待的会话不会在每次刷新时重复提示。未应答的交互会在 10 秒后再次提示,之后每 30 秒重复一次,直到被应答或会话消失;页面加载时已处于等待的交互同样启动提醒阶梯(但不会立即提示)。工作结束是它的镜像边沿:最后一个忙碌会话转为空闲的瞬间响一声,提示你 Agent 已完成。
  • 当前会话的对话快照(ctx.uiConversation.binding(id).snapshot,ObservableSnapshot<ConversationSnapshot>):其通知器在每一帧组装完成时触发;chat 目标实时 partial 中的可见输出文本增长时触发滴答声(频率受渲染节奏约束,音频引擎自身的防抖再叠加硬性下限)。当前会话 id 取自渲染器 scope 适配器 —— ctx.uiSession.adapter.current.getSnapshot().key(DSH 0.1.7 把它移到了这里;列表状态已不再携带 current)。

所有音色均在代码内合成,带线性渐入渐出包络——无需素材文件,状态快速切换也不会产生爆音。每种音色有 50 ms 的最小防抖间隔。

浏览器自动播放策略

浏览器会阻止未经用户手势的音频播放,因此引擎会在页面首次 pointerdown/keydown 时启动,在此之前一律静默不发声。音频不可用时不会抛出异常,也不会刷控制台。点试听按钮同样会直接启动引擎(点击本身就是手势)。

配置

行 config: 就是插件的设置文档 —— DSH 0.1.7 把插件设置存成它自己的 cordis Config,每个字段都声明为 .volatile(),因此在设置页写入的值会即时生效、无需重启。所有字段均可选:

- id: ui-beep
  name: '@xain_npm/dsh-client-ui-beep'
  config:
    enabled: true        # 「从未选择过的浏览器」的初始默认值(见下)
    masterVolume: 0.4    # 每浏览器默认值;主音量 0…2
    tickVolume: 1        # 每浏览器默认值;流式输出音量 0…2
    humVolume: 1         # 每浏览器默认值;工作心跳音量 0…2
    chimeVolume: 1       # 每浏览器默认值;等待输入/完成音量 0…2
    tickPath: ''         # 自定义音频的绝对路径(留空则用内置音)
    humPath: ''
    chimePath: ''

各项设置都是「每个浏览器各自一份」

enabled 与四个音量字段不是共享开关——它们是「从未选择过的浏览器」的 初始默认值。一旦你在某个浏览器里拨过开关或拖过滑块,该浏览器就把自己的值 存进 localStorage,此后 Host 里对应字段的值对它不再起作用:

设置页开关 + 各音量滑块 ┐
                        ├─→ localStorage(每浏览器)→ 各设备互不影响,
输入框小喇叭          ──┘                        同一浏览器内各处永远一致
        ↑
Host config.enabled / *Volume = 仅作初始默认值

自定义音频「路径」仍留在 Host(文件在运行 harness 的那台机器上)

原因:声音是从这个浏览器发出来的,就该由这个浏览器决定——而且手机外放 和电脑音箱本就不是一回事。这也意味着改动立即生效、不需要往返 Host;而往返 正是这些控件在手机上不可靠的原因:写入带乐观并发 revision,快照陈旧就会被拒, 旧代码又把失败静默丢弃(所以控件看起来「点不动」)。手机静音了,电脑照常响; 同一浏览器内的各处 UI 永远显示同一个状态。

每个字段独立回退:只改过音量,开关仍跟随 Host 默认值。这些选择跨刷新、 跨重启永久保存;无痕窗口(或存储被禁用)下退化为「仅本次会话有效」,而不是报错。

设置 → 提示音 页面管理同一份数据:一个启用开关、一个总音量,以及每个 模式各自的音量(流式输出提示音 / 工作提示音 / 等待输入提示音),均为 0–200% 并带试听按钮。100% 即 Web Audio 的标称满幅;超过 100% 的部分是留给用户自己的 余量——插件不做任何上限限制,拉高滑块的用户自己决定提示音有多响(超过满幅 可能削波)。默认值保持保守,不会吓到首次使用的用户。

内部节奏不可配置。 心跳周期(4 秒)、复响延迟(10 秒 / 30 秒)与流式暂停 窗口(1.5 秒)是 watcher 内部的默认值;0.5.0 之前它们是行配置项,现在刻意 不再放进 Config(它们属于调参,不是用户设置)。

输入框右下角(模型选择器旁边)还有一个静音开关:小喇叭按钮,点击切换 本浏览器的提示音静音(静音时喇叭带叉)。它与设置页开关读写的是同一个 本机文档,因此二者永远不会不一致;静音会立即停止循环中的工作心跳, 取消静音时若 Agent 正在工作会立即响起一拍(无需等待下一个心跳间隔)。

每个模式的自定义音频

每个模式都可以播放用户提供的音频文件,代替内置合成音。设置页每个模式 有「选择音频」按钮,点击打开全盘文件浏览器(从 / 或盘符根目录开始),列出 普通用户的文件夹和音频文件(.mp3、.wav、.ogg、.flac、.m4a、.aac、 .opus、.webm)——隐藏(dotfile)条目和系统目录会被跳过。所选绝对路径 存入插件 Config(一个普通字符串字段)——文件不会被上传或复制,磁盘上的文件 可随意移动/替换。播放语义:

  • tick / chime(输出/等待) — 每次触发播放一次自定义文件。
  • hum(工作) — Agent 忙碌时自定义文件无缝循环,因此文件自身长度 决定心跳节奏(文件越长 = 节拍越慢;换文件即可调整间隔)。
  • 试听(Preview) 按钮始终只播放一次——即使对 hum 也一样,试听不会循环。 试听不受静音开关限制:你想试听一个声音,恰恰是在决定要不要开启提示音的 时候,所以静音状态下点试听照样出声(主音量/单音色音量照常生效,听到的就是 该音色实际的响度)。
  • 没有路径、或路径对应的文件无法读取/解码(缺失、移动、权限不足、格式 不支持)时,自动回退到内置音。点「恢复默认」清除路径。

Host 端通过两条 loopback、浏览器鉴权的路由(GET /ui-beep/audio/:voice、 GET /ui-beep/browse)提供文件——路径从插件 Config 读取、而非来自请求 URL, 因此路由无法被指向任意文件。浏览器每次获取并解码文件一次,之后缓存解码结果。

版本历史

  • 0.6.1 —— 四个音量滑块也改为每个浏览器各自一份(与开关同一机制), 各设备独立控制自己的响度;只有自定义音频路径仍归 Host。
  • 0.6.0 —— 开关改为每个浏览器各自一份(localStorage):各设备独立控制 自己的提示音,设置页开关与输入框小喇叭永远不会不一致。Host 的 enabled 字段 降级为「从未选择过的浏览器」的默认值。另:静音状态下也能试听。
  • 0.5.0 —— 迁移到 DSH 0.1.7 的 settings seam(破坏性变更:要求 DSH ≥ 0.1.7 / cordis ≥ 4.0.4)。Host 设置改为插件自己的 Config schema; 浏览器端改用 ctx.configForms;watcher 改用 uiSession.sessionStatus。
  • 0.4.0 —— 设置页、0–200% 音量、每音色自定义音频文件、输入框静音开关。

模型体验

无。本包仅是浏览器端对已记录会话事实(运行/忙碌、流式输出、等待交互)的只读声音化;它只播放音频,不注册任何面向模型的内容。模型对自身工作的视图仍由产生这些事实的工具与宿主服务负责。

KV 缓存影响

无;本包从不组装或发送 provider 请求。

已知限制与后续工作

  • 声音仅限当前页面。 只有 Web GUI 所在标签页(且已接收过手势启动)才会发声;插件不会跨标签页或宿主进程。
  • 提醒阶梯没有系统通知层。 未应答的交互会在 10 秒、之后每 30 秒复响,但没有系统通知环节(macOS AgentPulse 阶梯的「120 秒系统通知」留作后续工作)。
  • 滴答声仅针对当前会话。 后台会话(你未在查看的子智能体)的输出不会发声;只有聚焦会话的流式输出驱动滴答声。