dsh-model-switch 0.4.18 → 0.4.20

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/index.js CHANGED
@@ -22,7 +22,10 @@ const Config = z.object({
22
22
  searchModel: z.string().volatile(),
23
23
  imageProvider: z.string().volatile(),
24
24
  imageModel: z.string().volatile(),
25
- compactOnSwitch: z.boolean().default(true).volatile()
25
+ compactOnSwitch: z.boolean().default(true).volatile(),
26
+ autoReviewProvider: z.string().volatile(),
27
+ autoReviewModel: z.string().volatile(),
28
+ planCompactThreshold: z.number().default(2e5).volatile()
26
29
  });
27
30
  function readConfig(config) {
28
31
  const subagentProvider = config.subagentProvider.get();
@@ -32,6 +35,8 @@ function readConfig(config) {
32
35
  const searchModel = config.searchModel.get();
33
36
  const imageProvider = config.imageProvider.get();
34
37
  const imageModel = config.imageModel.get();
38
+ const autoReviewProvider = config.autoReviewProvider.get();
39
+ const autoReviewModel = config.autoReviewModel.get();
35
40
  return {
36
41
  subagentMode: config.subagentMode.get(),
37
42
  ...subagentProvider === void 0 ? {} : { subagentProvider },
@@ -41,7 +46,10 @@ function readConfig(config) {
41
46
  ...searchModel === void 0 ? {} : { searchModel },
42
47
  ...imageProvider === void 0 ? {} : { imageProvider },
43
48
  ...imageModel === void 0 ? {} : { imageModel },
44
- compactOnSwitch: config.compactOnSwitch.get()
49
+ compactOnSwitch: config.compactOnSwitch.get(),
50
+ ...autoReviewProvider === void 0 || autoReviewProvider === "" ? {} : { autoReviewProvider },
51
+ ...autoReviewModel === void 0 || autoReviewModel === "" ? {} : { autoReviewModel },
52
+ planCompactThreshold: config.planCompactThreshold.get()
45
53
  };
46
54
  }
47
55
  //#endregion
@@ -339,8 +347,28 @@ async function handleFetchRpc(request, handler, operator) {
339
347
  return statusResponse(500);
340
348
  }
341
349
  }
350
+ /** Validate one plan-compact payload; returns the request or an error string. */
351
+ function planCompactRequest(payload) {
352
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return {
353
+ ok: false,
354
+ message: "Expected an object payload"
355
+ };
356
+ const { sessionId, provider, model } = payload;
357
+ if (typeof sessionId !== "string" || sessionId.length === 0 || typeof provider !== "string" || provider.length === 0 || typeof model !== "string" || model.length === 0) return {
358
+ ok: false,
359
+ message: "Expected sessionId, provider, and model strings"
360
+ };
361
+ return {
362
+ ok: true,
363
+ request: {
364
+ sessionId,
365
+ provider,
366
+ model
367
+ }
368
+ };
369
+ }
342
370
  /** Bounded long-poll on the existing registry; each caller owns and disposes its subscription. */
343
- function capabilitiesRpc(registry, snapshot, lifetime) {
371
+ function capabilitiesRpc(registry, snapshot, lifetime, planCompact) {
344
372
  let revision = 0;
345
373
  let previous;
346
374
  const read = () => {
@@ -354,6 +382,39 @@ function capabilitiesRpc(registry, snapshot, lifetime) {
354
382
  };
355
383
  };
356
384
  return async (endpoint, payload, signal) => {
385
+ if (endpoint === "plan-compact") {
386
+ if (planCompact === void 0) return {
387
+ ok: false,
388
+ error: {
389
+ details: {},
390
+ code: "unavailable",
391
+ message: "Plan compaction is not mounted on this Host"
392
+ }
393
+ };
394
+ const request = planCompactRequest(payload);
395
+ if (!request.ok) return {
396
+ ok: false,
397
+ error: {
398
+ details: {},
399
+ code: "invalid-request",
400
+ message: request.message
401
+ }
402
+ };
403
+ try {
404
+ return {
405
+ ok: true,
406
+ value: await planCompact(request.request, signal)
407
+ };
408
+ } catch (cause) {
409
+ return {
410
+ ok: true,
411
+ value: {
412
+ status: "error",
413
+ message: cause instanceof Error ? cause.message : String(cause)
414
+ }
415
+ };
416
+ }
417
+ }
357
418
  if (endpoint !== "capabilities") return {
358
419
  ok: false,
359
420
  error: {
@@ -406,10 +467,10 @@ function capabilitiesRpc(registry, snapshot, lifetime) {
406
467
  };
407
468
  };
408
469
  }
409
- function installCapabilitiesRpc(ctx, registry, snapshot) {
470
+ function installCapabilitiesRpc(ctx, registry, snapshot, planCompact) {
410
471
  ctx.inject(["connection"], (scope) => scope.effect(() => {
411
472
  const lifetime = new AbortController();
412
- const handler = capabilitiesRpc(registry, snapshot, lifetime.signal);
473
+ const handler = capabilitiesRpc(registry, snapshot, lifetime.signal, planCompact);
413
474
  const dispose = scope.connection.fetch.register({
414
475
  path: "/api/plugin-rpc/model-switch",
415
476
  methods: ["POST"],
@@ -864,6 +925,189 @@ async function compactWithPrevious(ctx, agent, previous, signal) {
864
925
  }
865
926
  }
866
927
  //#endregion
928
+ //#region lib/types/picker/plan-compact.js
929
+ /**
930
+ * Plan-exit compaction vocabulary shared by the card and the Host RPC.
931
+ *
932
+ * The card decides *when* to ask (only an approval that switches the execution
933
+ * model); the Host owns measurement, threshold policy, and the compaction
934
+ * itself. Failures are explicit: the Plan stays pending and can be retried,
935
+ * while an empty history is a completed attempt that may proceed.
936
+ *
937
+ * @module dsh-model-switch/picker/plan-compact
938
+ */
939
+ /** Whether the user's threshold arms the plan-exit mechanism at all. */
940
+ function planCompactEnabled(threshold) {
941
+ return Number.isFinite(threshold) && threshold > 0;
942
+ }
943
+ /**
944
+ * Decide whether a plan exit must compact before it switches models.
945
+ * @param threshold — user-configured token threshold; 0 disables the mechanism.
946
+ * @param estimate — measured next-request input tokens.
947
+ * @returns `disabled` when off, `compact` only above the threshold, else `skip`.
948
+ */
949
+ function planCompactDecision(threshold, estimate) {
950
+ if (!planCompactEnabled(threshold)) return "disabled";
951
+ if (!Number.isFinite(estimate) || estimate < 0) return "skip";
952
+ return estimate > threshold ? "compact" : "skip";
953
+ }
954
+ //#endregion
955
+ //#region lib/types/plan-compact.js
956
+ /**
957
+ * Plan-exit compaction on the Host: measure, threshold, then summarize the
958
+ * history with the switched-to model before the approval resumes the session.
959
+ *
960
+ * The approval answer lands inside a still-open `exit_plan_mode` tool call, so
961
+ * the agent phase is `running` and the idle-only `compactNow` seam is off
962
+ * limits. This module therefore uses `compactIfNeeded(agent, 'context-overflow')`
963
+ * — the same turn-safe, threshold-free operation official overflow recovery
964
+ * uses — under an isolated `compaction-basic` engine configured to summarize
965
+ * with the route the human just chose. The user's threshold decides *whether*
966
+ * to call it; the engine's overflow policy guarantees one useful balanced
967
+ * reduction whenever a safe range exists.
968
+ *
969
+ * @module dsh-model-switch/plan-compact
970
+ */
971
+ /**
972
+ * Estimate the next request's input tokens under both envelopes.
973
+ *
974
+ * Takes the larger of the target-route heuristic price and the previous
975
+ * envelope's anchored price — the same conservative stance the send-time
976
+ * switch guard takes, so dense histories cannot under-trigger the threshold.
977
+ *
978
+ * @param meter — replay token meter.
979
+ * @param session — session about to resume.
980
+ * @param route — the switched-to provider/model.
981
+ * @returns Estimated tokens, or undefined when no prior request exists to price.
982
+ */
983
+ function estimatePlanExitTokens(meter, session, route) {
984
+ const previousHeader = session.requestHeader();
985
+ if (previousHeader === void 0) return void 0;
986
+ try {
987
+ const targetHeader = canonicalHeader({ config: {
988
+ provider: route.provider,
989
+ model: route.model
990
+ } });
991
+ return Math.max(meter.measure(session, targetHeader).totalTokens, meter.measure(session, previousHeader).totalTokens);
992
+ } catch {
993
+ return;
994
+ }
995
+ }
996
+ /**
997
+ * Drive one compaction through an already-mounted isolated engine.
998
+ * @param engine — engine whose overflow policy selects the range.
999
+ * @param agent — live agent owning the session.
1000
+ * @param signal — cancellation scoped to this request.
1001
+ * @returns `empty` when no safe range exists, `compacted` after a summary, `error` on failure.
1002
+ */
1003
+ async function planCompactWithEngine(engine, agent, signal) {
1004
+ try {
1005
+ const result = await engine.compactIfNeeded(agent, "context-overflow", signal);
1006
+ return result === null || result === void 0 ? { status: "empty" } : { status: "compacted" };
1007
+ } catch (cause) {
1008
+ if (signal.aborted) return {
1009
+ status: "error",
1010
+ message: "compaction was cancelled"
1011
+ };
1012
+ return {
1013
+ status: "error",
1014
+ message: cause instanceof Error ? cause.message : String(cause)
1015
+ };
1016
+ }
1017
+ }
1018
+ /**
1019
+ * Mount an isolated compaction-basic engine pinned to the switched-to route
1020
+ * and run one overflow-policy reduction over the agent's session.
1021
+ * @param ctx — host context that owns token metering and session state.
1022
+ * @param agent — live agent about to resume the approved plan.
1023
+ * @param route — provider/model whose model must write the summary.
1024
+ * @param signal — cancellation scoped to this request.
1025
+ * @param loadEngine — dynamic import seam (tests inject the fake module).
1026
+ * @returns The compaction outcome for this request.
1027
+ */
1028
+ async function runPlanCompaction(ctx, agent, route, signal, loadEngine) {
1029
+ const load = loadEngine ?? (async () => import("@deepseek-ai/dsh-compaction-basic"));
1030
+ let loaded;
1031
+ try {
1032
+ loaded = await load();
1033
+ } catch (cause) {
1034
+ return {
1035
+ status: "error",
1036
+ message: cause instanceof Error ? cause.message : String(cause)
1037
+ };
1038
+ }
1039
+ const isolated = ctx.isolate("plan-compaction");
1040
+ const fiber = isolated.plugin(loaded.default, {
1041
+ auto: false,
1042
+ summarizationProvider: route.provider,
1043
+ summarizationModel: route.model,
1044
+ compactionRetries: 0
1045
+ });
1046
+ try {
1047
+ await fiber;
1048
+ const engine = isolated.get("compaction", false);
1049
+ if (engine === void 0 || typeof engine.compactIfNeeded !== "function") return {
1050
+ status: "error",
1051
+ message: "private compaction engine is unavailable"
1052
+ };
1053
+ return await planCompactWithEngine(engine, agent, signal);
1054
+ } finally {
1055
+ await fiber.dispose();
1056
+ }
1057
+ }
1058
+ /**
1059
+ * Build the `plan-compact` RPC runner bound to this host's live settings.
1060
+ * @param ctx — host context resolving the live agent registry.
1061
+ * @param settings — live Model Switch settings (`planCompactThreshold`).
1062
+ * @param loadEngine — dynamic import seam (tests inject the fake module).
1063
+ * @returns The runner the connection RPC dispatches to.
1064
+ */
1065
+ function planCompactRunner(ctx, settings, loadEngine) {
1066
+ return async (request, signal) => {
1067
+ const threshold = settings().planCompactThreshold;
1068
+ if (!planCompactEnabled(threshold)) return {
1069
+ status: "disabled",
1070
+ threshold
1071
+ };
1072
+ const provider = request.provider.trim();
1073
+ const model = request.model.trim();
1074
+ if (provider === "" || model === "") return {
1075
+ status: "error",
1076
+ message: "the execution route is incomplete"
1077
+ };
1078
+ const route = {
1079
+ provider,
1080
+ model
1081
+ };
1082
+ const meter = ctx.get("tokenMeter", false);
1083
+ const agent = ctx.get("agents", false)?.get(request.sessionId);
1084
+ if (agent === void 0) return {
1085
+ status: "error",
1086
+ message: "the session has no live agent"
1087
+ };
1088
+ if (meter === void 0) return {
1089
+ status: "error",
1090
+ message: "token metering is unavailable"
1091
+ };
1092
+ const estimate = estimatePlanExitTokens(meter, agent.session, route);
1093
+ const decision = planCompactDecision(threshold, estimate ?? NaN);
1094
+ if (decision === "disabled") return {
1095
+ status: "disabled",
1096
+ threshold
1097
+ };
1098
+ if (decision === "skip") return {
1099
+ status: "skipped",
1100
+ threshold,
1101
+ ...estimate === void 0 ? {} : { estimate }
1102
+ };
1103
+ return {
1104
+ ...await runPlanCompaction(ctx, agent, route, signal, loadEngine),
1105
+ threshold,
1106
+ ...estimate === void 0 ? {} : { estimate }
1107
+ };
1108
+ };
1109
+ }
1110
+ //#endregion
867
1111
  //#region lib/types/host-runtime.js
868
1112
  /** Host owner for Model Switch settings and the released Main-default adapter. */
869
1113
  var ModelSwitchRuntime = class extends Service {
@@ -890,7 +1134,7 @@ var ModelSwitchRuntime = class extends Service {
890
1134
  installSwitchCompaction(ctx, () => this.source(), (provider) => this.adapters.get(provider)?.role);
891
1135
  installDeepSeekSearchAdapter(ctx);
892
1136
  installModelSwitchSearchProvider(ctx, this);
893
- installCapabilitiesRpc(ctx, this.adapters, () => this.capabilities);
1137
+ installCapabilitiesRpc(ctx, this.adapters, () => this.capabilities, planCompactRunner(ctx, () => this.source()));
894
1138
  const imageTool = installGenerateImageTool(ctx, this);
895
1139
  ctx.on("loader/volatile-update", () => {
896
1140
  imageTool.reconcile().catch((error) => {
@@ -1003,6 +1247,7 @@ function planReviewOf(questions) {
1003
1247
  id: question.id,
1004
1248
  question: question.question,
1005
1249
  plan: question.detail,
1250
+ ...intent.callId === void 0 ? {} : { callId: intent.callId },
1006
1251
  approve,
1007
1252
  ...decline === void 0 ? {} : { decline }
1008
1253
  };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Model Switch-owned Auto reviewer row.
3
+ *
4
+ * The official `@deepseek-ai/dsh-experimental-auto-review` gate hardwires its
5
+ * reviewer to the session's own request route (`header.config`). This row runs
6
+ * the official `apply` under a scoped child context whose `llm` service is
7
+ * shadowed by a wrapper that rewrites only `stream()`'s provider/model — and
8
+ * only while the profile has a complete reviewer route configured. With no
9
+ * route configured the wrapper passes options through by identity, so the
10
+ * reviewer follows the session model exactly.
11
+ *
12
+ * The official gate stays an opt-in capability: the row mounts it only when
13
+ * the profile actually started with the experimental-auto-review bundle.
14
+ * `cordis.patch.yml` disables the official row (one `registerAuto` per
15
+ * context), so this row is its replacement rather than a second registration.
16
+ *
17
+ * @module dsh-model-switch/auto-review
18
+ */
19
+ import type { Context } from '@deepseek-ai/cordis';
20
+ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
21
+ /** Cordis plugin name used by loader diagnostics. */
22
+ export declare const name = "model-switch-auto-review";
23
+ /** The official gate's host services plus Model Switch's settings owner. */
24
+ export declare const inject: string[];
25
+ /** Pinned reviewer route; both identifiers are non-empty. */
26
+ export interface AutoReviewRoute {
27
+ readonly provider: string;
28
+ readonly model: string;
29
+ }
30
+ /** Minimal public llm face the official gate uses (it calls exactly `stream`). */
31
+ export interface ReviewLlm {
32
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
33
+ }
34
+ /**
35
+ * Resolve the pinned reviewer route from live settings.
36
+ * @param settings - Model Switch settings carrying the optional reviewer pair.
37
+ * @returns Both identifiers trimmed and non-empty, or undefined (follow session).
38
+ */
39
+ export declare function autoReviewRouteOf(settings: {
40
+ readonly autoReviewProvider?: string;
41
+ readonly autoReviewModel?: string;
42
+ } | undefined): AutoReviewRoute | undefined;
43
+ /**
44
+ * Wrap the real llm service so only `stream()` route fields are rewritten.
45
+ *
46
+ * Non-`stream` members pass through bound to the real service (Proxy reads
47
+ * would otherwise hand methods the proxy as `this`). The route is re-read per
48
+ * call so a settings change applies to the very next review without a reload.
49
+ *
50
+ * @param real - the host's `ctx.llm`.
51
+ * @param route - live route resolver; undefined means "do not rewrite".
52
+ * @returns A structurally compatible llm face for the scoped context.
53
+ */
54
+ export declare function reviewLlm(real: ReviewLlm, route: () => AutoReviewRoute | undefined): ReviewLlm;
55
+ /**
56
+ * Build the scoped context the official gate runs under.
57
+ * @param ctx - host context whose modelSwitch settings pin the reviewer route.
58
+ * @returns A child context shadowing only the llm service.
59
+ */
60
+ export declare function reviewScope(ctx: Context): Context;
61
+ /**
62
+ * Mount the official gate under the reviewer-route scope.
63
+ *
64
+ * A profile that never enabled the optional bundle keeps no Auto preset and no
65
+ * reviewer; a deployment missing the official package reports the lost
66
+ * capability explicitly instead of guessing another path.
67
+ */
68
+ export declare function apply(ctx: Context): Promise<void>;
@@ -2,11 +2,12 @@ import type { Context } from '@deepseek-ai/cordis';
2
2
  import { type ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection';
3
3
  import type { ModelSwitchAdapterRegistry } from './adapter-registry.js';
4
4
  import type { RuntimeCapabilities } from './runtime-capabilities.js';
5
+ import type { PlanCompactRunner } from './plan-compact.js';
5
6
  export declare const CAPABILITIES_RPC_METHOD = "plugin-rpc/model-switch";
6
7
  export interface CapabilitiesSnapshot {
7
8
  readonly revision: number;
8
9
  readonly capabilities: RuntimeCapabilities;
9
10
  }
10
11
  /** Bounded long-poll on the existing registry; each caller owns and disposes its subscription. */
11
- export declare function capabilitiesRpc(registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities, lifetime: AbortSignal): ConnectionRpcHandler;
12
- export declare function installCapabilitiesRpc(ctx: Context, registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities): void;
12
+ export declare function capabilitiesRpc(registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities, lifetime: AbortSignal, planCompact?: PlanCompactRunner): ConnectionRpcHandler;
13
+ export declare function installCapabilitiesRpc(ctx: Context, registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities, planCompact?: PlanCompactRunner): void;
@@ -14,13 +14,15 @@ export interface ModelSwitchSettingsFace {
14
14
  subagentSettings: ConfigViewForm<SubagentSettingsView>;
15
15
  searchSettings: ConfigViewForm<CapabilityRouteView>;
16
16
  imageSettings: ConfigViewForm<CapabilityRouteView>;
17
+ autoReviewSettings: ConfigViewForm<CapabilityRouteView>;
17
18
  switchSettings: Pick<ConfigForm<ModelSwitchSettingsView>, 'getSnapshot' | 'subscribe'>;
18
19
  };
19
20
  capabilities: RuntimeCapabilities;
20
21
  saveMain: (next: MainSettingsView, expectedRevision: number) => Promise<number>;
21
22
  setSubagent: (field: 'mode' | 'provider' | 'model' | 'reasoningEffort', value: string | undefined) => Promise<void>;
22
- setCapability: (route: 'search' | 'image', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
23
+ setCapability: (route: 'search' | 'image' | 'autoReview', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
23
24
  setCompactOnSwitch: (value: boolean) => Promise<void>;
25
+ setPlanCompactThreshold: (value: number) => Promise<void>;
24
26
  loadCatalog: () => Promise<readonly ModelProviderGroup[]>;
25
27
  /** Host capabilities long-poll; absent in legacy faces, which keep the static-capabilities path. */
26
28
  loadCapabilities?: (revision?: number, signal?: AbortSignal) => Promise<CapabilitiesSnapshot>;
@@ -34,6 +34,13 @@ export declare const zh: {
34
34
  readonly requestFailed: "保存失败";
35
35
  readonly catalogFailed: "无法加载模型目录。";
36
36
  readonly conflict: "设置已在其他位置更改,请检查最新值后重试。";
37
+ readonly autoReview: "自动审查模型";
38
+ readonly autoReviewFollow: "跟随会话模型";
39
+ readonly autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。";
40
+ readonly autoReviewRouteHelp: "审查请求固定使用这里的 Provider/模型,与会话模型无关;保存后对下一次审查立即生效。";
41
+ readonly planCompactThreshold: "计划执行前压缩阈值";
42
+ readonly planCompactThresholdHelp: "切换模型执行计划时,若上下文估算超过该 Token 数,先用切换后的模型压缩一次历史再提交执行;未切换模型不触发。0 = 关闭。压缩会产生额外模型用量,摘要可能省略细节。";
43
+ readonly planCompactThresholdInvalid: "请输入不小于 0 的整数。";
37
44
  readonly 'reason.central-subagent-routing': "Alpha.4 没有全局子代理启动路由接口。";
38
45
  readonly 'reason.packaged-preset-roots': "Alpha.4 不支持插件提供额外 preset root。";
39
46
  readonly 'reason.tool-owner-suppression': "Alpha.4 没有工具所有者或来源抑制接口。";
@@ -2,6 +2,9 @@ import type { PendingQuestion } from '@deepseek-ai/dsh-client-ui-user-questions/
2
2
  import type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
3
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  import type { MainSettingsView } from '../../client-contract.ts';
5
+ import { type GoalRemote } from '../../picker/plan-goal.ts';
6
+ import type { PlanCompactOutcome } from '../../picker/plan-compact.ts';
7
+ import type { PlanPreviewBinding } from './plan-preview.ts';
5
8
  import { type PickerDirectoryFace } from './PickerDirectory.ts';
6
9
  import type { PickerInteractionOperations } from './popup-dismissal.ts';
7
10
  import { type RuntimeProviderLock } from '../runtime-lock.ts';
@@ -22,6 +25,12 @@ export interface PlanReviewFace extends PickerDirectoryFace {
22
25
  refreshProviderLock: () => void;
23
26
  subscribeMainDefaults: (listener: () => void) => () => void;
24
27
  getMainDefaultsSnapshot: () => ConfigFormSnapshot<MainSettingsView>;
28
+ /** Released goal remote; absent when this deployment mounts no goal service. */
29
+ goalRemote: () => GoalRemote | undefined;
30
+ /** Full-plan sidebar navigation (ui-plan's registered opener). */
31
+ planPreview: PlanPreviewBinding;
32
+ /** Host-backed pre-execution compaction; absent seams degrade to no compaction. */
33
+ planCompact?: (sessionId: string, provider: string, model: string) => Promise<PlanCompactOutcome>;
25
34
  /** Live catalog-group-id → card-key map from ProviderDirectory. */
26
35
  catalogRoutes?: () => Readonly<Record<string, string>>;
27
36
  }
@@ -34,6 +34,7 @@ export declare const zh: {
34
34
  'choice.unavailable': string;
35
35
  'plan.kicker': string;
36
36
  'plan.header': string;
37
+ 'plan.openFull': string;
37
38
  'plan.execution': string;
38
39
  'plan.approve': string;
39
40
  'plan.keep': string;
@@ -44,8 +45,16 @@ export declare const zh: {
44
45
  'plan.feedbackPlaceholder': string;
45
46
  'plan.notRestored': string;
46
47
  'plan.dismissUnsupported': string;
48
+ 'plan.goalRun': string;
49
+ 'plan.goalFailed': string;
50
+ 'plan.goalReplaced': string;
51
+ 'plan.goalUnsupported': string;
47
52
  'plan.responseRejected': string;
48
53
  'plan.cancelRejected': string;
54
+ 'plan.compacting': string;
55
+ 'plan.compacted': string;
56
+ 'plan.compactEmpty': string;
57
+ 'plan.compactFailed': string;
49
58
  'markdown.copy': string;
50
59
  'markdown.copied': string;
51
60
  'markdown.footnotes': string;
@@ -86,6 +95,7 @@ export declare const en: {
86
95
  'choice.unavailable': string;
87
96
  'plan.kicker': string;
88
97
  'plan.header': string;
98
+ 'plan.openFull': string;
89
99
  'plan.execution': string;
90
100
  'plan.approve': string;
91
101
  'plan.keep': string;
@@ -96,8 +106,16 @@ export declare const en: {
96
106
  'plan.feedbackPlaceholder': string;
97
107
  'plan.notRestored': string;
98
108
  'plan.dismissUnsupported': string;
109
+ 'plan.goalRun': string;
110
+ 'plan.goalFailed': string;
111
+ 'plan.goalReplaced': string;
112
+ 'plan.goalUnsupported': string;
99
113
  'plan.responseRejected': string;
100
114
  'plan.cancelRejected': string;
115
+ 'plan.compacting': string;
116
+ 'plan.compacted': string;
117
+ 'plan.compactEmpty': string;
118
+ 'plan.compactFailed': string;
101
119
  'markdown.copy': string;
102
120
  'markdown.copied': string;
103
121
  'markdown.footnotes': string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Restore the official full-plan sidebar preview under the takeover card.
3
+ *
4
+ * The takeover replaces the official QuestionComposer in the
5
+ * `conversation.composer` chain, and that composer is the only entry allowed
6
+ * to render `conversation.plan-review.actions` (the SlotRegistry enforces one
7
+ * declarer per child key, and `renderSlot` dispatch checks the *rendering*
8
+ * entry's own children table). This module therefore drives ui-plan's own
9
+ * registered opener — its inject face owns the `dsh-resource://plan-review`
10
+ * address, the logged-plan branch, and the sidebar-mounted gate — instead of
11
+ * re-deriving any of them here. If ui-plan is absent, every operation
12
+ * degrades to "unavailable" and the card keeps working.
13
+ */
14
+ import type { StoredEntry } from '@deepseek-ai/dsh-client-ui-slots';
15
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
16
+ import type { PlanReview } from '@deepseek-ai/dsh-client-ui-user-questions/client';
17
+ /** Minimal registry face the binding reads (the public slot ledger snapshot). */
18
+ export interface PlanPreviewSlots {
19
+ entries(key: string): readonly StoredEntry[];
20
+ }
21
+ /** Sideways navigation controls for the full plan document in the right sidebar. */
22
+ export interface PlanPreviewBinding {
23
+ /** Whether ui-plan's opener entry is registered right now. */
24
+ available(): boolean;
25
+ /** Open the full plan immediately; returns false when unavailable. */
26
+ open(sessionId: SessionId, review: PlanReview, requestKey: string): boolean;
27
+ /** Open once as soon as a Session is on screen; idempotent per review identity. */
28
+ autoOpen(sessionId: SessionId, review: PlanReview, requestKey: string): () => void;
29
+ }
30
+ /**
31
+ * Stable identity of one review's sidebar preview.
32
+ * @param sessionId - session that owns the review.
33
+ * @param review - narrowed plan review.
34
+ * @param requestKey - pending-question request key.
35
+ * @returns `session:call:<callId>` for a logged plan, else `session:review:<requestKey>`.
36
+ */
37
+ export declare function planPreviewIdentity(sessionId: SessionId, review: PlanReview, requestKey: string): string;
38
+ /**
39
+ * Whether this process already opened the preview for one review identity.
40
+ * @param identity - value from {@link planPreviewIdentity}.
41
+ * @returns True once opened.
42
+ */
43
+ export declare function wasPlanPreviewOpened(identity: string): boolean;
44
+ /**
45
+ * Bind the preview navigation to one slot registry.
46
+ * @param slots - the live registry ledger.
47
+ * @returns an idempotent binding usable from the card's render and effects.
48
+ */
49
+ export declare function createPlanPreviewBinding(slots: PlanPreviewSlots): PlanPreviewBinding;
@@ -19,6 +19,10 @@ export interface ModelSwitchSettingsView {
19
19
  imageProvider?: string;
20
20
  imageModel?: string;
21
21
  compactOnSwitch?: boolean;
22
+ autoReviewProvider?: string;
23
+ autoReviewModel?: string;
24
+ /** Plan-exit compaction threshold in tokens; absent = the shipped default. */
25
+ planCompactThreshold?: number;
22
26
  }
23
27
  export interface SubagentSettingsView {
24
28
  mode: 'follow-main' | 'fixed';
@@ -46,6 +50,14 @@ export declare const IMAGE_SETTINGS_FIELDS: Readonly<{
46
50
  readonly provider: "imageProvider";
47
51
  readonly model: "imageModel";
48
52
  }>;
53
+ /** Auto-preset reviewer route; both unset means the reviewer follows the session model. */
54
+ export declare const AUTO_REVIEW_SETTINGS_FIELDS: Readonly<{
55
+ readonly provider: "autoReviewProvider";
56
+ readonly model: "autoReviewModel";
57
+ }>;
49
58
  export declare function deriveSubagentSettings(settings: ModelSwitchSettingsView): SubagentSettingsView;
50
59
  export declare function deriveSearchSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
51
60
  export declare function deriveImageSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
61
+ export declare function deriveAutoReviewSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
62
+ /** Whether both reviewer fields form a complete route; an incomplete pair still follows the session model. */
63
+ export declare function autoReviewRouteConfigured(route: CapabilityRouteView | undefined): boolean;
@@ -21,6 +21,9 @@ export declare class ModelSwitchRuntime extends Service {
21
21
  imageProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
22
22
  imageModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
23
23
  compactOnSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
24
+ autoReviewProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
25
+ autoReviewModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
26
+ planCompactThreshold: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
24
27
  }>>, Schemastery.ObjectT<NoInfer<{
25
28
  subagentMode: import("@deepseek-ai/schemastery").default<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
26
29
  subagentProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
@@ -31,6 +34,9 @@ export declare class ModelSwitchRuntime extends Service {
31
34
  imageProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
32
35
  imageModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
33
36
  compactOnSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
37
+ autoReviewProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
38
+ autoReviewModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
39
+ planCompactThreshold: import("@deepseek-ai/schemastery").default<number, number, "volatile-defined">;
34
40
  }>>, "plain">;
35
41
  get capabilities(): {
36
42
  searchProviderAdapters: {
@@ -12,6 +12,12 @@ export interface ModelSwitchSettings {
12
12
  imageProvider?: string;
13
13
  imageModel?: string;
14
14
  compactOnSwitch: boolean;
15
+ /** Reviewer provider for the Auto preset; both fields unset = follow the session model. */
16
+ autoReviewProvider?: string;
17
+ /** Reviewer model for the Auto preset; set together with `autoReviewProvider`. */
18
+ autoReviewModel?: string;
19
+ /** Plan-exit compaction threshold in tokens; 0 disables the mechanism. */
20
+ planCompactThreshold: number;
15
21
  }
16
22
  /** Loader-owned references; every field is directly editable in the profile form. */
17
23
  export interface Config {
@@ -24,6 +30,9 @@ export interface Config {
24
30
  imageProvider: Volatile<string | undefined>;
25
31
  imageModel: Volatile<string | undefined>;
26
32
  compactOnSwitch: Volatile<boolean>;
33
+ autoReviewProvider: Volatile<string | undefined>;
34
+ autoReviewModel: Volatile<string | undefined>;
35
+ planCompactThreshold: Volatile<number>;
27
36
  }
28
37
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
29
38
  subagentMode: z<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
@@ -35,6 +44,9 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
35
44
  imageProvider: z<string, string, "volatile">;
36
45
  imageModel: z<string, string, "volatile">;
37
46
  compactOnSwitch: z<boolean, boolean, "volatile-defined">;
47
+ autoReviewProvider: z<string, string, "volatile">;
48
+ autoReviewModel: z<string, string, "volatile">;
49
+ planCompactThreshold: z<number, number, "volatile-defined">;
38
50
  }>>, Schemastery.ObjectT<NoInfer<{
39
51
  subagentMode: z<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
40
52
  subagentProvider: z<string, string, "volatile">;
@@ -45,5 +57,8 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
45
57
  imageProvider: z<string, string, "volatile">;
46
58
  imageModel: z<string, string, "volatile">;
47
59
  compactOnSwitch: z<boolean, boolean, "volatile-defined">;
60
+ autoReviewProvider: z<string, string, "volatile">;
61
+ autoReviewModel: z<string, string, "volatile">;
62
+ planCompactThreshold: z<number, number, "volatile-defined">;
48
63
  }>>, "plain">;
49
64
  export declare function readConfig(config: Config): ModelSwitchSettings;