dsh-web-icon-indicator 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.4.2...v0.5.0) (2026-09-17)
11
+
12
+ ### Added
13
+
14
+ * add GitHub Pages showcase site with live bilingual demos ([92ecd70](https://github.com/waknow/dsh-web-icon-indicator/commit/92ecd705217bf3e3a2f52edb86a05665c4ed5b08))
15
+ * add skills-lock.json to manage skill dependencies ([935ad4c](https://github.com/waknow/dsh-web-icon-indicator/commit/935ad4c90a1ba5e6c969ad21dc52c5fbff516c1e))
16
+ * per-instance default icon colour with a similarity warning ([a673c10](https://github.com/waknow/dsh-web-icon-indicator/commit/a673c109b622a074b4d4018caa104cbfb819f149))
17
+
18
+ ### Fixed
19
+
20
+ * harden host/browser behavior and stop advertising unbakeable settings ([5dddbb8](https://github.com/waknow/dsh-web-icon-indicator/commit/5dddbb87c387c8e85785c1d298453919213492d9))
21
+ * repaint favicon instantly when the tab becomes visible ([cd5dfaf](https://github.com/waknow/dsh-web-icon-indicator/commit/cd5dfafdd79f330f775ea35a0a9163ad7ed838cb))
22
+
23
+ ### Changed
24
+
25
+ * pin the registration-time route keys and sync the workflow docs ([de370c5](https://github.com/waknow/dsh-web-icon-indicator/commit/de370c54d3b0777d028bd6ba871756202496c76d))
26
+
27
+ ## [0.4.2](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.4.1...v0.4.2) (2026-09-07)
28
+
29
+ ### Fixed
30
+
31
+ * offline-safe restore keeps the tab icon alive when the DSH host stops ([217f741](https://github.com/waknow/dsh-web-icon-indicator/commit/217f7412bc7f1e2214bd50877b542e5d49257fd4))
32
+
10
33
  ## [0.4.1](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.4.0...v0.4.1) (2026-09-04)
11
34
 
12
35
  ### Added
@@ -17,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
17
40
  ### Changed
18
41
 
19
42
  * document declaring/updating the DSH host requirement (engines.dsh) ([fb568b7](https://github.com/waknow/dsh-web-icon-indicator/commit/fb568b71362b4931712240087213845cfb3b76ee))
43
+
20
44
  ## [0.4.0](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.3.1...v0.4.0) (2026-09-04)
21
45
 
22
46
  ### Added
@@ -27,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
27
51
 
28
52
  * auto-create GitHub Release after successful npm publish ([02d98e3](https://github.com/waknow/dsh-web-icon-indicator/commit/02d98e3db9f83013a3e7667c6a0848efecb8ef37))
29
53
  * note DSH 0.1.2 support and add a version-support banner ([cd42d2c](https://github.com/waknow/dsh-web-icon-indicator/commit/cd42d2c5b5650800e3620c5806b1b4ac2b25c84f))
54
+
30
55
  ## [0.3.1](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.3.0...v0.3.1) (2026-09-02)
31
56
 
32
57
  ### Fixed
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-web-icon-indicator
2
2
 
3
- > 📖 [中文文档](README.zh.md) · [English](README.md) · 📝 [Changelog](CHANGELOG.md) · [Releases](https://github.com/waknow/dsh-web-icon-indicator/releases)
3
+ > 📖 [中文文档](README.zh.md) · [English](README.md) · 📝 [Changelog](CHANGELOG.md) · [Releases](https://github.com/waknow/dsh-web-icon-indicator/releases) · 🎨 [Live demo](https://waknow.github.io/dsh-web-icon-indicator/)
4
4
 
5
5
  [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
6
6
  [![npm version](https://img.shields.io/npm/v/dsh-web-icon-indicator)](https://www.npmjs.com/package/dsh-web-icon-indicator)
@@ -11,6 +11,8 @@
11
11
 
12
12
  Browser tab favicon reflects the current DSH session state — `idle` / `running` / `asking` / `done` — so you can see at a glance whether a session needs your attention, even when the tab is in the background.
13
13
 
14
+ > 🎨 **Live demo** — <https://waknow.github.io/dsh-web-icon-indicator/> · see the four states, the multi-agent counter and every effect rendered live in your browser, no install needed. The playground even drives the demo page's own tab favicon, exactly like the plugin does on a DSH page.
15
+
14
16
  ## ✨ What it does
15
17
 
16
18
  - **Live session state on the tab favicon** — the browser-tab icon mirrors `idle` / `running` / `asking` / `done` (aggregate priority: `asking` > `running` > `done` > `idle`), so background tabs tell you at a glance what your agents are doing — including `ask_user_question` prompts and approval / sandbox-escalation waits, which pin the icon to `asking`.
@@ -18,7 +20,7 @@ Browser tab favicon reflects the current DSH session state — `idle` / `running
18
20
  - **Six built-in effects** — `static`, `blink`, `breath`, `rainbow`, `heartbeat`, `bounce` — all driven by JavaScript, since favicons don't play SVG CSS animations.
19
21
  - **Fully configurable, applied live** — every state's color, effect and cycle speed, plus the asking/done hold timings, apply to the running tab within ~1 s — no reload, no restart.
20
22
  - **Built-in settings UI, zero YAML** — a *Favicon indicator* card in the DSH settings page edits the whole config with live color-swatch previews and persists it to `settings.yaml` for you (path below).
21
- - **Background-tab & restart-proof** — animated states keep a wall-clock fallback while `requestAnimationFrame` is paused in hidden tabs, and the status poll self-heals across host restarts.
23
+ - **Background-tab & restart-proof** — animated states keep a wall-clock fallback while `requestAnimationFrame` is paused in hidden tabs, and the status poll self-heals across host restarts. Returning to a tab repaints immediately: a `visibilitychange` listener fires an instant status fetch, so a state that flipped while the tab was hidden (e.g. the `done` hold expiring) shows at once instead of waiting for the next — possibly throttled — poll tick. When the backend is stopped, the tab never loses its icon: the outage restores the shell's own favicon from an offline-safe `data:`-URI copy (or keeps the last painted frame), and the live icon returns on the first successful poll.
22
24
  - **Active-agent count at a glance** — while **more than one** agent is active (non-idle: `asking` / `running` / `done`), the favicon switches from the whale to a **full-frame number block** showing the live count (up to `99+`), colored and animated exactly like the whale would be in that state; back to the whale when 0–1 agents are active. (Same visual language as the *满幅数字* channel in [`demo/badge.html`](./demo/badge.html).)
23
25
 
24
26
  ### 🛠 Configuration UI — how to get there
@@ -28,7 +30,7 @@ Browser tab favicon reflects the current DSH session state — `idle` / `running
28
30
  | 1 | Open the DSH Web GUI and go to **Settings / 设置**. |
29
31
  | 2 | In the **Plugins / 插件** tab, open **Plugin config / 插件配置**. |
30
32
  | 3 | Find the **Favicon indicator / 标签页图标指示器** card. |
31
- | 4 | Expand a state row (`idle` / `running` / `asking` / `done`) to edit **Effect / 特效**, **Colors / 颜色** (each swatch is a native color picker) and **Cycle (ms) / 周期(毫秒)** (shown only for animated states — static states have no cycle); use **Asking hold / 提问驻留** and **Done hold / 完成驻留** for the two timings. |
33
+ | 4 | Set **Default icon color / 默认图标颜色** for the idle whale (its only knob — idle paints one color and never animates), then expand a state row (`running` / `asking` / `done`) to edit **Effect / 特效**, **Colors / 颜色** (each swatch is a native color picker) and **Cycle (ms) / 周期(毫秒)** (shown only for animated states — static states have no cycle); use **Asking hold / 提问驻留** and **Done hold / 完成驻留** for the two timings. |
32
34
 
33
35
  Changes are saved through the settings transport into the profile's `settings.yaml` and applied to the running tab within ~1 s — no reload, no restart. See [Configure](#configure) for the full key reference.
34
36
 
@@ -42,7 +44,7 @@ The four default states, exactly as they appear in the browser tab (the `asking`
42
44
 
43
45
  | State | Default color | Default effect |
44
46
  | --- | --- | --- |
45
- | `idle` | `#1a1a1a` — deep whale | `static` |
47
+ | `idle` | `#1a1a1a` — deep whale (replaceable with `defaultColor`) | `static` |
46
48
  | `running` | `#FACC15` — yellow | `static` |
47
49
  | `asking` | `#E5484D` ⇄ `#FACC15` — red/yellow | `blink` (400 ms) |
48
50
  | `done` | `#22A06B` — green | `static`, stays `doneHoldMs`, then back to `idle` |
@@ -115,15 +117,19 @@ Or drop the directory into `~/.dsh/profiles/web/node_modules/<name>/` and ship a
115
117
 
116
118
  ## Configure
117
119
 
118
- All keys are optional; defaults shown.
120
+ All keys are optional; defaults shown. `statusPath` and `iconPathPrefix` are
121
+ **registration-time** keys: set them in the composition entry only — they are
122
+ baked into the route table and the injected script when the plugin mounts, so
123
+ they are intentionally **not** part of the settings surface (`settings.yaml`).
119
124
 
120
125
  | Key | Default | Meaning |
121
126
  | --- | --- | --- |
122
127
  | `iconsDir` | `<package>/icons/` | Directory holding the single `base.svg` |
123
- | `statusPath` | `/dsh-web-icon-status.json` | JSON status endpoint |
124
- | `iconPathPrefix` | `/dsh-web-icon-indicator` | URL prefix `base.svg` is served under |
128
+ | `statusPath` | `/dsh-web-icon-status.json` | JSON status endpoint — **registration-time (composition entry only)** |
129
+ | `iconPathPrefix` | `/dsh-web-icon-indicator` | URL prefix `base.svg` is served under — **registration-time (composition entry only)** |
125
130
  | `askingHoldMs` | `3500` | Minimum visibility of the asking state |
126
131
  | `doneHoldMs` | `5000` | Time the done state stays before falling back to idle |
132
+ | `defaultColor` | *(unset)* | Default icon color (the idle whale's primary) — tell multiple DSH instances apart. Warns when it is too close to another state's color |
127
133
  | `states` | see below | Per-state visual config |
128
134
 
129
135
  Each entry in `states` is one object per state: `{ effect, colors[], speed? }`:
@@ -138,9 +144,15 @@ config:
138
144
  ```
139
145
 
140
146
  - **`effect`** — one of `static | blink | breath | rainbow | heartbeat | bounce`.
141
- - **`colors`** — an **array** of hex colors. `colors[0]` is the primary. Multi-color effects read more entries: `blink` uses `colors[0]`⇄`colors[1]`, `breath` breathes `colors[0]`⇄`colors[1]` (each derives a darker second color if omitted), `rainbow` uses only `colors[0]` as the starting hue.
147
+ - **`colors`** — an **array** of hex colors (`#rgb` / `#rrggbb`; a malformed entry is ignored, and the state's built-in colour applies when none survives). `colors[0]` is the primary. Multi-color effects read more entries: `blink` uses `colors[0]`⇄`colors[1]`, `breath` breathes `colors[0]`⇄`colors[1]` (each derives a darker second color if omitted), `rainbow` uses only `colors[0]` as the starting hue.
142
148
  - **`speed`** — optional per-state cycle length in ms (also the `blink` toggle interval). Default `1200`.
143
149
 
150
+ `idle` is special: its color is the `defaultColor` key, and the settings card
151
+ offers **no per-state entry** for it (one color, no animation, no cycle). A
152
+ `states.idle` entry is still honored when it arrives from the composition entry
153
+ or a hand-written `settings.yaml` — backward compatibility only; it is simply
154
+ not editable from the card.
155
+
144
156
  Entries are shallow-merged over the defaults, so you can override only a few states. Example:
145
157
 
146
158
  ```yaml
@@ -153,6 +165,44 @@ Entries are shallow-merged over the defaults, so you can override only a few sta
153
165
  done: { effect: heartbeat, colors: ['#2ECC71'] }
154
166
  ```
155
167
 
168
+ ### Tell multiple instances apart (`defaultColor`)
169
+
170
+ Running several DSH instances at once (different projects, profiles or ports)?
171
+ Give each one its own default icon color and the browser tabs become
172
+ immediately distinguishable — no need to touch the per-state palette:
173
+
174
+ ```yaml
175
+ - id: dsh-web-icon-indicator
176
+ name: 'dsh-web-icon-indicator'
177
+ config:
178
+ defaultColor: '#5B8DEF'
179
+ ```
180
+
181
+ - `defaultColor` is the **idle whale's primary color**. It is folded into
182
+ `states.idle.colors[0]`, so the idle state keeps its effect and any second
183
+ color you configured; the other states keep their signal colors. Unset (the
184
+ default) means "the idle state's own color", i.e. today's behavior.
185
+ - It is a **per-DSH-instance** setting, not per browser tab: every tab of one
186
+ instance shares it, while another instance (its own profile /
187
+ `settings.yaml`, e.g. `dsh web --port 3081`) can use a different color.
188
+ - **Reset** removes your overrides back to the composition entry. When the
189
+ color comes from that entry (the `base` layer, which an `unset` cannot
190
+ reach), the card writes the idle state's own color instead — so "Reset to
191
+ defaults" really returns the icon to the plain whale color, and the entry's
192
+ value stays reachable through the clear-override control.
193
+ - **Similarity warning.** When the default color is perceptually too close to
194
+ another state's color you get a warning — live in the settings card, in the
195
+ host log, and as `warnings` on the status endpoint — but the value is still
196
+ applied (the warning never blocks saving). Distance is **CIE76 ΔE in
197
+ CIELAB**: `ΔE < 25` warns, `ΔE < 12` is reported as nearly identical
198
+ (`ΔE 2.3` is the just-noticeable difference). Every fill a state actually
199
+ paints is considered: the `asking` blink covers both of its colors,
200
+ `breath` its interpolation, and a `rainbow` state is flagged for any
201
+ chromatic default because it sweeps every hue. The shipped `running` and
202
+ `asking` colors deliberately share `#FACC15`, so states are never compared
203
+ with each other — only the default color against them. A malformed value is
204
+ ignored (and reported) rather than painted.
205
+
156
206
  ### Settings page & `settings.yaml` (DSH ≥ 0.1.2)
157
207
 
158
208
  The plugin registers the whole config surface above with the DSH settings
@@ -160,11 +210,26 @@ service under the `web-icon-indicator` namespace (a schemastery schema in
160
210
  `lib/index.js`):
161
211
 
162
212
  - **Web GUI:** open **设置 → 插件 → 插件配置** — a *Favicon indicator* card
163
- edits the same keys (asking/done hold, and per-state effect / colors /
164
- cycle), staged and saved through the settings transport. Each state is a
165
- collapsible row showing a color dot and a one-line summary (`blink ·
166
- #E5484D #FACC15 · 400ms`); expanding a row reveals its three fields, and
167
- the colors field previews parsed swatches live.
213
+ edits the same keys: asking/done hold, the **default icon color** (with a
214
+ live palette preview and the similarity warning), and the per-state effect /
215
+ colors / cycle for `running` / `asking` / `done`. Each of those states is a
216
+ collapsible row whose header shows one **color chip** per state split in two
217
+ for a multi-color state, so `asking` shows red | yellow at a glance — plus a
218
+ one-line summary of the effect and cycle (`Blink · 400ms`). Clicking a chip
219
+ opens the native color picker, which is where the hex value is shown/typed;
220
+ the card prints no colour codes itself, except in the similarity warning,
221
+ which names the offending hex to be actionable (e.g. `… Running color #FACC15
222
+ (ΔE 0)`). The expanded row lists one chip per
223
+ color the chosen effect actually uses: `blink` / `breath` get a `+` chip while
224
+ there is still room for a second color (it opens on the darker shade the
225
+ browser would derive anyway, and never appends a color the state already has),
226
+ every color after the first can be removed, and a single-color effect shows —
227
+ and saves — only one chip, so an unused second color can never linger
228
+ invisibly. `rainbow` carries no color list: its field and its chip show the
229
+ hue wheel, next to an optional chip for the **starting hue** (the stored
230
+ `colors[0]`). `idle` deliberately gets **no row** — it paints one
231
+ color and never animates, so the default-color field is its whole
232
+ configuration. Everything is staged and saved through the settings transport.
168
233
  - **Persistence:** values land in the profile's `settings.yaml` (default
169
234
  `~/.dsh/settings.yaml`) as a `web-icon-indicator:` section. The composition
170
235
  entry stays the `base` layer; resolution order is schema defaults →
@@ -174,6 +239,15 @@ service under the `web-icon-indicator` namespace (a schemastery schema in
174
239
  colors / cycle) is synced into the running tab through the status poll within
175
240
  ~1 s. Only code-level default changes in `lib/index.js` need a tab reload (or
176
241
  a DSH web rebuild).
242
+ - **Route paths are not settings.** `statusPath` / `iconPathPrefix` are
243
+ registration-time keys baked into the route table and the injected script, so
244
+ they live in the composition entry only (see the table above) and a restart is
245
+ required to change them. They are deliberately absent from the settings schema
246
+ and from `settings.yaml`: honoring them there would point the browser at a path
247
+ the server never serves.
248
+ - The settings surface therefore covers `askingHoldMs`, `doneHoldMs`,
249
+ `iconsDir` and `states`. `iconsDir` has no schema default, so it is omitted
250
+ from the settings document unless a user sets it.
177
251
  - The browser half is a hand-written `lib/client.js` (ModuleLoader factory
178
252
  format — no build step, no runtime deps beyond the shell's `react`). The DSH
179
253
  client scanner picks a new `dsh.client` declaration up on the next profile
@@ -187,7 +261,7 @@ service under the `web-icon-indicator` namespace (a schemastery schema in
187
261
  - Status is aggregated across live `agents.list()` with priority `asking > running > done > idle`. The aggregation runs a `reconcile()` step on every request to detect running → idle transitions, because `agent/status`'s idle delivery is not guaranteed at turn end. The status endpoint also reports `active` — the number of non-idle agents — and while that count is **> 1** the injected script renders a full-frame count block (the *满幅数字* channel of [`demo/badge.html`](./demo/badge.html): a rounded block filled with the same per-frame state color/effect as the whale, bold white count sized 31%–52% of the icon, capped at `99+`) instead of the whale, so the tab shows how many agents are busy at once even in a pinned 16px tab.
188
262
  - `ask_user_question` tool calls (via `tools/pre-execute` / `tools/result`) flip the session into `asking` with a configurable minimum-hold so the icon stays visible even when the user answers immediately.
189
263
  - Permission / **sandbox-interception** waits are also surfaced as `asking`: when the agent hits a sandbox denial and escalates (`sandbox_permissions` + `justification`), or any other tool asks for approval, the approval service appends an `approval/asked` session event and blocks the agent until you decide. The plugin watches `session/event` (with an authoritative fold over the live session log as a fallback) and pins the session into the `asking` state for that whole wait, clearing it on `approval/decided`.
190
- - The browser script polls `/dsh-web-icon-status.json` once a second, fetches `base.svg` once, and then on every `requestAnimationFrame` tick rebuilds the favicon as a `data:image/svg+xml,…` URI — replacing the `__COLOR__` placeholder with the state's configured color and applying the state's configured effect. The status response also echoes the current per-state visual config, so a settings save reaches the running tab on the next poll (~1 s) without a reload. Browsers don't play favicon SVG CSS animations, so all motion is JS-driven. Because browsers pause `requestAnimationFrame` in hidden tabs, the poll also repaints a wall-clock frame for animated states, so background tabs keep animating (coarsely) instead of freezing; full-speed animation resumes when the tab is visible again. The poll also survives host restarts: a transient fetch failure restores the original icon and retries on the next tick (the SPA reconnects in place, so the icon comes back without a manual refresh).
264
+ - The browser script polls `/dsh-web-icon-status.json` once a second (the interval is fixed at 1000 ms in the injected script — it is not a config key), fetches `base.svg` once, and then on every `requestAnimationFrame` tick rebuilds the favicon as a `data:image/svg+xml,…` URI — replacing the `__COLOR__` placeholder with the state's configured color and applying the state's configured effect. The status response also echoes the current per-state visual config, so a settings save reaches the running tab on the next poll (~1 s) without a reload. Browsers don't play favicon SVG CSS animations, so all motion is JS-driven. Because browsers pause `requestAnimationFrame` in hidden tabs, the poll also repaints a wall-clock frame for animated states, so background tabs keep animating (coarsely) instead of freezing; full-speed animation resumes when the tab is visible again. Returning to a tab also triggers an immediate status fetch and repaint (`visibilitychange`), so a state that flipped while the tab was hidden shows at once instead of on the next — possibly throttled — poll tick. The poll also survives host restarts — and a stopped backend never blanks the tab: at startup the script caches an offline-safe `data:`-URI copy of the original favicon, and on a fetch failure it restores that copy (or, if none could be captured, keeps the last painted frame) — it never writes the original server URL back, which would be unreachable exactly while the host is down. It retries every tick, and the live icon returns on the first successful poll (the SPA reconnects in place, so no manual refresh is needed).
191
265
 
192
266
  ## Browser support & known limitations
193
267
 
package/README.zh.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-web-icon-indicator
2
2
 
3
- > 📖 [English](README.md) · [中文文档](README.zh.md) · 📝 [更新记录](CHANGELOG.md) · [Releases](https://github.com/waknow/dsh-web-icon-indicator/releases)
3
+ > 📖 [English](README.md) · [中文文档](README.zh.md) · 📝 [更新记录](CHANGELOG.md) · [Releases](https://github.com/waknow/dsh-web-icon-indicator/releases) · 🎨 [在线演示](https://waknow.github.io/dsh-web-icon-indicator/)
4
4
 
5
5
  [![awesome · DSH plugin](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
6
6
  [![npm version](https://img.shields.io/npm/v/dsh-web-icon-indicator)](https://www.npmjs.com/package/dsh-web-icon-indicator)
@@ -11,6 +11,8 @@
11
11
 
12
12
  浏览器标签页 favicon 实时反映 DSH 会话状态——`待机` / `运行中` / `提问` / `完成`——让你在标签页置于后台时也能一眼看出是否有会话需要处理。
13
13
 
14
+ > 🎨 **在线演示** — <https://waknow.github.io/dsh-web-icon-indicator/> · 在浏览器里直接体验四种状态、多 Agent 计数与全部特效,无需安装。试玩间还能驱动演示页自身标签页的真实 favicon——正如插件在 DSH 页面里做的那样。
15
+
14
16
  ## ✨ 功能特性
15
17
 
16
18
  - **标签页 favicon 实时反映会话状态** —— 浏览器标签页图标同步 `idle` / `running` / `asking` / `done`(聚合优先级:`asking` > `running` > `done` > `idle`),后台标签页也能一眼看清 agent 们在做什么——包括 `ask_user_question` 提问,以及审批 / 沙箱提权等待(这两种情况会把图标钉在 `asking` 态)。
@@ -18,7 +20,7 @@
18
20
  - **六种内置特效** —— `static`(静止)、`blink`(闪烁)、`breath`(呼吸)、`rainbow`(彩虹)、`heartbeat`(心跳)、`bounce`(跳动),全部由 JavaScript 驱动(favicon 不会播放 SVG CSS 动画)。
19
21
  - **完全可配置、即时生效** —— 每个状态的颜色、特效、周期,以及提问 / 完成驻留时长,改动约 1 秒内同步到已打开的标签页——无需刷新、无需重启。
20
22
  - **内置配置 UI,无需手写 YAML** —— DSH 设置页里的 *标签页图标指示器* 卡片可编辑整套配置,带实时色块预览,保存后自动写入 `settings.yaml`(路径见下)。
21
- - **后台标签页与重启抗性** —— 隐藏标签页中 `requestAnimationFrame` 被暂停时,动画态会按墙钟时间补帧;状态轮询还能扛住 host 重启,图标自动恢复。
23
+ - **后台标签页与重启抗性** —— 隐藏标签页中 `requestAnimationFrame` 被暂停时,动画态会按墙钟时间补帧;状态轮询还能扛住 host 重启、后端停止:故障期间标签页图标绝不丢失(还原启动时缓存的原始图标 `data:`-URI 副本,或保留最后一帧插件图标),端点恢复后自动回到实时状态。切回前台时会立刻触发一次状态拉取并重绘——后台标签页的定时器会被浏览器节流,轮询可能滞后,所以回到标签页的瞬间就刷新最新状态(比如 `done` 保持期在隐藏期间过期、图标应退回 `idle` 的情况)。
22
24
  - **多 agent 一目了然** —— 当同时有**超过一个**活动 agent(非待机:`asking` / `running` / `done`)时,favicon 从鲸鱼切换为**占满整帧的数字块**,实时显示活动数(上限 `99+`),颜色与动画和该状态下鲸鱼完全一致;活动数回到 0–1 时恢复鲸鱼。(视觉与 [`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道一致。)
23
25
 
24
26
  ### 🛠 配置界面——怎么找到它
@@ -28,7 +30,7 @@
28
30
  | 1 | 打开 DSH Web GUI,进入 **设置**。 |
29
31
  | 2 | 在 **插件** 选项卡中,打开 **插件配置**。 |
30
32
  | 3 | 找到 **标签页图标指示器(Favicon indicator)** 卡片。 |
31
- | 4 | 展开某个状态行(`idle` / `running` / `asking` / `done`),编辑 **特效**、**颜色**(每个色块即原生取色器),以及**周期(毫秒)**——仅动画状态显示,静态状态无周期;用 **提问驻留** / **完成驻留** 调整两个时长。 |
33
+ | 4 | 先用 **默认图标颜色** 设置待机鲸鱼的颜色(这是待机唯一的设置项——待机只画一种颜色、不做动画),再展开状态行(`running` / `asking` / `done`)编辑 **特效**、**颜色**(每个色块即原生取色器),以及**周期(毫秒)**——仅动画状态显示,静态状态无周期;用 **提问驻留** / **完成驻留** 调整两个时长。 |
32
34
 
33
35
  改动会通过 settings 传输层持久化到 profile 的 `settings.yaml`,约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。
34
36
 
@@ -42,7 +44,7 @@
42
44
 
43
45
  | 状态 | 颜色(默认) | 特效(默认) |
44
46
  | --- | --- | --- |
45
- | `idle` 待机 | `#1a1a1a`——深色鲸鱼 | `static` |
47
+ | `idle` 待机 | `#1a1a1a`——深色鲸鱼(可用 `defaultColor` 替换) | `static` |
46
48
  | `running` 运行中 | `#FACC15`——黄色 | `static` |
47
49
  | `asking` 提问 | `#E5484D` ⇄ `#FACC15`——红/黄 | `blink`(400ms) |
48
50
  | `done` 完成 | `#22A06B`——绿色 | `static`,保持 `doneHoldMs` 后回到 `idle` |
@@ -110,15 +112,18 @@ dsh plugin --profile web add <路径或tarball>
110
112
 
111
113
  ## 配置
112
114
 
113
- 所有键均可选,默认值如下:
115
+ 所有键均可选,默认值如下。`statusPath` 与 `iconPathPrefix` 是**注册期**键:
116
+ 只能在合成条目(composition entry)里设置——它们在插件挂载时就被烘进路由表与注入
117
+ 脚本,因此刻意**不**进入设置面(`settings.yaml`)。
114
118
 
115
119
  | 键 | 默认值 | 含义 |
116
120
  | --- | --- | --- |
117
121
  | `iconsDir` | `<package>/icons/` | 单个 `base.svg` 所在目录 |
118
- | `statusPath` | `/dsh-web-icon-status.json` | JSON 状态端点 |
119
- | `iconPathPrefix` | `/dsh-web-icon-indicator` | `base.svg` 的 URL 前缀 |
122
+ | `statusPath` | `/dsh-web-icon-status.json` | JSON 状态端点 —— **注册期(仅合成条目)** |
123
+ | `iconPathPrefix` | `/dsh-web-icon-indicator` | `base.svg` 的 URL 前缀 —— **注册期(仅合成条目)** |
120
124
  | `askingHoldMs` | `3500` | 提问状态的最小保持时长 |
121
125
  | `doneHoldMs` | `5000` | 完成状态保持时长,随后回到 idle |
126
+ | `defaultColor` | *(未设置)* | 默认图标颜色(待机鲸鱼的主色)—— 用于区分多个 DSH 实例;与其它状态颜色过于接近时会告警 |
122
127
  | `states` | 见下 | 每个状态的视觉配置 |
123
128
 
124
129
  `states` 中每个状态是一个对象:`{ effect, colors[], speed? }`:
@@ -133,9 +138,13 @@ config:
133
138
  ```
134
139
 
135
140
  - **`effect`** — 取 `static | blink | breath | rainbow | heartbeat | bounce` 之一。
136
- - **`colors`** — **数组**,多个 hex 颜色。`colors[0]` 为主色。多色特效读取更多项:`blink` 用 `colors[0]`⇄`colors[1]`,`breath` 在 `colors[0]`⇄`colors[1]` 间过渡(缺省时自动推导更深的第二色),`rainbow` 仅用 `colors[0]` 作起始色相。
141
+ - **`colors`** — **数组**,多个 hex 颜色(`#rgb` / `#rrggbb`;非法项会被逐项忽略,全部无效时才回退到该状态的内置颜色)。`colors[0]` 为主色。多色特效读取更多项:`blink` 用 `colors[0]`⇄`colors[1]`,`breath` 在 `colors[0]`⇄`colors[1]` 间过渡(缺省时自动推导更深的第二色),`rainbow` 仅用 `colors[0]` 作起始色相。
137
142
  - **`speed`** — 可选,该状态的周期(ms),也是 `blink` 的切换间隔。默认 `1200`。
138
143
 
144
+ `idle` 比较特殊:它的颜色就是 `defaultColor` 键,设置卡片**不为它提供状态条目**
145
+ (一种颜色、不做动画、也没有周期)。若 `states.idle` 来自合成条目或手写的
146
+ `settings.yaml`,仍然会被沿用——这属于向后兼容,只是无法在卡片里编辑。
147
+
139
148
  每个状态条目会在默认值之上做浅合并,因此只需覆盖少量状态。示例:
140
149
 
141
150
  ```yaml
@@ -148,16 +157,55 @@ config:
148
157
  done: { effect: heartbeat, colors: ['#2ECC71'] }
149
158
  ```
150
159
 
160
+ ### 区分多个实例(`defaultColor`)
161
+
162
+ 同时开多个 DSH 实例(不同项目 / profile / 端口)时,给每个实例设一个自己的默认图标
163
+ 颜色,浏览器标签页就能一眼区分,不必去改整套状态配色:
164
+
165
+ ```yaml
166
+ - id: dsh-web-icon-indicator
167
+ name: 'dsh-web-icon-indicator'
168
+ config:
169
+ defaultColor: '#5B8DEF'
170
+ ```
171
+
172
+ - `defaultColor` 就是**待机鲸鱼的主色**。它会被折叠进 `states.idle.colors[0]`,
173
+ 因此 idle 仍保留自己配置的特效与第二色,其它状态的颜色语义(黄=运行、红/黄=提问、
174
+ 绿=完成)不受影响;不设置(默认)即等于「沿用 idle 自己的颜色」,行为与之前完全一致。
175
+ - 这是**按 DSH 实例**生效的设置,不是按标签页:同一实例的所有标签页共用它;另一个实例
176
+ (自己的 profile / `settings.yaml`,例如 `dsh web --port 3081`)可以用另一种颜色。
177
+ - **恢复默认**会把你的覆盖清回合成条目。当颜色本来就来自合成条目(`base` 层,
178
+ 用户层的 `unset` 触及不到)时,卡片改为写入 idle 自己的颜色——这样「恢复默认」真的
179
+ 能让图标回到朴素的鲸鱼色,而合成条目里配置的值仍可通过「清除覆盖」一键取回。
180
+ - **相似度告警**:当默认颜色与其它状态颜色在感知上过于接近时,你会收到告警 —— 设置卡片里
181
+ 实时显示(保存前即可见)、host 日志里记录、状态端点 `warnings` 中返回 —— 但该颜色
182
+ 仍然会被应用(告警绝不阻断保存)。度量方式是 **CIELAB 中的 CIE76 ΔE**:`ΔE < 25`
183
+ 告警,`ΔE < 12` 视为几乎相同(ΔE 2.3 是人眼恰可分辨的阈值)。比较覆盖每个状态实际
184
+ 会画出的所有填充:`asking` 闪烁的两种颜色、`breath` 的插值中间色;而 `rainbow`
185
+ 会扫过所有色相,因此任何有色默认色都会被标记。出厂默认中 `running` 与 `asking` 刻意
186
+ 共用 `#FACC15`,所以**状态之间互不比较**,只把默认色与它们逐一比较。格式非法的值会被
187
+ 忽略并上报,而不会被画到图标上。
188
+
151
189
  ### 设置页与 `settings.yaml`(DSH ≥ 0.1.2)
152
190
 
153
191
  插件把上面整套配置注册进 DSH settings 服务,命名空间为 `web-icon-indicator`
154
192
  (schema 为 `lib/index.js` 中的 schemastery schema):
155
193
 
156
194
  - **Web GUI:** 打开 **设置 → 插件 → 插件配置**,会出现 *标签页图标指示器*
157
- 卡片,可编辑同样的键(提问/完成驻留,以及每个状态的特效 / 颜色 / 周期),
158
- 通过 settings 传输层暂存并保存。每个状态是一行可折叠条目,带主色圆点和一行
159
- 摘要(如 `blink · #E5484D ⇄ #FACC15 · 400ms`);展开该行才显示它的三个字段,
160
- 颜色输入框旁会实时预览解析出的色块。
195
+ 卡片,可编辑:提问/完成驻留、**默认图标颜色**(带配色一览与相似度告警),以及
196
+ `running` / `asking` / `done` 三个状态各自的特效 / 颜色 / 周期。这三个状态
197
+ 各占一行可折叠条目,行首是一个**色块**——多色状态(asking)会左右分格同时显示
198
+ 红黄两色,旁边是一行「特效 · 周期」摘要(如 `Blink · 400ms`)。点击色块即打开
199
+ 系统取色弹窗(十六进制值在那里显示/输入),卡片本身不打印颜色代码——唯一例外是相似度
200
+ 告警,它会给出具体的 hex 以便定位(例如「与「运行中」的颜色 #FACC15 几乎相同(ΔE 0)」)。
201
+ 展开该行后只列出当前
202
+ 特效**实际会用到**的颜色:`blink` / `breath` 在还有空位时才多出一个 `+` 色块
203
+ (它默认停在浏览器本来就会推导的那个更深的第二色上,且不会重复添加已有颜色),第一个
204
+ 之后的色块都可以删除;单色特效则只显示、也只保存一个色块,不会留下看不见的第二色。
205
+ `rainbow` 不携带颜色列表:字段与色块都显示彩虹色环,色环旁另有一个**可选的起始色相**
206
+ 色块(也就是存下的 `colors[0]`)。`idle` 刻意**不占
207
+ 一行**——它只画一种颜色、不做动画,默认图标颜色就是它的全部配置。所有修改都通过
208
+ settings 传输层暂存并保存。
161
209
  - **持久化:** 值写入 profile 的 `settings.yaml`(默认 `~/.dsh/settings.yaml`)
162
210
  的 `web-icon-indicator:` 段。合成条目仍是 `base` 层;解析顺序为 schema 默认值
163
211
  → 合成条目 → 设置文档用户层。
@@ -165,6 +213,12 @@ config:
165
213
  `doneHoldMs` 在主机侧即时生效;各状态的视觉配置(特效 / 颜色 / 周期)会随状态
166
214
  轮询同步进正在运行的标签页,约 1 秒内生效。只有改 `lib/index.js` 里的代码级
167
215
  默认值才需要重载标签页(或重新构建 DSH Web)。
216
+ - **路由路径不是设置项。** `statusPath` / `iconPathPrefix` 是注册期键,已被烘进
217
+ 路由表与注入脚本,因此只存在于合成条目(见上方表格),改动需要重启。它们刻意
218
+ 不在设置 schema 与 `settings.yaml` 中:若在那里生效,浏览器会去请求服务器根本
219
+ 没有提供的路径。
220
+ - 因此设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir` 与 `states`。
221
+ `iconsDir` 没有 schema 默认值,用户未设置时不会出现在设置文档中。
168
222
  - 浏览器半区是手写的 `lib/client.js`(ModuleLoader factory 格式——无构建步骤、
169
223
  无额外运行期依赖,仅用 shell 自带的 `react`)。DSH 客户端扫描器会在下次启动
170
224
  profile 时识别新的 `dsh.client` 声明。
@@ -176,7 +230,7 @@ config:
176
230
  - 状态按 `agents.list()` 聚合,优先级 `asking > running > done > idle`。每次请求都会执行一次 `reconcile()` 检测 running → idle 的转换,因为 `agent/status` 的 idle 事件在回合结束时并不保证送达。状态端点还会上报 `active`——非待机 agent 数——当该数 **> 1** 时,注入脚本改为渲染占满整帧的数字块([`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道:圆角色块,填充色与鲸鱼同源的逐帧状态色/特效,白色粗体数字约占图标高度 31%–52%,上限 `99+`),而不是鲸鱼,这样即使在 16px 的固定标签页里也能一眼看出同时有几个 agent 在忙。
177
231
  - `ask_user_question` 工具调用(通过 `tools/pre-execute` / `tools/result`)把会话置为 `asking`,带可配置的最小保持时长,即使你立刻回答,图标也会保持可见。
178
232
  - 权限 / **沙箱拦截**等待同样会显示为 `asking`:当 agent 命中沙箱拒绝并请求提权(`sandbox_permissions` + `justification`),或其他工具需要征得同意时,审批服务会先写入一条 `approval/asked` 会话事件并阻塞 agent,直到你做出决定。插件监听 `session/event`(并以实时会话日志的权威折叠作为兜底)在整个等待期间将会话置为 `asking` 状态,收到 `approval/decided` 后清除。
179
- - 浏览器脚本每秒轮询 `/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 原地重连,无需手动刷新图标即可恢复)。
233
+ - 浏览器脚本每秒轮询 `/dsh-web-icon-status.json`(轮询间隔在注入脚本里固定为 1000 ms,不是配置项),首次获取 `base.svg`,然后每个 `requestAnimationFrame` 周期把 favicon 重建为 `data:image/svg+xml,…` URI——把 `__COLOR__` 占位符替换为状态配置的颜色,并应用该状态配置的特效。状态响应还会携带当前的每状态视觉配置,因此设置保存后约 1 秒内(下一个轮询 tick)即同步到已打开的标签页,无需刷新。浏览器不会播放 SVG favicon 的 CSS 动画,所以一切动画都由 JS 驱动。由于浏览器在**隐藏(后台)标签页会暂停 `requestAnimationFrame`**,轮询还会为动画态补绘一帧按墙钟时间计算的画面——后台标签页保持粗粒度动画(约每 1 秒)而不会冻结,切回前台后恢复满速动画。回到前台时还会通过 `visibilitychange` 立即触发一次状态拉取并重绘——后台定时器会被节流,轮询可能滞后,所以切回标签页的瞬间就能看到最新状态(例如隐藏期间 `done` 保持期已过、图标应退回 `idle`)。轮询还能**扛住 host 重启 / 后端停止**:启动时会把原始 favicon 缓存为离线安全的 `data:`-URI 副本,请求失败时还原该副本(副本未取到则保留最后一帧插件图标)——绝不写回原始的服务端 URL(后端停止时它恰恰不可达,写回正是「图标丢失」的根因);每个 tick 持续重试,端点恢复后第一个成功轮询即换回实时图标(SPA 原地重连,无需手动刷新)。
180
234
 
181
235
  ## 浏览器支持与已知限制
182
236