dsh-llm-fallbacks 0.7.0 → 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 roleChainEntries}
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,9 +16,8 @@
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
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. */
@@ -56,42 +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
- * The role's effective concatenated chain entries (spec §7.2) — the SINGLE
60
- * concatenation SSOT (qc1 S-3): the declared role's own `chain` followed by
61
- * `rootChain` unless `fallback: 'none'` (append-not-replace — role entries
62
- * first, rootChain as the tail); the built-in `inherit` id and any unknown
63
- * role id resolve to `rootChain` alone. {@link resolveChainViews} (candidate
64
- * resolution) and {@link hasWildcardEntry} (wildcard probe) both derive
65
- * their candidate source here, so the probe never diverges from the
66
- * resolution (grep-verifiable: one concatenation expression). Role ids are
67
- * compared by trim (qc2 F-001 — the runtime canonicalizes padded declared
68
- * ids, and `resolveRole` returns the declared raw id).
69
- *
70
- * Exported for the chain walk's head anchor (`src/chain-walk.ts`): the walk
71
- * resolves the FIRST DISPATCHABLE exact entry over these same entries, so the
72
- * head it starts at is the head the candidate walk below it starts from.
73
- */
74
- export declare function roleChainEntries(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string): readonly string[];
75
- /**
76
- * Single-pass chain resolution for the decision path (T1 review Important
77
- * #2). Walks the role's concatenated entries ONCE and returns the unfiltered
78
- * view — `all` (every resolvable candidate, no filter and no existence
79
- * probe: the caller's early-exit / annotation view) — plus the parallel
80
- * `wildcard` provenance (`wildcard[i]` = candidate `all[i]` came from a
81
- * `provider/*` entry). The caller builds the existence probe from `all` and
82
- * then derives the surviving view with {@link selectCandidates}, so the
83
- * decision path never resolves the chain twice (and an unknown role warns
84
- * at most once per decision).
85
- *
86
- * The reserved built-in `inherit` id (INHERIT_ROLE_ID) is legal — it
87
- * resolves to `rootChain` silently (the no-rule-match role, not a typo'd
88
- * id). Only a truly undeclared id (∉ declared ids ∪ {'inherit'}) warns once
89
- * (defensive — never crashes) through `warn` (defaults to `console.warn`
90
- * for direct-call compatibility; the decision path injects the cordis
91
- * `logger.warn` — qc2 F-002 / qc3 S-3). Malformed entries never become
92
- * 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.
93
49
  */
94
- 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): {
95
51
  all: Selector[];
96
52
  wildcard: boolean[];
97
53
  };
@@ -105,46 +61,24 @@ export declare function resolveChainViews(roles: readonly FallbacksRole[], rootC
105
61
  */
106
62
  export declare function selectCandidates(all: readonly Selector[], wildcard: readonly boolean[], filter?: (candidate: Selector) => boolean, modelExists?: (provider: string, model: string) => boolean): Selector[];
107
63
  /**
108
- * Ordered fallback candidates for the failing (provider, model).
109
- *
110
- * Candidates are the role's concatenated entries (spec §7.2, see
111
- * {@link roleChainEntries} — the single concatenation SSOT): the declared
112
- * role's own `chain` followed by `rootChain` unless `fallback: 'none'`
113
- * (append-not-replace — role entries first, rootChain as the tail). The
114
- * reserved built-in `inherit` id resolves to `rootChain` silently; a truly
115
- * undeclared/unknown `role` id resolves to `rootChain` alone and warns once
116
- * (defensive — never crashes). `filter` optionally drops candidates — the
117
- * caller owns cooldown/failed-set/same-model filtering (see
118
- * {@link createCandidateFilter}). The `modelExists` existence probe is NOT
119
- * applied per entry here: {@link resolveChainViews} returns the parallel
120
- * `wildcard` provenance, and {@link selectCandidates} applies `modelExists`
121
- * to `provider/*`-origin candidates ONLY (qc1 S-2 — exact entries are never
122
- * existence-filtered, spec §2 clause 2 / T2 review Important #1
123
- * decision-path contract). `warn` forwards to {@link resolveChainViews}
124
- * (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}.
125
70
  *
126
71
  * Single-pass: delegates to {@link resolveChainViews} + {@link selectCandidates}
127
- * (one concatenated-entry walk, warn at most once per call).
72
+ * (one entry walk).
128
73
  */
129
- 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[];
130
75
  /**
131
- * Whether any entry of the role's concatenated candidates is a wildcard
132
- * (`provider/*`). F-002: {@link resolveChain} resolves wildcard entries to
133
- * concrete models, so the wildcard provenance is invisible on the resolved
134
- * candidate list — the decision path consults the same concatenation
135
- * `resolveChain` walks to decide whether the catalog existence probe is
136
- * needed at all.
137
- *
138
- * The probe walks {@link roleChainEntries} — the SAME concatenation
139
- * {@link resolveChainViews} resolves (qc1 S-3 single SSOT) — so it is
140
- * exact, never an over-approximation (qc2 F-003 / qc3 S-1): for
141
- * `fallback: 'none'` roles `rootChain` is excluded, and a wildcard that can
142
- * never reach the candidate list never builds a catalog probe. No
143
- * false-negative is possible: any wildcard the resolution could reach is on
144
- * this concatenation by construction. Malformed entries never become
145
- * 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.
146
80
  */
147
- export declare function hasWildcardEntry(roles: readonly FallbacksRole[], rootChain: readonly string[], role: string): boolean;
81
+ export declare function hasWildcardEntry(entries: readonly string[]): boolean;
148
82
  /** Inputs for {@link createCandidateFilter}. */
149
83
  export interface CandidateFilterOptions {
150
84
  /** The currently active (failing) model — skipped as "same as current". */
@@ -179,11 +113,8 @@ export declare function createCandidateFilter(options: CandidateFilterOptions):
179
113
  * 行为可见性). `context-window` is the `CONTEXT_WINDOW_EXCEEDED` walk's
180
114
  * exclusion: the candidate's disclosed window is not larger than the failing
181
115
  * model's, so the request that just overflowed cannot fit there either.
182
- * `allowlist` is the subagent-policy exclusion: the candidate survived every
183
- * filter but sits outside the host allowlist, so a plugin-originated walk may
184
- * not switch to it. The walk owns that label — the policy read lives there.
185
116
  */
186
- export type CandidateSkipReason = 'same-as-current' | 'cooldown' | 'step-failed' | 'context-window' | 'missing-id' | 'allowlist';
117
+ export type CandidateSkipReason = 'same-as-current' | 'cooldown' | 'step-failed' | 'context-window' | 'missing-id';
187
118
  /** One entry of the ordered, per-candidate annotation (T3 review Minor 1). */
188
119
  export interface AnnotatedCandidate {
189
120
  candidate: Selector;
@@ -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;