dsh-web-icon-indicator 0.5.0 → 0.5.2

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,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.2](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.1...v0.5.2) (2026-09-24)
11
+
12
+ ### Added
13
+
14
+ * give the official Plugins page its own artwork ([332896b](https://github.com/waknow/dsh-web-icon-indicator/commit/332896b4b05af94877e63a33ad9c4895b94ad7dd))
15
+
16
+ ### Fixed
17
+
18
+ * repaint the favicon on DSH 0.1.7's theme-scoped link pair ([a57f78f](https://github.com/waknow/dsh-web-icon-indicator/commit/a57f78ff98acd4d9ae41c03c3b0519113e357bb2))
19
+
20
+ ## [0.5.1](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.0...v0.5.1) (2026-09-22)
21
+
22
+ ### Fixed
23
+
24
+ * load on DSH 0.1.7 and keep the legacy settings contract ([6e8dbb5](https://github.com/waknow/dsh-web-icon-indicator/commit/6e8dbb5402b9847eecc33029d68316407f767288))
25
+
10
26
  ## [0.5.0](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.4.2...v0.5.0) (2026-09-17)
11
27
 
12
28
  ### Added
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm downloads](https://img.shields.io/npm/dm/dsh-web-icon-indicator)](https://www.npmjs.com/package/dsh-web-icon-indicator)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
9
9
 
10
- > **⚠️ DSH version support** — requires **DSH ≥ 0.1.2** (the settings-service API the configuration card uses). Built & verified against **DSH 0.1.2-rc.1**, the highest version currently tested. On **DSH < 0.1.2** the favicon still works, but the settings UI (**Settings → Plugins → Plugin config**) is unavailable.
10
+ > **⚠️ DSH version support** — requires **DSH ≥ 0.1.2-rc.1**. One bundle serves both settings generations: **modern** (≥ 0.1.7-alpha.1: exported `Config` schema + `configForms` + `plugins.row.config`) and **legacy** (≤ 0.1.6-alpha.1: `settings.installSection` + `settingsScope` + `settings.plugin.item`). Verified on **DSH 0.1.5-rc.3** and **DSH 0.1.7-alpha.1**; on any older host the favicon still works even if the settings page is not reachable.
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
 
@@ -19,7 +19,7 @@ Browser tab favicon reflects the current DSH session state — `idle` / `running
19
19
  - **One SVG, recolored & animated in the browser** — ships a single whale template ([`icons/base.svg`](./icons/base.svg)); every state, color and frame is rendered client-side as a `data:image/svg+xml` URI. No per-color icon files.
20
20
  - **Six built-in effects** — `static`, `blink`, `breath`, `rainbow`, `heartbeat`, `bounce` — all driven by JavaScript, since favicons don't play SVG CSS animations.
21
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.
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).
22
+ - **Built-in settings UI, zero YAML** — the *Favicon indicator* page edits the whole config with live color-swatch previews and persists it for you (modern hosts: the profile patch; legacy hosts: the profile `settings.yaml` — paths below).
23
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.
24
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).)
25
25
 
@@ -27,12 +27,14 @@ Browser tab favicon reflects the current DSH session state — `idle` / `running
27
27
 
28
28
  | # | Step |
29
29
  | --- | --- |
30
- | 1 | Open the DSH Web GUI and go to **Settings / 设置**. |
31
- | 2 | In the **Plugins / 插件** tab, open **Plugin config / 插件配置**. |
32
- | 3 | Find the **Favicon indicator / 标签页图标指示器** card. |
30
+ | 1 | Open the DSH Web GUI and go to **Settings / 设置** → **Plugins / 插件**. |
31
+ | 2 | On ≥ 0.1.7: open the **dsh-web-icon-indicator** bundle and click **Configure / 配置** on its `dsh-web-icon-indicator` row. On ≤ 0.1.6-alpha.1: open **Plugin config / 插件配置** in that tab. |
32
+ | 3 | The **Favicon indicator / 标签页图标指示器** page opens with the full form. |
33
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. |
34
34
 
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.
35
+ Changes are saved through the settings transport into the profile patch and applied to the running tab within ~1 s — no reload, no restart. See [Configure](#configure) for the full key reference.
36
+
37
+ > 🐳 **The Plugins page shows this plugin's own artwork** — on DSH ≥ 0.1.7 the **Plugins / 插件** page draws a 48 px icon on every bundle card (a 40 px one on every row) from `package.json` → `icon`. This repo declares `"icon": "icons/plugin.svg"`, so the card shows the whale instead of the generic placeholder. The icon is read by the host (`readPluginMeta` → `iconOf`) and shipped to the browser as a base64 `data:` URL; **a missing or malformed icon never blanks the card — it turns the plugin's `meta.error` on and the card shows a problem tag**, so keep the path relative, inside the package, and ≤ 256 KiB. Adding `icon` needs no settings, no code, and no host restart — only a new install or publish; see [Plugin artwork on the Plugins page](#plugin-artwork-on-the-plugins-page).
36
38
 
37
39
  ## 🎬 Default configuration, visualized
38
40
 
@@ -115,12 +117,18 @@ dsh plugin --profile web add <path-or-tarball>
115
117
 
116
118
  Or drop the directory into `~/.dsh/profiles/web/node_modules/<name>/` and ship a `cordis.patch.yml` that matches the one shipped here.
117
119
 
120
+ > ℹ️ The official **Plugins** page reads the artwork from **`package.json.icon` of the
121
+ > installed package**, so it appears only after an install (or a publish) — a page
122
+ > that was already open keeps its last snapshot until you reload it. See
123
+ > [Plugin artwork on the Plugins page](#plugin-artwork-on-the-plugins-page).
124
+
118
125
  ## Configure
119
126
 
120
127
  All keys are optional; defaults shown. `statusPath` and `iconPathPrefix` are
121
128
  **registration-time** keys: set them in the composition entry only — they are
122
129
  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`).
130
+ they are intentionally **not** part of the settings page's live form (they are
131
+ `Config` fields, but not `.volatile()`).
124
132
 
125
133
  | Key | Default | Meaning |
126
134
  | --- | --- | --- |
@@ -132,6 +140,10 @@ they are intentionally **not** part of the settings surface (`settings.yaml`).
132
140
  | `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 |
133
141
  | `states` | see below | Per-state visual config |
134
142
 
143
+ > The Plugins-page artwork is **not** a config key — it comes from
144
+ > `package.json.icon` (see [Plugin artwork on the Plugins page](#plugin-artwork-on-the-plugins-page)).
145
+ > `iconsDir` only tells the *favicon* routes where `base.svg` lives.
146
+
135
147
  Each entry in `states` is one object per state: `{ effect, colors[], speed? }`:
136
148
 
137
149
  ```yaml
@@ -150,8 +162,8 @@ config:
150
162
  `idle` is special: its color is the `defaultColor` key, and the settings card
151
163
  offers **no per-state entry** for it (one color, no animation, no cycle). A
152
164
  `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.
165
+ or the settings document's user layer — backward compatibility only; it is
166
+ simply not editable from the page.
155
167
 
156
168
  Entries are shallow-merged over the defaults, so you can override only a few states. Example:
157
169
 
@@ -183,8 +195,8 @@ immediately distinguishable — no need to touch the per-state palette:
183
195
  color you configured; the other states keep their signal colors. Unset (the
184
196
  default) means "the idle state's own color", i.e. today's behavior.
185
197
  - 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.
198
+ instance shares it, while another instance (its own profile, e.g.
199
+ `dsh web --port 3081`) can use a different color.
188
200
  - **Reset** removes your overrides back to the composition entry. When the
189
201
  color comes from that entry (the `base` layer, which an `unset` cannot
190
202
  reach), the card writes the idle state's own color instead — so "Reset to
@@ -203,14 +215,21 @@ immediately distinguishable — no need to touch the per-state palette:
203
215
  with each other — only the default color against them. A malformed value is
204
216
  ignored (and reported) rather than painted.
205
217
 
206
- ### Settings page & `settings.yaml` (DSH ≥ 0.1.2)
207
-
208
- The plugin registers the whole config surface above with the DSH settings
209
- service under the `web-icon-indicator` namespace (a schemastery schema in
210
- `lib/index.js`):
211
-
212
- - **Web GUI:** open **设置 → 插件 → 插件配置** — a *Favicon indicator* card
213
- edits the same keys: asking/done hold, the **default icon color** (with a
218
+ ### Settings page (DSH ≥ 0.1.2-rc.1, both settings generations)
219
+
220
+ The plugin exports the whole config surface above as its Cordis `Config`
221
+ schema (a schemastery schema in `lib/index.js`) **and** registers the same
222
+ schema through the legacy settings service when the running host still exposes
223
+ it — the two paths are feature-detected at mount, so one bundle serves both.
224
+ The namespace differs by generation: **≥ 0.1.7** keys every live form by
225
+ **profile entry id**, so it is `dsh-web-icon-indicator` (the row id the bundle
226
+ patch declares); **≤ 0.1.6-alpha.1** uses the plugin-chosen
227
+ `web-icon-indicator`, the same string 0.5.x used, so an existing section keeps
228
+ resolving:
229
+
230
+ - **Web GUI:** open **设置 → 插件**, expand the **dsh-web-icon-indicator**
231
+ bundle and configure its row. The *Favicon indicator* page edits the same
232
+ keys: asking/done hold, the **default icon color** (with a
214
233
  live palette preview and the similarity warning), and the per-state effect /
215
234
  colors / cycle for `running` / `asking` / `done`. Each of those states is a
216
235
  collapsible row whose header shows one **color chip** per state — split in two
@@ -230,34 +249,84 @@ service under the `web-icon-indicator` namespace (a schemastery schema in
230
249
  `colors[0]`). `idle` deliberately gets **no row** — it paints one
231
250
  color and never animates, so the default-color field is its whole
232
251
  configuration. Everything is staged and saved through the settings transport.
233
- - **Persistence:** values land in the profile's `settings.yaml` (default
234
- `~/.dsh/settings.yaml`) as a `web-icon-indicator:` section. The composition
235
- entry stays the `base` layer; resolution order is schema defaults →
236
- composition entry → settings document user layer.
237
- - **No server restart, no tab reload** for settings-card saves: `askingHoldMs` /
238
- `doneHoldMs` apply live host-side, and per-state visual config (effect /
239
- colors / cycle) is synced into the running tab through the status poll within
240
- ~1 s. Only code-level default changes in `lib/index.js` need a tab reload (or
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.
252
+ - **Persistence:** on ≥ 0.1.7 values land in the profile patch
253
+ (`~/.dsh/profiles/<profile>/cordis.patch.yml`) under the
254
+ `dsh-web-icon-indicator` row's `config:` block; on ≤ 0.1.6-alpha.1 they land
255
+ in `~/.dsh/settings.yaml` as a `web-icon-indicator:` section. In both cases
256
+ the composition entry stays the `base` layer; resolution order is schema
257
+ defaults → composition entry → user layer.
258
+ - **No server restart, no tab reload** for settings-page saves. On ≥ 0.1.7 a
259
+ live write is committed into the running plugin's config references
260
+ (`loader/volatile-update` — the loader does not restart the plugin); on the
261
+ legacy line the service calls the registered `onChange` hook. Either way
262
+ `askingHoldMs` / `doneHoldMs` apply host-side immediately and per-state visual
263
+ config (effect / colors / cycle) is synced into the running tab through the
264
+ status poll within ~1 s. Only code-level default changes in `lib/index.js`
265
+ need a tab reload (or a DSH web rebuild).
266
+ - **Route paths are not live settings.** `statusPath` / `iconPathPrefix` are
267
+ validated by `Config` but deliberately **not** `.volatile()`, so they are
268
+ absent from the live form: they are baked into the route table and the
269
+ injected script at registration time, and honoring an edit there would point
270
+ the browser at a path the server never serves. They are composition-entry
271
+ only (see the table above); changing them re-applies the plugin.
272
+ - The live surface therefore covers `askingHoldMs`, `doneHoldMs`, `iconsDir`,
273
+ `defaultColor` and `states`. `iconsDir` has no schema default, so it is
274
+ omitted from the resolved form until a user sets it.
251
275
  - The browser half is a hand-written `lib/client.js` (ModuleLoader factory
252
- format — no build step, no runtime deps beyond the shell's `react`). The DSH
253
- client scanner picks a new `dsh.client` declaration up on the next profile
254
- start.
255
- - Deployments without a settings service are unaffected: the plugin falls back
256
- to reading the composition entry exactly as before.
276
+ format — no build step, no runtime deps beyond the shell's `react`). It
277
+ registers on both page slots (`plugins.row.config` — gated on the served
278
+ namespace — and the legacy `settings.plugin.item`) and resolves whichever
279
+ settings provider the host exposes (`configForms` or `settingsScope`) at
280
+ render time, so neither is a hard dependency of the module. The DSH client
281
+ scanner picks a new `dsh.client` declaration up on the next profile start.
282
+ - Deployments without a settings service are unaffected: the plugin keeps
283
+ running on the composition entry + schema defaults it was mounted with.
284
+
285
+ ## Plugin artwork on the Plugins page
286
+
287
+ DSH ≥ 0.1.7 renders a **per-plugin icon on the official Plugins page**: 48 px on
288
+ each bundle card, 40 px on each row (`PackageArtwork` in
289
+ `@deepseek-ai/dsh-client-ui-plugin-manager`, which falls back to a generic
290
+ placeholder when a package declares none). The artwork comes straight from the
291
+ package manifest, so shipping one is pure metadata — **no code, no settings, no
292
+ host restart**:
293
+
294
+ ```jsonc
295
+ // package.json
296
+ {
297
+ "name": "dsh-web-icon-indicator",
298
+ "icon": "icons/plugin.svg" // relative to package.json, and inside the package
299
+ }
300
+ ```
301
+
302
+ The host reads it before evaluating any plugin code
303
+ (`readPluginMeta` → `iconOf` in `@deepseek-ai/dsh-app-boot`):
304
+
305
+ | Rule | Value |
306
+ | --- | --- |
307
+ | Field | top-level `package.json.icon` (a sibling of `name`/`version` — **not** under `dsh`) |
308
+ | Path form | relative to the manifest's directory; absolute paths, Windows drive letters (`C:\…`) and URLs are rejected |
309
+ | Containment | must stay inside the manifest directory after `realpath` resolution (`..` escapes are rejected) |
310
+ | Formats | `.svg` → `image/svg+xml`, `.png` → `image/png`, `.jpg` / `.jpeg` → `image/jpeg`, `.webp` → `image/webp` |
311
+ | Size | ≤ 256 KiB (checked both on `stat` and on the bytes actually read) |
312
+ | Delivery | read as bytes and shipped to the browser as a base64 `data:` URL on the wire (`PluginLocalizedMeta.icon`) |
313
+
314
+ > ⚠️ **A bad icon is worse than no icon.** Any violation is a *metadata error*,
315
+ > not a silent fallback: the reader returns `meta.error`, the host surfaces it,
316
+ > and the card shows a **problem tag** for the plugin. Keep the file in the
317
+ > package (`files` ships `icons/`) and make sure it is standalone — a raw
318
+ > `icons/base.svg` would be **invalid**, because its `#p { fill: __COLOR__ }`
319
+ > placeholder is not a colour. This repo therefore ships the pre-filled
320
+ > [`icons/plugin.svg`](./icons/plugin.svg) (`base.svg` with `__COLOR__` →
321
+ > `#1a1a1a`) and asserts the whole contract in `test/verify.js` (F62–F65).
322
+
323
+ Because the icon is read from the **installed** package, a local edit takes
324
+ effect only after reinstalling the plugin (`dsh plugin --profile web add
325
+ <path>`) or publishing a new version.
257
326
 
258
327
  ## How it works
259
328
 
260
- - Host plugin with a small browser half: registers routes on the existing `webServer` — the status JSON endpoint, a static `/dsh-web-icon-indicator/base.svg` (the whale template), and one `tapIndex` that injects a small browser script into every served `index.html`. The config surface is registered with the DSH settings service (`web-icon-indicator` namespace) for validation, persistence, and the settings-page card (see above).
329
+ - Host plugin with a small browser half: registers routes on the existing `webServer` — the status JSON endpoint, a static `/dsh-web-icon-indicator/base.svg` (the whale template), and one `tapIndex` that injects a small browser script into every served `index.html`. The config surface is the plugin's exported `Config` schema, which the DSH settings service projects into a live page keyed by the `dsh-web-icon-indicator` entry id (see above).
261
330
  - 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.
262
331
  - `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.
263
332
  - 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`.
@@ -277,6 +346,7 @@ The favicon is a plain image, so browsers never run the SVG's own CSS/JS animati
277
346
  Known limitations (current as of Safari 26.3):
278
347
 
279
348
  - **Favicons have their own cache.** Chrome keeps a favicon database, Firefox a `favicons.sqlite`, and **Safari a system-level icon cache** — none of which a normal *clear cache* touches, and WebKit even caches the "no icon" case. That is why a changed icon can linger for an existing tab. The plugin already mitigates this: it serves `base.svg` and the status endpoint with `Cache-Control: no-store`, bundles a freshness query (`?t=Date.now()`) on its fetches, and replaces the `<link rel=icon>` node on each state change.
349
+ - **A theme-scoped favicon pair must be collapsed first (DSH ≥ 0.1.7).** The shell's `index.html` ships *two* `rel=icon` links — `favicon-dark.svg` for `prefers-color-scheme: dark` and `favicon.svg` for light — because up to `0.1.6-alpha.2` there was exactly one. A browser resolves the favicon to the **last connected link whose `media` matches**, so repainting only the first one left the tab showing the shell's icon no matter what the session did. The plugin now normalizes the set to a single always-matching link at startup (keeping the variant your scheme selects) before painting. If you are on a version before 0.5.2 and the tab icon simply never changes, this is why.
280
350
  - **Safari renders SVG favicons but ignores their internal CSS** — no `@media`, no `prefers-color-scheme`, no CSS animation. So all recoloring must be baked into each frame's markup (which the plugin does) rather than driven by CSS variables.
281
351
  - **`data:`-URI SVG favicons are unreliable in Safari** (WebKit bug 236616, still open; reproduced on Safari 17.6). The plugin currently builds each frame as a `data:image/svg+xml` URI, so on Safari the tab icon may not render at all — the biggest known gap.
282
352
  - **Dynamic JS updates in Safari are hit-or-miss;** they may require a reload, and Safari "locks onto" the first icon it sees. There is no guaranteed, spec-supported way to swap a favicon live in Safari today.
package/README.zh.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![npm downloads](https://img.shields.io/npm/dm/dsh-web-icon-indicator)](https://www.npmjs.com/package/dsh-web-icon-indicator)
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
9
9
 
10
- > **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2**(设置卡片所用的 settings 服务 API)。已针对当前最高测试版本 **DSH 0.1.2-rc.1** 构建并验证。在 **DSH < 0.1.2** 上 favicon 仍可用,但设置页(**设置 → 插件 → 插件配置**)不可用。
10
+ > **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2-rc.1**。同一份插件同时兼容两代 settings 契约:**新一代**(≥ 0.1.7-alpha.1:导出 `Config` schema + `configForms` + `plugins.row.config`)与**旧一代**(≤ 0.1.6-alpha.1:`settings.installSection` + `settingsScope` + `settings.plugin.item`)。已在 **DSH 0.1.5-rc.3** 与 **DSH 0.1.7-alpha.1** 上验证;更早的宿主上 favicon 仍可用,只是配置页可能不可达。
11
11
 
12
12
  浏览器标签页 favicon 实时反映 DSH 会话状态——`待机` / `运行中` / `提问` / `完成`——让你在标签页置于后台时也能一眼看出是否有会话需要处理。
13
13
 
@@ -19,7 +19,7 @@
19
19
  - **单个 SVG,浏览器内上色与动画** —— 只内置一个鲸鱼模板([`icons/base.svg`](./icons/base.svg));每个状态、颜色、每一帧都在客户端渲染为 `data:image/svg+xml` URI,不再有按颜色拆分的图标文件。
20
20
  - **六种内置特效** —— `static`(静止)、`blink`(闪烁)、`breath`(呼吸)、`rainbow`(彩虹)、`heartbeat`(心跳)、`bounce`(跳动),全部由 JavaScript 驱动(favicon 不会播放 SVG CSS 动画)。
21
21
  - **完全可配置、即时生效** —— 每个状态的颜色、特效、周期,以及提问 / 完成驻留时长,改动约 1 秒内同步到已打开的标签页——无需刷新、无需重启。
22
- - **内置配置 UI,无需手写 YAML** —— DSH 设置页里的 *标签页图标指示器* 卡片可编辑整套配置,带实时色块预览,保存后自动写入 `settings.yaml`(路径见下)。
22
+ - **内置配置 UI,无需手写 YAML** —— *标签页图标指示器* 页面可编辑整套配置,带实时色块预览并自动持久化(新宿主:profile patch;旧宿主:profile `settings.yaml`,路径见下)。
23
23
  - **后台标签页与重启抗性** —— 隐藏标签页中 `requestAnimationFrame` 被暂停时,动画态会按墙钟时间补帧;状态轮询还能扛住 host 重启、后端停止:故障期间标签页图标绝不丢失(还原启动时缓存的原始图标 `data:`-URI 副本,或保留最后一帧插件图标),端点恢复后自动回到实时状态。切回前台时会立刻触发一次状态拉取并重绘——后台标签页的定时器会被浏览器节流,轮询可能滞后,所以回到标签页的瞬间就刷新最新状态(比如 `done` 保持期在隐藏期间过期、图标应退回 `idle` 的情况)。
24
24
  - **多 agent 一目了然** —— 当同时有**超过一个**活动 agent(非待机:`asking` / `running` / `done`)时,favicon 从鲸鱼切换为**占满整帧的数字块**,实时显示活动数(上限 `99+`),颜色与动画和该状态下鲸鱼完全一致;活动数回到 0–1 时恢复鲸鱼。(视觉与 [`demo/badge.html`](./demo/badge.html) 的「满幅数字」通道一致。)
25
25
 
@@ -27,12 +27,14 @@
27
27
 
28
28
  | # | 步骤 |
29
29
  | --- | --- |
30
- | 1 | 打开 DSH Web GUI,进入 **设置**。 |
31
- | 2 | 在 **插件** 选项卡中,打开 **插件配置**。 |
32
- | 3 | 找到 **标签页图标指示器(Favicon indicator)** 卡片。 |
30
+ | 1 | 打开 DSH Web GUI,进入 **设置** → **插件**。 |
31
+ | 2 | ≥ 0.1.7:展开 **dsh-web-icon-indicator** bundle,在其 `dsh-web-icon-indicator` 行上点击 **配置**;≤ 0.1.6-alpha.1:在该选项卡里打开 **插件配置**。 |
32
+ | 3 | 打开 **标签页图标指示器(Favicon indicator)** 配置页。 |
33
33
  | 4 | 先用 **默认图标颜色** 设置待机鲸鱼的颜色(这是待机唯一的设置项——待机只画一种颜色、不做动画),再展开状态行(`running` / `asking` / `done`)编辑 **特效**、**颜色**(每个色块即原生取色器),以及**周期(毫秒)**——仅动画状态显示,静态状态无周期;用 **提问驻留** / **完成驻留** 调整两个时长。 |
34
34
 
35
- 改动会通过 settings 传输层持久化到 profile 的 `settings.yaml`,约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。
35
+ 改动会通过 settings 传输层持久化到 profile patch,约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。
36
+
37
+ > 🐳 **插件页会显示本插件自己的图标** —— DSH ≥ 0.1.7 的 **设置 → 插件** 页面会为每个 bundle 卡片画一个 48px 图标(每一行 40px),来源是 `package.json` → `icon`。本仓库声明了 `"icon": "icons/plugin.svg"`,所以卡片显示的是鲸鱼而不是通用占位图。图标由宿主读取(`readPluginMeta` → `iconOf`),并以 base64 `data:` URL 传给浏览器;**图标缺失或不合规不会把卡片变空——而是点亮该插件的 `meta.error`,卡片上出现「问题」标签**,所以路径必须相对、必须在包内、且 ≤ 256 KiB。加这个字段不需要任何配置、代码或宿主重启,只需重新安装或发一个新版本;详见[插件页图标](#插件页图标)。
36
38
 
37
39
  ## 🎬 默认配置,可视化
38
40
 
@@ -110,11 +112,15 @@ dsh plugin --profile web add <路径或tarball>
110
112
 
111
113
  或将目录放进 `~/.dsh/profiles/web/node_modules/<name>/`,并附带与包内一致的 `cordis.patch.yml`。
112
114
 
115
+ > ℹ️ 官方**插件页**的图标读取的是**已安装包的 `package.json.icon`**,因此只有在安装
116
+ > (或发布新版本)之后才会出现——已经打开的插件页会一直显示上一次的快照,直到你刷新。
117
+ > 详见[插件页图标](#插件页图标)。
118
+
113
119
  ## 配置
114
120
 
115
121
  所有键均可选,默认值如下。`statusPath` 与 `iconPathPrefix` 是**注册期**键:
116
122
  只能在合成条目(composition entry)里设置——它们在插件挂载时就被烘进路由表与注入
117
- 脚本,因此刻意**不**进入设置面(`settings.yaml`)。
123
+ 脚本,因此刻意**不**进入配置页的实时表单(它们是 `Config` 字段,但非 `.volatile()`)。
118
124
 
119
125
  | 键 | 默认值 | 含义 |
120
126
  | --- | --- | --- |
@@ -126,6 +132,10 @@ dsh plugin --profile web add <路径或tarball>
126
132
  | `defaultColor` | *(未设置)* | 默认图标颜色(待机鲸鱼的主色)—— 用于区分多个 DSH 实例;与其它状态颜色过于接近时会告警 |
127
133
  | `states` | 见下 | 每个状态的视觉配置 |
128
134
 
135
+ > 插件页的图标**不是**配置键——它来自 `package.json.icon`
136
+ > (见[插件页图标](#插件页图标))。`iconsDir` 只告诉 *favicon* 路由去哪里取
137
+ > `base.svg`。
138
+
129
139
  `states` 中每个状态是一个对象:`{ effect, colors[], speed? }`:
130
140
 
131
141
  ```yaml
@@ -143,7 +153,7 @@ config:
143
153
 
144
154
  `idle` 比较特殊:它的颜色就是 `defaultColor` 键,设置卡片**不为它提供状态条目**
145
155
  (一种颜色、不做动画、也没有周期)。若 `states.idle` 来自合成条目或手写的
146
- `settings.yaml`,仍然会被沿用——这属于向后兼容,只是无法在卡片里编辑。
156
+ 设置文档的用户层,仍然会被沿用——这属于向后兼容,只是无法在配置页里编辑。
147
157
 
148
158
  每个状态条目会在默认值之上做浅合并,因此只需覆盖少量状态。示例:
149
159
 
@@ -173,7 +183,7 @@ config:
173
183
  因此 idle 仍保留自己配置的特效与第二色,其它状态的颜色语义(黄=运行、红/黄=提问、
174
184
  绿=完成)不受影响;不设置(默认)即等于「沿用 idle 自己的颜色」,行为与之前完全一致。
175
185
  - 这是**按 DSH 实例**生效的设置,不是按标签页:同一实例的所有标签页共用它;另一个实例
176
- (自己的 profile / `settings.yaml`,例如 `dsh web --port 3081`)可以用另一种颜色。
186
+ (自己的 profile,例如 `dsh web --port 3081`)可以用另一种颜色。
177
187
  - **恢复默认**会把你的覆盖清回合成条目。当颜色本来就来自合成条目(`base` 层,
178
188
  用户层的 `unset` 触及不到)时,卡片改为写入 idle 自己的颜色——这样「恢复默认」真的
179
189
  能让图标回到朴素的鲸鱼色,而合成条目里配置的值仍可通过「清除覆盖」一键取回。
@@ -186,13 +196,18 @@ config:
186
196
  共用 `#FACC15`,所以**状态之间互不比较**,只把默认色与它们逐一比较。格式非法的值会被
187
197
  忽略并上报,而不会被画到图标上。
188
198
 
189
- ### 设置页与 `settings.yaml`(DSH ≥ 0.1.2)
199
+ ### 设置页(DSH ≥ 0.1.2-rc.1,兼容两代 settings 契约)
190
200
 
191
- 插件把上面整套配置注册进 DSH settings 服务,命名空间为 `web-icon-indicator`
192
- (schema 为 `lib/index.js` 中的 schemastery schema):
201
+ 插件把上面整套配置作为 Cordis `Config` schema 导出(`lib/index.js` 中的
202
+ schemastery schema),**并在宿主仍提供旧版服务时**用同一份 schema 走
203
+ `settings.installSection` 注册——两条路径在挂载时按能力探测,因此同一份 bundle
204
+ 两代宿主都能用。命名空间随代际不同:**≥ 0.1.7** 按 **profile entry id** 组织实时
205
+ 表单,即 `dsh-web-icon-indicator`(bundle patch 声明的行 id);**≤ 0.1.6-alpha.1**
206
+ 使用插件自定的 `web-icon-indicator`,与 0.5.x 保持一致,老用户的 section 继续生效:
193
207
 
194
- - **Web GUI:** 打开 **设置 → 插件 → 插件配置**,会出现 *标签页图标指示器*
195
- 卡片,可编辑:提问/完成驻留、**默认图标颜色**(带配色一览与相似度告警),以及
208
+ - **Web GUI:** 打开 **设置 → 插件**,展开 **dsh-web-icon-indicator** bundle 并
209
+ 配置它的行,即出现 *标签页图标指示器* 页面,可编辑:提问/完成驻留、
210
+ **默认图标颜色**(带配色一览与相似度告警),以及
196
211
  `running` / `asking` / `done` 三个状态各自的特效 / 颜色 / 周期。这三个状态
197
212
  各占一行可折叠条目,行首是一个**色块**——多色状态(asking)会左右分格同时显示
198
213
  红黄两色,旁边是一行「特效 · 周期」摘要(如 `Blink · 400ms`)。点击色块即打开
@@ -206,27 +221,72 @@ config:
206
221
  色块(也就是存下的 `colors[0]`)。`idle` 刻意**不占
207
222
  一行**——它只画一种颜色、不做动画,默认图标颜色就是它的全部配置。所有修改都通过
208
223
  settings 传输层暂存并保存。
209
- - **持久化:** 值写入 profile 的 `settings.yaml`(默认 `~/.dsh/settings.yaml`)
210
- 的 `web-icon-indicator:` 段。合成条目仍是 `base` 层;解析顺序为 schema 默认值
211
- → 合成条目 → 设置文档用户层。
212
- - **无需重启服务器、无需刷新标签页**即可让设置卡片的修改生效:`askingHoldMs` /
213
- `doneHoldMs` 在主机侧即时生效;各状态的视觉配置(特效 / 颜色 / 周期)会随状态
214
- 轮询同步进正在运行的标签页,约 1 秒内生效。只有改 `lib/index.js` 里的代码级
215
- 默认值才需要重载标签页(或重新构建 DSH Web)。
216
- - **路由路径不是设置项。** `statusPath` / `iconPathPrefix` 是注册期键,已被烘进
217
- 路由表与注入脚本,因此只存在于合成条目(见上方表格),改动需要重启。它们刻意
218
- 不在设置 schema 与 `settings.yaml` 中:若在那里生效,浏览器会去请求服务器根本
219
- 没有提供的路径。
220
- - 因此设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir` 与 `states`。
221
- `iconsDir` 没有 schema 默认值,用户未设置时不会出现在设置文档中。
224
+ - **持久化:** ≥ 0.1.7 写入 profile patch
225
+ (`~/.dsh/profiles/<profile>/cordis.patch.yml`)中 `dsh-web-icon-indicator`
226
+ 行的 `config:` 段;≤ 0.1.6-alpha.1 写入 `~/.dsh/settings.yaml` 的
227
+ `web-icon-indicator:` 段。两者都保持合成条目为 `base` 层;解析顺序为
228
+ schema 默认值 → 合成条目 → 用户层。
229
+ - **无需重启服务器、无需刷新标签页**即可让配置页的修改生效:≥ 0.1.7 时实时写入
230
+ 直接提交到运行中插件的配置引用(`loader/volatile-update`——loader 不会重启插件);
231
+ 旧一代则由 settings 服务回调注册的 `onChange`。两种情况 `askingHoldMs` /
232
+ `doneHoldMs` 都在主机侧即时生效;各状态的视觉配置(特效 / 颜色 / 周期)会随状态
233
+ 轮询同步进正在运行的标签页,约 1 秒内生效。只有改 `lib/index.js` 里的代码级默认值
234
+ 才需要重载标签页(或重新构建 DSH Web)。
235
+ - **路由路径不是实时设置项。** `statusPath` / `iconPathPrefix` 由 `Config` 校验,但
236
+ 刻意**不加** `.volatile()`,因此不出现在实时表单里:它们已被烘进路由表与注入脚本,
237
+ 在那里生效只会让浏览器去请求服务器根本没有提供的路径。它们只存在于合成条目
238
+ (见上方表格),改动会重新挂载插件。
239
+ - 因此实时设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir`、`defaultColor`
240
+ 与 `states`。`iconsDir` 没有 schema 默认值,用户未设置时不会出现在解析后的表单中。
222
241
  - 浏览器半区是手写的 `lib/client.js`(ModuleLoader factory 格式——无构建步骤、
223
- 无额外运行期依赖,仅用 shell 自带的 `react`)。DSH 客户端扫描器会在下次启动
224
- profile 时识别新的 `dsh.client` 声明。
225
- - 未组合 settings 服务的部署不受影响:插件回退到直接读取合成条目,行为与之前完全一致。
242
+ 无额外运行期依赖,仅用 shell 自带的 `react`)。它同时往两代页面插槽注册
243
+ (`plugins.row.config`——按「Host 是否提供该命名空间」门控;以及旧的
244
+ `settings.plugin.item`),并在渲染时按宿主实际提供的服务解析表单
245
+ (`configForms` 或 `settingsScope`),因此两者都不是模块的硬依赖。DSH 客户端
246
+ 扫描器会在下次启动 profile 时识别新的 `dsh.client` 声明。
247
+ - 未组合 settings 服务的部署不受影响:插件继续使用挂载时的合成条目 + schema 默认值。
248
+
249
+ ## 插件页图标
250
+
251
+ DSH ≥ 0.1.7 会在官方**插件页**为每个插件渲染图标:bundle 卡片 48px、每一行
252
+ 40px(`@deepseek-ai/dsh-client-ui-plugin-manager` 的 `PackageArtwork`,未声明时
253
+ 回退到通用占位图)。图标直接来自包清单,所以给出它纯粹是元数据——**不需要改代码、
254
+ 不需要配置、不需要重启宿主**:
255
+
256
+ ```jsonc
257
+ // package.json
258
+ {
259
+ "name": "dsh-web-icon-indicator",
260
+ "icon": "icons/plugin.svg" // 相对 package.json,且必须留在包内
261
+ }
262
+ ```
263
+
264
+ 宿主在**不执行任何插件代码**的前提下读取它(`@deepseek-ai/dsh-app-boot` 的
265
+ `readPluginMeta` → `iconOf`):
266
+
267
+ | 规则 | 取值 |
268
+ | --- | --- |
269
+ | 字段 | 顶层 `package.json.icon`(与 `name`/`version` 同级,**不是**放在 `dsh` 下) |
270
+ | 路径形式 | 相对清单所在目录;绝对路径、Windows 盘符(`C:\…`)与 URL 都会被拒绝 |
271
+ | 包含关系 | 经 `realpath` 解析后必须仍在清单目录内(`..` 越界会被拒绝) |
272
+ | 格式 | `.svg` → `image/svg+xml`,`.png` → `image/png`,`.jpg` / `.jpeg` → `image/jpeg`,`.webp` → `image/webp` |
273
+ | 大小 | ≤ 256 KiB(`stat` 与实际读到的字节数各校验一次) |
274
+ | 传输 | 读成字节后以 base64 `data:` URL 随元数据下发(`PluginLocalizedMeta.icon`) |
275
+
276
+ > ⚠️ **坏的图标比没有图标更糟。** 任何不合规都不是静默回退,而是**元数据错误**:
277
+ > 读取器返回 `meta.error`,宿主把它暴露出来,卡片上会给这个插件打上**问题标签**。
278
+ > 所以文件要留在包内(`files` 里已包含 `icons/`),并且必须是独立可用的 SVG——
279
+ > 直接用 `icons/base.svg` 是**无效**的,因为它里面的 `#p { fill: __COLOR__ }`
280
+ > 占位符并不是颜色。本仓库因此提供预填好的
281
+ > [`icons/plugin.svg`](./icons/plugin.svg)(即 `base.svg` 把 `__COLOR__` 换成
282
+ > `#1a1a1a`),并在 `test/verify.js` 里把整套约束都断言下来(F62–F65)。
283
+
284
+ 由于图标是从**已安装**的包读取的,本地改动只有在重新安装插件
285
+ (`dsh plugin --profile web add <path>`)或发布新版本之后才会生效。
226
286
 
227
287
  ## 实现原理
228
288
 
229
- - Host 插件 + 一个小型浏览器半区:在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`(鲸鱼模板),以及一个 `tapIndex` 向每个 `index.html` 注入小段浏览器脚本。整套配置已注册进 DSH settings 服务(`web-icon-indicator` 命名空间)用于校验、持久化与设置页卡片(见上)。
289
+ - Host 插件 + 一个小型浏览器半区:在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`(鲸鱼模板),以及一个 `tapIndex` 向每个 `index.html` 注入小段浏览器脚本。整套配置就是插件导出的 `Config` schema,DSH settings 服务按 `dsh-web-icon-indicator` entry id 把它投射为实时配置页(见上)。
230
290
  - 状态按 `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 在忙。
231
291
  - `ask_user_question` 工具调用(通过 `tools/pre-execute` / `tools/result`)把会话置为 `asking`,带可配置的最小保持时长,即使你立刻回答,图标也会保持可见。
232
292
  - 权限 / **沙箱拦截**等待同样会显示为 `asking`:当 agent 命中沙箱拒绝并请求提权(`sandbox_permissions` + `justification`),或其他工具需要征得同意时,审批服务会先写入一条 `approval/asked` 会话事件并阻塞 agent,直到你做出决定。插件监听 `session/event`(并以实时会话日志的权威折叠作为兜底)在整个等待期间将会话置为 `asking` 状态,收到 `approval/decided` 后清除。
@@ -246,6 +306,7 @@ favicon 本质是一张图片,浏览器不会在标签页 UI 里运行 SVG 自
246
306
  已知限制(截至 Safari 26.3):
247
307
 
248
308
  - **favicon 有专属缓存。** Chrome 用 favicon 数据库、Firefox 用 `favicons.sqlite`、**Safari 用系统级图标缓存**——清普通缓存都清不掉,WebKit 甚至会把「无图标」这一状态也缓存起来。这就是改了图标后,已打开的标签页还可能显示旧图标的原因。本插件已通过「给 `base.svg` 与状态端点设置 `Cache-Control: no-store`、请求携带 freshness 参数(`?t=Date.now()`)、每次切换状态时重建 `<link rel=icon>` 节点」来缓解。
309
+ - **必须先折叠「按主题配对」的两个 favicon link(DSH ≥ 0.1.7)。** 宿主的 `index.html` 里有**两个** `rel=icon`——`favicon-dark.svg` 对应 `prefers-color-scheme: dark`、`favicon.svg` 对应浅色(0.1.6-alpha.2 及更早只有一个)。浏览器取的是**最后一个 media 命中的已连接 link**,所以只重绘第一个的话,无论会话状态怎么变,标签页始终显示宿主自己的图标。插件现在会在首次绘制前把这一组规范成**唯一一个不带 media 的 link**(保留你当前配色方案对应的那个)。如果你用的是 0.5.2 之前的版本、且标签页图标完全不变化,原因就在这里。
249
310
  - **Safari 渲染 SVG favicon,但忽略其内部 CSS**——不支持 `@media`、`prefers-color-scheme`、CSS 动画。所以所有上色必须烘焙进每一帧的标记(本插件正是这么做的),而不能依赖 CSS 变量。
250
311
  - **`data:` URI 的 SVG favicon 在 Safari 不可靠**(WebKit bug 236616,仍未关闭;Safari 17.6 复现)。本插件当前每帧都生成 `data:image/svg+xml` URI,因此在 Safari 上标签页图标可能完全不显示——这是最大的已知缺口。
251
312
  - **Safari 的动态 JS 更新为 hit-or-miss**,可能需要刷新一次;Safari 会「锁定」它首次看到的图标。目前没有保证可靠的、符合规范的手段能在 Safari 中实时更换 favicon。
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50" fill="none">
2
+ <style>
3
+ #p { fill: #1a1a1a; }
4
+ </style>
5
+ <path id="p" fill="#1a1a1a" d="M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z"/>
6
+ </svg>