dsh-llm-fallbacks 0.6.2 → 0.6.4

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
@@ -7,7 +7,7 @@
7
7
  ![node](https://img.shields.io/badge/node-%3E%3D22-339933.svg)
8
8
  ![pnpm](https://img.shields.io/badge/pnpm-%3E%3D10-f69220.svg)
9
9
  ![dsh tui](https://img.shields.io/badge/dsh%20tui-compatible-4B32C3.svg)
10
- ![dsh](https://img.shields.io/badge/DSH-0.1.7--rc.2-4B32C3.svg)
10
+ ![dsh](https://img.shields.io/badge/DSH-0.2.0--rc.1-4B32C3.svg)
11
11
  [![dshfind](https://dshfind.com/api/badge/omdsh-dev/dsh-llm-fallbacks?lang=en)](https://dshfind.com/zh/plugins/omdsh-dev/dsh-llm-fallbacks?ref=badge)
12
12
 
13
13
  Automatic provider/model fallback chains for dsh (DeepSeek Harness): when an agent's LLM requests keep failing — retries exhausted, auth errors, quota exceeded, rate limiting (429) — the plugin switches provider/model along the fallback chain for the current role, and the current step/turn continues on the target model: tasks are not interrupted by model problems.
@@ -94,7 +94,7 @@ Build the section up in four steps:
94
94
 
95
95
  **2. Set the all-day `rootChain`.** Leading entries are the fallback chain, walked first when a request fails; the **last** entry is the Default model.
96
96
 
97
- > **Conformance**: the last entry must be exactly one official model — `deepseek-official/deepseek-flash` or `deepseek-official/deepseek-v4-pro` (XOR). The settings card and gateway reject any other tail on save; a legacy non-official tail warns at startup and keeps working as a fallback-only walk, but cannot be saved as-is. The retired `deepseek-v4-flash` id is no longer a legal tail — a saved tail with it now warns, goes inert (slot rows + virtual picker), and blocks save until a legal tail is picked. The official Pro tail is catalog-served as `deepseek-official/deepseek-v4-pro` (dsh 0.1.7-rc.2; the claim re-verified against the rc.2 default catalog) and selectable in the card; a hand-written legacy `deepseek-official/deepseek-pro` tail — never catalog-served — reads back as non-conforming (no migration; re-pick the tail). The plugin does not probe catalog availability — a chain containing `deepseek-official/deepseek-v4-pro` dispatches to it like any other exact entry, and on the virtual route the `stream()` delegate serves the first dispatchable exact head of the effective chain, so a chain with a working entry before Pro still routes to that earlier entry.
97
+ > **Conformance**: the last entry must be exactly one official model — `deepseek-official/deepseek-flash` or `deepseek-official/deepseek-v4-pro` (XOR). The settings card and gateway reject any other tail on save; a legacy non-official tail warns at startup and keeps working as a fallback-only walk, but cannot be saved as-is. The retired `deepseek-v4-flash` id is no longer a legal tail — a saved tail with it now warns, goes inert (slot rows + virtual picker), and blocks save until a legal tail is picked. The official Pro tail is catalog-served as `deepseek-official/deepseek-v4-pro` (dsh 0.2.0-rc.1; the claim re-verified against the rc.2 default catalog) and selectable in the card; a hand-written legacy `deepseek-official/deepseek-pro` tail — never catalog-served — reads back as non-conforming (no migration; re-pick the tail). The plugin does not probe catalog availability — a chain containing `deepseek-official/deepseek-v4-pro` dispatches to it like any other exact entry, and on the virtual route the `stream()` delegate serves the first dispatchable exact head of the effective chain, so a chain with a working entry before Pro still routes to that earlier entry.
98
98
 
99
99
  **3. Add `timeSlots` (optional).** Rows rotate the effective root chain by wall-clock windows. Preset rows use frozen UTC+8 windows (only their chain is editable; while a preset row exists, `tz` locks to `Asia/Shanghai`); custom rows take `start`/`end` (may wrap midnight) and an optional `days` list. The first row whose window contains the current moment wins; no match → the all-day `rootChain`. Rotation is a routing seed — it applies on the next root request and consumes no cooldown (see [Time slots](#time-slots)).
100
100
 
@@ -10,7 +10,7 @@
10
10
  * so the `unknown-surface` fallback never picked it up and the transcript
11
11
  * showed nothing).
12
12
  *
13
- * Contract notes (dsh-private, 0.1.2 homes, verified 2026-08-28):
13
+ * Contract notes (dsh-private, current homes, verified 2026-08-28):
14
14
  * - D1 registry: `ConversationEventRegistry.register(definition)` — the
15
15
  * client Context's `uiConversation.events` service seat
16
16
  * (`ui-conversation conversation/event-registry.ts:17-25`); external
@@ -11,11 +11,13 @@
11
11
  * rotating chevron, no dirty "unsaved" pill, no card-local open state, no
12
12
  * bordered `<li>` box. The page already renders the plugin title/description
13
13
  * above the card (the `locale/*.json` meta files the host resolves per UI
14
- * language), so the notices, the form, and the footer tile directly beneath
15
- * it in the official flat settings-form language (reference §3): fields at
14
+ * language), so the notices, the form, and the section actions directly
15
+ * beneath it in the official flat settings-form language (reference §3):
16
+ * fields at
16
17
  * 12px vertical padding with 0.5px hairline separators, 34px inputs on the
17
- * 0.5px l4 border, one dark solid Save footer. The card is ALWAYS open —
18
- * nothing is hidden, so nothing needs disclosure state; the degraded /
18
+ * 0.5px l4 border, per-section Discard + Save pairs on the section
19
+ * headings. The card is ALWAYS open —
20
+ * nothing needs card-level disclosure state; the degraded /
19
21
  * error / read-only / migration notices render flat atop the form (the
20
22
  * unavailable notice stays always-visible per KD-G5 — the documented
21
23
  * divergence from the advisor, whose unavailable card renders nothing: the
@@ -27,19 +29,22 @@
27
29
  * unconditionally; the Plugins-page row toggle is the master switch and the
28
30
  * runtime gates read content presence (`isFallbackActive`).
29
31
  *
30
- * ONE Save replaces the per-section saves (T3): the per-section split
31
- * existed so one section's Save could not ride along another section's
32
- * unsaved edits (PR #62 UX round 3) — with ONE save over ONE document that
33
- * concern is moot. The footer's Save writes the whole validated draft
34
- * (disabled = `!dirty || invalid || saving || !writable` — `invalid` is the
35
- * LIVE whole-form `validateDraft` verdict plus the empty-rule-row check;
36
- * section bucketing survives for ERROR PLACEMENT only: each violation
37
- * renders under its owning section heading). Save additionally carries
38
- * `!writable` (W-1, qc2 fix wave: the dirty-implies-writable assumption
39
- * does not hold for this in-place-draft store). Discard is kept — our
40
- * staged edits survive refresh, the advisor's documented divergence from
41
- * the official no-discard form — a pure client-side revert of the WHOLE
42
- * draft to the last accepted config (`!dirty || saving`).
32
+ * PER-SECTION Save/Discard return beside each section heading (plan
33
+ * fallbacks-card-section-ux, the maintainer's live-host feedback reversing
34
+ * the flat plan's one-document model): the scroll-to-save cost of ONE
35
+ * bottom Save outweighed it. Each section's Save writes ONLY that section's
36
+ * fields (patch = the last ACCEPTED config with the section's fields
37
+ * replaced — the ride-along protection, PR #62 UX round 3), gated by that
38
+ * section's dirty term and validation bucket only (KD-U1: save =
39
+ * `!sectionDirty || saving || !writable`, discard = `!sectionDirty ||
40
+ * saving`; Save additionally carries `!writable` — W-1, qc2 fix wave: the
41
+ * dirty-implies-writable assumption does not hold for this in-place-draft
42
+ * store). Violations render live under their OWNING section heading, and a
43
+ * store write failure renders under the section whose Save was last
44
+ * clicked (`lastSaveSection`). Discard is kept — our staged edits survive
45
+ * refresh, the advisor's documented divergence from the official
46
+ * no-discard form — a pure client-side revert of THAT section's editors to
47
+ * the last accepted config.
43
48
  *
44
49
  * The form body is the two-block editing surface (spec §8): the 6 top-level
45
50
  * scalar fields (trigger codes / revert policy / three numeric fields), the
@@ -51,7 +56,9 @@
51
56
  * the time-slot row cards and the role entity cards (with the seeded
52
57
  * persona briefs) stay collapsible — a read-only view forces them open so
53
58
  * the configs stay reachable. The 主代理 / 子代理 / 高级选项 group labels
54
- * stay as flat, non-collapsible section headings.
59
+ * are semantic `<h2>` headings (T1); 高级选项 is a content-level disclosure
60
+ * collapsed by default (T3 — same expand/collapse contract as the rows, a
61
+ * read-only view forces it open).
55
62
  *
56
63
  * Validation: id format/reserved word/duplicates, undeclared rule role
57
64
  * references, illegal selectors, and a role with no chain entries (no model
@@ -88,8 +95,8 @@ export interface FallbacksCardInjected {
88
95
  export type FallbacksCardProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<'fallbacks'> & InjectFace<FallbacksCardInjected>;
89
96
  /**
90
97
  * Render the Fallbacks settings card inside the plugin-config section —
91
- * flat: the notices, the form, and the footer tile directly under the
92
- * page's plugin title/description, with no chrome of our own.
98
+ * flat: the notices, the form, and the section action pairs directly under
99
+ * the page's plugin title/description, with no chrome of our own.
93
100
  * @param props - slot-delivered injected dependencies and the synthesized t seat.
94
101
  * @returns the card.
95
102
  */
@@ -363,7 +363,7 @@ export declare function rulesToRows(rules: readonly FallbacksRoleRule[], catalog
363
363
  export declare function rowsToRules(rows: readonly RoleRuleRow[]): FallbacksRoleRule[];
364
364
  /**
365
365
  * The settings Remote namespace face the fallbacks store reads
366
- * (0.1.2 `ctx.remote.settings`; api-settings-controller `@Remote` methods).
366
+ * (`ctx.remote.settings`; api-settings-controller `@Remote` methods).
367
367
  */
368
368
  export interface FallbacksSettingsRemote {
369
369
  /** Redacted describe: provider writability plus the namespace directory. */
@@ -371,7 +371,7 @@ export interface FallbacksSettingsRemote {
371
371
  }
372
372
  /**
373
373
  * The llm Remote namespace face the fallbacks store reads
374
- * (0.1.2 `ctx.remote.llm`; dsh-llm `@Remote` methods).
374
+ * (`ctx.remote.llm`; dsh-llm `@Remote` methods).
375
375
  */
376
376
  export interface FallbacksLlmRemote {
377
377
  /** Declared configurable-provider directory, registered or dormant. */
@@ -379,7 +379,7 @@ export interface FallbacksLlmRemote {
379
379
  }
380
380
  /**
381
381
  * The session Remote namespace face the fallbacks store reads
382
- * (0.1.2 `ctx.remote.session`; api-session-controller `@Remote` methods).
382
+ * (`ctx.remote.session`; api-session-controller `@Remote` methods).
383
383
  */
384
384
  export interface FallbacksSessionRemote {
385
385
  /** Host-generation model catalog: provider groups plus per-provider failures. */
@@ -387,7 +387,7 @@ export interface FallbacksSessionRemote {
387
387
  /**
388
388
  * Live event stream for one durable session address; the opening snapshot
389
389
  * frame carries the tail page this store reads, then the stream is
390
- * cancelled (0.1.2 successor of the apiproxy `sessions.history` unary —
390
+ * cancelled (successor of the apiproxy `sessions.history` unary —
391
391
  * `session/page` needs the follow opening frame's cursor, so a cold tail
392
392
  * read goes through the follow opening).
393
393
  */
@@ -479,7 +479,7 @@ export declare class FallbacksSettingsController {
479
479
  /**
480
480
  * Read the recent-switch summary for the current session (spec §2.5 D-5):
481
481
  * one tail page (`maxMessages` = {@link SWITCHES_HISTORY_PAGE}) read from
482
- * the `session/follow` opening snapshot — 0.1.2 has no unary tail-page
482
+ * the `session/follow` opening snapshot — the host has no unary tail-page
483
483
  * read (`session/page` needs the follow opening frame's cursor), so the
484
484
  * stream is cancelled right after its first frame —
485
485
  * `fallbacks/switch` events extracted newest-first capped at
@@ -9,8 +9,8 @@
9
9
  * config rides the plugin's gateway channel (`connection.rpc` →
10
10
  * `/api/fallbacks/get|set|reset`), while `settings/describe` (writable +
11
11
  * namespace directory), the provider/model catalog, and the switch-history
12
- * tail page ride the typed `ctx.remote` namespaces (0.1.2:
13
- * `ConnectionHandle` dropped `api`; see `fallbacks-store.ts`).
12
+ * tail page ride the typed `ctx.remote` namespaces (`ConnectionHandle`
13
+ * dropped `api`; see `fallbacks-store.ts`).
14
14
  * - Registers the `plugins.bundle.config` card `key: 'dsh-llm-fallbacks'`
15
15
  * (bundle package name) with a business-only inject face
16
16
  * ({@link FallbacksSettingsController} + hooks.snapshot); the old
@@ -48,7 +48,7 @@ export { FallbacksSettingsController, FALLBACKS_SETTINGS_NS } from './fallbacks-
48
48
  * (`setCurrentSession` never called, `loadSwitches` ready with an empty
49
49
  * array, which the store already supports).
50
50
  *
51
- * rc.1 dotted-namespace contract: each client Remote namespace is a
51
+ * The host's dotted-namespace contract: each client Remote namespace is a
52
52
  * child-fiber service named `remote.<ns>` (upstream `remoteServiceKey`), and
53
53
  * the traceable proxy forwards `ctx.remote.llm` / `.settings` / `.session`
54
54
  * to those context properties — consumers MUST declare the dotted names