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/README.md +34 -30
- package/README.zh-CN.md +32 -28
- package/dist/chain-walk.d.ts +197 -0
- package/dist/chains.d.ts +35 -82
- 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 +1332 -2110
- package/dist/client/locales.d.ts +61 -117
- package/dist/commands.d.ts +70 -112
- package/dist/config.d.ts +152 -117
- package/dist/cooldown.d.ts +1 -15
- package/dist/events.d.ts +8 -8
- package/dist/gateway.d.ts +66 -110
- package/dist/index.d.ts +21 -64
- package/dist/index.js +900 -1313
- package/dist/role-resolution.d.ts +15 -83
- package/dist/schema.d.ts +6 -5
- package/dist/seeds.d.ts +5 -7
- package/dist/state.d.ts +15 -141
- package/dist/subagents-seam.d.ts +51 -14
- package/dist/time-slots.d.ts +21 -24
- package/dist/tui-settings.d.ts +5 -6
- package/dist/virtual-adapter.d.ts +70 -119
- package/docs/DETAIL.md +1 -0
- package/docs/DETAIL.zh.md +1 -0
- package/docs/preview.svg +47 -0
- package/docs/preview.zh.svg +47 -0
- package/locale/en.json +2 -1
- package/locale/zh.json +2 -1
- package/package.json +29 -25
- package/dist/authorized-route.d.ts +0 -68
- package/dist/automatch.d.ts +0 -98
- package/dist/client/GeneralFallbacksRow.d.ts +0 -19
- package/dist/override.d.ts +0 -39
- package/dist/roles.d.ts +0 -90
- package/dist/route-allowlist.d.ts +0 -43
- package/dist/subagent-policy.d.ts +0 -123
|
@@ -1,105 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dispatch-time role resolution (
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Precedence (first hit wins) across four ordered stages:
|
|
2
|
+
* Dispatch-time role resolution (named virtual chains, plan
|
|
3
|
+
* named-virtual-chains T3): narrowed to TWO stages —
|
|
6
4
|
* 1. explicit — `session.header.agentPreset` trimmed matches a declared role
|
|
7
|
-
* id → that role's DECLARED RAW id (via the `roleIds` trimmed-id map
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* through to the next stage;
|
|
5
|
+
* id → that role's DECLARED RAW id (via the `roleIds` trimmed-id map); the
|
|
6
|
+
* reserved `'inherit'` and undeclared presets are NEVER dispatchable
|
|
7
|
+
* explicit roles;
|
|
11
8
|
* 2. dispatch — `opts.dispatchRole`: the role the dispatch seam resolved from
|
|
12
9
|
* the Assignment/teammate prompt (`**Execute as**: <id>`, the single
|
|
13
10
|
* parser in `src/subagents-seam.ts`), canonicalized by the same trim +
|
|
14
11
|
* `roleIds` lookup. An absent, empty, `'inherit'` or no-longer-declared
|
|
15
|
-
* reference
|
|
16
|
-
* not an error;
|
|
17
|
-
* 3. rules — the existing `resolveRole` passthrough, unchanged (a declared
|
|
18
|
-
* role wins; no-rule-match / an explicit rule targeting `'inherit'`
|
|
19
|
-
* resolve to `'inherit'`);
|
|
20
|
-
* 4. auto-match — only when stage 3 resolved to `'inherit'` AND
|
|
21
|
-
* `opts.automatchEnabled`: call `opts.automatch` with the agent. The hook
|
|
22
|
-
* (Task 3 supplies the bounded LLM caller) MUST return a declared raw id
|
|
23
|
-
* or `null`; the resolver defensively validates the return against
|
|
24
|
-
* `roleIds` — an unknown id warns and resolves to `'inherit'`, never an
|
|
25
|
-
* undeclared role. When `automatch` is undefined or `automatchEnabled`
|
|
26
|
-
* is false the hook is skipped.
|
|
27
|
-
*
|
|
28
|
-
* `'inherit'` is the single "no specific role" outcome — reached by
|
|
29
|
-
* no-rule-match OR an explicit rule targeting `'inherit'` — and both are
|
|
30
|
-
* auto-match eligible when enabled. An operator who wants a class to never
|
|
31
|
-
* auto-match sets `roleAutoMatch: false` (→ `automatchEnabled: false`).
|
|
12
|
+
* reference resolves to `undefined` — no role, no error.
|
|
32
13
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
14
|
+
* The rules stage is DELETED with `roles.rules` (v2). `undefined` is the
|
|
15
|
+
* single "no specific role" outcome — the plugin does not intervene and the
|
|
16
|
+
* child inherits the delegating agent's route.
|
|
35
17
|
*
|
|
36
18
|
* @module dsh-llm-fallbacks/role-resolution
|
|
37
19
|
*/
|
|
38
|
-
|
|
39
|
-
import { type AgentLike } from './roles.ts';
|
|
40
|
-
import type { Selector } from './selectors.ts';
|
|
41
|
-
export type { FallbacksRoleRule } from './config.ts';
|
|
42
|
-
/** Options for {@link resolveRoleAtDispatch}. */
|
|
43
|
-
export interface ResolveRoleAtDispatchOptions {
|
|
20
|
+
export interface DeclaredRoleOptions {
|
|
44
21
|
/**
|
|
45
22
|
* Dispatch-declared role: the DECLARED RAW id the seam resolved from the
|
|
46
23
|
* Assignment/teammate prompt (`**Execute as**: <id>`). Validated against the
|
|
47
24
|
* live `roleIds` map; absent, `inherit`, or no-longer-declared → skipped.
|
|
48
25
|
*/
|
|
49
26
|
dispatchRole?: string;
|
|
50
|
-
/**
|
|
51
|
-
* Whether the auto-match stage may run (the `roleAutoMatch` config switch).
|
|
52
|
-
* When `false`, stage 4 never runs and the outcome is identical to the
|
|
53
|
-
* explicit/dispatch/rules stages alone.
|
|
54
|
-
*/
|
|
55
|
-
automatchEnabled: boolean;
|
|
56
|
-
/**
|
|
57
|
-
* Auto-match hook — Task 3 supplies the bounded LLM caller; the resolver
|
|
58
|
-
* itself contains no LLM. Contract: given the agent being resolved, return
|
|
59
|
-
* a declared raw role id or `null`. The resolver defensively validates the
|
|
60
|
-
* return against `roleIds` — an unknown id warns and resolves to
|
|
61
|
-
* `'inherit'`, never an undeclared role. Skipped when undefined.
|
|
62
|
-
*/
|
|
63
|
-
automatch?: (agent: AgentLike) => Promise<string | null>;
|
|
64
|
-
/** Warning sink — the decision path injects the plugin logger. */
|
|
65
|
-
warn: (message: string) => void;
|
|
66
|
-
/**
|
|
67
|
-
* The route the agent's request was actually SERVED by, when that differs
|
|
68
|
-
* from the pair recorded on the agent (the virtual `FallbacksChain/Auto`
|
|
69
|
-
* row). Rule matching accepts either, so a rule keyed on the real chain head
|
|
70
|
-
* keeps matching a child whose recorded route is the virtual pair (QC1 I-1).
|
|
71
|
-
* `undefined` for every real route.
|
|
72
|
-
*/
|
|
73
|
-
servedRoute?: {
|
|
74
|
-
provider: string;
|
|
75
|
-
model: string;
|
|
76
|
-
};
|
|
77
27
|
}
|
|
78
28
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* Task-3/4 wiring bounds the LLM call so it never throws out of the request
|
|
83
|
-
* path).
|
|
29
|
+
* The dispatch role: explicit preset → dispatch-declared. Returns
|
|
30
|
+
* `undefined` when neither stage resolves (NOT SPECIFIED — no plugin
|
|
31
|
+
* intervention).
|
|
84
32
|
*
|
|
85
33
|
* `roleIds` is the canonical trimmed-id map (`trimmed id → declared raw id`,
|
|
86
34
|
* built in `src/index.ts` apply()): every stage canonicalizes its reference
|
|
87
|
-
* by trim and returns the DECLARED RAW id
|
|
88
|
-
* `chains.ts` / `commands.ts` find the stored `roles.list[]` entry exactly
|
|
89
|
-
* (qc2 F-001 — no silent role inertness on padded YAML ids).
|
|
90
|
-
*/
|
|
91
|
-
export declare function resolveRoleAtDispatch(agent: AgentLike, rules: FallbacksRoleRule[], roleIds: ReadonlyMap<string, string>, opts: ResolveRoleAtDispatchOptions): Promise<string>;
|
|
92
|
-
/**
|
|
93
|
-
* The dispatch-time chain head (plan fallbacks-role-automatch Task 4): the
|
|
94
|
-
* FIRST candidate of a `resolveChainViews` pass whose `wildcard` flag is
|
|
95
|
-
* false — an exact `provider/model` entry. `provider/*` entries are NOT
|
|
96
|
-
* dispatch injection targets (no failing model to anchor them; a guessed
|
|
97
|
-
* injection risks a hard failure whose code may sit outside `triggerCodes`),
|
|
98
|
-
* so a wildcard-only chain yields `undefined` → no injection (today's
|
|
99
|
-
* behavior). No cooldown/failed filtering and no existence probe run here —
|
|
100
|
-
* a fresh subagent's first request has no failure-scoped state (see the Task
|
|
101
|
-
* 4 decision) and exact heads are never probed. Pairs with
|
|
102
|
-
* {@link resolveChainViews}'s parallel `wildcard` provenance (`src/chains.ts`);
|
|
103
|
-
* the caller resolves the role's concatenated chain first.
|
|
35
|
+
* by trim and returns the DECLARED RAW id.
|
|
104
36
|
*/
|
|
105
|
-
export declare function
|
|
37
|
+
export declare function resolveDeclaredRole(agentPreset: string | undefined, roleIds: ReadonlyMap<string, string>, opts: DeclaredRoleOptions): string | undefined;
|
package/dist/schema.d.ts
CHANGED
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* Object fields are optional by default in schemastery; `.default()` fills
|
|
16
16
|
* the spec defaults, `.required()` keeps mandatory fields. Unknown keys are
|
|
17
17
|
* RETAINED by the composition (verified plan Task 1 Step 1) — that is what
|
|
18
|
-
* lets `detectLegacyKeys` flag two-block
|
|
19
|
-
* `roles.
|
|
20
|
-
*
|
|
18
|
+
* lets `detectLegacyKeys` flag v1 two-block leftovers (`rootChain` /
|
|
19
|
+
* `roles.rules` / …) on the composed object at startup (warn-only
|
|
20
|
+
* migration; the gateway strips them via LEGACY_KEYS, see
|
|
21
|
+
* `src/gateway.ts`).
|
|
21
22
|
*
|
|
22
23
|
* Current host contract: the whole schema is marked `volatile` (the host
|
|
23
24
|
* retired `installSection` — the Loader entry Config IS the settings
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
* @module dsh-llm-fallbacks/schema
|
|
35
36
|
*/
|
|
36
37
|
import z from '@deepseek-ai/schemastery';
|
|
37
|
-
import type {
|
|
38
|
+
import type { FallbacksConfigSource } from './config.ts';
|
|
38
39
|
declare module '@deepseek-ai/schemastery' {
|
|
39
40
|
namespace Schemastery {
|
|
40
41
|
interface Meta {
|
|
@@ -43,4 +44,4 @@ declare module '@deepseek-ai/schemastery' {
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
|
-
export declare const Config: z<
|
|
47
|
+
export declare const Config: z<FallbacksConfigSource>;
|
package/dist/seeds.d.ts
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
*
|
|
43
43
|
* @module dsh-llm-fallbacks/seeds
|
|
44
44
|
*/
|
|
45
|
-
import { type
|
|
45
|
+
import { type FallbacksConfig, type FallbacksConfigLogger, type FallbacksRoles } from './config.ts';
|
|
46
46
|
/** A seed declaration from a companion plugin (spec §9.1). */
|
|
47
47
|
export interface SeedDeclaration {
|
|
48
48
|
id: string;
|
|
@@ -112,10 +112,8 @@ export interface EffectiveRole {
|
|
|
112
112
|
id: string;
|
|
113
113
|
/** Effective row persona. */
|
|
114
114
|
persona: string;
|
|
115
|
-
/** Passthrough — never touched by seeds (R4). */
|
|
116
|
-
chain?: string
|
|
117
|
-
/** Passthrough — never touched by seeds (R4). */
|
|
118
|
-
fallback?: FallbackStrategy;
|
|
115
|
+
/** Passthrough — never touched by seeds (R4): the named-chain POINTER. */
|
|
116
|
+
chain?: string;
|
|
119
117
|
/** Id is in the live declaration set (trimmed row-id match). */
|
|
120
118
|
seeded: boolean;
|
|
121
119
|
/** `seeded` && row persona !== current seed default. */
|
|
@@ -154,7 +152,7 @@ export interface SeedsWireStatus {
|
|
|
154
152
|
export interface SeedsIo {
|
|
155
153
|
/** Fresh composed config read (the same source the gateway reads). */
|
|
156
154
|
read(): FallbacksConfig;
|
|
157
|
-
/** Persist
|
|
155
|
+
/** Persist the `{ list }` to the settings user layer. */
|
|
158
156
|
writeRoles(roles: FallbacksRoles): Promise<void>;
|
|
159
157
|
}
|
|
160
158
|
/**
|
|
@@ -192,7 +190,7 @@ export declare class FallbacksSeedManager {
|
|
|
192
190
|
* RESOLVED effective default before/after the candidate slice commit
|
|
193
191
|
* (at-default tracking follows the resolution winner — a producer that
|
|
194
192
|
* is not the winner for an id it declares never rewrites the row),
|
|
195
|
-
* writes only when the computed `{ list
|
|
193
|
+
* writes only when the computed `{ list }` differs from the
|
|
196
194
|
* current composed roles (idempotent, AC-1), and commits the slice only
|
|
197
195
|
* after a successful write (compute → write → commit; retry-safe). An
|
|
198
196
|
* empty batch drops the producer's slice.
|
package/dist/state.d.ts
CHANGED
|
@@ -1,137 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-agent fallback state
|
|
2
|
+
* Per-agent fallback state (direction C, plan dsh-022-rc1-upgrade).
|
|
3
3
|
*
|
|
4
4
|
* One `AgentFallbackState` per `agent.id`, held in a `FallbackStateStore`
|
|
5
|
-
* created at plugin apply, removed on `agent/disposed`,
|
|
6
|
-
*
|
|
7
|
-
* 无残留状态).
|
|
5
|
+
* created at plugin apply, removed on `agent/disposed`, and cleared wholesale
|
|
6
|
+
* on plugin dispose (spec §6: 无残留状态).
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (turn, step), records `appliedTurnStep`, and clears `pendingSwitch`
|
|
18
|
-
* (anti-replay guard).
|
|
19
|
-
* 3. **Discard**: chain exhaustion / safety valve → the caller delegates
|
|
20
|
-
* (`next()`) and the pending state dies with the agent.
|
|
21
|
-
*
|
|
22
|
-
* **stepFailures** resets whenever (turn, step) advances (`syncStep`);
|
|
23
|
-
* **cooldown** (`CooldownStore`) is a `provider/model → expiry epoch ms` map
|
|
24
|
-
* with lazy expiry (`revertPolicy: 'never'` = `Infinity` TTL). The time-boxed
|
|
25
|
-
* cooldown deliberately survives idle cleanup so `cooldown-expiry` revert
|
|
26
|
-
* (US-4 / T4 integration) works across turns.
|
|
8
|
+
* The store is the CROSS-CALL memory the chain walk reads: **cooldown**
|
|
9
|
+
* (`CooldownStore` — a `provider/model → expiry epoch ms` map with lazy expiry;
|
|
10
|
+
* `revertPolicy: 'never'` = `Infinity` TTL) plus the **recovery** circuit
|
|
11
|
+
* (`RecoveryStore`). The time-boxed cooldown deliberately survives idle
|
|
12
|
+
* cleanup so `cooldown-expiry` revert (US-4 / T4 integration) works across
|
|
13
|
+
* turns. Per-request bookkeeping (the failed-candidate set, the switch cap) is
|
|
14
|
+
* walk-local: a walk lives inside one `stream()` call, so nothing durable is
|
|
15
|
+
* kept for it here.
|
|
27
16
|
*
|
|
28
17
|
* @module dsh-llm-fallbacks/state
|
|
29
18
|
*/
|
|
30
|
-
import
|
|
31
|
-
import { CooldownStore, StepFailureSet } from './cooldown.ts';
|
|
19
|
+
import { CooldownStore } from './cooldown.ts';
|
|
32
20
|
import { RecoveryStore } from './recovery.ts';
|
|
33
21
|
import type { RecoveryPolicy } from './config.ts';
|
|
34
|
-
/**
|
|
35
|
-
* What a switch's failure was actually about.
|
|
36
|
-
*
|
|
37
|
-
* `'route'` (the default) is every failure the plugin has ever walked: the
|
|
38
|
-
* provider/model itself is unhealthy (auth, quota, rate limit, ...), so the
|
|
39
|
-
* from-route is cooled down and counted as a route failure.
|
|
40
|
-
* `'request'` is a failure of the REQUEST on a healthy route — a
|
|
41
|
-
* `CONTEXT_WINDOW_EXCEEDED` rejection is the request being too big, not the
|
|
42
|
-
* model being down. `commit()` keeps the step-scoped bookkeeping for those
|
|
43
|
-
* but writes no suppression and no recovery failure (see `src/index.ts`
|
|
44
|
-
* `REQUEST_SCOPED_CODES`).
|
|
45
|
-
*/
|
|
46
|
-
export type SwitchScope = 'route' | 'request';
|
|
47
|
-
/** Decision awaiting application at the next `agent/request` boundary (spec §5.1). */
|
|
48
|
-
export interface PendingSwitch {
|
|
49
|
-
from: {
|
|
50
|
-
provider: string;
|
|
51
|
-
model: string;
|
|
52
|
-
};
|
|
53
|
-
to: {
|
|
54
|
-
provider: string;
|
|
55
|
-
model: string;
|
|
56
|
-
};
|
|
57
|
-
role: string;
|
|
58
|
-
reason: FallbackSwitchReason;
|
|
59
|
-
/**
|
|
60
|
-
* What the failure was about — absent means `'route'`, the semantics every
|
|
61
|
-
* switch had before request-scoped codes existed (same optional-with-a-
|
|
62
|
-
* documented-default idiom as `FallbacksRole.fallback`). Only the
|
|
63
|
-
* `agent/request-error` listener ever sets `'request'`.
|
|
64
|
-
*/
|
|
65
|
-
scope?: SwitchScope;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* One plugin-originated subagent switch the host allowlist blocked (plan
|
|
69
|
-
* dsh-012-subagent-routing T3; spec D1). The intersection of the resolved
|
|
70
|
-
* candidates with the effective allowlist was empty while the unconstrained walk
|
|
71
|
-
* would have switched — the attempt is recorded in-memory only (issue #52:
|
|
72
|
-
* no durable session write) for the Subagents card warning (spec D4/T5).
|
|
73
|
-
*/
|
|
74
|
-
export interface BlockedSwitchAttempt {
|
|
75
|
-
/** Epoch ms when the switch was blocked. */
|
|
76
|
-
at: number;
|
|
77
|
-
/** The route the unconstrained walk would have switched to (exact provider+model). */
|
|
78
|
-
route: {
|
|
79
|
-
provider: string;
|
|
80
|
-
model: string;
|
|
81
|
-
};
|
|
82
|
-
/** The failure reason that triggered the would-be switch. */
|
|
83
|
-
reason: FallbackSwitchReason;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* How the effective subagent chain head was chosen (plan dsh-012-subagent-routing
|
|
87
|
-
* T5 / T2 M3). Lives on a per-agent map (mirrors {@link BlockedSwitchAttempt}),
|
|
88
|
-
* not on {@link AgentFallbackState} — recording a head is not a switch intent
|
|
89
|
-
* (F-004: the fallback store grows only inside `commit()`).
|
|
90
|
-
*/
|
|
91
|
-
export type ChainHeadSource = 'authorized' | 'injected';
|
|
92
|
-
/**
|
|
93
|
-
* The effective chain head for one subagent, plus the source label the
|
|
94
|
-
* Subagents card renders (spec D4). In-memory only (issue #52).
|
|
95
|
-
*/
|
|
96
|
-
export interface EffectiveChainHead {
|
|
97
|
-
/** Epoch ms when this head was recorded (recency for the card snapshot). */
|
|
98
|
-
at: number;
|
|
99
|
-
/** Exact provider+model of the effective chain head. */
|
|
100
|
-
route: {
|
|
101
|
-
provider: string;
|
|
102
|
-
model: string;
|
|
103
|
-
};
|
|
104
|
-
/** `authorized` = host spawn preserved; `injected` = plugin role-inject. */
|
|
105
|
-
source: ChainHeadSource;
|
|
106
|
-
}
|
|
107
|
-
/** The current (turn, step)'s failed-model set and switch budget (spec §5.1). */
|
|
108
|
-
export interface StepFailures {
|
|
109
|
-
turn: number;
|
|
110
|
-
step: number;
|
|
111
|
-
/** `${provider}/${model}` keys failed in this step (double suppression with cooldown). */
|
|
112
|
-
failed: StepFailureSet;
|
|
113
|
-
/** Switches committed in this step; the caller judges it against `maxSwitchesPerStep`. */
|
|
114
|
-
switchCount: number;
|
|
115
|
-
}
|
|
116
|
-
/** One agent's whole fallback runtime state (spec §5.1). */
|
|
22
|
+
/** One agent's fallback runtime state. */
|
|
117
23
|
export interface AgentFallbackState {
|
|
118
|
-
/** Decision produced but not yet applied at an `agent/request` boundary. */
|
|
119
|
-
pendingSwitch?: PendingSwitch;
|
|
120
|
-
/** The (turn, step) the last pending switch was applied to — anti-replay. */
|
|
121
|
-
appliedTurnStep?: {
|
|
122
|
-
turn: number;
|
|
123
|
-
step: number;
|
|
124
|
-
};
|
|
125
|
-
/** Current (turn, step)'s failure bookkeeping. */
|
|
126
|
-
stepFailures: StepFailures;
|
|
127
24
|
/** `provider/model → expiry epoch ms`; lazily expired on read. */
|
|
128
25
|
cooldown: CooldownStore;
|
|
129
26
|
/** Half-open recovery bookkeeping (plan fallbacks-half-open-recovery P2). */
|
|
130
27
|
recovery: RecoveryStore;
|
|
131
28
|
}
|
|
132
29
|
/**
|
|
133
|
-
* The `Map<agent.id, AgentFallbackState>` store plus the state
|
|
134
|
-
*
|
|
30
|
+
* The `Map<agent.id, AgentFallbackState>` store plus the state operations the
|
|
31
|
+
* walk and the diagnostics surfaces read.
|
|
135
32
|
*/
|
|
136
33
|
export declare class FallbackStateStore {
|
|
137
34
|
private readonly states;
|
|
@@ -146,29 +43,6 @@ export declare class FallbackStateStore {
|
|
|
146
43
|
delete(agentId: string): void;
|
|
147
44
|
/** Remove every agent's state (plugin dispose — no residual state). */
|
|
148
45
|
clear(): void;
|
|
149
|
-
/** Reset step-scoped bookkeeping when (turn, step) advances (spec §5.1). */
|
|
150
|
-
syncStep(state: AgentFallbackState, turn: number, step: number): void;
|
|
151
|
-
/** Record the current model as failed in this step (spec §5.1). */
|
|
152
|
-
recordFailure(state: AgentFallbackState, key: string): void;
|
|
153
|
-
/** Bump the step's switch budget (spec §5.1). */
|
|
154
|
-
recordSwitch(state: AgentFallbackState): void;
|
|
155
|
-
/**
|
|
156
|
-
* Produce a pending switch. A fresh decision always supersedes a previous
|
|
157
|
-
* one and must be applicable at the current (turn, step), so the applied
|
|
158
|
-
* marker is cleared here (see module doc — chains).
|
|
159
|
-
*/
|
|
160
|
-
writePending(state: AgentFallbackState, pending: PendingSwitch): void;
|
|
161
|
-
/**
|
|
162
|
-
* Apply the pending switch at (turn, step) when one exists and has not
|
|
163
|
-
* already been applied there; records the applied marker and clears the
|
|
164
|
-
* pending switch (spec §5.1 lifecycle step 2).
|
|
165
|
-
*/
|
|
166
|
-
applyPending(state: AgentFallbackState, turn: number, step: number): PendingSwitch | undefined;
|
|
167
|
-
/**
|
|
168
|
-
* Defensive `agent/status` idle cleanup: drop per-step state. The time-boxed
|
|
169
|
-
* cooldown survives so `cooldown-expiry` revert works across turns (US-4).
|
|
170
|
-
*/
|
|
171
|
-
clearStepState(state: AgentFallbackState): void;
|
|
172
46
|
/** Suppress `key` until `untilEpochMs` (`Infinity` for `revertPolicy: 'never'`). */
|
|
173
47
|
suppress(state: AgentFallbackState, key: string, untilEpochMs: number): void;
|
|
174
48
|
/**
|
package/dist/subagents-seam.d.ts
CHANGED
|
@@ -58,8 +58,28 @@
|
|
|
58
58
|
* (the runtime mints its own child id and `agent/created` has already run), in
|
|
59
59
|
* which case this module resolves the same question against the live roles
|
|
60
60
|
* source and carries the verdict onto the record keyed off the RESOLVED id,
|
|
61
|
-
* before the child's first pre-step. This module
|
|
62
|
-
*
|
|
61
|
+
* before the child's first pre-step. This module reads no provider persona
|
|
62
|
+
* capability and injects no agent preset.
|
|
63
|
+
*
|
|
64
|
+
* Role chain stamp (named virtual chains §3, `injectRoleChain`): the SAME
|
|
65
|
+
* resolution point is where a dispatch child is pointed at a named chain —
|
|
66
|
+
* `request.agentOptions` is stamped with `{ provider: 'FallbacksChain',
|
|
67
|
+
* model: <role.chain> }` (rc.1 merges it over the parent's route when
|
|
68
|
+
* `manager.startLocal` creates the child) when all FOUR conditions hold: a
|
|
69
|
+
* declared role resolved, its `chain` is non-empty AND declared, the request
|
|
70
|
+
* carries no explicit `agentOptions` (explicit = authorized, untouched), and
|
|
71
|
+
* the backend is LOCAL. No host-policy consultation: the mapping is the
|
|
72
|
+
* user's STATIC config, never a model-authored route — no allowlist, no
|
|
73
|
+
* `unprovable` branch, no `virtualRouteInjected` flag. The seam does NOT
|
|
74
|
+
* check conformance: a declared-but-non-conforming chain is still stamped and
|
|
75
|
+
* the walk's own tail gate refuses it — one gate, in the walk. Injection is
|
|
76
|
+
* creation-time only BY CONSTRUCTION: rc.1's `startActivation` rejects any
|
|
77
|
+
* child id that already exists (`DUPLICATE_CHILD`): a caller-reserved
|
|
78
|
+
* `childId` the seam already recorded is a RE-ACTIVATION of that child (its
|
|
79
|
+
* durable selection is already fixed), so it stamps nothing — that activation
|
|
80
|
+
* writes the role record only (I-000081). A non-stamped dispatch forwards the
|
|
81
|
+
* caller's spec untouched (external backends keep pure inheritance; not
|
|
82
|
+
* specified ⇒ the child inherits the delegating agent's route).
|
|
63
83
|
*
|
|
64
84
|
* In-session notice row (plan Task 3): the SAME install point registers the
|
|
65
85
|
* `agent/pre-step` emitter (`./role-notice.ts`) over the record map it owns, so
|
|
@@ -89,7 +109,7 @@
|
|
|
89
109
|
* @module dsh-llm-fallbacks/subagents-seam
|
|
90
110
|
*/
|
|
91
111
|
import type { Context } from '@deepseek-ai/cordis';
|
|
92
|
-
import { type FallbacksRole } from './config.ts';
|
|
112
|
+
import { type FallbacksConfig, type FallbacksRole } from './config.ts';
|
|
93
113
|
/**
|
|
94
114
|
* The cordis service-read waterfall event the seam registers on. Referenced
|
|
95
115
|
* ONCE (registration) so a future cordis rename is a one-line change here.
|
|
@@ -169,14 +189,25 @@ export interface SubagentStartRequestView {
|
|
|
169
189
|
readonly id?: string;
|
|
170
190
|
};
|
|
171
191
|
};
|
|
192
|
+
/**
|
|
193
|
+
* rc.1 `SubagentStartRequest.agentOptions` (design §1.1) — the route the
|
|
194
|
+
* caller (or the model, on the tool path) asked the child to run on. Read
|
|
195
|
+
* only: an explicit pair here is the AUTHORIZED route and is never touched.
|
|
196
|
+
*/
|
|
197
|
+
readonly agentOptions?: {
|
|
198
|
+
readonly provider?: string;
|
|
199
|
+
readonly model?: string;
|
|
200
|
+
};
|
|
172
201
|
}
|
|
173
202
|
/**
|
|
174
203
|
* Structural view of the activation spec (rc.1 `SubagentActivationSpec`,
|
|
175
|
-
* consumed fields only): `childId` is the caller-
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
204
|
+
* consumed fields only): `childId` is the caller-RESERVED identity of a NEW
|
|
205
|
+
* local child (rc.1 `startLocal` rejects an id that already exists with
|
|
206
|
+
* `DUPLICATE_CHILD`, so `startActivation` never attaches to a child that
|
|
207
|
+
* outlived an earlier activation) — also the record fallback when the request
|
|
208
|
+
* carries no Assignment header — and `label` is the catalog join's other half
|
|
209
|
+
* (the cataloged child membership carries this exact string). `signal`,
|
|
210
|
+
* `delivery` and every future field travel with the caller's spec object.
|
|
180
211
|
*/
|
|
181
212
|
export interface SubagentActivationSpecView {
|
|
182
213
|
readonly provider: string;
|
|
@@ -199,6 +230,12 @@ export interface SubagentSeamOptions {
|
|
|
199
230
|
* wired) ⇒ no verdict, exactly like a listener that never saw the child.
|
|
200
231
|
*/
|
|
201
232
|
roles?: () => readonly FallbacksRole[];
|
|
233
|
+
/**
|
|
234
|
+
* Live config reader — the SAME `source()` the runtime and the adapter walk
|
|
235
|
+
* read. Absent ⇒ the seam never stamps a role chain (a partial wiring keeps
|
|
236
|
+
* the native dispatch unchanged).
|
|
237
|
+
*/
|
|
238
|
+
source?: () => FallbacksConfig;
|
|
202
239
|
/** Contained debug sink (at most one line per contained degrade). */
|
|
203
240
|
debug?: (message: string) => void;
|
|
204
241
|
}
|
|
@@ -220,8 +257,8 @@ export interface SubagentSeam {
|
|
|
220
257
|
* deliberately NOT cleared on `agent/disposed` — a continuable child's durable
|
|
221
258
|
* session survives the activation, so clearing the marker would let a
|
|
222
259
|
* re-activation resume append a second notice row to the same log. It is
|
|
223
|
-
* bounded (`NOTICE_EMITTED_LIMIT`) and cleared only
|
|
224
|
-
*
|
|
260
|
+
* bounded (`NOTICE_EMITTED_LIMIT`) and cleared only when the LAST applied
|
|
261
|
+
* fiber of its root disposes (a surviving fiber's children still need it).
|
|
225
262
|
*/
|
|
226
263
|
readonly noticeEmitted: Set<string>;
|
|
227
264
|
/**
|
|
@@ -233,7 +270,7 @@ export interface SubagentSeam {
|
|
|
233
270
|
* message.
|
|
234
271
|
*/
|
|
235
272
|
readonly ownsWrapper: boolean;
|
|
236
|
-
/**
|
|
273
|
+
/** Withdraw this fiber's registrations (its own lifecycle effect does this on teardown). */
|
|
237
274
|
dispose(): void;
|
|
238
275
|
}
|
|
239
276
|
/**
|
|
@@ -272,15 +309,15 @@ export declare function resolveDeclaredRoleFromAssignment(prompt: string, roleId
|
|
|
272
309
|
* DECLARED RAW id the seam already resolved, so this is an exact id match, not
|
|
273
310
|
* a second canonicalization pass.
|
|
274
311
|
*
|
|
275
|
-
* Only `id` and `persona` are read: no `chain
|
|
276
|
-
*
|
|
312
|
+
* Only `id` and `persona` are read: no `chain` pointer, no routing state — a
|
|
313
|
+
* role that specifies a chain names its persona exactly like one that does not.
|
|
277
314
|
*
|
|
278
315
|
* @param roles - the live declared roles (`roles.list`).
|
|
279
316
|
* @param roleId - the DECLARED RAW role id resolved at the seam.
|
|
280
317
|
*/
|
|
281
318
|
export declare function personaForRole(roles: readonly FallbacksRole[], roleId: string): string | undefined;
|
|
282
319
|
/**
|
|
283
|
-
* @internal Test seam
|
|
320
|
+
* @internal Test seam: the seam installed on
|
|
284
321
|
* `ctx`, if any. Not part of the plugin's public surface — lets tests read and
|
|
285
322
|
* seed the per-child record map without reaching into the installation
|
|
286
323
|
* closure. `undefined` when no seam is installed.
|
package/dist/time-slots.d.ts
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* wires these helpers at request time (Task 2). Malformed rows NEVER
|
|
10
10
|
* throw: they warn ONCE per row instance (through `console.warn` — the
|
|
11
11
|
* resolver's 3-argument contract has no logger parameter) and are
|
|
12
|
-
* skipped; a
|
|
13
|
-
*
|
|
12
|
+
* skipped; a non-empty non-conforming `entries` tail keeps the failure walk
|
|
13
|
+
* verbatim (the chain's delegate stays inert).
|
|
14
14
|
*
|
|
15
15
|
* @module dsh-llm-fallbacks/time-slots
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { NamedChain } from './config.ts';
|
|
18
18
|
/** Official all-day selectors — the ONLY legal tails (length 1, XOR):
|
|
19
19
|
* Flash (the current official default) and Pro, both selectable. The Pro
|
|
20
20
|
* tail id is the catalog id verbatim: the official default catalog
|
|
@@ -87,32 +87,29 @@ export declare const PRESETS: Record<PresetId, PresetDefinition>;
|
|
|
87
87
|
*/
|
|
88
88
|
export declare function isAllDayConforming(chain: readonly string[]): boolean;
|
|
89
89
|
/**
|
|
90
|
-
* Effective chain for a
|
|
91
|
-
*
|
|
92
|
-
* REPLACES
|
|
93
|
-
*
|
|
94
|
-
*
|
|
90
|
+
* Effective chain for a request on ONE named chain at `now`: the FIRST
|
|
91
|
+
* matching row of {@link NamedChain.timeSlots} (stored order) — that row's
|
|
92
|
+
* `chain` REPLACES {@link NamedChain.entries} (never concatenated); no match
|
|
93
|
+
* ⇒ the chain's own `entries`. Malformed rows warn once and are skipped;
|
|
94
|
+
* never throws.
|
|
95
95
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* `/fallbacks` strip, the virtual adapter delegate, its served-route anchor)
|
|
100
|
-
* reads.
|
|
96
|
+
* Without a conforming entries tail the slot rows are inert, so this IS the
|
|
97
|
+
* raw `entries` — the gate lives in {@link resolveSlotState}, the single
|
|
98
|
+
* source every slot surface reads.
|
|
101
99
|
*/
|
|
102
|
-
export declare function resolveEffectiveChain(
|
|
100
|
+
export declare function resolveEffectiveChain(chain: NamedChain, now: Date, tz: string): string[];
|
|
103
101
|
/**
|
|
104
|
-
* Slot winner + display label
|
|
105
|
-
* last-winner marker, in-process only) and the card /
|
|
106
|
-
* strip. `winner` is the matching row or `'all-day'`;
|
|
107
|
-
* slot (frozen preset label or `custom HH:mm-HH:mm`).
|
|
102
|
+
* Slot winner + display label for one named chain: drives 分时切换 detection
|
|
103
|
+
* (per-(agent, chain) last-winner marker, in-process only) and the card /
|
|
104
|
+
* `/fallbacks` status strip. `winner` is the matching row or `'all-day'`;
|
|
105
|
+
* `label` names the slot (frozen preset label or `custom HH:mm-HH:mm`).
|
|
108
106
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* the raw `rootChain` (the legacy walk verbatim).
|
|
107
|
+
* Gate: without a conforming entries tail (`isAllDayConforming(chain.entries)`)
|
|
108
|
+
* the winner is ALWAYS `'all-day'` — a multi-model (or empty) chain earns no
|
|
109
|
+
* slot rows, so every surface fed by this resolver reports the inert state and
|
|
110
|
+
* routing stays on the raw `entries`.
|
|
114
111
|
*/
|
|
115
|
-
export declare function resolveSlotState(
|
|
112
|
+
export declare function resolveSlotState(chain: NamedChain, now: Date, tz: string): {
|
|
116
113
|
winner: SlotRowConfig | 'all-day';
|
|
117
114
|
label: string;
|
|
118
115
|
};
|
package/dist/tui-settings.d.ts
CHANGED
|
@@ -114,12 +114,11 @@ export declare const FALLBACKS_TUI_SECTION_NS = "fallbacks";
|
|
|
114
114
|
*/
|
|
115
115
|
export declare const JSON_FIELD_MAX_DRAFT_BYTES: number;
|
|
116
116
|
/**
|
|
117
|
-
* The `fallbacks` /settings section
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* `
|
|
121
|
-
*
|
|
122
|
-
* content presence). Scalar
|
|
117
|
+
* The `fallbacks` /settings section covering the web-card capabilities
|
|
118
|
+
* (named virtual chains v2: ONE `Chains (JSON)` field over `chains` on the
|
|
119
|
+
* same bounded-parse machinery; `Roles (JSON)` keeps the chain POINTER; the
|
|
120
|
+
* v1 `rootChain` / top-level `timeSlots` / `roles.rules` fields are REMOVED
|
|
121
|
+
* with their mechanisms). Scalar
|
|
123
122
|
* capabilities use native kinds (boolean/number/select); complex structures
|
|
124
123
|
* use `text` fields with custom `format`/`parse` that mirror the gateway's
|
|
125
124
|
* validation. Built fresh per call — the host deep-freezes whatever it
|