dsh-model-switch 0.4.20 → 0.4.21

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
@@ -38,6 +38,11 @@ window.__ModuleLoader__.load({
38
38
  provider: "autoReviewProvider",
39
39
  model: "autoReviewModel"
40
40
  });
41
+ /** Compaction summarization route for `/compact` and automatic compaction; both unset means the official default (the conversation target). */
42
+ const COMPACTION_SETTINGS_FIELDS = Object.freeze({
43
+ provider: "compactProvider",
44
+ model: "compactModel"
45
+ });
41
46
  function deriveSubagentSettings(settings) {
42
47
  return {
43
48
  mode: settings.subagentMode,
@@ -64,8 +69,14 @@ window.__ModuleLoader__.load({
64
69
  ...settings.autoReviewModel === void 0 ? {} : { model: settings.autoReviewModel }
65
70
  };
66
71
  }
67
- /** Whether both reviewer fields form a complete route; an incomplete pair still follows the session model. */
68
- function autoReviewRouteConfigured(route) {
72
+ function deriveCompactionSettings(settings) {
73
+ return {
74
+ ...settings.compactProvider === void 0 ? {} : { provider: settings.compactProvider },
75
+ ...settings.compactModel === void 0 ? {} : { model: settings.compactModel }
76
+ };
77
+ }
78
+ /** Whether both fields of a model route form a complete pair; an incomplete pair stays off. */
79
+ function routePairConfigured(route) {
69
80
  return route !== void 0 && (route.provider ?? "").trim() !== "" && (route.model ?? "").trim() !== "";
70
81
  }
71
82
  //#endregion
@@ -618,6 +629,19 @@ window.__ModuleLoader__.load({
618
629
  return values.filter((value) => value !== void 0 && value !== "").join(" · ");
619
630
  }
620
631
  function RouteIcon({ kind }) {
632
+ if (kind === "compaction") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
633
+ width: "18",
634
+ height: "18",
635
+ viewBox: "0 0 18 18",
636
+ fill: "none",
637
+ "aria-hidden": "true",
638
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
639
+ d: "M5 3.5 9 7.5l4-4M5 14.5 9 10.5l4 4",
640
+ stroke: "currentColor",
641
+ strokeLinecap: "round",
642
+ strokeLinejoin: "round"
643
+ })
644
+ });
621
645
  if (kind === "main") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
622
646
  width: "18",
623
647
  height: "18",
@@ -890,45 +914,18 @@ window.__ModuleLoader__.load({
890
914
  const search = props.useSearchSettings((value) => value);
891
915
  const image = props.useImageSettings((value) => value);
892
916
  const autoReview = props.useAutoReviewSettings((value) => value);
917
+ const compaction = props.useCompactionSettings((value) => value);
893
918
  const switchSettings = props.useSwitchSettings((value) => value);
894
919
  const [open, setOpen] = (0, react.useState)();
895
920
  const [subagentDraft, setSubagentDraft, resetSubagent] = useDraft(subagent);
896
921
  const [searchDraft, setSearchDraft, resetSearch] = useDraft(search);
897
922
  const [imageDraft, setImageDraft, resetImage] = useDraft(image);
898
923
  const [autoReviewDraft, setAutoReviewDraft, resetAutoReview] = useDraft(autoReview);
924
+ const [compactionDraft, setCompactionDraft, resetCompaction] = useDraft(compaction);
899
925
  const [busy, setBusy] = (0, react.useState)();
900
926
  const [message, setMessage] = (0, react.useState)();
901
927
  const [compactBusy, setCompactBusy] = (0, react.useState)(false);
902
928
  const [compactError, setCompactError] = (0, react.useState)();
903
- const planCompactValue = switchSettings.value?.planCompactThreshold ?? 2e5;
904
- const [planCompactDraft, setPlanCompactDraft] = (0, react.useState)(() => String(planCompactValue));
905
- const [planCompactBusy, setPlanCompactBusy] = (0, react.useState)(false);
906
- const [planCompactError, setPlanCompactError] = (0, react.useState)();
907
- (0, react.useEffect)(() => {
908
- setPlanCompactDraft(String(planCompactValue));
909
- setPlanCompactError(void 0);
910
- }, [planCompactValue]);
911
- /** Commit the threshold on blur/Enter only when it is a non-negative integer. */
912
- const commitPlanCompactThreshold = () => {
913
- const parsed = Number(planCompactDraft);
914
- if (!Number.isInteger(parsed) || parsed < 0) {
915
- setPlanCompactError(props.t("planCompactThresholdInvalid"));
916
- return;
917
- }
918
- if (parsed === planCompactValue) {
919
- setPlanCompactError(void 0);
920
- return;
921
- }
922
- setPlanCompactBusy(true);
923
- setPlanCompactError(void 0);
924
- props.setPlanCompactThreshold(parsed).then(() => {
925
- setPlanCompactBusy(false);
926
- }, (error) => {
927
- setPlanCompactBusy(false);
928
- setPlanCompactDraft(String(planCompactValue));
929
- setPlanCompactError(error instanceof Error ? error.message : props.t("requestFailed"));
930
- });
931
- };
932
929
  const loadSearchCapabilities = props.loadCapabilities;
933
930
  const [searchSnapshot, setSearchSnapshot] = (0, react.useState)(void 0);
934
931
  const [searchError, setSearchError] = (0, react.useState)(void 0);
@@ -1020,13 +1017,16 @@ window.__ModuleLoader__.load({
1020
1017
  const searchAvailable = searchLive?.searchProviderAdapters.available ?? false;
1021
1018
  const imageChoices = capabilityChoices(groups, imageDraft, props.capabilities.imageProviderAdapters.providers ?? [], "image");
1022
1019
  const autoReviewChoices = deriveRouteChoices(groups, autoReviewDraft);
1023
- const autoReviewOn = autoReviewRouteConfigured(autoReview.value);
1020
+ const autoReviewOn = routePairConfigured(autoReview.value);
1021
+ const compactionChoices = deriveRouteChoices(groups, compactionDraft);
1022
+ const compactionOn = routePairConfigured(compaction.value);
1024
1023
  const synced = [
1025
1024
  main,
1026
1025
  subagent,
1027
1026
  search,
1028
1027
  image,
1029
- autoReview
1028
+ autoReview,
1029
+ compaction
1030
1030
  ].every((snapshot) => snapshot.status === "ready");
1031
1031
  const run = async (route, operation) => {
1032
1032
  if (busy !== void 0) return;
@@ -1092,6 +1092,13 @@ window.__ModuleLoader__.load({
1092
1092
  if (autoReview.value?.model !== autoReviewDraft.model) await props.setCapability("autoReview", "model", autoReviewDraft.model);
1093
1093
  });
1094
1094
  };
1095
+ const saveCompaction = () => {
1096
+ if (compactionDraft === void 0) return;
1097
+ run("compaction", async () => {
1098
+ if (compaction.value?.provider !== compactionDraft.provider) await props.setCapability("compaction", "provider", compactionDraft.provider);
1099
+ if (compaction.value?.model !== compactionDraft.model) await props.setCapability("compaction", "model", compactionDraft.model);
1100
+ });
1101
+ };
1095
1102
  /**
1096
1103
  * The reviewer switch: Off unsets both stored fields (follow the session
1097
1104
  * model); On seeds the route from Main so the pin takes effect immediately
@@ -1129,6 +1136,44 @@ window.__ModuleLoader__.load({
1129
1136
  });
1130
1137
  });
1131
1138
  };
1139
+ /**
1140
+ * The compaction switch: Off unsets both stored fields (official default =
1141
+ * the conversation target); On seeds the route from Main so the pin takes
1142
+ * effect immediately and stays editable below. A failed write reports,
1143
+ * never flips silently.
1144
+ */
1145
+ const persistCompactionEnabled = (enabled) => {
1146
+ if (busy === "compaction" || !compaction.writable || compaction.value === void 0) return;
1147
+ if (!enabled) {
1148
+ setOpen((current) => current === "compaction" ? void 0 : current);
1149
+ run("compaction", async () => {
1150
+ if (compaction.value?.provider !== void 0) await props.setCapability("compaction", "provider", void 0);
1151
+ if (compaction.value?.model !== void 0) await props.setCapability("compaction", "model", void 0);
1152
+ });
1153
+ return;
1154
+ }
1155
+ const seed = draft;
1156
+ if (seed === void 0 || seed.provider.trim() === "" || seed.model.trim() === "") return;
1157
+ setBusy("compaction");
1158
+ setMessage(void 0);
1159
+ (async () => {
1160
+ if (compaction.value?.provider !== seed.provider) await props.setCapability("compaction", "provider", seed.provider);
1161
+ if (compaction.value?.model !== seed.model) await props.setCapability("compaction", "model", seed.model);
1162
+ })().then(() => {
1163
+ setCompactionDraft({
1164
+ provider: seed.provider,
1165
+ model: seed.model
1166
+ });
1167
+ setBusy(void 0);
1168
+ setOpen("compaction");
1169
+ }, (error) => {
1170
+ setBusy(void 0);
1171
+ setMessage({
1172
+ route: "compaction",
1173
+ text: error instanceof Error ? error.message : props.t("requestFailed")
1174
+ });
1175
+ });
1176
+ };
1132
1177
  const mainSummary = draft === void 0 ? props.t("loading") : compact(routeName(groups, draft), mainEffectiveEffort);
1133
1178
  const subagentRole = subagentDraft?.mode === "fixed" && subagentDraft.provider !== void 0 && subagentDraft.provider !== "" ? props.providerRoleOf?.(subagentDraft.provider) : void 0;
1134
1179
  const subagentOn = subagentDraft?.mode === "fixed";
@@ -1136,6 +1181,7 @@ window.__ModuleLoader__.load({
1136
1181
  const subagentDisabled = subagent.status !== "ready" || !subagent.writable || subagentDraft === void 0 || busy === "subagent" || !subagentOn || (subagentDraft.provider ?? "").trim() === "" || (subagentDraft.model ?? "").trim() === "";
1137
1182
  const capabilityDisabled = (route, snapshot, next) => snapshot.status !== "ready" || !snapshot.writable || next === void 0 || busy === route || (next.provider ?? "").trim() === "" || (next.model ?? "").trim() === "";
1138
1183
  const autoReviewDisabled = autoReview.status !== "ready" || !autoReview.writable || autoReviewDraft === void 0 || busy === "autoReview" || !autoReviewOn || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "";
1184
+ const compactionDisabled = compaction.status !== "ready" || !compaction.writable || compactionDraft === void 0 || busy === "compaction" || !compactionOn || (compactionDraft.provider ?? "").trim() === "" || (compactionDraft.model ?? "").trim() === "";
1139
1185
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
1140
1186
  className: ModelSwitchSettings_module_css_default.section,
1141
1187
  children: [
@@ -1153,129 +1199,66 @@ window.__ModuleLoader__.load({
1153
1199
  }),
1154
1200
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1155
1201
  className: ModelSwitchSettings_module_css_default.group,
1156
- children: [
1157
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1158
- className: ModelSwitchSettings_module_css_default.groupLabel,
1159
- children: props.t("sendProtection")
1160
- }),
1161
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1162
- className: ModelSwitchSettings_module_css_default.rowCard,
1163
- children: [
1164
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1165
- className: ModelSwitchSettings_module_css_default.routeIcon,
1166
- "aria-hidden": "true",
1167
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1168
- width: "18",
1169
- height: "18",
1170
- viewBox: "0 0 18 18",
1171
- fill: "none",
1172
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1173
- d: "M4 5.5h10M4 9h7M4 12.5h5",
1174
- stroke: "currentColor",
1175
- strokeLinecap: "round"
1176
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1177
- d: "M13 11.5v3.5l2-1.6",
1178
- stroke: "currentColor",
1179
- strokeLinecap: "round",
1180
- strokeLinejoin: "round"
1181
- })]
1182
- })
1183
- }),
1184
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1185
- className: ModelSwitchSettings_module_css_default.rowCopy,
1186
- children: [
1187
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1188
- className: ModelSwitchSettings_module_css_default.rowTitle,
1189
- children: props.t("compactOnSwitch")
1190
- }),
1191
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1192
- className: ModelSwitchSettings_module_css_default.hint,
1193
- children: props.t("compactOnSwitchHelp")
1194
- }),
1195
- compactError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1196
- className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
1197
- children: compactError
1198
- })
1199
- ]
1200
- }),
1201
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1202
- checked: switchSettings.value?.compactOnSwitch !== false,
1203
- disabled: !switchSettings.writable || compactBusy,
1204
- label: props.t("compactOnSwitch"),
1205
- onChange: (value) => {
1206
- setCompactBusy(true);
1207
- setCompactError(void 0);
1208
- props.setCompactOnSwitch(value).then(() => {
1209
- setCompactBusy(false);
1210
- }, (error) => {
1211
- setCompactBusy(false);
1212
- setCompactError(error instanceof Error ? error.message : props.t("requestFailed"));
1213
- });
1214
- }
1202
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1203
+ className: ModelSwitchSettings_module_css_default.groupLabel,
1204
+ children: props.t("sendProtection")
1205
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1206
+ className: ModelSwitchSettings_module_css_default.rowCard,
1207
+ children: [
1208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1209
+ className: ModelSwitchSettings_module_css_default.routeIcon,
1210
+ "aria-hidden": "true",
1211
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1212
+ width: "18",
1213
+ height: "18",
1214
+ viewBox: "0 0 18 18",
1215
+ fill: "none",
1216
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1217
+ d: "M4 5.5h10M4 9h7M4 12.5h5",
1218
+ stroke: "currentColor",
1219
+ strokeLinecap: "round"
1220
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1221
+ d: "M13 11.5v3.5l2-1.6",
1222
+ stroke: "currentColor",
1223
+ strokeLinecap: "round",
1224
+ strokeLinejoin: "round"
1225
+ })]
1215
1226
  })
