dsh-model-switch 0.4.32 → 0.4.33

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/lib/client.js CHANGED
@@ -156,7 +156,8 @@ window.__ModuleLoader__.load({
156
156
  imageProviderAdapters: Object.freeze({
157
157
  available: true,
158
158
  providers: Object.freeze(["codex", "grok"])
159
- })
159
+ }),
160
+ autoReview: Object.freeze({ available: true })
160
161
  });
161
162
  //#endregion
162
163
  //#region src/client/search-capabilities.ts
@@ -1802,7 +1803,7 @@ window.__ModuleLoader__.load({
1802
1803
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1803
1804
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1804
1805
  className: ModelSwitchSettings_module_css_default.hint,
1805
- children: props.t("autoReviewTakeoverNote")
1806
+ children: props.t(props.capabilities.autoReview?.available ?? true ? "autoReviewTakeoverNote" : "autoReviewTakeoverWarning")
1806
1807
  }),
1807
1808
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1808
1809
  className: ModelSwitchSettings_module_css_default.hint,
@@ -2289,6 +2290,7 @@ window.__ModuleLoader__.load({
2289
2290
  autoReviewFollow: "跟随会话模型",
2290
2291
  autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。本插件启用时,官方「自动审查」组件由本插件接管。",
2291
2292
  autoReviewRouteHelp: "审查请求固定使用这里的 Provider/模型,与会话模型无关;保存后对下一次审查立即生效。",
2293
+ autoReviewTakeoverWarning: "审查路由当前未接管(官方「自动审查」行仍占着唯一注册位):工具调用会照常执行,但这些调用未经审查。重启 DSH 即可收敛;若重启后仍显示此提示,请把日志里的 fiber 链反馈给上游。",
2292
2294
  autoReviewTakeoverNote: "官方「自动审查」组件已由本插件接管(提供可配置审查路由);在插件管理里把它重新打开会被再次关闭,并在日志里留下一条记录。",
2293
2295
  compaction: "压缩模型",
2294
2296
  compactionFollow: "跟随会话模型",
@@ -2348,6 +2350,7 @@ window.__ModuleLoader__.load({
2348
2350
  autoReviewFollow: "Follows the session model",
2349
2351
  autoReviewHelp: "Pin the reviewer model for the official auto-review gate (per-call review under the Auto permission preset). Requires the official auto-review bundle. When off, reviews follow the session model. With this plugin enabled the official auto-review component is taken over.",
2350
2352
  autoReviewRouteHelp: "Review requests use exactly this provider/model regardless of the session model; saved changes apply to the very next review.",
2353
+ autoReviewTakeoverWarning: "The reviewer route is not held right now (the official Auto-review row still owns the single registration): tool calls keep running, but they are not reviewed. Restart DSH to converge; if this stays after a restart, report the fiber chain from the log upstream.",
2351
2354
  autoReviewTakeoverNote: "The official auto-review component is taken over by this plugin (configurable reviewer route); toggling it back on in the plugin manager is re-disabled, with one log line.",
2352
2355
  compaction: "Compaction model",
2353
2356
  compactionFollow: "Follows the conversation model",
package/lib/compaction.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as sessionProviderOf, r as resolveCapabilityRoute } from "./session-route-BjNPlliu.js";
1
+ import { i as sessionProviderOf, r as resolveCapabilityRoute } from "./session-route-D1zoZgf3.js";
2
2
  //#region lib/types/compaction.js
3
3
  /** Official compaction row this module supersedes. */
4
4
  const OFFICIAL_COMPACTION_ROW = "compaction-basic";
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { n as normalizeProviderGroups } from "./provider-group-CwadImhH.js";
1
+ import { o as normalizeProviderGroups } from "./session-route-D1zoZgf3.js";
2
+ import { i as autoReviewStatus } from "./auto-review-BLtwr83V.js";
2
3
  import { ModelSwitchAdapterRegistry } from "./adapter-registry.js";
3
4
  import { createRequire } from "node:module";
4
5
  import z from "@deepseek-ai/schemastery";
@@ -103,7 +104,8 @@ const RUNTIME_CAPABILITIES = Object.freeze({
103
104
  imageProviderAdapters: Object.freeze({
104
105
  available: true,
105
106
  providers: Object.freeze(["codex", "grok"])
106
- })
107
+ }),
108
+ autoReview: Object.freeze({ available: true })
107
109
  });
108
110
  //#endregion
109
111
  //#region lib/types/search-provider.js
@@ -771,6 +773,10 @@ var ModelSwitchRuntime = class extends Service {
771
773
  available: true,
772
774
  providers: catalog.map((provider) => provider.id),
773
775
  catalog
776
+ },
777
+ autoReview: autoReviewStatus.held ? { available: true } : {
778
+ available: false,
779
+ reason: "auto-review-takeover"
774
780
  }
775
781
  };
