dsh-web-icon-indicator 0.5.0 → 0.5.1
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 +6 -0
- package/README.md +59 -43
- package/README.zh.md +43 -31
- package/lib/client.js +141 -32
- package/lib/index.js +171 -94
- package/lib/types/client/index.d.ts +13 -7
- package/lib/types/index.d.ts +14 -5
- package/package.json +3 -2
- package/test/verify.js +312 -65
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.1](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.5.0...v0.5.1) (2026-09-22)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
* load on DSH 0.1.7 and keep the legacy settings contract ([6e8dbb5](https://github.com/waknow/dsh-web-icon-indicator/commit/6e8dbb5402b9847eecc33029d68316407f767288))
|
|
15
|
+
|
|
10
16
|
## [0.5.0](https://github.com/waknow/dsh-web-icon-indicator/compare/v0.4.2...v0.5.0) (2026-09-17)
|
|
11
17
|
|
|
12
18
|
### Added
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-web-icon-indicator)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
|
|
10
|
-
> **⚠️ DSH version support** — requires **DSH ≥ 0.1.2** (
|
|
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** —
|
|
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,12 @@ 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 |
|
|
32
|
-
| 3 |
|
|
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
|
|
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
36
|
|
|
37
37
|
## 🎬 Default configuration, visualized
|
|
38
38
|
|
|
@@ -120,7 +120,8 @@ Or drop the directory into `~/.dsh/profiles/web/node_modules/<name>/` and ship a
|
|
|
120
120
|
All keys are optional; defaults shown. `statusPath` and `iconPathPrefix` are
|
|
121
121
|
**registration-time** keys: set them in the composition entry only — they are
|
|
122
122
|
baked into the route table and the injected script when the plugin mounts, so
|
|
123
|
-
they are intentionally **not** part of the settings
|
|
123
|
+
they are intentionally **not** part of the settings page's live form (they are
|
|
124
|
+
`Config` fields, but not `.volatile()`).
|
|
124
125
|
|
|
125
126
|
| Key | Default | Meaning |
|
|
126
127
|
| --- | --- | --- |
|
|
@@ -150,8 +151,8 @@ config:
|
|
|
150
151
|
`idle` is special: its color is the `defaultColor` key, and the settings card
|
|
151
152
|
offers **no per-state entry** for it (one color, no animation, no cycle). A
|
|
152
153
|
`states.idle` entry is still honored when it arrives from the composition entry
|
|
153
|
-
or
|
|
154
|
-
not editable from the
|
|
154
|
+
or the settings document's user layer — backward compatibility only; it is
|
|
155
|
+
simply not editable from the page.
|
|
155
156
|
|
|
156
157
|
Entries are shallow-merged over the defaults, so you can override only a few states. Example:
|
|
157
158
|
|
|
@@ -183,8 +184,8 @@ immediately distinguishable — no need to touch the per-state palette:
|
|
|
183
184
|
color you configured; the other states keep their signal colors. Unset (the
|
|
184
185
|
default) means "the idle state's own color", i.e. today's behavior.
|
|
185
186
|
- 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
|
-
`
|
|
187
|
+
instance shares it, while another instance (its own profile, e.g.
|
|
188
|
+
`dsh web --port 3081`) can use a different color.
|
|
188
189
|
- **Reset** removes your overrides back to the composition entry. When the
|
|
189
190
|
color comes from that entry (the `base` layer, which an `unset` cannot
|
|
190
191
|
reach), the card writes the idle state's own color instead — so "Reset to
|
|
@@ -203,14 +204,21 @@ immediately distinguishable — no need to touch the per-state palette:
|
|
|
203
204
|
with each other — only the default color against them. A malformed value is
|
|
204
205
|
ignored (and reported) rather than painted.
|
|
205
206
|
|
|
206
|
-
### Settings page
|
|
207
|
-
|
|
208
|
-
The plugin
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
207
|
+
### Settings page (DSH ≥ 0.1.2-rc.1, both settings generations)
|
|
208
|
+
|
|
209
|
+
The plugin exports the whole config surface above as its Cordis `Config`
|
|
210
|
+
schema (a schemastery schema in `lib/index.js`) **and** registers the same
|
|
211
|
+
schema through the legacy settings service when the running host still exposes
|
|
212
|
+
it — the two paths are feature-detected at mount, so one bundle serves both.
|
|
213
|
+
The namespace differs by generation: **≥ 0.1.7** keys every live form by
|
|
214
|
+
**profile entry id**, so it is `dsh-web-icon-indicator` (the row id the bundle
|
|
215
|
+
patch declares); **≤ 0.1.6-alpha.1** uses the plugin-chosen
|
|
216
|
+
`web-icon-indicator`, the same string 0.5.x used, so an existing section keeps
|
|
217
|
+
resolving:
|
|
218
|
+
|
|
219
|
+
- **Web GUI:** open **设置 → 插件**, expand the **dsh-web-icon-indicator**
|
|
220
|
+
bundle and configure its row. The *Favicon indicator* page edits the same
|
|
221
|
+
keys: asking/done hold, the **default icon color** (with a
|
|
214
222
|
live palette preview and the similarity warning), and the per-state effect /
|
|
215
223
|
colors / cycle for `running` / `asking` / `done`. Each of those states is a
|
|
216
224
|
collapsible row whose header shows one **color chip** per state — split in two
|
|
@@ -230,34 +238,42 @@ service under the `web-icon-indicator` namespace (a schemastery schema in
|
|
|
230
238
|
`colors[0]`). `idle` deliberately gets **no row** — it paints one
|
|
231
239
|
color and never animates, so the default-color field is its whole
|
|
232
240
|
configuration. Everything is staged and saved through the settings transport.
|
|
233
|
-
- **Persistence:** values land in the profile
|
|
234
|
-
`~/.dsh/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
241
|
+
- **Persistence:** on ≥ 0.1.7 values land in the profile patch
|
|
242
|
+
(`~/.dsh/profiles/<profile>/cordis.patch.yml`) under the
|
|
243
|
+
`dsh-web-icon-indicator` row's `config:` block; on ≤ 0.1.6-alpha.1 they land
|
|
244
|
+
in `~/.dsh/settings.yaml` as a `web-icon-indicator:` section. In both cases
|
|
245
|
+
the composition entry stays the `base` layer; resolution order is schema
|
|
246
|
+
defaults → composition entry → user layer.
|
|
247
|
+
- **No server restart, no tab reload** for settings-page saves. On ≥ 0.1.7 a
|
|
248
|
+
live write is committed into the running plugin's config references
|
|
249
|
+
(`loader/volatile-update` — the loader does not restart the plugin); on the
|
|
250
|
+
legacy line the service calls the registered `onChange` hook. Either way
|
|
251
|
+
`askingHoldMs` / `doneHoldMs` apply host-side immediately and per-state visual
|
|
252
|
+
config (effect / colors / cycle) is synced into the running tab through the
|
|
253
|
+
status poll within ~1 s. Only code-level default changes in `lib/index.js`
|
|
254
|
+
need a tab reload (or a DSH web rebuild).
|
|
255
|
+
- **Route paths are not live settings.** `statusPath` / `iconPathPrefix` are
|
|
256
|
+
validated by `Config` but deliberately **not** `.volatile()`, so they are
|
|
257
|
+
absent from the live form: they are baked into the route table and the
|
|
258
|
+
injected script at registration time, and honoring an edit there would point
|
|
259
|
+
the browser at a path the server never serves. They are composition-entry
|
|
260
|
+
only (see the table above); changing them re-applies the plugin.
|
|
261
|
+
- The live surface therefore covers `askingHoldMs`, `doneHoldMs`, `iconsDir`,
|
|
262
|
+
`defaultColor` and `states`. `iconsDir` has no schema default, so it is
|
|
263
|
+
omitted from the resolved form until a user sets it.
|
|
251
264
|
- 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`).
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
265
|
+
format — no build step, no runtime deps beyond the shell's `react`). It
|
|
266
|
+
registers on both page slots (`plugins.row.config` — gated on the served
|
|
267
|
+
namespace — and the legacy `settings.plugin.item`) and resolves whichever
|
|
268
|
+
settings provider the host exposes (`configForms` or `settingsScope`) at
|
|
269
|
+
render time, so neither is a hard dependency of the module. The DSH client
|
|
270
|
+
scanner picks a new `dsh.client` declaration up on the next profile start.
|
|
271
|
+
- Deployments without a settings service are unaffected: the plugin keeps
|
|
272
|
+
running on the composition entry + schema defaults it was mounted with.
|
|
257
273
|
|
|
258
274
|
## How it works
|
|
259
275
|
|
|
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
|
|
276
|
+
- 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
277
|
- 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
278
|
- `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
279
|
- 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`.
|
package/README.zh.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/dsh-web-icon-indicator)
|
|
8
8
|
[](./LICENSE)
|
|
9
9
|
|
|
10
|
-
> **⚠️ DSH 版本支持** —— 需要 **DSH ≥ 0.1.2
|
|
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** ——
|
|
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,12 @@
|
|
|
27
27
|
|
|
28
28
|
| # | 步骤 |
|
|
29
29
|
| --- | --- |
|
|
30
|
-
| 1 | 打开 DSH Web GUI,进入
|
|
31
|
-
| 2 |
|
|
32
|
-
| 3 |
|
|
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
|
|
35
|
+
改动会通过 settings 传输层持久化到 profile patch,约 1 秒内应用到已打开的标签页——无需刷新、无需重启。完整键说明见 [配置](#配置)。
|
|
36
36
|
|
|
37
37
|
## 🎬 默认配置,可视化
|
|
38
38
|
|
|
@@ -114,7 +114,7 @@ dsh plugin --profile web add <路径或tarball>
|
|
|
114
114
|
|
|
115
115
|
所有键均可选,默认值如下。`statusPath` 与 `iconPathPrefix` 是**注册期**键:
|
|
116
116
|
只能在合成条目(composition entry)里设置——它们在插件挂载时就被烘进路由表与注入
|
|
117
|
-
|
|
117
|
+
脚本,因此刻意**不**进入配置页的实时表单(它们是 `Config` 字段,但非 `.volatile()`)。
|
|
118
118
|
|
|
119
119
|
| 键 | 默认值 | 含义 |
|
|
120
120
|
| --- | --- | --- |
|
|
@@ -143,7 +143,7 @@ config:
|
|
|
143
143
|
|
|
144
144
|
`idle` 比较特殊:它的颜色就是 `defaultColor` 键,设置卡片**不为它提供状态条目**
|
|
145
145
|
(一种颜色、不做动画、也没有周期)。若 `states.idle` 来自合成条目或手写的
|
|
146
|
-
|
|
146
|
+
设置文档的用户层,仍然会被沿用——这属于向后兼容,只是无法在配置页里编辑。
|
|
147
147
|
|
|
148
148
|
每个状态条目会在默认值之上做浅合并,因此只需覆盖少量状态。示例:
|
|
149
149
|
|
|
@@ -173,7 +173,7 @@ config:
|
|
|
173
173
|
因此 idle 仍保留自己配置的特效与第二色,其它状态的颜色语义(黄=运行、红/黄=提问、
|
|
174
174
|
绿=完成)不受影响;不设置(默认)即等于「沿用 idle 自己的颜色」,行为与之前完全一致。
|
|
175
175
|
- 这是**按 DSH 实例**生效的设置,不是按标签页:同一实例的所有标签页共用它;另一个实例
|
|
176
|
-
(自己的 profile
|
|
176
|
+
(自己的 profile,例如 `dsh web --port 3081`)可以用另一种颜色。
|
|
177
177
|
- **恢复默认**会把你的覆盖清回合成条目。当颜色本来就来自合成条目(`base` 层,
|
|
178
178
|
用户层的 `unset` 触及不到)时,卡片改为写入 idle 自己的颜色——这样「恢复默认」真的
|
|
179
179
|
能让图标回到朴素的鲸鱼色,而合成条目里配置的值仍可通过「清除覆盖」一键取回。
|
|
@@ -186,13 +186,18 @@ config:
|
|
|
186
186
|
共用 `#FACC15`,所以**状态之间互不比较**,只把默认色与它们逐一比较。格式非法的值会被
|
|
187
187
|
忽略并上报,而不会被画到图标上。
|
|
188
188
|
|
|
189
|
-
###
|
|
189
|
+
### 设置页(DSH ≥ 0.1.2-rc.1,兼容两代 settings 契约)
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
插件把上面整套配置作为 Cordis `Config` schema 导出(`lib/index.js` 中的
|
|
192
|
+
schemastery schema),**并在宿主仍提供旧版服务时**用同一份 schema 走
|
|
193
|
+
`settings.installSection` 注册——两条路径在挂载时按能力探测,因此同一份 bundle
|
|
194
|
+
两代宿主都能用。命名空间随代际不同:**≥ 0.1.7** 按 **profile entry id** 组织实时
|
|
195
|
+
表单,即 `dsh-web-icon-indicator`(bundle patch 声明的行 id);**≤ 0.1.6-alpha.1**
|
|
196
|
+
使用插件自定的 `web-icon-indicator`,与 0.5.x 保持一致,老用户的 section 继续生效:
|
|
193
197
|
|
|
194
|
-
- **Web GUI:** 打开 **设置 →
|
|
195
|
-
|
|
198
|
+
- **Web GUI:** 打开 **设置 → 插件**,展开 **dsh-web-icon-indicator** bundle 并
|
|
199
|
+
配置它的行,即出现 *标签页图标指示器* 页面,可编辑:提问/完成驻留、
|
|
200
|
+
**默认图标颜色**(带配色一览与相似度告警),以及
|
|
196
201
|
`running` / `asking` / `done` 三个状态各自的特效 / 颜色 / 周期。这三个状态
|
|
197
202
|
各占一行可折叠条目,行首是一个**色块**——多色状态(asking)会左右分格同时显示
|
|
198
203
|
红黄两色,旁边是一行「特效 · 周期」摘要(如 `Blink · 400ms`)。点击色块即打开
|
|
@@ -206,27 +211,34 @@ config:
|
|
|
206
211
|
色块(也就是存下的 `colors[0]`)。`idle` 刻意**不占
|
|
207
212
|
一行**——它只画一种颜色、不做动画,默认图标颜色就是它的全部配置。所有修改都通过
|
|
208
213
|
settings 传输层暂存并保存。
|
|
209
|
-
- **持久化:**
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
|
|
214
|
+
- **持久化:** ≥ 0.1.7 写入 profile patch
|
|
215
|
+
(`~/.dsh/profiles/<profile>/cordis.patch.yml`)中 `dsh-web-icon-indicator`
|
|
216
|
+
行的 `config:` 段;≤ 0.1.6-alpha.1 写入 `~/.dsh/settings.yaml` 的
|
|
217
|
+
`web-icon-indicator:` 段。两者都保持合成条目为 `base` 层;解析顺序为
|
|
218
|
+
schema 默认值 → 合成条目 → 用户层。
|
|
219
|
+
- **无需重启服务器、无需刷新标签页**即可让配置页的修改生效:≥ 0.1.7 时实时写入
|
|
220
|
+
直接提交到运行中插件的配置引用(`loader/volatile-update`——loader 不会重启插件);
|
|
221
|
+
旧一代则由 settings 服务回调注册的 `onChange`。两种情况 `askingHoldMs` /
|
|
222
|
+
`doneHoldMs` 都在主机侧即时生效;各状态的视觉配置(特效 / 颜色 / 周期)会随状态
|
|
223
|
+
轮询同步进正在运行的标签页,约 1 秒内生效。只有改 `lib/index.js` 里的代码级默认值
|
|
224
|
+
才需要重载标签页(或重新构建 DSH Web)。
|
|
225
|
+
- **路由路径不是实时设置项。** `statusPath` / `iconPathPrefix` 由 `Config` 校验,但
|
|
226
|
+
刻意**不加** `.volatile()`,因此不出现在实时表单里:它们已被烘进路由表与注入脚本,
|
|
227
|
+
在那里生效只会让浏览器去请求服务器根本没有提供的路径。它们只存在于合成条目
|
|
228
|
+
(见上方表格),改动会重新挂载插件。
|
|
229
|
+
- 因此实时设置面覆盖 `askingHoldMs`、`doneHoldMs`、`iconsDir`、`defaultColor`
|
|
230
|
+
与 `states`。`iconsDir` 没有 schema 默认值,用户未设置时不会出现在解析后的表单中。
|
|
222
231
|
- 浏览器半区是手写的 `lib/client.js`(ModuleLoader factory 格式——无构建步骤、
|
|
223
|
-
无额外运行期依赖,仅用 shell 自带的 `react
|
|
224
|
-
|
|
225
|
-
|
|
232
|
+
无额外运行期依赖,仅用 shell 自带的 `react`)。它同时往两代页面插槽注册
|
|
233
|
+
(`plugins.row.config`——按「Host 是否提供该命名空间」门控;以及旧的
|
|
234
|
+
`settings.plugin.item`),并在渲染时按宿主实际提供的服务解析表单
|
|
235
|
+
(`configForms` 或 `settingsScope`),因此两者都不是模块的硬依赖。DSH 客户端
|
|
236
|
+
扫描器会在下次启动 profile 时识别新的 `dsh.client` 声明。
|
|
237
|
+
- 未组合 settings 服务的部署不受影响:插件继续使用挂载时的合成条目 + schema 默认值。
|
|
226
238
|
|
|
227
239
|
## 实现原理
|
|
228
240
|
|
|
229
|
-
- Host 插件 + 一个小型浏览器半区:在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`(鲸鱼模板),以及一个 `tapIndex` 向每个 `index.html`
|
|
241
|
+
- Host 插件 + 一个小型浏览器半区:在现有 `webServer` 上注册路由——状态 JSON 端点、静态 `/dsh-web-icon-indicator/base.svg`(鲸鱼模板),以及一个 `tapIndex` 向每个 `index.html` 注入小段浏览器脚本。整套配置就是插件导出的 `Config` schema,DSH settings 服务按 `dsh-web-icon-indicator` entry id 把它投射为实时配置页(见上)。
|
|
230
242
|
- 状态按 `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
243
|
- `ask_user_question` 工具调用(通过 `tools/pre-execute` / `tools/result`)把会话置为 `asking`,带可配置的最小保持时长,即使你立刻回答,图标也会保持可见。
|
|
232
244
|
- 权限 / **沙箱拦截**等待同样会显示为 `asking`:当 agent 命中沙箱拒绝并请求提权(`sandbox_permissions` + `justification`),或其他工具需要征得同意时,审批服务会先写入一条 `approval/asked` 会话事件并阻塞 agent,直到你做出决定。插件监听 `session/event`(并以实时会话日志的权威折叠作为兜底)在整个等待期间将会话置为 `asking` 状态,收到 `approval/decided` 后清除。
|
package/lib/client.js
CHANGED
|
@@ -2,15 +2,26 @@
|
|
|
2
2
|
* Browser half of dsh-web-icon-indicator (dsh.client bundle, ModuleLoader
|
|
3
3
|
* factory format — hand-written, no build step).
|
|
4
4
|
*
|
|
5
|
-
* Registers one
|
|
6
|
-
*
|
|
7
|
-
* namespace). The card binds the namespace's settings scope, stages edits
|
|
8
|
-
* locally, and writes them through `scope.set` / `scope.unset` (validated and
|
|
9
|
-
* persisted host-side into the profile's settings.yaml).
|
|
5
|
+
* Registers one configuration page and serves BOTH host generations from one
|
|
6
|
+
* bundle:
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* - DSH ≥ 0.1.7 (modern): page slot `plugins.row.config` keyed
|
|
9
|
+
* `<package name>#<row id>`, bound to the profile-entry settings namespace
|
|
10
|
+
* through `configForms` (`ctx.configForms.get(entryId)`).
|
|
11
|
+
* - DSH ≤ 0.1.6-alpha.1 (legacy): page slot `settings.plugin.item` keyed by the
|
|
12
|
+
* legacy `web-icon-indicator` namespace, bound through `settingsScope`.
|
|
13
|
+
*
|
|
14
|
+
* Neither settings provider is declared in `inject` — both are read weakly at
|
|
15
|
+
* render time (`resolveScope`) and the slot registration is attempted for both
|
|
16
|
+
* names (`slots.inject` is a no-op for a slot the host never declares). The
|
|
17
|
+
* card body is generation-agnostic: the modern controller and the legacy scope
|
|
18
|
+
* expose the same snapshot + `mutate` / `unset` face. The card stages edits
|
|
19
|
+
* locally and writes them through that scope (validated, revision-fenced, and
|
|
20
|
+
* persisted host-side).
|
|
21
|
+
*
|
|
22
|
+
* The page is inert on deployments that do not compose the host plugin: on the
|
|
23
|
+
* modern line registration is gated on `configForms.whileServed`, and the
|
|
24
|
+
* legacy tab only dispatches namespaces the host actually serves.
|
|
14
25
|
*/
|
|
15
26
|
window.__ModuleLoader__.load({
|
|
16
27
|
id: "dsh-web-icon-indicator",
|
|
@@ -26,9 +37,28 @@ window.__ModuleLoader__.load({
|
|
|
26
37
|
// Official UI primitives (same source the shell's own plugin cards use,
|
|
27
38
|
// e.g. dsh-client-ui-settings-plugins): the standard collapse chevron.
|
|
28
39
|
var primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
40
|
+
// The primitives icon set moved from size-suffixed names (`…Outline14`) to
|
|
41
|
+
// stroke-weight names (`…OutlineRegular` / `…OutlineMedium`); fall back so
|
|
42
|
+
// one bundle keeps working on both host lines.
|
|
43
|
+
var ChevronDownIcon = primitives.IconChevronDownOutlineRegular || primitives.IconChevronDownOutline14;
|
|
29
44
|
|
|
30
|
-
/**
|
|
31
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Settings namespace owned by the host plugin on the MODERN host line
|
|
47
|
+
* (DSH ≥ 0.1.7): the settings service keys every form by the profile entry
|
|
48
|
+
* id, and the bundle patch in `cordis.patch.yml` inserts exactly one row
|
|
49
|
+
* with this id. Keep it in sync with `SETTINGS_NAMESPACE` in lib/index.js
|
|
50
|
+
* and the row id in the patch.
|
|
51
|
+
*/
|
|
52
|
+
var NS = "dsh-web-icon-indicator";
|
|
53
|
+
/**
|
|
54
|
+
* Settings namespace the LEGACY host line (DSH ≤ 0.1.6-alpha.1) serves: the
|
|
55
|
+
* plugin registers it through `settings.installSection`, and this half binds
|
|
56
|
+
* the legacy `settingsScope` to the same string. Also the key the legacy
|
|
57
|
+
* settings tab dispatches in `settings.plugin.item`.
|
|
58
|
+
*/
|
|
59
|
+
var LEGACY_NS = "web-icon-indicator";
|
|
60
|
+
/** `plugins.row.config` key: `<bundle package name>#<row id as the patch declares it>`. */
|
|
61
|
+
var ROW_KEY = "dsh-web-icon-indicator#dsh-web-icon-indicator";
|
|
32
62
|
var LOCALE = "dsh-web-icon-indicator";
|
|
33
63
|
var STATE_NAMES = ["idle", "running", "asking", "done"];
|
|
34
64
|
var EFFECT_NAMES = ["static", "blink", "breath", "rainbow", "heartbeat", "bounce"];
|
|
@@ -82,8 +112,17 @@ window.__ModuleLoader__.load({
|
|
|
82
112
|
bounce: "effectBounce",
|
|
83
113
|
};
|
|
84
114
|
|
|
85
|
-
/**
|
|
86
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Cordis fiber services this browser plugin injects.
|
|
117
|
+
*
|
|
118
|
+
* Deliberately only the two services every host line provides: the settings
|
|
119
|
+
* provider itself is read weakly at render time (`resolveScope`), so one
|
|
120
|
+
* bundle serves both the modern `configForms` line (DSH ≥ 0.1.7) and the
|
|
121
|
+
* legacy `settingsScope` line (≤ 0.1.6-alpha.1). A hard `inject` on either
|
|
122
|
+
* name would leave the fiber un-activated on the other line, which is what
|
|
123
|
+
* broke the 0.5.x bundle on 0.1.7.
|
|
124
|
+
*/
|
|
125
|
+
var inject = ["slots", "locale"];
|
|
87
126
|
|
|
88
127
|
// ---------------------------------------------------------------------------
|
|
89
128
|
// Locale copy
|
|
@@ -642,12 +681,17 @@ window.__ModuleLoader__.load({
|
|
|
642
681
|
return { kind: "set", value: parsed };
|
|
643
682
|
}
|
|
644
683
|
|
|
645
|
-
/**
|
|
684
|
+
/**
|
|
685
|
+
* Subscribe a React component to the scope's snapshot store. `scope` may be
|
|
686
|
+
* null while the host serves no settings provider for our namespace (or
|
|
687
|
+
* before one appears); the hook count stays identical either way, and a
|
|
688
|
+
* null scope yields a null snapshot that renders nothing.
|
|
689
|
+
*/
|
|
646
690
|
function useScopeSnapshot(scope) {
|
|
647
691
|
return useSyncExternalStore(
|
|
648
|
-
function (listener) { return scope.subscribe(listener); },
|
|
649
|
-
function () { return scope.getSnapshot(); },
|
|
650
|
-
function () { return scope.getSnapshot(); }
|
|
692
|
+
function (listener) { return scope ? scope.subscribe(listener) : function () {}; },
|
|
693
|
+
function () { return scope ? scope.getSnapshot() : null; },
|
|
694
|
+
function () { return scope ? scope.getSnapshot() : null; }
|
|
651
695
|
);
|
|
652
696
|
}
|
|
653
697
|
|
|
@@ -1050,11 +1094,18 @@ window.__ModuleLoader__.load({
|
|
|
1050
1094
|
};
|
|
1051
1095
|
|
|
1052
1096
|
var disabled = !writable || saving;
|
|
1097
|
+
// The Plugins page asks a configuration entry twice: `view: 'summary'`
|
|
1098
|
+
// for the row's one-liner and `view: 'page'` for the body of its own
|
|
1099
|
+
// page (the page already heads it with the plugin's title and
|
|
1100
|
+
// description). A host that renders this card standalone passes no
|
|
1101
|
+
// `view` and keeps the collapsible header.
|
|
1102
|
+
if (props.view === "summary") return t("description");
|
|
1103
|
+
var pageView = props.view === "page";
|
|
1053
1104
|
|
|
1054
1105
|
return createElement(
|
|
1055
1106
|
"div",
|
|
1056
1107
|
{ style: styles.card },
|
|
1057
|
-
createElement(
|
|
1108
|
+
pageView ? null : createElement(
|
|
1058
1109
|
"button",
|
|
1059
1110
|
{
|
|
1060
1111
|
type: "button",
|
|
@@ -1073,10 +1124,10 @@ window.__ModuleLoader__.load({
|
|
|
1073
1124
|
createElement(
|
|
1074
1125
|
"span",
|
|
1075
1126
|
{ style: Object.assign({}, styles.chevron, open ? styles.chevronOpen : {}) },
|
|
1076
|
-
createElement(
|
|
1127
|
+
createElement(ChevronDownIcon, {})
|
|
1077
1128
|
)
|
|
1078
1129
|
),
|
|
1079
|
-
open ? createElement(
|
|
1130
|
+
(open || pageView) ? createElement(
|
|
1080
1131
|
"div",
|
|
1081
1132
|
{ style: styles.body },
|
|
1082
1133
|
!writable ? createElement("p", { style: styles.hint }, t("readOnly")) : null,
|
|
@@ -1274,23 +1325,81 @@ window.__ModuleLoader__.load({
|
|
|
1274
1325
|
// ---------------------------------------------------------------------------
|
|
1275
1326
|
// Plugin entry
|
|
1276
1327
|
// ---------------------------------------------------------------------------
|
|
1328
|
+
|
|
1329
|
+
/**
|
|
1330
|
+
* The settings form this card binds, on whichever host line is running.
|
|
1331
|
+
*
|
|
1332
|
+
* - DSH ≥ 0.1.7 exposes `configForms`, one controller per profile entry id
|
|
1333
|
+
* (snapshot + revision-fenced `mutate` / `unset`); the provider owns the
|
|
1334
|
+
* remote write, so this plugin never declares `remote` itself.
|
|
1335
|
+
* - DSH ≤ 0.1.6-alpha.1 exposes `settingsScope`; binding it to the legacy
|
|
1336
|
+
* namespace yields the same snapshot/write face the card already uses.
|
|
1337
|
+
*
|
|
1338
|
+
* Read weakly on purpose: the two services are mutually exclusive, and the
|
|
1339
|
+
* caller's `inject` list must stay valid on both lines.
|
|
1340
|
+
*/
|
|
1341
|
+
function resolveScope(ctx) {
|
|
1342
|
+
var forms = ctx.get("configForms");
|
|
1343
|
+
if (forms && typeof forms.get === "function") return forms.get(NS);
|
|
1344
|
+
var legacy = ctx.get("settingsScope");
|
|
1345
|
+
if (legacy && typeof legacy.bind === "function") return legacy.bind({ namespace: LEGACY_NS });
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1277
1349
|
function apply(ctx) {
|
|
1278
1350
|
var t = ctx.locale.bind(LOCALE);
|
|
1279
1351
|
ctx.effect(function () {
|
|
1280
1352
|
return ctx.locale.register(LOCALE, { zh: zh, en: en });
|
|
1281
|
-
}, "dsh-web-icon-indicator: settings
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1353
|
+
}, "dsh-web-icon-indicator: settings page dictionaries");
|
|
1354
|
+
// The scope is resolved per render (see the inject face below), so the
|
|
1355
|
+
// card follows whichever provider is mounted and renders nothing while
|
|
1356
|
+
// neither is.
|
|
1357
|
+
var pageFace = function () { return { scope: resolveScope(ctx), t: t }; };
|
|
1358
|
+
// Register on BOTH page slots. `ctx.slots.inject(key, …)` is a no-op for a
|
|
1359
|
+
// slot this host never declares, so each line runs exactly the branch it
|
|
1360
|
+
// understands:
|
|
1361
|
+
// - `plugins.row.config` (≥0.1.6-alpha.2): keyed `<package>#<row id>`,
|
|
1362
|
+
// the row's configure control comes from this registration;
|
|
1363
|
+
// - `settings.plugin.item` (≤0.1.6-alpha.1): keyed by the legacy
|
|
1364
|
+
// settings namespace, dispatched by the settings tab per served
|
|
1365
|
+
// namespace.
|
|
1366
|
+
var start = function () {
|
|
1367
|
+
var disposeRow = ctx.slots.inject("plugins.row.config", function () {
|
|
1368
|
+
return ctx.slots.register(
|
|
1369
|
+
{
|
|
1370
|
+
name: "plugins.row.config",
|
|
1371
|
+
key: ROW_KEY,
|
|
1372
|
+
locale: LOCALE,
|
|
1373
|
+
inject: pageFace,
|
|
1374
|
+
},
|
|
1375
|
+
IconConfigCard
|
|
1376
|
+
);
|
|
1377
|
+
});
|
|
1378
|
+
var disposeItem = ctx.slots.inject("settings.plugin.item", function () {
|
|
1379
|
+
return ctx.slots.register(
|
|
1380
|
+
{
|
|
1381
|
+
name: "settings.plugin.item",
|
|
1382
|
+
key: LEGACY_NS,
|
|
1383
|
+
locale: LOCALE,
|
|
1384
|
+
inject: pageFace,
|
|
1385
|
+
},
|
|
1386
|
+
IconConfigCard
|
|
1387
|
+
);
|
|
1388
|
+
});
|
|
1389
|
+
return function () { disposeRow(); disposeItem(); };
|
|
1390
|
+
};
|
|
1391
|
+
// On the modern line, gate registration on the served namespace so an
|
|
1392
|
+
// uncomposed plugin shows no page; `whileServed` owns the returned
|
|
1393
|
+
// disposer, so `start` must hand one back. The legacy line needs no gate:
|
|
1394
|
+
// its tab only dispatches namespaces the host serves.
|
|
1395
|
+
var forms = ctx.get("configForms");
|
|
1396
|
+
if (forms && typeof forms.whileServed === "function") {
|
|
1397
|
+
ctx.effect(function () {
|
|
1398
|
+
return forms.whileServed([NS], start);
|
|
1399
|
+
}, "dsh-web-icon-indicator: settings page");
|
|
1400
|
+
} else {
|
|
1401
|
+
ctx.effect(start, "dsh-web-icon-indicator: settings page");
|
|
1402
|
+
}
|
|
1294
1403
|
}
|
|
1295
1404
|
|
|
1296
1405
|
exports.apply = apply;
|