1216
- ]
1217
- }),
1218
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1219
- className: ModelSwitchSettings_module_css_default.rowCard,
1220
- children: [
1221
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1222
- className: ModelSwitchSettings_module_css_default.routeIcon,
1223
- "aria-hidden": "true",
1224
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1225
- width: "18",
1226
- height: "18",
1227
- viewBox: "0 0 18 18",
1228
- fill: "none",
1229
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1230
- d: "M4 5.5h10M4 9h7M4 12.5h5",
1231
- stroke: "currentColor",
1232
- strokeLinecap: "round"
1233
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1234
- d: "M13 11.5v3.5l2-1.6",
1235
- stroke: "currentColor",
1236
- strokeLinecap: "round",
1237
- strokeLinejoin: "round"
1238
- })]
1227
+ }),
1228
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1229
+ className: ModelSwitchSettings_module_css_default.rowCopy,
1230
+ children: [
1231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1232
+ className: ModelSwitchSettings_module_css_default.rowTitle,
1233
+ children: props.t("compactOnSwitch")
1234
+ }),
1235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1236
+ className: ModelSwitchSettings_module_css_default.hint,
1237
+ children: props.t("compactOnSwitchHelp")
1238
+ }),
1239
+ compactError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1240
+ className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
1241
+ children: compactError
1239
1242
  })