776
782
  }
@@ -0,0 +1,258 @@
1
+ //#region lib/types/provider-group.js
2
+ /**
3
+ * Provider configuration groups: per-provider presets for the compaction,
4
+ * auto-review, session-naming, and subagent routes.
5
+ *
6
+ * A group is a DERIVED policy, not a write: `src/session-route.ts` resolves the
7
+ * routes one session should use from its own provider plus this stored table, so
8
+ * switching a session's provider never rewrites deployment-wide settings and two
9
+ * sessions on different providers cannot overwrite each other. The field names
10
+ * below are the flat settings field names a group pins, which keeps the stored
11
+ * shape identical across releases.
12
+ *
13
+ * All functions here are pure and never throw on malformed storage.
14
+ */
15
+ const ROUTE_FIELDS = Object.freeze([
16
+ "compactProvider",
17
+ "compactModel",
18
+ "autoReviewProvider",
19
+ "autoReviewModel",
20
+ "subagentProvider",
21
+ "subagentModel",
22
+ "subagentReasoningEffort",
23
+ "titleProvider",
24
+ "titleModel"
25
+ ]);
26
+ /** Own data property, so a literal '__proto__' provider id can never touch a prototype. */
27
+ function setKey(target, key, value) {
28
+ Object.defineProperty(target, key, {
29
+ value,
30
+ enumerable: true,
31
+ writable: true,
32
+ configurable: true
33
+ });
34
+ }
35
+ /** A non-blank string. Blank values behave exactly like an absent field. */
36
+ function present$1(value) {
37
+ return typeof value === "string" && value.trim() !== "";
38
+ }
39
+ /** Normalize one raw entry to its owning provider and route view; undefined for non-object junk. */
40
+ function normalizeEntry(value) {
41
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
42
+ const source = value;
43
+ const group = {};
44
+ for (const field of ROUTE_FIELDS) {
45
+ const item = source[field];
46
+ if (present$1(item)) group[field] = item;
47
+ }
48
+ return {
49
+ provider: present$1(source.provider) ? source.provider : "",
50
+ group
51
+ };
52
+ }
53
+ /**
54
+ * Strip a raw stored value to well-formed groups; never throws. Both storage
55
+ * shapes are accepted: the stored entry array (keys live in each entry's
56
+ * `provider` field) and a hand-written provider → routes map (keys are the
57
+ * providers). All-empty entries stay: they pin a provider to every default.
58
+ */
59
+ function normalizeProviderGroups(raw) {
60
+ const groups = {};
61
+ if (Array.isArray(raw)) {
62
+ for (const entry of raw) {
63
+ const view = normalizeEntry(entry);
64
+ if (view === void 0 || view.provider === "") continue;
65
+ setKey(groups, view.provider, view.group);
66
+ }
67
+ return groups;
68
+ }
69
+ if (raw !== null && typeof raw === "object") for (const [key, value] of Object.entries(raw)) {
70
+ if (key.trim() === "") continue;
71
+ const view = normalizeEntry(value);
72
+ if (view === void 0) continue;
73
+ setKey(groups, key, view.group);
74
+ }
75
+ return groups;
76
+ }
77
+ /**
78
+ * The saved group for a provider.
79
+ * @returns the normalized group, or undefined when the provider has no group —
80
+ * which is what leaves the live routes untouched during a switch.
81
+ */
82
+ function groupOf(groups, provider) {
83
+ if (groups === void 0 || provider.trim() === "") return void 0;
84
+ if (!Object.prototype.hasOwnProperty.call(groups, provider)) return void 0;
85
+ return groups[provider];
86
+ }
87
+ //#endregion
88
+ //#region lib/types/session-route.js
89
+ /**
90
+ * Per-session capability route derivation.
91
+ *
92
+ * Model Switch stores one deployment-wide route per capability (the flat
93
+ * settings fields) plus, per provider, a saved provider group. Nothing is
94
+ * written when a session changes provider: this module DERIVES the route a
95
+ * capability should use for one session from (a) the session's own provider and
96
+ * (b) the stored group table. Two sessions on different providers therefore
97
+ * resolve independently, and no session's actions rewrite another session's
98
+ * route.
99
+ *
100
+ * The session's provider is read from durable, official state in this order:
101
+ * the `modelSelection` session projection (`pending` before `lastUsed`, so a
102
+ * selection the user just made counts), then the latest logged request-context
103
+ * route, then the latest `request/header` route. Every read is optional: a
104
+ * session with no observable provider, an unregistered projection, or a
105
+ * profile without the projection package all resolve to the deployment default,
106
+ * which is exactly the pre-derivation behavior.
107
+ *
108
+ * All functions are pure except the readers, which never throw and never write.
109
+ */
110
+ /** Flat deployment-default fields per capability, in group-storage field names. */
111
+ const CAPABILITY_FIELDS = Object.freeze({
112
+ compaction: {
113
+ provider: "compactProvider",
114
+ model: "compactModel"
115
+ },
116
+ autoReview: {
117
+ provider: "autoReviewProvider",
118
+ model: "autoReviewModel"
119
+ },
120
+ title: {
121
+ provider: "titleProvider",
122
+ model: "titleModel"
123
+ },
124
+ subagent: {
125
+ provider: "subagentProvider",
126
+ model: "subagentModel"
127
+ }
128
+ });
129
+ /** A non-blank string; blank values behave exactly like an absent field. */
130
+ function present(value) {
131
+ return typeof value === "string" && value.trim() !== "";
132
+ }
133
+ /** Read one capability's pair from a flat route record; undefined unless both halves are present. */
134
+ function pairOf(source, fields) {
135
+ if (source === void 0) return void 0;
136
+ const record = source;
137
+ const provider = record[fields.provider];
138
+ const model = record[fields.model];
139
+ if (!present(provider) || !present(model)) return void 0;
140
+ return {
141
+ provider,
142
+ model
143
+ };
144
+ }
145
+ /**
146
+ * The deployment-wide route for one capability: the flat settings pair.
147
+ * @param settings - live Model Switch settings; undefined means none are reachable.
148
+ * @param capability - capability whose flat pair is read.
149
+ * @returns The complete pair, or undefined when the capability stays at its official/derived default.
150
+ */
151
+ function deploymentRoute(settings, capability) {
152
+ return pairOf(settings, CAPABILITY_FIELDS[capability]);
153
+ }
154
+ /**
155
+ * The route a subagent spawn should use for one parent-session provider.
156
+ *
157
+ * A saved group for that provider pins the spawn; otherwise the flat Subagent
158
+ * card pins it, but only in `fixed` mode — `follow-main` IS the official inherit
159
+ * path, expressed by leaving the request untouched rather than by naming it.
160
+ * @param settings - live Model Switch settings.
161
+ * @param provider - the delegating session's provider, when one is observable.
162
+ * @returns The complete route, or undefined for official inherit.
163
+ */
164
+ function subagentRouteOf(settings, provider) {
165
+ const group = groupRoute(settings, provider, "subagent");
166
+ if (group !== void 0) {
167
+ const effort = groupOf(settings?.providerGroups, provider ?? "")?.subagentReasoningEffort;
168
+ return present(effort) ? {
169
+ ...group,
170
+ reasoningEffort: effort
171
+ } : group;
172
+ }
173
+ if (settings?.subagentMode !== "fixed") return void 0;
174
+ const fixed = deploymentRoute(settings, "subagent");
175
+ if (fixed === void 0) return void 0;
176
+ return present(settings.subagentReasoningEffort) ? {
177
+ ...fixed,
178
+ reasoningEffort: settings.subagentReasoningEffort
179
+ } : fixed;
180
+ }
181
+ /**
182
+ * One provider's saved route for one capability.
183
+ * @param settings - live Model Switch settings carrying the group table.
184
+ * @param provider - the session's provider; blank or unknown resolves to undefined.
185
+ * @param capability - capability whose group pin is read.
186
+ * @returns The complete group pair, or undefined when the group pins nothing here.
187
+ */
188
+ function groupRoute(settings, provider, capability) {
189
+ if (!present(provider)) return void 0;
190
+ return pairOf(groupOf(settings?.providerGroups, provider), CAPABILITY_FIELDS[capability]);
191
+ }
192
+ /**
193
+ * The route one capability should use for one session.
194
+ *
195
+ * A saved provider group for the session's provider wins; otherwise the flat
196
+ * deployment default applies. An unknown session provider skips every group, so
197
+ * a session Model Switch cannot attribute keeps the deployment default.
198
+ * @param settings - live Model Switch settings.
199
+ * @param provider - the session's provider, when one is observable.
200
+ * @param capability - capability being resolved.
201
+ * @returns The complete pair, or undefined for the capability's official default.
202
+ */
203
+ function resolveCapabilityRoute(settings, provider, capability) {
204
+ return groupRoute(settings, provider, capability) ?? deploymentRoute(settings, capability);
205
+ }
206
+ /**
207
+ * Read the provider from the durable model-selection projection.
208
+ * @param ctx - context that may expose `sessionProjections`.
209
+ * @param session - the session whose selection is read.
210
+ * @returns The pending provider, else the last-used provider, else undefined.
211
+ */
212
+ function projectionProvider(ctx, session) {
213
+ let state;
214
+ try {
215
+ state = ctx.get("sessionProjections", false)?.stateOf(session, "modelSelection");
216
+ } catch {
217
+ state = void 0;
218
+ }
219
+ if (state === void 0) return void 0;
220
+ if (present(state.pending?.provider)) return state.pending.provider;
221
+ if (present(state.lastUsed?.provider)) return state.lastUsed.provider;
222
+ }
223
+ /**
224
+ * The provider one session is using, from official durable state.
225
+ *
226
+ * Preferred source is the session's own model selection (it covers a switch the
227
+ * client has sent but no request has consumed yet); the logged request-context
228
+ * and request-header routes cover profiles without the projection package and
229
+ * replayed sessions that never emitted a selection.
230
+ * @param ctx - context used for the optional projection read.
231
+ * @param session - the session to resolve; undefined resolves to undefined.
232
+ * @returns The provider, or undefined when nothing durable names one.
233
+ */
234
+ function sessionProviderOf(ctx, session) {
235
+ if (session === void 0) return void 0;
236
+ const selected = projectionProvider(ctx, session);
237
+ if (selected !== void 0) return selected;
238
+ try {
239
+ const context = session.requestContext()?.provider;
240
+ if (present(context)) return context;
241
+ const header = session.requestHeader()?.config.provider;
242
+ if (present(header)) return header;
243
+ } catch {
244
+ return;
245
+ }
246
+ }
247
+ /**
248
+ * The subagent route one exact session's spawns should use.
249
+ * @param ctx - context used for the optional session reads.
250
+ * @param session - the delegating session; undefined keeps official inherit.
251
+ * @param settings - live Model Switch settings.
252
+ * @returns The complete route, or undefined for official inherit.
253
+ */
254
+ function sessionSubagentRouteOf(ctx, session, settings) {
255
+ return subagentRouteOf(settings, sessionProviderOf(ctx, session));
256
+ }
257
+ //#endregion
258
+ export { sessionSubagentRouteOf as a, sessionProviderOf as i, groupRoute as n, normalizeProviderGroups as o, resolveCapabilityRoute as r, deploymentRoute as t };
@@ -1,4 +1,4 @@
1
- import { i as sessionProviderOf, n as groupRoute, t as deploymentRoute } from "./session-route-BjNPlliu.js";
1
+ import { i as sessionProviderOf, n as groupRoute, t as deploymentRoute } from "./session-route-D1zoZgf3.js";
2
2
  //#region lib/types/session-title.js
