dsh-model-switch 0.4.23 → 0.4.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## v0.4.24
2
+
3
+ Three coordinated changes: session naming gains a configurable model; provider groups (and the Default Subagent route) become per-session derived policy instead of a write, fixing sessions on different providers overwriting or redirecting each other's auxiliary routes; and every settings row save becomes one atomic revision-fenced mutation, fixing the save-while-running freeze, half-written provider/model pairs, and the ambiguous ⚠ marker.
4
+
5
+ - **New: session-naming model card.** The settings page now configures the model behind DSH's automatic session titles. Off (both fields empty) is the official behavior — naming follows the session's own model; On pins a provider/model. The route is **read through on every naming request**, so a saved change applies to the very next title with no restart, and a naming request already in flight is never interrupted by a save. Each request resolves the session's provider group first, then the flat pair, then the session's own model, and the official implementation records the exact pair used in the session's own `session/title-llm-request` event — the durable record and the real request can never disagree. The plugin replaces the official `session-title-llm` row (the seam allows one registered naming provider per context) and mirrors the cadence that row declared; because a `disabled` row is not an instantiated loader entry, a profile where the displaced row cannot be inspected mirrors the shipped first-prompt cadence rather than dropping model-backed naming.
6
+ - **Provider groups are derived, not written (cross-session overwrite fixed).** Previously a provider switch *wrote* the group's routes into the shared deployment-wide fields, so two sessions on different providers could overwrite or redirect each other's auxiliary routes — a real cross-session hazard. Now a group saved for provider X merely applies to the sessions whose own provider is X: the Host resolves **group pair → flat deployment-wide pair → official default** per session and per request, and switching a session's provider writes NOTHING. The group editor therefore covers exactly the session-naming and subagent routes; auto review and compaction stay deployment-wide cards (the official auto-review gate exposes no session identity at its interception point, and the official compaction transaction durably records the provider/model that wrote its summary — both recorded in `docs/seam-gap.md`). The stored configuration format is unchanged and no migration runs: an older entry may still carry its compaction/auto-review fields, and they are ignored.
7
+ - **Subagent routing follows the delegating session.** A spawn that names no provider, model, or effort is routed with the delegating session's derived route (its provider group's subagent pair and effort, else the fixed card), so sessions on different providers route their own spawns. A caller that names a route is never overridden, and a provider whose capability is not `agentOptions` (for example a one-shot process adapter) is never reinterpreted as an Agent-capable route; with nothing pinning, the request stays untouched — official inherit (parent live request, then creation snapshot).
8
+ - **Compaction stays deployment-wide by design, with a supported per-route alternative.** The official compaction transaction durably records the provider/model that wrote the summary, so rewriting the summarization call per session would make that record false. A deployment that needs route-scoped summarization uses the official exact-target `modelPolicies` array on the profile's `compaction-basic` row config — each entry matches one exact routed conversation target and overrides its policy including the summarization pair, and this plugin forwards that row config verbatim (YAML example in the READMEs).
9
+ - **Atomic settings writes (freeze / half-pair / ⚠ fixed).** Every row save is now ONE atomic, revision-fenced mutation through the official ConfigForm `mutate`: a refused write can no longer leave a half-written provider/model pair. A draft the user edited survives external snapshot updates; a conflict is reported with a reload path instead of silently losing either side; and a write that never settles is reported after 30 seconds instead of freezing the page on "保存中…". The ⚠ marker now means only "this value is not in the current provider catalog".
10
+ - **The switch-time compaction guard is never dropped.** The send-time guard is installed *before* the previous one retires, and a refused registration is reported while the live guard stays in place, so a failed refresh can no longer leave a session without switch-time protection for its remaining steps.
11
+ - **Documentation.** The settings-page inventory is documented as shipped — Subagent, Web search, Image generation, Auto review, Compaction, and Session naming route cards plus the Provider groups card — in `README.md`, `README.zh.md`, and `PRODUCT.md`, with the two deployment-wide justifications and the `modelPolicies` alternative recorded in `docs/seam-gap.md`.
12
+
13
+ - **Verification.** `pnpm run check` green (`tsc` + `tsdown`, 53 test files / 425 tests, pack gate). Reverse verification: inverting the group-vs-deployment precedence turns the resolver spec red, removing the draft's pristine guard turns the external-snapshot spec red, and restoring the retire-then-install guard order turns the guard-lifecycle spec red — each green again after restore. Lab 3082 smoke: the naming card's save arrives as ONE `settings/mutate` carrying both `titleProvider` and `titleModel`, the group editor lists exactly the naming and subagent rows, and session naming was checked in both modes — card off produced a model-generated title within seconds, the card pinned to the keyless `deepseek-official` route produced no title across three sessions, and the same card pinned to a working route produced a model-generated title within seconds, which is the differential that shows the pinned route is the one the naming request uses. Residual: the `session/title-llm-request` field itself was not read (the session store's concatenated zstd frames could not be walked offline, and the trace view did not render the event), so the route match rests on that differential; compaction and auto review remain deployment-wide by design (`docs/seam-gap.md`).
14
+
1
15
  ## v0.4.23
2
16
 
3
17
  Three coordinated changes: the Main-default card is removed entirely; provider groups now apply on every switch surface of the seat session; and the subagent summary distinguishes a pinned effort from a provider default.
package/PRODUCT.md CHANGED
@@ -5,12 +5,13 @@
5
5
  ## Routes
6
6
 
7
7
  - **Session model:** the active conversation's provider, model, and optional effort, switched through the official Model Directory and session-local by nature. The deployment default for new sessions belongs to DSH itself: the Host persists the last session switch as the default, and since 0.4.23 this plugin no longer configures, restores, or protects it (its Main-default card was removed; a fixed default is configured on DSH's own Models page).
