@gehennawu/dsh-service 2.0.8 → 2.0.9

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/README.en.md CHANGED
@@ -9,9 +9,9 @@
9
9
  <em>DeepSeek Harness (DSH) Web 服务控制与运维插件。</em>
10
10
  </p>
11
11
 
12
- [![Version](https://img.shields.io/badge/version-2.0.8-3b82f6.svg?style=flat-square)](package.json)
12
+ [![Version](https://img.shields.io/badge/version-2.0.9-3b82f6.svg?style=flat-square)](package.json)
13
13
  [![License: MIT](https://img.shields.io/badge/License-MIT-10b981.svg?style=flat-square)](LICENSE)
14
- [![DSH Compatibility](https://img.shields.io/badge/DSH-%E2%89%A50.1.1--rc.2%20%C2%B7%20compatible%20with%200.2.1--alpha.2-6366f1.svg?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
14
+ [![DSH Compatibility](https://img.shields.io/badge/DSH-%E2%89%A50.1.1--rc.2%20%C2%B7%20compatible%20with%200.2.1--alpha.3-6366f1.svg?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
15
15
  [![Cordis](https://img.shields.io/badge/Cordis-v4.x-f59e0b.svg?style=flat-square)](https://cordis.moe/)
16
16
  [![Platform](https://img.shields.io/badge/platform-DSH%20Web-ec4899.svg?style=flat-square)](https://github.com/gehennawu/dsh-service)
17
17
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/gehennawu/dsh-service/issues)
@@ -57,8 +57,8 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
57
57
 
58
58
  ### Overview
59
59
 
60
- - Status summary (error → warning → info → normal aggregation with a status dot) → actionable items (only when present) → version and runtime → metrics grid → recent errors (rendered only when non-empty, collapsed by default)
61
- - Aggregation rules: any health/diagnostics/backup/statistics/quota/restart failure is error; permission issues and non-advisory diagnostic warnings are warning; available updates and no backups yet are info (high quota-window usage only shows as a progress bar on the quota page, and a likely manual terminal launch — a standing environment fact — appears only in the health checks and in the restart/upgrade confirmations; neither surfaces as an overview reminder)
60
+ - View service status, actionable issues, DSH / plugin versions, runtime and recent errors.
61
+ - Health, backup, statistics and quota failures surface as alerts; available updates and missing backups are informational.
62
62
 
63
63
  ### Maintenance and Configuration pages
64
64
 
@@ -66,15 +66,15 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
66
66
 
67
67
  ![Configuration page](./screenshots/configuration_en.png)
68
68
 
69
- - Maintenance groups Sessions, Skills, Backups, and Restart; subagents have a separate subpage under Model configuration, with no duplicate entry; it remembers the most recent subpage and falls back to an available item when a feature is disabled
70
- - Configuration groups feature switches, task notifications, and settings nav tabs; switches are grouped and apply live, Notifications stays visible but disabled when that feature is off, and Settings Nav supports manual reordering (drag / arrows) and visibility management of all tabs in the settings dialog sidebar, persisted to the server-side unified config file `$DSH_HOME/dsh-service-config.json` (cross-device sync, auto-fetched on startup with a local-cache fallback), applying live (the Service Control tab is permanently locked visible to prevent lockout); quota card order and visibility use the same config (the `quotaCards` section, fetched when the quota page opens)
71
- - Unified plugin config file: lightweight per-feature preferences converge into a single `$DSH_HOME/dsh-service-config.json` (atomic write, `0600`), partitioned by feature section — updating or clearing one section never touches others; large caches (usage index etc.) and encrypted credentials stay out of this file
69
+ - Maintenance groups Sessions, Skills, Backups and Restart, remembering the latest subpage.
70
+ - Configuration groups feature switches, notifications and settings-navigation tabs, with drag / arrow ordering and visibility controls. Service Control always remains visible.
71
+ - Navigation and quota-card preferences sync across devices through `$DSH_HOME/dsh-service-config.json`; feature sections do not affect one another.
72
72
 
73
73
  ### Version and updates
74
74
 
75
75
  - Shows the current DSH and plugin versions, linking to GitHub Releases
76
76
  - Automatically checks npm **stable + preview** (latest / next dist-tags)
77
- - "What's new" sits right after the current version number and is always available: it opens the notes for **the version you are running**; when a new version exists, the status text ("New version: x.y.z") is clickable and expands **the new version's** release info. Either way the host fetches the body from the GitHub Releases API and the client renders it in place (plain Markdown — no iframe, no navigation), with the version number, publish date and a pre-release tag; a missing Release or a failed read each get their own message; clicking anywhere outside the version card or pressing Escape closes it. Both rows lay their version number and "What's new" button out on **one shared set of columns**, with the version number right-aligned so that the gap before the button is fixed — they line up **regardless of version-number length**, since a longer version only widens the whole column instead of knocking one row out of place; narrow containers (phones and narrow settings panels) keep the same alignment — the version number absorbs the row's slack and right-aligns, so both rows still share their column edges
77
+ - "What's new" opens release notes for the running version; an available-update link opens the new release notes in place. Press Escape to close.
78
78
  - One-click upgrade with automatic restart; when no process manager is detected, it confirms the consequences first, keeps running, and shows manual-restart instructions
79
79
  - Between the upgrade landing and the process restart (common in a manual-launch environment) the version row reads "Installed X — restart to take effect" and the upgrade button is withdrawn; reopening the panel or refreshing the page keeps that state until the process is restarted
80
80
 
@@ -92,28 +92,25 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
92
92
 
93
93
  ![Health diagnostics](./screenshots/health-diagnostics_en.png)
94
94
 
95
- - Uptime, memory, session count, active agents, and background jobs; a "Process and runtime" card shows platform, architecture, and Node version
96
- - Full diagnostics: session storage, workspace registry, backup storage, tar availability, file permissions, runtime environment, Node version, and the **usage index** — rendered as a two-line check list (name + status dot / detail) with abnormal rows locally emphasized
97
- - **Usage index**: reports how many sessions are indexed, when the index was last updated, and how many sessions failed to index (a failed fold silently undercounts usage). Failed sessions are partial-success, so they are reported as informational rather than a warning and do not surface as an overview attention item; an index that has never been built (the Model statistics page has not been opened yet) is informational; the whole check is omitted while the Model usage feature is off (the host only skips refreshing the index in that state, so reporting would be stale and misleading)
98
- - **Notification permission**: the diagnostics page appends a browser-side check after the host checks — a blocked or not-yet-granted system-notification permission both mean "notifications will not fire", with a pointer to restore it; an unsupported browser is informational. The row stays inside the diagnostics page: it never enters overview attention items or lights the ⚠ (using notifications is a user choice), and it is not rendered at all while Task notifications is off
99
- - **Plugin health checks**: checks for anomalies only (the official plugin page already provides the full inventory and toggles, so no duplicate list here) — a failed plugin or one waiting on dependencies marks the check as error/warning, while fresh pending/loading fibers receive a short startup grace period; disposed or unknown states are explicit informational rows and do not become warnings. Affected plugins are listed below the check list with clipped, redacted failure text and missing deps; failed plugins can be reloaded behind a two-step confirmation (only entries the host confirmed as failed); manually disabled plugins (built-in or custom) never count as an anomaly
100
- - File-permission deep scan and repair (two-step confirmation) behind a collapsed "Permissions & repair" section
101
- - Suspected manual launch → yellow "no restart assurance" caution; no backups is informational only and never lights the ⚠
95
+ - View uptime, memory, sessions, active agents, background jobs, platform, architecture and Node version.
96
+ - Check session storage, workspace registry, backups, tar, file permissions, runtime and the usage index.
97
+ - **Usage index** reports indexed / failed sessions and update time. Partial failures do not block other sessions; disabling model statistics omits this check.
98
+ - **Notification permission** reports blocked or missing browser permission with recovery guidance; it does not affect overview status and is omitted when notifications are disabled.
99
+ - **Plugin health checks** list failures and missing dependencies; failed plugins can be reloaded after confirmation. Manually disabled plugins are not anomalies; disposed or unknown states are informational only.
100
+ - Permissions & repair provides deep scans and two-step permission repairs.
101
+ - Suspected manual launches warn of no restart assurance; missing backups are informational.
102
102
 
103
103
  ### Model statistics
104
104
 
105
105
  ![Model statistics](./screenshots/model-usage_en.png)
106
106
 
107
- - 7-day stacked bar chart of input / output / cache tokens; filter by project, hover for exact values; legend and refresh live in a unified region header; an accessible text summary accompanies the chart. The hover tooltip has seven lines: the date plus input/output/cache-hit detail, then **total tokens, successful model steps, and cache hit rate**; the hit rate is the host's per-day weighted value (ratios must not be summed across buckets)
108
- - Per-model horizontal bars with a "Today / Last 7 days / All time" toggle
109
- - A **daily usage heatmap calendar** at the bottom of the card: every day in the index (up to 20 weeks, columns aligned to Monday), four intensity steps by single-day peak, hover for that day's details; follows the project filter. The chart covers only the last 7 days, so this adds the long-range view
110
- - The heatmap header can switch to an **hourly punch card**: 7 rows (Monday–Sunday) × 24 columns (00–23), each cell the tokens for that slot, answering "when am I busiest". It is the same hourly buckets re-folded along a different axis, with no new persisted fields. The square-cell layout reserves space for weekday labels and gaps, with internal horizontal scrolling on narrow screens
111
- - **Indexed usage is kept permanently**: deleting a session or a project folder never subtracts history, so totals and the heatmap are always the complete history since the index was built; an index version bump preserves old summaries until readable sessions are fully rebuilt successfully; failures retain history and are retried
112
- - When a **project folder no longer exists**, that project disappears from the project filter, yet its usage still counts toward the "All projects" totals, day buckets, and model breakdown
113
- - Last-48-hour model/tool errors (collapsed by default, rendered only when present)
114
- - **Opening the panel syncs automatically — no manual refresh needed**: the mount sync always runs once (it renders the persisted cache first, then folds the delta in the background); after that it only syncs on **real edges** — crossing local midnight (the "Today" card lands on the new day), returning from a background tab, and each time you enter the usage page. Idling on the page arms no polling at all, and other pages pay none of that cost. The refresh button reads "Refreshing…" during a sync (about 0.3 s when idle, but tens of seconds when historical-format sessions exist and the corpus is changing)
115
- - Steps whose provider reports no token usage are excluded
116
- - A session that cannot be read, migrated, or parsed no longer blocks other sessions. An all-projects warning shows successful/skipped counts and expandable session IDs, error categories, and safe summaries. Previously indexed data is retained and marked stale; first-time failures contribute nothing and are retried on the next refresh. Only global errors, such as an unavailable service, failed session listing, or failed index write, fail the entire refresh. The warning can be dismissed (remembered locally), and silencing it mutes only that same failure set: a newly skipped session or a changed error category brings it back, so dismissing once never hides a new problem for good
107
+ - Seven-day input / output / cache token trends, filtered by project. Hover for totals, successful model steps and cache hit rates.
108
+ - Per-model breakdowns switch between Today, Last 7 days and All time.
109
+ - A **daily usage heatmap** covers up to 20 weeks, with a weekday × hour view; both follow the project filter.
110
+ - **Indexed history is permanent**: deleting sessions or projects never subtracts usage. Deleted projects leave the filter but remain in overall totals.
111
+ - View model / tool errors from the last 48 hours; omitted when empty.
112
+ - Sync automatically on panel opening, page entry, local midnight and return to the foreground, or refresh manually. No idle polling.
113
+ - Steps without provider-reported tokens are excluded. Unreadable sessions retain stale cached data and are retried while other sessions continue; expand failure details, with new failures bringing the notice back.
117
114
 
118
115
  ### Quota lookup
119
116
 
@@ -131,6 +128,9 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
131
128
  | OpenCode Go | `{baseURL}/usage` (built-in channel without an explicit baseURL uses the registry default `https://opencode.ai/zen/go/v1`) |
132
129
  | OpenRouter | Credits used % |
133
130
  | Kimi / SiliconFlow | CNY balance |
131
+ | Vercel AI Gateway | Official `GET /v1/credits` for the team's remaining USD balance (Gateway API key; lifetime spend is not a quota limit) |
132
+ | Poe | API-key query for remaining points (non-monetary; no currency gauge) |
133
+ | ClinePass | API-key query for 5-hour / weekly / monthly plan usage and reset times |
134
134
  | StepFun Balance | Official `GET /v1/accounts` (API key, com/ai dual domains) |
135
135
  | StepFun Step Plan | Console BFF subscription quota (Oasis-Token console session; 5-hour/weekly windows vs Credit pool auto-detected) |
136
136
  | Xiaomi MiMo Token Plan | Console-origin plan quota (web session cookie) |
@@ -149,7 +149,6 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
149
149
 
150
150
  ![Backup management](./screenshots/backup-management_en.png)
151
151
 
152
- - Backup records use light two-line rows (name on the first line, size · time on the second, separator layout; same style as the session list)
153
152
 
154
153
  - Creates `.tar.gz` archives of sessions, configuration, and plugin-profile manifests; sessions are snapshotted through the persistence layer's stable-read seam (active-agent writes no longer fail creation), and creation shows one continuous phase progress bar (copy / pack / verify / publish, step counter 1/4–4/4, real percentage during copy)
155
154
  - **DSH version at backup time**: the archive filename carries the DSH version running when the backup was taken (`dsh-backup-YYYYMMDD-HHmmss-dsh0.1.7-rc.1.tar.gz`), and `meta/backup.json` inside the archive records the DSH version, plugin version, and creation time; both the panel row and the restore preflight show it, so you can tell which version a snapshot came from (restoring across a downgrade cannot read newer session formats). Archives written by older plugin versions have no version segment: they still list and restore normally, they simply show no version line, and import accepts filenames both with and without the segment
@@ -165,64 +164,46 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
165
164
  ![Skills management](./screenshots/skill-manager_en.png)
166
165
 
167
166
  - Lists local skills in three sections — **auto-loaded / manual-only / fully disabled**; same-name shadowing marks both copies, bundled directories are read-only
168
- - Entries start collapsed into one row (name plus source / read-only / annotated badges); the top button expands or collapses every visible entry at once, and clicking an entry's name row toggles that entry alone; an invalid entry keeps its ⚠ and one-click fix visible while collapsed
169
167
  - Two switches edit the SKILL.md frontmatter directly (`disable-model-invocation` / `user-invocable`); changes go live within ~200 ms
170
168
  - Entries with legacy camelCase keys are dropped by the official parser: ⚠ warning + one-click canonical fix
171
169
  - ✨ Fill with AI: pick a model to draft a description (follows the UI language), saved to a plugin sidecar index — **SKILL.md is never modified**; one-click batch fill runs in the host background and can be cancelled. Already-annotated skills are listed separately in the plan and are only overwritten after a "Confirm forced refill" second confirmation (annotating no longer blocks future batch fills forever); completion-log timestamps use your local timezone
172
170
 
173
171
  ### Model configuration
174
172
 
175
- > Model-configuration writes require a host with trackable configuration revisions. Older hosts without this capability remain view-only and writes are explicitly refused. Legacy workbench source and tests are retained as compatibility regression assets; the current UI and host register only the models3 flow. Sensitive authentication headers and historical manual-recovery states must be handled through native DSH configuration/recovery rather than automatically cleared here.
176
-
177
- - A top-level **Model configuration** page: the left sub-tab **Channels & models** (default) plus the right **Subagent routing** (reusing the "Subagent model" block; the sub-tab bar disappears entirely when only one sub-page applies). There is **one interface only** — the old hidden localStorage Simple/Legacy split is retired
178
- - **Vertical accordion**: providers are laid out as cards in two groups (built-in / custom); clicking a title row expands that provider's detail, one card is open at a time, and clicking again collapses it. On narrow screens (≤640px) only the inner padding shrinks — it **never becomes a side-by-side layout**. Switching providers with unsaved edits asks first
179
- - **Channel-first loop**: provider list → "Add custom provider" (id / display name / protocol / address / API key / manual or picked model IDs) → provider detail → model list · independent model detail · separate credential replacement · deletion and latest restore. Creating does not automatically discover models; an entered API key is stored under an independent credential reference after creation is confirmed
180
- - **This page edits only a few provider fields**: display name, address, protocol (editable for custom providers; built-in providers show the currently resolved protocol read-only) and credentials. Provider-level advanced keys — provider defaults, headers, thinkingBudgets, transport/cache, timeouts, image budgets and retryPolicy — have no editor here, and **saving never clears them** (writes merge as field patches; keys that are not shown survive untouched). No second JSON editing mode either
181
- - **Anthropic model discovery**: `anthropic-messages` uses `/v1/models?limit=1000`, accepts base URLs with or without trailing `/v1`, sends `x-api-key` and `anthropic-version`, and reads `display_name`. Only one page is fetched; pagination is not followed automatically. Existing public-address and response guardrails remain in force rather than using the official unguarded fetch path
182
- - **Discovery and manual entry are peers**: the network is contacted only when you click "Fetch online"; results distinguish already-added from addable models and support search plus multi-select batch adds. A failed discovery, an unsupported protocol or a refused probe only reports the reason — **entering ids manually always keeps working**. Listing models does not prove they can be called
183
- - **The per-model panel**: the model ID is **editable** (including rows added from an online discovery list); the value is bounded in three ways — it must match the model-ID format, it may not duplicate another ID on the same provider, and a rename is likewise refused when it collides with an existing ID, and disabled models must be explicitly enabled before renaming. A rename is submitted as "delete the old, add the new", so it still passes the deletion-reference guard. The same panel carries display name, context window / max output tokens (**accepts K/M suffixes such as `256K` or `1M`**; blank means inherit the catalog default), input modalities (the text box is checked and disabled, only image can be toggled; saved as an explicit host-schema declaration), output modalities (display only, text — the pi-ai model profile has no such field) and reasoning levels. The save button sits at the **bottom** of the panel next to the "unsaved" status and is disabled until something changes; submitting writes only the changed fields
184
- - **The advanced model-parameter area has no collapsible header**: input modalities and reasoning levels are always expanded inside model detail, not hidden behind a disclosure title
185
- - **Deleting a model or a provider both check references**: the confirmation first lists the **exact references** — main-chat default model, subagent primary model, subagent fallback model — each as `provider / model`, and any reference blocks deletion. A custom provider cannot lose its final model, and inherited-provider reset is not presented as deletion
186
- - **Enabling and disabling apply immediately, with no second confirmation**: the provider switch in the header and each model-row switch commit as soon as you click them (the host fetches a plan and submits it within the same click; no confirmation dialog follows). The provider switch's hover text states that disabling makes the provider unselectable and blocks new main-chat and subagent calls while keeping configuration and credentials. Disabled state is Profile-scoped, and disabled providers leave composer and subagent catalogs and reject new exact-model calls, including saved routes, explicit overrides and prepared calls not yet dispatched. Already-running calls are not forcibly cancelled. This is plugin policy, not an official `enabled` configuration field; unloading the plugin removes enforcement
187
- - **A basic call test is not a capability audit**: the model-row test action directly tests the saved entry without a second fee confirmation. The host sends fixed short text with a 30,000-token / 120-second ceiling and may incur charges. Elapsed time is shown while running; failures expose sanitized codes, HTTP status, finish reason and details. It verifies only a basic text call, not images, capacity or every reasoning level. A failure never rolls back saved configuration
188
- - **Built-in inheritance is not a gap**: built-in channels inherit capability fields from the catalog (runtime falls back to the catalog base), so "not written in settings" does not mean "missing capability" — you will not be nagged to fill in reasoning levels
189
- - **Manual editor**: id / name / context / max output always visible; the capabilities area is always expanded (two vision switches, six equal-width horizontal reasoning checkboxes (off is not shown); metadata wire mappings are retained, but wire-edit inputs are currently not visible). Clearing a field removes the explicit override to inherit the default, and `reasoningEfforts: false` explicitly declares "not supported" (distinct from omitting it)
190
- - **Deletion keeps the credential and is restorable**: deleting a channel removes only the route from settings and keeps the credential reference (other channels sharing it are unaffected). The full pre-deletion configuration goes into a snapshot first, and the **restore entry stays at the bottom of the list** instead of disappearing with the row. Restore writes the reference back but **never the old key value**
191
- - **Configuration is not a call-time selection**: effort checkboxes declare selectable levels, not the level selected for every call. Unconfigured levels inherit existing values or leave behavior to the API default; they do not automatically disable reasoning. Smart fill retains mappings explicitly declared by metadata. Manually checking a non-off level currently uses its English name as the wire value, whose suitability must be checked. Saving requires at least one non-off mapping
192
- - **Drafts stay on the page**: a failed save or a concurrent conflict never clears the draft, and a conflict asks you to re-confirm against the latest values; leaving with unsaved edits warns first; drafts are never persisted across refreshes (so keys never reach browser storage)
193
- - **Built-in and custom catalogs are handled separately**: built-in providers show the resolved protocol and allow adding out-of-catalog models or editing current Profile model entries. Catalog defaults are displayed as inherited values rather than copied wholesale when unchanged. Unknown overrides and advanced fields are retained; the entire dynamic catalog is not frozen
194
- - **Zero external network on the overview**: reads the current Profile, pi-ai catalog, runtime providers and sidecar, with saved / loaded / basic-call status shown separately. Opening the provider list does not query external metadata
195
- - **Per-model smart fill**: OFF whenever the detail opens. ON queries the local pi-ai index and fixed modelparams.dev / OpenRouter sources, **never provider discovery, inference or automatic save**. **This round is model-ID-scoped filling: it binds no current-provider or protocol source**, and the UI has no source picker or binding action (candidates are evidence only and are never written into configuration)
196
- - **Suggestions shown per parameter**: display name, context capacity, maximum output, input modalities and reasoning levels are shown separately. Modalities are compared as sets, so ordering is not a difference. Suggestions use a stable source order; reasoning prefers explicitly declared mappings without filtering by the current provider, and missing mappings never fabricate levels. modelparams request ranges are not model output limits. Metadata is reference information, not a real API capability test
197
- - **One confirmation to fill**: "Fill all" first lists which draft fields it will replace; confirming **only updates the draft**, writes no configuration and still requires Save. There is no source binding or per-field ✓ / ✕. Hovering the switch explains the sources and operation boundaries
198
- - **The two reasoning notices are distinct**: when the model metadata declares reasoning support but no concrete level suggestions were found, the panel says reasoning is supported yet the level values are unavailable and the API default is unknown; when there is no reasoning metadata at all it says no reasoning level suggestions were found and to configure them manually. **Absent metadata is not "unsupported"**; only `reasoningEfforts: false` declares that explicitly
199
- - **Effort badges use English level names**: current and suggested levels render as `minimal / low / medium / high / xhigh / max` badges regardless of the UI language (the `models.smart.effort.*` Chinese dictionary is retained but not used here); the "Current levels / Suggested levels" labels themselves still follow the UI language
200
- - **Snapshot before mutation**: ordinary save is one click; delete / credential replacement / restore preview consequences before confirmation. Snapshot failure prevents configuration writes. Creation, edits and restore capture owner / full fingerprint / revision and reject concurrent overwrite; subsequent external edits block undo. Ordinary snapshots exclude sensitive headers; deletion is refused if sensitive values cannot be restored in full safely
201
- - **Online verification**: looks a model up in the OpenRouter model directory by id (fixed domain, redirects not followed); results are shown for reference only and never change your config
173
+ ![Model configuration](./screenshots/model-configuration_en.png)
174
+
175
+ - Model configuration contains **Providers & models** and **Subagent routing**, separating built-in and custom providers.
176
+ - **Add custom providers** with an ID, display name, protocol, address, API key and model IDs. Creation never probes the endpoint automatically.
177
+ - **Mixed-protocol notice**: when a catalog mixes protocols (such as pi-ai's `azure` since 0.2.1-alpha.3), the template picker and provider editor explain that a route-level protocol overrides every model on the route, preventing Chat Completions models from being pinned to the Responses protocol.
178
+ - **Provider editing** covers display name, address and credentials. Custom protocols are editable; built-in protocols are read-only. Unshown headers, timeouts, cache settings and advanced parameters remain unchanged.
179
+ - **Model discovery** supports manual IDs or explicit Fetch online, with search and batch selection for OpenAI-compatible and Anthropic model lists. Discovery failure never blocks manual entry; a returned list does not prove calls work.
180
+ - **Model editing** covers ID, display name, context window, max output, image input and reasoning levels. Capacity accepts `256K` / `1M`; blank inherits catalog defaults. IDs must be unique per provider, and disabled models must be enabled before renaming.
181
+ - **Immediate toggles** block new main-chat and subagent calls while retaining configuration and keys; running calls are not cancelled. Unloading the plugin removes this policy.
182
+ - **Basic call tests may incur charges**. They send short text through saved models and report elapsed time and sanitized errors; they do not verify images, capacity or every reasoning level, and failures never roll back configuration.
183
+ - **Smart fill starts disabled**. It queries local pi-ai, modelparams.dev and OpenRouter metadata by model ID, without provider discovery, inference or automatic saving. Suggestions are per parameter; confirming Fill all changes only the draft and still requires Save.
184
+ - **Check reasoning levels against the API**: selectable levels differ from the effort selected for a call. Missing metadata does not mean unsupported; unconfigured values inherit or use API defaults. Manually supplied mappings require verification.
185
+ - **Deletion checks references** to the main-chat default, subagent primary and fallback models, blocking deletion when referenced. A custom provider cannot lose its final model.
186
+ - **Restore and credentials**: provider deletion snapshots configuration and keeps credential references. Restore recovers configuration, never old key values. Replacement defaults to an independent reference; shared replacement first lists users.
187
+ - **Draft and concurrency protection**: failed saves retain drafts, leaving unsaved edits warns, and external changes prevent overwrite until reviewed again. Drafts do not persist across reloads.
202
188
 
203
189
  #### URL and credential safety
204
190
 
205
- - **No input concatenation**: address edits are schema-validated configuration data; entering or saving an address never fetches it. Discovery uses only the **baseURL read from the current host Profile**. Model IDs / names never enter shell commands, filesystem paths or command arguments; metadata requests use fixed host-owned source domains
206
- - **Online discovery guardrails**: HTTPS-only, no userinfo, port empty or 443, IP literals refused, **every** DNS resolution result must be outside private/reserved ranges, redirects refused, the resolved address pinned for a direct connection with the correct Host header (anti DNS-rebinding), 10s timeout, ≤4MB transferred and ≤8MB decompressed. Non-public / plain-HTTP / non-443 endpoints can still be saved and written by hand — they are simply barred from online discovery and labelled "not verified by the plugin"
207
- - **Separate credential management**: replacement values pass over loopback into the system store, never returned, logged or snapshotted. Defaults to a new independent reference, not overwriting shared credentials. Explicit shared replacement previews users; configuration failure reports stored credentials honestly and never deletes them as compensation. Discovery resolves `apiKeyEnv` and sends it only to the current provider address, never metadata sites
208
- - **Short-lived host handles**: editing baselines, deletion, restore and credential consequences bind to `sessionId` / `planId`; submission re-checks the complete baseline and revision. Unknown, expired or replayed handles are rejected. Manual parameters pass a closed schema vocabulary
209
- - **Structural write protection**: writes use `settings.mutate(entryId, ops, expectedRevision)` with the current pi-ai owner's dynamically resolved entry ID; only legacy hosts use `llm-pi-ai`. Complete official validation, atomic persistence and live reconciliation are retained; no direct writes to real configuration YAML
210
- - **Loopback only**: every endpoint is exposed through the single-segment `/dsh-service` channel with `loopback` authority
191
+ - Hosts without trackable configuration revisions remain view-only and refuse writes; configuration changes retain official DSH validation and atomic saving.
192
+ - Entering or saving an address never fetches it. Discovery uses the saved current-Profile address and requires public HTTPS on port 443, rejecting private addresses, IP literals and redirects, with timeout / size limits. Local or HTTP providers can still be saved with manual IDs.
193
+ - Keys pass over loopback into the DSH credential store, never echoed, logged or snapshotted. They go only to the corresponding provider, never metadata sites.
211
194
 
212
195
  ### Subagent model
213
196
 
214
197
  ![Subagent model](./screenshots/subagent-model_en.png)
215
198
 
216
- - Three modes: **Default** (no override) / **Follow main model** (the provider/model actually used by the latest main-conversation request) / **Custom** (pin every unspecified subagent)
217
- - A provider/model explicitly carried by the delegation always wins; pinned presets are never overridden
218
- - Custom mode optionally selects a **reasoning effort**: the dropdown appears only for the exact provider/model when its adapter declares selectable levels; leaving it empty means "use the target model default", materialized by the adapter
219
- - Values come from adapter metadata (`reasoning.efforts[].id`); effort ids are opaque to the host. Models with no declared levels disable the dropdown and show a hint
220
- - **inherit / follow / feature gate off** inject no provider, model, or reasoning effort at all; subagents that carry an explicit provider/model are unaffected
221
- - **Mode switching keeps the config** — the saved custom model (including its reasoning effort) and the fallback list survive switching between the three modes; a mode only decides whether the route applies, so switching back to "Custom" needs no re-selection (provider/model missing from the runtime catalog fall back to the first catalog entry)
222
- - **No shift on entry** — the page seeds its first frame from the most recent successful read, so it lands directly on the real mode instead of showing "Default" and then jumping to "Custom"; on a fresh install or a different browser (no cache) it briefly shows "Reading configuration…"
223
- - **Fallback models (in order)** — both Follow and Custom modes accept an ordered fallback list: when the primary route is unavailable (channel unloaded, or quota state marks it unserviceable), the next model is tried in order; if none works, delegations fall back to native inheritance instead of failing. Fallback entries pass the same allow-list check as the primary route, with an optional reasoning effort per entry
224
- - **Conversation-page visibility** — a session-level line sits under the composer listing the models your subagents actually ran on, e.g. `Subagents: cpa/gpt-5.6-luna (xhigh) · opencode-go/deepseek-v4-flash (max)` — covering fallback hits, explicit routes, and inherited sources, so you can verify the custom route at a glance. The line is mounted on the official `conversation.composer.dock` slot, occupying its own line right below the official stats instead of sharing their row; 20s refresh, independent of turn data (it keeps showing even when compaction folds the delegation tool calls), covered from the host dispatch records and visible in any view; switch it off independently from Model configuration → Subagent routing. Records live in host memory (survive page reloads, cleared on process restart). Works regardless of the official "assign subagent models" switch
225
- - Config stored in `$DSH_HOME/dsh-service-subagent-route.json` (atomic writes, `0600`); one-click reset
199
+ - Three modes: **Default** (no override), **Follow main model** (the latest model actually used by the main chat), and **Custom**. Entry: Model configuration → Subagent routing.
200
+ - Explicit provider/model values in a delegation always take priority.
201
+ - Custom models with declared reasoning levels offer an effort selection; blank uses the target default. Models without declarations explain that selection is unavailable.
202
+ - **Mode switching preserves configuration**: custom model, effort and fallback list remain saved when switching away and back.
203
+ - **Ordered fallbacks** in Follow and Custom modes handle unavailable channels or quota. If all routes are unavailable, use native inheritance.
204
+ - **Independent Agent preset** (0.2.1-alpha.3+ hosts): give every spawned subagent one independent preset (role and tools together); leave empty to follow the parent, explicit presets win, and only backends that support independent presets are affected.
205
+ - An optional composer line shows the models actually used by the session's subagents, including fallback and explicit routes. Records survive page reloads, not process restarts.
206
+ - Configuration lives at `$DSH_HOME/dsh-service-subagent-route.json`, with one-click reset.
226
207
 
227
208
  ### Task notifications
228
209
 
@@ -237,49 +218,36 @@ Under **Plugins → Plugin configuration**, thirteen host-level switches: **Heal
237
218
 
238
219
  ![Mobile adaptation](./screenshots/mobile-adaptation_en.png)
239
220
 
240
- - Off by default; active only below a 1024 px viewport (phones / narrow windows), desktops unaffected
241
- - Sidebar becomes a drawer, details column is hidden on mobile (matching the official narrow-screen behavior), modals become full-screen panels, settings left nav a horizontal top strip
242
- - The official Plugins panel header never wraps its text on phones: when "title + intro" and "refresh / Add plugin" no longer fit one row, the whole toolbar drops to a second row flush right, keeping the intro and the button label each on a single line; this also fixes the old drawer-button clearance rule that rewrote this page header's vertical/right padding (it used to squeeze the intro into two lines with an orphan character and wrap the "Add plugin" label inside its pill)
243
- - The model selector follows dynamic untruncated measurement with persistent reasoning effort: removes the 45cqw clamp so measurement reflects natural unclipped text; when space allows, it shows the brand icon, full model name, and reasoning effort without truncation; when space is tight (narrow screens, long names, or tools added by other plugins), the model name collapses into compact form while the reasoning effort (e.g. High / Low) and brand icon remain persistently displayed ([brand icon] High ▾), models without reasoning effort collapse into a sleek 45px icon button, with zero wrapping and zero awkward clipping; on mobile the "Search models…" input row is hidden to prevent autoFocus from forcing up the mobile on-screen keyboard, presenting the model list directly
244
- - The stats line under the composer ("turns/steps · tok/s | tok · cache hit") **stays on one line, centred and straight** on phones: this row's own horizontal padding is tightened (official 32 px → 2 px) along with its gaps to free up width, and the official `justify-content` centres both chips — zero truncation from ~420 px up, proportionally less text cut than the official fixed truncation below that, with no wrapping and no horizontal scrolling; this also works on older DSH (on 0.1.5-rc.2 the row spans the full width, where the earlier sub-item `flex-grow` pushed both chips as a group to the left — they no longer grow) (desktops unaffected)
245
- - The session header is re-flowed on phones (≤560 px): the title row keeps only the **title + preset chip** (the chip hugs the "…" button on the right, the title stays nearly fully visible), while the "N subagents" and "N background jobs running" counter chips park as a pair on the right of the Conversation/Trajectory tab row (tab gap 36→20, tapping a chip still opens its menu); the official crumbs **hard-clip** is eliminated, and layouts ≥561 px keep the official flow. From DSH 0.1.7-rc.1 the official preset label hides itself via `@container (width<=540px)` (the container is the title row, roughly viewports up to 606 px), so the mode chip vanished on every phone; the plugin overrides that back to visible across the whole mobile range (from 390 px up the title stays untruncated, only 320–375 px trade away 15–70 px of title as an ellipsis)
246
- - The Agent Team panel stays on screen on phones (<1024 px): the official panel is left-anchored (`left:0`), so sitting in the header's right-hand action slot pushes it off the right edge (measured 112–352 px past the viewport at 320–1023 px). On mobile the session header becomes the positioning context and the panel is re-anchored to the **right with a 16 px inset** and capped at `100vw − 32px`, leaving 16 px on each side; desktop ≥1024 px keeps the official geometry byte-for-byte
247
- - Scroll immersion: inside a conversation, swiping down auto-hides the header, the top-left drawer icon, and composer for full-screen reading (the top-left drawer icon slides up and fades out in sync with the top-right sidebar button, and the composer yields its layout space so the transcript really fills the screen; on reveal, if you are still at the end of the conversation it re-aligns to the bottom). Swipe up, tapping the official "Back to bottom" button, or focusing the composer brings them back (no extra floating button); already sitting at the end of the conversation, a single small backward nudge reveals it right away (no full threshold needed), and after a back-to-bottom tap the resting position gets one extra bottom snap. Programmatic scrolling (streaming pinning, anchor jumps) never triggers it
248
- - Swipeable drawers: with both drawers closed, a horizontally dominant swipe rightward anywhere opens the sidebar drawer and a swipe leftward anywhere opens the official right sidebar (DSH 0.1.5+; stays inert when unavailable); once open, a swipe in the reverse direction anywhere closes them. Swipes starting at the screen edge additionally benefit from stolen-gesture completion for browser edge navigation, and horizontal scrolling inside editors never misfires
249
- - The "Back to bottom" button is shifted flush right on mobile (no more large empty strip); a matching circular up-arrow now sits above it on all platforms, jumping to the previous user message on each click for step-by-step back navigation. Targets outside the loaded history auto-trigger the official "load earlier" action; the button hides once you reach the very top and reappears when you scroll back down
250
- - Transparent large-JSON compression (≥4KB auto gzip/brotli per `Accept-Encoding`) speeds up long session histories
251
- - Adds `viewport-fit=cover` with safe-area avoidance, disables double-tap zoom, keeps inputs ≥16px against iOS focus zoom
252
- - `?dshsvc-mobile-debug=1` shows a floating diagnostics chip (debugging only)
221
+ - Off by default; enabled behavior applies only below a 1024px viewport, leaving desktop unaffected.
222
+ - Supports sidebar drawers, narrow-screen settings navigation and full-screen panels, with model selection, task menus and Agent Team dialogs adapted for phones.
223
+ - **Scroll immersion** hides the header and composer when scrolling down; scroll up, go to bottom or focus the composer to restore them. Streaming updates never trigger it.
224
+ - **Drawer gestures**: swipe right to open the sidebar, left to open the right sidebar, and reverse to close. Editor horizontal scrolling is unaffected.
225
+ - An up-arrow jumps through previous user messages, automatically loading earlier history as needed.
226
+ - Large conversation JSON responses are compressed, with safe-area and mobile input zoom handling.
253
227
 
254
228
  ### Session manager
255
229
 
256
230
  ![Session manager](./screenshots/session-manager_en.png)
257
231
 
258
- - **View**: one unified list for sessions (running / cold / archived) with status badges, workspace, event count, and size; the list sorts by creation time (newest/oldest first) or by title, or **by project** — grouped into one section per workspace (each section header shows the path and session count; newest first within a project; sections are **collapsed by default** and expand/collapse on header click); **starts on the “Archived” view by default**, and each of the All / Archived / Deleted filters fetches its own subset from the host **once,** then keeps it in a **module-level cache** — switching filters sends no requests, and **closing and reopening the panel renders the cache instantly while quietly refreshing the current view once in the background** (only a page reload clears the cache), with a “Refresh” button for a forced refetch of the current view; normal lists also provide a “Select multiple” button; in that mode, clicking anywhere on a session row selects or clears it without requiring a precise checkbox click, while one-click select-all / clear-all remains available for the current filtered result, and selected rows use a slim brand-colored edge without replacing their background; the toolbar shows eligible counts and runs batch export / archive / delete actions (changing filters, searching, or opening details exits selection mode automatically); sizes are never shipped with the list — each row fetches its size lazily (double-cached in the module and in host memory: reopened panels and refreshed pages reuse it, cleared on delete); the detail page walks events as paged cards (single-slot host snapshot cache: paging and reopening the same session never re-reads the log, live sessions stay fresh within 30 seconds), with **event bodies rendered as official Markdown** (reusing the platform renderer `MarkdownText`, same look as the chat UI: code blocks, lists, tables, math — raw HTML and unsafe links are rejected by default; older DSH shells without the renderer automatically fall back to plain text), and consecutive system events **and tool messages each collapse into their own countable block by default** (tool messages = `tool/call`, `tool/result` and the other `tool/*` events, plus assistant messages that carry nothing but tool calls — those dominate real long sessions, so tool arguments no longer flood the detail page); click a collapsed line to expand the details and click again to collapse; when a search hit falls inside a collapsed block that block opens automatically and keeps the hit highlighted; **entering a detail remembers the list scroll position and returning to the list drops you back exactly where you were** (reusing the official panel's scroll container; changing the filter or search while in the detail discards the restore)
259
- - **Subagent recognition**: sessions delegated to subagents carry a “Subagent” badge (the marker comes from official session header fields: `origin=subagent` is the product classification, with `delegationDepth` as the fallback for older logs; a plain fork lineage carrying only `parentSession` does not count); a “Subagents only” checkbox in the search row focuses the list (same row and widget language as “search archived only”; an orthogonal qualifier layered on top of All / Archived — hidden in the Deleted view, and its state survives view switches), and batch mode offers “Select subagents” to fold every visible subagent session into the selection (without clearing existing picks; the button stays hidden when no subagent rows are visible) for quick batch archive / delete cleanups; on older plugin hosts that do not ship the flag the list behaves as before, and enabling the qualifier explains that the host is outdated and carries no subagent flags instead of showing an unexplained empty list
260
- - **Export**: one-click or batch download through the official export path (one full ZIP per session, including subagents and attachments) — the host never assembles a package itself
261
- - **Archive & unarchive**: archive one or many non-running sessions, hiding them from the official sidebar; hosts running DSH ≥0.1.6 support unarchiving individually or in batches, while older DSH hosts keep the one-way notice
262
- - **Content search**: full-text semantic search over conversations (case-insensitive, whitespace-flexible) with cross-session hits (matched text is highlighted; multiple matches show seq chips for one-click jumps) → **hit-window view**: opening a result centers a context window on the matched seq (15 events on each side; the matched event gets a HIT badge, is highlighted, **auto-scrolled into view and flashes for 2 seconds**), with **previous / next match** navigation and navigator seq chips for direct jumps (mirroring dsh-session-kb's Locate interaction); the window can keep loading later events; optionally restricted to the archived zone
263
- - **Delete**: Only archived sessions can be deleted, and a session that becomes live is rejected again immediately before execution; the two-phase confirmation shows its id / title / workspace / size, persists the deletion record atomically first, and only then removes the log directory; deleted records stay visible under the Deleted filter, with support for single-item clear or batch multi-select / select-all clear (two-phase confirmation, permanently removes from record list); a successful delete notifies the official side immediately — it re-emits the official session-removed event and clears the dead id from the archive set — so the official sidebar and the “Archived sessions” Settings page reflect the new state without a browser reload
264
- - Entry: the “Sessions” subpage under “Maintenance” (on by default); the optional settings-sidebar entry is off by default
265
- - Delete records live at `$DSH_HOME/dsh-service-sessions-deleted.json` (atomic write, `0600`, title/time only — no content, not recoverable)
232
+ - **Browse** running, cold and archived sessions with workspace, event count and size. Sort by time / title or group by project; Archived is the default view.
233
+ - **Details** offer paged Markdown transcripts and collapsible system / tool events; returning preserves list scroll position.
234
+ - **Subagents** can be filtered or selected in bulk; plain forks are not misclassified as subagents.
235
+ - **Export** complete official ZIPs individually or in batches, including subagents and attachments.
236
+ - **Archive and unarchive** non-running sessions individually or in batches; unarchiving requires DSH ≥0.1.6, with older hosts remaining archive-only.
237
+ - **Content search** spans sessions with case-insensitive, whitespace-flexible matching; optionally restrict to archived sessions, jump into match context and navigate previous / next hits.
238
+ - **Delete**: Only archived sessions can be deleted. Running sessions are checked again and refused; two-step confirmation lists ID, title, workspace and size, then updates the official sidebar.
239
+ - **Deleted records** can be viewed and cleared individually or in bulk. They contain title / time only, no transcript, and cannot restore a session.
240
+ - Entry: Maintenance → Sessions; the optional settings-sidebar shortcut is off by default.
266
241
 
267
242
  ### Model provider icons
268
243
 
269
- - The **model button in the composer shows the current provider's brand icon**: on wide screens (>480px) it is **prepended to the model name**; on phones (≤480px, where the official UI collapses the name into an icon) it **replaces** the official generic icon
270
- - **Providers with no matching icon keep the official default icon untouched** (nothing prepended on wide screens, official icon unchanged on phones) — never a blank or a wrong logo
271
- - Covers **38 of pi-ai's 40 built-in providers** (`ant-ling` and `radius` have no matching brand mark, so they fall back), plus commonly used routes: Ollama, vLLM, LM Studio, Perplexity, Cohere, Volcengine, Doubao, Hunyuan, Yuanbao, StepFun, SenseNova, Baichuan, 01.AI, Fal, Replicate, Midjourney and more — 62 marks across 76 provider mappings; regional and billing variants (e.g. `xiaomi-token-plan-*`, `qwen-token-plan-*`) share one brand mark
272
- - **Custom route names are recognised by prefix/alias**: `opencode-goo` → opencode, `openrouter-f` → openrouter, `zai-coding-cn` → Zhipu, `xiaomi-token-plan-cn` → Xiaomi, `command-goat` → Command Code
273
- - **Unrecognised names fall back to the quota manual adaptation**: when a channel matches neither the built-in table nor any prefix rule, but you have **manually adapted** it in the quota page to some kind (`cpa` → CLIProxyAPI, or any relay → OpenRouter / Zhipu / Command Code / StepFun…), its brand mark is shown; changing the adapted kind swaps the icon immediately, and un-adapting falls back to the official default icon. Two premises: only **manual** adaptation counts (a kind merely auto-inferred from the baseURL does not), and **the channel name always wins** when it matches (`openrouter-f` keeps showing OpenRouter even if you adapt it to something else)
274
- - **The CLIProxyAPI icon appears on demand**: the hand-drawn "concave diamond + mirrored swirl" mark shows on the model button **only after you have manually adapted some provider as CLIProxyAPI in the quota page** (regardless of the provider's name — `cpa` or a custom one alike); it is one instance of the fallback rule above, and un-adapting immediately falls back to the official default icon
275
- - **Quota cards carry the icons too**: adapted provider cards show the same brand mark before the channel name (14px; colour tiers keep their brand colour, mono tiers follow the theme text colour). The same "fall back to the manual adaptation" rule applies here — the card list is exactly where you adapt, so the mark appears/swaps immediately
276
- - **The model picker's group headings carry them too**: open the model button → the "Model" list, and each **provider/channel group heading gets the same mark in front of it** (13px, matching the 12px heading scale). Same resolution and fallback rules: unrecognised groups keep the official heading untouched, with no icon added
277
- - **Legible in both light and dark themes**: a brand colour is kept only when its contrast is adequate against **both** a light and a dark background; otherwise the icon automatically switches to a monochrome variant that follows the theme's text colour — so you never get a black logo on a dark background, or a nearly invisible one in light mode
278
- - **Zero runtime network requests**: icons are inlined into the client bundle at build time, so it works offline, needs no CSP exceptions, and never leaks your provider names to a third party
279
- - The icon is sized to match the **quota ring** beside the composer (each mark's viewBox is tightened to its real drawn extent and squared at build time, so every brand reads at the same visual size instead of some filling the box and others shrinking)
280
- - Toggle under Plugins → Plugin configuration → Interaction (on by default, applied live)
281
- - Icons come from the MIT-licensed [LobeHub Icons](https://github.com/lobehub/lobe-icons) (pinned to `@1.95.0`); the Xiaomi icon uses the plain "mi" mark from CC0 [Simple Icons](https://github.com/simple-icons/simple-icons) (LobeHub's version is a two-line "Xiaomi / MIMO" wordmark that turns to mush at 15px). **Brand marks remain the property of their owners**, so review each vendor's brand guidelines before public-facing use
282
- - **Every icon is browsable in one page**: [icon catalog](docs/model-icons.html) — 62 marks, 76 provider mappings, the matching rules, and a light/dark comparison; a single offline file, generated from the very data inlined into the client bundle
244
+ - Provider marks appear in model buttons, model lists and quota cards; unknown providers retain the official default icon.
245
+ - Covers common built-in pi-ai providers plus Ollama, vLLM, LM Studio, Perplexity, Cohere, Volcengine, Doubao, Hunyuan, StepFun, Cline and others.
246
+ - Custom names are recognized by prefixes / aliases such as `openrouter-f`, `zai-coding-cn`, `command-goat` and `cline-official`.
247
+ - Unknown names can use the adapter type **manually chosen** on the quota page. Name matches take priority; CLIProxyAPI icons require manual adaptation.
248
+ - Icons are bundled, work offline and never send provider names to third parties. Toggle under Plugin configuration → Interaction, on by default.
249
+ - Sources: [LobeHub Icons](https://github.com/lobehub/lobe-icons) (MIT) and [Simple Icons](https://github.com/simple-icons/simple-icons) (CC0). Brand marks belong to their owners.
250
+ - See the [icon catalog](docs/model-icons.html) for all marks and provider matching rules.
283
251
 
284
252
  ### Right-Sidebar file editing
285
253
 
@@ -349,7 +317,7 @@ Key contracts:
349
317
  1. Open the **Plugins** page in the left sidebar.
350
318
  2. Click the **+ Add plugin** button in the top-right corner.
351
319
  3. In the dialog, fill in **one of the three** source forms (the same field accepts all three, and the hint below it follows what you type):
352
- - **npm package name (recommended)**: `@gehennawu/dsh-service@2.0.8` — **include the exact version** (written as `package@version`);
320
+ - **npm package name (recommended)**: `@gehennawu/dsh-service@2.0.9` — **include the exact version** (written as `package@version`);
353
321
  - **GitHub repository URL**: `https://github.com/gehennawu/dsh-service` (public repository, no token needed; the shorthand `github:gehennawu/dsh-service` works too);
354
322
  - **Local plugin directory**: an absolute path on this machine, e.g. `/path/to/dsh-service` (for your own or pre-downloaded plugins).
355
323
  4. Pick the **install source** for your network: the default **official npm registry**, or the **China mainland mirror** if fetching from npm is restricted there (it affects package-name installs only; GitHub URLs and local directories are unaffected).
@@ -360,17 +328,17 @@ Key contracts:
360
328
 
361
329
  > Tip: all three forms go through the official installation path — no manual profile edits; entering a GitHub URL in the dialog is equivalent to the `github:` prefix on the command line.
362
330
 
363
- > **Why the npm form wants a version**: pnpm 11 enables a supply-chain release cooldown by default (`minimumReleaseAge`, 1440 minutes). **Without a version** (including `@latest`), a release published less than the cooldown period ago is not installed automatically, so the package manager falls back to the newest version that has cleared it — which is why the plugin page may report that it installed x.y.z instead of the latest a.b.c. Writing an **exact version** (e.g. `@gehennawu/dsh-service@2.0.8`) installs exactly the release you name. The cooldown is a supply-chain safety policy, not a sign that the new release is broken; if you are not in a hurry, installing without a version after the 24 hours have passed also reaches the new release.
331
+ > **Why the npm form wants a version**: pnpm 11 enables a supply-chain release cooldown by default (`minimumReleaseAge`, 1440 minutes). **Without a version** (including `@latest`), a release published less than the cooldown period ago is not installed automatically, so the package manager falls back to the newest version that has cleared it — which is why the plugin page may report that it installed x.y.z instead of the latest a.b.c. Writing an **exact version** (e.g. `@gehennawu/dsh-service@2.0.9`) installs exactly the release you name. The cooldown is a supply-chain safety policy, not a sign that the new release is broken; if you are not in a hurry, installing without a version after the 24 hours have passed also reaches the new release.
364
332
 
365
333
  ### Option 2: Command line
366
334
 
367
335
  | Method | Command |
368
336
  | --- | --- |
369
- | npm (recommended) | `dsh plugin --profile web add @gehennawu/dsh-service@2.0.8` |
337
+ | npm (recommended) | `dsh plugin --profile web add @gehennawu/dsh-service@2.0.9` |
370
338
  | GitHub | `dsh plugin --profile web add github:gehennawu/dsh-service` |
371
339
  | Local development | `dsh plugin --profile web add link:/path/to/dsh-service` |
372
340
 
373
- The command line uses the default npm / pnpm registry; if you need a mirror, point npm / pnpm's registry at one the usual way first, then run the command above. The npm method is subject to the same pnpm release cooldown: without a version it installs the newest release that has cleared the cooldown, while an exact version (`@gehennawu/dsh-service@2.0.8`) installs that release (see the note under Option 1).
341
+ The command line uses the default npm / pnpm registry; if you need a mirror, point npm / pnpm's registry at one the usual way first, then run the command above. The npm method is subject to the same pnpm release cooldown: without a version it installs the newest release that has cleared the cooldown, while an exact version (`@gehennawu/dsh-service@2.0.9`) installs that release (see the note under Option 1).
374
342
 
375
343
  Restart DSH Web after installing or updating:
376
344
 
@@ -422,11 +390,10 @@ pm2 start "dsh web --host 127.0.0.1" --name dsh-web
422
390
 
423
391
  Requirements: Node.js `>=22`, and a DSH Web installation capable of loading both Host and Client plugin halves. Update checks require access to `registry.npmjs.org`; network failures do not affect other features.
424
392
 
425
- **DSH compatibility statement (alpha.1 baseline)**: adapted to DSH `0.2.1-alpha.1` — session format V4 (V3 logs are migrated by the official layer into a `session.v4.jsonl.zstd` generation file on first read, with the old `session.v3.jsonl.zstd` retained per the format catalog's policy; the detail view automatically archives system events), the `SettingsForms` configuration surface (plugin config now lives in the Profile's `cordis.patch.yml` with hot updates over `loader/volatile-update`, while the legacy `settings.register` remains authoritative on 0.1.5/0.1.6 so the two never cross-contaminate on a shared host), and all existing V3 adaptation (the `system/message` history, the handle-based sessionPersistence, the official right sidebar, the object-shaped turn-process), plus dual-hash compatibility for mobile bottom-row triggers, subagent turn-tail list-slot adaptive compatibility, `plugins.bundle.config` slot injection, and session-detail open fallback through `uiWorkspace`. Older DSH releases (`>=0.1.1-rc.2`) remain supported. The alpha.1 review established the previous declared range through `0.2.1-alpha.1` (then bounded by `<0.2.1-alpha.2`); source-level, npm-artifact-level (per-source-module sha256 via rolldown `//#region` slices) and real-host mounting reviews of 0.2.1-alpha.1 (266 commits) confirmed that 17 of the 20 existing seams are region-identical and the 3 changed ones do not touch this plugin's contracts, that the 9 slot keys, 25 theme tokens and the 35 official `data-*` hooks this plugin uses show zero removals, and that host RPC, `webServer`, the settings surface, persistence and session-format artifacts are byte-identical; the removed runtime invariant mechanism (each package's `./invariant` export 38->0, `dsh-invariants` retired) is unreferenced here, and the upstream fix for toggling a plugin removing other plugins' styles benefits this plugin. **The single display-level break is adapted in dual mode**: upstream split the composer stats row into `activity`/`usage` entries and dropped the row root class `.root`, so the mobile rules keep the legacy `[class*="bOPqQW_root"]` anchor for older hosts and add the official `[data-composer-stat]` hook plus the <=375px font step for the new host; the 0.2.0-rc.2 MenuGroup group-heading dual selector remains effective and the Client Inspect hang defect stays fixed as of 0.2.0-rc.2. The plugin-version gate introduced in rc.1 reads only `peerDependencies` (this plugin declares none, so the gate does not apply); real-host mounting on DSH `0.2.1-alpha.1` is operational. The settings surface and the persistence/layout seams all run in dual shapes detected from runtime capabilities, and adaptation items that target newer structures are naturally inert on older hosts (cosmetic only, no functional loss). Note: sessions written after upgrading cannot be read by older DSH releases — **backups do not restore across a version downgrade**. The plugin marketplace judges compatibility from the `engines.dsh` range in `package.json`, which is the single declaration of the supported range.
393
+ **DSH compatibility**: `>=0.1.1-rc.2 <0.2.1-alpha.4`, adapted through `0.2.1-alpha.3`. Older DSH cannot read newer session formats; **backups cannot be restored across a downgrade**.
426
394
 
427
- **DSH 0.2.1-alpha.2 compatibility update**: the supported range now includes `0.2.1-alpha.2` (`engines.dsh`: `>=0.1.1-rc.2 <0.2.1-alpha.3`). Subagent startup supports both the new `startActivation(spec)` and older `start` / `startContinuable` methods. Mobile task-chip CSS handles the removal of `.QsffPG_root` and the switch to a body-portaled task menu. On the current alpha.2 Web host, the plugin RPC, `available:true` subagent seam, real `subagent` and `subagent_fork` executions and their routed dispatch records were verified; legacy and new API tests pass. With an actual background task on alpha.2, the mobile chip geometry was checked at 320/360/390/560px, its in-flow fallback at 561px, and the portaled menu was opened twice at 390px without crossing the viewport. At 320px the task-count text is fully elided, but the icons and complete `aria-label` remain. **A real older-host UI comparison and other tab counts remain unverified.**
428
395
 
429
- **DSH Desktop (Electron) statement**: the desktop app reuses the same Web frontend and the same client plugin graph (`platform: web`), so the plugin loads as usual and almost everything works unchanged; only "restart" and "one-click upgrade" differ in meaning. The host is owned by the desktop app, so **restart never calls `process.exit(42)`** there (the shell treats any non-zero exit as a host crash, shows a native failure dialog and does not relaunch it; there is no official programmatic restart RPC) — the Restart button, the `/restart` command and the post-restore restart all guide the user to quit and reopen the desktop app instead; **one-click upgrade is greyed out on the desktop** and points at the app's own plugin manager (the bundled CLI cannot boot or mutate `profiles/desktop`, so the upgrade endpoint short-circuits with `desktop-managed-upgrade` and spawns nothing); the Health diagnostics "Runtime environment" row reads "Managed by DSH Desktop (Electron)". The signal is `process.versions.electron` inside the host process (measured on the real Windows desktop build: `44.0.0`, with `ELECTRON_RUN_AS_NODE=1` and non-TTY stdio); non-Electron desktop wrappers can declare `DSH_SERVICE_RUNTIME_ENV=desktop`. Task notifications **do work on the desktop app** (the permission is granted and a real system notification has been observed); the only boundary is that **clicking a notification cannot bring the window back** (closing the window hides it and `dshDesktop` exposes no window API — use the tray). macOS / Linux desktop builds are not verified.
396
+ **DSH Desktop**: restart and post-restore restart require quitting and reopening the app; upgrade through its own plugin manager. Notifications require system permission, and clicking one may not restore a hidden window—use the tray. Other platforms are best-effort, without compatibility guarantees.
430
397
 
431
398
  ## 🔒 Security design
432
399
 
@@ -516,7 +483,7 @@ Concurrency protection kicked in: SKILL.md was just modified by an external edit
516
483
  <details>
517
484
  <summary><strong>The plugin page installed the plugin, but the version is an older one?</strong></summary>
518
485
 
519
- pnpm 11 enables a supply-chain release cooldown by default (`minimumReleaseAge`, 1440 minutes): **installing without a version** (including `@latest`) only selects the newest release that has already cleared the cooldown, so a freshly published release is skipped and the official plugin page reports that it installed x.y.z instead of the latest a.b.c. To get the new version right away: uninstall it in the plugin page and reinstall with `package@version` (e.g. `@gehennawu/dsh-service@2.0.8`), or use this plugin's **Service Control → Upgrade** — it installs an exact version and is not subject to the cooldown. The cooldown is a supply-chain policy (a fresh release may contain defects that are not known yet); after the 24 hours have passed, installing without a version also reaches the new release.
486
+ pnpm 11 enables a supply-chain release cooldown by default (`minimumReleaseAge`, 1440 minutes): **installing without a version** (including `@latest`) only selects the newest release that has already cleared the cooldown, so a freshly published release is skipped and the official plugin page reports that it installed x.y.z instead of the latest a.b.c. To get the new version right away: uninstall it in the plugin page and reinstall with `package@version` (e.g. `@gehennawu/dsh-service@2.0.9`), or use this plugin's **Service Control → Upgrade** — it installs an exact version and is not subject to the cooldown. The cooldown is a supply-chain policy (a fresh release may contain defects that are not known yet); after the 24 hours have passed, installing without a version also reaches the new release.
520
487
  </details>
521
488
 
522
489
  <details>