3
3
  /**
4
4
  * Model Switch-owned session-naming row.
@@ -1,4 +1,4 @@
1
- import { a as sessionSubagentRouteOf } from "./session-route-BjNPlliu.js";
1
+ import { a as sessionSubagentRouteOf } from "./session-route-D1zoZgf3.js";
2
2
  import { ReasoningEffortId } from "@deepseek-ai/dsh-llm";
3
3
  import OfficialSubagentRuntime from "@deepseek-ai/dsh-subagent";
4
4
  //#region lib/types/subagent-runtime.js
@@ -51,6 +51,19 @@ export declare const name = "model-switch-auto-review";
51
51
  * takeover).
52
52
  */
53
53
  export declare const inject: string[];
54
+ /**
55
+ * Live Auto-reviewer takeover status, read by the runtime-capability snapshot.
56
+ *
57
+ * `held: false` means the official row kept the single `registerAuto` slot (or
58
+ * has not released it yet): the reviewer route is not configurable and the
59
+ * tool-call safety net allows unreviewed calls instead of failing them.
60
+ * `allowedCalls` counts the calls the net let through in that state, so the
61
+ * settings card can tell the human how much review was skipped.
62
+ */
63
+ export declare const autoReviewStatus: {
64
+ held: boolean;
65
+ allowedCalls: number;
66
+ };
54
67
  /** Pinned reviewer route; both identifiers are non-empty. */