8
- - **Default Subagent:** one optional fixed provider/model/effort, injected only when a child spawn names no provider, model, or effort. When the setting is off, stored `follow-main` is read as unset and Model Switch injects nothing; Official inherit (parent live request, then creation snapshot) and the official Allowlist remain DSH's. The default is not an Allowlist and need not be a member of one. Local spawn, fork, continuable, and workflow children use this policy unless the spawn already supplies provider, model, or effort. Remote Codex/Claude children are outside unified routing.
8
+ - **Default Subagent:** one optional deployment-wide provider/model/effort plus optional per-provider group pins, derived into the route a spawn receives. A child spawn that names no provider, model, or effort is routed with the DELEGATING session's derived route: the group saved for that session's provider (subagent pair and effort), else the fixed card pair in `fixed` mode. When the setting is off, stored `follow-main` is read as unset and Model Switch injects nothing; Official inherit (parent live request, then creation snapshot) and the official Allowlist remain DSH's. A spawn that already supplies provider, model, or effort is never overridden, and a provider whose capability is not `agentOptions` (for example a one-shot process adapter) is never reinterpreted as an Agent-capable route. The default is not an Allowlist and need not be a member of one. Local spawn, fork, continuable, and workflow children use this policy unless the spawn already supplies provider, model, or effort. Remote Codex/Claude children are outside unified routing.
9
9
  - **Search Router:** does not register or replace `web_search`. Model Switch registers one public `WebSearchProvider` with id `model-switch`; the existing official `web_search` continues through `ctx.web`, whose configured search provider must be `model-switch`. The thin provider selects the configured backend adapter and model at execution time and returns the official `WebSearchResult` unchanged. v0.2.0 supports Codex Search only. `web_fetch` and its configured provider remain unchanged.
10
10
  - **Vision:** excluded. Model Switch exposes no Vision setting, does not shadow `read_image`, and does not preprocess chat attachments.
11
11
  - **Image Router:** owns a new, uniquely named stable `generate_image` tool. It selects the configured Codex or Grok adapter/model at execution time, rejects backend-incompatible fields, and returns normalized image metadata. Existing provider-specific image tools remain available as rollback paths.
12
12
  - **Composer Picker:** replaces the composer model seat with a suffix-grouped Model / Effort / Context / Fast / Thinking picker and submits through the official Model Directory. Variant changes preserve an effort only when the target catalog row supports it, otherwise they use the target default or omit effort. Picker changes are session-local. A submitted prompt reserves the selected runtime immediately (composer submitting, pending first turn, or running). Native Antigravity keeps its models and effort; DSH keeps LLM-provider choices and disables Agent providers. After a successful native binding, other groups stay disabled. Cancel without a binding releases the reservation; existing DSH history still cannot convert onto an External Agent. The deployment default write a Host performs alongside a Session switch is left to DSH (since 0.4.23 the plugin no longer restores a pre-switch default).
13
13
  - **Plan Review:** intercepts the official `plan-review` composer takeover with a header execution-model picker and right-aligned Discuss in chat / Reject / Set as goal and run / Confirm actions. Confirm commits the selected model before answering; a refused commit leaves the review pending and retryable. Reject carries optional human feedback as the answer's own `custom` text, which plan mode reports to the model. **Set as goal and run** arms this Plan as the session's goal through the released `remote.goals.create` — replacing an unfinished goal with `get` + `clear` first so the new Plan receives a fresh round cap — and only then answers, so `dsh-goal-round-driver` carries the Plan after plan mode exits. The goal is set before the answer, a refusal keeps the review pending and retryable, and a deployment that mounts no goal service disables the action with an explicit reason; the plugin stores no goal state, schedules no rounds, and owns no part of the goal domain. The card body carries only the official two-part summary (first line, first paragraph); the full plan opens in the right sidebar through ui-plan's registered preview opener, which this card drives because a composer-takeover entry cannot declare the official actions slot. Discuss in chat withdraws the takeover through the Host's own verb — `dismiss()` where the Host publishes it, `cancel()` on the older generation — and names the Host when neither seam exists, so a settled review is never a dead end. The same runtime reservation applies.
14
+ - **Session naming:** the model behind DSH's automatic session titles, off by default — both fields empty means naming follows the session's own model, which is the official behavior. On pins a deployment-wide provider/model that each session re-resolves per request: provider group → flat pair → the session's own model. The route is read through on every naming request, so a save applies to the very next request with no restart and never interrupts an in-flight request; the exact pair used is recorded in the session's own `session/title-llm-request` event by the official naming implementation, so the record and the request always agree. The plugin replaces the official `session-title-llm` row (one registered naming provider per context) and mirrors the cadence that row declared; naming cadence and title policy remain DSH's.
14
15
 
15
16
  Model choices are capability-driven. Invalid, unavailable, or unsupported selections remain visible to configuration surfaces and fail loudly at use; routing never silently falls back.
16
17
 
