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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "dsh",
6
6
  "dsh-plugin"
7
7
  ],
8
- "version": "0.6.13",
8
+ "version": "0.8.0",
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts",
@@ -28,7 +28,11 @@
28
28
  "bundle",
29
29
  "locale",
30
30
  "icon.svg",
31
- "scripts"
31
+ "scripts",
32
+ "docs/DETAIL.md",
33
+ "docs/DETAIL.zh.md",
34
+ "docs/preview.svg",
35
+ "docs/preview.zh.svg"
32
36
  ],
33
37
  "dsh": {
34
38
  "bundle": {
@@ -65,29 +69,29 @@
65
69
  },
66
70
  "peerDependencies": {
67
71
  "@deepseek-ai/cordis": "^4.0.5",
68
- "@deepseek-ai/dsh-agent": "^0.2.1-rc.4",
69
- "@deepseek-ai/dsh-api-gateway": "^0.2.1-rc.4",
70
- "@deepseek-ai/dsh-api-remotes": "^0.2.1-rc.4",
71
- "@deepseek-ai/dsh-api-session-controller": "^0.2.1-rc.4",
72
- "@deepseek-ai/dsh-client-connection": "^0.2.1-rc.4",
73
- "@deepseek-ai/dsh-client-locale": "^0.2.1-rc.4",
74
- "@deepseek-ai/dsh-client-store": "^0.2.1-rc.4",
75
- "@deepseek-ai/dsh-client-ui-chat": "^0.2.1-rc.4",
76
- "@deepseek-ai/dsh-client-ui-conversation": "^0.2.1-rc.4",
77
- "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.1-rc.4",
78
- "@deepseek-ai/dsh-client-ui-renderer": "^0.2.1-rc.4",
79
- "@deepseek-ai/dsh-client-ui-settings": "^0.2.1-rc.4",
80
- "@deepseek-ai/dsh-client-ui-slots": "^0.2.1-rc.4",
81
- "@deepseek-ai/dsh-commands": "^0.2.1-rc.4",
82
- "@deepseek-ai/dsh-llm": "^0.2.1-rc.4",
83
- "@deepseek-ai/dsh-llm-retry": "^0.2.1-rc.4",
84
- "@deepseek-ai/dsh-session": "^0.2.1-rc.4",
85
- "@deepseek-ai/dsh-session-projection": "^0.2.1-rc.4",
86
- "@deepseek-ai/dsh-settings": "^0.2.1-rc.4",
87
- "@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.4",
88
- "@deepseek-ai/dsh-tool-subagent": "^0.2.1-rc.4",
89
- "@deepseek-ai/dsh-typert-protocol": "^0.2.1-rc.4",
90
- "@deepseek-ai/dsh-typert-registry": "^0.2.1-rc.4",
72
+ "@deepseek-ai/dsh-agent": "^0.2.2-rc.1",
73
+ "@deepseek-ai/dsh-api-gateway": "^0.2.2-rc.1",
74
+ "@deepseek-ai/dsh-api-remotes": "^0.2.2-rc.1",
75
+ "@deepseek-ai/dsh-api-session-controller": "^0.2.2-rc.1",
76
+ "@deepseek-ai/dsh-client-connection": "^0.2.2-rc.1",
77
+ "@deepseek-ai/dsh-client-locale": "^0.2.2-rc.1",
78
+ "@deepseek-ai/dsh-client-store": "^0.2.2-rc.1",
79
+ "@deepseek-ai/dsh-client-ui-chat": "^0.2.2-rc.1",
80
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.2.2-rc.1",
81
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.2.2-rc.1",
82
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.2.2-rc.1",
83
+ "@deepseek-ai/dsh-client-ui-settings": "^0.2.2-rc.1",
84
+ "@deepseek-ai/dsh-client-ui-slots": "^0.2.2-rc.1",
85
+ "@deepseek-ai/dsh-commands": "^0.2.2-rc.1",
86
+ "@deepseek-ai/dsh-llm": "^0.2.2-rc.1",
87
+ "@deepseek-ai/dsh-llm-retry": "^0.2.2-rc.1",
88
+ "@deepseek-ai/dsh-session": "^0.2.2-rc.1",
89
+ "@deepseek-ai/dsh-session-projection": "^0.2.2-rc.1",
90
+ "@deepseek-ai/dsh-settings": "^0.2.2-rc.1",
91
+ "@deepseek-ai/dsh-system-prompt": "^0.2.2-rc.1",
92
+ "@deepseek-ai/dsh-tool-subagent": "^0.2.2-rc.1",
93
+ "@deepseek-ai/dsh-typert-protocol": "^0.2.2-rc.1",
94
+ "@deepseek-ai/dsh-typert-registry": "^0.2.2-rc.1",
91
95
  "@deepseek-ai/schemastery": "^3.18.5",
92
96
  "react": "^18.2.0"
93
97
  },
@@ -1,68 +0,0 @@
1
- /**
2
- * Authorized-route detection from durable child state (plan
3
- * dsh-012-subagent-routing T2; spec D2).
4
- *
5
- * Pure, host-free module: a plain-data session view in, one route out. The
6
- * wiring captures the view at a subagent's first request from the detection
7
- * sources the plan names (shapes verified against upstream
8
- * `packages/subagent/subagent/src/child-agent.ts:68-85` and
9
- * `packages/api/session-controller/src/types.ts:284`):
10
- *
11
- * - the session's `model/selection` log entries (plain descriptors; payload
12
- * is the `ModelSelection` shape — provider, model, optional
13
- * reasoningEffort), an explicit host-recorded selection for this session;
14
- * - the durable `request/header` config fold (`requestHeader()?.config`);
15
- * - the resolved creation options (`agent.options`) as the pre-first-request
16
- * fallback — upstream precedence: the durable header wins, options
17
- * fallback (`parentAgentOptionsForDelegation`);
18
- * - the pure-inheritance baseline: the delegating parent's current route,
19
- * resolved by the wiring the same way (`requestHeader()?.config` else
20
- * creation options).
21
- *
22
- * A source yields a route only when BOTH provider and model are present
23
- * non-empty strings — an effort alone is not a route (spec Terms). A
24
- * `model/selection` entry is definitionally an explicit selection, so it is
25
- * authorized as-is. A route read from the header or the creation options is
26
- * explicit only when it is NOT the pure-inheritance continuation: when the
27
- * baseline is provable (complete) and equals the candidate exactly, the
28
- * candidate is the parent's inherited route, not a selection — pure
29
- * inheritance is NOT an authorized route (spec Terms; upstream
30
- * `resolveChildAgentOptions` materializes the parent route into child
31
- * options even without an explicit selection, so equality with the baseline
32
- * is the only child-side inheritance signal). With the baseline unprovable
33
- * the candidate stands: D2 keeps authorized chain heads unoverwritten, and
34
- * the allowlist-constrained inject below this decision holds D1 either way.
35
- *
36
- * Never throws; malformed source shapes yield `undefined`, not routes.
37
- *
38
- * @module dsh-llm-fallbacks/authorized-route
39
- */
40
- import type { PolicyRoute } from './subagent-policy.ts';
41
- /** One plain route-bearing source shape (header config, options, selection payload, baseline). */
42
- export type RouteSource = {
43
- readonly provider?: unknown;
44
- readonly model?: unknown;
45
- readonly reasoningEffort?: unknown;
46
- };
47
- /** Structural session view consumed by {@link detectAuthorizedRoute} — plain data only, no live host objects. */
48
- export interface AuthorizedRouteSession {
49
- /** Durable request-header config fold captured from the child session (absent before the first logged header). */
50
- readonly requestHeader?: RouteSource | undefined;
51
- /** Plain event descriptors (`type` + optional `data`) captured from the child session's log. */
52
- readonly events?: readonly {
53
- readonly type: string;
54
- readonly data?: unknown;
55
- }[] | undefined;
56
- /** Resolved creation options captured from the agent (pre-first-request fallback). */
57
- readonly options?: RouteSource | undefined;
58
- /** Pure-inheritance baseline: the delegating parent's current route (absent when unprovable). */
59
- readonly inherited?: RouteSource | undefined;
60
- }
61
- /**
62
- * Detect the subagent's explicit authorized route at its first request, or
63
- * `undefined` for pure inheritance (semantics in the module doc). The wiring
64
- * skips role-inject on a defined result — the authorized route is the chain
65
- * head (spec D2) — and runs the allowlist-constrained four-stage inject on
66
- * `undefined`.
67
- */
68
- export declare function detectAuthorizedRoute(session: AuthorizedRouteSession): PolicyRoute | undefined;
@@ -1,98 +0,0 @@
1
- /**
2
- * LLM auto-match role selection (plan fallbacks-role-automatch Task 3).
3
- *
4
- * This module is the bounded LLM caller behind the resolver's stage-3
5
- * auto-match hook (`resolveRoleAtDispatch`, `role-resolution.ts`). When the
6
- * explicit and rules stages resolve to `'inherit'` and `roleAutoMatch` is
7
- * enabled, `pickRoleByLlm` asks the model to pick the best-fit declared role
8
- * from the taxonomy and returns its declared RAW id, or `null` (= `'inherit'`).
9
- *
10
- * Contract (architect-locked, plan Global Constraints · Auto-match bounded):
11
- * - ONE bounded completion per decision: small `maxTokens`
12
- * ({@link AUTOMATCH_MAX_TOKENS}) and a timeout
13
- * ({@link AUTOMATCH_TIMEOUT_MS}, default 5s) that abandons the stream via
14
- * the streaming contract's cancellation (`options.signal`) — no dangling
15
- * iterator, no unhandled rejection.
16
- * - Never throws. Every failure mode — throw, timeout, `none`, no valid id,
17
- * garbage answer, malformed inputs — resolves to `null`, so the "never
18
- * throws out of `agent/request`" invariant holds end-to-end (the resolver
19
- * deliberately does NOT catch the hook).
20
- * - Fast paths that avoid an LLM call entirely: empty taxonomy, absent
21
- * `ctx.llm` service, and no concrete provider/model route for the judgment
22
- * call. The plugin does NOT listen on `llm/stream`, so this call cannot
23
- * recurse into the plugin.
24
- *
25
- * Judgment-call route choice (documented): the concrete provider/model for the
26
- * judgment call is taken from the roles' DECLARED context first — the first
27
- * exact (non-wildcard) `provider/model` selector across the declared chains,
28
- * in declaration order (the operator's own taxonomy is the most stable route
29
- * set). If no declared chain yields an exact selector, the agent's current
30
- * `options.provider`/`options.model` is used when both halves are present;
31
- * otherwise the call is skipped (`null`).
32
- *
33
- * Pure helpers exported for unit testing:
34
- * - {@link buildAutomatchPrompt} — the taxonomy prompt (declared ids +
35
- * personas + agent origin/agentPreset; instructs exactly-one-declared-id-or-
36
- * `none`).
37
- * - {@link parseAutomatchAnswer} — parses a declared id out of the model
38
- * answer with surrounding whitespace/punctuation/quote/backtick tolerance;
39
- * `none` (case-insensitive) and unknown ids → `null`. `roleIds` is the
40
- * trimmed-id → declared-raw-id map, the same canonicalization the resolver
41
- * uses.
42
- *
43
- * @module dsh-llm-fallbacks/automatch
44
- */
45
- import { type GenerateOptions, type StreamChunk } from '@deepseek-ai/dsh-llm';
46
- import type { FallbacksRole, FallbacksRoles } from './config.ts';
47
- import type { AgentLike } from './roles.ts';
48
- /** Default auto-match completion timeout in ms — abandons the stream. */
49
- export declare const AUTOMATCH_TIMEOUT_MS = 5000;
50
- /** Output bound (tokens) for the judgment call: one id or the literal `none`. */
51
- export declare const AUTOMATCH_MAX_TOKENS = 32;
52
- /**
53
- * Minimal `llm` service surface {@link pickRoleByLlm} reads. The real
54
- * `LlmRuntime.stream(options)` matches structurally; this face keeps the
55
- * module free of the full cordis Context merge.
56
- */
57
- export interface AutomatchLlm {
58
- stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
59
- }
60
- /**
61
- * Minimal context surface: `ctx.get('llm')` (optional service — absent →
62
- * `null` fast-path). The broad `get(name: string)` face is satisfied by the
63
- * real cordis `Context` (`ReflectService.get` overloads return `any`/the
64
- * merged service), so the Task-4 wiring can pass `ctx` directly.
65
- */
66
- export interface AutomatchContext {
67
- get(name: string): AutomatchLlm | undefined;
68
- }
69
- /** Options for {@link pickRoleByLlm}. */
70
- export interface PickRoleByLlmOptions {
71
- /** Completion timeout in ms; defaults to {@link AUTOMATCH_TIMEOUT_MS}. */
72
- timeoutMs?: number;
73
- /** Warning sink — the decision path injects the plugin logger. */
74
- warn: (message: string) => void;
75
- }
76
- /**
77
- * Build the auto-match taxonomy prompt: every declared role's trimmed id and
78
- * persona, the agent's origin (and non-empty `agentPreset` when present), and
79
- * the exactly-one-declared-id-or-`none` instruction. Pure — no I/O.
80
- */
81
- export declare function buildAutomatchPrompt(roles: readonly FallbacksRole[], agent: AgentLike): string;
82
- /**
83
- * Parse the model's answer into a declared role id.
84
- *
85
- * The answer is expected to BE an id (possibly wrapped in whitespace, quotes,
86
- * backticks, or trailing punctuation); surrounding characters outside the id
87
- * grammar (`[a-zA-Z0-9-]`) are stripped, then the remaining string must match
88
- * a declared id exactly (case-insensitive) via `roleIds` (trimmed id →
89
- * declared RAW id, the resolver's canonicalization). `none` (case-insensitive)
90
- * and any unknown/malformed answer → `null`.
91
- */
92
- export declare function parseAutomatchAnswer(answer: string, roleIds: ReadonlyMap<string, string>): string | null;
93
- /**
94
- * Bounded LLM role-selection call (see module header). Resolves the declared
95
- * RAW id of the best-fit role, or `null` on any fast path or failure. NEVER
96
- * throws.
97
- */
98
- export declare function pickRoleByLlm(ctx: AutomatchContext, roles: FallbacksRoles, agent: AgentLike, opts: PickRoleByLlmOptions): Promise<string | null>;
@@ -1,19 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
3
- import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
4
- import type { FallbacksSettingsController, FallbacksSettingsState } from './fallbacks-store.ts';
5
- /** Injected dependencies of {@link GeneralFallbacksRow} (slot `inject`). */
6
- export interface GeneralFallbacksRowInjected {
7
- /** The shared controller (loaded on first mount, refreshed on pushed invalidations). */
8
- controller: FallbacksSettingsController;
9
- /** uSES subscription hook bound to the store (inject face — advisor pattern). */
10
- useSnapshot: SnapshotSelectorHook<FallbacksSettingsState>;
11
- }
12
- /** Props delivered by the slot outlet: runtime share + locale seat + inject face. */
13
- export type GeneralFallbacksRowProps = PropsRuntime<'settings.general.item'> & PropsLocale<'fallbacks'> & GeneralFallbacksRowInjected;
14
- /**
15
- * Render the Fallbacks status row.
16
- * @param props - composed slot props.
17
- * @returns the row element tree.
18
- */
19
- export declare function GeneralFallbacksRow({ controller, useSnapshot, t }: GeneralFallbacksRowProps): ReactNode;
@@ -1,39 +0,0 @@
1
- /**
2
- * RouteChanged effort rule for config overrides (plan dsh-012-subagent-routing
3
- * T4; spec D3).
4
- *
5
- * Pure, host-free module: plain seed config plus a target route in, the
6
- * overridden config out. The effort handling mirrors the upstream
7
- * `resolveChildAgentOptions` rule (deepseek-harness
8
- * `subagent/src/child-agent.ts:117`): a changed provider+model route drops the
9
- * seed's route-owned `reasoningEffort` so the selected model resolves its own
10
- * default, unless an effort is explicitly named for the override; an
11
- * unchanged route keeps the seed effort. Policy-independent by design — the
12
- * same rule applies on every override path whether the subagent routing
13
- * policy is on or off (spec D3). Never throws.
14
- *
15
- * @module dsh-llm-fallbacks/override
16
- */
17
- import type { LlmCallConfig } from '@deepseek-ai/dsh-llm';
18
- /** Reasoning-effort value carried by `LlmCallConfig.reasoningEffort`. */
19
- export type LlmReasoningEffort = NonNullable<LlmCallConfig['reasoningEffort']>;
20
- /**
21
- * Override a request config with a target route under the upstream
22
- * routeChanged effort rule (spec D3):
23
- *
24
- * - An explicitly named effort always survives — it wins over the seed effort
25
- * on either route (upstream semantics: the request overrides the parent).
26
- * - Same provider+model route → the seed `reasoningEffort` is preserved.
27
- * - Route changed without an explicit effort → the seed effort is dropped
28
- * (never carry a stale effort into a different provider/model route).
29
- *
30
- * @param seed - the caller's current request config.
31
- * @param to - the effective target route.
32
- * @param explicitEffort - an effort explicitly named for this override.
33
- * @returns a fresh config routed to `to` with the effort rule applied; never
34
- * mutates `seed`.
35
- */
36
- export declare function overrideConfigWithRouteRule(seed: LlmCallConfig, to: {
37
- provider: string;
38
- model: string;
39
- }, explicitEffort?: LlmReasoningEffort): LlmCallConfig;
package/dist/roles.d.ts DELETED
@@ -1,90 +0,0 @@
1
- /**
2
- * Role resolution for fallback chains (spec §7.1; plan fallbacks-role-runtime
3
- * Task 1).
4
- *
5
- * This module is the RULES stage. Dispatch-time role resolution
6
- * (plan fallbacks-role-automatch; plan dsh-148 added the dispatch stage) is
7
- * the four-stage resolver in `role-resolution.ts` — explicit
8
- * (`session.header.agentPreset`) → dispatch-declared role (the seam record
9
- * resolved from `**Execute as**`) → this rules stage → an optional auto-match
10
- * hook; `AgentLike` carries the additive `session.header.agentPreset?`
11
- * carrier for that explicit stage.
12
- *
13
- * Precedence (first hit wins):
14
- * 1. the first `rules` entry whose specified provider/model patterns match
15
- * the agent — rules are SUBAGENT-ONLY (PR #62 feedback): a root-origin
16
- * agent never matches rules and resolves straight to the built-in
17
- * `'inherit'` (→ `rootChain`). The legacy per-rule `origin` field (a
18
- * pre-feedback config may still carry `origin: root|subagent`) is
19
- * IGNORED at match time — every rule applies to subagents regardless
20
- * of the stored origin value;
21
- * 2. the built-in `'inherit'` role (no-rule-match default, spec §7.1 / D4).
22
- *
23
- * A matched rule must target a declared role id (`roleIds`, derived from
24
- * `config.roles.list`) or the built-in `'inherit'` — an undeclared target
25
- * warns and falls back to `'inherit'` (defensive; startup validation already
26
- * flagged the reference, spec §7.1 / AC-4).
27
- *
28
- * Role ids are canonicalized by trim (qc2 F-001, client-canonical trim
29
- * alignment — `validateFallbacksConfig` trims ids and rule references, so
30
- * the runtime must too): `roleIds` maps a TRIMMED id to the DECLARED RAW
31
- * id, membership compares `rule.role.trim()`, and a matched rule returns
32
- * the declared raw id — so roleDef lookups in `chains.ts` / `commands.ts`
33
- * match the stored `roles.list[].id` exactly and a padded YAML id
34
- * (`' coder '`) with a trimmed rule reference (`'coder'`) resolves to the
35
- * same role instead of silently degrading to `'inherit'`.
36
- *
37
- * A missing agent origin is treated as `'root'` — and root agents never
38
- * match rules (rules are subagent-only, PR #62 feedback). Origin is read
39
- * from `session.header.origin` — a native `SessionHeader` field the store folds
40
- * from `CreateSessionOptions.meta.origin` (`packages/core/session/src/
41
- * index.ts:884`); subagent children set it via `childSessionMeta`
42
- * (`packages/subagent/subagent/src/child-agent.ts:115`), root agents carry
43
- * none. A subagent `persona` is NOT readable at the decision point:
44
- * `AgentOptions` is provider/model/maxTokens only, and persona is installed
45
- * as a scoped system-prompt section in the child's creation window — see
46
- * `.mstar/iterations/iter-20260811-fallbacks-mount-only/guides/
47
- * role-and-model-selection-exploration.md` (Role section).
48
- *
49
- * @module dsh-llm-fallbacks/roles
50
- */
51
- import { type FallbacksRoleRule } from './config.ts';
52
- export type { FallbacksRoleRule } from './config.ts';
53
- /** Agent origins (spec §3) — an agent property, not a rule constraint:
54
- * rules are subagent-only (PR #62 feedback). */
55
- export type Origin = 'root' | 'subagent';
56
- /** Loose agent shape sufficient for role resolution (spec §3 / brief). */
57
- export interface AgentLike {
58
- options?: {
59
- provider?: string;
60
- model?: string;
61
- };
62
- session?: {
63
- header?: {
64
- origin?: Origin;
65
- /** Dispatch-time explicit role carrier (plan fallbacks-role-automatch Task 2). */
66
- agentPreset?: string;
67
- };
68
- };
69
- }
70
- /**
71
- * Resolve the fallback-chain role for an agent: first matching rule (in
72
- * listed order) → the built-in `'inherit'` role. A rule targeting an id
73
- * outside the declared-id set ∪ {'inherit'} warns once and resolves to
74
- * `'inherit'` (the defensive path — `validateFallbacksConfig` already
75
- * warned at startup, spec §7.1).
76
- *
77
- * `roleIds` is the canonical trimmed-id map built by the wiring
78
- * (`trimmed id → declared raw id`, see `src/index.ts` apply()): a matched
79
- * rule returns the declared RAW id so the roleDef lookups in
80
- * `chains.ts` / `commands.ts` find the stored `roles.list[]` entry exactly
81
- * (qc2 F-001 — no silent rule inertness on padded YAML ids).
82
- *
83
- * `warn` defaults to `console.warn` (direct-call compatibility); the
84
- * decision path injects the cordis `logger.warn` so every plugin warning
85
- * flows through the plugin log namespace (qc2 F-002 / qc3 S-3).
86
- */
87
- export declare function resolveRole(agent: AgentLike, rules: FallbacksRoleRule[], roleIds: ReadonlyMap<string, string>, warn?: (message: string) => void, servedRoute?: {
88
- provider: string;
89
- model: string;
90
- }): string;
@@ -1,43 +0,0 @@
1
- /**
2
- * Route identity and allowlist intersection on resolved candidates (plan
3
- * dsh-012-subagent-routing T2; spec D1 ∩ D2).
4
- *
5
- * Pure, host-free module: plain route data in, results out. Route identity is
6
- * the EXACT provider string plus the EXACT model string — no aliasing, no
7
- * case-folding, no catalog-id substitution (spec Terms). Intersections run on
8
- * RESOLVED candidates (concrete provider+model after the chain candidate
9
- * pipeline); this module never resolves chains or expands wildcards — the
10
- * caller passes already-resolved candidates in.
11
- *
12
- * Fail-closed direction: an empty allowlist contains nothing and an empty
13
- * intersection yields `undefined` — the caller must skip the
14
- * plugin-originated route rather than fall back to an unproven one (spec D1).
15
- * Never throws.
16
- *
17
- * @module dsh-llm-fallbacks/route-allowlist
18
- */
19
- import type { PolicyRoute } from './subagent-policy.ts';
20
- /** One resolved chain candidate: a selector anchored to a concrete model id. */
21
- export type ResolvedCandidate = {
22
- readonly provider: string;
23
- readonly model?: string;
24
- };
25
- /**
26
- * Normalize resolved chain candidates into detached plain routes. Candidates
27
- * without a concrete model id are dropped (they are not routes); every
28
- * surviving entry is a fresh object, so the result never aliases the
29
- * caller's candidate list.
30
- */
31
- export declare function resolvedRoutes(candidates: readonly ResolvedCandidate[]): PolicyRoute[];
32
- /**
33
- * Whether the route is on the allowlist under exact route identity: both the
34
- * provider and the model strings must equal an allowed route exactly.
35
- */
36
- export declare function routeOnAllowlist(route: PolicyRoute, allowedModels: readonly PolicyRoute[]): boolean;
37
- /**
38
- * The first resolved candidate that is on the allowlist, preserving the
39
- * candidate order (the chain's first in-allowlist entry wins), or `undefined`
40
- * when the intersection is empty — the caller then skips the inject / switch
41
- * and the host seed stands (spec D1 ∩ D2).
42
- */
43
- export declare function firstAllowedCandidate(resolved: readonly PolicyRoute[], allowedModels: readonly PolicyRoute[]): PolicyRoute | undefined;
@@ -1,123 +0,0 @@
1
- /**
2
- * Subagent routing policy reader (plan dsh-012-subagent-routing T1; spec D1).
3
- *
4
- * Pure, host-free module: plain data in, results out. It resolves the
5
- * effective subagent model-selection policy from two snapshots the wiring
6
- * layer extracts from the host:
7
- *
8
- * 1. the session's `subagent/model-selection-policy` event payload, read
9
- * STRUCTURALLY — the upstream reader is not published from
10
- * `@deepseek-ai/dsh-tool-subagent` and throws on malformed payloads,
11
- * which is incompatible with the fail-closed no-throw requirement;
12
- * 2. the host settings service snapshot (`SubagentModelSelectionConfig`
13
- * `.current()`), typed via a type-only peer import that is erased at emit.
14
- *
15
- * The session event wins over settings (ADR read order). Anything present but
16
- * unreadable yields `'unprovable'` (fail-closed): callers must not emit a
17
- * plugin-originated route they cannot prove is allowed. This module never
18
- * throws.
19
- *
20
- * @module dsh-llm-fallbacks/subagent-policy
21
- */
22
- import type { SubagentModelSelectionSettings } from '@deepseek-ai/dsh-tool-subagent/model-selection-settings';
23
- declare module '@deepseek-ai/dsh-session/types' {
24
- interface SessionEventMap {
25
- /**
26
- * Host-recorded route policy for a model-selectable subagent session.
27
- * The payload shape mirrors upstream exactly, so a future published
28
- * upstream augmentation merges identically. Read structurally by this
29
- * plugin; never written here.
30
- */
31
- 'subagent/model-selection-policy': {
32
- /** Exact routes this session may select explicitly for a child. */
33
- allowedModels: {
34
- provider: string;
35
- model: string;
36
- }[];
37
- };
38
- }
39
- }
40
- /** One exact child LLM route (structural twin of upstream `AllowedModelRoute`). */
41
- export type PolicyRoute = {
42
- readonly provider: string;
43
- readonly model: string;
44
- };
45
- /**
46
- * Effective subagent routing policy for one session.
47
- *
48
- * - `disabled`: host policy off or absent — selection matches pre-policy
49
- * behavior.
50
- * - `enabled`: an allowlist is proven; plugin-originated routes must stay
51
- * inside it.
52
- * - `unprovable`: the policy is on but unreadable (malformed event payload,
53
- * or enabled settings with a malformed route list) — fail-closed; no
54
- * plugin-originated route may be emitted.
55
- */
56
- export type EffectivePolicy = {
57
- readonly state: 'disabled';
58
- } | {
59
- readonly state: 'enabled';
60
- readonly allowedModels: readonly PolicyRoute[];
61
- } | {
62
- readonly state: 'unprovable';
63
- };
64
- /** Outcome of the structural session-event read. */
65
- export type SessionPolicyEventRead = {
66
- readonly ok: true;
67
- readonly allowedModels: PolicyRoute[];
68
- } | {
69
- readonly ok: false;
70
- readonly present: true;
71
- } | {
72
- readonly ok: false;
73
- readonly present: false;
74
- };
75
- /** Settings snapshot consumed by the reader — plain structural data, no host objects. */
76
- export type PolicySettings = {
77
- readonly enabled: boolean;
78
- readonly allowedModels: readonly PolicyRoute[];
79
- };
80
- /** Assignability guard: fails typecheck if the peer's settings shape drifts from the reader's snapshot contract. */
81
- type AssertSettingsShape<T extends PolicySettings> = T;
82
- /**
83
- * The host settings service snapshot type, re-exported from the published
84
- * peer subpath (type-only). Wiring reads the service's `current()` and passes
85
- * the result in as plain data; if upstream drifts, this alias fails typecheck
86
- * instead of drifting silently at runtime (plan ADR).
87
- */
88
- export type PeerSettingsSnapshot = AssertSettingsShape<SubagentModelSelectionSettings>;
89
- /**
90
- * Structurally read the session's `subagent/model-selection-policy` event.
91
- * Never throws: a present-but-malformed payload is reported, not raised.
92
- *
93
- * Recency (qc fix wave S-asym — deliberate asymmetry, both sides mirror
94
- * upstream): this read takes the FIRST occurrence, exactly like the upstream
95
- * reader's `.find` — the host writes the policy event at most once per
96
- * session (its ensure-guard checks the reader before appending), so a second
97
- * occurrence would already be off-contract. The re-emittable
98
- * `model/selection` log is the opposite: `authorized-route.ts`
99
- * `lastModelSelection` scans backwards because upstream's selection
100
- * projection folds to the newest entry.
101
- *
102
- * @param events - plain event descriptors (`type` + optional `data`) captured
103
- * from the session by the wiring layer.
104
- * @returns the detached route list when the event is present and valid;
105
- * otherwise a presence flag distinguishing "no policy event" (pure
106
- * inheritance path) from "policy event unreadable" (fail-closed).
107
- */
108
- export declare function readSessionPolicyEvent(events: readonly {
109
- type: string;
110
- data?: unknown;
111
- }[]): SessionPolicyEventRead;
112
- /**
113
- * Resolve the effective policy from the session-event read and the settings
114
- * snapshot. The session event wins over settings (ADR read order).
115
- * Fail-closed: a present-but-malformed event, or enabled settings with a
116
- * malformed route list, yields `'unprovable'`. Never throws.
117
- *
118
- * @param event - result of {@link readSessionPolicyEvent}.
119
- * @param settings - settings service snapshot, or undefined when the host
120
- * does not compose the service.
121
- */
122
- export declare function effectivePolicy(event: SessionPolicyEventRead, settings: PolicySettings | undefined): EffectivePolicy;
123
- export {};