55
68
  export interface AutoReviewRoute {
56
69
  readonly provider: string;
@@ -32,6 +32,7 @@ export declare const zh: {
32
32
  readonly autoReviewFollow: "跟随会话模型";
33
33
  readonly autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。本插件启用时,官方「自动审查」组件由本插件接管。";
34
34
  readonly autoReviewRouteHelp: "审查请求固定使用这里的 Provider/模型,与会话模型无关;保存后对下一次审查立即生效。";
35
+ readonly autoReviewTakeoverWarning: "审查路由当前未接管(官方「自动审查」行仍占着唯一注册位):工具调用会照常执行,但这些调用未经审查。重启 DSH 即可收敛;若重启后仍显示此提示,请把日志里的 fiber 链反馈给上游。";
35
36
  readonly autoReviewTakeoverNote: "官方「自动审查」组件已由本插件接管(提供可配置审查路由);在插件管理里把它重新打开会被再次关闭,并在日志里留下一条记录。";
36
37
  readonly compaction: "压缩模型";
37
38
  readonly compactionFollow: "跟随会话模型";
@@ -19,6 +19,13 @@ export declare class ModelSwitchRuntime extends Service {
19
19
  providers: string[];
20
20
  catalog: readonly import("./adapter-registry.js").SearchProviderMetadata[];
21
21
  };
22
+ autoReview: {
23
+ available: boolean;
24
+ reason?: never;
25
+ } | {
26
+ available: boolean;
27
+ reason: "auto-review-takeover";
28
+ };
22
29
  mainDefaults: Readonly<{
23
30
  available: true;
24
31
  }>;
@@ -1,4 +1,4 @@
1
- export type RuntimeCapabilityReason = 'central-subagent-routing' | 'packaged-preset-roots' | 'tool-owner-suppression' | 'search-provider-adapters' | 'vision-provider-adapters' | 'image-provider-adapters';
1
+ export type RuntimeCapabilityReason = 'central-subagent-routing' | 'packaged-preset-roots' | 'tool-owner-suppression' | 'search-provider-adapters' | 'vision-provider-adapters' | 'image-provider-adapters' | 'auto-review-takeover';
2
2
  import type { SearchProviderMetadata } from './adapter-registry.js';
3
3
  export interface RuntimeCapability {
4
4
  readonly available: boolean;
@@ -15,6 +15,14 @@ export interface RuntimeCapabilities {
15
15
  readonly searchProviderAdapters: RuntimeCapability;
16
16
  readonly visionProviderAdapters: RuntimeCapability;
17
17
  readonly imageProviderAdapters: RuntimeCapability;
18
+ /**
19
+ * Whether this row currently holds the official Auto reviewer route.
20
+ *
21
+ * `false` means the official row kept the single `registerAuto` slot (or has
22
+ * not released it yet): the reviewer route is not configurable, and the
23
+ * tool-call safety net allows unreviewed calls instead of failing them.
24
+ */
25
+ readonly autoReview: RuntimeCapability;
18
26
  }
19
27
  export declare const RUNTIME_CAPABILITIES: Readonly<{
20
28
  mainDefaults: Readonly<{
@@ -47,4 +55,7 @@ export declare const RUNTIME_CAPABILITIES: Readonly<{
47
55
  available: true;
48
56
  providers: readonly string[];
49
57
  }>;
58
+ autoReview: Readonly<{
59
+ available: true;
60
+ }>;
50
61
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model-switch",
3
- "version": "0.4.32",
3
+ "version": "0.4.33",
4
4
  "description": "Explicit model routing plus composer/Plan picker for DeepSeek Harness",
5
5
  "repository": {
6
6
  "type": "git",