dsh-llm-fallbacks 0.2.0 → 0.2.2

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/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,7 +13,7 @@
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
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
3
3
  import z from "@deepseek-ai/schemastery";
4
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
4
5
  import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
5
6
  //#region .build/host/selectors.js
6
7
  var SelectorError = class extends Error {
@@ -52,7 +53,8 @@ const defaultFallbacksConfig = {
52
53
  revertPolicy: "cooldown-expiry",
53
54
  maxSwitchesPerStep: 8,
54
55
  alwaysModeRetryCap: 5,
55
- presets: "bundled"
56
+ presets: "bundled",
57
+ roleAutoMatch: true
56
58
  };
57
59
  const INHERIT_ROLE_ID = "inherit";
58
60
  const ROLE_ID_PATTERN = /^[a-z0-9-]{1,32}$/;
@@ -138,8 +140,101 @@ const Config = z.object({
138
140
  revertPolicy: z.union([z.const("cooldown-expiry"), z.const("never")]).default("cooldown-expiry"),
139
141
  maxSwitchesPerStep: z.number().default(8),
140
142
  alwaysModeRetryCap: z.number().default(5),
141
- presets: z.union([z.const("bundled"), z.const("none")]).default("bundled")
143
+ presets: z.union([z.const("bundled"), z.const("none")]).default("bundled"),
144
+ roleAutoMatch: z.boolean().default(true)
142
145
  });
146
+ function buildAutomatchPrompt(roles, agent) {
147
+ const lines = [
148
+ "You are assigning a subagent to its best-fit role from a declared taxonomy.",
149
+ "",
150
+ "Declared roles:",
151
+ ...roles.map((role) => `- ${role.id.trim()}: ${role.persona}`),
152
+ "",
153
+ `Agent context — origin: ${agent.session?.header?.origin ?? "root"}`
154
+ ];
155
+ const preset = agent.session?.header?.agentPreset?.trim();
156
+ if (preset !== void 0 && preset !== "") lines.push(`agentPreset: ${preset}`);
157
+ lines.push("", "Reply with EXACTLY ONE of the declared role ids above, or the literal \"none\" when no role fits.", "Do not add any explanation, punctuation, or other text.");
158
+ return lines.join("\n");
159
+ }
160
+ function parseAutomatchAnswer(answer, roleIds) {
161
+ const cleaned = answer.replace(/^[^a-zA-Z0-9-]+/, "").replace(/[^a-zA-Z0-9-]+$/, "");
162
+ if (cleaned === "") return null;
163
+ const key = cleaned.toLowerCase();
164
+ if (key === "none") return null;
165
+ return roleIds.get(key) ?? null;
166
+ }
167
+ async function pickRoleByLlm(ctx, roles, agent, opts) {
168
+ try {
169
+ const list = Array.isArray(roles.list) ? roles.list : [];
170
+ if (list.length === 0) return null;
171
+ const llm = ctx.get("llm");
172
+ if (llm === void 0) return null;
173
+ const route = pickJudgmentRoute(list, agent);
174
+ if (route === null) return null;
175
+ const roleIds = new Map(list.map((role) => [role.id.trim(), role.id]));
176
+ const answer = await streamRoleAnswer(llm, route, buildAutomatchPrompt(list, agent), opts);
177
+ if (answer === null) return null;
178
+ return parseAutomatchAnswer(answer, roleIds);
179
+ } catch (error) {
180
+ opts.warn(`llm-fallbacks: auto-match failed — falling back to "inherit": ${describeError(error)}`);
181
+ return null;
182
+ }
183
+ }
184
+ function pickJudgmentRoute(roles, agent) {
185
+ for (const role of roles) for (const entry of role.chain ?? []) try {
186
+ const selector = parseSelector(entry);
187
+ if (selector.model !== void 0) return {
188
+ provider: selector.provider,
189
+ model: selector.model
190
+ };
191
+ } catch {}
192
+ const provider = agent.options?.provider;
193
+ const model = agent.options?.model;
194
+ if (provider !== void 0 && model !== void 0) return {
195
+ provider,
196
+ model
197
+ };
198
+ return null;
199
+ }
200
+ async function streamRoleAnswer(llm, route, prompt, opts) {
201
+ const controller = new AbortController();
202
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 5e3);
203
+ let text = "";
204
+ let finishKind;
205
+ try {
206
+ for await (const chunk of llm.stream({
207
+ provider: route.provider,
208
+ model: route.model,
209
+ system: prompt,
210
+ messages: [createUserMessage({
211
+ content: [{
212
+ type: "text",
213
+ text: "Pick the best-fit role for this agent."
214
+ }],
215
+ source: { kind: "user" }
216
+ })],
217
+ maxTokens: 32,
218
+ signal: controller.signal
219
+ })) if (chunk.type === "text-delta") text += chunk.text;
220
+ else if (chunk.type === "finish") {
221
+ finishKind = chunk.reason.kind;
222
+ break;
223
+ }
224
+ if (controller.signal.aborted) return null;
225
+ if (finishKind === "error" || finishKind === "aborted") return null;
226
+ return text;
227
+ } catch (error) {
228
+ if (controller.signal.aborted) opts.warn("llm-fallbacks: auto-match timed out — falling back to \"inherit\"");
229
+ else opts.warn(`llm-fallbacks: auto-match call failed — falling back to "inherit": ${describeError(error)}`);
230
+ return null;
231
+ } finally {
232
+ clearTimeout(timer);
233
+ }
234
+ }
235
+ function describeError(error) {
236
+ return error instanceof Error ? error.message : String(error);
237
+ }
143
238
  //#endregion
144
239
  //#region .build/host/chains.js
145
240
  function resolveCandidate(entry, failing, modelExists) {
@@ -263,6 +358,29 @@ function resolveRole(agent, rules, roleIds, warn = console.warn) {
263
358
  return INHERIT_ROLE_ID;
264
359
  }
265
360
  //#endregion
361
+ //#region .build/host/role-resolution.js
362
+ async function resolveRoleAtDispatch(agent, rules, roleIds, opts) {
363
+ const preset = agent.session?.header?.agentPreset?.trim();
364
+ if (preset !== void 0 && preset !== "" && preset !== "inherit") {
365
+ const declared = roleIds.get(preset);
366
+ if (declared !== void 0) return declared;
367
+ }
368
+ const role = resolveRole(agent, rules, roleIds, opts.warn);
369
+ if (role !== "inherit") return role;
370
+ if (opts.automatchEnabled && opts.automatch !== void 0) {
371
+ const picked = await opts.automatch(agent);
372
+ if (picked !== null && picked !== void 0) {
373
+ const declared = roleIds.get(picked.trim());
374
+ if (declared !== void 0) return declared;
375
+ opts.warn(`llm-fallbacks: auto-match returned undeclared role "${picked}" — falling back to "inherit"`);
376
+ }
377
+ }
378
+ return INHERIT_ROLE_ID;
379
+ }
380
+ function firstExactCandidate(all, wildcard) {
381
+ for (let index = 0; index < all.length; index += 1) if (!wildcard[index]) return all[index];
382
+ }
383
+ //#endregion
266
384
  //#region .build/host/cooldown.js
267
385
  var CooldownStore = class {
268
386
  entries = new Map();
@@ -398,7 +516,8 @@ const CONFIG_KEYS = {
398
516
  revertPolicy: true,
399
517
  maxSwitchesPerStep: true,
400
518
  alwaysModeRetryCap: true,
401
- presets: true
519
+ presets: true,
520
+ roleAutoMatch: true
402
521
  };
403
522
  const ROLES_KEYS = {
404
523
  list: true,
@@ -598,6 +717,7 @@ const FALLBACKS_COMMAND_LOCALES = {
598
717
  configMaxSwitches: "单步最大切换",
599
718
  configAlwaysCap: "always 上限",
600
719
  configPresets: "预置",
720
+ configRoleAutoMatch: "角色自动匹配",
601
721
  configEdit: "编辑:~/.dsh/profiles/<profile>/cordis.patch.yml(插件行)或 $DSH_HOME/settings.yaml(fallbacks: 分节)",
602
722
  configEditHint: "TUI 无法修改配置——只能编辑文件"
603
723
  },
@@ -635,6 +755,7 @@ const FALLBACKS_COMMAND_LOCALES = {
635
755
  configMaxSwitches: "Max switches/step",
636
756
  configAlwaysCap: "Always-mode cap",
637
757
  configPresets: "Presets",
758
+ configRoleAutoMatch: "Auto-match",
638
759
  configEdit: "Edit: ~/.dsh/profiles/<profile>/cordis.patch.yml (plugin row) or $DSH_HOME/settings.yaml (fallbacks: section)",
639
760
  configEditHint: "TUI cannot change config — edit files only"
640
761
  }
@@ -708,6 +829,7 @@ function fallbacksConfigText(summary, locale = "zh") {
708
829
  `${t.configMaxSwitches}: ${summary.maxSwitchesPerStep}`,
709
830
  `${t.configAlwaysCap}: ${summary.alwaysModeRetryCap}`,
710
831
  `${t.configPresets}: ${summary.presets}`,
832
+ `${t.configRoleAutoMatch}: ${summary.roleAutoMatch ? t.configEnabled : t.configDisabled}`,
711
833
  "",
712
834
  t.configEdit,
713
835
  t.configEditHint
@@ -1122,6 +1244,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
1122
1244
  });
1123
1245
  const states = new FallbackStateStore();
1124
1246
  stateStores.set(ctx, states);
1247
+ const dispatchInjected = new Set();
1125
1248
  async function decide(agent, turn, step, current, reason, state) {
1126
1249
  const config = source();
1127
1250
  if (state !== void 0) {
@@ -1159,7 +1282,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
1159
1282
  reason
1160
1283
  };
1161
1284
  }
1162
- function commit(agent, state, pending, turn, step) {
1285
+ function commit(state, pending, turn, step) {
1163
1286
  const config = source();
1164
1287
  const fromKey = selectorKey(pending.from.provider, pending.from.model);
1165
1288
  const until = config.revertPolicy === "never" ? Number.POSITIVE_INFINITY : Date.now() + config.cooldownMs;
@@ -1168,14 +1291,6 @@ function apply(ctx, config = defaultFallbacksConfig) {
1168
1291
  states.suppress(state, fromKey, until);
1169
1292
  states.recordFailure(state, fromKey);
1170
1293
  states.recordSwitch(state);
1171
- agent.session.append("fallbacks/switch", {
1172
- turn,
1173
- step,
1174
- from: pending.from,
1175
- to: pending.to,
1176
- role: pending.role,
1177
- reason: pending.reason
1178
- });
1179
1294
  }
1180
1295
  ctx.on("agent/request-error", async ({ agent, turn, step, provider, failure }, next) => {
1181
1296
  const config = source();
@@ -1185,7 +1300,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
1185
1300
  try {
1186
1301
  const pending = await decide(agent, turn, step, current, "trigger-code", states.peek(agent.id));
1187
1302
  if (pending === null) return next();
1188
- commit(agent, states.get(agent.id), pending, turn, step);
1303
+ commit(states.get(agent.id), pending, turn, step);
1189
1304
  return { kind: "retry" };
1190
1305
  } catch (error) {
1191
1306
  logger.warn("llm-fallbacks: decision path failed, passing the original failure through: %s", error?.message ?? String(error));
@@ -1198,6 +1313,30 @@ function apply(ctx, config = defaultFallbacksConfig) {
1198
1313
  const applied = state === void 0 ? void 0 : states.applyPending(state, turn, step);
1199
1314
  if (applied !== void 0) return overrideConfig(seed, applied.to);
1200
1315
  const config = source();
1316
+ if (config.enabled && hasChains && agent.session?.header?.origin === "subagent" && !dispatchInjected.has(agent.id)) {
1317
+ dispatchInjected.add(agent.id);
1318
+ try {
1319
+ const role = await resolveRoleAtDispatch(agent, config.roles.rules, roleIds, {
1320
+ automatchEnabled: config.roleAutoMatch ?? true,
1321
+ automatch: (agent) => pickRoleByLlm(ctx, config.roles, agent, { warn: logger.warn }),
1322
+ warn: logger.warn
1323
+ });
1324
+ if (role !== "inherit") {
1325
+ const { all, wildcard } = resolveChainViews(config.roles.list, config.rootChain, role, seed.provider, seed.model, logger.warn);
1326
+ const head = firstExactCandidate(all, wildcard);
1327
+ if (head !== void 0 && head.model !== void 0 && !(head.provider === seed.provider && head.model === seed.model)) {
1328
+ const to = {
1329
+ provider: head.provider,
1330
+ model: head.model
1331
+ };
1332
+ logger.info("llm-fallbacks: agent \"%s\" role-inject role=%s model=%s/%s", agent.id, role, head.provider, head.model);
1333
+ return overrideConfig(seed, to);
1334
+ }
1335
+ }
1336
+ } catch (error) {
1337
+ logger.warn("llm-fallbacks: dispatch role-injection failed, proceeding with the request unchanged: %s", error?.message ?? String(error));
1338
+ }
1339
+ }
1201
1340
  if (hasChains && config.enabled && config.alwaysModeRetryCap > 0 && countRetryEvents(agent.session, turn, step, seed.provider) >= config.alwaysModeRetryCap) {
1202
1341
  const decisionState = states.peek(agent.id);
1203
1342
  const pending = await decide(agent, turn, step, {
@@ -1206,7 +1345,7 @@ function apply(ctx, config = defaultFallbacksConfig) {
1206
1345
  }, "always-cap", decisionState);
1207
1346
  if (pending !== null) {
1208
1347
  const commitState = states.get(agent.id);
1209
- commit(agent, commitState, pending, turn, step);
1348
+ commit(commitState, pending, turn, step);
1210
1349
  const appliedCap = states.applyPending(commitState, turn, step);
1211
1350
  if (appliedCap !== void 0) return overrideConfig(seed, appliedCap.to);
1212
1351
  }
@@ -1220,9 +1359,11 @@ function apply(ctx, config = defaultFallbacksConfig) {
1220
1359
  });
1221
1360
  ctx.on("agent/disposed", ({ agent }) => {
1222
1361
  states.delete(agent.id);
1362
+ dispatchInjected.delete(agent.id);
1223
1363
  });
1224
1364
  ctx.effect(() => () => {
1225
1365
  states.clear();
1366
+ dispatchInjected.clear();
1226
1367
  }, "llm-fallbacks: clear per-agent state");
1227
1368
  const fallbacksCommandController = {
1228
1369
  getSnapshot(agent) {
@@ -1251,7 +1392,8 @@ function apply(ctx, config = defaultFallbacksConfig) {
1251
1392
  revertPolicy: config.revertPolicy,
1252
1393
  maxSwitchesPerStep: config.maxSwitchesPerStep,
1253
1394
  alwaysModeRetryCap: config.alwaysModeRetryCap,
1254
- presets: config.presets ?? "bundled"
1395
+ presets: config.presets ?? "bundled",
1396
+ roleAutoMatch: config.roleAutoMatch ?? true
1255
1397
  };
1256
1398
  }
1257
1399
  };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Dispatch-time three-stage role resolution (plan fallbacks-role-automatch
3
+ * Task 2).
4
+ *
5
+ * Precedence (first hit wins):
6
+ * 1. explicit — `session.header.agentPreset` trimmed matches a declared role
7
+ * id → that role's DECLARED RAW id (via the `roleIds` trimmed-id map, the
8
+ * same canonicalization the rules path uses); the reserved `'inherit'`
9
+ * and undeclared presets are NEVER dispatchable explicit roles and fall
10
+ * through to rules;
11
+ * 2. rules — the existing `resolveRole` passthrough, unchanged (a declared
12
+ * role wins; no-rule-match / an explicit rule targeting `'inherit'`
13
+ * resolve to `'inherit'`);
14
+ * 3. auto-match — only when stage 2 resolved to `'inherit'` AND
15
+ * `opts.automatchEnabled`: call `opts.automatch` with the agent. The hook
16
+ * (Task 3 supplies the bounded LLM caller) MUST return a declared raw id
17
+ * or `null`; the resolver defensively validates the return against
18
+ * `roleIds` — an unknown id warns and resolves to `'inherit'`, never an
19
+ * undeclared role. When `automatch` is undefined or `automatchEnabled`
20
+ * is false the hook is skipped.
21
+ *
22
+ * `'inherit'` is the single "no specific role" outcome — reached by
23
+ * no-rule-match OR an explicit rule targeting `'inherit'` — and both are
24
+ * auto-match eligible when enabled. An operator who wants a class to never
25
+ * auto-match sets `roleAutoMatch: false` (→ `automatchEnabled: false`).
26
+ *
27
+ * Pure ordering logic: no config object and no LLM service inside this
28
+ * module — the LLM is an injectable hook owned by Task 3/4.
29
+ *
30
+ * @module dsh-llm-fallbacks/role-resolution
31
+ */
32
+ import { type FallbacksRoleRule } from './config.ts';
33
+ import { type AgentLike } from './roles.ts';
34
+ import type { Selector } from './selectors.ts';
35
+ export type { FallbacksRoleRule } from './config.ts';
36
+ /** Options for {@link resolveRoleAtDispatch}. */
37
+ export interface ResolveRoleAtDispatchOptions {
38
+ /**
39
+ * Whether the auto-match stage may run (the `roleAutoMatch` config switch).
40
+ * When `false`, stage 3 never runs and the outcome is identical to the
41
+ * rules stage alone.
42
+ */
43
+ automatchEnabled: boolean;
44
+ /**
45
+ * Auto-match hook — Task 3 supplies the bounded LLM caller; the resolver
46
+ * itself contains no LLM. Contract: given the agent being resolved, return
47
+ * a declared raw role id or `null`. The resolver defensively validates the
48
+ * return against `roleIds` — an unknown id warns and resolves to
49
+ * `'inherit'`, never an undeclared role. Skipped when undefined.
50
+ */
51
+ automatch?: (agent: AgentLike) => Promise<string | null>;
52
+ /** Warning sink — the decision path injects the plugin logger. */
53
+ warn: (message: string) => void;
54
+ }
55
+ /**
56
+ * Resolve the dispatch-time role for an agent across three ordered stages
57
+ * (explicit → rules → auto-match hook). Never throws on its own inputs; the
58
+ * hook's own rejection is deliberately NOT caught here (the Task-3/4 wiring
59
+ * bounds the LLM call so it never throws out of the request path).
60
+ *
61
+ * `roleIds` is the canonical trimmed-id map (`trimmed id → declared raw id`,
62
+ * built in `src/index.ts` apply()): every stage canonicalizes its reference
63
+ * by trim and returns the DECLARED RAW id, so roleDef lookups in
64
+ * `chains.ts` / `commands.ts` find the stored `roles.list[]` entry exactly
65
+ * (qc2 F-001 — no silent role inertness on padded YAML ids).
66
+ */
67
+ export declare function resolveRoleAtDispatch(agent: AgentLike, rules: FallbacksRoleRule[], roleIds: ReadonlyMap<string, string>, opts: ResolveRoleAtDispatchOptions): Promise<string>;
68
+ /**
69
+ * The dispatch-time chain head (plan fallbacks-role-automatch Task 4): the
70
+ * FIRST candidate of a `resolveChainViews` pass whose `wildcard` flag is
71
+ * false — an exact `provider/model` entry. `provider/*` entries are NOT
72
+ * dispatch injection targets (no failing model to anchor them; a guessed
73
+ * injection risks a hard failure whose code may sit outside `triggerCodes`),
74
+ * so a wildcard-only chain yields `undefined` → no injection (today's
75
+ * behavior). No cooldown/failed filtering and no existence probe run here —
76
+ * a fresh subagent's first request has no failure-scoped state (see the Task
77
+ * 4 decision) and exact heads are never probed. Pairs with
78
+ * {@link resolveChainViews}'s parallel `wildcard` provenance (`src/chains.ts`);
79
+ * the caller resolves the role's concatenated chain first.
80
+ */
81
+ export declare function firstExactCandidate(all: readonly Selector[], wildcard: readonly boolean[]): Selector | undefined;
package/dist/roles.d.ts CHANGED
@@ -2,6 +2,12 @@
2
2
  * Role resolution for fallback chains (spec §7.1; plan fallbacks-role-runtime
3
3
  * Task 1).
4
4
  *
5
+ * This module is the RULES stage. Dispatch-time role resolution
6
+ * (plan fallbacks-role-automatch) is the three-stage resolver in
7
+ * `role-resolution.ts` — explicit (`session.header.agentPreset`) → this
8
+ * rules stage → an optional auto-match hook; `AgentLike` carries the additive
9
+ * `session.header.agentPreset?` carrier for that explicit stage.
10
+ *
5
11
  * Precedence (first hit wins):
6
12
  * 1. the first `rules` entry whose specified origin/provider/model patterns
7
13
  * all match the agent;
@@ -47,6 +53,8 @@ export interface AgentLike {
47
53
  session?: {
48
54
  header?: {
49
55
  origin?: Origin;
56
+ /** Dispatch-time explicit role carrier (plan fallbacks-role-automatch Task 2). */
57
+ agentPreset?: string;
50
58
  };
51
59
  };
52
60
  }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "dsh",
6
6
  "dsh-plugin"
7
7
  ],
8
- "version": "0.2.0",
8
+ "version": "0.2.2",
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",
11
11
  "types": "./dist/index.d.ts",
@@ -48,6 +48,7 @@
48
48
  "build-client": "tsx scripts/build-client.ts",
49
49
  "release:prepare": "tsx scripts/prepare-release.ts",
50
50
  "release:validate": "tsx scripts/validate-release-version.ts",
51
+ "repair:fallbacks-switch-logs": "tsx scripts/repair-fallbacks-switch-logs.ts",
51
52
  "test": "vitest run",
52
53
  "typecheck": "tsc -p tsconfig.build.json --noEmit && tsc --noEmit",
53
54
  "prepare": "pnpm run build"
@@ -57,23 +58,24 @@
57
58
  },
58
59
  "peerDependencies": {
59
60
  "@deepseek-ai/cordis": "^4.0.1",
60
- "@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
61
- "@deepseek-ai/dsh-api-gateway": "^0.1.0-rc.6",
62
- "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
63
- "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
64
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
65
- "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
66
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
67
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
68
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
69
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
70
- "@deepseek-ai/dsh-client-web-react": "^0.1.0-rc.6",
71
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
72
- "@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
73
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
74
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
75
- "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6",
76
- "@deepseek-ai/dsh-typert-registry": "^0.1.0-rc.6",
61
+ "@deepseek-ai/dsh-agent": "^0.1.0-rc.7",
62
+ "@deepseek-ai/dsh-api-gateway": "^0.1.0-rc.7",
63
+ "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.7",
64
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.7",
65
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
66
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
67
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.7",
68
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.7",
69
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
70
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
71
+ "@deepseek-ai/dsh-client-web-react": "^0.1.0-rc.7",
72
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
73
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.7",
74
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
75
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.7",
76
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
77
+ "@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.7",
78
+ "@deepseek-ai/dsh-typert-registry": "^0.1.0-rc.7",
77
79
  "react": "^18.2.0",
78
80
  "@deepseek-ai/schemastery": "^3.18.1"
79
81
  },