# Changelog All notable changes to dsh-provider-pro. ## [0.6.2] - 2026-10-07 ### Fixed — reasoning-effort auto-fill can no longer swallow a live model-add Newly added custom models sometimes showed no thinking-level dropdown until DSH was restarted. The startup pass filled `reasoningEfforts`, but a fill triggered while the previous pass was still in flight (e.g. the startup fill running as the user adds a model) was dropped by a boolean in-flight guard, leaving the new model without `reasoningEfforts` until the next boot. - The fill consumer now serializes through a promise **chain** instead of a boolean flag: every fill request eventually executes, in arrival order, so a busy chain no longer swallows a queued fill. - A periodic safety-net recheck now re-runs `fill()` (alongside the existing probe recheck), so a fill missed by a dropped/raced `settings/document-updated` event self-heals within the recheck interval. ### Verified - `scripts/smoke-provider-pro.mjs` gains a regression case that holds a `settings.mutate` to simulate a fill in flight, then bumps a model-add event: the queued fill still lands and writes `reasoningEfforts` for the newly added model. - Live repro on DSH Desktop 2.0.17: after adding a new custom model, its `reasoningEfforts` appears in `cordis.patch.yml` immediately (no restart) and the chat model picker shows the thinking-level dropdown. ### Added — dual-version compatibility seam (`src/compat.ts`) The plugin now targets DSH **0.1.7-rc.2 (Desktop 2.0.15)** and **0.2.0-rc.2 (npm latest)** from one codebase. SHA-256 over the shipped `lib/index.js` shows the contract surface is identical between them: | package | 0.1.7-rc.2 vs 0.2.0-rc.2 | | --------------------------- | ------------------------------- | | dsh-settings | **IDENTICAL** (544 lines) | | dsh-api-settings-controller | **IDENTICAL** (510 lines) | | dsh-llm | **IDENTICAL** (2390 lines) | | dsh-credentials | **IDENTICAL** | | dsh-attachment | **IDENTICAL** | | dsh-typert-protocol | **IDENTICAL** | | cordis | 4.0.4 in both | | dsh-llm-pi-ai | differs (2635→2639) | | dsh-config-editor | differs (132→138) | Neither differing package touches a surface this plugin uses: pi-ai's diff is builtin-provider compat tables plus anthropic response-model bookkeeping (`compat.supportsDeveloperRole` stays declared as `z.boolean()`); config-editor only optimizes `list()`'s `inherited` field — the write path (`edit()`) is byte-identical and this plugin reads neither `base` nor `inherited`. So the strategy is **convergence, not version branching**. `src/compat.ts` is the single seam both halves import: - `rowsOf()` normalizes EVERY settings shape seen in the wild — the host service's bare row array, the RPC `{ writable, hasDocument, namespaces }` wrapper, and the client's `{ ok, value }` result envelope — degrading to "no rows" instead of throwing on an unknown future shape. - `mutateRow()` owns the compare-and-set lost-update retry for both halves (host service calls and client RPC calls differ only in sync/async and throw/return, both awaited and both handled). - `subscribeSettings()` subscribes under **both** settings event names (`settings/document-updated` and the pre-0.1.7 `settings/updated`); a name nothing emits is inert, so an older host is never silently missed. - `layerOf()`/`rowOf()`/`SettingsRow`/`SettingsPathOp` are now the one vocabulary shared by host, client, and the client's wire types — the host half and the bundle can no longer drift on row or op shapes. ### Fixed - **Auto-fill could double-count `backfilled`.** The count incremented inside the CAS mutator, which re-runs on a lost-update retry; the count is now computed locally per attempt and committed only after the write lands. - **Two timers outlived their probe.** The 100s budget guard was only cleared on the success path (a throwing probe left it running), and the 20s wire body-read cap was never cleared when the body won — both now end with their link (`finally`), so no stale timer holds the event loop. ### Changed — review-driven refactor (behavior-preserving) - **One tuning surface.** Every scattered millisecond literal (wire header budget and its retry, body cap, discovery timeout and cache window, stream budget, first-token gate, host budget, client wait cap, debounce, recheck, startup poll) moved into `PROBE_TIMING` in `shared.ts`, with the load-bearing relationships documented (client cap > host budget; header vs body budget). The user-visible strings they feed still render the same numbers. - **Image verdict classification extracted** into one `classifyImageFailure()` — the two near-duplicate regex pairs (error-finish path and thrown-error path) are now one function. - **One probe result shape.** The success and failure returns of `runFullProbe` were two nearly identical object literals; they are now one literal with the outcome-specific fields spread conditionally, producing the same wire keys. - **Client deduplication.** `userSectionOf`/`layerOfRow`/`mutateFresh` are gone in favour of the shared compat helpers; the last two `namespaces.find(...)` reads became `rowOf(...)`. - **Dead code removed.** The unreachable pre-0.5.0 `capabilities` probe branch (the shipped host only ever answers the full probe) and the unread legacy `ProviderProfile.userAgent` field. - **Probe image has one source** (`shared.PROBE_IMAGE_BASE64`); the live measurement harness now imports it from the built module instead of duplicating the base64 blob. ### Verification - `tsc --noEmit` clean (host + client halves). - 7 smoke blocks pass: UA patch (origin + path boundary, own-entry storage), auto-fill (fill / keep explicit / stay stable), CAS conflict retry, master switch suppression, UA boundary safety, **compat seam shape normalization and dual event names**, **legacy-event wake**. - Live gateway (`http://10.10.10.5:8317/v1`): discovery `200` in ~0.1s; `AMD/DeepSeek-V4-Flash` baseline `200` (alive), developer-role `200` (admitted), image POST `400` with an explicit capability refusal — the image path ran and classified it as `unsupported`, exactly as designed. - Contract scan of `src/`: no guarded cordis property reads (`ctx.config` class); every service reached through `ctx.get()`, every listener and timer paired with a disposer. ### Environment note (not a plugin defect) The gateway no longer routes the `nvidia/*` ids declared in the profile patch (its `/v1/models` lists six `AMD/*` + `zen/*` models), so probes for those report `400 unknown provider for model …` — reproduced with a direct `fetch` to the gateway, i.e. gateway-side routing, reported faithfully by the plugin as an UPSTREAM verdict. ## [0.6.0] - 2026-09-27 ### Changed — DSH Desktop 2.0.15 (dsh-* 0.1.7-rc.2) settings-model migration DSH 2.0.15 retired `settings.yaml` entirely: settings now live in the profile's Cordis patch, one entry per plugin, and the settings service (dsh-settings 0.1.7-rc.2) only exposes fields a plugin declares `.volatile()` in its Config schema. pi-ai's section became zod-strict — every unknown key the old versions stored inside `llm-pi-ai` (the plugin's probe flags, the auto-reasoning switch, per-route `userAgent`) is now stripped on write. Everything this plugin mutates at runtime moved into its OWN settings entry (`dsh-provider-pro`), declared as a volatile Schemastery Config: - `userAgents` — per provider-route User-Agent overrides (replaces `llm-pi-ai.providers..userAgent`, which pi-ai no longer keeps). - `autoReasoning` — the reasoning-effort auto-fill master switch (replaces the `dshProviderProAutoReasoning` top-level flag; default on). - `probeRequest` / `probeResult` — the single-slot probe IPC between the client section and the host half (replaces `dshProviderProProbe`/`dshProviderProProbeResult`). Per-model `reasoningEfforts` / `contextWindow` / `maxTokens` / `input` / `compat` remain schema-declared under pi-ai's volatile `providers` dict, so the auto-fill, the probe backfill/compat sync, and the image-input declaration keep writing there — now against the per-row describe revision. ### Changed - **Settings reads/writes moved to the 0.1.7 contract.** The removed `settings.get(ns)` / `settings.section(ns)` calls are replaced by `settings.describe()` row lookups; `settings/updated` (removed upstream) is replaced by `settings/document-updated`, ns-filtered to `llm-pi-ai` + `dsh-provider-pro` on both halves (host consumer and the client events bridge). - **No auto-generated settings page.** The host registers `configure({ auto: false })` through a settings-injected child (the official dsh-agent-default-model pattern) so the settings UI does not add a second, plain form page next to this plugin's own section. - **Client CAS writes are per-namespace.** `mutateFresh` now takes the target namespace — probe/switch/UA writes CAS against the `dsh-provider-pro` row, model-array writes against the `llm-pi-ai` row. - The live measurement harness (`scripts/measure-probe-live.mjs`) now reads the real provider config from the profile's `cordis.patch.yml` (with a permissive loader for DSH's `!!js` expression tag) instead of the retired `settings.yaml`. - New dependency: `@deepseek-ai/schemastery` ^3.18.4 (the volatile-flag schema flavor the 0.1.7 settings service requires). ### Fixed - **Host crash on load: `cannot get property "config" without inject`.** The host half read the plugin's own volatile config through `ctx.config`, but cordis 4 guards that property behind an inject declaration — the first read was a fatal load failure (host exit 1). The module contract's `apply(ctx, config)` second argument is now captured instead, and every own-config read goes through the settings describe row of our entry first (the authoritative, always-fresh view), with the apply-time node as the pre-settings fallback. ### Migration notes - One-time import on first 2.0.15 boot already stripped the old plugin keys from the pi-ai section (they were not schema-declared); this version re-creates them in the plugin's own entry on first use. Nothing to do by hand except restarting DSH Desktop. - Previously stored per-route `userAgent` values (if any) are not migrated automatically; re-enter them once in the 模型增强 section. ## [0.5.7] - 2026-09-24 ### Fixed - **TIMEOUT flood root cause: a consumed-but-stranded probe request never self-heals.** If the debounced `settings/updated` handler ever missed a request (event dropped, transient read failure, unexpected throw in the timer callback), the request sat in the `dshProviderProProbe` slot forever and the client burned its full 110s cap showing `TIMEOUT`. The host consumer is now unstrandable: 1. A 5s periodic recheck consumes any slot request that is neither the last published id (`lastProbeId`) nor currently in flight (new `activeProbeId`), so a missed event costs at most 5s instead of 110s. 2. The probe chain link is now fully wrapped in try/catch — ANY unexpected throw publishes an error result instead of silently dropping the request. 3. `finally` now clears `activeController`/`activeProbeId` only when they still belong to THIS probe — a superseded probe can no longer wipe a newer probe's controller registration. ### Changed - **INFRA split into INFRA (gateway unreachable) vs UPSTREAM (model's upstream route broken).** new-api-class gateways relay their upstreams' Go errors verbatim (`dial tcp: lookup api.x.com: no such host`) inside HTTP response bodies; the old classifier matched that text against the WHOLE raw error and called it "网关基础设施故障" — flooding the UI with gateway-infra verdicts while the gateway was actually alive and only per-model upstreams were down. Now: - `INFRA` — only when the plugin's own transport to the gateway failed at the network level (DNS/TCP/TLS on the `unreachable:` wire errors): "网关不可达(…)— 网关主机 DNS/网络故障,恢复后重试". - `UPSTREAM` — gateway answered, body carries the upstream's network error: "网关正常,该模型上游线路故障(…)— 模型侧问题,稍后重试此模型". - Node-style transport errors (`getaddrinfo ENOTFOUND`, `ECONNREFUSED`, `ECONNRESET`, …) are also recognized on the INFRA side. - **Hung-model error text de-cryptified.** A status-0 wire failure whose body was a bare abort message ("This operation was aborted") now publishes "transport silence — no HTTP response within 8s (retried once at 2s); upstream hung, cooling down, or gateway overloaded" instead of the cryptic raw abort text. ### Added - **Per-stage timings published with every verdict** (`stages.baselineMs / devMs / sysMs / streamMs`) and rendered in the success line (`base 823ms/dev 1.2s/…`) — the user can see exactly which phase consumed the wall-clock time, addressing the "对结果存疑" trust concern with visible evidence. ## [0.5.6] - 2026-09-17 ### Fixed - **Stale lock blocks UI toggle (auto-reasoning switch).** When a probe was running, its `writeResult` called `settings.mutate()` outside the `enqueueWrite` queue, so a concurrent UI toggle write (e.g. switching `dshProviderProAutoReasoning`) was blocked by the in-flight probe's settings lock for up to 100s — the toggle appeared to not persist. Two fixes: 1. New probe arrival now immediately `abort()`s the previous in-flight probe's controller, releasing its wire connections and settings lock instantly instead of waiting for the 100s budget to expire. 2. `writeResult` now routes through `enqueueWrite`, serializing the probe's CAS write with other plugin mutations so the UI toggle is never blocked by a long-running probe. ## [0.5.5] - 2026-09-17 ### Fixed - **INFRA false positives eliminated.** The infra-detection regex matched `TLS handshake timeout`, `connection refused`, and `unexpected eof` — all of which can be model-level errors (upstream timeout, cooldown), not gateway infrastructure. Narrowed the regex to only match unambiguous gateway infra: `lookup `, `dial tcp ... `, `no such host`, `network is unreachable`. Model timeouts now show as `TIMEOUT` (correct) instead of `INFRA` (misleading). - **TIMEOUT from hung-model budget overflow.** Seven hung models × 12s (8s header + 4s retry) = 84s baseline + alive models = ~101s > 100s host budget → client TIMEOUT. Reduced the fast-retry header timeout from 4s to 2s: per-hung-model cost 12s → 10s. Total serial wire time on the real gateway: 95.9s → 84.6s (−12%), now within the 100s budget. ## [0.5.4] - 2026-09-17 ### Fixed - **settings/updated handler debounce (500ms).** The handler fired `fill() + probe()` immediately on every `settings/updated` event. When the user switches models, `session-controller.saveSelection()` writes settings first; the plugin's `probe()` then grabs the same settings lock (up to 110s budget), and the next `saveSelection()` queues behind it — the settings page freezes until probe completes (gray screen). The 500ms debounce lets all writes in a rapid burst finish before the plugin reads/writes again. `sync()` (a fast read) still runs immediately; only the expensive `fill()`/`probe()` are delayed. The debounce timer is cleared on disposal. ## [0.5.3] - 2026-09-17 ### Changed - **Wire phase header/body separation.** `wirePost` now uses `AbortSignal.timeout(8s)` to gate the header phase only; the body read is NOT signal-gated, so a gateway that answers HTTP headers promptly (any status) is classified as ALIVE even if the body stalls — only a transport-level silence (status 0) is the "no evidence of life" hang signal. Previously the flat 10s abort killed slow-but-alive models and gave a false DEAD verdict (the root cause of "results are questionable"). - **Dead-model fast-fail.** Transport silence (status 0) gets ONE fast retry at 4s half-budget; the old code retried at full 10s for 20s total per hung model. A 429/5xx HTTP failure gets one retry at full 8s. Live measurement: per-hung-model cost dropped from ~20s to ~12s; HTTP error models now fail in 4–583ms instead of burning the full timeout. Total wire time on the real 13-model gateway: **95.9s → 66.7s (−30%)**. - **`imageVerdict: 'unsupported'`** distinguishes "the model has no image capability at all" (HTTP 400 "does not support image input") from "a vision-capable model refused THIS image request" (`'rejected'`). Both clear a wrong image declaration; the verdict and failure code now tell the user exactly why. Previously all image failures landed in one bucket, producing "partial" on every text-only model — the core of "对结果存疑". - **Result shape exposes `imageSupported`** for both `rejected` and `unsupported` verdicts; the client shows `'image: unsupported'` as a distinct badge text. ## [0.5.2] - 2026-09-09 ### Changed - **Sync to DSH Desktop 2.0.10** (cordis 4.0.2, dsh-llm-pi-ai 0.1.5-rc.2). Verified surface-by-surface against the installed build: settings service & RPC, `settings.section`, the bundle protocol, design tokens, pi-ai reasoning-effort semantics, and `signal` passthrough for `llm.stream` / `discoverModels`. No breaking drift — one upgrade taken: - **Host writes are now compare-and-set.** The settings service exposes `expectedRevision` (`mutate(ns, ops, expectedRevision)`, undefined = unconditional; mismatch throws `SettingsConflict`). The three host whole-array write paths — auto-fill, capacity backfill + compat fix, and image-declaration sync — re-read the namespace, write with that revision, and on conflict re-read + rebuild + retry (bounded). Previously they wrote unconditionally (the audit's P1 lost-update root cause); an external edit or another plugin writing between read and write could be silently clobbered. Now the conflict is detected and the write re-applied against the fresh document. The plugin-internal `enqueueWrite` queue is retained on top, so no interleaving can occur even without a revision-capable settings face (older DSH 2.0.x falls back to unconditional writes). - **Probe budget timer is cleared** (`clearTimeout`) as soon as the 100s race settles — a stale timer no longer keeps the event loop (and the process, in tests) alive for the full 100s. ### Smoke - Fake settings now models DSH 2.0.10: `describe()` returns `{ ns, revision }` and `mutate` enforces the CAS with the real `SettingsConflict` text. - New smoke block: a CAS conflict during auto-fill (external edit lands between the plugin's read and write) is retried — both edits survive. - All six smoke blocks pass and the process exits cleanly. ## [0.5.1] - 2026-09-08 ### Added - **Probe cancellation & supersede guard**: every probe run carries an AbortController and a generation token. The 100s host budget, plugin disposal, and each newer probe request abort the previous run's stream / wire / discovery requests, and every settings write checks `isCurrent()` before touching the document — a timed-out probe can no longer publish a stale result, unset a NEWER request slot, or clobber a newer probe's result. - **Discovery cache keyed by baseURL + credential** (not baseURL alone), so two routes sharing an endpoint with different credentials cannot reuse an incompatible `/v1/models` listing; the 10s cap ABORTS the underlying discovery request instead of just abandoning the wait. - **Plugin-internal write queue**: auto-fill, capacity backfill, compat fix, and image-declaration sync serialize their read→write cycles through one queue — the previous fill-vs-probe race could overwrite concurrent edits. ### Changed - **Role classification narrowed**: `compat.supportsDeveloperRole: false` is only written when the developer refusal carries role-specific evidence (400/422 or 5xx whose body names roles / 1214 / 角色), never for bare auth (401/403), unknown-model (404), quota, or generic 5xx — those used to produce persistent bogus compat writes. - **Image rejection detection**: a stream `finish` chunk with an error/aborted reason naming the image classifies `imageVerdict: rejected` and clears the declaration (providers encoding refusal as an error finish were previously left unclassified forever). - **Stream iterator cleanup**: `iterator.return()` is awaited with its rejection caught instead of fire-and-forget (an unhandled rejection and a dangling upstream request after timeout). - **UA baseURL matching is origin + path-boundary safe**: `https://h/api` no longer matches `/apiX` or an attacker-style `h.evil` host-prefix. - **Client probe-all**: a single model's exception no longer aborts the walk or wedges the busy flag; the button is always restored via `finally`. The timeout cleanup only unsets the request slot when it still holds THIS request — a blind unset used to delete the next model's freshly written request. - **Result freshness**: results carry a `receivedAt` stamp; the newer of a manual or bulk probe result wins. Manual probe results also feed the provider badge; the badge only counts models still in the profile. - **UA input re-syncs** with an externally changed `userAgent` without clobbering in-progress typing. - **Revision-conflict retry narrowed** to the actual conflict message ("changed since it was read" / "expected revision") instead of any error containing "revision". - **Master-switch "saved" indicator resets** at each flip start; `RpcResult.value` is typed optional; `ModelRow` no longer returns before its hooks (a deleted model row could crash React with a hook-order violation). ### Docs & packaging - **README/install examples updated**: `--profile desktop` (was `web`), tarball example 0.5.1 (was 0.3.0), feature intro "two" → "four". - **package.json**: adds `engines: node >=20` and a `verify` script (check + smoke + pack); version 0.5.1. ## [0.5.0] - 2026-09-05 ### Added - **One-button full model probe** (replaces the 0.4.0 capabilities/deep split): a single "Probe" button per model row measures three things in one pass — 1. context window/maxTokens via `discoverModels` (GET /v1/models), missing values backfilled into the model entry; 2. **message-role admission** — pi-ai's OpenAI-completions compat defaults `supportsDeveloperRole` to true, so a reasoning-capable model makes pi-ai send its system prompt as OpenAI's `developer` role, which some upstreams (GLM behind a relay, error 1214 "角色信息不正确") refuse. One minimal `developer` POST + one `system` baseline settles it; a refusal with a passing `system` auto-writes `compat.supportsDeveloperRole: false` so pi-ai falls back to `system`; 3. **image admission + latency** — a real stream carrying a 1×1 PNG through the LLM runtime, run last so it exercises the exact post-fix configuration the chat will use; the measured verdict is synced into the `input` declaration (adds/removes only `image`; other declared modalities are preserved). Each write-back re-reads the freshest settings right before its mutate and writes only fields that actually changed, so concurrent edits (UI, auto-fill) survive. Wire checks run only for `openai-completions` routes and skip silently otherwise. - Provider-wide "Probe all" now walks every model through the same full probe (it also feeds the alive badges). Each result line shows `ctx · max · role · image · first-token · written/backfilled`. Models with an active credential cooldown are skipped and keep their previous result. ### Changed - **Reasoning-effort levels are no longer wire-probed.** The 0.5.0 drafts validated low/medium/high/max with per-level requests and rewrote `reasoningEfforts` accordingly; that rewrite proved unreliable on heterogeneous relays (intermittent upstream 5xx produced "flaky" verdicts) and could drop levels the gateway normally accepts. The default five-level dictionary is instead provided by the `fillEfforts` auto-fill pass, which the probe never overwrites. - **Probe efficiency + verdict coverage** (live-fire tuned): - discovery (GET /v1/models) is cached per baseURL for 60s — a "probe all" pass over N models no longer re-fetches the same listing N times; - when the wire baseline already proves the model unreachable or hard-refused, the 30s image stream is skipped (the upstream is dead; a second, slower error adds nothing) — hung upstream models now cost ~10s instead of ~50s; - a timeout abort on the baseline is not retried (an upstream that hung 10s will not answer in the next 10); fast transport failures still get one retry; - developer-role admission treats a gateway-wrapped 5xx as a candidate refusal when `system` passes (measured: the live gateway wraps upstream 4xx refusals in 500s), so GLM-style models get the compat fix on the probe instead of failing in chat; - any streamed chunk (reasoning delta included) is first-token evidence — reasoning models that burn their token cap on thinking now get an image-acceptance verdict instead of an inconclusive stall; a clean `finish` counts too, an error/aborted finish does not; - the image stream carries a 12s first-token gate inside the 30s budget: tokens flowing → burn the budget; nothing flowing → fail fast with "no first token within 12s". - Probe summary formatting consolidated; removed the separate "Capabilities"/"Deep probe" buttons and their locale keys. - A stream that yields no first token within its budget is an explicit probe failure (the model did not answer) instead of a success with a budget note. - The provider badge reports "cooldown" only when EVERY failed model carries a defined, still-active cooldown; any hard failure counts as down. ## [0.4.0] - 2026-09-02 ### Added - **Image-input declaration**: each model card now shows a "Support image input" checkbox; checking it writes `input: [text, image]` to the model entry in `settings.yaml` so DSH allows image attachments for that model. - **Built-in probe**: each model row gains "Capabilities" (zero-cost `discoverModels` / GET /v1/models — contextWindow/maxTokens, with auto-backfill of missing fields) and "Deep probe" (fallback: a real minimal stream request carrying a 1×1 PNG to exercise image admission and measure latency). Provider-wide "Probe all" walks every model in capabilities mode by default. Host and client exchange through the `llm-pi-ai` user layer (settings IPC), so no extra service or process restart is needed. - **Alive-status UI**: each provider card header shows an up/down/untested badge, and each model row a matching status dot — aggregated from the latest probe results. ### Changed - Provider cards now render the full model list below the User-Agent field (collapsed by default). Each model row shows its declared `contextWindow`/`maxTokens` as compact chips (hand-set or probe-backfilled values — the current source of truth). - **Debt cleanup**: removed the dead `dsh-provider-probe` soft dependency, the unused `llmWire` client prop threading, and unused locale keys; probe waiting is now event-driven instead of polling `describe()`. ## [0.3.0] - 2026-09-02 ### Fixed - **Settings section failed on DSH 2.0.x** with `TypeError: Cannot read properties of undefined (reading 'settings')`: the client RPC surface moved. The section now talks to `ctx.remote.settings` (the same face the official settings pages use) — positional `mutate(ns, ops, expectedRevision)` parameters, zero-argument `describe()`, and the flat `{ok, value, error}` envelope. `connection` is no longer injected, and a missing face degrades to a clear in-section message instead of a TypeError. ### Changed - **Reasoning-level auto-fill now offers five levels** (off / low / medium / high / max); `minimal` and `xhigh` are removed by request. Models still carrying a byte-exact seven-level dictionary auto-filled by 0.1.0–0.2.0 migrate down automatically on the next fill pass, and the master switch's cleanup recognizes both shapes. Hand-customized dictionaries and explicit `false` are never touched. - The plugin now **requires DSH Desktop 2.0+** (the `remote.settings` face). ## [0.2.0] - 2026-09-02 ### Compatibility - Verified surface-by-surface against **DSH Desktop 2.0.4** (harness `0.1.2-alpha.1`, cordis `4.0.1`): the `settings.section` slot, the ModuleLoader client-bundle protocol (now hosted by `dsh-client-modules`), the settings service (`get`/`section`/`mutate` + `settings/updated`), the client RPC surface (`settings.describe`/`settings.mutate`, forwarded `settings/document-updated`/`llm/adapters-updated`), all 13 design tokens, and the pi-ai `reasoningEfforts` → `thinkingLevelMap` semantics are unchanged. No code change was required. - Attribution handling in 2.0.x moved to `@deepseek-ai/dsh-llm` (`attributionHeaders()`); pi-ai's header merge still strips case-insensitive collisions from user headers, so the fetch-level UA replacement remains both necessary and effective. ### Changed - Removed the stale `@deepseek-ai/dsh-client-runtime` reference from `dsh.client.inject` — the package no longer ships with DSH Desktop 2.0.x (the module system now lives in `dsh-client-modules`). The bundle only ever required `react`/`react/jsx-runtime` seed modules, so loading was never affected; this is a manifest cleanup. - Updated the tsdown externals allow-list to the current DSH 2.0.x platform surface (`dsh-client-runtime/client` dropped). - `prepare`/`prepack` now invoke `tsc`/`tsdown` directly instead of `npm run build`, so git installs work under any package manager. - README: bilingual compatibility note + LINUX DO community link. ## [0.1.0] - 2026-08-20 ### Added - **Request-level User-Agent override** per custom provider (stored as `llm-pi-ai.providers..userAgent`). The host half patches `globalThis.fetch` with a baseURL-prefix matcher and replaces the `User-Agent` header on matching requests — the DSH built-in attribution header is overridden, not appended. - **Official-channel-style reasoning-level switching** for custom models. A background filler gives every hand-declared custom model without a `reasoningEfforts` dictionary the full seven-level set, so the chat model picker shows the same reasoning-level row as the official channels, with the effective `defaultEffort` unset (the picker's "Default" entry is preselected; no thinking parameter is sent unless the user switches). - **Settings → 模型增强 section** (`settings.section` slot, id `provider-pro`, order 15): a master switch (`llm-pi-ai.dshProviderProAutoReasoning`, absent = on) for the auto fill — turning it off strips only byte-identical auto-filled dictionaries and returns those models to provider defaults — plus one User-Agent input per provider. Styled with the platform design tokens (theme-following). - Offline validation (`npm run check`) and a host-side smoke test (`npm run smoke`) covering the UA patch, the filler, and the master switch. ### Notes - Effort-level names inside the model picker are hardcoded English in `dsh-llm-pi-ai` and cannot be localized by a third-party plugin; the Chinese labels live in this plugin's own settings section only. - The filler touches only hand-declared `models[]` under the `llm-pi-ai` namespace; it never touches `modelOverrides` or catalog (official) models.