1240
- }),
1241
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1242
- className: ModelSwitchSettings_module_css_default.rowCopy,
1243
- children: [
1244
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1245
- className: ModelSwitchSettings_module_css_default.rowTitle,
1246
- children: props.t("planCompactThreshold")
1247
- }),
1248
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1249
- className: ModelSwitchSettings_module_css_default.hint,
1250
- children: props.t("planCompactThresholdHelp")
1251
- }),
1252
- planCompactError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1253
- className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
1254
- children: planCompactError
1255
- })
1256
- ]
1257
- }),
1258
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1259
- className: ModelSwitchSettings_module_css_default.input,
1260
- type: "number",
1261
- min: 0,
1262
- step: 1e3,
1263
- inputMode: "numeric",
1264
- "aria-label": props.t("planCompactThreshold"),
1265
- value: planCompactDraft,
1266
- disabled: !switchSettings.writable || planCompactBusy,
1267
- onChange: (event) => {
1268
- setPlanCompactDraft(event.target.value);
1269
- setPlanCompactError(void 0);
1270
- },
1271
- onBlur: commitPlanCompactThreshold,
1272
- onKeyDown: (event) => {
1273
- if (event.key === "Enter") commitPlanCompactThreshold();
1274
- }
1275
- })
1276
- ]
1277
- })
1278
- ]
1243
+ ]
1244
+ }),
1245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1246
+ checked: switchSettings.value?.compactOnSwitch !== false,
1247
+ disabled: !switchSettings.writable || compactBusy,
1248
+ label: props.t("compactOnSwitch"),
1249
+ onChange: (value) => {
1250
+ setCompactBusy(true);
1251
+ setCompactError(void 0);
1252
+ props.setCompactOnSwitch(value).then(() => {
1253
+ setCompactBusy(false);
1254
+ }, (error) => {
1255
+ setCompactBusy(false);
1256
+ setCompactError(error instanceof Error ? error.message : props.t("requestFailed"));
1257
+ });
1258
+ }
1259
+ })
1260
+ ]
1261
+ })]
1279
1262
  }),
