# Changelog

All notable changes to dsh-ears are recorded here.

## [0.4.3] - 2026-10-17

Bailian console guidance. The upgraded Alibaba Cloud Model Studio console lists each ASR family with its synchronous, file-transcription, streaming, message, and realtime members side by side. This patch rejects the non-synchronous variants before they reach the sync endpoint, turns model-selection and model-not-found failures into actionable guidance, and treats Qwen-Audio 3.1's short-audio no-word reply as empty speech. No dsh baseline or peer-range change: `0.4.3` is a drop-in patch for `0.4.2`.

### Fixed

- **Non-synchronous Bailian models are rejected before the request.** `-filetrans`, `-streaming`, `-message`, and `-realtime` variants, including dated snapshots such as `qwen3-asr-flash-filetrans-2025-11-17`, and async-only `fun-asr` / `paraformer-v2` ids return `asr.modelUnsupported` with a localized synchronous-model example. Previously they reached the sync endpoint and failed with `url error, please check url`.
- **Unknown Bailian model ids get actionable guidance.** A provider `Model not exist.` reply is mapped to `asr.modelNotFound`, which asks for the exact case-sensitive model ID and confirmation that the model is enabled in the current workspace.
- **Qwen-Audio 3.1 no-word replies end quietly.** `CLIENT_ERROR: ASR_RESPONSE_HAVE_NO_WORDS` is treated as empty audio, so very short or silent clips no longer surface as an upstream failure. Short voice input should prefer `qwen3-asr-flash`; both READMEs record that limitation.
- **Bailian response parsing accepts both placements.** Top-level `text` / `sentence.text` and the nested `output.*` shapes returned by the workspace-specific and legacy hosts are read.

### Changed

- **Bailian settings guidance follows the upgraded console.** The API-host hint names the workspace-specific Beijing and Singapore hosts and the allowed `/compatible-mode/v1` suffix; the model hint asks for the model ID and warns against display names and `-filetrans` / `-streaming` / `-message` variants.
- **Both READMEs document the console upgrade.** They list the supported synchronous models, the unsupported transport variants, the short-audio limitation, and the current non-realtime HTTP API reference.
- D-060 records the model-selection and no-word decisions.

### Verification notes

- `pnpm check`, `pnpm test` (588 tests), `pnpm build`, `pnpm verify:package`, and `pnpm release:check` pass.
- The CI matrix passes on Ubuntu and Windows, the dependency audit passes, and both DSH compatibility smokes (`0.2.0-rc.1` and `0.2.1-rc.2`) pass.
- The existing manual gates are unchanged: a real renderer and a real microphone are not automated.

## [0.4.2] - 2026-10-17

dsh 0.2.1 support. The compile, lockfile, and certification baseline moves to dsh `0.2.1-rc.2`, the newest published dsh release, while the supported minimum stays at `>=0.2.0-rc.1`. Upstream again changed no API this plugin consumes, so this is a baseline and certification move with no runtime source change and no floor break: `0.4.2` is a drop-in patch for every host that already runs `0.4.1`.

### Changed

- **The compile and lockfile baseline is dsh `0.2.1-rc.2`.** Every `@deepseek-ai/dsh-*` development pin, the bundled `@deepseek-ai/dsh-client-store` implementation dependency, `@deepseek-ai/cordis` (`4.0.5`), and `@deepseek-ai/schemastery` (`3.18.5`) move together. Bumping a single package in that family is not a valid state: a newer dsh package resolves its own copy of `dsh-llm`, and the branded `ReasoningEffortId` / `ToolCallId` / `MessageId` types of the two copies do not unify, so the tree stops type-checking.
- **The supported dsh minimum is unchanged at `>=0.2.0-rc.1`.** The peer ranges, `engines.dsh`, the READMEs and their badge, the About surface, and package verification keep stating that floor. The open `>=` range is deliberate (D-058): it admits later 0.2.x hosts without a new plugin release, so a newer certification target is not a compatibility break.
- **The compatibility smoke certifies two dsh lines.** The CI matrix and `scripts/compat-smoke.mjs` cover `0.2.0-rc.1` and `0.2.1-rc.2`, and the smoke now takes each line's cordis/schemastery pins from a per-version table instead of one row pinned for every host, because dsh 0.2.1 peers `~4.0.5` and `~3.18.5`.
- Dependabot groups every `@deepseek-ai/*` update into one pull request, so the release line cannot be bumped one package at a time again. The seven per-package pull requests this replaced are recorded in the repository as superseded, not merged.

### Upstream context

- dsh `0.2.1-rc.2` is the npm `next` release; npm `latest` remains `0.2.0-rc.2` and no stable `0.2.1` exists yet. The line's user-facing work is the plugin market, session-query and terminal tooling, and a resource-scoped access-control model for Remote calls.
- Tarball comparison of the thirteen packages this plugin consumes against `0.2.0-rc.1` found only additive or optional changes. `@deepseek-ai/dsh-settings`, `@deepseek-ai/dsh-client-locale`, `@deepseek-ai/dsh-client-store`, and `@deepseek-ai/dsh-client-ui-settings-plugins` are unchanged outside `package.json`; `@deepseek-ai/cordis` and `@deepseek-ai/schemastery` are unchanged outside their version. The rest add declarations: Typert gains `resources`, `principal`, and `TypertRemoteValue`, `TypertCodec` keeps requiring `create()` through that value type, `LlmCallConfig` is split into `LlmCallControls` plus the captured route without losing a field, and the conversation, settings, and primitives surfaces add `draft`, `user-text`, `CommandChip`, `InlineEditor`, `MenuGroup`, and `HoverCardGroup` exports. The exported Cordis `Config` settings policy, `settings.describe()`/`update()`/`replace()`, the icon exports, slot registration, `ctx.credentials`, `ctx.llm`, and the strict Typert `create()` codec contract are unchanged.
- The new Remote access-control layer gives a call that names no resource the built-in `global` resource, and dsh-ears' namespace touches no session, workspace, or file identity, so it needs no resource declaration. The certification run exercises that path through the real HTTP Remote boundary rather than by inspection.

### Verification notes