@@ -20,11 +21,11 @@ Official `agent-presets` stay in the process. A nested `ctx.plugin(AgentPresets)
20
21
 
21
22
  ## Settings and lifecycle
22
23
 
23
- The settings surface aggregates public Settings namespaces, reads and mutates with optimistic revisions, and saves rows independently. Changing a fixed Subagent model applies only the target catalog row's default effort, so an effort never leaks across providers or models. The Subagent card is a RouteCard with an instant on/off for the Default Subagent route; turning it off keeps the last provider/model on disk. Switch compaction is a B-row in the Send protection group (发送保护), default on, written immediately. The Auto review card is a RouteCard with an instant switch defaulting **off = reviews follow the session model**; turning it on pins the catalog head as an editable starting point. The Compaction card mirrors it: default **off = official default = compaction follows the conversation target model**; turning it on pins the catalog head as an editable starting point, and the pinned pair drives `/compact` and official automatic compaction alike. Provider groups store one compaction/subagent/review route group per provider in the shared `model-switch` namespace and apply it as a full replacement whenever the seat session's model provider changes on any surface — the plugin watches the session's official model-selection projection, so plugin-picker, TUI, and other-client switches share one applier (the residual boundary is a session this Web UI never opened); the apply rewrites only the existing flat fields (an unpinned capability reverts to its own default, `subagentMode` last), a same-provider switch or a provider without a stored group writes nothing, the effect is profile-wide rather than per session, and a failed apply warns without failing the switch. There is no Main-default editing surface: the Host persists the deployment default from the last session switch, and a fixed default belongs to DSH's own Models page. Search and image routes are out of scope for groups. Every registration, watcher, and dynamic tool generation has clean disposal. All visible copy lives in zh/en locale dictionaries.
24
+ The settings surface aggregates public Settings namespaces, reads and mutates with optimistic revisions, and saves rows independently. Changing a fixed Subagent model applies only the target catalog row's default effort, so an effort never leaks across providers or models. The Subagent card is a RouteCard with an instant on/off for the Default Subagent route; turning it off keeps the last provider/model on disk. Switch compaction is a B-row in the Send protection group (发送保护), default on, written immediately. The Auto review card is a RouteCard with an instant switch defaulting **off = reviews follow the session model**; turning it on pins the catalog head as an editable starting point. The Compaction card mirrors it: default **off = official default = compaction follows the conversation target model**; turning it on pins the catalog head as an editable starting point, and the pinned pair drives `/compact` and official automatic compaction alike. The Session naming card mirrors them again: default **off = official default = naming follows the session's own model**, and every naming request re-reads the resolved route. The settings page hosts six route cards — Subagent, Web search, Image generation, Auto review, Compaction, and Session naming — plus the Provider groups card. Provider groups store one session-naming/subagent route group per provider in the shared `model-switch` namespace as a DERIVED policy, never a write: each session and each request resolves those two routes from its own provider — group pair → flat deployment-wide pair → official default — so switching a session's provider writes nothing and two sessions on different providers can no longer overwrite or redirect each other's auxiliary routes; the group editor covers exactly those two routes, auto review and compaction stay deployment-wide cards, and the stored format is unchanged (an older entry's compaction/auto-review fields are ignored; no migration). Every row save is one atomic, revision-fenced official ConfigForm mutation: a refused write leaves no half-written provider/model pair, an edited draft survives external snapshot updates, a conflict is reported with a reload path, a write that never settles is reported after 30 seconds instead of freezing the page, and the ⚠ marker means only "this value is not in the current provider catalog". There is no Main-default editing surface: the Host persists the deployment default from the last session switch, and a fixed default belongs to DSH's own Models page. Search and image routes are out of scope for groups. Every registration, watcher, and dynamic tool generation has clean disposal. All visible copy lives in zh/en locale dictionaries.
24
25
 
25
26
  ## Exclusions
26
27
 
27
- Send-time context protection (check, and compact with the previous model when a sent message would exceed the target window) is in scope. The Auto reviewer route override rides a name-matched replacement of the experimental auto-review row and changes only the review request's provider/model. The compaction summarization route override rides a name-matched replacement of the official compaction-basic engine and changes only the summarization request's provider/model, forwarding the row's own engine config unchanged. Because the Web surface runs session compaction inside each agent preset's isolated compaction group and profile patches cannot address rows inside a preset's plugins list, the plugin replaces the shipped preset rows wholesale (the Web preset editor's own save mechanism) to swap that group's engine; the copies are re-synced per DSH release during the existing compatibility verification, and a deployment that edits presets in the Web editor owns the merge from its own wholesale row. Model Switch does not own global compaction policy, titles, fetch routing, fallback, load balancing, provider credentials, CLI/API, configuration history, or DSH Core edits. Coordinated changes are limited to Model Switch and self-maintained provider plugins. Installing this package replaces a standalone `dsh-composer-picker` install; keep only one composer model seat.
28
+ Send-time context protection (check, and compact with the previous model when a sent message would exceed the target window) is in scope. The Auto reviewer route override rides a name-matched replacement of the experimental auto-review row and changes only the review request's provider/model. The compaction summarization route override rides a name-matched replacement of the official compaction-basic engine and changes only the summarization request's provider/model, forwarding the row's own engine config unchanged. Both auxiliary routes stay deployment-wide by design. The official compaction transaction durably records the provider/model that wrote the summary, so rewriting the summarization call per session would make that record false — the supported per-route alternative is the official exact-target `modelPolicies` array on the profile's `compaction-basic` row config (each entry matches one exact routed conversation target and overrides its compaction policy, including the summarization pair), which this plugin forwards verbatim. The official auto-review gate builds its `GenerateOptions` with no `sessionId`/`purpose` and closes over the single context it was applied with, so its interception point carries no session identity and a review cannot be attributed to a session. Both gaps, and the seams they would need, are recorded in `docs/seam-gap.md`. Because the Web surface runs session compaction inside each agent preset's isolated compaction group and profile patches cannot address rows inside a preset's plugins list, the plugin replaces the shipped preset rows wholesale (the Web preset editor's own save mechanism) to swap that group's engine; the copies are re-synced per DSH release during the existing compatibility verification, and a deployment that edits presets in the Web editor owns the merge from its own wholesale row. Model Switch does not own global compaction policy, titles, fetch routing, fallback, load balancing, provider credentials, CLI/API, configuration history, or DSH Core edits. Coordinated changes are limited to Model Switch and self-maintained provider plugins. Installing this package replaces a standalone `dsh-composer-picker` install; keep only one composer model seat.
28
29
 
29
30
  Official `settings.section` has no icon field. The Settings nav glyph is a label-matched DOM swap, the same published pattern as usage-monitor, until an official icon seam exists. The Plan Review takeover uses priority -5, and the composer model seat uses priority -10.
30
31
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
4
 
5
- Choose one explicit route for the active session, Subagents, Web Search, image generation, 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.
5
+ Choose one explicit route for the active session, Subagents, Web Search, image generation, Plan execution, and automatic session naming in DeepSeek Harness. Model Switch uses public DSH services and provider-owned adapters; it does not patch DSH Core or manage provider credentials.
6
6
 
7
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
8
 
@@ -13,7 +13,7 @@ The picker retains keyboard focus when changing panes or closing search, without
13
13
  | Route | Behavior |
14
14
  | --- | --- |
15
15
  | Session model | The active conversation's provider/model/effort, changed by the Composer Picker or Plan Review only. The deployment default (new sessions) is DSH's own; the picker never writes it. |
16
- | Subagent | Follow the active parent route, or use a fixed provider/model/effort. Workflow overrides remain authoritative. |
16
+ | Subagent | When a spawn names no provider, model, or effort: the delegating session's derived route — its provider group's subagent pair, else the fixed card's pair; with neither, official inherit (parent live request, then creation snapshot). A spawn that names a route is never overridden; workflow overrides remain authoritative. |
17
17
  | Composer Picker | Changes only the active session and submits the exact catalog model id. |
18
18
  | Plan Review | Chooses the execution model before the Plan approval response is sent. |
19
19
  | Web Search | Keeps official `web_search`; after deployment opt-in, routes through the selected, dynamically declared provider search adapter. |
@@ -27,7 +27,7 @@ The existing 3082 lab replay probe is `node scripts/check-lab-runtime-lock.mjs [
27
27
 
28
28
  ## Configure Subagents and capability routes
29
29
 
30
- Open **Settings → Model Switch**. Subagents may follow the parent request or use a fixed provider, model, and effort.
30
+ Open **Settings → Model Switch**. The page hosts six route cards — Subagent, Web search, Image generation, Auto review, Compaction, and Session naming — plus the Provider groups card. Subagents may follow the delegating session's derived route or a fixed provider, model, and effort.
31
31
 
32
32
  Since 0.4.23 this plugin no longer configures or protects the deployment default ("Main model"). That default is DSH's own: the Host persists the last session switch as the default (including switches made through this plugin's picker or the TUI), exactly like native switching — so a new session starts on whatever was last used. To pin a fixed default, use DSH's own **Settings → Models** page. Official DSH makes ConfigForms memory-only in non-loopback browsers, so the Settings page shows an unavailable message there; open Settings on the Host loopback URL instead. As a side effect of the removal, model switching is never blocked by settings state (previously the picker waited for a writable Main ConfigForm).