1280
1263
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1281
1264
  className: ModelSwitchSettings_module_css_default.group,
@@ -1632,6 +1615,70 @@ window.__ModuleLoader__.load({
1632
1615
  onSave: saveAutoReview
1633
1616
  })
1634
1617
  ] })
1618
+ }),
1619
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1620
+ title: props.t("compaction"),
1621
+ summary: compactionDraft === void 0 ? props.t("loading") : compactionOn ? routeName(groups, compactionDraft) : props.t("compactionFollow"),
1622
+ hint: props.t("compactionHelp"),
1623
+ icon: "compaction",
1624
+ open: open === "compaction" && compactionOn,
1625
+ onToggle: () => {
1626
+ if (compactionOn) toggle("compaction");
1627
+ },
1628
+ expandable: compactionOn,
1629
+ trailing: compactionDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1630
+ checked: compactionOn,
1631
+ disabled: busy === "compaction" || !compaction.writable,
1632
+ label: props.t("compaction"),
1633
+ onChange: persistCompactionEnabled
1634
+ }),
1635
+ children: compactionDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1636
+ className: ModelSwitchSettings_module_css_default.hint,
1637
+ children: props.t("loading")
1638
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1639
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1640
+ className: ModelSwitchSettings_module_css_default.hint,
1641
+ children: props.t("compactionRouteHelp")
1642
+ }),
1643
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1644
+ className: ModelSwitchSettings_module_css_default.formGrid,
1645
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1646
+ label: props.t("provider"),
1647
+ value: compactionDraft.provider ?? "",
1648
+ disabled: busy === "compaction" || !compaction.writable,
1649
+ choices: compactionChoices.providers,
1650
+ onChange: (provider) => {
1651
+ const first = groups.find((group) => group.id === provider)?.models[0];
1652
+ setCompactionDraft({
1653
+ provider,
1654
+ ...first === void 0 ? {} : { model: first.id }
1655
+ });
1656
+ }
1657
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1658
+ label: props.t("model"),
1659
+ value: compactionDraft.model ?? "",
1660
+ disabled: busy === "compaction" || !compaction.writable,
1661
+ choices: compactionChoices.models,
1662
+ onChange: (model) => {
1663
+ setCompactionDraft({
1664
+ ...compactionDraft,
1665
+ model
1666
+ });
1667
+ }
1668
+ })]
1669
+ }),
1670
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
1671
+ t: props.t,
1672
+ busy: busy === "compaction",
1673
+ disabled: compactionDisabled,
1674
+ ...message?.route === "compaction" ? { message: message.text } : {},
1675
+ onCancel: () => {
1676
+ resetCompaction();
1677
+ setMessage(void 0);
1678
+ },
1679
+ onSave: saveCompaction
1680
+ })
1681
+ ] })
1635
1682
  })