- `pnpm check`, `pnpm test` (582 tests), `pnpm build`, `pnpm verify:package`, and `pnpm release:check` pass against the `0.2.1-rc.2` baseline with no source change.
- The compatibility smoke ran its `fresh`, `canonical`, and `legacy` scenarios against dsh `0.2.1-rc.2` and against dsh `0.2.0-rc.1`. Each run served the Client contribution, returned a redacted `getSettings` view, applied a settings write through the strict Remote, and read that write back after a `dsh web` restart. The same build passes on both lines, which is what makes the unchanged `>=0.2.0-rc.1` floor an assertion instead of a claim.
- The existing manual gates are unchanged: a real renderer and a real microphone are not automated, so the desktop and browser click-through remains a manual result rather than a test.

## [0.4.1] - 2026-09-28

dsh 0.2.0 support. The supported minimum moves to dsh `>=0.2.0-rc.1`, and dsh-ears `0.4.1` is the first release that runs on the dsh 0.2 desktop client; `0.4.0` remains the last release for dsh 0.1.x. The range break lands in a patch release, and dsh enforces it: an install on dsh `0.1.7-rc.2` is refused instead of run. No runtime source change was required, because every dsh package dsh-ears consumes kept its public contract across that step.

dsh 0.2 has no stable release yet: `0.2.0-rc.1` is the npm `next` release and the version the DeepSeek Harness desktop application ships.

### Changed

- **The supported dsh minimum is `>=0.2.0-rc.1`**, replacing `>=0.1.7-rc.2`. The peer minimum, `engines.dsh`, the compile baseline, lockfile, CI matrix, compatibility smoke, package verification, About surface, and READMEs all state that one version. dsh enforces that floor through `peerDependencies` on `@deepseek-ai/dsh*`; `engines.dsh` is declarative metadata that the running version does not read.
- **Pre-0.2.0 hosts are refused rather than tolerated.** The compatibility gate reads the declared peer ranges when a plugin is admitted or installed, so installing this release on dsh `0.1.7-rc.2` stops with `installation rejected: Plugin dsh-ears@0.4.1 is incompatible with dsh 0.1.7-rc.2` and offers the same-version exemption path instead. Users on dsh 0.1.2–0.1.6 stay on dsh-ears `<0.4.0`, users on dsh 0.1.7 stay on `<0.4.1`, and users on dsh 0.1.1 stay on `<0.3.0`; each range resolves to a release whose declared peers their host accepts.
- The compatibility smoke runs `fresh`, `canonical`, and `legacy` in one certification pass. Each scenario keeps its own isolated profile, so a failing upgrade document no longer hides behind a passing fresh install.
- `pnpm-workspace.yaml` records `minimumReleaseAgeExclude` entries for the `0.2.0-rc.1` packages, because that release is younger than pnpm's release-age gate.

### Added

- **Desktop is a profile, not a second client platform.** The dsh 0.2 desktop application composes the same `@deepseek-ai/dsh-web-app` bundle as the Web UI, and its client-modules service honours exactly one plugin platform word: `web`. The plugin therefore keeps `dsh.client.platform: "web"` and `exports["./client"]` unchanged, and works on both surfaces from one bundle. Desktop installs go through the application's own plugin manager into its reserved `desktop` profile; the `dsh` CLI refuses to manage that profile by design.

### Upstream context

- dsh `0.2.0-rc.1` is the first candidate of the 0.2 line. Its user-facing changes are the desktop client, plugin-management and settings-page interaction work, and the arrival of automation as an optional plugin bundle.
- Tarball comparison against `0.1.7-rc.2` found no breaking change in any package this plugin consumes. Ten of the thirteen are byte-identical outside their `package.json`; the three that differ only widen types (`SessionInput.submit(mode?, source?)`, `TextShimmer` children, `overlayTopMargin()` return value) or add optional desktop submission metadata. The settings service, client icon exports, slot registration, credentials, `ctx.llm`, and the strict Typert `create()` codec contract are unchanged, and `@deepseek-ai/cordis` `~4.0.4` with `@deepseek-ai/schemastery` `~3.18.4` stay the vendor pins.

### Upgrade evidence

- Measured on a real dsh `0.2.0-rc.1` host: the client contribution is served, `getSettings` returns a redacted view, a settings write through the strict Remote applies live, and the write survives a `dsh web` restart — in all three settings-document scenarios, including the canonical v4 section that dsh-ears 0.3.x wrote and the pre-0.3 flat section the Host declines to import.
- The legacy `settings.yaml` import is asynchronous, so a certification run must wait for it rather than assert on the first read. On dsh `0.2.0-rc.1` the seeded canonical model, credential, and backend were live two seconds after a first read that still showed defaults; the smoke now polls the settings view until the imported values settle, which keeps the canonical assertions strict on every host instead of encoding a latency difference as an expectation.

### Verification notes

- The manual desktop click-through passed on 2026-09-28 on the shipped desktop application (`0.2.0-rc.1`). dsh-ears `0.4.1` was installed from its packed tarball into the application's own `desktop` profile through its plugin manager, and the plugin row and localized metadata, the `dsh-ears` settings page, real-microphone Web Speech recognition, the editable draft, polishing, and manual sending all behaved as expected. That run satisfies the gate D-057 left in force for later releases; it is a recorded manual result rather than automated evidence.
- Automated evidence still does not execute the browser client in a real renderer. Only the compatibility smoke's client contribution fetch is automated.

## [0.4.0] - 2026-09-28

dsh 0.1.7 support. This is a breaking release: the supported minimum moves to dsh `>=0.1.7-rc.2`, and the plugin is ported to the settings, icon, and Remote contracts that `0.1.7` introduced.

### Changed