33
33
 
@@ -35,7 +35,11 @@ Changing the fixed Subagent provider/model replaces the previous model's effort
35
35
 
36
36
  ![Model Switch settings with a fixed Subagent route](docs/screenshots/settings-subagent.png)
37
37
 
38
- Follow resolves the active parent request first, then the Host's own session 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
+ Official inherit resolves the active parent request first, then the Host's own session default. A derived or fixed route is injected before the official Subagent descriptor is created, and only when the spawn names no provider, model, or effort — a caller that names a route is never overridden, and a provider whose capability is not `agentOptions` (for example a one-shot process adapter) is never reinterpreted as an Agent-capable route. DSH 0.1.2-alpha.4 carries provider, model, and fixed reasoning effort in that descriptor.
39
+
40
+ ### Saving settings
41
+
42
+ Every row save is one atomic, revision-fenced mutation through the official ConfigForm `mutate`: a refused write either commits the whole row or nothing, so it can no longer leave a half-written provider/model pair. A draft you edited survives external snapshot updates (another tab's save, the capability poll); a revision conflict is reported with a reload path instead of silently losing either side; and a write that never settles is reported after 30 seconds rather than freezing the page on "Saving…". The ⚠ marker next to a value means exactly one thing: this value is not in the current provider catalog.
39
43
 
40
44
  ## How custom models appear in the Picker
41
45
 
@@ -92,19 +96,42 @@ When a sent message will use a different model or context-tier id than the last
92
96
 
93
97
  ## Auto review model
94
98
 
95
- 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 pins the catalog head as an editable starting point, 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`.
99
+ 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 pins the catalog head as an editable starting point, 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`. The reviewer route is deliberately deployment-wide: the official gate builds its review request with no session identity and closes over the one context it was applied with, so no public seam can attribute a review to a session — the per-session route it would need is recorded in `docs/seam-gap.md`.
96
100
 
97
101
  ## Compaction model
98
102
 
99
103
  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 pins the catalog head as an editable starting point, 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.
100
104
 
105
+ The pair stays deployment-wide by design: the official compaction transaction durably records the provider/model that wrote the summary, so rewriting the summarization call per session would make that record false. For route-scoped summarization the supported alternative is the official exact-target `modelPolicies` array on the profile's `compaction-basic` row config: each entry matches one exact routed conversation target and overrides its compaction policy — including the summarization pair — for that target, and this plugin forwards the row's own config verbatim (the card's pair is overlaid only when both fields are set):
106
+
107
+ ```yaml
108
+ - id: compaction-basic
109
+ name: '@deepseek-ai/dsh-compaction-basic'
110
+ config:
111
+ modelPolicies:
112
+ - provider: deepseek
113
+ model: deepseek-chat
114
+ summarizationProvider: deepseek
115
+ summarizationModel: deepseek-reasoner
116
+ ```
117
+
101
118
  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.
102
119
 
103
120
  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.
104
121
 
122
+ ## Session naming model
123
+
124
+ DSH names new sessions automatically; the profile's official naming row decides the cadence (right after the first prompt, or on every prompt). The "Session naming model" card picks the model those naming requests use. **Off — both fields empty — is the official behavior: naming follows the session's own model.** On pins a provider/model as an editable starting point.
125
+
126
+ The route is read through on every naming request: a saved change applies to the very next title with no restart, and a naming request already in flight is never interrupted by a save. Each request resolves its route in order — the session's provider group's naming pair, then the card's flat pair, then the session's own model. The request runs through the official naming implementation, which records the exact provider/model in the session's own `session/title-llm-request` event, so the durable record and the real request always agree. The plugin replaces the official `session-title-llm` row instead of forking the naming logic (the seam allows one registered naming provider per context) and mirrors the cadence that row declared; a profile with no official naming row keeps DSH's deterministic fallback title.
127
+
105
128
  ## Provider groups
106
129
 
107
- The "Provider groups" card stores one route group per provider — the compaction, subagent, and review routes saved together — and applies it automatically whenever **this session's model provider** changes, on any surface: this plugin's composer picker, the TUI, or another client — the plugin observes the official model-selection projection of the session, so every switch lands in the same watcher. 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 the parent). 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; the residual boundary is that the watcher only exists while the session is open in this Web UI — a switch on a session this browser never opened 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: the watcher logs a warning and the routes stay as they were.
130
+ The "Provider groups" card stores one route group per provider — the session-naming and subagent routes saved together — and a group saved for provider X applies to the sessions whose own provider is X. Groups are **derived, not written**: switching a session's provider writes nothing, so two sessions on different providers can no longer overwrite or redirect each other's auxiliary routes (before 0.4.24 a switch *wrote* the group into the shared fields, and one session's switch could steer another session's naming and subagent requests).
131
+
132
+ Every request derives its own route from durable state: the session's provider (its model selection — a just-made pending selection counts — else its latest logged request route), then the group pair for that provider, then the flat deployment-wide pair, then the official default (the session's own model for naming; official inherit for subagents, where the fixed card applies only in `fixed` mode). Because nothing is written, this works for sessions this browser never opened and for switches made from the TUI or another client, with no watcher and no residual boundary; a session with no observable provider simply resolves to the deployment default. A switch to a provider without a group changes nothing, and a switch within one provider re-resolves to the same routes.
133
+
134
+ The group editor therefore covers exactly these two routes: auto review and compaction stay deployment-wide cards (see the two sections above for why), and search and image routes are out of scope. The stored configuration format is unchanged — an older group entry that still carries compaction or auto-review fields is accepted as-is and those fields are ignored, so no migration runs. In the editor, a provider's draft seeds from the currently derived routes the first time you configure it, each row's off state means "no pin — fall through to the card, then the official default", and **Delete group** removes the provider's entry entirely (clearing the stored field when the last group goes).
108
135
 
