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/README.md +27 -27
- package/README.zh-CN.md +25 -25
- package/dist/chain-walk.d.ts +63 -77
- package/dist/chains.d.ts +32 -101
- package/dist/client/FallbacksCard.d.ts +27 -80
- package/dist/client/fallbacks-store.d.ts +71 -225
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.js +1256 -1926
- package/dist/client/locales.d.ts +59 -105
- package/dist/commands.d.ts +68 -103
- package/dist/config.d.ts +152 -105
- package/dist/gateway.d.ts +63 -109
- package/dist/index.d.ts +3 -25
- package/dist/index.js +552 -775
- package/dist/role-resolution.d.ts +14 -43
- package/dist/schema.d.ts +6 -5
- package/dist/seeds.d.ts +5 -7
- package/dist/state.d.ts +0 -42
- package/dist/subagents-seam.d.ts +23 -45
- package/dist/time-slots.d.ts +21 -24
- package/dist/tui-settings.d.ts +5 -8
- package/dist/virtual-adapter.d.ts +63 -108
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +1 -1
- package/dist/client/GeneralFallbacksRow.d.ts +0 -19
- package/dist/roles.d.ts +0 -89
- package/dist/route-allowlist.d.ts +0 -43
- package/dist/subagent-policy.d.ts +0 -137
package/dist/config.d.ts
CHANGED
|
@@ -4,32 +4,27 @@
|
|
|
4
4
|
* module must stay free of `@deepseek-ai/*` value imports so the client
|
|
5
5
|
* bundle never reaches schemastery).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (id
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
7
|
+
* Named virtual chains (config v2, plan named-virtual-chains): N named
|
|
8
|
+
* chains each selectable as `FallbacksChain/<chain id>`, carrying their own
|
|
9
|
+
* `entries` (默认降级链 + 链尾) and `timeSlots`. Declared roles keep identity
|
|
10
|
+
* (`id`/`persona`/`prompt?`/`permissions?`) plus an OPTIONAL `chain`
|
|
11
|
+
* POINTER — a manually specified chain id (「指定降级链」), never an auto
|
|
12
|
+
* mode: absent/empty = NOT SPECIFIED, the plugin does not intervene and the
|
|
13
|
+
* child inherits the delegating agent's route. The v1 two-block model
|
|
14
|
+
* (`rootChain` + per-role inline chains + `roles.rules`) is deleted; the
|
|
15
|
+
* runtime consumes the v2 shape directly and flags surviving v1 keys at
|
|
16
|
+
* startup via `detectLegacyKeys` (warn-only migration — no auto-write).
|
|
16
17
|
*
|
|
17
|
-
* Spec §
|
|
18
|
+
* Spec §1 is authoritative for field names and default values — notably
|
|
18
19
|
* `triggerCodes` defaults to dsh's stable failure codes `['AUTH', 'QUOTA',
|
|
19
|
-
* 'RATE_LIMIT']
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* master switch (row disable = fiber destroy), and the runtime gates re-key
|
|
25
|
-
* to content presence. A stored profile still carrying the removed
|
|
26
|
-
* `enabled:` key loads cleanly — the gateway tolerates and strips it (the
|
|
27
|
-
* advisor LEGACY_KEYS mechanism), it is never read and never re-persisted.
|
|
20
|
+
* 'RATE_LIMIT']`. An unconfigured install (empty `chains`) is a no-op
|
|
21
|
+
* pass-through exactly like an uninstalled plugin — there is NO config-level
|
|
22
|
+
* `enabled` switch: the Plugins-page row toggle is the master switch and the
|
|
23
|
+
* runtime gates re-key to `isFallbackActive` (`chains.length > 0`). Nothing
|
|
24
|
+
* pre-creates a default chain: empty `chains` is the ONLY first-run state.
|
|
28
25
|
*
|
|
29
26
|
* This module is pure logic: it must not import any `@deepseek-ai/*` package
|
|
30
|
-
* (types included) — `FallbacksConfig` is the plugin's own type.
|
|
31
|
-
* registers the schemastery schema (see `./schema.ts`) with
|
|
32
|
-
* `installSettingsSection` under the `fallbacks` settings namespace.
|
|
27
|
+
* (types included) — `FallbacksConfig` is the plugin's own type.
|
|
33
28
|
*
|
|
34
29
|
* @module dsh-llm-fallbacks/config
|
|
35
30
|
*/
|
|
@@ -38,31 +33,31 @@ import type { SlotRowConfig } from './time-slots.ts';
|
|
|
38
33
|
export type RecoveryPolicy = 'timer' | 'half-open';
|
|
39
34
|
/** How a cooled-down model comes back (spec §4). */
|
|
40
35
|
export type RevertPolicy = 'cooldown-expiry' | 'never';
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* so this constraint is IGNORED at match time — root requests never
|
|
46
|
-
* match rules. Kept in the type/schema so pre-feedback `settings.yaml`
|
|
47
|
-
* files that carry `origin` still parse, validate, and save unchanged.
|
|
48
|
-
*/
|
|
49
|
-
origin?: 'root' | 'subagent';
|
|
50
|
-
provider?: string;
|
|
51
|
-
model?: string;
|
|
52
|
-
role: string;
|
|
53
|
-
}
|
|
36
|
+
/** Chain id format (v2): case-sensitive, uppercase allowed so the migrated
|
|
37
|
+
* default id is exactly `Auto`. Deliberately NOT {@link ROLE_ID_PATTERN},
|
|
38
|
+
* which keeps governing role ids. */
|
|
39
|
+
export declare const CHAIN_ID_PATTERN: RegExp;
|
|
54
40
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* `
|
|
41
|
+
* One named virtual chain (v2). `id` doubles as the picker model id —
|
|
42
|
+
* renaming it would orphan every saved selection, so only `label` is
|
|
43
|
+
* editable in the UI. The LAST `entries` element is the 链尾: exactly one
|
|
44
|
+
* official model. The FIRST matching `timeSlots` row REPLACES `entries`.
|
|
58
45
|
*/
|
|
59
|
-
export
|
|
46
|
+
export interface NamedChain {
|
|
47
|
+
id: string;
|
|
48
|
+
/** Settings tab title + picker display prefix (display-only). */
|
|
49
|
+
label: string;
|
|
50
|
+
/** 默认降级链 + 链尾. */
|
|
51
|
+
entries: string[];
|
|
52
|
+
/** 分时 rows scoped to this chain; first match replaces {@link entries}. */
|
|
53
|
+
timeSlots?: SlotRowConfig[];
|
|
54
|
+
}
|
|
60
55
|
/**
|
|
61
|
-
* A declared role entity (
|
|
56
|
+
* A declared role entity (v2): identity + an optional chain POINTER.
|
|
62
57
|
*
|
|
63
58
|
* `prompt` / `permissions` are schema-reserved for the next iteration
|
|
64
|
-
*
|
|
65
|
-
*
|
|
59
|
+
* — no consumer this round, and writing them does NOT change this
|
|
60
|
+
* round's degradation behavior.
|
|
66
61
|
*/
|
|
67
62
|
export interface FallbacksRole {
|
|
68
63
|
id: string;
|
|
@@ -75,21 +70,45 @@ export interface FallbacksRole {
|
|
|
75
70
|
allow?: string[];
|
|
76
71
|
deny?: string[];
|
|
77
72
|
};
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Manually specified named chain id for children dispatched under this
|
|
75
|
+
* role (「指定降级链」) — a POINTER into `chains[]`, never a chain
|
|
76
|
+
* definition. Absent or empty ⇒ NOT SPECIFIED: the plugin does not
|
|
77
|
+
* intervene and the child inherits the delegating agent's route. The
|
|
78
|
+
* plugin never picks a chain on its own.
|
|
79
|
+
*/
|
|
80
|
+
chain?: string;
|
|
80
81
|
}
|
|
81
|
-
/** Role grouping
|
|
82
|
+
/** Role grouping (v2): declared entities only — `rules` is deleted. */
|
|
82
83
|
export interface FallbacksRoles {
|
|
83
84
|
list: FallbacksRole[];
|
|
84
|
-
rules: FallbacksRoleRule[];
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* One declared role as the SCHEMA may compose it: identical to
|
|
88
|
+
* {@link FallbacksRole} except that a v1 profile still carries the ARRAY form
|
|
89
|
+
* of `chain`. The schema must accept it (composition happens in cordis BEFORE
|
|
90
|
+
* `apply()`, so a rejection there kills the fiber and no migration can run);
|
|
91
|
+
* {@link migrateLegacyConfig} narrows it to the string pointer.
|
|
92
|
+
*/
|
|
93
|
+
export interface FallbacksRoleSource extends Omit<FallbacksRole, 'chain'> {
|
|
94
|
+
chain?: string | string[];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The composed settings shape produced by the schemastery `Config` — the input
|
|
98
|
+
* of {@link migrateLegacyConfig}, not the shape the runtime consumes.
|
|
99
|
+
*/
|
|
100
|
+
export interface FallbacksConfigSource extends Omit<FallbacksConfig, 'roles'> {
|
|
101
|
+
roles: {
|
|
102
|
+
list: FallbacksRoleSource[];
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The full `fallbacks` settings shape (named virtual chains, verbatim
|
|
88
107
|
* field names).
|
|
89
108
|
*/
|
|
90
109
|
export interface FallbacksConfig {
|
|
91
110
|
triggerCodes: string[];
|
|
92
|
-
|
|
111
|
+
chains: NamedChain[];
|
|
93
112
|
roles: FallbacksRoles;
|
|
94
113
|
cooldownMs: number;
|
|
95
114
|
revertPolicy: RevertPolicy;
|
|
@@ -98,21 +117,12 @@ export interface FallbacksConfig {
|
|
|
98
117
|
* Preset-role injection switch: `'bundled'` declares the 5 preset roles
|
|
99
118
|
* (spec §9.2) as seed rows on apply; `'none'` disables declaration.
|
|
100
119
|
* Optional on purpose — a required field would break library consumers
|
|
101
|
-
* that construct `FallbacksConfig` literals
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
120
|
+
* that construct `FallbacksConfig` literals. The value domain is guarded
|
|
121
|
+
* by the schema (`Config` in `src/schema.ts`), NOT by
|
|
122
|
+
* `validateFallbacksConfig`, and every resolved config carries a value
|
|
123
|
+
* via the schema default.
|
|
105
124
|
*/
|
|
106
125
|
presets?: 'bundled' | 'none';
|
|
107
|
-
/**
|
|
108
|
-
* Extra time-slot rows (plan fallbacks-timeslots Task 1, P5): the FIRST
|
|
109
|
-
* matching row's chain becomes the effective root chain at request time;
|
|
110
|
-
* `rootChain` (the all-day chain) is always the last row.
|
|
111
|
-
* Optional on purpose, mirroring `presets` — additive, non-breaking for
|
|
112
|
-
* library consumers. Malformed rows warn once and are skipped by the
|
|
113
|
-
* resolver; the gateway rejects them on save (Task 3).
|
|
114
|
-
*/
|
|
115
|
-
timeSlots?: SlotRowConfig[];
|
|
116
126
|
/**
|
|
117
127
|
* Config-level timezone for slot matching (default `Asia/Shanghai`,
|
|
118
128
|
* UTC+8). Not per-slot.
|
|
@@ -123,41 +133,40 @@ export interface FallbacksConfig {
|
|
|
123
133
|
* 1): `'timer'` restores the preferred candidate when the cooldown
|
|
124
134
|
* expires (today's behavior); `'half-open'` leaves the route half-open
|
|
125
135
|
* for one logged probe instead. Optional on purpose, mirroring
|
|
126
|
-
* `presets
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* (`Config` in `src/schema.ts`), NOT by `validateFallbacksConfig`, and
|
|
130
|
-
* every resolved config carries a value via the schema default.
|
|
136
|
+
* `presets`. The value domain is guarded by the schema, NOT by
|
|
137
|
+
* `validateFallbacksConfig`, and every resolved config carries a value
|
|
138
|
+
* via the schema default.
|
|
131
139
|
*/
|
|
132
140
|
recovery?: RecoveryPolicy;
|
|
133
141
|
}
|
|
134
142
|
/**
|
|
135
|
-
* Spec §
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* (
|
|
143
|
+
* Spec §1 defaults — `Config({})` must equal this (no-op install).
|
|
144
|
+
* Empty `chains` is the ONLY first-run state: nothing pre-creates an
|
|
145
|
+
* `Auto` chain and nothing seeds `entries` — the picker row appears only
|
|
146
|
+
* after the user creates a chain in the card. The migrated/seeded default
|
|
147
|
+
* chain (`{ id: 'Auto', label: '默认链', entries, timeSlots }`) exists only
|
|
148
|
+
* in the migration table (docs) applied by hand — warn-only migration.
|
|
140
149
|
*/
|
|
141
150
|
export declare const defaultFallbacksConfig: FallbacksConfig;
|
|
151
|
+
/** Role id format (aligned with yet-another-subagent `isValidProfileId`). */
|
|
152
|
+
export declare const ROLE_ID_PATTERN: RegExp;
|
|
142
153
|
/**
|
|
143
|
-
* Reserved role id:
|
|
144
|
-
*
|
|
154
|
+
* Reserved role id: FORBIDDEN in `roles.list[].id` (the validator's reserved
|
|
155
|
+
* warn) and in seed declarations (`seeds.ts` reserved-id skip). `rules` that
|
|
156
|
+
* could target it are deleted (v2), but the id itself stays reserved so a
|
|
157
|
+
* declared role can never shadow the "no specific role" meaning.
|
|
145
158
|
*/
|
|
146
159
|
export declare const INHERIT_ROLE_ID = "inherit";
|
|
147
160
|
/**
|
|
148
|
-
* The no-op gate (AC-8)
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* `config.enabled` sites (request waterfalls, picker-row registration, TUI
|
|
156
|
-
* summary, General-row badge) all read through this.
|
|
161
|
+
* The no-op gate (AC-8): the plugin intervenes exactly when at least one
|
|
162
|
+
* named chain is configured. Roles alone no longer activate (a role
|
|
163
|
+
* without a chain is pure identity). An inactive config is a pass-through
|
|
164
|
+
* exactly like an uninstalled plugin; the Plugins-page row toggle remains
|
|
165
|
+
* the master switch (row disable = fiber destroy). A non-empty chain with
|
|
166
|
+
* empty `entries` still registers the picker row (content presence) but
|
|
167
|
+
* refuses to delegate (conformance gate in the walk).
|
|
157
168
|
*/
|
|
158
169
|
export declare function isFallbackActive(config: FallbacksConfig): boolean;
|
|
159
|
-
/** Role id format (aligned with yet-another-subagent `isValidProfileId`). */
|
|
160
|
-
export declare const ROLE_ID_PATTERN: RegExp;
|
|
161
170
|
/**
|
|
162
171
|
* Minimal logger surface {@link validateFallbacksConfig} warns through —
|
|
163
172
|
* keeps this module free of `@deepseek-ai/*` imports (a cordis Logger is
|
|
@@ -168,29 +177,67 @@ export interface FallbacksConfigLogger {
|
|
|
168
177
|
}
|
|
169
178
|
/**
|
|
170
179
|
* Validate a fallbacks config (pure, warn-only — never throws, never
|
|
171
|
-
* mutates):
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
+
* mutates). Per chain: id format (case-sensitive, {@link CHAIN_ID_PATTERN})
|
|
181
|
+
* + cross-chain uniqueness, tail conformance (the LAST entry must be
|
|
182
|
+
* exactly one official model — a non-conforming chain's slot rows and
|
|
183
|
+
* delegate stay inert), selector legality per entry, and the slot-row
|
|
184
|
+
* guards (unknown preset, duplicate preset per chain, preset rows carrying
|
|
185
|
+
* their own windows). Per role (after the chain loop, so the declared id
|
|
186
|
+
* set is complete): the existing id format/reserved/duplicate warns, and a
|
|
187
|
+
* non-empty `chain` pointer naming no declared chain id earns ONE warn and
|
|
188
|
+
* behaves as NOT SPECIFIED. Each violation emits one `llm-fallbacks: ...`
|
|
189
|
+
* warn — the config stays usable (warn-not-crash semantics). `persona` and
|
|
190
|
+
* `label` are free text and deliberately not validated.
|
|
180
191
|
*/
|
|
181
192
|
export declare function validateFallbacksConfig(config: FallbacksConfig, logger: FallbacksConfigLogger): void;
|
|
182
193
|
/**
|
|
183
|
-
* Detect legacy
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
194
|
+
* Detect legacy config leftovers in a config SOURCE — the composed object
|
|
195
|
+
* `source()` returns, or a raw settings document. Two generations:
|
|
196
|
+
*
|
|
197
|
+
* - Older removals: `alwaysModeRetryCap`, `roleAutoMatch` (direction C),
|
|
198
|
+
* `roles.default`, and the renamed role-entity fields
|
|
199
|
+
* `roles.list[].label` / `roles.list[].description`.
|
|
200
|
+
* - v1 two-block keys (named-virtual-chains): `rootChain`, top-level
|
|
201
|
+
* `timeSlots`, `roles.rules`, `roles.list[].fallback`, and the v1 ARRAY
|
|
202
|
+
* form of `roles.list[].chain` (an array where v2 declares a string —
|
|
203
|
+
* stripped rather than coerced).
|
|
204
|
+
*
|
|
205
|
+
* Returns descriptive key names; the gateway attaches them as
|
|
206
|
+
* `get().legacyKeys` so the client can show a migration banner. Warn-only:
|
|
207
|
+
* no legacy value is ever read or re-persisted.
|
|
195
208
|
*/
|
|
196
209
|
export declare function detectLegacyKeys(source: Record<string, unknown>): string[];
|
|
210
|
+
/**
|
|
211
|
+
* The chain a v1 `rootChain` migrates into (plan fallbacks-v1-auto-migration):
|
|
212
|
+
* the id is stable so an already-selected `FallbacksChain/Auto` route keeps
|
|
213
|
+
* resolving, and the label is display-only.
|
|
214
|
+
*/
|
|
215
|
+
export declare const MIGRATED_CHAIN_ID = "Auto";
|
|
216
|
+
/** Display label of {@link MIGRATED_CHAIN_ID}. */
|
|
217
|
+
export declare const MIGRATED_CHAIN_LABEL = "\u9ED8\u8BA4\u94FE";
|
|
218
|
+
/**
|
|
219
|
+
* Upgrade a config SOURCE to the v2 named-chain shape — in memory, at every
|
|
220
|
+
* read. Pure, total, idempotent: a v2 config comes back unchanged, a v1 one is
|
|
221
|
+
* converted, and any malformed shape degrades to the defaults instead of
|
|
222
|
+
* throwing (the banner keeps reporting the raw leftovers, so honesty survives
|
|
223
|
+
* the coercion).
|
|
224
|
+
*
|
|
225
|
+
* Conversion rules (plan §Goal):
|
|
226
|
+
* 1. v1 `rootChain` + top-level `timeSlots` become chain #0
|
|
227
|
+
* (`{ id: 'Auto', label: '默认链', entries, timeSlots }`), so the picker
|
|
228
|
+
* route the user already selected keeps working.
|
|
229
|
+
* 2. `roles.list[]` survive as identity rows (`id` / `persona` / reserved
|
|
230
|
+
* `prompt` / `permissions`); a v1 ARRAY `chain` is dropped (NOT SPECIFIED —
|
|
231
|
+
* the card's 指定降级链 select takes over) and `fallback` is dropped;
|
|
232
|
+
* `roles.rules` / `roles.default` / `enabled` never reach the output.
|
|
233
|
+
* 3. A config that already carries a non-empty `chains` array keeps it and only
|
|
234
|
+
* loses the v1 leftovers. An EMPTY `chains` alongside a v1 `rootChain` is a
|
|
235
|
+
* profile that never had v2 content, so it migrates (otherwise the user's
|
|
236
|
+
* chain would silently vanish).
|
|
237
|
+
*
|
|
238
|
+
* No file is rewritten here — the next explicit save persists the v2 shape.
|
|
239
|
+
* @param source - the composed config (schema defaults → plugin row → user
|
|
240
|
+
* layer) or any raw document.
|
|
241
|
+
* @returns a complete {@link FallbacksConfig}; never the input object itself.
|
|
242
|
+
*/
|
|
243
|
+
export declare function migrateLegacyConfig(source: unknown): FallbacksConfig;
|
package/dist/gateway.d.ts
CHANGED
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
* @module dsh-llm-fallbacks/gateway
|
|
67
67
|
*/
|
|
68
68
|
import type { Context } from '@deepseek-ai/cordis';
|
|
69
|
-
import { type SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
69
|
+
import SettingsForms, { type SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
70
70
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
71
71
|
import type { TypertContribution } from '@deepseek-ai/dsh-typert-registry';
|
|
72
72
|
import type { FallbacksConfig } from './config';
|
|
@@ -91,63 +91,6 @@ export declare const FALLBACKS_SETTINGS_NAMESPACE: SettingsNamespace;
|
|
|
91
91
|
* reference `apply()` reads — no separate registration step.
|
|
92
92
|
*/
|
|
93
93
|
export declare const FALLBACKS_PROFILE_ENTRY = "llm-fallbacks";
|
|
94
|
-
/** One exact child LLM route on the additive `subagentPolicy` wire field (T5). */
|
|
95
|
-
export type SubagentPolicyRoute = {
|
|
96
|
-
provider: string;
|
|
97
|
-
model: string;
|
|
98
|
-
};
|
|
99
|
-
/** Settings namespace of the host's subagent-model-selection policy. */
|
|
100
|
-
export declare const SUBAGENT_POLICY_SETTINGS_NS = "subagent-model-selection-settings";
|
|
101
|
-
/**
|
|
102
|
-
* Additive `/api/fallbacks/get` host-policy snapshot (spec D4 / T5).
|
|
103
|
-
* Omitted only when the snapshot is unwired (3-arg constructor) so older
|
|
104
|
-
* clients and gateway unit tests keep a byte-identical `{ config,
|
|
105
|
-
* legacyKeys, seeds }` payload. A wired snapshot always emits the field —
|
|
106
|
-
* `{ state: 'disabled' }` included — so a write after an enabled get cannot
|
|
107
|
-
* keep-last an allowlist. Never written through `set`/`reset`.
|
|
108
|
-
*/
|
|
109
|
-
export type SubagentPolicyWire = {
|
|
110
|
-
state: 'enabled';
|
|
111
|
-
allowedModels: SubagentPolicyRoute[];
|
|
112
|
-
head?: {
|
|
113
|
-
route: SubagentPolicyRoute;
|
|
114
|
-
source: 'authorized' | 'injected';
|
|
115
|
-
};
|
|
116
|
-
blockedAttempt?: {
|
|
117
|
-
at: number;
|
|
118
|
-
route: SubagentPolicyRoute;
|
|
119
|
-
reason: string;
|
|
120
|
-
};
|
|
121
|
-
} | {
|
|
122
|
-
state: 'disabled';
|
|
123
|
-
} | {
|
|
124
|
-
state: 'unprovable';
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* Live host-policy snapshot the gateway projects onto the wire. Built in
|
|
128
|
-
* `apply()` from the SAME T1 reader the override paths use — no second
|
|
129
|
-
* policy source. Optional: the 3-arg constructor (gateway unit tests)
|
|
130
|
-
* omits the field entirely.
|
|
131
|
-
*/
|
|
132
|
-
export type SubagentPolicySnapshotFn = () => {
|
|
133
|
-
policy: {
|
|
134
|
-
state: 'disabled';
|
|
135
|
-
} | {
|
|
136
|
-
state: 'enabled';
|
|
137
|
-
allowedModels: readonly SubagentPolicyRoute[];
|
|
138
|
-
} | {
|
|
139
|
-
state: 'unprovable';
|
|
140
|
-
};
|
|
141
|
-
head?: {
|
|
142
|
-
route: SubagentPolicyRoute;
|
|
143
|
-
source: 'authorized' | 'injected';
|
|
144
|
-
};
|
|
145
|
-
blockedAttempt?: {
|
|
146
|
-
at: number;
|
|
147
|
-
route: SubagentPolicyRoute;
|
|
148
|
-
reason: string;
|
|
149
|
-
};
|
|
150
|
-
};
|
|
151
94
|
/**
|
|
152
95
|
* The live configuration source for the gateway (guide §7 — the same bridge
|
|
153
96
|
* shape the runtime reads through). `source()` returns the live composed
|
|
@@ -157,7 +100,15 @@ export type SubagentPolicySnapshotFn = () => {
|
|
|
157
100
|
* `onChange` fan-out was removed in the QC fix wave — nothing subscribed).
|
|
158
101
|
*/
|
|
159
102
|
export interface FallbacksSettingsBridge {
|
|
103
|
+
/** The EFFECTIVE config the runtime reads (v1 already upgraded in memory). */
|
|
160
104
|
source(): FallbacksConfig;
|
|
105
|
+
/**
|
|
106
|
+
* The STORED view, before the in-memory upgrade. Legacy detection reads it,
|
|
107
|
+
* so the migration banner keeps reporting keys the effective config no
|
|
108
|
+
* longer carries (plan fallbacks-v1-auto-migration). Optional: a caller
|
|
109
|
+
* without one is assumed to hold the raw shape itself.
|
|
110
|
+
*/
|
|
111
|
+
rawSource?(): FallbacksConfig;
|
|
161
112
|
}
|
|
162
113
|
/** Patch shape accepted by `fallbacks.set` — any subset of the config keys. */
|
|
163
114
|
export type FallbacksConfigPatch = Partial<FallbacksConfig>;
|
|
@@ -170,13 +121,6 @@ export interface FallbacksReadResult {
|
|
|
170
121
|
config: FallbacksConfig;
|
|
171
122
|
legacyKeys: string[];
|
|
172
123
|
seeds: SeedsWireStatus[];
|
|
173
|
-
/**
|
|
174
|
-
* Host subagent-model-selection policy (spec D4). Additive — old readers
|
|
175
|
-
* ignore it; omitted only when no snapshot is wired. A wired snapshot
|
|
176
|
-
* always carries the field (`disabled` included) so keep-last cannot
|
|
177
|
-
* retain an allowlist after policy-off.
|
|
178
|
-
*/
|
|
179
|
-
subagentPolicy?: SubagentPolicyWire;
|
|
180
124
|
}
|
|
181
125
|
/** The `fallbacks/revert-seed` response — a read result plus the revert outcome. */
|
|
182
126
|
export interface FallbacksRevertResult extends FallbacksReadResult {
|
|
@@ -200,12 +144,6 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
200
144
|
private readonly seeds;
|
|
201
145
|
/** The live settings service once the optional inject child activates. */
|
|
202
146
|
private settings;
|
|
203
|
-
/**
|
|
204
|
-
* Optional host-policy snapshot (T5). When omitted the additive
|
|
205
|
-
* `subagentPolicy` field is absent on every read — the 3-arg constructor
|
|
206
|
-
* used by gateway unit tests stays byte-identical to the pre-T5 payload.
|
|
207
|
-
*/
|
|
208
|
-
private readonly subagentPolicy;
|
|
209
147
|
/**
|
|
210
148
|
* @param ctx - owning context (the plugin fiber's ctx inside `apply`).
|
|
211
149
|
* @param bridge - the same `FallbacksSettingsBridge` the runtime reads, so
|
|
@@ -213,22 +151,21 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
213
151
|
* @param seeds - the per-apply `FallbacksSeedManager` constructed in
|
|
214
152
|
* `apply()` — the gateway delegates badge state and revert to it (no
|
|
215
153
|
* copied manager logic), through the io seam built over this bridge.
|
|
216
|
-
* @param subagentPolicy - live host-policy snapshot from `apply()`; omit
|
|
217
|
-
* to leave the additive field off the wire.
|
|
218
154
|
*/
|
|
219
|
-
constructor(ctx: Context, bridge: FallbacksSettingsBridge, seeds: FallbacksSeedManager
|
|
155
|
+
constructor(ctx: Context, bridge: FallbacksSettingsBridge, seeds: FallbacksSeedManager);
|
|
220
156
|
/**
|
|
221
157
|
* Read the current composed config (schema defaults → entry base → profile
|
|
222
158
|
* patch). No hard-gate resolver (ADR-2): the raw composed config is
|
|
223
159
|
* the wire value. The removed `enabled` key never crosses the wire (the
|
|
224
160
|
* read whitelist drops it — the snapshot-strip half of LEGACY_KEYS).
|
|
225
|
-
* @returns the wire-normalized composed config plus `legacyKeys` —
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* ignore it)
|
|
161
|
+
* @returns the wire-normalized composed config plus `legacyKeys` — every
|
|
162
|
+
* v1 field detected on the composed source (`rootChain`, top-level
|
|
163
|
+
* `timeSlots`, `roles.rules`, `roles.default`, `roles.list[].fallback`,
|
|
164
|
+
* the v1 ARRAY form of `roles.list[].chain`, plus the older
|
|
165
|
+
* `alwaysModeRetryCap` / `roleAutoMatch` / renamed role fields —
|
|
166
|
+
* schemastery retains them), so the client can show a migration banner
|
|
167
|
+
* (incremental field — old clients ignore it) — plus the additive `seeds`
|
|
168
|
+
* badge state (legacyKeys precedent — old clients ignore it).
|
|
232
169
|
*/
|
|
233
170
|
get(): FallbacksReadResult;
|
|
234
171
|
/**
|
|
@@ -277,22 +214,6 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
277
214
|
* write channel is unavailable (KD-G5).
|
|
278
215
|
*/
|
|
279
216
|
revertSeed(id: string): Promise<FallbacksRevertResult>;
|
|
280
|
-
/**
|
|
281
|
-
* Merge the CURRENT host allowlist with the exact routes configured in this
|
|
282
|
-
* plugin's chains (every `roles.list[].chain` entry plus `rootChain`), plus
|
|
283
|
-
* the virtual `FallbacksChain/Auto` route. De-duplicated by exact
|
|
284
|
-
* provider+model with first-seen order kept (the current allowlist first).
|
|
285
|
-
* Wildcard (`provider/*`) and unparseable entries are skipped — never
|
|
286
|
-
* expanded into routes. This is the only allowlist write face; it runs
|
|
287
|
-
* exclusively from the explicit card button (never silently) and writes
|
|
288
|
-
* ONLY `allowedModels` — never `enabled`.
|
|
289
|
-
* @returns the post-write read result (the fresh `subagentPolicy` snapshot
|
|
290
|
-
* reflects the new allowlist).
|
|
291
|
-
* @throws when no live policy snapshot is wired, the policy is not proven
|
|
292
|
-
* enabled (`disabled` / `unprovable`), or no settings service is composed
|
|
293
|
-
* (KD-G5: no allowlist write without a proven-enabled policy).
|
|
294
|
-
*/
|
|
295
|
-
syncAllowlist(): Promise<FallbacksReadResult>;
|
|
296
217
|
/**
|
|
297
218
|
* Read the live composed config and normalize it to the typertGateway JSON
|
|
298
219
|
* wire boundary. Containment (guide §10): a malformed stored patch section
|
|
@@ -318,26 +239,59 @@ export declare class FallbacksConfigGateway extends TypertRemoteService {
|
|
|
318
239
|
*/
|
|
319
240
|
private readResult;
|
|
320
241
|
/**
|
|
321
|
-
* The io seam the seed manager writes through (spec §9.1): `read` walks
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
* correct under the settings merge's array
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
242
|
+
* The io seam the seed manager writes through (spec §9.1): `read` walks the
|
|
243
|
+
* same live bridge source the gateway reads; `writeRoles` persists the
|
|
244
|
+
* declared roles as `{ list }` — the v2 shape, whose rows are `{ id,
|
|
245
|
+
* persona, chain? }` — computed from a fresh composed read, so the write
|
|
246
|
+
* stays correct under the settings merge's replace-whole-array semantics
|
|
247
|
+
* and every operator-specified `chain` pointer survives verbatim. It goes
|
|
248
|
+
* through {@link writeSection}, so a seed write also drops any stored v1
|
|
249
|
+
* field. The write channel fails with the same KD-G5 message as set/reset
|
|
250
|
+
* when no settings service is composed. Built fresh per call so the mutable
|
|
251
|
+
* settings capture is read at call time (the inject child swaps it when the
|
|
252
|
+
* settings service appears/disappears).
|
|
330
253
|
*/
|
|
331
254
|
private seedsIo;
|
|
255
|
+
/**
|
|
256
|
+
* Write a CLEANED patch and, in the same save, remove v1 fields the
|
|
257
|
+
* persisted section may still carry. `settings.update` is merge-only, so a
|
|
258
|
+
* stored `rootChain` / top-level `timeSlots` / `roles.rules` /
|
|
259
|
+
* `roles.list[].fallback` would otherwise survive every v2 save; the
|
|
260
|
+
* removals ride `settings.mutate`'s `unset` path ops, which touch only the
|
|
261
|
+
* named fields (unrelated configuration, foreign keys and redacted secrets
|
|
262
|
+
* are preserved — a wholesale `replace` rebuilt from a descriptor would not
|
|
263
|
+
* be).
|
|
264
|
+
* @param patch - already stripped of v1 fields and null values.
|
|
265
|
+
*/
|
|
266
|
+
private writeSection;
|
|
332
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Persist a CLEANED patch and, in the same operation, remove the v1 fields the
|
|
270
|
+
* ACTIVE PROFILE's persisted section still holds. `settings.update` is
|
|
271
|
+
* merge-only, so a stored `rootChain` / top-level `timeSlots` / `roles.rules` /
|
|
272
|
+
* `roles.list[].fallback` would otherwise survive every v2 save; the removals
|
|
273
|
+
* ride `settings.mutate`'s path ops, which touch only the named fields
|
|
274
|
+
* (unrelated configuration, foreign keys and redacted secrets are preserved —
|
|
275
|
+
* a wholesale `replace` rebuilt from a descriptor would delete them).
|
|
276
|
+
*
|
|
277
|
+
* Exported because the plugin has TWO write entry points — the gateway
|
|
278
|
+
* (`set` / `writeRoles`) and the apply-level `SeedsIo` (`declareSeeds`,
|
|
279
|
+
* `revertSeededPersona`, the bundled preset self-declaration) — and the same
|
|
280
|
+
* logical operation must clean the same way through both.
|
|
281
|
+
* @param settings - the live settings service.
|
|
282
|
+
* @param patch - a patch; v1 fields are stripped here, callers need not.
|
|
283
|
+
* @param ns - profile entry id (defaults to the plugin's own).
|
|
284
|
+
*/
|
|
285
|
+
export declare function writeFallbacksSection(settings: SettingsForms, patch: Record<string, unknown>, ns?: string): Promise<void>;
|
|
333
286
|
/**
|
|
334
287
|
* Reject a patch the `Config` schema cannot express: non-object input, unknown
|
|
335
288
|
* top-level keys (schemastery merges them silently — the settings service
|
|
336
289
|
* would accept them), and schema type violations — plus the Task 3 semantic
|
|
337
290
|
* guards the permissive schema deliberately does not carry: nested `roles`
|
|
338
|
-
* keys, `
|
|
339
|
-
*
|
|
340
|
-
*
|
|
291
|
+
* keys, `chains[]` row shape (id pattern, case-sensitive uniqueness, label),
|
|
292
|
+
* per-chain `timeSlots` row shapes (unknown preset ids, duplicate presets,
|
|
293
|
+
* preset rows carrying windows, non-`HH:mm` custom bounds, out-of-range days,
|
|
294
|
+
* empty chains), and each chain's non-empty `entries` tail conformance.
|
|
341
295
|
*
|
|
342
296
|
* Exported (plan fallbacks-tui-settings Task 1): the TUI settings section's
|
|
343
297
|
* JSON-field `parse` mirrors the gateway save rules through this single
|