- **Host settings use the exported `Config`.** The plugin no longer calls `ctx.settings.register`, which dsh 0.1.7 removed; it exports its settings schema as the Cordis `Config` and reads and writes through the dsh settings service instead. Every leaf is volatile, so saving a field applies live rather than recomposing the plugin entry, and a corrupt stored value falls back to its field default instead of preventing the plugin from loading.
- **Settings validation moved to the write path.** Field-scoped validation still runs before a write is persisted, replacing the schema validation dsh performed at registration time.
- **Client icons follow the 0.1.7 rename** from size-suffixed to weight-suffixed exports (`IconLoadingOutline16` → `IconLoadingOutlineRegular`, and the same for the stop, trash, and chevron glyphs). The previous names resolve to `undefined` on 0.1.7, which fails renderer boot.
- **Every strict Remote codec now carries a `create()` factory**, on both parameters and results. dsh 0.1.7 rejects a codec without one, on the Host and again inside the client bundle's own copy of the descriptor table.
- **The supported dsh minimum is `>=0.1.7-rc.2`**, replacing `>=0.1.2-rc.1`. The compile baseline, lockfile, CI matrix, compatibility smoke, package verification, About surface, and READMEs all move together. Users on dsh 0.1.2–0.1.5 must install `dsh-ears@<0.4.0`; users on dsh 0.1.1 must install `dsh-ears@<0.3.0`.
- Entry configuration disables the automatically generated dsh settings form, because the plugin ships its own settings page.
- `@deepseek-ai/cordis` moves to `^4.0.4` and `@deepseek-ai/schemastery` to `^3.18.4`, matching the versions dsh 0.1.7 ships.

### Added

- Plugin display metadata for the dsh settings and plugin surfaces: an `icon`, `locale/en.json`, and `locale/zh.json`, so the plugin shows a title, summary, and icon there instead of a bare package name.
- Package verification executes the built Remote descriptor table, checks that the client bundle inlines every endpoint, and rejects the removed icon names in the shipped bundle.
- The compatibility smoke now writes a settings field through the strict Remote, restarts `dsh web`, and confirms the write survived, which exercises the new settings path end to end on a real host.

Upstream `0.1.7` replaces the plugin-settings namespace model with Loader-entry config forms, so a settings section now lives in the profile entry config rather than a plugin-owned document. A section written by `0.3.x` already uses the canonical nested v4 shape and carries over unchanged — verified on a real `0.1.7-rc.2` host, including that the stored credential still reaches the Host while staying redacted on the wire. The pre-0.3 flat shapes are undeclared fields that the Host refuses to import, so those values stay in `settings.yaml.imported`; the users affected are the ones already routed to dsh-ears `<0.3.0>` on dsh 0.1.1.

### Verification notes

- The isolated compatibility smoke additionally runs settings-document scenarios (empty, canonical v4, pre-0.3 flat) against a real dsh `0.1.7-rc.2`, and its assertions fail the run rather than only logging.
- Automated evidence does not include executing the browser client in a real renderer, or the manual Windows/desktop browser smoke; those remain human gates.

## [0.3.3] - 2026-09-27

dsh 0.1.5-rc.3 certification preparation. No dsh-ears runtime source change was required.

### Changed

- The compile and compatibility-smoke baseline moves from dsh `0.1.5-rc.2` to `0.1.5-rc.3`; the supported minimum remains `>=0.1.2-rc.1`.
- Compatibility CI exercises dsh `0.1.2-rc.1` and `0.1.5-rc.3`. `0.1.5-rc.2` is superseded as a certification target within the same rc line.
- `@deepseek-ai/dsh-client-store` and all direct dsh development dependencies move to `0.1.5-rc.3`; `zustand` and `immer` remain explicit dependencies so the client bundle stays self-contained.
- The compatibility smoke pins the Cordis plugin family co-tested with the `0.1.2-rc.1`/Cordis 4.0.2 floor so later plugin releases cannot change that fixture through registry drift.

Upstream `0.1.5-rc.3` changes only release manifests, lockfile, and dependency-verification scripts. Every consumed package has identical runtime code and public declarations to `0.1.5-rc.2`; each package manifest differs only in version, rc.3 dependency ranges, and the Cordis/Schemastery vendor pins.

## [0.3.2] - 2026-09-18

dsh 0.1.5-rc.2 certification patch. No dsh-ears source change was required.

### Changed

- The compile and compatibility-smoke baseline moves from dsh `0.1.5-rc.1` to `0.1.5-rc.2`; the supported minimum remains `>=0.1.2-rc.1`.
- Compatibility CI exercises dsh `0.1.2-rc.1` and `0.1.5-rc.2`. `0.1.5-rc.1` is superseded as a certification target within the same rc line.
- `@deepseek-ai/dsh-client-store` moves to `0.1.5-rc.2`; `zustand` and `immer` remain explicit dependencies so the client bundle stays self-contained.

Upstream `0.1.5-rc.2` changes only delivered-file card layout and code-file icon artwork. The public type surface of `@deepseek-ai/dsh-client-ui-primitives` is byte-identical to `0.1.5-rc.1`, and no other consumed package differs outside its `package.json` bytes.

## [0.3.1] - 2026-09-10

dsh 0.1.5 compatibility release.

### Changed

- The compile and compatibility-smoke baseline now includes dsh `0.1.5-rc.1` while the supported minimum remains `>=0.1.2-rc.1`.
- Compatibility CI exercises both dsh `0.1.2-rc.1` and `0.1.5-rc.1`.
- `zustand` and `immer` are explicit dependencies so the dsh-client-store implementation used by the client bundle remains self-contained with dsh `0.1.5-rc.1`.

## [0.3.0] - 2026-09-04

Breaking compatibility release for the dsh 0.1.2 package family.

### Changed

- dsh-ears 0.3.0 now requires dsh `>=0.1.2-rc.1`. Users who remain on dsh 0.1.1 must install dsh-ears `<0.3.0`.
- The Host now uses the 0.1.2 Typert `RemoteError` boundary through `remoteErrorOf`, preserving framework failures while removing the retired lookup-error import.
- The browser contribution now uses the 0.1.2 Remote mount, slot, locale, and snapshot-store contracts. `@deepseek-ai/dsh-client-store` and its state engine are bundled into `client.js`.
- Direct dsh peer dependencies use the `>=0.1.2-rc.1` minimum; the compile baseline moves to exact `0.1.2-rc.1`, Cordis 4.0.2, and Schemastery 3.18.2.
- CI and compatibility smoke run on dsh `0.1.2-rc.1`, including browser-session authentication, Client artifact loading, strict Remote settings, and secret redaction.

## [0.2.2] - 2026-08-29

Volcengine cloud ASR and realtime finalization release.

### Added

- Volcengine as a first-class cloud ASR provider with recording-file recognition and one-way realtime streaming, new-console `X-Api-Key` authentication, selectable resource IDs, recognition language, and localized settings.

### Changed

- The README badge row now includes DeepSeek branding and npm total downloads with a dedicated download icon.