109
136
  ## What Model Switch does not change
110
137
 
@@ -114,7 +141,7 @@ The "Provider groups" card stores one route group per provider — the compactio
114
141
  - Official Agent Presets
115
142
  - Existing provider-specific image tools
116
143
  - The deployment default model (DSH persists the last session switch itself, same as native TUI switching)
117
- - Global compaction policy, titles, or DSH Core
144
+ - Global compaction policy, naming cadence (only the naming model is configurable), or DSH Core
118
145
 
119
146
  ## Installation
120
147
 
package/README.zh.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 在 DeepSeek Harness 中为当前会话、Subagent、Web Search、图像生成和 Plan 执行模型配置明确路由。Model Switch 只使用 DSH 公开服务和 Provider 自有 Adapter;不修改 DSH Core,也不管理 Provider 登录凭证。
5
+ 在 DeepSeek Harness 中为当前会话、Subagent、Web Search、图像生成、Plan 执行和自动会话命名配置明确路由。Model Switch 只使用 DSH 公开服务和 Provider 自有 Adapter;不修改 DSH Core,也不管理 Provider 登录凭证。
6
6
 
7
7
  <p align="center"><img src="docs/screenshots/composer-picker.png" alt="包含 Model、Effort、Context 和 Fast 的 Composer Picker" width="314"></p>
8
8
 
@@ -13,7 +13,7 @@ Picker 在切换页面或关闭搜索时保留键盘焦点,不覆盖搜索框
13
13
  | 路由 | 行为 |
