dsh-model-switch 0.4.33 → 0.4.35

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,21 @@
1
+ ## v0.4.35
2
+
3
+ 「自动审查」不再在每次热重载后掉回「完全权限」:释放 Auto 注册之前先把当时选着 Auto 的会话落盘,重新认领后按 id 恢复。
4
+
5
+ - **P0 root cause: every release moves the selection, and the in-memory guard dies with the row (`src/auto-review.ts`).** The official gate's teardown moves live `auto` sessions to the fallback preset (`danger-full-access`) by design. This row triggers that teardown on both of its release paths: the `yieldRegistration` seam the takeover runs before a recompose, and its own row disposal during a hot reload. `preserveAuto` could not cover either window — it remembers around an **in-process** action, but a reload loses the selection before anything of ours runs again, and the next mount's `rememberAuto()` returns `[]` because the sessions were already flipped. The user-visible symptom: the session kept reverting from「自动审查」to「完全权限」, reproducibly **at the moment `cordis.patch.yml` was edited** (any profile patch edit forces a recompose).
6
+ - **Fix: durable snapshot + replay (`src/auto-sessions-state.ts`, `src/auto-review.ts`).** `persistAutoSnapshot()` writes the ids of the sessions currently on Auto to `$DSH_HOME/model-switch/auto-sessions.json` **before** each release — from the takeover's `yieldRegistration` seam and from the last-registered teardown effect (the first thing to run on disposal). `claim()` then calls `flushAutoSnapshot()` once the registration is held again, restoring exactly those sessions and consuming the file. A snapshot is rejected after 15 minutes (`AUTO_SESSION_STATE_MAX_AGE_MS`), so a later restart cannot resurrect a preset the user has since chosen; every malformed shape degrades to a no-op, and a read-only `DSH_HOME` never blocks the release.
7
+ - **Tests.** New `tests/auto-sessions-state.spec.ts` pins the round trip, dedupe and dropping of non-string ids, the malformed-shape matrix (missing, empty, invalid JSON, wrong shape, non-numeric `savedAt`), and the 15-minute replay window. `pnpm run check` green (56 test files / **500** tests + pack gate).
8
+ - **Live verification (before tagging).** The build was installed into the real desktop profile and the web UI was driven over CDP from this machine: the session was switched to「自动审查」, a `cordis.patch.yml` edit forced a recompose — the exact action that used to flip the preset — and the preset chip in the live page still read「自动审查」afterwards. The snapshot directory (`$DSH_HOME/model-switch/`) was created and consumed by the same recompose, so the persist → replay path ran end to end.
9
+
10
+ ## v0.4.34
11
+
12
+ The「停用失败:dsh: warning: 1 entry did not activate auto-review … permission: preset "auto" is already registered」toast is gone: every official-row activation now gets the registration yielded first, and this row releases the slot in the first synchronous step of its own teardown.
13
+
14
+ - **P0 root cause: the yield was one-shot (`src/auto-review-takeover.ts`).** The `loader/patch-context` listener kept an `handled` set so "a repeated patch-context does not loop" — but that guard also **skipped the yield from the second activation on**. The loader calls `_patchContext([])` right before every `registry.plugin` mount (confirmed in the loader source: `Entry._init()` = import → `_patchContext([])` → `registry.plugin`), and a plugin-manager enable goes through `Entry.update({ disabled: false }) → init()` — so the re-activating official row's `registerAuto` ran while this row still held the slot, failed with `preset "auto" is already registered`, and the manager reported the whole operation as 「停用失败」. `Entry.update({ disabled: true })` never emits `partial-dispose`, so the set was never cleared and the suppression was permanent. The guard is gone: **every** activation of the official row yields synchronously before the loader continues (`releaseForTakeover` is idempotent), and the existing per-entry single-flight reclaim keeps repeated triggers on one cycle, so this cannot loop. Two regression tests pin it: a second activation yields again, and non-official rows stay out of the way.
15
+ - **Release first on our own teardown (`src/auto-review.ts`).** Disposing the gate fiber now releases the Auto registration in its **first synchronous step** (the loader does not await fiber disposal, and the official row's re-activation in the same recompose must find the slot free), and a last-registered effect releases it again as the teardown's first step — belt-and-braces for registrations claimed without a gate fiber (a direct `reclaimRegistration()`).
16
+ - **Live reproduction and verification.** Reproduced on the real desktop Host through the web UI before fixing: enabling「自动授权审查」with `dsh-model-switch` active **failed twice** (the switch silently reverted), and the very same operation succeeded with `dsh-model-switch` disabled — the seam conflict isolated by a single variable. `pnpm run check` green (55 test files / **496** tests + pack gate). Post-fix web-UI regression covers enable/disable cycles with the official row active: no toasts, no warnings, no failed activations.
17
+ - **Diagnosis recorded (the reported "运行失败" was not this plugin).** The turn-28 outage was a **model-request transport failure** (`Connection error.` / `TRANSPORT`, provider `apigoto`, five fast rejections in 8 s) — the same failure class this deployment has seen on provider accounts before (10-13 `ark` ×10, 10-15 `xiaomi` ×29). A web-UI A/B with this plugin **on and off** ran 5/5 model requests clean in both states (and the naming call alongside), and this repo has no code path that touches the request transport (no `llm/stream` listener, no fetch/global patch). The 17:49 recovery after disabling plugins is confounded: the permission preset flipped `auto → danger-full-access` in the same minute, which also removes the per-tool-call review traffic against the same account.
18
+
1
19
  ## v0.4.33
2
20
 
3
21
  The reviewer takeover now reports its own health to the settings page instead of only writing a Host log line.
@@ -5,6 +23,7 @@ The reviewer takeover now reports its own health to the settings page instead of
5
23
  - **Runtime-capability field (`src/runtime-capabilities.ts`, `src/host-runtime.ts`).** `autoReview` joins the capability snapshot: `{ available: true }` while this row holds the official reviewer route, `{ available: false, reason: 'auto-review-takeover' }` once it does not. The value comes from a live record (`autoReviewStatus` in `src/auto-review.ts`) updated on every claim, on the lost-seam path, and by the tool-call safety net — which also counts the calls it allowed unreviewed, so the card can say how much review was skipped.
6
24
  - **Settings card warning (`src/client/ModelSwitchSettings.tsx`, `src/client/locales.ts`).** The「自动审查」section renders `autoReviewTakeoverNote` while the route is held and `autoReviewTakeoverWarning` otherwise (zh + en: tool calls keep running, those calls are unreviewed, restart DSH to converge). What used to be a Host-only log line is now visible where the human configures the route.
7
25
  - **Verification.** `pnpm run check` green (55 test files / 494 tests + pack gate). The safety net's test now also asserts the allowed-call counter the snapshot reads, so the card's data path is covered end to end (host record → capability snapshot → card text selected by `available`).
26
+ - **Lab reproduction of the reported outage (released artifact).** The published `dsh-model-switch@0.4.33` was installed in the lab profile (`DSH_HOME=~/.dsh-lab`) with the official `auto-review` row **force-enabled** in the profile patch, the Host was booted on 3082, and the web UI was driven over CDP (a dedicated Edge instance, the user's own browser untouched). A first real tool call completed in 8 s with the actual directory listing. **Without restarting the Host**, `dsh plugin --profile lab remove dsh-model-switch` + `add dsh-model-switch@0.4.33` performed the hot swap and the profile patch toggled the official row off and back on (two runtime recompositions); a second real tool call then completed in 7 s with the file list, and the page contained **no** `inactive context` / `cannot get required service` / `body was not executed` / `Auto review of tool` failure. This is the disable → remove → install → enable sequence that used to require a DSH restart.
8
27
 
9
28
  ## v0.4.32
10
29