1636
1683
  ]
1637
1684
  })
@@ -1680,9 +1727,10 @@ window.__ModuleLoader__.load({
1680
1727
  autoReviewFollow: "跟随会话模型",
1681
1728
  autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。",
1682
1729
  autoReviewRouteHelp: "审查请求固定使用这里的 Provider/模型,与会话模型无关;保存后对下一次审查立即生效。",
1683
- planCompactThreshold: "计划执行前压缩阈值",
1684
- planCompactThresholdHelp: "切换模型执行计划时,若上下文估算超过该 Token 数,先用切换后的模型压缩一次历史再提交执行;未切换模型不触发。0 = 关闭。压缩会产生额外模型用量,摘要可能省略细节。",
1685
- planCompactThresholdInvalid: "请输入不小于 0 的整数。",
1730
+ compaction: "压缩模型",
1731
+ compactionFollow: "跟随会话模型",
1732
+ compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。",
1733
+ compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。",
1686
1734
  "reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
1687
1735
  "reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
1688
1736
  "reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
@@ -1730,9 +1778,10 @@ window.__ModuleLoader__.load({
1730
1778
  autoReviewFollow: "Follows the session model",
1731
1779
  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.",
1732
1780
  autoReviewRouteHelp: "Review requests use exactly this provider/model regardless of the session model; saved changes apply to the very next review.",
1733
- planCompactThreshold: "Plan-exit compaction threshold",
1734
- planCompactThresholdHelp: "When a Plan is approved with a switched model and the estimated context exceeds this many tokens, compact history once with the switched-to model before committing. Approvals without a model switch never trigger it. 0 disables. Compaction uses extra model quota and may omit details.",
1735
- planCompactThresholdInvalid: "Enter an integer of 0 or more.",
1781
+ compaction: "Compaction model",
1782
+ compactionFollow: "Follows the conversation model",
1783
+ compactionHelp: "Pick the summarization model for official compaction (/compact and official automatic compaction). When off (both fields empty), the official default applies: the conversation target model.",
1784
+ compactionRouteHelp: "Summarization requests use exactly this provider/model regardless of the conversation target; saved changes apply to the very next compaction. Saving while a compaction is running may interrupt it; it retries next time.",
1736
1785
  "reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
1737
1786
  "reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
1738
1787
  "reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
@@ -1826,14 +1875,6 @@ window.__ModuleLoader__.load({
1826
1875
  return false;
1827
1876
  }
1828
1877
  }
1829
- //#endregion
1830
- //#region src/picker/plan-compact.ts
1831
- /** Runtime guard for the wire value returned by the Host RPC. */
1832
- function isPlanCompactOutcome(value) {
1833
- if (value === null || typeof value !== "object") return false;
1834
- const status = value.status;
1835
- return status === "disabled" || status === "skipped" || status === "compacted" || status === "empty" || status === "error";
1836
- }
1837
1878
  const CONTEXT_SUFFIX = /-(\d+)(k|m)$/iu;
1838
1879
  /** Peel Fast and `-<n>k` / `-<n>m` in either order. Product names like `-max` stay. */
1839
1880
  function parsePickerId(id) {
@@ -3119,7 +3160,7 @@ window.__ModuleLoader__.load({
3119
3160
  };
3120
3161
  }
3121
3162
  //#endregion
3122
- //#region node_modules/.pnpm/dsh-llm-providers-ui@https+_f1dadb1b56406564fc1bb8f2d953b82d/node_modules/dsh-llm-providers-ui/lib/order.js
3163
+ //#region node_modules/.pnpm/dsh-llm-providers-ui@https+++github.com+NOirBRight+dsh-llm-providers-ui+releases+downlo_f1dadb1b56406564fc1bb8f2d953b82d/node_modules/dsh-llm-providers-ui/lib/order.js
3123
3164
  /** Display order for installed provider cards when the user has not saved one. */
3124
3165
  const PROVIDER_ITEM_ORDER = [
3125
3166
  "llm-cursor",
@@ -3407,7 +3448,28 @@ window.__ModuleLoader__.load({
3407
3448
  };
3408
3449
  }
3409
3450
  /**
3410
- * Make the plan text this session's armed goal, replacing any unfinished goal.
3451
+ * Address the plan document exactly as the official opener does.
3452
+ *
3453
+ * A logged plan (`callId` present) resolves from `dsh-resource://plan/...`
3454
+ * for as long as the session log holds the call; before that the pending
3455
+ * review's own `dsh-resource://plan-review/...` address is the one that
3456
+ * opens its preview.
3457
+ *
3458
+ * @param sessionId - the session that owns the review.
3459
+ * @param ref - logged call identity plus the pending request key.
3460
+ * @returns The plan resource address.
3461
+ */
3462
+ function planDocumentAddress(sessionId, ref) {
3463
+ const encode = encodeURIComponent;
3464
+ return ref.callId === void 0 ? `dsh-resource://plan-review/${encode(sessionId)}/${encode(ref.requestKey)}` : `dsh-resource://plan/${encode(sessionId)}/${encode(ref.callId)}`;
3465
+ }
3466
+ /**
3467
+ * Make this plan the session's armed goal, replacing any unfinished goal.
3468
+ *
3469
+ * The objective is a short instruction carrying the plan document's address,
3470
+ * not the plan's raw markdown: goal objectives are injected into every round,
3471
+ * and a full plan would be truncated by length limits mid-sentence. The round
3472
+ * driver reads the plan from the address instead.
3411
3473
  *
3412
3474
  * Ordering mirrors the card's own commit-then-answer rule: a refusal here keeps
3413
3475
  * the Plan pending instead of approving work that no goal tracks. The replace
@@ -3416,17 +3478,18 @@ window.__ModuleLoader__.load({
3416
3478
  *
3417
3479
  * @param remote - the released goal remote, absent when no goal package is mounted.
3418
3480
  * @param sessionId - the session that owns both the review and the goal.
3419
- * @param plan - the plan markdown under review.
3481
+ * @param plan - the plan markdown under review (validated non-empty).
3482
+ * @param ref - the plan document identity used for its address.
3420
3483
  * @returns Whether the goal now tracks this plan, or why it does not.
3421
3484
  */
3422
- async function setPlanAsGoal(remote, sessionId, plan) {
3485
+ async function setPlanAsGoal(remote, sessionId, plan, ref) {
3423
3486
  if (remote === void 0) return { kind: "unsupported" };
3424
- const objective = plan.trim();
3425
- if (objective === "") return {
3487
+ if (plan.trim() === "") return {
3426
3488
  kind: "failed",
3427
3489
  code: "GOAL_INVALID_OBJECTIVE",
3428
3490
  message: "goal objective must be a non-empty string"
3429
3491
  };
3492
+ const objective = `持续推进当前plan直到完成(plan 文档:${planDocumentAddress(sessionId, ref)})`;
3430
3493
  const created = await remote.create(sessionId, { objective });
3431
3494
  if (created.ok) return { kind: "created" };
3432
3495
  if (created.error.code !== "GOAL_ALREADY_EXISTS") return {
@@ -3624,12 +3687,11 @@ window.__ModuleLoader__.load({
3624
3687
  t: props.t,
3625
3688
  goalRemote: props.goalRemote,
3626
3689
  planPreview: props.planPreview,
3627
- ...props.planCompact === void 0 ? {} : { planCompact: props.planCompact },
3628
3690
  ...props.resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations: props.resolveInteractionOperations },
3629
3691
  ...props.roleOf === void 0 ? {} : { roleOf: props.roleOf }
3630
3692
  }, props.matched.key);
3631
3693
  }
3632
- function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, settingsUnavailableReason, directory, t, goalRemote, planPreview, planCompact, resolveInteractionOperations, roleOf }) {
3694
+ function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, settingsUnavailableReason, directory, t, goalRemote, planPreview, resolveInteractionOperations, roleOf }) {
3633
3695
  const { snapshot, getDirectorySnapshot, load, select } = directory;
3634
3696
  const [execution, setExecution] = (0, react.useState)(snapshot.current ?? void 0);
3635
3697
  const [feedback, setFeedback] = (0, react.useState)("");
@@ -3715,36 +3777,9 @@ window.__ModuleLoader__.load({
3715
3777
  throw new Error(message === null ? t("plan.modelFailed") : t("error.action", { message }));
3716
3778
  }
3717
3779
  };
3718
- /**
3719
- * Compact the history before a switched approval commits anything.
3720
- *
3721
- * Only an approval that changes provider/model triggers the Host check —
3722
- * effort-only changes and same-model approvals execute untouched. A failed
3723
- * compaction keeps the Plan pending and retryable; `empty` announces that
3724
- * nothing safe could be reduced and execution proceeds.
3725
- */
3726
- const compactBeforeRun = async () => {
3727
- if (execution === void 0) return;
3728
- const current = snapshot.current;
3729
- if (!(current === null || current === void 0 || current.provider !== execution.provider || current.model !== execution.model)) return;
3730
- if (planCompact === void 0) {
3731
- console.warn("dsh-model-switch: plan compaction seam absent; executing the switched Plan without it");
3732
- return;
3733
- }
3734
- setNotice(t("plan.compacting", { model: execution.model }));
3735
- let outcome;
3736
- try {
3737
- outcome = await planCompact(matched.sessionId, execution.provider, execution.model);
3738
- } catch (cause) {
3739
- throw new Error(t("plan.compactFailed", { message: cause instanceof Error ? cause.message : String(cause) }));
3740
- }
3741
- if (outcome.status === "error") throw new Error(t("plan.compactFailed", { message: outcome.message ?? "unknown failure" }));
3742
- setNotice(outcome.status === "empty" ? t("plan.compactEmpty") : outcome.status === "compacted" ? t("plan.compacted") : null);
3743
- };
3744
3780
  const onApprove = () => {
3745
3781
  if (execution === void 0 || !executionAllowed || !available || busy || blocked) return;
3746
3782
  settle(async () => {
3747
- await compactBeforeRun();
3748
3783
  await approveWith(execution);
3749
3784
  });
3750
3785
  };
@@ -3759,8 +3794,10 @@ window.__ModuleLoader__.load({
3759
3794
  const onRunAsGoal = () => {
3760
3795
  if (execution === void 0 || !executionAllowed || !available || busy || blocked || goal === void 0) return;
3761
3796
  settle(async () => {
3762
- await compactBeforeRun();
3763
- const handoff = await setPlanAsGoal(goal, matched.sessionId, review.plan);
3797
+ const handoff = await setPlanAsGoal(goal, matched.sessionId, review.plan, {
3798
+ ...review.callId === void 0 ? {} : { callId: review.callId },
3799
+ requestKey: matched.key
3800
+ });
3764
3801
  if (handoff.kind === "unsupported") throw new Error(t("plan.goalUnsupported"));
3765
3802
  if (handoff.kind === "failed") {
3766
3803
  const message = handoff.code === void 0 ? handoff.message : `${handoff.code}: ${handoff.message}`;
@@ -3998,10 +4035,6 @@ window.__ModuleLoader__.load({
3998
4035
  "plan.goalUnsupported": "当前部署未挂载 goal 服务,无法把计划设为目标",
3999
4036
  "plan.responseRejected": "计划答复已被另一客户端处理;已提交的模型切换无法由插件回滚。",
4000
4037
  "plan.cancelRejected": "计划审查已被另一客户端处理,无法返回讨论。",
4001
- "plan.compacting": "正在压缩上下文,将使用 {model}…",
4002
- "plan.compacted": "上下文已压缩,开始执行。",
4003
- "plan.compactEmpty": "没有可压缩的历史,继续执行。",
4004
- "plan.compactFailed": "压缩上下文失败:{message}(计划仍待批准,可重试)",
4005
4038
  "markdown.copy": "复制代码",
4006
4039
  "markdown.copied": "已复制",
4007
4040
  "markdown.footnotes": "脚注"
@@ -4058,10 +4091,6 @@ window.__ModuleLoader__.load({
4058
4091
  "plan.goalUnsupported": "This deployment mounts no goal service, so the Plan cannot be set as a goal",
4059
4092
  "plan.responseRejected": "Another client already handled this Plan response. A committed model change cannot be rolled back by this plugin.",
4060
4093
  "plan.cancelRejected": "Another client already handled this Plan review, so it cannot return to discussion.",
4061
- "plan.compacting": "Compacting context with {model}…",
4062
- "plan.compacted": "Context compacted; starting execution.",
4063
- "plan.compactEmpty": "No compactable history; continuing.",
4064
- "plan.compactFailed": "Context compaction failed: {message} (the Plan stays pending and can be retried)",
4065
4094
  "markdown.copy": "Copy code",
4066
4095
  "markdown.copied": "Copied",
4067
4096
  "markdown.footnotes": "Footnotes"
@@ -4084,45 +4113,6 @@ window.__ModuleLoader__.load({
4084
4113
  }
4085
4114
  const EMPTY_ORDER = [];
4086
4115
  /**
4087
- * Ask the Host to compact this session before a switched approval executes.
4088
- *
4089
- * An absent connection degrades to `skipped` (with a console note): the
4090
- * mechanism protects against long-context timeouts, it must not brick plan
4091
- * approvals in environments that expose no Host RPC at all.
4092
- * @param rpcOf - resolves the live connection RPC.
4093
- * @returns The card-facing runner bound to this seat's connection.
4094
- */
4095
- function planCompactFrom(rpcOf) {
4096
- return async (sessionId, provider, model) => {
4097
- const rpc = rpcOf();
4098
- if (rpc === void 0) {
4099
- console.warn("dsh-model-switch: Host RPC unavailable; executing the Plan without a compaction check");
4100
- return { status: "skipped" };
4101
- }
4102
- try {
4103
- const response = await callPluginRpc(rpc, "plugin-rpc/model-switch", "plan-compact", {
4104
- sessionId,
4105
- provider,
4106
- model
4107
- });
4108
- if (!response.ok) return {
4109
- status: "error",
4110
- message: "the Host rejected the plan compaction request"
4111
- };
4112
- if (!isPlanCompactOutcome(response.value)) return {
4113
- status: "error",
4114
- message: "the Host returned an unreadable plan compaction verdict"
4115
- };
4116
- return response.value;
4117
- } catch (cause) {
4118
- return {
4119
- status: "error",
4120
- message: cause instanceof Error ? cause.message : String(cause)
4121
- };
4122
- }
4123
- };
4124
- }
4125
- /**
4126
4116
  * Resolve the released goal remote without hard-depending on it.
4127
4117
  *
4128
4118
  * `remote.goals` exists only when the deployment mounts `@deepseek-ai/dsh-goal`,
@@ -4282,7 +4272,6 @@ window.__ModuleLoader__.load({
4282
4272
  getMainDefaultsSnapshot: () => mainDefaults.getSnapshot(),
4283
4273
  goalRemote: () => goalRemoteFrom(scope),
4284
4274
  planPreview,
4285
- planCompact: planCompactFrom(rpcOf),
4286
4275
  hooks: {
4287
4276
  directory: directory.store,
4288
4277
  providerOrder: orderStore
@@ -4425,6 +4414,7 @@ window.__ModuleLoader__.load({
4425
4414
  const search = deriveConfigForm(owned, deriveSearchSettings, SEARCH_SETTINGS_FIELDS);
4426
4415
  const image = deriveConfigForm(owned, deriveImageSettings, IMAGE_SETTINGS_FIELDS);
4427
4416
  const autoReview = deriveConfigForm(owned, deriveAutoReviewSettings, AUTO_REVIEW_SETTINGS_FIELDS);
4417
+ const compaction = deriveConfigForm(owned, deriveCompactionSettings, COMPACTION_SETTINGS_FIELDS);
4428
4418
  const saveMain = async (next, expectedRevision) => {
4429
4419
  if (!await mainForm.mutate([
4430
4420
  {
@@ -4490,22 +4480,19 @@ window.__ModuleLoader__.load({
4490
4480
  searchSettings: search,
4491
4481
  imageSettings: image,
4492
4482
  autoReviewSettings: autoReview,
4483
+ compactionSettings: compaction,
4493
4484
  switchSettings: owned
4494
4485
  },
4495
4486
  capabilities: RUNTIME_CAPABILITIES,
4496
4487
  saveMain,
4497
4488
  setSubagent: (field, value) => value === void 0 ? subagent.unset(field) : subagent.set(field, value),
4498
4489
  setCapability: (route, field, value) => {
4499
- const scope = route === "search" ? search : route === "image" ? image : autoReview;
4490
+ const scope = route === "search" ? search : route === "image" ? image : route === "compaction" ? compaction : autoReview;
4500
4491
  return value === void 0 ? scope.unset(field) : scope.set(field, value);
4501
4492
  },
4502
4493
  setCompactOnSwitch: async (value) => {
4503
4494
  if (!await owned.set("compactOnSwitch", value)) throw new Error("settings-rejected");
4504
4495
  },
4505
- setPlanCompactThreshold: async (value) => {
4506
- if (!Number.isInteger(value) || value < 0) throw new Error("settings-rejected");
4507
- if (!await owned.set("planCompactThreshold", value)) throw new Error("settings-rejected");
4508
- },
4509
4496
  providerRoleOf,
4510
4497
  loadCatalog: async () => {
4511
4498
  const response = await remote.session.modelCatalog();