14
14
  | --- | --- |
15
15
  | 会话模型 | 当前对话的 provider/model/effort,只由 Composer Picker 或 Plan Review 修改。部署默认模型(新建会话)归 DSH 自己管理;选择器从不写它。 |
16
- | Subagent | 跟随当前父请求,或使用固定 provider/model/effort;Workflow 显式覆盖始终优先。 |
16
+ | Subagent | 子代理 spawn 未指定 provider/model/effort 时:使用委派会话自身推导出的路由——其供应商组的子代理对,其次是固定卡片的对;两者都没有则为官方继承(先父请求实时路由,再创建时快照)。显式指定路由的 spawn 永远不会被覆盖;Workflow 显式覆盖始终优先。 |
17
17
  | Composer Picker | 只修改当前会话,并提交 catalog 中的原始 model id。 |
18
18
  | Plan Review | 在发送 Plan 审核确认前,先提交执行模型。 |
19
19
  | Web Search | 保留官方 `web_search`;部署显式启用后,通过 Provider 动态声明的独立搜索适配器路由。 |
@@ -25,7 +25,7 @@ Picker 在切换页面或关闭搜索时保留键盘焦点,不覆盖搜索框
25
25
 
26
26
  ## 配置 Subagent 与能力路由
27
27
 
28
- 打开 **设置 → Model Switch**。Subagent 可以跟随父请求,也可以使用固定 provider、model 和 effort。
28
+ 打开 **设置 → Model Switch**。设置页共有六张路由卡片——Subagent、Web 搜索、图像生成、自动审查、压缩、会话命名——外加「供应商配置组」卡片。子代理可以使用委派会话推导出的路由,也可以使用固定的 provider、model 和 effort。
29
29
 
30
30
  自 0.4.23 起本插件不再配置、也不再保护部署默认模型(原「主模型」卡片)。默认模型归 DSH 自己:Host 会把最后一次会话切换写成默认值(包括经本插件选择器或 TUI 的切换),与原生切换行为一致——新会话从"上次用过的模型"开始。需要固定默认值时,请使用 DSH 自带的 **设置 → 模型** 页面。官方 DSH 在非本机浏览器将 ConfigForms 设为仅内存模式,设置页会明确显示不可用;请在 Host 的 127.0.0.1 页面打开设置。移除主卡片的附带收益:模型切换从此不再依赖设置可写状态(此前选择器要等 Main 表单可写才允许切换)。
31
31
 
@@ -33,7 +33,11 @@ Picker 在切换页面或关闭搜索时保留键盘焦点,不覆盖搜索框
33
33
 
34
34
  ![Model Switch 设置中的固定 Subagent 路由](docs/screenshots/settings-subagent.png)
35
35
 
36
- Follow 先读取当前父请求,再读取 Host 自身的会话默认模型。固定路由会在官方 Subagent descriptor 创建前注入。DSH 0.1.2-alpha.4 会在该 descriptor 中携带 provider、model 和固定 reasoning effort。
36
+ 官方继承先读取当前父请求,再读取 Host 自身的会话默认模型。推导或固定的路由会在官方 Subagent descriptor 创建前注入,且仅当 spawn 未指定 provider、model 或 effort——显式指定路由的 spawn 永远不会被覆盖,能力不是 `agentOptions` 的 provider(例如一次性进程适配器)也不会被重新解释为可路由的 Agent。DSH 0.1.2-alpha.4 会在该 descriptor 中携带 provider、model 和固定 reasoning effort。
37
+
38
+ ### 保存设置
39
+
40
+ 每一行的保存都是**一次**原子的、带版本围栏的官方 ConfigForm `mutate`:写入被拒绝时要么整行生效、要么什么都不写,不会再留下只写了一半的 provider/model 对。你编辑中的草稿不会被外部快照更新(另一标签页的保存、能力轮询)覆盖;版本冲突会明确报告并提供重新加载路径,而不是悄悄丢掉任何一方;30 秒内始终没有落定的写入会明确报错,而不是让页面卡在「保存中…」。值旁边的 ⚠ 标记现在只有一个含义:该值不在当前 Provider 目录中。
37
41
 
38
42
  ## 自定义模型如何出现在 Picker
39
43
 
@@ -90,19 +94,42 @@ Plan Review 拥有独立于会话当前路由的执行模型草稿。**确认执
90
94
 
91
95
  ## 自动审查模型(Auto review)
92
96
 
93
- 官方 `@deepseek-ai/dsh-experimental-auto-review`(Auto 权限预设的逐调用审查)把审查请求固定在会话自身的路由上。本插件通过 `cordis.patch.yml` 的行替换接管这一行:官方行被禁用,改挂 `dsh-model-switch/auto-review`,它在作用域子上下文里运行**同一份官方 `apply`**,只把 `llm.stream` 的 `provider/model` 改写为设置里固定的审查模型。设置页「自动审查模型」默认关闭 = 审查跟随当前会话模型;打开时以目录首位模型作为可再改的固定起点,保存后对下一次审查立即生效。审查未配置官方能力包(`@deepseek-ai/dsh-experimental-auto-review` 未随 profile 启动)时本行不挂载,Auto 预设保持官方的关闭状态;官方行若已在别处注册,本行明确报错而不产生第二个 `registerAuto`。
97
+ 官方 `@deepseek-ai/dsh-experimental-auto-review`(Auto 权限预设的逐调用审查)把审查请求固定在会话自身的路由上。本插件通过 `cordis.patch.yml` 的行替换接管这一行:官方行被禁用,改挂 `dsh-model-switch/auto-review`,它在作用域子上下文里运行**同一份官方 `apply`**,只把 `llm.stream` 的 `provider/model` 改写为设置里固定的审查模型。设置页「自动审查模型」默认关闭 = 审查跟随当前会话模型;打开时以目录首位模型作为可再改的固定起点,保存后对下一次审查立即生效。审查未配置官方能力包(`@deepseek-ai/dsh-experimental-auto-review` 未随 profile 启动)时本行不挂载,Auto 预设保持官方的关闭状态;官方行若已在别处注册,本行明确报错而不产生第二个 `registerAuto`。审查路由刻意保持部署级:官方闸门构造审查请求时不带会话身份、且只闭包它被 apply 的那一个上下文,因此没有任何公开接缝能把一次审查归属到某个会话——它需要的按会话路由已记录在 `docs/seam-gap.md`。
94
98
 
