dsh-llm-fallbacks 0.6.13 → 0.8.0

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/dist/chains.d.ts CHANGED
@@ -1,30 +1,13 @@
1
1
  /**
2
- * Fallback chain resolution (spec §7.2; plan fallbacks-role-runtime Task 1).
2
+ * Fallback chain resolution: candidate resolution over ONE chain's effective
3
+ * entries (named virtual chains, plan named-virtual-chains T2).
3
4
  *
4
- * `resolveChain` returns the ordered candidate list for a failing
5
- * (provider, model). Candidates come from the role's own chain plus
6
- * `rootChain` appended as an inherit-root fallback — **append-not-replace**:
7
- * `[...(roleDef.chain ?? []), ...(roleDef.fallback === 'none' ? [] :
8
- * rootChain)]` (role entries first, rootChain entries after). The reserved
9
- * built-in `inherit` id (INHERIT_ROLE_ID) resolves to `rootChain` silently —
10
- * it is the legal no-rule-match role, not a typo'd id. A truly undeclared
11
- * role id (defense) resolves to `rootChain` alone + one warn. Chain-key
12
- * specificity lookup (`exact` / `provider/*` / role / `default` keys) is
13
- * deleted — the two-block config model has no chain keys.
14
- *
15
- * Wildcard entries (`provider/*`) are resolved against the failing model —
16
- * keep the model id, swap only the provider; the "target provider has no
17
- * such model id" skip is judged by {@link resolveCandidate} (via
18
- * `modelExists`) or by the caller's filter.
19
- *
20
- * The concatenation itself lives in ONE helper — {@link buildRoleEntries}
21
- * (qc1 S-3 SSOT) — shared by {@link resolveChainViews} and
22
- * {@link hasWildcardEntry}, so the wildcard probe is exact (qc2 F-003:
23
- * `fallback: 'none'` excludes `rootChain`) and can never diverge from the
24
- * resolution. Defensive warns (unknown role / undeclared rule target) go
25
- * through an injected `warn` callback — the wiring passes the cordis
26
- * `logger.warn` (qc2 F-002 / qc3 S-3), with `console.warn` as the
27
- * direct-call default.
5
+ * `resolveChainViews` / `resolveChain` walk the chain's entries ONCE and
6
+ * resolve each against the failing (provider, model). Wildcard entries
7
+ * (`provider/*`) keep the model id and swap only the provider; the "target
8
+ * provider has no such model id" skip is judged by {@link resolveCandidate}
9
+ * (via `modelExists`) or by the caller's filter — applied to wildcard-origin
10
+ * candidates ONLY ({@link selectCandidates}).
28
11
  *
29
12
  * Cooldown / failed-set / same-as-current filtering is caller-side;
30
13
  * {@link createCandidateFilter} provides the ready-made predicate. A
@@ -33,10 +16,9 @@
33
16
  * than the failing model's cannot fit the request either
34
17
  * ({@link fitsContextWindow}; unknown windows keep the candidate).
35
18
  *
36
- * @module dsh-llm-fallbacks/chains
19
+ @module dsh-llm-fallbacks/chains
37
20
  */
38
- import { type FallbacksRole } from './config.ts';
39
- import type { CooldownStore, StepFailureSet } from './cooldown.ts';
21
+ import type { CooldownStore } from './cooldown.ts';
40
22
  import { type Selector } from './selectors.ts';
41
23
  /** The model that just failed — the anchor for chain resolution. */
