dsh-llm-fallbacks 0.2.1 → 0.3.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.
@@ -37,15 +37,65 @@ export declare const zh: {
37
37
  'advanced.label': string;
38
38
  'advanced.expand': string;
39
39
  'advanced.collapse': string;
40
+ 'roleAutoMatch.label': string;
41
+ 'roleAutoMatch.hint': string;
42
+ 'roleAutoMatch.tooltip': string;
40
43
  'rootChain.label': string;
41
- 'rootChain.hint': string;
42
44
  'rootChain.tooltip': string;
43
- 'rootChain.selector.add': string;
45
+ 'defaultModel.label': string;
44
46
  'chains.selector.remove': string;
45
47
  'chains.selector.providerPlaceholder': string;
46
48
  'chains.selector.modelPlaceholder': string;
47
49
  'chains.selector.wildcardLegacy': string;
48
50
  'chains.selector.noModelsStrict': string;
51
+ 'mainAgent.label': string;
52
+ 'subagents.label': string;
53
+ 'timeSlots.label': string;
54
+ 'timeSlots.hint': string;
55
+ 'timeSlots.tz.label': string;
56
+ 'timeSlots.drag': string;
57
+ 'timeSlots.name': string;
58
+ 'timeSlots.expand': string;
59
+ 'timeSlots.collapse': string;
60
+ 'timeSlots.tooltip': string;
61
+ 'timeSlots.addPreset': string;
62
+ 'timeSlots.addCustom': string;
63
+ 'timeSlots.presetPlaceholder': string;
64
+ 'timeSlots.remove': string;
65
+ 'timeSlots.moveUp': string;
66
+ 'timeSlots.moveDown': string;
67
+ 'timeSlots.start': string;
68
+ 'timeSlots.end': string;
69
+ 'timeSlots.days': string;
70
+ 'timeSlots.days.hint': string;
71
+ 'timeSlots.preset.name': string;
72
+ 'timeSlots.preset.windowLabel': string;
73
+ 'timeSlots.preset.chainsOnly': string;
74
+ 'timeSlots.preset.liang-peak.label': string;
75
+ 'timeSlots.preset.liang-peak.window': string;
76
+ 'timeSlots.preset.liang-valley.label': string;
77
+ 'timeSlots.preset.liang-valley.window': string;
78
+ 'timeSlots.preset.glm-peak.label': string;
79
+ 'timeSlots.preset.glm-peak.window': string;
80
+ 'timeSlots.preset.glm-valley.label': string;
81
+ 'timeSlots.preset.glm-valley.window': string;
82
+ 'timeSlots.preset.glm.note': string;
83
+ 'timeSlots.preset.highCost': string;
84
+ 'timeSlots.preset.multiplier': string;
85
+ 'timeSlots.active': string;
86
+ 'timeSlots.preset.glm.unconfigured': string;
87
+ 'timeSlots.day.sun': string;
88
+ 'timeSlots.day.mon': string;
89
+ 'timeSlots.day.tue': string;
90
+ 'timeSlots.day.wed': string;
91
+ 'timeSlots.day.thu': string;
92
+ 'timeSlots.day.fri': string;
93
+ 'timeSlots.day.sat': string;
94
+ 'timeSlots.selector.add': string;
95
+ 'allDay.hint': string;
96
+ 'allDay.flash': string;
97
+ 'allDay.pro': string;
98
+ 'allDay.nonconforming': string;
49
99
  'roles.list.label': string;
50
100
  'roles.list.hint': string;
51
101
  'roles.list.tooltip': string;
@@ -63,14 +113,12 @@ export declare const zh: {
63
113
  'roles.fallback.none': string;
64
114
  'roles.add': string;
65
115
  'roles.remove': string;
116
+ 'roles.expand': string;
117
+ 'roles.collapse': string;
66
118
  'roles.selector.add': string;
67
119
  'roles.rules': string;
68
120
  'roles.rules.hint': string;
69
121
  'roles.rules.tooltip': string;
70
- 'roles.rule.origin': string;
71
- 'roles.rule.origin.any': string;
72
- 'roles.rule.origin.root': string;
73
- 'roles.rule.origin.subagent': string;
74
122
  'roles.rule.provider': string;
75
123
  'roles.rule.provider.any': string;
76
124
  'roles.rule.model': string;
@@ -88,6 +136,14 @@ export declare const zh: {
88
136
  'validation.ruleRoleUndeclared': string;
89
137
  'validation.ruleRoleRequired': string;
90
138
  'validation.roleChainRequired': string;
139
+ 'validation.allDayRequired': string;
140
+ 'validation.slotChainRequired': string;
141
+ 'validation.slotWindow': string;
142
+ 'validation.slotDays': string;
143
+ 'validation.slotKind': string;
144
+ 'validation.slotPresetUnknown': string;
145
+ 'validation.slotPresetDuplicate': string;
146
+ 'validation.slotPresetFrozen': string;
91
147
  'validation.selector': string;
92
148
  'legacy.banner': string;
93
149
  'catalog.empty': string;
@@ -98,37 +154,32 @@ export declare const zh: {
98
154
  'catalog.outside.short': string;
99
155
  'catalog.unconfigured.short': string;
100
156
  'status.title': string;
101
- 'status.effectiveModel.label': string;
102
- 'status.effectiveModel.unavailable': string;
103
- 'status.effectiveModel.note': string;
104
157
  'status.switches.label': string;
105
158
  'status.switches.empty': string;
106
159
  'status.switches.error': string;
107
160
  'status.switches.compact': string;
161
+ 'status.switches.compact.roleInject': string;
108
162
  'status.switches.reason.trigger-code': string;
109
163
  'status.switches.reason.always-cap': string;
110
- 'status.selectionNote': string;
164
+ 'status.switches.reason.role-inject': string;
111
165
  'general.title': string;
112
166
  'general.enabled': string;
113
167
  'general.disabled': string;
114
168
  'general.unknown': string;
115
169
  'general.unavailable': string;
116
170
  'general.switch': string;
171
+ 'general.switch.roleInject': string;
117
172
  'general.switch.empty': string;
118
173
  'general.error': string;
119
174
  'chat.switch.title': string;
120
175
  'chat.switch.summary': string;
176
+ 'chat.switch.summary.roleInject': string;
177
+ 'chat.switch.roleMap': string;
121
178
  'defaults.prefix': string;
122
179
  save: string;
123
180
  'save.saving': string;
124
181
  'save.error': string;
125
182
  close: string;
126
- reset: string;
127
- 'reset.confirmTitle': string;
128
- 'reset.confirm': string;
129
- 'reset.confirm.cancel': string;
130
- 'reset.confirm.action': string;
131
- 'reset.saving': string;
132
183
  loading: string;
133
184
  unavailable: string;
134
185
  'error.generic': string;
@@ -173,15 +224,65 @@ export declare const en: {
173
224
  'advanced.label': string;
174
225
  'advanced.expand': string;
175
226
  'advanced.collapse': string;
227
+ 'roleAutoMatch.label': string;
228
+ 'roleAutoMatch.hint': string;
229
+ 'roleAutoMatch.tooltip': string;
176
230
  'rootChain.label': string;
177
- 'rootChain.hint': string;
178
231
  'rootChain.tooltip': string;
179
- 'rootChain.selector.add': string;
232
+ 'defaultModel.label': string;
180
233
  'chains.selector.remove': string;
181
234
  'chains.selector.providerPlaceholder': string;
182
235
  'chains.selector.modelPlaceholder': string;
183
236
  'chains.selector.wildcardLegacy': string;
184
237
  'chains.selector.noModelsStrict': string;
238
+ 'mainAgent.label': string;
239
+ 'subagents.label': string;
240
+ 'timeSlots.label': string;
241
+ 'timeSlots.hint': string;
242
+ 'timeSlots.tz.label': string;
243
+ 'timeSlots.drag': string;
244
+ 'timeSlots.name': string;
245
+ 'timeSlots.expand': string;
246
+ 'timeSlots.collapse': string;
247
+ 'timeSlots.tooltip': string;
248
+ 'timeSlots.addPreset': string;
249
+ 'timeSlots.addCustom': string;
250
+ 'timeSlots.presetPlaceholder': string;
251
+ 'timeSlots.remove': string;
252
+ 'timeSlots.moveUp': string;
253
+ 'timeSlots.moveDown': string;
254
+ 'timeSlots.start': string;
255
+ 'timeSlots.end': string;
256
+ 'timeSlots.days': string;
257
+ 'timeSlots.days.hint': string;
258
+ 'timeSlots.preset.name': string;
259
+ 'timeSlots.preset.windowLabel': string;
260
+ 'timeSlots.preset.chainsOnly': string;
261
+ 'timeSlots.preset.liang-peak.label': string;
262
+ 'timeSlots.preset.liang-peak.window': string;
263
+ 'timeSlots.preset.liang-valley.label': string;
264
+ 'timeSlots.preset.liang-valley.window': string;
265
+ 'timeSlots.preset.glm-peak.label': string;
266
+ 'timeSlots.preset.glm-peak.window': string;
267
+ 'timeSlots.preset.glm-valley.label': string;
268
+ 'timeSlots.preset.glm-valley.window': string;
269
+ 'timeSlots.preset.glm.note': string;
270
+ 'timeSlots.preset.highCost': string;
271
+ 'timeSlots.preset.multiplier': string;
272
+ 'timeSlots.active': string;
273
+ 'timeSlots.preset.glm.unconfigured': string;
274
+ 'timeSlots.day.sun': string;
275
+ 'timeSlots.day.mon': string;
276
+ 'timeSlots.day.tue': string;
277
+ 'timeSlots.day.wed': string;
278
+ 'timeSlots.day.thu': string;
279
+ 'timeSlots.day.fri': string;
280
+ 'timeSlots.day.sat': string;
281
+ 'timeSlots.selector.add': string;
282
+ 'allDay.hint': string;
283
+ 'allDay.flash': string;
284
+ 'allDay.pro': string;
285
+ 'allDay.nonconforming': string;
185
286
  'roles.list.label': string;
186
287
  'roles.list.hint': string;
187
288
  'roles.list.tooltip': string;
@@ -199,14 +300,12 @@ export declare const en: {
199
300
  'roles.fallback.none': string;
200
301
  'roles.add': string;
201
302
  'roles.remove': string;
303
+ 'roles.expand': string;
304
+ 'roles.collapse': string;
202
305
  'roles.selector.add': string;
203
306
  'roles.rules': string;
204
307
  'roles.rules.hint': string;
205
308
  'roles.rules.tooltip': string;
206
- 'roles.rule.origin': string;
207
- 'roles.rule.origin.any': string;
208
- 'roles.rule.origin.root': string;
209
- 'roles.rule.origin.subagent': string;
210
309
  'roles.rule.provider': string;
211
310
  'roles.rule.provider.any': string;
212
311
  'roles.rule.model': string;
@@ -224,6 +323,14 @@ export declare const en: {
224
323
  'validation.ruleRoleUndeclared': string;
225
324
  'validation.ruleRoleRequired': string;
226
325
  'validation.roleChainRequired': string;
326
+ 'validation.allDayRequired': string;
327
+ 'validation.slotChainRequired': string;
328
+ 'validation.slotWindow': string;
329
+ 'validation.slotDays': string;
330
+ 'validation.slotKind': string;
331
+ 'validation.slotPresetUnknown': string;
332
+ 'validation.slotPresetDuplicate': string;
333
+ 'validation.slotPresetFrozen': string;
227
334
  'validation.selector': string;
228
335
  'legacy.banner': string;
229
336
  'catalog.empty': string;
@@ -234,37 +341,32 @@ export declare const en: {
234
341
  'catalog.outside.short': string;
235
342
  'catalog.unconfigured.short': string;
236
343
  'status.title': string;
237
- 'status.effectiveModel.label': string;
238
- 'status.effectiveModel.unavailable': string;
239
- 'status.effectiveModel.note': string;
240
344
  'status.switches.label': string;
241
345
  'status.switches.empty': string;
242
346
  'status.switches.error': string;
243
347
  'status.switches.compact': string;
348
+ 'status.switches.compact.roleInject': string;
244
349
  'status.switches.reason.trigger-code': string;
245
350
  'status.switches.reason.always-cap': string;
246
- 'status.selectionNote': string;
351
+ 'status.switches.reason.role-inject': string;
247
352
  'general.title': string;
248
353
  'general.enabled': string;
249
354
  'general.disabled': string;
250
355
  'general.unknown': string;
251
356
  'general.unavailable': string;
252
357
  'general.switch': string;
358
+ 'general.switch.roleInject': string;
253
359
  'general.switch.empty': string;
254
360
  'general.error': string;
255
361
  'chat.switch.title': string;
256
362
  'chat.switch.summary': string;
363
+ 'chat.switch.summary.roleInject': string;
364
+ 'chat.switch.roleMap': string;
257
365
  'defaults.prefix': string;
258
366
  save: string;
259
367
  'save.saving': string;
260
368
  'save.error': string;
261
369
  close: string;
262
- reset: string;
263
- 'reset.confirmTitle': string;
264
- 'reset.confirm': string;
265
- 'reset.confirm.cancel': string;
266
- 'reset.confirm.action': string;
267
- 'reset.saving': string;
268
370
  loading: string;
269
371
  unavailable: string;
270
372
  'error.generic': string;
@@ -279,9 +381,12 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
279
381
  }
280
382
  /**
281
383
  * Reason → locale key map for switch summaries (S-c; shared by the card's
282
- * status block and the General page status row). The session log is durable
283
- * and forward-compatible: a reason value outside the current union (a newer
284
- * plugin wrote it) renders raw instead of falling into a binary else branch.
384
+ * status block, the General page status row, and the conversation node). All
385
+ * reasons resolve from ONE key family (`status.switches.reason.*`) — the
386
+ * shared reason vocabulary must not mix dictionary families (qc1 F-004). The
387
+ * session log is durable and forward-compatible: a reason value outside the
388
+ * current union (a newer plugin wrote it) renders raw instead of falling
389
+ * into a binary else branch.
285
390
  */
286
391
  export declare const SWITCH_REASON_KEYS: Readonly<Partial<Record<FallbackSwitchReason, FallbacksKey>>>;
287
392
  /** Human-readable trigger-code labels (spec §4 用户直观性). */
@@ -24,6 +24,7 @@ import type { CommandDefinition } from '@deepseek-ai/dsh-commands';
24
24
  import { type FallbacksRole } from './config.ts';
25
25
  import type { Origin } from './roles.ts';
26
26
  import type { FallbacksSwitchEventData } from './events.ts';
27
+ import type { SlotRowConfig } from './time-slots.ts';
27
28
  /** How many recent `fallbacks/switch` events `/fallbacks` shows (newest first). */
28
29
  export declare const RECENT_SWITCHES_LIMIT = 5;
29
30
  /** Minimal command registry surface (satisfied by the dsh `CommandService`). */
@@ -68,7 +69,15 @@ export interface FallbacksCommandSnapshot {
68
69
  * `fallback` is `'inherit-root'` — or the role is unknown — and rootChain
69
70
  * is non-empty; the diagnostic annotation source, spec §7.4). */
70
71
  readonly inherit: boolean;
71
- /** Recent `fallbacks/switch` events, newest first, capped at {@link RECENT_SWITCHES_LIMIT}. */
72
+ /** Current time-slot winner + display label (P5/P7): the 分时切换 side of
73
+ * the status strip. The switches section below is the 降级切换 side —
74
+ * the copy never mixes. */
75
+ readonly slot: {
76
+ readonly winner: SlotRowConfig | 'all-day';
77
+ readonly label: string;
78
+ };
79
+ /** Recent `fallbacks/switch` events (failure walks — 降级切换), newest
80
+ * first, capped at {@link RECENT_SWITCHES_LIMIT}. */
72
81
  readonly switches: readonly FallbacksSwitchEventData[];
73
82
  /** Active cooldown entries for the agent. */
74
83
  readonly cooldown: readonly FallbacksCooldownEntry[];
@@ -95,6 +104,8 @@ export interface FallbacksConfigSummary {
95
104
  readonly maxSwitchesPerStep: number;
96
105
  readonly alwaysModeRetryCap: number;
97
106
  readonly presets: 'bundled' | 'none';
107
+ /** Dispatch-time LLM role auto-match switch (default true). */
108
+ readonly roleAutoMatch: boolean;
98
109
  }
99
110
  /**
100
111
  * The session-scoped read-only operations the `/fallbacks` handler drives.
@@ -116,7 +127,7 @@ export interface FallbacksCommandController {
116
127
  export declare const FALLBACKS_COMMAND_LOCALES: {
117
128
  readonly zh: {
118
129
  readonly title: "当前会话 fallback 诊断(只读)";
119
- readonly description: "查看当前会话的降级链、最近切换与冷却状态(只读)";
130
+ readonly description: "查看当前会话的降级链、最近降级切换与冷却状态(只读)";
120
131
  readonly usageConfig: "查看组合后的 fallbacks 配置(设置回读)";
121
132
  readonly usage: " /fallbacks config 查看组合后的 fallbacks 配置(设置回读)";
122
133
  readonly origin: "会话来源";
@@ -124,7 +135,8 @@ export declare const FALLBACKS_COMMAND_LOCALES: {
124
135
  readonly chain: "链";
125
136
  readonly inheritRoot: "(inherit-root)";
126
137
  readonly chainNone: "未配置";
127
- readonly switches: "最近切换";
138
+ readonly slot: "分时";
139
+ readonly switches: "最近降级切换";
128
140
  readonly switchesNone: "本会话暂无 fallback 切换";
129
141
  readonly switchLine: "{from} → {to}(role={role},reason={reason})";
130
142
  readonly cooldown: "冷却";
@@ -148,12 +160,13 @@ export declare const FALLBACKS_COMMAND_LOCALES: {
148
160
  readonly configMaxSwitches: "单步最大切换";
149
161
  readonly configAlwaysCap: "always 上限";
150
162
  readonly configPresets: "预置";
163
+ readonly configRoleAutoMatch: "角色自动匹配";
151
164
  readonly configEdit: "编辑:~/.dsh/profiles/<profile>/cordis.patch.yml(插件行)或 $DSH_HOME/settings.yaml(fallbacks: 分节)";
152
165
  readonly configEditHint: "TUI 无法修改配置——只能编辑文件";
153
166
  };
154
167
  readonly en: {
155
168
  readonly title: "Session fallback diagnostics (read-only)";
156
- readonly description: "Inspect fallback chain, recent switches, and cooldown for this session (read-only)";
169
+ readonly description: "Inspect fallback chain, recent fallback switches, and cooldown for this session (read-only)";
157
170
  readonly usageConfig: "show the composed fallbacks config (settings readback)";
158
171
  readonly usage: " /fallbacks config show the composed fallbacks config (settings readback)";
159
172
  readonly origin: "Session origin";
@@ -161,7 +174,8 @@ export declare const FALLBACKS_COMMAND_LOCALES: {
161
174
  readonly chain: "Chain";
162
175
  readonly inheritRoot: " (inherit-root)";
163
176
  readonly chainNone: "not configured";
164
- readonly switches: "Recent switches";
177
+ readonly slot: "Time slot";
178
+ readonly switches: "Recent fallback switches";
165
179
  readonly switchesNone: "No fallback switches in this session";
166
180
  readonly switchLine: "{from} → {to} (role={role}, reason={reason})";
167
181
  readonly cooldown: "Cooldown";
@@ -185,6 +199,7 @@ export declare const FALLBACKS_COMMAND_LOCALES: {
185
199
  readonly configMaxSwitches: "Max switches/step";
186
200
  readonly configAlwaysCap: "Always-mode cap";
187
201
  readonly configPresets: "Presets";
202
+ readonly configRoleAutoMatch: "Auto-match";
188
203
  readonly configEdit: "Edit: ~/.dsh/profiles/<profile>/cordis.patch.yml (plugin row) or $DSH_HOME/settings.yaml (fallbacks: section)";
189
204
  readonly configEditHint: "TUI cannot change config — edit files only";
190
205
  };
@@ -249,8 +264,8 @@ export declare const FALLBACKS_CONFIG_LIST_CAP = 5;
249
264
  export declare function fallbacksConfigText(summary: FallbacksConfigSummary, locale?: FallbacksCommandLocale): string;
250
265
  /**
251
266
  * Render the `/fallbacks` status surface for one snapshot. Kept minimal and
252
- * truthful: origin → role → chain (+ inherit tail) → recent switches →
253
- * cooldown.
267
+ * truthful: origin → role → chain (+ inherit tail) → current slot (分时) →
268
+ * recent switches (降级切换) → cooldown.
254
269
  */
255
270
  export declare function fallbacksCommandText(snapshot: FallbacksCommandSnapshot, locale?: FallbacksCommandLocale): string;
256
271
  /**
package/dist/config.d.ts CHANGED
@@ -27,10 +27,17 @@
27
27
  *
28
28
  * @module dsh-llm-fallbacks/config
29
29
  */
30
+ import type { SlotRowConfig } from './time-slots.ts';
30
31
  /** How a cooled-down model comes back (spec §4). */
31
32
  export type RevertPolicy = 'cooldown-expiry' | 'never';
32
- /** A single role rule: match on origin/provider/model patterns (spec §3). */
33
+ /** A single role rule: match on provider/model patterns (spec §3). */
33
34
  export interface FallbacksRoleRule {
35
+ /**
36
+ * Legacy persisted wire field (PR #62 feedback): rules are subagent-only,
37
+ * so this constraint is IGNORED at match time — root requests never
38
+ * match rules. Kept in the type/schema so pre-feedback `settings.yaml`
39
+ * files that carry `origin` still parse, validate, and save unchanged.
40
+ */
34
41
  origin?: 'root' | 'subagent';
35
42
  provider?: string;
36
43
  model?: string;
@@ -91,6 +98,33 @@ export interface FallbacksConfig {
91
98
  * every resolved config carries a value via the schema default.
92
99
  */
93
100
  presets?: 'bundled' | 'none';
101
+ /**
102
+ * Dispatch-time LLM role auto-match switch (plan fallbacks-role-automatch
103
+ * Task 1): when `true` (default), a subagent-origin request with no
104
+ * explicit/rules-resolved role may have the best-fit declared role picked
105
+ * by the LLM; `false` reproduces today's behavior exactly. Optional on
106
+ * purpose, mirroring `presets` — a required field would break library
107
+ * consumers that construct `FallbacksConfig` literals with the existing
108
+ * keys (additive, non-breaking). The value domain is guarded by the
109
+ * schema (`z.boolean().default(true)` in `src/schema.ts`), and the
110
+ * runtime reads it defensively as `config.roleAutoMatch ?? true` (safe
111
+ * for direct constructors that omit it).
112
+ */
113
+ roleAutoMatch?: boolean;
114
+ /**
115
+ * Extra time-slot rows (plan fallbacks-timeslots Task 1, P5): the FIRST
116
+ * matching row's chain becomes the effective root chain at request time;
117
+ * `rootChain` (the all-day chain) is always the last row.
118
+ * Optional on purpose, mirroring `presets` — additive, non-breaking for
119
+ * library consumers. Malformed rows warn once and are skipped by the
120
+ * resolver; the gateway rejects them on save (Task 3).
121
+ */
122
+ timeSlots?: SlotRowConfig[];
123
+ /**
124
+ * Config-level timezone for slot matching (default `Asia/Shanghai`,
125
+ * UTC+8). Not per-slot.
126
+ */
127
+ tz?: string;
94
128
  }
95
129
  /**
96
130
  * Spec §4 defaults — `Config({})` must equal this (no-op install).
package/dist/events.d.ts CHANGED
@@ -1,15 +1,29 @@
1
1
  /**
2
2
  * `fallbacks/switch` session event vocabulary (spec §5 table; plan Task 3).
3
3
  *
4
- * The event is appended (never rewrites history — AC-7) at every switch,
5
- * including the always-mode cap path: "无事件即无切换" (Global Constraint).
4
+ * Writing is stopped (issue #52): the plugin no longer appends this event —
5
+ * the apply()-time event-type registration was proven ineffective (module
6
+ * instance mismatch), and a session containing the event refused to load
7
+ * after a dsh restart. The event now exists only in history: old logs are
8
+ * repaired by `scripts/repair-fallbacks-switch-logs.ts`, which marks legacy
9
+ * events ignorable so affected sessions load again.
6
10
  * The module is type-only — the augmentation is erased at runtime; the
7
11
  * plugin's runtime behavior lives in `src/index.ts`.
8
12
  *
9
13
  * @module dsh-llm-fallbacks/events
10
14
  */
11
- /** Why a switch was decided (spec §5.1 `PendingSwitch.reason`). */
12
- export type FallbackSwitchReason = 'trigger-code' | 'always-cap';
15
+ /**
16
+ * Why a switch was decided (spec §5.1 `PendingSwitch.reason`).
17
+ *
18
+ * `role-inject` is the dispatch-time reason (plan fallbacks-role-automatch
19
+ * Task 4): a subagent-origin agent's first request was re-routed to its
20
+ * resolved role's chain head. It is ADDITIVE to the failure-time reasons —
21
+ * the event shape stays a superset of the pre-feature payload, and existing
22
+ * renderers show unknown reasons raw (AC-5). Unlike `trigger-code` /
23
+ * `always-cap`, a `role-inject` event is NOT a failure decision: it carries
24
+ * no pending switch / cooldown / failure bookkeeping (no `commit()`).
25
+ */
26
+ export type FallbackSwitchReason = 'trigger-code' | 'always-cap' | 'role-inject';
13
27
  /** Durable payload of one provider/model switch (spec §5 table). */
14
28
  export interface FallbacksSwitchEventData {
15
29
  turn: number;
@@ -30,7 +44,7 @@ export interface FallbacksSwitchEventData {
30
44
  }
31
45
  declare module '@deepseek-ai/dsh-session/types' {
32
46
  interface SessionEventMap {
33
- /** Durable, append-only record of one provider/model switch decided by dsh-llm-fallbacks. */
47
+ /** Historical, append-only record of one provider/model switch decided by dsh-llm-fallbacks (no longer written — issue #52 stop-write). */
34
48
  'fallbacks/switch': FallbacksSwitchEventData;
35
49
  }
36
50
  }
package/dist/index.d.ts CHANGED
@@ -13,14 +13,17 @@
13
13
  * (**always mode included**) → `next()`; otherwise resolve role + chain,
14
14
  * and when a candidate survives the filter (current / cooldown /
15
15
  * step-failed / `provider/*`-missing-id) write the pending switch +
16
- * cooldown + failure bookkeeping + append `fallbacks/switch`, then return
16
+ * cooldown + failure bookkeeping, then return
17
17
  * `{ kind: 'retry' }` (own recovery, no `next()`).
18
18
  * - `agent/request` waterfall: apply a pending switch after `await next()`
19
19
  * (provider/model override, inherited `reasoningEffort` dropped — the
20
- * `installModelSelection` `withoutInheritedEffort` pattern), then the
21
- * always-mode cap check (count `llm/retry` events for the current
22
- * turn/step/provider; ≥ `alwaysModeRetryCap` → same decision path, reason
23
- * `always-cap` — ADR-2).
20
+ * `installModelSelection` `withoutInheritedEffort` pattern); a
21
+ * root-origin `FallbacksChain/Auto` seed then overrides to the
22
+ * effective chain's first exact head (select-is-primary, plan
23
+ * fallbacks-virtual-chain Task 2); then the always-mode cap check (count
24
+ * `llm/retry` events for the current turn/step/provider; ≥
25
+ * `alwaysModeRetryCap` → same decision path, reason `always-cap` —
26
+ * ADR-2).
24
27
  * - Per-agent state (`FallbackStateStore`): `agent/disposed` removes it,
25
28
  * `agent/status` idle prunes per-step state defensively, plugin dispose
26
29
  * clears everything (spec §6 — no residual state).