95
99
  ## 压缩模型(Compaction model)
96
100
 
97
101
  官方压缩——`/compact` 与 DSH 的自动压缩——默认使用会话当前的目标模型做摘要。「压缩模型」卡片(默认关闭)改为固定摘要路由:打开时以目录首位模型作为可再改的固定起点,保存后对下一次压缩立即生效。两个字段都留空即回到官方默认。保存时若恰有压缩进行中,可能被打断并在下次重试。
98
102
 
103
+ 摘要模型的固定对刻意保持部署级:官方压缩事务会持久记录写下摘要的 provider/model,若按会话改写摘要调用,这条持久记录就会与实际使用的路由不符。需要按路由区分摘要的部署,官方支持的做法是在 profile 的 `compaction-basic` 行配置里使用精确目标的 `modelPolicies` 数组:每个条目精确匹配一个实际路由的会话目标模型,并只对该目标覆盖其压缩策略——包括摘要路由;本插件会原样转发该行配置(仅当卡片两个字段都填写时才覆盖全局摘要对):
104
+
105
+ ```yaml
106
+ - id: compaction-basic
107
+ name: '@deepseek-ai/dsh-compaction-basic'
108
+ config:
109
+ modelPolicies:
110
+ - provider: deepseek
111
+ model: deepseek-chat
112
+ summarizationProvider: deepseek
113
+ summarizationModel: deepseek-reasoner
114
+ ```
115
+
99
116
  路由如何到达压缩引擎取决于部署在哪一层组合 agent。Web 面把压缩放进每个 Agent 预设的隔离 `compaction` 组里,而 profile patch 无法寻址 preset `plugins` 列表内部的行——因此本插件整表替换各预设行(与 Web 预设编辑器保存修改用的是同一套 id 定位机制),把该组的官方 `compaction-basic` 换成 `model-switch-compaction`,由它挂载**同一份官方 `BasicCompactionEngine`**、只覆盖 `summarizationProvider`/`summarizationModel`(只填其一忽略)。进程级组合 agent 的面(如 TUI)则走顶层行替换。两个后果:DSH 发布若改动这些预设,随包副本需要同步(本插件的逐版本核对覆盖这一步);部署若用 Web 编辑器改预设,会写出自己的整表行、并从那之后自行负责合并。
100
117
 
101
118
  已知问题(上游侧,见 `docs/seam-gap.md`):当工作集持续超过官方引擎的压力阈值时,自动压缩可能每步触发,并反复重试摘要过不了官方「摘要必须更小」守卫的小片段——表现为"不停自动压缩",本插件不控制该行为(已在同一会话做过插件开/关 A/B 对照)。可用的缓解(都在转发行配置上):`auto: false`(保留 `/compact`)、调高 `thresholdRatio`、减少挂载的工具 schema,或固定一个摘要更精炼的压缩模型。
102
119
 
120
+ ## 会话命名模型(Session naming model)
121
+
122
+ DSH 会自动为新会话起名;命名的触发节奏由 profile 的官方命名行决定(首条提示后一次,或每条提示一次)。「会话命名模型」卡片选择这些命名请求使用的模型。**关闭——两个字段都留空——即官方行为:命名跟随会话自身的模型。** 打开时以目录首位模型作为可再改的固定起点。
123
+
124
+ 路由在**每次命名请求时现读**:保存后对下一次起名立即生效、无需重启,且进行中的命名请求不会被保存打断。每次请求按顺序解析路由:该会话供应商组里的命名对,其次卡片的部署级对,其次会话自身的模型。请求经由官方命名实现发出,它会把实际使用的 provider/model 记录到该会话自己的 `session/title-llm-request` 事件里,因此持久记录与真实请求永远一致。本插件替换官方的 `session-title-llm` 行而不分叉命名逻辑(该接缝每个上下文只允许注册一个命名 provider),并镜像该行声明的节奏;profile 没有官方命名行时保持 DSH 的确定性兜底标题。
125
+
103
126
  ## 供应商配置组(Provider groups)
104
127
 