### Fixed

- Realtime PCM chunks retain strict FIFO order while Volcengine, Tencent Cloud, and Deepgram audio sends complete after ordered WebSocket enqueue; asynchronous recognition responses continue updating snapshots, and `finish()` waits for terminal provider text.
- Repeated stop requests share one finalization per realtime session, while discard invalidates queued packets and performs session-safe cleanup.

## [0.2.1] - 2026-08-29

Update-command copy fix.

### Changed

- The About panel, the update check result, and the README update section now show `dsh plugin --profile web add dsh-ears` instead of `dsh plugin --profile web update dsh-ears`. `add` re-resolves `latest` from npm and rewrites the saved range, so one command works from any installed version, including crossings that `pnpm update` clamps to the existing range (such as 0.1.x to 0.2.0).
- The README update section names the manual steps the command does not perform: restart `dsh web` to load the new Host code, then refresh the Web UI.

## [0.2.0] - 2026-08-29

SiliconFlow provider and ASR/settings hardening release.

### Added

- [SiliconFlow](https://siliconflow.cn) CN edition (`api.siliconflow.cn`) as a cloud ASR provider on the Recognition tab: an OpenAI-compatible preset over the shared transcription adapter with the default model `FunAudioLLM/SenseVoiceSmall`, a live model list scoped by `sub_type=speech-to-text`, write-only API key storage, and a per-provider recognition language. The international edition is deferred (D-047).
- A versioned settings migration path (V1 through V4) that upgrades flat and grouped legacy layouts in place, with conservative normalization for files written by newer versions.
- Automatic repair of invalid persisted settings: unknown providers, backends, models, shortcuts, or out-of-range values fall back to safe defaults, the settings page lists the recovered fields, and the repaired configuration persists on the next save.
- SemVer-correct update checks, a dsh compatibility smoke suite, package/release verification scripts, Dependabot configuration, and CI coverage for the whole matrix.

### Changed

- Cloud ASR providers are registry-driven: provider metadata, field validation, credential/model resolution, persistence mappings, secret redaction, and the settings UI rows derive from one `CLOUD_ASR_PROVIDERS` table.
- Fresh installs default to browser Web Speech recognition; Local Whisper remains selectable, and its `default` acceleration resolves automatically against platform-supported native variants and locks only after native initialization (D-045, D-046).
- Polishing is enabled by default and follows dsh's live Agent default model, including its reasoning effort, when no explicit dsh-ears route is configured; the projected default is not persisted (D-045).
- Deepgram model lists come from the live catalog with provider-reported capabilities; models whose wire transport the adapter cannot execute (Listen V2, e.g. Flux) stay hidden per service.
- Remote descriptors and strict wire schemas are unified in one descriptor table; credentials stay Host-owned and are redacted as configured booleans.

### Fixed

- Invalid persisted settings no longer abort plugin registration or block the settings and config pages.
- Hardened request cancellation, timeouts, response-size caps, realtime session cleanup, and stale async writes across providers and client controllers.
- Bailian endpoints enforce HTTPS for credential-bearing requests, and DashScope failures surface the provider error code.

## [0.1.6] - 2026-08-27

Xiaomi MiMo cloud ASR provider release.

### Added

- [Xiaomi MiMo](https://mimo.mi.com) as a cloud ASR provider on the Recognition tab: API key, model selector (`mimo-v2.5-asr` static list), recognition-language, and an access-method toggle between the standard `api` and the `token-plan` subscription. Standard API posts to `https://api.xiaomimimo.com/v1/chat/completions`; Token Plan selects a regional cluster (`cn` default, `sgp`, `ams`) and derives the matching `token-plan-<cluster>.xiaomimimo.com` endpoint. Recording uses authenticated, OpenAI-compatible `/chat/completions` transcription with a bounded response, abort propagation, and an HTTPS-enforced endpoint.
- MiMo Host storage under `cloudAsr.mimo` (`apiKey` with `role('secret')`, `model`, `language`, `endpoint`, `cluster`, `service`), Typert / `remote-contract` wire fields, and settings-store backing.
- Cloud provider vendor icons in the client bundle (Groq, Deepgram, Alibaba Cloud, Tencent Cloud, Xiaomi MiMo, OpenAI); `@thesvg/react` moves to devDependencies so the icons are inlined at build time rather than shipped as a runtime icon dependency.

### Changed

- Cloud provider model listing is isolated per provider: `listCloudProviderModels(provider, signal)` now takes an explicit provider argument and Resolves it by id on the Host instead of trusting a stale settings snapshot, so model lists no longer leak across providers.
- The recognition-language row is unified so every auto-detection backend (Local Whisper, Groq, Deepgram, Bailian, MiMo, and the custom OpenAI-compatible branch) uses a `placeholder="auto"` input whose empty value means automatic detection; Web Speech keeps its locale-following placeholder.
- Recognition-tab copy, provider presets, and the About/Context translation strings updated for the MiMo provider.

### Fixed

- Cloud model listing and validation no longer depend on the previously applied settings snapshot, preventing cross-provider model list contamination.

## [0.1.5] - 2026-08-26

Cloud ASR recognition and recognition-language ownership release.

### Added

- [Deepgram](https://deepgram.com) as a cloud ASR provider on the Recognition tab: API key, model selector (live-fetched via `GET /v1/models` with a static `nova-3` fallback and "Custom model" branch), recognition-language, and `recording-file` / `realtime` service toggle. Recording uses `POST https://api.deepgram.com/v1/listen`; realtime uses `wss://api.deepgram.com/v1/listen` with `linear16` / `16000` / `interim_results` / `vad_events`.
- Deepgram Host storage under `cloudAsr.deepgram` (`apiKey` with `role('secret')`, `model`, `language`, `service`), Typert / `remote-contract` wire fields, and settings-store migration that drops the legacy top-level `recognitionLanguage` on upgrade to schema version 4.
- Tencent Cloud realtime recognition: an `realtime` branch next to `recording-file` on the Recognition tab, backed by WebSocket recognition in `PolishService` (`startRealtime` / `sendRealtimeAudio` / `finishRealtime` / `cancelRealtime`) and a shared realtime PCM capture path.
- Recognition-tab microphone routing for `tencent` / `deepgram` realtime modes; realtime backends reuse Web Speech-style gating semantics via `isRealtimeAudioCaptureAvailable`.

### Changed

- The General-tab recognition-language row is removed (D-042). Every backend now owns its recognition-language field on the Recognition tab: a language row inside the Web Speech, Local Whisper, Groq, Deepgram, Bailian, and custom OpenAI-compatible branches; Tencent Cloud keeps its engine-type selector and gains no language row.
- Leaving the recognition-language field blank now means automatic detection for Local Whisper and the cloud providers (Groq, Deepgram, Bailian, custom OpenAI-compatible) — the language parameter is omitted — and following the interface language for Web Speech. Previously stored recognition-language values are dropped when the settings store upgrades to schema version 4.
- Tencent Cloud recognized-engine field renamed to `cloudAsrTencentEngineType` with a tightened hint string.
- Recognition-tab copy and provider presets simplified; Tencent / Deepgram service-row copy refined.
- Deepgram recording / realtime recognition URLs reject non-`https:` / non-`wss:` schemes and purge stale `language` / `detect_language` query parameters when switching to `auto` / empty detection, so `auto` cannot leak a prior explicit language.
- Recognition-tab polishing and settings copy use a stricter one-clause hint style.

### Fixed

- Primary action button focus no longer flickers the microphone button: `MicrophoneButton` no longer relies on a fragile `:last-child` flex order, and its active state icon stays white in dark mode.
- Polishing LLM route discovery now refreshes automatically on window focus / tab visibility instead of requiring an explicit button, fixing stale provider/model lists after the Host is reconfigured.
- Deepgram realtime recognition no longer blocks on a 120 ms grace window per chunk: the realtime path sends PCM with a non-blocking grace and enables `interim_results` / `endpointing=300` / `vad_events=true`, so transcription streams promptly and finishes without a post-recording hang.
- Realtime WebSocket sessions clean up listeners correctly on server-initiated close, preserve `EarsError` business codes for `startRealtime`, and close leaked sockets when `open()` fails.
- `fetchCloudProviderModels` no longer misses an already-aborted signal before installing its forward listener. Deepgram's `/v1/models` response is parsed from `stt` rather than `data` and empty filtered results are rejected with `cloudModelsNoModels`. Deepgram `stt` model names that are internal / test entries are filtered and family aliases are injected before ranking.
- The Recognition -> Polishing tab's provider/model/route race no longer replaces the current `routeState` with a stale request's fallback routes.
- Cross-Host/Client version pairs no longer fail `dshEars/getSettings`: the `earsSettingsView` schema provides defaults for the Deepgram fields so an older Host's response validates, and the settings store migrates top-level `recognitionLanguage` without clobbering per-provider languages.

## [0.1.4] - 2026-08-26

Local Whisper native runtime release with voice-input quality fixes.

### Changed

- Local Whisper now uses the bundled `@fugood/whisper.node` native dependency and separately downloaded whisper.cpp GGML models instead of Python, Torch, FFmpeg, or the `whisper` CLI. Browser capture is normalized to mono 16 kHz PCM16 WAV, and there is no fallback engine.
- Recognition exposes Default, Vulkan, and CUDA acceleration choices where supported. Changing the native variant after its first load requires restarting the dsh Host; the npm root package is materially larger because official optional platform variants participate in installation, while model weights remain outside the npm tarball.
- The old OS-aware Python/FFmpeg/openai-whisper setup guide and its copyable commands are removed. Unavailable native packages or acceleration variants now produce concise diagnostics.
- Host persistence is organized into four fixed slots — `general`, `recognition`, `cloudAsr`, and `polishing` — while the flat Remote wire remains for client compatibility and per-field auto-save.
- Recognition and polishing settings copy is reworded for clarity.

### Fixed

- Rapid microphone toggles no longer leak an orphaned media recorder session that keeps the audio track open; a pending capture aborts itself when a newer start or a Web Speech start supersedes it (#9).
- Streamed Chinese/Japanese recognition results and draft appends no longer insert half-width spaces between CJK characters or around full-width punctuation (#11).
- A failed Whisper model download reports once and then reflects the real filesystem state again, so restored or manually placed models are detected without restarting the Host; leftover artifact sweeps can no longer race an in-flight retry download (#8).
- Chronological Chinese narratives such as "第一天……第二天……" stay prose instead of being corrupted into broken numbered lists (#12).
- Local Whisper state responses stay JSON-safe across download, cancel, and dispose paths.
- Whisper acceleration options list only what the current platform supports.
- The dev HMR patch emits a file URL base so module imports resolve on Windows.

## [0.1.3] - 2026-08-23

Structured error handling and localized settings copy release.

### Added

- Stable structured error codes, messages, and interpolation parameters across the Host, Remote, and browser client surfaces.
- Chinese and English localization coverage for settings, voice states, backend availability, and structured errors.
- Regression coverage for Remote contracts, localized interpolation, voice flows, DashScope failure classification, and Whisper lifecycle errors.

### Changed

- DashScope and other ASR failures now preserve actionable HTTP/status details while distinguishing empty-audio results from upstream failures.
- Host diagnostics crossing the Remote boundary sanitize credentials and cap string values, including Whisper model state errors and cloud model listing failures.
- Voice error presentation falls back safely when a localized template lacks required interpolation parameters.

## [0.1.2] - 2026-08-21

Compatibility release for every dsh published since the first release, including the `0.1.1-*` line. No product behavior changes.

### Fixed

- Peer dependencies on all `@deepseek-ai/dsh-*` packages are now `*` (D-035). node-semver prerelease matching made the D-034-era `^0.1.0-rc.6` floor reject every dsh `0.1.1-*` host — the tuple has no matching prerelease comparator — so the published plugin failed peer resolution on current releases. Installation no longer fails on an unlisted host while its audit is pending; compatibility claims stay documentation-scoped.
- The About tab reports the verified range as `0.1.0-rc.6 - 0.1.1-rc.2`.

### Changed

- Compatibility covers dsh `0.1.0-rc.6` through `0.1.1-rc.2` (D-035), extending D-034's rc.8 set with `0.1.1-rc.1` and `0.1.1-rc.2`. The rc.7 → `0.1.1-rc.2` audit found only additive host changes; `pnpm check`, the full unit suite, and a browser smoke on the local rc.2 CLI pass with zero source changes beyond this release's constants.
- The compile/test baseline moved to exact `0.1.1-rc.2` devDependencies, and the release-age exemptions gain the rc.2 set alongside rc.8.
- README badges and prerequisites, contributor docs, and `.agent` context state the same verified range.

## [0.1.1] - 2026-08-20

dsh rc.8 compatibility release (D-034).

### Added

- GitHub Actions `publish.yml` publishes tagged `v*` releases to npm with trusted publishing (OIDC). The first `0.1.0` tarball is still created once with a maintainer token so the npm package exists before that trust relationship can be attached.
- GitHub issue forms (bug, feature, new ASR backend) and a pull request template. Public issues that would contain a secret are steered to private advisories.
- README backend table lists Groq and Model Studio free-allowance notes with links to the provider docs.

### Changed

- Compatibility extended to dsh `0.1.0-rc.8` (D-034): the compile/test baseline moves to the exact rc.8 packages, the `react` peer widens to `^18.2.0 || ^19.0.0`, and the About tab range reports rc.6 / rc.7 / rc.8. No source change was needed beyond the range constant and a lowercase `@thesvg/react/github` import for case-sensitive CI.

## [0.1.0] - 2026-08-19

First public release: GitHub `WizisCool/dsh-ears` is public, npm `dsh-ears@0.1.0`. Later bullets in the same list supersede earlier ones. The live save model is D-031 auto-save, not D-026 Save/Discard. Cloud ASR keys are per-provider `role('secret')` fields, not dsh credential-references. Modifier-only shortcut chords are valid.

### Changed

- Public README is now Chinese-first (`README.md`) with an English sibling (`README.en.md`).
- README hero is a white-and-blue product banner at `assets/banner.jpg`. The old `assets/dsh-ear.svg` icon is removed.
- Repository layout: the product plan, architecture, and ADRs live under `.agent/`. Delivery history lives in this changelog.

### Added

- While the recognition bar is transcribing or polishing, the disabled stop square becomes the same trash icon as an in-progress Goal row. Clicking it discards that voice task, aborts the Host request, and collapses the bar with the existing exit animation.
- An **关于 / About** tab at the end of the `dsh-ear` settings page: a jumpable GitHub repo row (`@WizisCool/dsh-ears` with the thesvg.org mark), installed version, MIT license, and dsh compatibility. Identity rows have no hint copy. Check for updates is click-only; the Host compares npm `latest` and never installs. Until the package is published the check reports that honestly.
- A General-tab display-name picker for the settings page: `dsh-ears` (default) or `语音` / `Voice`.
- Synthesized voice-input click (no audio files) on start/stop. Toggle is in General, on by default.
- Alibaba Cloud Model Studio (百炼) as a cloud ASR provider: DashScope sync `multimodal-generation` for Qwen3-ASR-Flash and Fun-ASR-Flash / Qwen-Audio-3.0-ASR-Flash. Users enter an HTTPS origin, a dedicated API key, and a model name. Recordings on this provider are capped at 300 seconds. Filetrans / realtime remain out of scope.
- Cloud ASR API keys are now per provider, with explicit names and a grouped Host settings file: `groq`, `customOpenAi`, and `bailian` each have their own `apiKey` (and model / endpoint / host). The previous flat `cloudAsrApiKey` is Groq-only after rewrite.
- Codex-like dsh composer microphone control with live Web Speech input.
- Native taskbar-style recognition card above the composer: standalone Task/Goal geometry, status, stop action, and a full-width rolling microphone waveform sampled from the active input stream. The card follows dsh's typography and semantic surfaces, stays above queued messages and closest to the composer, preserves the composer microphone throughout the flow, and collapses smoothly before unmounting after completion. Its active listening dot now uses a restrained pulse that respects reduced-motion preferences.
- Optional dsh-owned transcript polishing using any configured provider/model route.
- Dedicated native dsh `settings.section` configuration page.
- Host-side local Whisper and OpenAI-compatible cloud ASR backends.
- Typed Typert Remote contracts, cancellation, bounded payloads, and draft-race protection.
- Whisper model availability UI: downloaded state, download action with tqdm-derived progress, and honest errors across pip/Homebrew/pipx/conda/Windows environments. The status reuses the row's hint line — spinner while checking, click-to-download, cancel-download, and delete-model (two-step confirm) links, no floating blocks or state dots — and later queries are instant after a one-time authoritative model-table load. Cancelling a download also removes its partial file.
- Per-field validate-on-edit settings model (D-024): each edited field is validated alone, red errors appear only for invalid user input and real failures, untouched fields never show errors, and an incomplete polishing pair leaves polishing dormant. Guidance prompts for "not yet configured" states are removed, and the first-load alert appears only after the automatic retry fails, with neutral wording.
- Host-side settings validation is field-level only: the registration validate no longer rejects a Groq API key write while the cloud model is unselected or a custom provider without an endpoint, removing the key-vs-model deadlock; runtime readiness stays guarded by the transcribe RPC and the microphone gating.
- Whisper download completion markers: a model file only counts as downloaded when its `.dsh-ears-done` sidecar exists; marker-less files are reported as not downloaded and orphaned markers are removed (closes the crash-residue decision D-019).
- Local Whisper transcription pre-flight: recording is rejected with a settings-page hint when the CLI or a downloaded, marked model is missing, instead of the CLI auto-downloading weights inside the transcription timeout.
- Composer microphone availability gating: the button grays out with a bilingual tooltip when the selected backend is reported unavailable, the Whisper model is downloading, or the model file with its marker is missing (positive signals only; active flow states are never gated).
- Cloud ASR provider presets (D-023): a Host-side provider registry with the Groq preset (pinned endpoint, live model listing, inline `role('secret')` API key) and the Custom OpenAI-compatible provider (free endpoint/model, `whisper-1` default). Future providers plug in as registry entries.
- `dshEars/listCloudProviderModels` RPC: replicates the dsh-llm-pi-ai catalog pattern (`GET {baseUrl}/models`, bearer header, bounded parse, registry filter) with a 15-second timeout and a 30-second failure negative cache.
- Grouped recognition selector: 本地/云提供商 groups with a separator and the Custom OpenAI-compatible entry, rendered through the primitives `MenuLabel`/`MenuSeparator`; menu entries map onto the existing `asrBackend` + `cloudAsrProvider` fields.
- Cloud-readiness microphone gating: the cloud backend reports unavailable until a provider's key and model are configured.
- Customizable polish system prompt (D-029): the Polishing tab gains an optional `polishPrompt` field that replaces the built-in default system prompt entirely when non-empty; blank means "use the built-in default". The host always appends an invisible output-contract guard (return only the polished text; treat the transcript as data) to a custom prompt. The editor is a multiline textarea with a live `n/4000` character counter, a Reset-to-default action that stages an empty draft, and a read-only "View default" expand showing the shipped prompt; an over-length prompt is invalid and is skipped by auto-save until shortened (D-024/D-031).

### Hardened

- The built-in polish prompt is now a multilingual ASR-editing contract instead of a Chinese-targeted one-liner: it preserves the transcript's original language and technical/code terminology (brands, version markers, path/CLI entities), resolves spoken self-corrections ("not X, but Y"), formats explicit enumerations as numbered lists, segments long transcripts into paragraphs, and ships few-shot examples, while keeping the transcript-is-data and output-only-the-polished-text rules.
- The recognition bar now names each stage (starting, listening, transcribing, polishing) and stays up with the existing error line when capture fails, or a polish-specific line when polish fails, instead of adding a separate toast.
- Polish results are applied even if the composer has not flushed the committed raw draft yet. A failed Host polish keeps the raw transcript and surfaces `润色失败，已保留原文` on the bar. If an explicit reasoning-effort call fails or returns the raw transcript unchanged, the Host retries once with the model default. A real route failure now rejects the RPC instead of silently returning the raw text.
- Light/dark theme adaptation through dsh semantic tokens.
- Model → microphone → send visual ordering on dsh rc.6.
- MediaRecorder stop/error/teardown cleanup and Web Speech silent abort.
- Bounded local Whisper files, cloud response bodies, and ASR payloads.
- Whisper helper scripts exit via `os._exit`, avoiding the Homebrew python/torch/openblas OpenMP-teardown SIGSEGV (isolated to the child process; download results stay authoritative).
- Whisper model state actions surface Host/Remote failures and retain the last known state instead of silently ignoring failed requests.
- Cloud ASR requests now have a 120-second timeout; streamed polish output is bounded and falls back to raw text when oversized.
- Unknown ASR backend/model identifiers are rejected, canceled Whisper probes short-circuit, and Host/Client Remote cancellation metadata is kept in parity.
- Stale Whisper action responses, late aborted polish results, and MediaRecorder start failures are handled without overwriting current state or leaking microphone tracks.
- Failed Whisper model operations retain an actionable retry/cancel/delete path in the settings row.
- The Whisper model manager is a per-service disposable instance: dispose kills an active download and removes its partial file, and interpreter/model-table discovery failures are negative-cached for 30 seconds instead of re-spawning probes.
- Failed transcriptions carry the whisper stderr tail (bounded to 800 characters) instead of a bare exit code.
- Windows probing resolves `python.exe`/`py.exe` launchers with PATHEXT expansion (documented as not yet smoke-tested on Windows).
- Fake-python integration tests cover the full model lifecycle: download/cancel/delete, progress parsing, completion markers, dispose cleanup, and failure negative-caches.
- The `dsh-ear` settings page gains a leading **通用 / General** tab as its default landing tab. It hosts the voice-input keyboard shortcut — an enable switch plus a Raycast-style recorder — and the recognition language and recording-limit rows that moved here from the Recognition tab.
- In-page voice-input keyboard shortcut (D-028): default `Ctrl+Shift+Space` on all three platforms starts/stops voice input while the dsh page is focused and the composer is visible without a modal overlay; recording press stops and transcribes, transcribing/polishing presses are ignored, IME composition and key auto-repeat never trigger it, and when the microphone is gated the shortcut focuses the grayed button so its existing bilingual tooltip explains why. The listener lives in the composer microphone and ignores events inside `[role="dialog"]`.
- Hand-written shortcut module (`src/shortcut.ts`, no third-party dependency, per the D-025 precedent and the keyboard-shortcut research in `.agent/research/voice-dictation-shortcuts.md`): layout-stable `KeyboardEvent.code` key tokens, canonical `ctrl+shift+space` storage, platform-aware display (⌃⌥⇧⌘ on macOS). Modifier-only chords are valid. Bare letter/digit/text-action keys and Alt/Option+letter chords are rejected. Browser/OS-reserved chords warn amber without blocking the write. Escape cancels capture; Reset restores the default. Host settings validation enforces the same chord rules on every write.

### Fixed

- The polishing prompt's View default / Reset actions no longer reuse the absolutely positioned shortcut-reset class, which had stacked both buttons and made View default miss its click.
- The recognition-bar discard control now uses the same 14px `IconTrashOutline16` as the in-progress Goal row, instead of a larger 16px glyph.
- Capturing a two-modifier shortcut such as `Ctrl+Shift` now keeps both keys. The recorder used to replace the held set on each release, so only the last remaining modifier was saved.
- Changing the settings display name now updates the left-rail nav as well as the page title. Switching dsh between 中文 and English also retargets the Voice / 语音 label.
- Voice shortcuts may be a modifier-only chord such as `Ctrl` or `Ctrl+Shift`. Releasing the modifiers during capture saves that chord. On macOS, Control is kept when capturing Control+another key even if the browser omits `ctrlKey`.
- The shortcut reset control now sits inside the same 240px lane as the other settings fields, so opening the page no longer grows or shifts the row.
- The General tab now hides the shortcut recorder when the voice shortcut is off, matching the polishing tab's progressive disclosure.
- Silent or too-short Bailian recordings (including `qwen-audio-3.0-asr-flash` HTTP 400) now end quietly with no polish, instead of a red `语音识别上游错误： internal: Cloud ASR request failed with HTTP 400`.
- The live recognition waveform again fills the bar after the status label: a previous overflow tweak had made the label grow and pushed a smaller waveform to the right.
- The composer microphone and the dsh-ear settings page no longer crash on render: the UI-locale hook now calls `locale.subscribe` as a method instead of passing the unbound function.
- Clicking the microphone and stopping without speaking no longer shows `请检查配置后重试`. Empty transcripts and `no-speech` just close the bar.
- Upstream ASR and polish failures stay on the bar as a red `语音识别上游错误：` / `润色上游错误：` line with the raw service code, then dismiss themselves. Configuration problems stay amber.
- Recognition and polish errors on the status bar now dismiss themselves after a short delay, so a failed attempt no longer occupies the composer. Empty Bailian / cloud transcripts use the same path, with amber `请检查配置后重试` copy instead of a sticky red `语音输入失败，点击重试`.
- Host-backed recording starts sooner: the microphone is warmed on hover or focus, and Web Speech no longer waits for a second `getUserMedia` analyser before it is ready to listen.

- Closing the polishing toggle no longer flashes `正在润色…`: the client only enters the polishing state when `polishingEnabled` is on. An enabled toggle still asks the Host even if the local provider/model pair is empty.
- The shortcut recorder now gives live feedback while only modifier keys are held: the capture button shows the pressed modifiers (e.g. `Ctrl+Shift+…`). Releasing every modifier commits that modifier-only chord. A non-modifier key still completes the capture and Escape still resets and cancels.
- Modifier+character chords are now allowed, revising the D-028 shortcut rule: letters and digits with Ctrl/Shift/Meta record and save as valid shortcuts, with browser/OS-reserved combinations (Ctrl+letter/digit, Cmd+letter/digit, and the reserved Ctrl+Shift/Cmd+Shift sets) shown as amber warnings instead of being blocked. Bare letters/digits/text keys and Alt/Option+letter chords (which type special characters on macOS) are still rejected, and the error copy explains why.
- The voice hotkey now **outranks text input**: the composer listener runs on the window capture phase and calls `preventDefault`/`stopPropagation` on a matching chord, so pressing the shortcut inside the composer or any text field triggers voice input instead of being swallowed by the editor. Non-matching, disabled, or gated chords leave the event untouched.
- The shortcut recorder control is now right-aligned like the other settings rows: the capture pill uses the same content-width, `flex-end` alignment as the shipped selectors (it no longer sits at the left of the 240px API-key lane), and the Reset-to-default action sits directly next to the pill instead of floating at the far edge. At narrow widths the pill stretches full-width with the reset action below it.

### Changed

- The default recognition language is empty and follows the dsh English / 中文 setting. A value the user types is kept.

- Settings field hints now follow the official dsh plugin one-clause style instead of multi-sentence tutorials.

- The built-in default polish system prompt is now the tighter ASR-cleaning contract: stay close to the speaker, repair ASR/technical terms, list only explicit enumerations, and output only the cleaned text. A custom `polishPrompt` still replaces it entirely.
- The `dsh-ear` settings page drops the card wrapper and Save/Discard footer (D-031). Rows match native General: hairline dividers, no card surface. Valid edits auto-save after 400 ms, on blur, or when the panel closes; an invalid draft is skipped and stays local; a failed write keeps the drafts and does not retry in a loop.
- Compatibility now covers dsh `0.1.0-rc.6` and `0.1.0-rc.7`. Peer ranges stay `^0.1.0-rc.6`; the compile/test baseline is the exact `0.1.0-rc.7` packages.
- Tab layout: the settings page is now 通用 / General → 识别 / Recognition → 润色 / Polishing, with General as the default landing tab; the Recognition tab keeps only ASR backend/provider/model/key rows.
- The language and recording-limit rows moved from the Recognition tab to the General tab (their validation, defaults, and save semantics are unchanged).

- Plugin configuration card title renamed to `dsh-ear`.
- The read-only settings hint now explains the settings-provider condition and the required fix (zh/en).
- Plugin configuration moved to a dedicated `settings.section` page beside General, Models, and Plugins, with grouped Recognition/Polishing cards styled like the shipped settings pages.
- The settings page splits Recognition and Polishing into Plugins-style tab cards.
- Settings fields use the General/Permissions row pattern with pill Menu selectors. The live save model is the D-031 per-field auto-save (see Changed above); an earlier D-026 Save/Discard footer was tried and removed.
- Auto-save feedback follows the native pattern: no success/saving text, only a failure line (with row-level red hints for invalid values).
- Backend labels are clean (`Web Speech`, `Local Whisper`, `Cloud ASR`); only the active backend's configuration rows are shown, the selector hint follows the active backend, and each backend's settings still persist independently across switches.
- Polishing rows were renamed (Provider / Model) with simplified hints and display-name-only pickers; the polishing toggle stays a pill selector (a switch variant was tried and reverted).
- A per-route reasoning-effort picker (dsh `resolveModelInfo`-driven, model default supported) was added to the polishing settings (zh/en copy kept in sync).
- A failed first settings fetch now shows a dedicated load-failure hint with one delayed retry instead of the misleading read-only message; Host-side updates require a `dsh web` restart (the browser bundle updates on refresh).
- Switching the polish provider clears the stale model and reasoning-effort selection, so another provider's models can never linger in the picker.
- The provider/model pair rule now applies only while polishing is enabled; the reasoning picker labels the adapter's `off` effort as an explicit switch-off option.
- Polishing is now disabled by default, its configuration rows appear only after the toggle is enabled (progressive disclosure), the meaningless no-polish picker option is gone, an enabled polish requires a complete provider/model pair, and reasoning-effort labels use the adapter's native names with an untranslated `Default` entry (matching the composer model selector).
- The not-downloaded hint no longer promises automatic first-use downloads; the Local Whisper hint documents that `medium` and larger models need a GPU or a faster local runtime within the 120-second limit.
- Cloud ASR credentials moved from dsh credential references to a plugin-owned `role('secret')` inline API key (write-only across the plugin wire, redacted `getSettings`, absent=keep/set/clear patch semantics), reversing D-014 for the cloud ASR surface; the `cloudAsrCredentialRef` field is removed (package unreleased, no migration).
- The Recognition backend selector became a single grouped menu with bilingual group labels and per-provider hint text; the pinned Groq endpoint is no longer shown as a row (presets hide it entirely), and the Groq model row shows live-fetched models (empty until a key is configured, inline warning plus retry on fetch failure, stale-model notice when the saved model leaves the live list).

### Deferred

- Emotion recognition and emotion UI.
- Additional cloud-provider-specific protocols.
- Compatibility claims for dsh releases other than rc.6 and rc.7.