42
24
  export interface FailingModel {
@@ -56,25 +38,16 @@ export interface FailingModel {
56
38
  */
57
39
  export declare function resolveCandidate(entry: string, failing: FailingModel, modelExists?: (provider: string, model: string) => boolean): Selector | null;
58
40
  /**
59
- * Single-pass chain resolution for the decision path (T1 review Important
60
- * #2). Walks the role's concatenated entries ONCE and returns the unfiltered
61
- * view — `all` (every resolvable candidate, no filter and no existence
62
- * probe: the caller's early-exit / annotation view) — plus the parallel
63
- * `wildcard` provenance (`wildcard[i]` = candidate `all[i]` came from a
64
- * `provider/*` entry). The caller builds the existence probe from `all` and
65
- * then derives the surviving view with {@link selectCandidates}, so the
66
- * decision path never resolves the chain twice (and an unknown role warns
67
- * at most once per decision).
68
- *
69
- * The reserved built-in `inherit` id (INHERIT_ROLE_ID) is legal — it
70
- * resolves to `rootChain` silently (the no-rule-match role, not a typo'd
71
- * id). Only a truly undeclared id (∉ declared ids ∪ {'inherit'}) warns once
72
- * (defensive — never crashes) through `warn` (defaults to `console.warn`
73
- * for direct-call compatibility; the decision path injects the cordis
74
- * `logger.warn` — qc2 F-002 / qc3 S-3). Malformed entries never become
75
- * candidates.
41
+ * Single-pass chain resolution over one chain's entries. Walks `entries` ONCE
42
+ * and returns the unfiltered view — `all` (every resolvable candidate, no
43
+ * filter and no existence probe: the caller's early-exit / annotation view) —
44
+ * plus the parallel `wildcard` provenance (`wildcard[i]` = candidate `all[i]`
45
+ * came from a `provider/*` entry). The caller builds the existence probe from
46
+ * `all` and then derives the surviving view with {@link selectCandidates}, so
47
+ * the decision path never resolves the chain twice. Malformed entries never
48
+ * become candidates.
76
49
  */
77
- export declare function resolveChainViews(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string, provider: string, model: string, warn?: (message: string) => void): {
50
+ export declare function resolveChainViews(entries: readonly string[], provider: string, model: string, warn?: (message: string) => void): {
78
51
  all: Selector[];
79
52
  wildcard: boolean[];
80
53
  };
@@ -88,46 +61,24 @@ export declare function resolveChainViews(roles: readonly FallbacksRole[], rootC
88
61
  */
89
62
  export declare function selectCandidates(all: readonly Selector[], wildcard: readonly boolean[], filter?: (candidate: Selector) => boolean, modelExists?: (provider: string, model: string) => boolean): Selector[];
90
63
  /**
91
- * Ordered fallback candidates for the failing (provider, model).
92
- *
93
- * Candidates are the role's concatenated entries (spec §7.2, see
94
- * {@link buildRoleEntries} — the single concatenation SSOT): the declared
95
- * role's own `chain` followed by `rootChain` unless `fallback: 'none'`
96
- * (append-not-replace — role entries first, rootChain as the tail). The
97
- * reserved built-in `inherit` id resolves to `rootChain` silently; a truly
98
- * undeclared/unknown `role` id resolves to `rootChain` alone and warns once
99
- * (defensive — never crashes). `filter` optionally drops candidates — the
100
- * caller owns cooldown/failed-set/same-model filtering (see
101
- * {@link createCandidateFilter}). The `modelExists` existence probe is NOT
102
- * applied per entry here: {@link resolveChainViews} returns the parallel
103
- * `wildcard` provenance, and {@link selectCandidates} applies `modelExists`
104
- * to `provider/*`-origin candidates ONLY (qc1 S-2 — exact entries are never
105
- * existence-filtered, spec §2 clause 2 / T2 review Important #1
106
- * decision-path contract). `warn` forwards to {@link resolveChainViews}
107
- * (defaults to `console.warn`; the decision path injects `logger.warn`).
64
+ * Ordered fallback candidates for the failing (provider, model), over ONE
65
+ * chain's `entries`. `filter` optionally drops candidates — the caller owns
66
+ * cooldown/failed-set/same-model filtering (see {@link createCandidateFilter}).
67
+ * The `modelExists` existence probe is applied by {@link selectCandidates} to
68
+ * `provider/*`-origin candidates ONLY (exact entries are never
69
+ * existence-filtered). `warn` forwards to {@link resolveChainViews}.
108
70
  *
109
71
  * Single-pass: delegates to {@link resolveChainViews} + {@link selectCandidates}
110
- * (one concatenated-entry walk, warn at most once per call).
72
+ * (one entry walk).
111
73
  */
112
- export declare function resolveChain(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string, provider: string, model: string, filter?: (candidate: Selector) => boolean, modelExists?: (provider: string, model: string) => boolean, warn?: (message: string) => void): Selector[];
74
+ export declare function resolveChain(entries: readonly string[], provider: string, model: string, filter?: (candidate: Selector) => boolean, modelExists?: (provider: string, model: string) => boolean, warn?: (message: string) => void): Selector[];
113
75
  /**
114
- * Whether any entry of the role's concatenated candidates is a wildcard
115
- * (`provider/*`). F-002: {@link resolveChain} resolves wildcard entries to
116
- * concrete models, so the wildcard provenance is invisible on the resolved
117
- * candidate list — the decision path consults the same concatenation
118
- * `resolveChain` walks to decide whether the catalog existence probe is
119
- * needed at all.
120
- *
121
- * The probe walks {@link buildRoleEntries} — the SAME concatenation
122
- * {@link resolveChainViews} resolves (qc1 S-3 single SSOT) — so it is
123
- * exact, never an over-approximation (qc2 F-003 / qc3 S-1): for
124
- * `fallback: 'none'` roles `rootChain` is excluded, and a wildcard that can
125
- * never reach the candidate list never builds a catalog probe. No
126
- * false-negative is possible: any wildcard the resolution could reach is on
127
- * this concatenation by construction. Malformed entries never become
128
- * candidates and are skipped.
76
+ * Whether any entry of `entries` is a wildcard (`provider/*`). The decision
77
+ * path consults this to decide whether the catalog existence probe is needed
78
+ * at all — a wildcard that can never reach the candidate list never builds a
79
+ * catalog probe. Malformed entries never become candidates and are skipped.
129
80
  */
130
- export declare function hasWildcardEntry(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string): boolean;
81
+ export declare function hasWildcardEntry(entries: readonly string[]): boolean;
131
82
  /** Inputs for {@link createCandidateFilter}. */
132
83
  export interface CandidateFilterOptions {
133
84
  /** The currently active (failing) model — skipped as "same as current". */
@@ -135,7 +86,9 @@ export interface CandidateFilterOptions {
135
86
  /** Cooldown store: suppressed candidates are skipped (keyed `provider/model`). */
136
87
  cooldown: Pick<CooldownStore, 'isSuppressed'>;
137
88
  /** Failed-set for the current step: already-failed candidates are skipped. */
138
- failed: Pick<StepFailureSet, 'has'>;
89
+ failed: {
90
+ has(key: string): boolean;
91
+ };
139
92
  /** Optional existence probe: candidates the target provider lacks are skipped. */
140
93
  modelExists?: (provider: string, model: string) => boolean;
141
94
  /**
@@ -1,83 +1,32 @@
1
1
  /**
2
- * Fallbacks settings card — the `fallbacks` plugin card on the web
3
- * Plugins page (spec §4). Registered into the `plugins.bundle.config` keyed
4
- * slot (key `dsh-llm-fallbacks`, the bundle package name, alongside
5
- * the upstream bash/agent-loop/web-search cards and the advisor card, in
6
- * registration order); owner props are empty and all data flows
7
- * through {@link FallbacksSettingsController}.
2
+ * Fallbacks settings card — the `fallbacks` plugin card on the web Plugins
3
+ * page (named virtual chains, plan named-virtual-chains T5; the FROZEN UI
4
+ * mockup `.mstar/design/named-virtual-chains/settings-mockup.html`).
8
5
  *
9
- * FLAT rebuild (plan fallbacks-web-ux-alignment T3, the advisor PR #98
10
- * pattern): the collapsible card chrome is gone — no header button, no
11
- * rotating chevron, no dirty "unsaved" pill, no card-local open state, no
12
- * bordered `<li>` box. The page already renders the plugin title/description
13
- * above the card (the `locale/*.json` meta files the host resolves per UI
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
17
- * 12px vertical padding with 0.5px hairline separators, 34px inputs on the
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 /
21
- * error / read-only / migration notices render flat atop the form (the
22
- * unavailable notice stays always-visible per KD-G5 — the documented
23
- * divergence from the advisor, whose unavailable card renders nothing: the
24
- * fallbacks skeleton stays the USABLE last-known form and saves are
25
- * attempted).
6
+ * Flat form vocabulary unchanged: fields at 12px vertical padding with 0.5px
7
+ * hairline separators, 34px inputs on the 0.5px l4 border, per-section
8
+ * Discard + Save pairs. The 虚拟链 group is TAB-PER-CHAIN: underline tabs on
9
+ * the 0.5px l2 hairline (active label = label-primary + 2px brand underline,
10
+ * dirty pip, error "!" mark), a "+" add affordance and a "⋯" chain menu
11
+ * (delete, ≥1 chain), WAI-ARIA tabs with roving tabindex.
12
+ * Each tab body carries 链名称 (the single label editor — the ⋯ menu binds to
13
+ * the same draft field) followed by the SHIPPED block order 分时槽设置 →
14
+ * 默认降级链 → 默认模型(链尾) plus a per-tab Discard/Save writing ONLY that
15
+ * chain (block ids per chain, error text under the owning block).
26
16
  *
27
- * The config-level `enabled` switch is REMOVED (T2, breaking): there is no
28
- * checkbox row and no `enabled.off` hiding — all fields render
29
- * unconditionally; the Plugins-page row toggle is the master switch and the
30
- * runtime gates read content presence (`isFallbackActive`).
17
+ * The 子代理 section is role cards: id + persona + 指定降级链 (a MANUAL
18
+ * named-chain pointer; 未指定(沿用 dsh 默认) = the plugin does not
19
+ * intervene). No rules, no per-role chain entries, no host-policy block.
20
+ * The 高级选项 group is unchanged; the 运行状态(只读) block and the
21
+ * General settings row are REMOVED with the client switch-history read.
31
22
  *
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.
23
+ * Validation blocks the owning save only: chain id format/duplicates,
24
+ * missing label, per-chain slot guards, tail conformance, role id checks.
25
+ * A non-empty `state.legacyKeys` renders the migration banner at the top.
48
26
  *
49
- * The form body is the two-block editing surface (spec §8): the 6 top-level
50
- * scalar fields (trigger codes / revert policy / three numeric fields), the
51
- * `rootChain` block (block 1 — the root agent's single chain, no key input),
52
- * and the roles block (block 2 — declared role entity cards from
53
- * `roles.list` plus the rule rows from `roles.rules`, whose role field is a
54
- * dropdown bound to the declared ids + the built-in `inherit`, same-page
55
- * live). Row-level disclosures that are CONTENT keep their expand/collapse:
56
- * the time-slot row cards and the role entity cards (with the seeded
57
- * persona briefs) stay collapsible — a read-only view forces them open so
58
- * the configs stay reachable. The 主代理 / 子代理 / 高级选项 group labels
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).
62
- *
63
- * Validation: id format/reserved word/duplicates, undeclared rule role
64
- * references, illegal selectors, and a role with no chain entries (no model
65
- * config) block the write (the Save disabled gate) with per-section banners
66
- * + inline red borders / hints (never touching the store error path); a
67
- * non-empty `state.legacyKeys` renders the migration banner at the top.
68
- * The reset-to-defaults affordance is GONE from the card (PR #62 UX
69
- * round 3) — the gateway RPC `fallbacks/reset` and the store
70
- * `resetToDefaults()` stay as host APIs, only the card UI was removed.
71
- *
72
- * The AC-7 read-only status block (recent-switch summary, D-5) stays at the
73
- * bottom, flat.
74
- *
75
- * Presentation is ui-primitives-free (T4, advisor parity + reference §4
76
- * risk): Button/Tooltip and the five icon imports are replaced by
77
- * self-drawn styled elements — inline SVG icons (plus / trash / chevron /
78
- * grip) and the CSS `data-tip` tooltip bubble (the card's existing
79
- * icon-button pattern); every color resolves through a `--dsw-alias-*`
80
- * token so the card adapts to the light/dark theme.
27
+ * Presentation stays ui-primitives-free: inline SVG icons and the CSS
28
+ * `data-tip` tooltip bubble; every color resolves through a
29
+ * `--dsw-alias-*` token (light/dark parity).
81
30
  */
82
31
  import type { ReactNode } from 'react';
83
32
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
@@ -94,10 +43,8 @@ export interface FallbacksCardInjected {
94
43
  /** Props delivered by the slot outlet: runtime share + locale seat + inject face. */
95
44
  export type FallbacksCardProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<'fallbacks'> & InjectFace<FallbacksCardInjected>;
96
45
  /**
97
- * Render the Fallbacks settings card inside the plugin-config section —
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.
100
- * @param props - slot-delivered injected dependencies and the synthesized t seat.
101
- * @returns the card.
46
+ * Render the Fallbacks settings card inside the plugin-config section
47
+ * (named virtual chains): the 虚拟链 tab group, the 子代理 role cards and
48
+ * the 高级选项 group, flat under the page's plugin title/description.
102
49
  */
103
50
  export declare function FallbacksCard({ controller, useSnapshot, t }: FallbacksCardProps): ReactNode;