105
- 「供应商配置组」卡片为每个供应商保存一组路由——压缩、子代理、审查三条路由一起存;当**本会话模型的供应商**发生变化时自动应用,且无论变化来自插件的选择器、TUI 还是其他客户端——插件观察的是该会话的官方模型选择投影,所有切换都会落到同一个 watcher。应用是一次完整替换:三条路由一次性写入上方对应卡片,覆盖卡片当前值**(包括手动修改)**;组里没有固定的项回到该能力的默认值(压缩/审查跟随会话模型、子代理跟随父请求)。切换到没有配置组的供应商时现有路由保持不变,同一供应商内部切换不会重写路由。存储是 profile 级的(共享设置命名空间),不按会话生效;剩余边界是 watcher 只存在于本 Web 打开过的会话——从未在本浏览器打开的会话上的切换不会动路由。编辑器里首次配置某供应商时从当前生效路由种子化;每行能力开关的关闭态表示"用该能力的默认值"并按默认存储;「删除该组」彻底移除该组(删掉最后一组时同时清掉存储字段)。搜索与图片路由不在本功能范围内。应用失败不会让切换失败:watcher 记录一条警告,路由保持原样。
128
+ 「供应商配置组」卡片为每个供应商保存一组路由——会话命名与子代理两条路由一起存;为供应商 X 保存的组,作用于自身供应商就是 X 的会话。组是**推导出来的,而不是写进去的**:切换某个会话的供应商不写任何东西,因此不同供应商上的两个会话再也不能互相覆盖、互相改写对方的辅助路由(0.4.24 之前切换会把组**写进**共享字段,一个会话的切换能改道另一个会话的命名与子代理请求)。
129
+
130
+ 每次请求都从持久状态推导自己的路由:先取该会话的供应商(它的模型选择——刚做出、尚未被请求消费的 pending 选择优先——其次最近一次已记录的请求路由),再取该供应商的组对,再取部署级的平铺对,最后是官方默认(命名用会话自身模型;子代理用官方继承,且平铺卡片仅在 `fixed` 模式下生效)。因为什么都不写,本浏览器从未打开过的会话、以及来自 TUI 或其他客户端的切换同样生效——没有 watcher,也没有剩余边界;观察不到供应商的会话直接落到部署默认。切换到没有配置组的供应商不改变任何东西,同一供应商内部切换推导出的还是同一些路由。
131
+
132
+ 因此组编辑器恰好只覆盖这两条路由:自动审查与压缩保持部署级卡片(原因见上面两节),搜索与图片路由不在范围内。存储格式不变——较早的组条目若还带着压缩/自动审查字段,会原样接受并忽略这些字段,无需迁移。编辑器里首次配置某供应商时,草稿从当前推导出的路由种子化;每行的关闭态表示"不固定——先落到卡片、再落到官方默认";「删除该组」彻底移除该供应商的条目(删掉最后一组时同时清掉存储字段)。
106
133
 
107
134
  ## Model Switch 不会改变什么
108
135
 
@@ -112,7 +139,7 @@ Plan Review 拥有独立于会话当前路由的执行模型草稿。**确认执
112
139
  - 官方 Agent Presets
113
140
  - 已有的 Provider 专属图像工具
114
141
  - 部署默认模型本身(DSH 自己把最后一次会话切换写成默认值,与原生 TUI 切换一致)
115
- - 全局压缩策略、标题或 DSH Core
142
+ - 全局压缩策略、命名节奏(只可配置命名模型)或 DSH Core
116
143
 
117
144
  ## 安装
118
145
 
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
- # Replace independently packaged host modules only through public profile patches.
1
+ # Replace independently packaged host modules only through public profile patches.
2
2
  # Existing rows are disabled with their original name; replacements use unique ids.
3
3
  # Do not replace agent-presets: a nested AgentPresets instance loads a second
4
4
  # @deepseek-ai/dsh-scope identity, and session.create then fails as unscoped.
@@ -24,6 +24,16 @@
24
24
  name: '@deepseek-ai/dsh-compaction-basic'
25
25
  disabled: true
26
26
 
27
+ # The official naming provider derives the session-title route from the
28
+ # session's own logged route; Model Switch replaces it with its own row so the
29
+ # naming model stays configurable (one registered title provider per context).
30
+ # The row id is stable across both cadence packages, and the replacement row
31
+ # mounts whatever package the row declared, so a profile that restated this
32
+ # row as all-prompts keeps its cadence.
33
+ - id: session-title-llm
34
+ name: '@deepseek-ai/dsh-session-title-first-prompt-llm'
35
+ disabled: true
36
+
27
37
  # The Web surface moves the agent plane (tools, compaction, delegation) into
28
38
  # per-session agent presets: session /compact and official automatic compaction
29
39
  # run against the compaction service inside each preset's isolated `compaction`
@@ -513,3 +523,7 @@
513
523
  # The official compaction engine under the configurable summarization route.
514
524
  - id: model-switch-compaction
515
525
  name: 'dsh-model-switch/compaction'
526
+
527
+ # The official naming provider under the configurable naming route.
528
+ - id: model-switch-session-title
529
+ name: 'dsh-model-switch/session-title'
@@ -10,6 +10,14 @@
10
10
  * route configured the wrapper passes options through by identity, so the
11
11
  * reviewer follows the session model exactly.
12
12
  *
13
+ * The reviewer route is deliberately DEPLOYMENT-WIDE. The official gate builds
14
+ * its request with no session id and no purpose and closes over the single
15
+ * context it was applied with, so this plugin has no public way to tell which
16
+ * session a review serves; guessing one would attribute a review to the wrong
17
+ * session and make the recorded reviewer route false. The unsupported
18
+ * per-session reviewer route is recorded in `docs/seam-gap.md` with the public
19
+ * seam it would need.
20
+ *
13
21
  * The official gate stays an opt-in capability: the row mounts it only when
14
22
  * the profile actually started with the experimental-auto-review bundle.
15
23
  * `cordis.patch.yml` disables the official row (one `registerAuto` per