dsh-web-icon-indicator
浏览器标签页 favicon 实时反映 DSH 会话状态——待机 / 运行中 / 提问 / 完成——让你在标签页置于后台时也能一眼看出是否有会话需要处理。
✨ 功能特性
- 标签页 favicon 实时反映会话状态 —— 浏览器标签页图标同步
idle/running/asking/done(聚合优先级:asking>running>done>idle),后台标签页也能一眼看清 agent 们在做什么——包括ask_user_question提问,以及审批 / 沙箱提权等待(这两种情况会把图标钉在asking态)。 - 单个 SVG,浏览器内上色与动画 —— 只内置一个鲸鱼模板([
icons/base.svg](./icons/base.svg));每个状态、颜色、每一帧都在客户端渲染为data:image/svg+xmlURI,不再有按颜色拆分的图标文件。 - 六种内置特效 ——
static(静止)、blink(闪烁)、breath(呼吸)、rainbow(彩虹)、heartbeat(心跳)、bounce(跳动),全部由 JavaScript 驱动(favicon 不会播放 SVG CSS 动画)。 - 完全可配置、即时生效 —— 每个状态的颜色、特效、周期,以及提问 / 完成驻留时长,改动约 1 秒内同步到已打开的标签页——无需刷新、无需重启。
- 内置配置 UI,无需手写 YAML —— DSH 设置页里的 标签页图标指示器 卡片可编辑整套配置,带实时色块预览,保存后自动写入
settings.yaml(路径见下)。 - 后台标签页与重启抗性 —— 隐藏标签页中
requestAnimationFrame被暂停时,动画态会按墙钟时间补帧;状态轮询还能扛住 host 重启,图标自动恢复。
🛠 配置界面——怎么找到它
| # | 步骤 |
|---|---|
| 1 | 打开 DSH Web GUI,进入 设置。 |
| 2 | 在 插件 选项卡中,打开 插件配置。 |
| 3 | 找到 标签页图标指示器(Favicon indicator) 卡片。 |
| 4 | 展开某个状态行(idle / running / asking / done),编辑 特效、颜色(每个色块即原生取色器),以及周期(毫秒)——仅动画状态显示,静态状态无周期;用 提问驻留 / 完成驻留 调整两个时长。 |
改动会通过 settings 传输层持久化到 profile 的 settings.yaml,约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。
🎬 默认配置,可视化
四个默认状态在浏览器标签页中的实际效果(asking 那条鲸鱼真的在闪烁):
<p align="center"> <img src="assets/states-default.svg" width="420" alt="默认状态:idle 深色鲸鱼、running 黄色、asking 红/黄闪烁、done 绿色"> </p>
| 状态 | 颜色(默认) | 特效(默认) |
|---|---|---|
idle 待机 | #1a1a1a——深色鲸鱼 | static |
running 运行中 | #FACC15——黄色 | static |
asking 提问 | #E5484D ⇄ #FACC15——红/黄 | blink(400ms) |
done 完成 | #22A06B——绿色 | static,保持 doneHoldMs 后回到 idle |
✨ 全部特效,动画演示
下面每个预览都是真实的鲸鱼路径,按插件实际渲染方式做动画(预览是自包含的动画 SVG,在浏览器里直接播放):
| 特效 | 效果 | 预览 |
|---|---|---|
static | 纯色单帧,无动画——使用 colors[0] | <img src="assets/effects/static.svg" width="56" alt="static 特效预览"> |
blink | 在 colors[0] ⇄ colors[1] 之间按 speed 切换(缺省时自动推导更深的第二色) | <img src="assets/effects/blink.svg" width="56" alt="blink 特效预览"> |
breath | 在 colors[0] 与 colors[1] 之间平滑呼吸过渡(缺省时推导) | <img src="assets/effects/breath.svg" width="56" alt="breath 特效预览"> |
rainbow | 以 colors[0] 为起始色相,在 speed 内绕色轮循环 | <img src="assets/effects/rainbow.svg" width="56" alt="rainbow 特效预览"> |
heartbeat | 在 speed 内做「lub-dub」式的尖锐缩放脉冲——颜色为 colors[0] | <img src="assets/effects/heartbeat.svg" width="56" alt="heartbeat 特效预览"> |
bounce | 鲸鱼在 speed 内上下跳动——颜色为 colors[0] | <img src="assets/effects/bounce.svg" width="56" alt="bounce 特效预览"> |
想改颜色并实时观察标签页 favicon 变化?打开自包含 demo([demo/dynamic-color.html](./demo/dynamic-color.html))——选择状态 + 特效并实时改色,favicon 即时更新(无构建、无依赖)。
安装
这是一个标准 DSH bundle 插件。安装到 web profile(GUI/TUI profile 会自动通过 cordis patch 层加载):
从 npm 安装(推荐):
dsh plugin --profile web add dsh-web-icon-indicator@latest从 Git 源码安装:
dsh plugin --profile web add github:waknow/dsh-web-icon-indicator或从本地目录 / tarball 安装:
dsh plugin --profile web add <路径或tarball>或将目录放进 ~/.dsh/profiles/web/node_modules/<name>/,并附带与包内一致的 cordis.patch.yml。
配置
所有键均可选,默认值如下:
| 键 | 默认值 | 含义 |
|---|---|---|
iconsDir | <package>/icons/ | 单个 base.svg 所在目录 |
statusPath | /dsh-web-icon-status.json | JSON 状态端点 |
iconPathPrefix | /dsh-web-icon-indicator | base.svg 的 URL 前缀 |
askingHoldMs | 3500 | 提问状态的最小保持时长 |
doneHoldMs | 5000 | 完成状态保持时长,随后回到 idle |
states | 见下 | 每个状态的视觉配置 |
states 中每个状态是一个对象:{ effect, colors[], speed? }:
config:
states:
idle: { effect: static, colors: ['#1a1a1a'] }
running: { effect: static, colors: ['#FACC15'] }
asking: { effect: blink, colors: ['#E5484D', '#FACC15'], speed: 400 }
done: { effect: static, colors: ['#22A06B'] }effect— 取static | blink | breath | rainbow | heartbeat | bounce之一。colors— 数组,多个 hex 颜色。colors[0]为主色。多色特效读取更多项:blink用colors[0]⇄colors[1],breath在colors[0]⇄colors[1]间过渡(缺省时自动推导更深的第二色),rainbow仅用colors[0]作起始色相。speed— 可选,该状态的周期(ms),也是blink的切换间隔。默认1200。
每个状态条目会在默认值之上做浅合并,因此只需覆盖少量状态。示例:
- id: dsh-web-icon-indicator
name: 'dsh-web-icon-indicator'
config:
states:
running: { effect: breath, colors: ['#FF9900', '#FFD9A0'], speed: 900 }
asking: { effect: rainbow, colors: ['#FF0000'] }
done: { effect: heartbeat, colors: ['#2ECC71'] }设置页与 settings.yaml(DSH ≥ rc7)
插件把上面整套配置注册进 DSH settings 服务,命名空间为 web-icon-indicator (schema 为 lib/index.js 中的 schemastery schema):
- Web GUI: 打开 设置 → 插件 → 插件配置,会出现 标签页图标指示器
卡片,可编辑同样的键(提问/完成驻留,以及每个状态的特效 / 颜色 / 周期), 通过 settings 传输层暂存并保存。每个状态是一行可折叠条目,带主色圆点和一行 摘要(如 blink · #E5484D ⇄ #FACC15 · 400ms);展开该行才显示它的三个字段, 颜色输入框旁会实时预览解析出的色块。
- 持久化: 值写入 profile 的
settings.yaml(默认~/.dsh/settings.yaml)
的 web-icon-indicator: 段。合成条目仍是 base 层;解析顺序为 schema 默认值 → 合成条目 → 设置文档用户层。
- 无需重启服务器、无需刷新标签页即可让设置卡片的修改生效:
askingHoldMs/
doneHoldMs 在主机侧即时生效;各状态的视觉配置(特效 / 颜色 / 周期)会随状态 轮询同步进正在运行的标签页,约 1 秒内生效。只有改 lib/index.js 里的代码级 默认值才需要重载标签页(或重新构建 DSH Web)。
- 浏览器半区是手写的
lib/client.js(ModuleLoader factory 格式——无构建步骤、
无额外运行期依赖,仅用 shell 自带的 react)。DSH 客户端扫描器会在下次启动 profile 时识别新的 dsh.client 声明。
- 未组合 settings 服务的部署不受影响:插件回退到直接读取合成条目,行为与之前完全一致。
实现原理
- Host 插件 + 一个小型浏览器半区:在现有
webServer上注册路由——状态 JSON 端点、静态/dsh-web-icon-indicator/base.svg(鲸鱼模板),以及一个tapIndex向每个index.html注入小段浏览器脚本。整套配置已注册进 DSH settings 服务(web-icon-indicator命名空间)用于校验、持久化与设置页卡片(见上)。 - 状态按
agents.list()聚合,优先级asking > running > done > idle。每次请求都会执行一次reconcile()检测 running → idle 的转换,因为agent/status的 idle 事件在回合结束时并不保证送达。 ask_user_question工具调用(通过tools/pre-execute/tools/result)把会话置为asking,带可配置的最小保持时长,即使你立刻回答,图标也会保持可见。- 权限 / 沙箱拦截等待同样会显示为
asking:当 agent 命中沙箱拒绝并请求提权(sandbox_permissions+justification),或其他工具需要征得同意时,审批服务会先写入一条approval/asked会话事件并阻塞 agent,直到你做出决定。插件监听session/event(并以实时会话日志的权威折叠作为兜底)在整个等待期间将会话置为asking状态,收到approval/decided后清除。 - 浏览器脚本每秒轮询
/dsh-web-icon-status.json,首次获取base.svg,然后每个requestAnimationFrame周期把 favicon 重建为data:image/svg+xml,…URI——把__COLOR__占位符替换为状态配置的颜色,并应用该状态配置的特效。状态响应还会携带当前的每状态视觉配置,因此设置保存后约 1 秒内(下一个轮询 tick)即同步到已打开的标签页,无需刷新。浏览器不会播放 SVG favicon 的 CSS 动画,所以一切动画都由 JS 驱动。由于浏览器在隐藏(后台)标签页会暂停requestAnimationFrame,轮询还会为动画态补绘一帧按墙钟时间计算的画面——后台标签页保持粗粒度动画(约每 1 秒)而不会冻结,切回前台后恢复满速动画。轮询还能扛住 host 重启:瞬时请求失败时先还原原始图标,并在下一个 tick 重试(SPA 原地重连,无需手动刷新图标即可恢复)。
浏览器支持与已知限制
favicon 本质是一张图片,浏览器不会在标签页 UI 里运行 SVG 自带的 CSS/JS 动画——每一帧都在本插件里由 JavaScript 生成。
| 浏览器 | SVG favicon | 逐帧换色 / 换特效 | 说明 |
|---|---|---|---|
| Chrome / Edge | ✅ | ✅ 顺滑 | 实时重读 <link rel=icon>;data: URI 的 SVG 可用。 |
| Firefox | ✅ | ✅ 顺滑 | 对 SVG favicon 支持良好(且会响应其 prefers-color-scheme,本插件未使用)。 |
| Safari(macOS) | ✅ 渲染为静态图 | ⚠️ 尽力而为 | 忽略 SVG 内嵌 CSS;favicon 缓存激进。 |
| Safari(iOS) | ✅ 渲染为静态图 | ⚠️ 基本不刷 | 通常需重新访问标签页才刷新。 |
已知限制(截至 Safari 26.3):
- favicon 有专属缓存。 Chrome 用 favicon 数据库、Firefox 用
favicons.sqlite、Safari 用系统级图标缓存——清普通缓存都清不掉,WebKit 甚至会把「无图标」这一状态也缓存起来。这就是改了图标后,已打开的标签页还可能显示旧图标的原因。本插件已通过「给base.svg与状态端点设置Cache-Control: no-store、请求携带 freshness 参数(?t=Date.now())、每次切换状态时重建<link rel=icon>节点」来缓解。 - Safari 渲染 SVG favicon,但忽略其内部 CSS——不支持
@media、prefers-color-scheme、CSS 动画。所以所有上色必须烘焙进每一帧的标记(本插件正是这么做的),而不能依赖 CSS 变量。 data:URI 的 SVG favicon 在 Safari 不可靠(WebKit bug 236616,仍未关闭;Safari 17.6 复现)。本插件当前每帧都生成data:image/svg+xmlURI,因此在 Safari 上标签页图标可能完全不显示——这是最大的已知缺口。- Safari 的动态 JS 更新为 hit-or-miss,可能需要刷新一次;Safari 会「锁定」它首次看到的图标。目前没有保证可靠的、符合规范的手段能在 Safari 中实时更换 favicon。
- 固定标签页图标(
<link rel="mask-icon">)使用独立缓存,与普通 favicon 分开;它是靠color属性着色的单色剪影——仅 macOS + 固定标签页、页面加载时读取一次、并非实时。
完整机制与来源(WebKit bugs、Stack Overflow、浏览器工程博客)以及让 Safari 更顺滑变色/切换的推荐路径见 [docs/safari-favicon-research.md](./docs/safari-favicon-research.md)。
已知限制
- favicon 的 SVG CSS 动画在浏览器标签页 UI 中不会运行——所有特效都由 JavaScript 每帧重建 data-URI 实现,这是零依赖设计的刻意取舍。(本文档中的动画预览只是演示素材——真实 favicon 的动画始终由 JS 驱动。)
- favicon 行为因浏览器而异,其中 Safari 限制最多——见 [浏览器支持与已知限制](#浏览器支持与已知限制)。
base.svg模板必须保留#p { fill: … }规则中的__COLOR__占位符;浏览器会替换该标记为每帧上色。- 插件运行在 host 平面,必须挂载进 profile 的组合配置,不能作为会话级 agent preset。
- 文件读取走
fs服务,以配置的iconsDir为cwd。请确保该路径在部署环境的沙箱策略下可读。
许可
MIT