dsh-model-switch 0.4.20 → 0.4.22

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.md CHANGED
@@ -1,208 +1,218 @@
1
- # Model Switch
2
-
3
- English | [中文](README.zh.md)
4
-
5
- Choose one explicit route for Main, Subagents, Web Search, image generation, the active conversation, and Plan execution in DeepSeek Harness. Model Switch uses public DSH services and provider-owned adapters; it does not patch DSH Core or manage provider credentials.
6
-
7
- <p align="center"><img src="docs/screenshots/composer-picker.png" alt="Composer Picker with Model, Effort, Context, and Fast controls" width="314"></p>
8
-
9
- The picker retains keyboard focus when changing panes or closing search, without overriding search autofocus. Escape from the summary closes the picker and returns focus to its trigger.
10
-
11
- ## Routes
12
-
13
- | Route | Behavior |
14
- | --- | --- |
15
- | Main model | Default provider, model, and optional effort for newly created sessions. Existing sessions are not migrated. |
16
- | Subagent | Follow the active parent route, or use a fixed provider/model/effort. Workflow overrides remain authoritative. |
17
- | Composer Picker | Changes only the active session and submits the exact catalog model id. Main defaults are preserved. |
18
- | Plan Review | Chooses the execution model before the Plan approval response is sent. |
19
- | Web Search | Keeps official `web_search`; after deployment opt-in, routes through the selected, dynamically declared provider search adapter. |
20
- | Image generation | Provides one stable `generate_image` tool routed through a selected Codex or Grok adapter. |
21
-
22
- Invalid, unavailable, or unsupported routes fail explicitly. Model Switch never silently falls back to another provider or model. Composer icons identify the execution runtime: DSH-owned LLM routes use the theme-adaptive DSH whale; providers declaring the Agent role use their own mark. Sidebar quota icons remain provider-specific.
23
-
24
- Idle blank sessions may pick an External Agent. Submission, pending-first-turn, and running state reserve the selected execution runtime even before a token or native binding exists: native Antigravity keeps its model/effort choices; DSH keeps LLM-provider choices but disables Agent providers. After the first accepted user message, Composer and Plan Review keep Agent-role rows visible but disabled unless the session is already native-bound. After the active session successfully opens a native Antigravity session, the pickers read its plugin-owned binding and disable other providers while keeping available Antigravity model and effort controls. The DSH global picker lock remains authoritative. Binding reads refresh on mount, input-phase, request-activity or catalog changes, and before selection; failed reads show a localized alert and preserve a known native lock. These reads do not start the native runtime or gate conversation history. Antigravity owns the separate execution-time guard; Model Switch does not create custom Core history events.
25
-
26
- The existing 3082 lab replay probe is `node scripts/check-lab-runtime-lock.mjs [native|runtime-absent|plugin-absent]`. It checks the dedicated QA conversation under the already-established condition; it does not disable plugins, select a model, or send a turn. `node scripts/check-lab-native-execution.mjs` verifies the saved guard rejection, restored native continuation, DeepSeek-parent/Antigravity-child completion, and restoration of temporary subagent preferences through public history streams.
27
-
28
- ## Configure Main and Subagents
29
-
30
- Open **Settings → Model Switch**. Main changes affect new sessions only. Subagents may follow Main or use a fixed provider, model, and effort.
31
- Official DSH `0.1.7-alpha.2` makes ConfigForms memory-only in non-loopback browsers. This page shows an unavailable message there; open Settings on the Host loopback URL instead. The composer picker refuses model changes in that mode because the published `session.selectModel` also persists the deployment default, which cannot be safely restored without a writable Main ConfigForm. Remote editing and session-only selection require an upstream authenticated ConfigForm seam and a session-only selection API; this plugin does not bypass the official policy.
32
-
33
- Changing the Main or fixed Subagent provider/model replaces the previous model's effort with the target model's default. Models without reasoning support receive no effort.
34
-
35
- ![Model Switch settings with a fixed Subagent route](docs/screenshots/settings-subagent.png)
36
-
37
- Follow Main resolves the active parent request first, then the configured Main default. A fixed route is injected before the official Subagent descriptor is created. DSH 0.1.2-alpha.4 carries provider, model, and fixed reasoning effort in that descriptor.
38
-
39
- ## How custom models appear in the Picker
40
-
41
- Model Switch does not turn an arbitrary string in its own settings into a model. A provider plugin must publish the model to the official DSH model catalog first:
42
-
43
- ```text
44
- Provider configuration
45
- → provider publishes model rows to the DSH catalog
46
- → the active session Model Directory exposes provider/model metadata
47
- → Model Switch groups those catalog rows
48
- → the Picker submits the original provider id and model id
49
- ```
50
-
51
- The catalog supplies the provider name, model id/name, reasoning efforts, and default effort. A saved route that is no longer present remains visible as unavailable in Settings; the Picker does not pretend it is routable.
52
-
53
- ### Variant id grammar
54
-
55
- Model Switch groups catalog rows by provider and by the model id after peeling these suffixes:
56
-
57
- | Catalog model id | Picker variant |
58
- | --- | --- |
59
- | `acme-v1` | Standard row |
60
- | `acme-v1-fast` | Fast |
61
- | `acme-v1-128k` | Context 128K |
62
- | `acme-v1-1m` | Context 1M |
63
- | `acme-v1-1m-fast` | Context 1M + Fast |
64
-
65
- Rules:
66
-
67
- - `-fast` creates the Fast axis.
68
- - `-<n>k` and `-<n>m` create Context tiers; suffixes may be combined with `-fast` in either order.
69
- - `reasoning.efforts` creates the Effort choices; `reasoning.defaultEffort` selects the initial value.
70
- - Switching Fast, Context, or Thinking variants keeps the current effort only when the target catalog row supports it; otherwise the target row's default effort (or no effort) is used.
71
- - Reasoning metadata marks a catalog row as Thinking-capable.
72
- - Unrecognized ids remain independent model families; Model Switch never drops them.
73
-
74
- To support a combined selection, the provider must publish the combined row. Publishing only `acme-v1-fast` and `acme-v1-1m` cannot represent Fast + 1M; publish `acme-v1-1m-fast` as well. The Picker never synthesizes a model id that the provider did not publish.
75
-
76
- ## Plan Review
77
-
78
- Plan Review owns an execution-model draft separate from Main. **Confirm** first commits that model to the active session, then answers the pending Plan review. A refused commit leaves the review pending and retryable; a Session switch the Host accepted but whose deployment default could not be restored is announced after the answer instead of withholding it. **Reject** and **Discuss in chat** do not execute the Plan.
79
-
80
- **Reject** sends the option the asker offered, and any text typed in the card's feedback field as the answer's own `custom` text — plan mode reports that text back to the model as the reason to keep planning, so a rejection can carry what to change. **Discuss in chat** withdraws the takeover so the composer returns and the human speaks in their own words; it uses the Host's `dismiss()` where published and the older generation's `cancel()` otherwise, and names the Host when neither exists. Answering a settled review closes Confirm and Reject, while Discuss and the reload affordance stay reachable so the card always has a way back. Failures keep their wire code (`session/writer-held: …`) visible instead of a bare localized sentence.
81
-
82
- **Set as goal and run** arms this Plan as the session's goal and only then answers it, so `dsh-goal-round-driver` keeps the Plan moving after plan mode exits. It calls the released `remote.goals.create`; when a non-complete goal already exists it clears that one first so the new Plan starts with a fresh round cap, and says so in the card. A refusal keeps the review pending with its wire code (`GOAL_AGENT_NOT_LIVE: …`), and a deployment that mounts no goal service disables the button with an explicit reason while the other three actions keep working. The objective is the Plan's full markdown, trimmed — the goal domain imposes no length limit, and the objective rides along as model-visible context every round, so a long Plan costs tokens on each continuation.
83
-
84
- The card body shows only the Plan's first-line title and first-paragraph summary; the **full plan** opens automatically in the right sidebar through official ui-plan (the header's "View full plan" reopens it any time). The takeover entry cannot render the official `conversation.plan-review.actions` slot (a child slot allows exactly one declarer), so the card drives ui-plan's registered opener instead — the preview document's address and params stay ui-plan's own construction (see `docs/seam-gap.md`).
85
-
86
- ![Plan Review with an execution-model picker](docs/screenshots/plan-review.png)
87
-
88
- **Plan-exit context compaction (0.4.20):** when an approval **switches the execution model** (provider/model change; an effort-only change does not count) and the estimated next-request input exceeds the threshold (in tokens, default 200,000, adjustable in Settings, 0 = off), the card first calls the Host's compaction endpoint — summarizing history once **with the switched-to model** — and only then commits the model and answers; **Set as goal and run** compacts before arming the goal too. Approvals without a model switch never trigger it. Nothing compactable proceeds after a notice; a failed compaction aborts execution, keeps the review pending, and stays retryable.
89
-
90
- ## Send-time context protection
91
-
92
- When a sent message will use a different model or context-tier id than the last request, Model Switch checks the full pending request against the target window before that request is sent. Choosing a model in the picker does not compact. If the target is over budget, history is compacted once with the previous model through a private compaction engine; DSH's own automatic compaction settings are not changed. Progress appears as plugin notices in the transcript. The budget includes the completion notice and pending model-change notice; an oversized summary blocks the request. Failure keeps the user message in the session and does not send the target request or emit a switch-success notice. Turn the behavior off with **Settings → Model Switch → Check and compact context on send** (on by default). Native External Agent runtimes are not treated as DSH-compactable history.
93
-
94
- ## Auto review model
95
-
96
- The official `@deepseek-ai/dsh-experimental-auto-review` (the per-call reviewer for the Auto permission preset) hardwires its review requests onto the session's own route. This plugin takes that row over through a `cordis.patch.yml` row replacement: the official row is disabled and `dsh-model-switch/auto-review` mounts the **same official `apply`** under a scoped child context whose `llm.stream` `provider/model` are rewritten to the reviewer model pinned in Settings. The "Auto review model" card defaults to off = reviews follow the session model; turning it on seeds from Main and stays editable, and saved changes apply to the very next review. When the optional auto-review bundle never started with the profile the row stays unmounted and the Auto preset keeps its official off state; an official row already active elsewhere is reported explicitly instead of producing a second `registerAuto`.
97
-
98
- ## What Model Switch does not change
99
-
100
- - `web_fetch` and its configured provider
101
- - Vision routing, `read_image`, and ordinary chat attachments
102
- - Provider login, credentials, or provider settings cards
103
- - Official Agent Presets
104
- - Existing provider-specific image tools
105
- - Existing sessions when the Main default changes
106
- - Global compaction policy, titles, or DSH Core
107
-
108
- ## Installation
109
-
110
- Model Switch ships prebuilt code, so an install needs no build authorization. Install it from the npm registry:
111
-
112
- ```sh
113
- dsh plugin --profile web add --force dsh-model-switch
114
- ```
115
-
116
- A packed tarball installs the same build without a registry:
117
-
118
- ```sh
119
- dsh plugin --profile web add --force ./dsh-model-switch-0.4.17.tgz
120
- ```
121
-
122
- Then install only the provider adapters you use. This release was checked against official DeepSeek Harness `0.1.7-rc.2` and verified on `0.2.0-rc.2`:
123
-
124
- ```sh
125
- dsh plugin --profile web add --force \
126
- https://github.com/NOirBRight/dsh-llm-providers-ui/releases/latest/download/dsh-llm-providers-ui-0.2.14.tgz
127
- dsh plugin --profile web add --force \
128
- https://github.com/NOirBRight/dsh-llm-codex/releases/latest/download/dsh-llm-codex-0.3.23.tgz
129
- dsh plugin --profile web add --force \
130
- https://github.com/NOirBRight/dsh-llm-grok/releases/latest/download/dsh-llm-grok-0.3.19.tgz
131
- ```
132
-
133
- ### Search routing (0.4.7)
134
-
135
- Providers declare independent search adapters and model metadata on the existing Host `ModelSwitchAdapterRegistry`. The browser receives only id/name/model metadata over authenticated Connection RPC, not adapters or credentials. Registration/unload updates subscribers; in-place catalog changes are picked up by the bounded 20-second heartbeat. `ProviderDirectory` remains the client role/usage owner, not a second Host execution registry. Published 0.2.8 `sortCatalogGroups` only ranks hardcoded LLM routes; Model Switch ranks live `catalogRoutes` itself so Agent catalog ids follow saved card order when the Owner publishes them. Native `catalogId` / `unknown` account styling still need a newer Owner than published 0.2.8.
136
-
137
- DeepSeek uses the official public `DeepSeekSearchProvider`, the existing `web-search-deepseek` settings and credential service. Codex uses its existing ChatGPT credentials; Grok uses its existing subscription token and provider-owned Responses search. Native conversational networking alone does not create a search option. Missing credentials, invalid settings, unsupported models and unavailable adapters fail explicitly, without fallback.
138
-
139
- **Registering an adapter does not select the global Web route.** Installation leaves the deployment's existing Web configuration untouched. To opt in, set the existing Web entry's `searchProvider` to `model-switch` while retaining **every other existing config field**, especially `fetchProvider`. DSH id-targeted patches replace the entire config rather than deep-merging it: copy the complete effective config from `dsh --profile web --dump-config`, then change only searchProvider in the deployment/user patch. Do not copy an assumed `fetchProvider: http` over a custom pin.
140
-
141
- After opting in, configure the requested search provider/model in Model Switch Settings. No complete supported route means `WEB_PROVIDER_CONFIGURED_UNAVAILABLE` through the official selector; there is no automatic DeepSeek fallback. Without the global pin, the dropdown does not control `web_search`. An unpinned Web service can become ambiguous when multiple search providers are usable. Before uninstalling Model Switch, restore the deployment's prior search pin; otherwise the official selector correctly reports a missing configured provider. The official `web_search` and `web_fetch` tools are never replaced.
142
-
143
- Release-aligned search regression: `DSH_RELEASE_ANCHOR=/path/to/official/install/package.json pnpm exec vitest run --config tests/vitest.release.config.ts`. See [search audit and acceptance](docs/search-provider-audit.md) for exact baselines, dependencies and live status.
144
-
145
- If `dsh-composer-picker` is installed, remove it from the profile before enabling Model Switch. Model Switch already owns the Composer Picker and Plan Review seat; two owners produce duplicate or competing UI.
146
-
147
- Production profiles must use a released registry version or GitHub tag rather than workspace-local dependencies. Restart the selected DSH profile after installation or route changes.
148
-
149
- ## Compatibility
150
-
151
- DSH Host peers and development dependencies accept `>=0.1.7-alpha.2`, including rc.1 and later releases. The development lockfile resolves to rc.2. Cordis accepts `>=4.0.4 <5.0.0`.
152
-
153
- Verified releases: `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, `0.2.0-rc.2`. The 0.2.0 claim covers the Host's renamed question verbs, its unawaited `session.selectModel` default write, and its returned `ModelDirectory.select` refusal, each verified in the lab and on a live desktop session.
154
-
155
- The verified releases in `package.json#dsh.compatibility.dshReleases` are evidence, not an allowlist. Review public API changes and test new Host releases before adding a verification claim.
156
-
157
- ## Development
158
-
159
- Requires Node 22.19+ and pnpm.
160
-
161
- ```sh
162
- pnpm install
163
- pnpm run check
164
- ```
165
-
166
- `check` builds Host and Client artifacts, runs unit and Cordis/Settings composition tests, validates the extracted package, and verifies reproducible bundles. Product scope is defined in [PRODUCT.md](PRODUCT.md); implementation constraints live in [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md).
167
-
168
- ## Release installation (Latest)
169
-
170
- Explicit model routing for Main, Subagent, Composer, Plan Review, and capability tools, checked on official DeepSeek Harness `0.1.7-rc.2` and `0.2.0-rc.2`. The release artifact contains built Host/Client files, not sibling source or local paths.
171
-
172
- Latest installation (the asset filename must match the current latest release):
173
-
174
- ~~~sh
175
- dsh plugin --profile web add --force \
176
- https://github.com/NOirBRight/dsh-llm-providers-ui/releases/latest/download/dsh-llm-providers-ui-0.2.14.tgz
177
- dsh plugin --profile web add --force \
178
- https://github.com/NOirBRight/dsh-model-switch/releases/latest/download/dsh-model-switch-0.4.16.tgz
179
- ~~~
180
-
181
- Fixed-version installation:
182
-
183
- ~~~sh
184
- dsh plugin --profile web add --force \
185
- https://github.com/NOirBRight/dsh-llm-providers-ui/releases/download/v0.2.14/dsh-llm-providers-ui-0.2.14.tgz
186
- dsh plugin --profile web add --force \
187
- https://github.com/NOirBRight/dsh-model-switch/releases/download/v0.4.16/dsh-model-switch-0.4.16.tgz
188
- ~~~
189
-
190
- Update, uninstall, and verify:
191
-
192
- ~~~sh
193
- # Update to the latest Release
194
- dsh plugin --profile web add --force \
195
- https://github.com/NOirBRight/dsh-llm-providers-ui/releases/latest/download/dsh-llm-providers-ui-0.2.14.tgz
196
- dsh plugin --profile web add --force \
197
- https://github.com/NOirBRight/dsh-model-switch/releases/latest/download/dsh-model-switch-0.4.16.tgz
198
- # Verify the loaded version
199
- dsh plugin --profile web list
200
- # Uninstall only this plugin
201
- dsh plugin --profile web remove dsh-model-switch
202
- ~~~
203
-
204
- Configuration: use the plugin section in Settings for Web UI plugins, or the profile dsh.profile.bundles entry for Host-only plugins. Start with this README's minimal YAML/JSON example and provide credentials/backend addresses explicitly.
205
-
206
- Rollback: reinstall the immutable v0.4.15 archive and restore the recorded profile; no Host downgrade is required. Inspect journalctl --user -u dsh-web.service; never link a checkout into production.
207
-
208
- Release and integrity: [v0.4.16](https://github.com/NOirBRight/dsh-model-switch/releases/tag/v0.4.16) · [SHA256](https://github.com/NOirBRight/dsh-model-switch/releases/download/v0.4.16/dsh-model-switch-0.4.16.tgz.sha256).
1
+ # Model Switch
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ Choose one explicit route for Main, Subagents, Web Search, image generation, the active conversation, and Plan execution in DeepSeek Harness. Model Switch uses public DSH services and provider-owned adapters; it does not patch DSH Core or manage provider credentials.
6
+
7
+ <p align="center"><img src="docs/screenshots/composer-picker.png" alt="Composer Picker with Model, Effort, Context, and Fast controls" width="314"></p>
8
+
9
+ The picker retains keyboard focus when changing panes or closing search, without overriding search autofocus. Escape from the summary closes the picker and returns focus to its trigger.
10
+
11
+ ## Routes
12
+
13
+ | Route | Behavior |
14
+ | --- | --- |
15
+ | Main model | Default provider, model, and optional effort for newly created sessions. Existing sessions are not migrated. |
16
+ | Subagent | Follow the active parent route, or use a fixed provider/model/effort. Workflow overrides remain authoritative. |
17
+ | Composer Picker | Changes only the active session and submits the exact catalog model id. Main defaults are preserved. |
18
+ | Plan Review | Chooses the execution model before the Plan approval response is sent. |
19
+ | Web Search | Keeps official `web_search`; after deployment opt-in, routes through the selected, dynamically declared provider search adapter. |
20
+ | Image generation | Provides one stable `generate_image` tool routed through a selected Codex or Grok adapter. |
21
+
22
+ Invalid, unavailable, or unsupported routes fail explicitly. Model Switch never silently falls back to another provider or model. Composer icons identify the execution runtime: DSH-owned LLM routes use the theme-adaptive DSH whale; providers declaring the Agent role use their own mark. Sidebar quota icons remain provider-specific.
23
+
24
+ Idle blank sessions may pick an External Agent. Submission, pending-first-turn, and running state reserve the selected execution runtime even before a token or native binding exists: native Antigravity keeps its model/effort choices; DSH keeps LLM-provider choices but disables Agent providers. After the first accepted user message, Composer and Plan Review keep Agent-role rows visible but disabled unless the session is already native-bound. After the active session successfully opens a native Antigravity session, the pickers read its plugin-owned binding and disable other providers while keeping available Antigravity model and effort controls. The DSH global picker lock remains authoritative. Binding reads refresh on mount, input-phase, request-activity or catalog changes, and before selection; failed reads show a localized alert and preserve a known native lock. These reads do not start the native runtime or gate conversation history. Antigravity owns the separate execution-time guard; Model Switch does not create custom Core history events.
25
+
26
+ The existing 3082 lab replay probe is `node scripts/check-lab-runtime-lock.mjs [native|runtime-absent|plugin-absent]`. It checks the dedicated QA conversation under the already-established condition; it does not disable plugins, select a model, or send a turn. `node scripts/check-lab-native-execution.mjs` verifies the saved guard rejection, restored native continuation, DeepSeek-parent/Antigravity-child completion, and restoration of temporary subagent preferences through public history streams.
27
+
28
+ ## Configure Main and Subagents
29
+
30
+ Open **Settings → Model Switch**. Main changes affect new sessions only. Subagents may follow Main or use a fixed provider, model, and effort.
31
+ Official DSH `0.1.7-alpha.2` makes ConfigForms memory-only in non-loopback browsers. This page shows an unavailable message there; open Settings on the Host loopback URL instead. The composer picker refuses model changes in that mode because the published `session.selectModel` also persists the deployment default, which cannot be safely restored without a writable Main ConfigForm. Remote editing and session-only selection require an upstream authenticated ConfigForm seam and a session-only selection API; this plugin does not bypass the official policy.
32
+
33
+ Changing the Main or fixed Subagent provider/model replaces the previous model's effort with the target model's default. Models without reasoning support receive no effort.
34
+
35
+ ![Model Switch settings with a fixed Subagent route](docs/screenshots/settings-subagent.png)
36
+
37
+ Follow Main resolves the active parent request first, then the configured Main default. A fixed route is injected before the official Subagent descriptor is created. DSH 0.1.2-alpha.4 carries provider, model, and fixed reasoning effort in that descriptor.
38
+
39
+ ## How custom models appear in the Picker
40
+
41
+ Model Switch does not turn an arbitrary string in its own settings into a model. A provider plugin must publish the model to the official DSH model catalog first:
42
+
43
+ ```text
44
+ Provider configuration
45
+ → provider publishes model rows to the DSH catalog
46
+ → the active session Model Directory exposes provider/model metadata
47
+ → Model Switch groups those catalog rows
48
+ → the Picker submits the original provider id and model id
49
+ ```
50
+
51
+ The catalog supplies the provider name, model id/name, reasoning efforts, and default effort. A saved route that is no longer present remains visible as unavailable in Settings; the Picker does not pretend it is routable.
52
+
53
+ ### Variant id grammar
54
+
55
+ Model Switch groups catalog rows by provider and by the model id after peeling these suffixes:
56
+
57
+ | Catalog model id | Picker variant |
58
+ | --- | --- |
59
+ | `acme-v1` | Standard row |
60
+ | `acme-v1-fast` | Fast |
61
+ | `acme-v1-128k` | Context 128K |
62
+ | `acme-v1-1m` | Context 1M |
63
+ | `acme-v1-1m-fast` | Context 1M + Fast |
64
+
65
+ Rules:
66
+
67
+ - `-fast` creates the Fast axis.
68
+ - `-<n>k` and `-<n>m` create Context tiers; suffixes may be combined with `-fast` in either order.
69
+ - `reasoning.efforts` creates the Effort choices; `reasoning.defaultEffort` selects the initial value.
70
+ - Switching Fast, Context, or Thinking variants keeps the current effort only when the target catalog row supports it; otherwise the target row's default effort (or no effort) is used.
71
+ - Reasoning metadata marks a catalog row as Thinking-capable.
72
+ - Unrecognized ids remain independent model families; Model Switch never drops them.
73
+
74
+ To support a combined selection, the provider must publish the combined row. Publishing only `acme-v1-fast` and `acme-v1-1m` cannot represent Fast + 1M; publish `acme-v1-1m-fast` as well. The Picker never synthesizes a model id that the provider did not publish.
75
+
76
+ ## Plan Review
77
+
78
+ Plan Review owns an execution-model draft separate from Main. **Confirm** first commits that model to the active session, then answers the pending Plan review. A refused commit leaves the review pending and retryable; a Session switch the Host accepted but whose deployment default could not be restored is announced after the answer instead of withholding it. **Reject** and **Discuss in chat** do not execute the Plan.
79
+
80
+ **Reject** sends the option the asker offered, and any text typed in the card's feedback field as the answer's own `custom` text — plan mode reports that text back to the model as the reason to keep planning, so a rejection can carry what to change. **Discuss in chat** withdraws the takeover so the composer returns and the human speaks in their own words; it uses the Host's `dismiss()` where published and the older generation's `cancel()` otherwise, and names the Host when neither exists. Answering a settled review closes Confirm and Reject, while Discuss and the reload affordance stay reachable so the card always has a way back. Failures keep their wire code (`session/writer-held: …`) visible instead of a bare localized sentence.
81
+
82
+ **Set as goal and run** arms this Plan as the session's goal and only then answers it, so `dsh-goal-round-driver` keeps the Plan moving after plan mode exits. It calls the released `remote.goals.create`; when a non-complete goal already exists it clears that one first so the new Plan starts with a fresh round cap, and says so in the card. A refusal keeps the review pending with its wire code (`GOAL_AGENT_NOT_LIVE: …`), and a deployment that mounts no goal service disables the button with an explicit reason while the other three actions keep working. The objective is the Plan's full markdown, trimmed — the goal domain imposes no length limit, and the objective rides along as model-visible context every round, so a long Plan costs tokens on each continuation.
83
+
84
+ The card body shows only the Plan's first-line title and first-paragraph summary; the **full plan** opens automatically in the right sidebar through official ui-plan (the header's "View full plan" reopens it any time). The takeover entry cannot render the official `conversation.plan-review.actions` slot (a child slot allows exactly one declarer), so the card drives ui-plan's registered opener instead — the preview document's address and params stay ui-plan's own construction (see `docs/seam-gap.md`).
85
+
86
+ ![Plan Review with an execution-model picker](docs/screenshots/plan-review.png)
87
+
88
+ ## Send-time context protection
89
+
90
+ When a sent message will use a different model or context-tier id than the last request, Model Switch checks the full pending request against the target window before that request is sent. Choosing a model in the picker does not compact. If the target is over budget, history is compacted once with the previous model through a private compaction engine; DSH's own automatic compaction settings are not changed. Progress appears as plugin notices in the transcript. The budget includes the completion notice and pending model-change notice; an oversized summary blocks the request. Failure keeps the user message in the session and does not send the target request or emit a switch-success notice. Turn the behavior off with **Settings → Model Switch → Check and compact context on send** (on by default). Native External Agent runtimes are not treated as DSH-compactable history.
91
+
92
+ ## Auto review model
93
+
94
+ The official `@deepseek-ai/dsh-experimental-auto-review` (the per-call reviewer for the Auto permission preset) hardwires its review requests onto the session's own route. This plugin takes that row over through a `cordis.patch.yml` row replacement: the official row is disabled and `dsh-model-switch/auto-review` mounts the **same official `apply`** under a scoped child context whose `llm.stream` `provider/model` are rewritten to the reviewer model pinned in Settings. The "Auto review model" card defaults to off = reviews follow the session model; turning it on seeds from Main and stays editable, and saved changes apply to the very next review. When the optional auto-review bundle never started with the profile the row stays unmounted and the Auto preset keeps its official off state; an official row already active elsewhere is reported explicitly instead of producing a second `registerAuto`.
95
+
96
+ ## Compaction model
97
+
98
+ Official compaction — `/compact` and DSH's automatic compaction — summarizes history with the conversation's target model by default. The "Compaction model" card (default off) pins that summarization route instead: turning it on seeds from Main and stays editable, and saved changes apply to the very next compaction. Leaving both fields empty returns to the official default. Saving while a compaction is running may interrupt it; it retries next time.
99
+
100
+ How the route reaches the engine depends on where the deployment runs its agent plane. The Web surface moves compaction into each agent preset's isolated `compaction` group, and profile patches cannot address rows inside a preset's `plugins` list — so this plugin replaces each shipped preset row wholesale (the same id-targeted mechanism the Web preset editor saves with) and swaps that group's official `compaction-basic` for `model-switch-compaction`, which mounts the **same official `BasicCompactionEngine`** with only `summarizationProvider`/`summarizationModel` overlaid (either field alone is ignored). Surfaces that compose the agent process-wide (the TUI) use the top-level row replacement instead. Two consequences: a DSH release that edits those presets needs the copies re-synced (this plugin's per-release verification covers it), and a deployment that edits presets in the Web editor writes its own wholesale row that then owns the merge.
101
+
102
+ Known issue (upstream; recorded in `docs/seam-gap.md`): while the working set stays above the official engine's pressure threshold, automatic compaction can fire on every step and keep re-attempting ranges whose summaries fail the engine's own "summary must be smaller" guard — a visible loop of automatic compactions that this plugin does not control (verified in a session A/B with the plugin enabled and disabled). Mitigations on the forwarded row config: `auto: false` (keeps `/compact`), a higher `thresholdRatio`, a smaller mounted tool schema, or a pinned compaction model whose summaries are leaner.
103
+
104
+ ## Provider groups
105
+
106
+ The "Provider groups" card stores one route group per provider — the compaction, subagent, and review routes saved together — and applies it automatically whenever the main model's **provider** changes, whether through the composer picker or by saving a different provider on the Main card. Applying a group is one full replacement: all three routes are written into the cards above in a single decision, overwriting their current values **including manual edits**, while anything the group does not pin reverts to that capability's default (compaction and review follow the conversation model, subagent follows main). Switching to a provider without a stored group leaves the current routes untouched, and a switch within one provider never rewrites them. The stored effect is profile-wide (the shared settings namespace), not per session, and only these two switch surfaces trigger it — a TUI or other switch leaves the routes alone. In the editor, a provider's draft seeds from the active routes the first time you configure it, each capability row's off state means "use the capability default" and is stored as such, and **Delete group** removes the provider's group entirely (clearing the stored field when the last group goes). Search and image routes are out of scope. A failed apply never fails the switch or the Main save: the picker logs a warning and commits anyway, and the Main card reports "Main model saved, but applying the provider group failed …" while the routes stay as they were.
107
+
108
+ ## What Model Switch does not change
109
+
110
+ - `web_fetch` and its configured provider
111
+ - Vision routing, `read_image`, and ordinary chat attachments
112
+ - Provider login, credentials, or provider settings cards
113
+ - Official Agent Presets
114
+ - Existing provider-specific image tools
115
+ - Existing sessions when the Main default changes
116
+ - Global compaction policy, titles, or DSH Core
117
+
118
+ ## Installation
119
+
120
+ Model Switch ships prebuilt code, so an install needs no build authorization. Install it from the npm registry:
121
+
122
+ ```sh
123
+ dsh plugin --profile web add --force dsh-model-switch
124
+ ```
125
+
126
+ A packed tarball installs the same build without a registry:
127
+
128
+ ```sh
129
+ dsh plugin --profile web add --force ./dsh-model-switch-0.4.17.tgz
130
+ ```
131
+
132
+ Then install only the provider adapters you use. This release was checked against official DeepSeek Harness `0.1.7-rc.2` and verified on `0.2.0-rc.2`:
133
+
134
+ ```sh
135
+ dsh plugin --profile web add --force \
136
+ https://github.com/NOirBRight/dsh-llm-providers-ui/releases/latest/download/dsh-llm-providers-ui-0.2.14.tgz
137
+ dsh plugin --profile web add --force \
138
+ https://github.com/NOirBRight/dsh-llm-codex/releases/latest/download/dsh-llm-codex-0.3.23.tgz
139
+ dsh plugin --profile web add --force \
140
+ https://github.com/NOirBRight/dsh-llm-grok/releases/latest/download/dsh-llm-grok-0.3.19.tgz
141
+ ```
142
+
143
+ ### Search routing (0.4.7)
144
+
145
+ Providers declare independent search adapters and model metadata on the existing Host `ModelSwitchAdapterRegistry`. The browser receives only id/name/model metadata over authenticated Connection RPC, not adapters or credentials. Registration/unload updates subscribers; in-place catalog changes are picked up by the bounded 20-second heartbeat. `ProviderDirectory` remains the client role/usage owner, not a second Host execution registry. Published 0.2.8 `sortCatalogGroups` only ranks hardcoded LLM routes; Model Switch ranks live `catalogRoutes` itself so Agent catalog ids follow saved card order when the Owner publishes them. Native `catalogId` / `unknown` account styling still need a newer Owner than published 0.2.8.
146
+
147
+ DeepSeek uses the official public `DeepSeekSearchProvider`, the existing `web-search-deepseek` settings and credential service. Codex uses its existing ChatGPT credentials; Grok uses its existing subscription token and provider-owned Responses search. Native conversational networking alone does not create a search option. Missing credentials, invalid settings, unsupported models and unavailable adapters fail explicitly, without fallback.
148
+
149
+ **Registering an adapter does not select the global Web route.** Installation leaves the deployment's existing Web configuration untouched. To opt in, set the existing Web entry's `searchProvider` to `model-switch` while retaining **every other existing config field**, especially `fetchProvider`. DSH id-targeted patches replace the entire config rather than deep-merging it: copy the complete effective config from `dsh --profile web --dump-config`, then change only searchProvider in the deployment/user patch. Do not copy an assumed `fetchProvider: http` over a custom pin.
150
+
151
+ After opting in, configure the requested search provider/model in Model Switch Settings. No complete supported route means `WEB_PROVIDER_CONFIGURED_UNAVAILABLE` through the official selector; there is no automatic DeepSeek fallback. Without the global pin, the dropdown does not control `web_search`. An unpinned Web service can become ambiguous when multiple search providers are usable. Before uninstalling Model Switch, restore the deployment's prior search pin; otherwise the official selector correctly reports a missing configured provider. The official `web_search` and `web_fetch` tools are never replaced.
152
+
153
+ Release-aligned search regression: `DSH_RELEASE_ANCHOR=/path/to/official/install/package.json pnpm exec vitest run --config tests/vitest.release.config.ts`. See [search audit and acceptance](docs/search-provider-audit.md) for exact baselines, dependencies and live status.
154
+
155
+ If `dsh-composer-picker` is installed, remove it from the profile before enabling Model Switch. Model Switch already owns the Composer Picker and Plan Review seat; two owners produce duplicate or competing UI.
156
+
157
+ Production profiles must use a released registry version or GitHub tag rather than workspace-local dependencies. Restart the selected DSH profile after installation or route changes.
158
+
159
+ ## Compatibility
160
+
161
+ DSH Host peers and development dependencies accept `>=0.1.7-alpha.2`, including rc.1 and later releases. The development lockfile resolves to rc.2. Cordis accepts `>=4.0.4 <5.0.0`.
162
+
163
+ Verified releases: `0.1.7-alpha.2`, `0.1.7-rc.1`, `0.1.7-rc.2`, `0.2.0-rc.2`. The 0.2.0 claim covers the Host's renamed question verbs, its unawaited `session.selectModel` default write, and its returned `ModelDirectory.select` refusal, each verified in the lab and on a live desktop session.
164
+
165
+ The verified releases in `package.json#dsh.compatibility.dshReleases` are evidence, not an allowlist. Review public API changes and test new Host releases before adding a verification claim.
166
+
167
+ ## Development
168
+
169
+ Requires Node 22.19+ and pnpm.
170
+
171
+ ```sh
172
+ pnpm install
173
+ pnpm run check
174
+ ```
175
+
176
+ `check` builds Host and Client artifacts, runs unit and Cordis/Settings composition tests, validates the extracted package, and verifies reproducible bundles. Product scope is defined in [PRODUCT.md](PRODUCT.md); implementation constraints live in [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md).
177
+
178
+ ## Release installation (Latest)
179
+
180
+ Explicit model routing for Main, Subagent, Composer, Plan Review, and capability tools, checked on official DeepSeek Harness `0.1.7-rc.2` and `0.2.0-rc.2`. The release artifact contains built Host/Client files, not sibling source or local paths.
181
+
182
+ Latest installation (the asset filename must match the current latest release):
183
+
184
+ ~~~sh
185
+ dsh plugin --profile web add --force \
186
+ https://github.com/NOirBRight/dsh-llm-providers-ui/releases/latest/download/dsh-llm-providers-ui-0.2.14.tgz
187
+ dsh plugin --profile web add --force \
188
+ https://github.com/NOirBRight/dsh-model-switch/releases/latest/download/dsh-model-switch-0.4.16.tgz
189
+ ~~~
190
+
191
+ Fixed-version installation:
192
+
193
+ ~~~sh
194
+ dsh plugin --profile web add --force \
195
+ https://github.com/NOirBRight/dsh-llm-providers-ui/releases/download/v0.2.14/dsh-llm-providers-ui-0.2.14.tgz
196
+ dsh plugin --profile web add --force \
197
+ https://github.com/NOirBRight/dsh-model-switch/releases/download/v0.4.16/dsh-model-switch-0.4.16.tgz
198
+ ~~~
199
+
200
+ Update, uninstall, and verify:
201
+
202
+ ~~~sh
203
+ # Update to the latest Release
204
+ dsh plugin --profile web add --force \
205
+ https://github.com/NOirBRight/dsh-llm-providers-ui/releases/latest/download/dsh-llm-providers-ui-0.2.14.tgz
206
+ dsh plugin --profile web add --force \
207
+ https://github.com/NOirBRight/dsh-model-switch/releases/latest/download/dsh-model-switch-0.4.16.tgz
208
+ # Verify the loaded version
209
+ dsh plugin --profile web list
210
+ # Uninstall only this plugin
211
+ dsh plugin --profile web remove dsh-model-switch
212
+ ~~~
213
+
214
+ Configuration: use the plugin section in Settings for Web UI plugins, or the profile dsh.profile.bundles entry for Host-only plugins. Start with this README's minimal YAML/JSON example and provide credentials/backend addresses explicitly.
215
+
216
+ Rollback: reinstall the immutable v0.4.15 archive and restore the recorded profile; no Host downgrade is required. Inspect journalctl --user -u dsh-web.service; never link a checkout into production.
217
+
218
+ Release and integrity: [v0.4.16](https://github.com/NOirBRight/dsh-model-switch/releases/tag/v0.4.16) · [SHA256](https://github.com/NOirBRight/dsh-model-switch/releases/download/v0.4.16/dsh-model-switch-0.4.16.tgz.sha256).