dsh-llm-fallbacks 0.2.2 → 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.
package/dist/roles.d.ts CHANGED
@@ -9,8 +9,13 @@
9
9
  * `session.header.agentPreset?` carrier for that explicit stage.
10
10
  *
11
11
  * Precedence (first hit wins):
12
- * 1. the first `rules` entry whose specified origin/provider/model patterns
13
- * all match the agent;
12
+ * 1. the first `rules` entry whose specified provider/model patterns match
13
+ * the agent — rules are SUBAGENT-ONLY (PR #62 feedback): a root-origin
14
+ * agent never matches rules and resolves straight to the built-in
15
+ * `'inherit'` (→ `rootChain`). The legacy per-rule `origin` field (a
16
+ * pre-feedback config may still carry `origin: root|subagent`) is
17
+ * IGNORED at match time — every rule applies to subagents regardless
18
+ * of the stored origin value;
14
19
  * 2. the built-in `'inherit'` role (no-rule-match default, spec §7.1 / D4).
15
20
  *
16
21
  * A matched rule must target a declared role id (`roleIds`, derived from
@@ -27,8 +32,9 @@
27
32
  * (`' coder '`) with a trimmed rule reference (`'coder'`) resolves to the
28
33
  * same role instead of silently degrading to `'inherit'`.
29
34
  *
30
- * A missing agent origin is treated as `'root'`. Origin is read from
31
- * `session.header.origin` — a native `SessionHeader` field the store folds
35
+ * A missing agent origin is treated as `'root'` — and root agents never
36
+ * match rules (rules are subagent-only, PR #62 feedback). Origin is read
37
+ * from `session.header.origin` — a native `SessionHeader` field the store folds
32
38
  * from `CreateSessionOptions.meta.origin` (`packages/core/session/src/
33
39
  * index.ts:884`); subagent children set it via `childSessionMeta`
34
40
  * (`packages/subagent/subagent/src/child-agent.ts:115`), root agents carry
@@ -42,7 +48,8 @@
42
48
  */
43
49
  import { type FallbacksRoleRule } from './config.ts';
44
50
  export type { FallbacksRoleRule } from './config.ts';
45
- /** Agent origins understood by role rules (spec §3). */
51
+ /** Agent origins (spec §3) — an agent property, not a rule constraint:
52
+ * rules are subagent-only (PR #62 feedback). */
46
53
  export type Origin = 'root' | 'subagent';
47
54
  /** Loose agent shape sufficient for role resolution (spec §3 / brief). */
48
55
  export interface AgentLike {
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Time-slot rows for `fallbacks` (plan fallbacks-timeslots Task 1, pins
3
+ * P4–P6): frozen preset windows, the official-V4 all-day conformance
4
+ * guard, and the pure `resolveEffectiveChain` / `resolveSlotState`
5
+ * resolver.
6
+ *
7
+ * Pure module — no `@deepseek-ai/*` runtime imports: the client card
8
+ * imports the row types type-only (mirroring `config.ts`) and the runtime
9
+ * wires these helpers at request time (Task 2). Malformed rows NEVER
10
+ * throw: they warn ONCE per row instance (through `console.warn` — the
11
+ * resolver's 3-argument contract has no logger parameter) and are
12
+ * skipped; a legacy non-empty non-conforming `rootChain` keeps the
13
+ * v0.2.2 failure walk verbatim (P6).
14
+ *
15
+ * @module dsh-llm-fallbacks/time-slots
16
+ */
17
+ import type { FallbacksConfig } from './config.ts';
18
+ /** Official V4 models — the ONLY legal all-day selectors (length 1, XOR). */
19
+ export declare const OFFICIAL_V4_FLASH = "deepseek-official/deepseek-v4-flash";
20
+ export declare const OFFICIAL_V4_PRO = "deepseek-official/deepseek-v4-pro";
21
+ /** The four frozen preset ids (exact strings, spec lock). */
22
+ export declare const PRESET_IDS: readonly ["liang-peak", "liang-valley", "glm-peak", "glm-valley"];
23
+ export type PresetId = (typeof PRESET_IDS)[number];
24
+ /** A frozen UTC+8 window: `start ≤ t < end`, wrap-midnight when `start > end`. */
25
+ export interface SlotWindow {
26
+ /** Window start, `HH:mm` (24h). */
27
+ start: string;
28
+ /** Window end, `HH:mm` — EXCLUSIVE (a window contains `t` iff the day
29
+ * matches AND (`start ≤ end` ? `start ≤ t < end` : `start ≤ t || t < end`)). */
30
+ end: string;
31
+ /** Day mask: 0=Sunday … 6=Saturday. Omitted/empty = every day. */
32
+ days?: number[];
33
+ }
34
+ /** One extra time-slot row (P4 storage shape; `chain` is always editable). */
35
+ export interface SlotRowConfig {
36
+ kind: 'preset' | 'custom';
37
+ /** Preset id — required for `kind: 'preset'`; windows live in
38
+ * {@link PRESETS}, never stored. */
39
+ preset?: PresetId;
40
+ /** Custom-only: window start `HH:mm`. */
41
+ start?: string;
42
+ /** Custom-only: window end `HH:mm` (exclusive; may wrap midnight). */
43
+ end?: string;
44
+ /** Custom-only: day mask 0=Sunday…6=Saturday; omitted/empty = all days. */
45
+ days?: number[];
46
+ /** Custom-only display name (PR #62 feedback round, collapsed rows). */
47
+ name?: string;
48
+ /** Models for this row (editable even on preset rows). */
49
+ chain: string[];
50
+ }
51
+ /** Frozen preset definition (P4): windows are code constants, never stored. */
52
+ export interface PresetDefinition {
53
+ windows: readonly SlotWindow[];
54
+ /** `true` = matches iff the peak windows do NOT match (valley derives
55
+ * from its peak — no duplicated window enumerations that can drift). */
56
+ complement: boolean;
57
+ /** Display label for the status strip / settings card. */
58
+ label: string;
59
+ }
60
+ /**
61
+ * Frozen preset windows (UTC+8). `liang-*` presets have NO day mask (they
62
+ * apply every day, weekends included); `glm-peak` is Monday–Friday only.
63
+ * The two valleys are `complement: true` of their peak.
64
+ */
65
+ export declare const PRESETS: Record<PresetId, PresetDefinition>;
66
+ /**
67
+ * All-day conformance (P6): the all-day chain is conforming when its LAST
68
+ * entry (the tail — the card's 默认模型 panel) is exactly one official V4
69
+ * model — Flash XOR Pro. Leading entries (the card's 默认降级链 block) are
70
+ * the ordered walk before that last-resort fallback. An empty chain or a
71
+ * chain whose tail is not an official V4 model keeps slot rows inert and
72
+ * refuses the virtual-row override/delegate; the v0.2.2 failure walk over
73
+ * the raw chain stays verbatim.
74
+ */
75
+ export declare function isAllDayConforming(chain: readonly string[]): boolean;
76
+ /**
77
+ * Effective chain for a root request at `now` (P5): the FIRST extra row
78
+ * whose descriptor contains `now` (stored order) — that row's `chain`
79
+ * REPLACES the all-day chain (never concatenated); no match ⇒ `rootChain`
80
+ * (the all-day row, always last and required). Malformed rows warn once
81
+ * and are skipped; never throws.
82
+ *
83
+ * P6 (qc1 F-001): without a conforming all-day the slot rows are inert, so
84
+ * this IS the raw `rootChain` — the gate lives in {@link resolveSlotState},
85
+ * the single source every slot surface (this, the 分时切换 log, the
86
+ * `/fallbacks` strip, select-is-primary, the virtual adapter delegate) reads.
87
+ */
88
+ export declare function resolveEffectiveChain(config: FallbacksConfig, now: Date, tz: string): string[];
89
+ /**
90
+ * Slot winner + display label (P5): drives 分时切换 detection (per-root-agent
91
+ * last-winner marker, in-process only) and the card / `/fallbacks` status
92
+ * strip. `winner` is the matching row or `'all-day'`; `label` names the
93
+ * slot (frozen preset label or `custom HH:mm-HH:mm`).
94
+ *
95
+ * P6 gate (qc1 F-001): without a conforming all-day
96
+ * (`isAllDayConforming(config.rootChain)`) the winner is ALWAYS `'all-day'`
97
+ * — a legacy multi-model (or empty) chain earns no slot rows, so every
98
+ * surface fed by this resolver reports the inert state and routing stays on
99
+ * the raw `rootChain` (the v0.2.2 walk verbatim).
100
+ */
101
+ export declare function resolveSlotState(config: FallbacksConfig, now: Date, tz: string): {
102
+ winner: SlotRowConfig | 'all-day';
103
+ label: string;
104
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Virtual `FallbacksChain/Auto` LLM adapter (plan fallbacks-virtual-chain
3
+ * Task 1, technical pins P1–P3; PR #62 feedback round): a mount-only
4
+ * catalog row that makes the configured effective chain selectable as the
5
+ * session **primary** in the host model picker, without patching dsh or
6
+ * dsh-TUI (web and TUI share the adapter catalog).
7
+ *
8
+ * Wiring (P2): `installFallbacksAdapter` installs ONE conditional
9
+ * `ctx.inject(['llm'])` child — absent `llm` service (test harness) is a
10
+ * clean no-op, and fiber unload ⇒ the child's disposer unregisters the
11
+ * route. Registration is an idempotent transition-reconcile on COMMITTED
12
+ * config snapshots: register on `enabled` false→true, unregister on
13
+ * true→false, driven by the settings `onChange` hook (the returned
14
+ * reconcile thunk, wired by `apply()`) plus child activation. The row is
15
+ * visible whenever the plugin is enabled — a non-conforming all-day chain
16
+ * does NOT hide it (PR #62 feedback); conformance still gates a
17
+ * successful override/delegate (`effectiveHeadOf` below refuses a
18
+ * non-conforming all-day). The condition deliberately ignores `timeSlots`
19
+ * and conformance, so slot-row edits and chain edits never churn
20
+ * registration.
21
+ *
22
+ * Adapter behavior (P1/P3): `listModels` advertises exactly the one virtual
23
+ * row; `stream()` is a THIN single-hop delegate to the effective chain head
24
+ * through the host LLM runtime — no chain walk, no cooldown/caps/revert
25
+ * bookkeeping, no state writes (a failure inside the delegate surfaces at
26
+ * `agent/request-error`, where the existing engine walks from there).
27
+ * `resolveModel` proxies the current effective head's metadata when
28
+ * resolvable (modalities/context-window/reasoning follow the head) with a
29
+ * permissive default otherwise — never throws.
30
+ *
31
+ * @module dsh-llm-fallbacks/virtual-adapter
32
+ */
33
+ import type { Context } from '@deepseek-ai/cordis';
34
+ import type { FallbacksConfig } from './config.ts';
35
+ /** Provider route of the virtual adapter (exact string, spec lock). */
36
+ export declare const FALLBACKS_PROVIDER = "FallbacksChain";
37
+ /**
38
+ * Model id of the virtual catalog row (exact string, spec lock). "Auto" is
39
+ * the hardcoded picker id (not i18n — user decision 2026-08-18). The
40
+ * catalog `name` the host picker renders is dynamic — see
41
+ * {@link pickerDisplayName} (`Auto: <model>[<slot>]`).
42
+ */
43
+ export declare const FALLBACKS_CHAIN_MODEL = "Auto";
44
+ /**
45
+ * `LlmError` code: the effective chain is empty — the virtual route has no
46
+ * head to delegate to (P1 guard).
47
+ */
48
+ export declare const EMPTY_EFFECTIVE_CHAIN_CODE = "EMPTY_EFFECTIVE_CHAIN";
49
+ /**
50
+ * `LlmError` code: the effective chain head is not a dispatchable real pair
51
+ * (non-conforming all-day, wildcard selector, malformed selector, or a
52
+ * self-route back to `FallbacksChain/*` — the P1 recursion guard).
53
+ */
54
+ export declare const UNDISPATCHABLE_HEAD_CODE = "UNDISPATCHABLE_EFFECTIVE_HEAD";
55
+ /** `LlmError` code (defensive): the `llm` runtime disappeared mid-flight. */
56
+ export declare const LLM_UNAVAILABLE_CODE = "LLM_UNAVAILABLE";
57
+ /** One dispatchable exact head: `provider/model`. Wildcards are never seeds (P3). */
58
+ export interface EffectiveHead {
59
+ provider: string;
60
+ model: string;
61
+ }
62
+ /**
63
+ * The FIRST DISPATCHABLE exact head of a chain — the single definition of
64
+ * "effective head" (F-001) shared by the root select-is-primary override
65
+ * (`src/index.ts`) and the virtual adapter's delegate paths. Walks the SAME
66
+ * chain `resolveEffectiveChain` produces, skipping entries that can never
67
+ * be dispatched: malformed selectors (config-warning path), `provider/*`
68
+ * `provider/*` wildcards (no real pair), and self-routes back to
69
+ * `FallbacksChain/*` (the P1 recursion guard). `undefined` when the chain
70
+ * is empty or no entry is dispatchable.
71
+ */
72
+ export declare function firstDispatchableExactHead(chain: readonly string[]): EffectiveHead | undefined;
73
+ /**
74
+ * Host picker label: `Auto: <displayName>[<slot>]`.
75
+ * Display name first so the trigger stays readable and platforms stay
76
+ * distinguishable (catalog `name`, not the model id). Bare `Auto` when
77
+ * there is no dispatchable head. Slot label from {@link resolveSlotState}.
78
+ * @param modelDisplayName - catalog/resolved name; falls back to the id.
79
+ */
80
+ export declare function pickerDisplayName(config: FallbacksConfig, now?: Date, modelDisplayName?: string): string;
81
+ /**
82
+ * Install the virtual adapter registration lifecycle (P2).
83
+ *
84
+ * @param ctx - the plugin context.
85
+ * @param readConfig - live config reader (the same `source()` the runtime
86
+ * reads, so reconcile always sees COMMITTED composed snapshots).
87
+ * @returns the reconcile thunk — call it from the settings `onChange` hook
88
+ * (child activation runs one reconcile on its own).
89
+ */
90
+ export declare function installFallbacksAdapter(ctx: Context, readConfig: () => FallbacksConfig): () => void;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "dsh",
6
6
  "dsh-plugin"
7
7
  ],
8
- "version": "0.2.2",
8
+ "version": "0.3.0",
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts",