dsh-model-router 0.2.0 → 0.4.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 CHANGED
@@ -46,6 +46,21 @@ Everything else is a planner. That logic lives in `src/policy.ts` as a plain fun
46
46
 
47
47
  The router always stamps `provider` + `model`. `reasoningEffort` and `maxTokens` are *optional per role*: set them in the config and they're enforced for that role; leave them out and those fields inherit from your session's selection. So picking "max effort" in the UI but not pinning `reasoningEffort` in the config still gives you max-effort thinking — it just happens on the routed model.
48
48
 
49
+ ## Turning the router off
50
+
51
+ Routing is on by default. Two ways to switch it off:
52
+
53
+ - **GUI (Settings → Plugins → dsh-model-router):** the plugin registers a live
54
+ settings section; flip `enabled` off. It applies immediately (no restart),
55
+ persists in `settings.yaml` under `model-router:`, and unregisters the prompt
56
+ section and the skill too. Flip it back on and everything returns.
57
+ - **Patch row:** set `enabled: false` in the profile's `cordis.patch.yml` row
58
+ (takes effect on the next boot). `disabled: true` still skips the row entirely.
59
+
60
+ With the router off, requests use the session's selected model (your
61
+ `agent-default-model` setting or the base default) — the router is simply not
62
+ rewriting them.
63
+
49
64
  ## Tuning
50
65
 
51
66
  All configuration lives on the plugin row. Patch it in the profile's `cordis.patch.yml`:
@@ -62,7 +77,10 @@ All configuration lives on the plugin row. Patch it in the profile's `cordis.pat
62
77
  executor: # subagent route
63
78
  provider: deepseek-official
64
79
  model: deepseek-v4-flash
65
- reasoningEffort: low
80
+ reasoningEffort: high
81
+ escalateOnError: true # after a failed step…
82
+ escalateTo: max # …bump effort for the next request
83
+ recoverySteps: 2 # …wearing off after N clean steps
66
84
  mode: strict # strict | plan (see below)
67
85
  promptSection: true # register the always-on routing section
68
86
  skill: true # register the pro-flash-routing skill
@@ -70,6 +88,8 @@ All configuration lives on the plugin row. Patch it in the profile's `cordis.pat
70
88
 
71
89
  `mode` controls how the root agent is treated: `strict` keeps it on the planner route always; `plan` sends the root to the executor route unless plan mode is active, reserving pro for real planning.
72
90
 
91
+ **Error-driven escalation** (`escalateOnError`): when a route's agent hits a failed tool step, the *next* request bumps to `escalateTo` and wears off after `recoverySteps` clean steps. It's deterministic and stateless — the router folds the session log per request, so only prior steps are considered (a failure can't escalate the very request that caused it). It's a per-route knob: enable it on the executor to make flash think harder after a flubbed execution step, without touching the baseline.
92
+
73
93
  The defaults are exactly the table at the top of this page. To switch the router off for a session, disable the row (`disabled: true`) or remove the plugin — `dsh plugin --profile web remove dsh-model-router`.
74
94
 
75
95
  ## Reduce pro token usage
package/cordis.patch.yml CHANGED
@@ -19,12 +19,16 @@
19
19
  executor:
20
20
  provider: deepseek-official
21
21
  model: deepseek-v4-flash
22
- # reasoningEffort: low
22
+ # reasoningEffort: high
23
23
  # maxTokens: 16384
24
+ # escalateOnError: true # v1: after a failed step, escalate effort
25
+ # escalateTo: max # to this level for the next request…
26
+ # recoverySteps: 2 # …wearing off after N clean steps
24
27
  # strict: root is always the planner (pro).
25
28
  # plan: root is pro only while plan mode is active; otherwise it
26
29
  # falls back to the executor route to reserve pro for planning.
27
30
  mode: strict
31
+ # enabled: false # off switch — GUI: Settings → Plugins → model-router (live, no restart); patch row needs a reboot
28
32
  # Publish the always-on routing convention prompt section.
29
33
  promptSection: true
30
34
  # Register the `pro-flash-routing` skill in the session catalog.
package/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as _deepseek_ai_dsh_settings from '@deepseek-ai/dsh-settings';
1
2
  import { Service, Context } from '@deepseek-ai/cordis';
2
3
  import z from '@deepseek-ai/schemastery';
3
4
 
@@ -26,6 +27,17 @@ interface ModelRoute {
26
27
  reasoningEffort?: ReasoningEffort;
27
28
  /** Optional output-token cap for the role; omitted means inherit. */
28
29
  maxTokens?: number;
30
+ /**
31
+ * Error-driven escalation (v1): when true, a failed execution step bumps the
32
+ * next request's effort to `escalateTo`, wearing off after `recoverySteps`
33
+ * clean steps. Deterministic, stateless — the session log is folded per
34
+ * request, so only *prior* steps are ever considered.
35
+ */
36
+ escalateOnError?: boolean;
37
+ /** Effort used for the request after a failed step. */
38
+ escalateTo?: ReasoningEffort;
39
+ /** Clean steps before escalation wears off. Defaults to 2. */
40
+ recoverySteps?: number;
29
41
  }
30
42
  /** The two roles the router distinguishes. */
31
43
  type AgentRole = "planner" | "executor";
@@ -34,6 +46,13 @@ interface RouterConfig {
34
46
  planner: ModelRoute;
35
47
  executor: ModelRoute;
36
48
  mode: RoutingMode;
49
+ /**
50
+ * Live off-switch. Defaults to true; settable from Settings → Plugins →
51
+ * model-router (applies immediately) or from the patch row (next boot).
52
+ * When false, the router stops rewriting requests and unregisters the
53
+ * prompt section and the skill.
54
+ */
55
+ enabled: boolean;
37
56
  promptSection: boolean;
38
57
  skill: boolean;
39
58
  }
@@ -58,27 +77,33 @@ declare function roleFor(agent: unknown): AgentRole;
58
77
  * @returns the model route to stamp, or `undefined` to leave the request alone.
59
78
  */
60
79
  declare function routeFor(agent: unknown, config: RouterConfig, planModeActive?: boolean): ModelRoute | undefined;
61
-
62
80
  /**
63
- * dsh-model-router: role-based model routing for the DeepSeek Harness.
81
+ * Whether any of the last `recoverySteps` completed steps carried a failed
82
+ * tool result. A failure is a `tool/result` event whose data carries an
83
+ * `error` field (the harness records tool failures there).
64
84
  *
65
- * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated
66
- * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent
67
- * `agent/request` rewrite registered when the agent is created, so it applies
68
- * in every mode (web / headless / tui) and every agent preset, including
69
- * subagents the delegation tools create.
85
+ * Steps are deduplicated by `turn:step`, and only *completed* steps count —
86
+ * events are scanned from the tail, so the current in-flight request is never
87
+ * considered.
70
88
  *
71
- * Each role route may also pin `reasoningEffort` and `maxTokens`; when set,
72
- * they override the session's selection for that role. A `mode` switch lets a
73
- * deployment reserve the planner route for actual planning.
89
+ * @param events - the agent's session event log (or `undefined`).
90
+ * @param recoverySteps - how many completed steps back to scan.
91
+ * @returns true when a failed step is within the window.
92
+ */
93
+ declare function recentStepsHadError(events: readonly unknown[] | undefined, recoverySteps?: number): boolean;
94
+ /**
95
+ * Resolve the reasoning effort to stamp for one request.
74
96
  *
75
- * The plugin also publishes:
76
- * - a system-prompt section stating the planner/executor convention, and
77
- * - the `pro-flash-routing` skill teaching the agent to plan itself and
78
- * delegate code execution to flash subagents.
97
+ * Baseline is the route's `reasoningEffort`; when `escalateOnError` is enabled
98
+ * and a recent step failed, the effort bumps to `escalateTo` (falling back to
99
+ * the baseline when `escalateTo` is unset). Returns `undefined` to leave the
100
+ * request's effort alone (inherit the session selection).
79
101
  *
80
- * @module dsh-model-router
102
+ * @param route - the resolved route for the agent.
103
+ * @param events - the agent's session event log.
104
+ * @returns the effort to stamp, or `undefined` to inherit.
81
105
  */
106
+ declare function effortFor(route: ModelRoute, events: readonly unknown[] | undefined): ReasoningEffort | undefined;
82
107
 
83
108
  /** Plugin row id; the bundle patch inserts it under this id. */
84
109
  declare const name = "model-router";
@@ -89,24 +114,37 @@ declare const Config: z<Schemastery.ObjectS<{
89
114
  model: z<string, string>;
90
115
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
91
116
  maxTokens: z<number, number>;
117
+ escalateOnError: z<boolean, boolean>;
118
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
119
+ recoverySteps: z<number, number>;
92
120
  }>, Schemastery.ObjectT<{
93
121
  provider: z<string, string>;
94
122
  model: z<string, string>;
95
123
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
96
124
  maxTokens: z<number, number>;
125
+ escalateOnError: z<boolean, boolean>;
126
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
127
+ recoverySteps: z<number, number>;
97
128
  }>>;
98
129
  executor: z<Schemastery.ObjectS<{
99
130
  provider: z<string, string>;
100
131
  model: z<string, string>;
101
132
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
102
133
  maxTokens: z<number, number>;
134
+ escalateOnError: z<boolean, boolean>;
135
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
136
+ recoverySteps: z<number, number>;
103
137
  }>, Schemastery.ObjectT<{
104
138
  provider: z<string, string>;
105
139
  model: z<string, string>;
106
140
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
107
141
  maxTokens: z<number, number>;
142
+ escalateOnError: z<boolean, boolean>;
143
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
144
+ recoverySteps: z<number, number>;
108
145
  }>>;
109
146
  mode: z<"strict" | "plan", "strict" | "plan">;
147
+ enabled: z<boolean, boolean>;
110
148
  promptSection: z<boolean, boolean>;
111
149
  skill: z<boolean, boolean>;
112
150
  }>, Schemastery.ObjectT<{
@@ -115,40 +153,112 @@ declare const Config: z<Schemastery.ObjectS<{
115
153
  model: z<string, string>;
116
154
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
117
155
  maxTokens: z<number, number>;
156
+ escalateOnError: z<boolean, boolean>;
157
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
158
+ recoverySteps: z<number, number>;
118
159
  }>, Schemastery.ObjectT<{
119
160
  provider: z<string, string>;
120
161
  model: z<string, string>;
121
162
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
122
163
  maxTokens: z<number, number>;
164
+ escalateOnError: z<boolean, boolean>;
165
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
166
+ recoverySteps: z<number, number>;
123
167
  }>>;
124
168
  executor: z<Schemastery.ObjectS<{
125
169
  provider: z<string, string>;
126
170
  model: z<string, string>;
127
171
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
128
172
  maxTokens: z<number, number>;
173
+ escalateOnError: z<boolean, boolean>;
174
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
175
+ recoverySteps: z<number, number>;
129
176
  }>, Schemastery.ObjectT<{
130
177
  provider: z<string, string>;
131
178
  model: z<string, string>;
132
179
  reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
133
180
  maxTokens: z<number, number>;
181
+ escalateOnError: z<boolean, boolean>;
182
+ escalateTo: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
183
+ recoverySteps: z<number, number>;
134
184
  }>>;
135
185
  mode: z<"strict" | "plan", "strict" | "plan">;
186
+ enabled: z<boolean, boolean>;
136
187
  promptSection: z<boolean, boolean>;
137
188
  skill: z<boolean, boolean>;
138
189
  }>>;
190
+ /** Settings namespace the live on/off toggle lives under (settings.yaml). */
191
+ declare const SETTINGS_NS: _deepseek_ai_dsh_settings.SettingsNamespace;
139
192
  declare const SKILL_NAME = "pro-flash-routing";
140
193
  declare const SKILL_DESCRIPTION = "Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.";
141
194
  declare const SKILL_WHEN_TO_USE = "Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.";
142
195
  declare const SKILL_CONTENT = "# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** \u2014 `deepseek-v4-pro`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** \u2014 `deepseek-v4-flash`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with `exit_plan_mode`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to `deepseek-v4-flash`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Keep this agent's context lean\n\nInput tokens are the expensive part of the planner. Don't re-read large files or full transcripts on this agent \u2014 trust the subagent's final report. Prefer targeted reads (offset/limit) over whole files. When the context grows, compact rather than re-sending everything.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer `subagent` for self-contained work and `workflow` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by `deepseek-v4-flash`; planner output by `deepseek-v4-pro`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the `model-router` plugin row in the profile composition is the single place that owns it.";
143
196
  /** The plugin row id the bundle patch must insert. */
144
197
  declare const ROW_ID = "model-router";
198
+ /** Minimal structural view of the live agent object the router reads. */
199
+ interface AgentLike {
200
+ ctx: AgentScopedContext;
201
+ options?: {
202
+ subagentDepth?: number;
203
+ };
204
+ session?: {
205
+ header?: {
206
+ origin?: string;
207
+ };
208
+ events?: unknown[];
209
+ };
210
+ }
211
+ /** The agent-scoped context's waterfall surface the router uses. */
212
+ interface AgentScopedContext {
213
+ on(event: "agent/request", listener: (payload: Record<string, unknown>, next: () => Promise<Record<string, unknown>>) => Promise<Record<string, unknown>>, options?: {
214
+ prepend?: boolean;
215
+ }): () => void;
216
+ }
217
+ /** Host-plane surface the router consumes (events, prompt registry, skills). */
218
+ interface HarnessContext {
219
+ on(event: "agent/created", listener: (payload: {
220
+ agent: AgentLike;
221
+ }) => void): () => void;
222
+ on(event: "agent/disposed", listener: (agent: unknown) => void): () => void;
223
+ systemPrompt: {
224
+ section(section: {
225
+ name: string;
226
+ order: number;
227
+ text: string;
228
+ }): () => void;
229
+ };
230
+ skills: {
231
+ register(skill: {
232
+ name: string;
233
+ description: string;
234
+ whenToUse?: string;
235
+ content: string;
236
+ source: string;
237
+ }): () => void;
238
+ };
239
+ }
145
240
  /**
146
241
  * Cordis service: per-agent request routing plus the convention surface.
147
242
  */
148
243
  declare class ModelRouter extends Service {
149
244
  static inject: string[];
150
245
  config: RouterConfig;
246
+ /** Currently authoritative config; swapped by the settings section when one is mounted. */
247
+ source: () => RouterConfig;
248
+ /** Host-plane surface the router consumes (events, prompt registry, skills). */
249
+ harness: HarnessContext;
250
+ /** Disposer of the currently registered prompt section, if any. */
251
+ promptDispose?: () => void;
252
+ /** Disposer of the currently registered skill, if any. */
253
+ skillDispose?: () => void;
151
254
  constructor(ctx: Context, rawConfig?: unknown);
255
+ /**
256
+ * Register the convention surface (prompt section + skill) for `cfg`,
257
+ * replacing whatever is currently registered. Called on construction and
258
+ * after every committed settings change; while `enabled` is false, nothing
259
+ * stays registered.
260
+ */
261
+ private render;
152
262
  }
153
263
 
154
- export { type AgentRole, Config, type ModelRoute, ModelRouter, ROW_ID, type ReasoningEffort, type RouterConfig, type RoutingMode, SKILL_CONTENT, SKILL_DESCRIPTION, SKILL_NAME, SKILL_WHEN_TO_USE, ModelRouter as default, name, roleFor, routeFor };
264
+ export { type AgentRole, Config, type ModelRoute, ModelRouter, ROW_ID, type ReasoningEffort, type RouterConfig, type RoutingMode, SETTINGS_NS, SKILL_CONTENT, SKILL_DESCRIPTION, SKILL_NAME, SKILL_WHEN_TO_USE, ModelRouter as default, effortFor, name, recentStepsHadError, roleFor, routeFor };
package/lib/index.js CHANGED
@@ -2,8 +2,10 @@
2
2
  import { Service } from "@deepseek-ai/cordis";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { foldPlanMode } from "@deepseek-ai/dsh-plan-mode";
5
+ import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
5
6
 
6
7
  // src/policy.ts
8
+ var DEFAULT_RECOVERY_STEPS = 2;
7
9
  function roleFor(agent) {
8
10
  const options = agent?.options;
9
11
  const depth = options?.subagentDepth;
@@ -19,6 +21,29 @@ function routeFor(agent, config, planModeActive = false) {
19
21
  if (config.mode === "plan" && !planModeActive) return config.executor;
20
22
  return config.planner;
21
23
  }
24
+ function recentStepsHadError(events, recoverySteps = DEFAULT_RECOVERY_STEPS) {
25
+ if (!Array.isArray(events) || recoverySteps <= 0) return false;
26
+ const seen = /* @__PURE__ */ new Set();
27
+ let steps = 0;
28
+ for (let i = events.length - 1; i >= 0; i -= 1) {
29
+ const event = events[i];
30
+ if (event?.type !== "tool/result" || event.data === void 0) continue;
31
+ const key = `${event.data.turn}:${event.data.step}`;
32
+ if (!seen.has(key)) {
33
+ if (steps >= recoverySteps) break;
34
+ seen.add(key);
35
+ steps += 1;
36
+ }
37
+ if (event.data.error !== void 0 && event.data.error !== null) return true;
38
+ }
39
+ return false;
40
+ }
41
+ function effortFor(route, events) {
42
+ if (route.escalateOnError === true && recentStepsHadError(events, route.recoverySteps)) {
43
+ return route.escalateTo ?? route.reasoningEffort;
44
+ }
45
+ return route.reasoningEffort;
46
+ }
22
47
 
23
48
  // src/index.ts
24
49
  var name = "model-router";
@@ -26,7 +51,10 @@ var ModelRouteSchema = z.object({
26
51
  provider: z.string().min(1),
27
52
  model: z.string().min(1),
28
53
  reasoningEffort: z.union(["off", "low", "high", "max"]),
29
- maxTokens: z.number().min(1)
54
+ maxTokens: z.number().min(1),
55
+ escalateOnError: z.boolean(),
56
+ escalateTo: z.union(["off", "low", "high", "max"]),
57
+ recoverySteps: z.number().min(1)
30
58
  });
31
59
  var Config = z.object({
32
60
  planner: ModelRouteSchema.default({
@@ -38,15 +66,18 @@ var Config = z.object({
38
66
  model: "deepseek-v4-flash"
39
67
  }),
40
68
  mode: z.union(["strict", "plan"]).default("strict"),
69
+ enabled: z.boolean().default(true),
41
70
  promptSection: z.boolean().default(true),
42
71
  skill: z.boolean().default(true)
43
72
  });
73
+ var SETTINGS_NS = settingsNamespace("model-router");
44
74
  function resolveConfig(raw) {
45
75
  const parsed = Config(raw ?? {});
46
76
  return {
47
77
  planner: parsed.planner,
48
78
  executor: parsed.executor,
49
79
  mode: parsed.mode,
80
+ enabled: parsed.enabled,
50
81
  promptSection: parsed.promptSection,
51
82
  skill: parsed.skill
52
83
  };
@@ -98,44 +129,86 @@ function isPlanModeActive(agent) {
98
129
  var ModelRouter = class extends Service {
99
130
  static inject = ["skills", "systemPrompt"];
100
131
  config;
132
+ /** Currently authoritative config; swapped by the settings section when one is mounted. */
133
+ source;
134
+ /** Host-plane surface the router consumes (events, prompt registry, skills). */
135
+ harness;
136
+ /** Disposer of the currently registered prompt section, if any. */
137
+ promptDispose;
138
+ /** Disposer of the currently registered skill, if any. */
139
+ skillDispose;
101
140
  constructor(ctx, rawConfig = {}) {
102
141
  super(ctx, "modelRouter");
103
142
  this.config = resolveConfig(rawConfig);
104
- const harness = ctx;
105
- harness.on("agent/created", ({ agent }) => {
143
+ this.source = () => this.config;
144
+ this.harness = ctx;
145
+ this.harness.on("agent/created", ({ agent }) => {
106
146
  const dispose = agent.ctx.on(
107
147
  "agent/request",
108
148
  async (payload, next) => {
109
149
  const resolved = await next();
110
- const route = routeFor(agent, this.config, isPlanModeActive(agent));
150
+ const cfg = this.source();
151
+ if (!cfg.enabled) return resolved;
152
+ const route = routeFor(agent, cfg, isPlanModeActive(agent));
111
153
  if (route === void 0) return resolved;
112
154
  const stamped = {
113
155
  ...resolved,
114
156
  provider: route.provider,
115
157
  model: route.model
116
158
  };
117
- if (route.reasoningEffort !== void 0) stamped.reasoningEffort = route.reasoningEffort;
118
159
  if (route.maxTokens !== void 0) stamped.maxTokens = route.maxTokens;
160
+ const effort = effortFor(route, agent.session?.events);
161
+ if (effort !== void 0) stamped.reasoningEffort = effort;
119
162
  return stamped;
120
163
  },
121
164
  { prepend: true }
122
165
  );
123
- harness.on("agent/disposed", (disposed) => {
166
+ this.harness.on("agent/disposed", (disposed) => {
124
167
  if (disposed === agent) dispose();
125
168
  });
126
169
  });
127
- if (this.config.promptSection) {
128
- harness.systemPrompt.section({
170
+ this.render(this.config);
171
+ installSettingsSection(
172
+ ctx,
173
+ SETTINGS_NS,
174
+ Config,
175
+ this.config,
176
+ {
177
+ setSource: (current) => {
178
+ this.source = current;
179
+ },
180
+ onChange: () => this.render(this.source())
181
+ }
182
+ );
183
+ }
184
+ /**
185
+ * Register the convention surface (prompt section + skill) for `cfg`,
186
+ * replacing whatever is currently registered. Called on construction and
187
+ * after every committed settings change; while `enabled` is false, nothing
188
+ * stays registered.
189
+ */
190
+ render(cfg) {
191
+ if (this.promptDispose) {
192
+ this.promptDispose();
193
+ this.promptDispose = void 0;
194
+ }
195
+ if (this.skillDispose) {
196
+ this.skillDispose();
197
+ this.skillDispose = void 0;
198
+ }
199
+ if (!cfg.enabled) return;
200
+ if (cfg.promptSection) {
201
+ this.promptDispose = this.harness.systemPrompt.section({
129
202
  name: ROW_ID,
130
203
  order: SECTION_ORDER,
131
- text: SECTION_TEXT.replaceAll("{PLANNER_MODEL}", this.config.planner.model).replaceAll(
204
+ text: SECTION_TEXT.replaceAll("{PLANNER_MODEL}", cfg.planner.model).replaceAll(
132
205
  "{EXECUTOR_MODEL}",
133
- this.config.executor.model
206
+ cfg.executor.model
134
207
  )
135
208
  });
136
209
  }
137
- if (this.config.skill) {
138
- harness.skills.register({
210
+ if (cfg.skill) {
211
+ this.skillDispose = this.harness.skills.register({
139
212
  name: SKILL_NAME,
140
213
  description: SKILL_DESCRIPTION,
141
214
  whenToUse: SKILL_WHEN_TO_USE,
@@ -149,12 +222,15 @@ export {
149
222
  Config,
150
223
  ModelRouter,
151
224
  ROW_ID,
225
+ SETTINGS_NS,
152
226
  SKILL_CONTENT,
153
227
  SKILL_DESCRIPTION,
154
228
  SKILL_NAME,
155
229
  SKILL_WHEN_TO_USE,
156
230
  ModelRouter as default,
231
+ effortFor,
157
232
  name,
233
+ recentStepsHadError,
158
234
  roleFor,
159
235
  routeFor
160
236
  };
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/policy.ts"],"sourcesContent":["/**\n * dsh-model-router: role-based model routing for the DeepSeek Harness.\n *\n * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated\n * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent\n * `agent/request` rewrite registered when the agent is created, so it applies\n * in every mode (web / headless / tui) and every agent preset, including\n * subagents the delegation tools create.\n *\n * Each role route may also pin `reasoningEffort` and `maxTokens`; when set,\n * they override the session's selection for that role. A `mode` switch lets a\n * deployment reserve the planner route for actual planning.\n *\n * The plugin also publishes:\n * - a system-prompt section stating the planner/executor convention, and\n * - the `pro-flash-routing` skill teaching the agent to plan itself and\n * delegate code execution to flash subagents.\n *\n * @module dsh-model-router\n */\nimport { Context, Service } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { foldPlanMode } from \"@deepseek-ai/dsh-plan-mode\";\nimport { roleFor, routeFor, type RouterConfig } from \"./policy.js\";\n\n/** Plugin row id; the bundle patch inserts it under this id. */\nconst name = \"model-router\";\n\n/** One provider/model pair, with defaults and optional effort/token caps. */\nconst ModelRouteSchema = z.object({\n provider: z.string().min(1),\n model: z.string().min(1),\n reasoningEffort: z.union([\"off\", \"low\", \"high\", \"max\"]),\n maxTokens: z.number().min(1),\n});\n\n/** The plugin's public config, validated at row load. */\nconst Config = z.object({\n planner: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-pro\",\n } as never),\n executor: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-flash\",\n } as never),\n mode: z.union([\"strict\", \"plan\"]).default(\"strict\"),\n promptSection: z.boolean().default(true),\n skill: z.boolean().default(true),\n});\n\n/**\n * Resolve raw row config into the internal shape, failing loud on garbage.\n * Schemastery schemas are callable: invoking validates and applies defaults.\n * @param raw - the row's config object.\n * @returns the validated RouterConfig.\n */\nfunction resolveConfig(raw: unknown): RouterConfig {\n const parsed = Config(raw ?? {});\n return {\n planner: parsed.planner,\n executor: parsed.executor,\n mode: parsed.mode,\n promptSection: parsed.promptSection,\n skill: parsed.skill,\n };\n}\n\n/**\n * Always-on guidance section. Negative order renders before the persona, so\n * the convention is established before the agent's identity line.\n */\nconst SECTION_ORDER = -50;\n\nconst SECTION_TEXT = `Model routing is role-based. Planning runs on {PLANNER_MODEL}; implementation runs on {EXECUTOR_MODEL}. You are the root agent: plan, design, review subagent output, and write the final answer here. Delegate implementation — writing code, running commands, builds, tests — to subagents with complete, self-contained prompts, preferring background delegation for independent work. Keep plans and replies concise. Do not hand-write large amounts of code or run long executions here; delegate instead.`;\n\nconst SKILL_NAME = \"pro-flash-routing\";\n\nconst SKILL_DESCRIPTION =\n \"Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.\";\n\nconst SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;\n\nconst SKILL_CONTENT = `# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** — \\`deepseek-v4-pro\\`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** — \\`deepseek-v4-flash\\`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with \\`exit_plan_mode\\`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to \\`deepseek-v4-flash\\`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Keep this agent's context lean\n\nInput tokens are the expensive part of the planner. Don't re-read large files or full transcripts on this agent — trust the subagent's final report. Prefer targeted reads (offset/limit) over whole files. When the context grows, compact rather than re-sending everything.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer \\`subagent\\` for self-contained work and \\`workflow\\` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by \\`deepseek-v4-flash\\`; planner output by \\`deepseek-v4-pro\\`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the \\`model-router\\` plugin row in the profile composition is the single place that owns it.`;\n\n/** The plugin row id the bundle patch must insert. */\nconst ROW_ID = \"model-router\";\n\n/** Minimal structural view of the live agent object the router reads. */\ninterface AgentLike {\n ctx: AgentScopedContext;\n options?: { subagentDepth?: number };\n session?: { header?: { origin?: string }; events?: unknown[] };\n}\n\n/** The agent-scoped context's waterfall surface the router uses. */\ninterface AgentScopedContext {\n on(\n event: \"agent/request\",\n listener: (\n payload: Record<string, unknown>,\n next: () => Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n options?: { prepend?: boolean },\n ): () => void;\n}\n\n/** Host-plane surface the router consumes (events, prompt registry, skills). */\ninterface HarnessContext {\n on(\n event: \"agent/created\",\n listener: (payload: { agent: AgentLike }) => void,\n ): () => void;\n on(event: \"agent/disposed\", listener: (agent: unknown) => void): () => void;\n systemPrompt: {\n section(section: { name: string; order: number; text: string }): unknown;\n };\n skills: {\n register(skill: {\n name: string;\n description: string;\n whenToUse?: string;\n content: string;\n source: string;\n }): unknown;\n };\n}\n\n/** Fold plan-mode state for an agent without trusting the agent's exact shape. */\nfunction isPlanModeActive(agent: AgentLike): boolean {\n const events = agent.session?.events;\n if (!Array.isArray(events)) return false;\n try {\n return foldPlanMode(events as Parameters<typeof foldPlanMode>[0]);\n } catch {\n return false;\n }\n}\n\n/**\n * Cordis service: per-agent request routing plus the convention surface.\n */\nclass ModelRouter extends Service {\n static inject = [\"skills\", \"systemPrompt\"];\n\n config: RouterConfig;\n\n constructor(ctx: Context, rawConfig: unknown = {}) {\n super(ctx, \"modelRouter\");\n this.config = resolveConfig(rawConfig);\n const harness = ctx as unknown as HarnessContext;\n\n // Every agent that gets created — root sessions, delegation children,\n // workflow workers, ralph rounds — passes through here.\n harness.on(\"agent/created\", ({ agent }) => {\n // `prepend` puts this listener OUTERMOST in the `agent/request`\n // waterfall: the harness's model-selection listener runs inside it, so\n // this rewrite is applied LAST and wins over the session's selected\n // model (which dsh-base defaults to deepseek-v4-flash and the user\n // settings or UI can change).\n const dispose = agent.ctx.on(\n \"agent/request\",\n async (payload, next) => {\n const resolved = await next();\n const route = routeFor(agent, this.config, isPlanModeActive(agent));\n if (route === undefined) return resolved;\n const stamped: Record<string, unknown> = {\n ...resolved,\n provider: route.provider,\n model: route.model,\n };\n if (route.reasoningEffort !== undefined) stamped.reasoningEffort = route.reasoningEffort;\n if (route.maxTokens !== undefined) stamped.maxTokens = route.maxTokens;\n return stamped;\n },\n { prepend: true },\n );\n harness.on(\"agent/disposed\", (disposed) => {\n if (disposed === agent) dispose();\n });\n });\n\n if (this.config.promptSection) {\n harness.systemPrompt.section({\n name: ROW_ID,\n order: SECTION_ORDER,\n text: SECTION_TEXT.replaceAll(\"{PLANNER_MODEL}\", this.config.planner.model).replaceAll(\n \"{EXECUTOR_MODEL}\",\n this.config.executor.model,\n ),\n });\n }\n\n if (this.config.skill) {\n harness.skills.register({\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: SKILL_WHEN_TO_USE,\n content: SKILL_CONTENT,\n source: \"runtime\",\n });\n }\n }\n}\n\nexport {\n Config,\n ModelRouter,\n ModelRouter as default,\n name,\n ROW_ID,\n SKILL_CONTENT,\n SKILL_DESCRIPTION,\n SKILL_NAME,\n SKILL_WHEN_TO_USE,\n roleFor,\n routeFor,\n};\nexport type {\n AgentRole,\n ModelRoute,\n ReasoningEffort,\n RoutingMode,\n RouterConfig,\n} from \"./policy.js\";\n","/**\n * Pure routing policy for dsh-model-router: which model each agent role gets.\n * Kept free of Cordis imports so the policy is trivially unit-testable.\n * @module dsh-model-router/policy\n */\n\n/** Reasoning-effort levels a route may pin (mirrors the harness vocabulary). */\nexport type ReasoningEffort = \"off\" | \"low\" | \"high\" | \"max\";\n\n/**\n * How the router treats the root agent.\n * - `strict`: the root agent is always the planner (pro).\n * - `plan`: the root agent is pro only while plan mode is active; otherwise it\n * falls back to the executor route, reserving pro for real planning.\n */\nexport type RoutingMode = \"strict\" | \"plan\";\n\n/** One route: a provider/model pair stamped onto an agent request. */\nexport interface ModelRoute {\n provider: string;\n model: string;\n /**\n * Optional reasoning-effort override. When omitted, the request inherits the\n * session's own selection; when set, the router pins it for that role.\n */\n reasoningEffort?: ReasoningEffort;\n /** Optional output-token cap for the role; omitted means inherit. */\n maxTokens?: number;\n}\n\n/** The two roles the router distinguishes. */\nexport type AgentRole = \"planner\" | \"executor\";\n\n/** Resolved router configuration: one route per role plus routing mode. */\nexport interface RouterConfig {\n planner: ModelRoute;\n executor: ModelRoute;\n mode: RoutingMode;\n promptSection: boolean;\n skill: boolean;\n}\n\n/**\n * Classify an agent as planner or executor.\n *\n * The main (root) agent of a session is the planner. Every agent created as a\n * delegation child — `subagent`, `subagent_fork`, workflow workers, ralph\n * rounds — is an executor. The harness stamps two durable facts on children:\n * `options.subagentDepth` (>= 1) and the session header `origin: \"subagent\"`.\n *\n * @param agent - the live agent (any subset of the runtime shape).\n * @returns the role the agent should be routed as.\n */\nexport function roleFor(agent: unknown): AgentRole {\n const options = (agent as { options?: unknown })?.options;\n const depth = (options as { subagentDepth?: unknown })?.subagentDepth;\n if (typeof depth === \"number\" && depth >= 1) return \"executor\";\n const session = (agent as { session?: unknown })?.session;\n const origin = (session as { header?: unknown })?.header\n ? ((session as { header: { origin?: unknown } }).header.origin)\n : undefined;\n if (origin === \"subagent\") return \"executor\";\n return \"planner\";\n}\n\n/**\n * Resolve the route for one agent.\n * @param agent - the live agent.\n * @param config - the resolved router configuration.\n * @param planModeActive - whether plan mode is currently folded active for the\n * agent's session; consulted only in `plan` routing mode.\n * @returns the model route to stamp, or `undefined` to leave the request alone.\n */\nexport function routeFor(\n agent: unknown,\n config: RouterConfig,\n planModeActive = false,\n): ModelRoute | undefined {\n const role = roleFor(agent);\n if (role === \"executor\") return config.executor;\n // Root agent. In `plan` mode, reserve the planner route for actual planning;\n // otherwise the root falls back to the executor route.\n if (config.mode === \"plan\" && !planModeActive) return config.executor;\n return config.planner;\n}\n"],"mappings":";AAoBA,SAAkB,eAAe;AACjC,OAAO,OAAO;AACd,SAAS,oBAAoB;;;AC+BtB,SAAS,QAAQ,OAA2B;AACjD,QAAM,UAAW,OAAiC;AAClD,QAAM,QAAS,SAAyC;AACxD,MAAI,OAAO,UAAU,YAAY,SAAS,EAAG,QAAO;AACpD,QAAM,UAAW,OAAiC;AAClD,QAAM,SAAU,SAAkC,SAC5C,QAA6C,OAAO,SACtD;AACJ,MAAI,WAAW,WAAY,QAAO;AAClC,SAAO;AACT;AAUO,SAAS,SACd,OACA,QACA,iBAAiB,OACO;AACxB,QAAM,OAAO,QAAQ,KAAK;AAC1B,MAAI,SAAS,WAAY,QAAO,OAAO;AAGvC,MAAI,OAAO,SAAS,UAAU,CAAC,eAAgB,QAAO,OAAO;AAC7D,SAAO,OAAO;AAChB;;;AD1DA,IAAM,OAAO;AAGb,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,iBAAiB,EAAE,MAAM,CAAC,OAAO,OAAO,QAAQ,KAAK,CAAC;AAAA,EACtD,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAC7B,CAAC;AAGD,IAAM,SAAS,EAAE,OAAO;AAAA,EACtB,SAAS,iBAAiB,QAAQ;AAAA,IAChC,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAU;AAAA,EACV,UAAU,iBAAiB,QAAQ;AAAA,IACjC,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAU;AAAA,EACV,MAAM,EAAE,MAAM,CAAC,UAAU,MAAM,CAAC,EAAE,QAAQ,QAAQ;AAAA,EAClD,eAAe,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EACvC,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAAI;AACjC,CAAC;AAQD,SAAS,cAAc,KAA4B;AACjD,QAAM,SAAS,OAAO,OAAO,CAAC,CAAC;AAC/B,SAAO;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA,IACjB,MAAM,OAAO;AAAA,IACb,eAAe,OAAO;AAAA,IACtB,OAAO,OAAO;AAAA,EAChB;AACF;AAMA,IAAM,gBAAgB;AAEtB,IAAM,eAAe;AAErB,IAAM,aAAa;AAEnB,IAAM,oBACJ;AAEF,IAAM,oBAAoB;AAE1B,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BtB,IAAM,SAAS;AA2Cf,SAAS,iBAAiB,OAA2B;AACnD,QAAM,SAAS,MAAM,SAAS;AAC9B,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,MAAI;AACF,WAAO,aAAa,MAA4C;AAAA,EAClE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,IAAM,cAAN,cAA0B,QAAQ;AAAA,EAChC,OAAO,SAAS,CAAC,UAAU,cAAc;AAAA,EAEzC;AAAA,EAEA,YAAY,KAAc,YAAqB,CAAC,GAAG;AACjD,UAAM,KAAK,aAAa;AACxB,SAAK,SAAS,cAAc,SAAS;AACrC,UAAM,UAAU;AAIhB,YAAQ,GAAG,iBAAiB,CAAC,EAAE,MAAM,MAAM;AAMzC,YAAM,UAAU,MAAM,IAAI;AAAA,QACxB;AAAA,QACA,OAAO,SAAS,SAAS;AACvB,gBAAM,WAAW,MAAM,KAAK;AAC5B,gBAAM,QAAQ,SAAS,OAAO,KAAK,QAAQ,iBAAiB,KAAK,CAAC;AAClE,cAAI,UAAU,OAAW,QAAO;AAChC,gBAAM,UAAmC;AAAA,YACvC,GAAG;AAAA,YACH,UAAU,MAAM;AAAA,YAChB,OAAO,MAAM;AAAA,UACf;AACA,cAAI,MAAM,oBAAoB,OAAW,SAAQ,kBAAkB,MAAM;AACzE,cAAI,MAAM,cAAc,OAAW,SAAQ,YAAY,MAAM;AAC7D,iBAAO;AAAA,QACT;AAAA,QACA,EAAE,SAAS,KAAK;AAAA,MAClB;AACA,cAAQ,GAAG,kBAAkB,CAAC,aAAa;AACzC,YAAI,aAAa,MAAO,SAAQ;AAAA,MAClC,CAAC;AAAA,IACH,CAAC;AAED,QAAI,KAAK,OAAO,eAAe;AAC7B,cAAQ,aAAa,QAAQ;AAAA,QAC3B,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,aAAa,WAAW,mBAAmB,KAAK,OAAO,QAAQ,KAAK,EAAE;AAAA,UAC1E;AAAA,UACA,KAAK,OAAO,SAAS;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,OAAO,OAAO;AACrB,cAAQ,OAAO,SAAS;AAAA,QACtB,MAAM;AAAA,QACN,aAAa;AAAA,QACb,WAAW;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/policy.ts"],"sourcesContent":["/**\n * dsh-model-router: role-based model routing for the DeepSeek Harness.\n *\n * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated\n * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent\n * `agent/request` rewrite registered when the agent is created, so it applies\n * in every mode (web / headless / tui) and every agent preset, including\n * subagents the delegation tools create.\n *\n * Each role route may also pin `reasoningEffort` and `maxTokens`; when set,\n * they override the session's selection for that role. A `mode` switch lets a\n * deployment reserve the planner route for actual planning.\n *\n * The plugin also publishes:\n * - a system-prompt section stating the planner/executor convention, and\n * - the `pro-flash-routing` skill teaching the agent to plan itself and\n * delegate code execution to flash subagents.\n *\n * @module dsh-model-router\n */\nimport { Context, Service } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { foldPlanMode } from \"@deepseek-ai/dsh-plan-mode\";\nimport { installSettingsSection, settingsNamespace } from \"@deepseek-ai/dsh-settings\";\nimport { effortFor, recentStepsHadError, roleFor, routeFor, type RouterConfig } from \"./policy.js\";\n\n/** Plugin row id; the bundle patch inserts it under this id. */\nconst name = \"model-router\";\n\n/** One provider/model pair, with defaults and optional effort/token caps. */\nconst ModelRouteSchema = z.object({\n provider: z.string().min(1),\n model: z.string().min(1),\n reasoningEffort: z.union([\"off\", \"low\", \"high\", \"max\"]),\n maxTokens: z.number().min(1),\n escalateOnError: z.boolean(),\n escalateTo: z.union([\"off\", \"low\", \"high\", \"max\"]),\n recoverySteps: z.number().min(1),\n});\n\n/** The plugin's public config, validated at row load. */\nconst Config = z.object({\n planner: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-pro\",\n } as never),\n executor: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-flash\",\n } as never),\n mode: z.union([\"strict\", \"plan\"]).default(\"strict\"),\n enabled: z.boolean().default(true),\n promptSection: z.boolean().default(true),\n skill: z.boolean().default(true),\n});\n\n/** Settings namespace the live on/off toggle lives under (settings.yaml). */\nconst SETTINGS_NS = settingsNamespace(\"model-router\");\n\n/**\n * Resolve raw row config into the internal shape, failing loud on garbage.\n * Schemastery schemas are callable: invoking validates and applies defaults.\n * @param raw - the row's config object.\n * @returns the validated RouterConfig.\n */\nfunction resolveConfig(raw: unknown): RouterConfig {\n const parsed = Config(raw ?? {});\n return {\n planner: parsed.planner,\n executor: parsed.executor,\n mode: parsed.mode,\n enabled: parsed.enabled,\n promptSection: parsed.promptSection,\n skill: parsed.skill,\n };\n}\n\n/**\n * Always-on guidance section. Negative order renders before the persona, so\n * the convention is established before the agent's identity line.\n */\nconst SECTION_ORDER = -50;\n\nconst SECTION_TEXT = `Model routing is role-based. Planning runs on {PLANNER_MODEL}; implementation runs on {EXECUTOR_MODEL}. You are the root agent: plan, design, review subagent output, and write the final answer here. Delegate implementation — writing code, running commands, builds, tests — to subagents with complete, self-contained prompts, preferring background delegation for independent work. Keep plans and replies concise. Do not hand-write large amounts of code or run long executions here; delegate instead.`;\n\nconst SKILL_NAME = \"pro-flash-routing\";\n\nconst SKILL_DESCRIPTION =\n \"Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.\";\n\nconst SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;\n\nconst SKILL_CONTENT = `# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** — \\`deepseek-v4-pro\\`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** — \\`deepseek-v4-flash\\`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with \\`exit_plan_mode\\`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to \\`deepseek-v4-flash\\`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Keep this agent's context lean\n\nInput tokens are the expensive part of the planner. Don't re-read large files or full transcripts on this agent — trust the subagent's final report. Prefer targeted reads (offset/limit) over whole files. When the context grows, compact rather than re-sending everything.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer \\`subagent\\` for self-contained work and \\`workflow\\` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by \\`deepseek-v4-flash\\`; planner output by \\`deepseek-v4-pro\\`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the \\`model-router\\` plugin row in the profile composition is the single place that owns it.`;\n\n/** The plugin row id the bundle patch must insert. */\nconst ROW_ID = \"model-router\";\n\n/** Minimal structural view of the live agent object the router reads. */\ninterface AgentLike {\n ctx: AgentScopedContext;\n options?: { subagentDepth?: number };\n session?: { header?: { origin?: string }; events?: unknown[] };\n}\n\n/** The agent-scoped context's waterfall surface the router uses. */\ninterface AgentScopedContext {\n on(\n event: \"agent/request\",\n listener: (\n payload: Record<string, unknown>,\n next: () => Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n options?: { prepend?: boolean },\n ): () => void;\n}\n\n/** Host-plane surface the router consumes (events, prompt registry, skills). */\ninterface HarnessContext {\n on(\n event: \"agent/created\",\n listener: (payload: { agent: AgentLike }) => void,\n ): () => void;\n on(event: \"agent/disposed\", listener: (agent: unknown) => void): () => void;\n systemPrompt: {\n section(section: { name: string; order: number; text: string }): () => void;\n };\n skills: {\n register(skill: {\n name: string;\n description: string;\n whenToUse?: string;\n content: string;\n source: string;\n }): () => void;\n };\n}\n\n/** Fold plan-mode state for an agent without trusting the agent's exact shape. */\nfunction isPlanModeActive(agent: AgentLike): boolean {\n const events = agent.session?.events;\n if (!Array.isArray(events)) return false;\n try {\n return foldPlanMode(events as Parameters<typeof foldPlanMode>[0]);\n } catch {\n return false;\n }\n}\n\n/**\n * Cordis service: per-agent request routing plus the convention surface.\n */\nclass ModelRouter extends Service {\n static inject = [\"skills\", \"systemPrompt\"];\n\n config: RouterConfig;\n\n /** Currently authoritative config; swapped by the settings section when one is mounted. */\n source: () => RouterConfig;\n\n /** Host-plane surface the router consumes (events, prompt registry, skills). */\n harness: HarnessContext;\n\n /** Disposer of the currently registered prompt section, if any. */\n promptDispose?: () => void;\n\n /** Disposer of the currently registered skill, if any. */\n skillDispose?: () => void;\n\n constructor(ctx: Context, rawConfig: unknown = {}) {\n super(ctx, \"modelRouter\");\n this.config = resolveConfig(rawConfig);\n this.source = () => this.config;\n this.harness = ctx as unknown as HarnessContext;\n\n // Every agent that gets created — root sessions, delegation children,\n // workflow workers, ralph rounds — passes through here.\n this.harness.on(\"agent/created\", ({ agent }) => {\n // `prepend` puts this listener OUTERMOST in the `agent/request`\n // waterfall: the harness's model-selection listener runs inside it, so\n // this rewrite is applied LAST and wins over the session's selected\n // model (which dsh-base defaults to deepseek-v4-flash and the user\n // settings or UI can change).\n const dispose = agent.ctx.on(\n \"agent/request\",\n async (payload, next) => {\n const resolved = await next();\n const cfg = this.source();\n if (!cfg.enabled) return resolved;\n const route = routeFor(agent, cfg, isPlanModeActive(agent));\n if (route === undefined) return resolved;\n const stamped: Record<string, unknown> = {\n ...resolved,\n provider: route.provider,\n model: route.model,\n };\n if (route.maxTokens !== undefined) stamped.maxTokens = route.maxTokens;\n const effort = effortFor(route, agent.session?.events);\n if (effort !== undefined) stamped.reasoningEffort = effort;\n return stamped;\n },\n { prepend: true },\n );\n this.harness.on(\"agent/disposed\", (disposed) => {\n if (disposed === agent) dispose();\n });\n });\n\n // Register the convention surface from the composition config, then let\n // the settings section (when mounted) take over as the live source.\n this.render(this.config);\n installSettingsSection(\n ctx,\n SETTINGS_NS,\n Config,\n this.config as unknown as ReturnType<typeof Config>,\n {\n setSource: (current) => {\n this.source = current;\n },\n onChange: () => this.render(this.source()),\n },\n );\n }\n\n /**\n * Register the convention surface (prompt section + skill) for `cfg`,\n * replacing whatever is currently registered. Called on construction and\n * after every committed settings change; while `enabled` is false, nothing\n * stays registered.\n */\n private render(cfg: RouterConfig): void {\n if (this.promptDispose) {\n this.promptDispose();\n this.promptDispose = undefined;\n }\n if (this.skillDispose) {\n this.skillDispose();\n this.skillDispose = undefined;\n }\n if (!cfg.enabled) return;\n if (cfg.promptSection) {\n this.promptDispose = this.harness.systemPrompt.section({\n name: ROW_ID,\n order: SECTION_ORDER,\n text: SECTION_TEXT.replaceAll(\"{PLANNER_MODEL}\", cfg.planner.model).replaceAll(\n \"{EXECUTOR_MODEL}\",\n cfg.executor.model,\n ),\n });\n }\n if (cfg.skill) {\n this.skillDispose = this.harness.skills.register({\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: SKILL_WHEN_TO_USE,\n content: SKILL_CONTENT,\n source: \"runtime\",\n });\n }\n }\n}\n\nexport {\n Config,\n ModelRouter,\n ModelRouter as default,\n SETTINGS_NS,\n name,\n ROW_ID,\n SKILL_CONTENT,\n SKILL_DESCRIPTION,\n SKILL_NAME,\n SKILL_WHEN_TO_USE,\n effortFor,\n recentStepsHadError,\n roleFor,\n routeFor,\n};\nexport type {\n AgentRole,\n ModelRoute,\n ReasoningEffort,\n RoutingMode,\n RouterConfig,\n} from \"./policy.js\";\n","/**\n * Pure routing policy for dsh-model-router: which model each agent role gets.\n * Kept free of Cordis imports so the policy is trivially unit-testable.\n * @module dsh-model-router/policy\n */\n\n/** Reasoning-effort levels a route may pin (mirrors the harness vocabulary). */\nexport type ReasoningEffort = \"off\" | \"low\" | \"high\" | \"max\";\n\n/**\n * How the router treats the root agent.\n * - `strict`: the root agent is always the planner (pro).\n * - `plan`: the root agent is pro only while plan mode is active; otherwise it\n * falls back to the executor route, reserving pro for real planning.\n */\nexport type RoutingMode = \"strict\" | \"plan\";\n\n/** One route: a provider/model pair stamped onto an agent request. */\nexport interface ModelRoute {\n provider: string;\n model: string;\n /**\n * Optional reasoning-effort override. When omitted, the request inherits the\n * session's own selection; when set, the router pins it for that role.\n */\n reasoningEffort?: ReasoningEffort;\n /** Optional output-token cap for the role; omitted means inherit. */\n maxTokens?: number;\n /**\n * Error-driven escalation (v1): when true, a failed execution step bumps the\n * next request's effort to `escalateTo`, wearing off after `recoverySteps`\n * clean steps. Deterministic, stateless — the session log is folded per\n * request, so only *prior* steps are ever considered.\n */\n escalateOnError?: boolean;\n /** Effort used for the request after a failed step. */\n escalateTo?: ReasoningEffort;\n /** Clean steps before escalation wears off. Defaults to 2. */\n recoverySteps?: number;\n}\n\n/** The two roles the router distinguishes. */\nexport type AgentRole = \"planner\" | \"executor\";\n\n/** Resolved router configuration: one route per role plus routing mode. */\nexport interface RouterConfig {\n planner: ModelRoute;\n executor: ModelRoute;\n mode: RoutingMode;\n /**\n * Live off-switch. Defaults to true; settable from Settings → Plugins →\n * model-router (applies immediately) or from the patch row (next boot).\n * When false, the router stops rewriting requests and unregisters the\n * prompt section and the skill.\n */\n enabled: boolean;\n promptSection: boolean;\n skill: boolean;\n}\n\n/** Default recovery window: an error escalates for the next two completed steps. */\nexport const DEFAULT_RECOVERY_STEPS = 2;\n\n/**\n * Classify an agent as planner or executor.\n *\n * The main (root) agent of a session is the planner. Every agent created as a\n * delegation child — `subagent`, `subagent_fork`, workflow workers, ralph\n * rounds — is an executor. The harness stamps two durable facts on children:\n * `options.subagentDepth` (>= 1) and the session header `origin: \"subagent\"`.\n *\n * @param agent - the live agent (any subset of the runtime shape).\n * @returns the role the agent should be routed as.\n */\nexport function roleFor(agent: unknown): AgentRole {\n const options = (agent as { options?: unknown })?.options;\n const depth = (options as { subagentDepth?: unknown })?.subagentDepth;\n if (typeof depth === \"number\" && depth >= 1) return \"executor\";\n const session = (agent as { session?: unknown })?.session;\n const origin = (session as { header?: unknown })?.header\n ? ((session as { header: { origin?: unknown } }).header.origin)\n : undefined;\n if (origin === \"subagent\") return \"executor\";\n return \"planner\";\n}\n\n/**\n * Resolve the route for one agent.\n * @param agent - the live agent.\n * @param config - the resolved router configuration.\n * @param planModeActive - whether plan mode is currently folded active for the\n * agent's session; consulted only in `plan` routing mode.\n * @returns the model route to stamp, or `undefined` to leave the request alone.\n */\nexport function routeFor(\n agent: unknown,\n config: RouterConfig,\n planModeActive = false,\n): ModelRoute | undefined {\n const role = roleFor(agent);\n if (role === \"executor\") return config.executor;\n // Root agent. In `plan` mode, reserve the planner route for actual planning;\n // otherwise the root falls back to the executor route.\n if (config.mode === \"plan\" && !planModeActive) return config.executor;\n return config.planner;\n}\n\n/**\n * Whether any of the last `recoverySteps` completed steps carried a failed\n * tool result. A failure is a `tool/result` event whose data carries an\n * `error` field (the harness records tool failures there).\n *\n * Steps are deduplicated by `turn:step`, and only *completed* steps count —\n * events are scanned from the tail, so the current in-flight request is never\n * considered.\n *\n * @param events - the agent's session event log (or `undefined`).\n * @param recoverySteps - how many completed steps back to scan.\n * @returns true when a failed step is within the window.\n */\nexport function recentStepsHadError(\n events: readonly unknown[] | undefined,\n recoverySteps: number = DEFAULT_RECOVERY_STEPS,\n): boolean {\n if (!Array.isArray(events) || recoverySteps <= 0) return false;\n const seen = new Set<string>();\n let steps = 0;\n for (let i = events.length - 1; i >= 0; i -= 1) {\n const event = events[i] as\n | { type?: string; data?: { turn?: number; step?: number; error?: unknown } }\n | undefined;\n if (event?.type !== \"tool/result\" || event.data === undefined) continue;\n const key = `${event.data.turn}:${event.data.step}`;\n if (!seen.has(key)) {\n // A new step beyond the recovery window ends the scan; events for steps\n // already inside the window are still checked below.\n if (steps >= recoverySteps) break;\n seen.add(key);\n steps += 1;\n }\n if (event.data.error !== undefined && event.data.error !== null) return true;\n }\n return false;\n}\n\n/**\n * Resolve the reasoning effort to stamp for one request.\n *\n * Baseline is the route's `reasoningEffort`; when `escalateOnError` is enabled\n * and a recent step failed, the effort bumps to `escalateTo` (falling back to\n * the baseline when `escalateTo` is unset). Returns `undefined` to leave the\n * request's effort alone (inherit the session selection).\n *\n * @param route - the resolved route for the agent.\n * @param events - the agent's session event log.\n * @returns the effort to stamp, or `undefined` to inherit.\n */\nexport function effortFor(\n route: ModelRoute,\n events: readonly unknown[] | undefined,\n): ReasoningEffort | undefined {\n if (route.escalateOnError === true && recentStepsHadError(events, route.recoverySteps)) {\n return route.escalateTo ?? route.reasoningEffort;\n }\n return route.reasoningEffort;\n}\n"],"mappings":";AAoBA,SAAkB,eAAe;AACjC,OAAO,OAAO;AACd,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB,yBAAyB;;;ACsCnD,IAAM,yBAAyB;AAa/B,SAAS,QAAQ,OAA2B;AACjD,QAAM,UAAW,OAAiC;AAClD,QAAM,QAAS,SAAyC;AACxD,MAAI,OAAO,UAAU,YAAY,SAAS,EAAG,QAAO;AACpD,QAAM,UAAW,OAAiC;AAClD,QAAM,SAAU,SAAkC,SAC5C,QAA6C,OAAO,SACtD;AACJ,MAAI,WAAW,WAAY,QAAO;AAClC,SAAO;AACT;AAUO,SAAS,SACd,OACA,QACA,iBAAiB,OACO;AACxB,QAAM,OAAO,QAAQ,KAAK;AAC1B,MAAI,SAAS,WAAY,QAAO,OAAO;AAGvC,MAAI,OAAO,SAAS,UAAU,CAAC,eAAgB,QAAO,OAAO;AAC7D,SAAO,OAAO;AAChB;AAeO,SAAS,oBACd,QACA,gBAAwB,wBACf;AACT,MAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,iBAAiB,EAAG,QAAO;AACzD,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,QAAQ;AACZ,WAAS,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;AAC9C,UAAM,QAAQ,OAAO,CAAC;AAGtB,QAAI,OAAO,SAAS,iBAAiB,MAAM,SAAS,OAAW;AAC/D,UAAM,MAAM,GAAG,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;AACjD,QAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAGlB,UAAI,SAAS,cAAe;AAC5B,WAAK,IAAI,GAAG;AACZ,eAAS;AAAA,IACX;AACA,QAAI,MAAM,KAAK,UAAU,UAAa,MAAM,KAAK,UAAU,KAAM,QAAO;AAAA,EAC1E;AACA,SAAO;AACT;AAcO,SAAS,UACd,OACA,QAC6B;AAC7B,MAAI,MAAM,oBAAoB,QAAQ,oBAAoB,QAAQ,MAAM,aAAa,GAAG;AACtF,WAAO,MAAM,cAAc,MAAM;AAAA,EACnC;AACA,SAAO,MAAM;AACf;;;AD1IA,IAAM,OAAO;AAGb,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,iBAAiB,EAAE,MAAM,CAAC,OAAO,OAAO,QAAQ,KAAK,CAAC;AAAA,EACtD,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC3B,iBAAiB,EAAE,QAAQ;AAAA,EAC3B,YAAY,EAAE,MAAM,CAAC,OAAO,OAAO,QAAQ,KAAK,CAAC;AAAA,EACjD,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC;AACjC,CAAC;AAGD,IAAM,SAAS,EAAE,OAAO;AAAA,EACtB,SAAS,iBAAiB,QAAQ;AAAA,IAChC,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAU;AAAA,EACV,UAAU,iBAAiB,QAAQ;AAAA,IACjC,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAU;AAAA,EACV,MAAM,EAAE,MAAM,CAAC,UAAU,MAAM,CAAC,EAAE,QAAQ,QAAQ;AAAA,EAClD,SAAS,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EACjC,eAAe,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EACvC,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAAI;AACjC,CAAC;AAGD,IAAM,cAAc,kBAAkB,cAAc;AAQpD,SAAS,cAAc,KAA4B;AACjD,QAAM,SAAS,OAAO,OAAO,CAAC,CAAC;AAC/B,SAAO;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA,IACjB,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,IAChB,eAAe,OAAO;AAAA,IACtB,OAAO,OAAO;AAAA,EAChB;AACF;AAMA,IAAM,gBAAgB;AAEtB,IAAM,eAAe;AAErB,IAAM,aAAa;AAEnB,IAAM,oBACJ;AAEF,IAAM,oBAAoB;AAE1B,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BtB,IAAM,SAAS;AA2Cf,SAAS,iBAAiB,OAA2B;AACnD,QAAM,SAAS,MAAM,SAAS;AAC9B,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,MAAI;AACF,WAAO,aAAa,MAA4C;AAAA,EAClE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,IAAM,cAAN,cAA0B,QAAQ;AAAA,EAChC,OAAO,SAAS,CAAC,UAAU,cAAc;AAAA,EAEzC;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA,EAEA,YAAY,KAAc,YAAqB,CAAC,GAAG;AACjD,UAAM,KAAK,aAAa;AACxB,SAAK,SAAS,cAAc,SAAS;AACrC,SAAK,SAAS,MAAM,KAAK;AACzB,SAAK,UAAU;AAIf,SAAK,QAAQ,GAAG,iBAAiB,CAAC,EAAE,MAAM,MAAM;AAM9C,YAAM,UAAU,MAAM,IAAI;AAAA,QACxB;AAAA,QACA,OAAO,SAAS,SAAS;AACvB,gBAAM,WAAW,MAAM,KAAK;AAC5B,gBAAM,MAAM,KAAK,OAAO;AACxB,cAAI,CAAC,IAAI,QAAS,QAAO;AACzB,gBAAM,QAAQ,SAAS,OAAO,KAAK,iBAAiB,KAAK,CAAC;AAC1D,cAAI,UAAU,OAAW,QAAO;AAChC,gBAAM,UAAmC;AAAA,YACvC,GAAG;AAAA,YACH,UAAU,MAAM;AAAA,YAChB,OAAO,MAAM;AAAA,UACf;AACA,cAAI,MAAM,cAAc,OAAW,SAAQ,YAAY,MAAM;AAC7D,gBAAM,SAAS,UAAU,OAAO,MAAM,SAAS,MAAM;AACrD,cAAI,WAAW,OAAW,SAAQ,kBAAkB;AACpD,iBAAO;AAAA,QACT;AAAA,QACA,EAAE,SAAS,KAAK;AAAA,MAClB;AACA,WAAK,QAAQ,GAAG,kBAAkB,CAAC,aAAa;AAC9C,YAAI,aAAa,MAAO,SAAQ;AAAA,MAClC,CAAC;AAAA,IACH,CAAC;AAID,SAAK,OAAO,KAAK,MAAM;AACvB;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL;AAAA,QACE,WAAW,CAAC,YAAY;AACtB,eAAK,SAAS;AAAA,QAChB;AAAA,QACA,UAAU,MAAM,KAAK,OAAO,KAAK,OAAO,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,OAAO,KAAyB;AACtC,QAAI,KAAK,eAAe;AACtB,WAAK,cAAc;AACnB,WAAK,gBAAgB;AAAA,IACvB;AACA,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa;AAClB,WAAK,eAAe;AAAA,IACtB;AACA,QAAI,CAAC,IAAI,QAAS;AAClB,QAAI,IAAI,eAAe;AACrB,WAAK,gBAAgB,KAAK,QAAQ,aAAa,QAAQ;AAAA,QACrD,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,aAAa,WAAW,mBAAmB,IAAI,QAAQ,KAAK,EAAE;AAAA,UAClE;AAAA,UACA,IAAI,SAAS;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH;AACA,QAAI,IAAI,OAAO;AACb,WAAK,eAAe,KAAK,QAAQ,OAAO,SAAS;AAAA,QAC/C,MAAM;AAAA,QACN,aAAa;AAAA,QACb,WAAW;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-model-router",
3
3
  "description": "DeepSeek Harness plugin: role-based model routing — the planner agent runs on deepseek-v4-pro, delegated executor subagents run on deepseek-v4-flash.",
4
- "version": "0.2.0",
4
+ "version": "0.4.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -35,12 +35,14 @@
35
35
  "peerDependencies": {
36
36
  "@deepseek-ai/cordis": "^4.0.1",
37
37
  "@deepseek-ai/schemastery": "^3.18.1",
38
- "@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.8"
38
+ "@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.8",
39
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.8"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@deepseek-ai/cordis": "^4.0.1",
42
43
  "@deepseek-ai/schemastery": "^3.18.1",
43
44
  "@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.8",
45
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
44
46
  "@types/node": "^22.10.0",
45
47
  "tsup": "^8.3.5",
46
48
  "typescript": "^5.7.2",