dsh-model-switch 0.4.22 → 0.4.24

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
@@ -10,11 +10,7 @@ window.__ModuleLoader__.load({
10
10
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
11
  //#region src/client-contract.ts
12
12
  const MODEL_SWITCH_CONFIG_ID = "model-switch";
13
- const MAIN_DEFAULT_CONFIG_ID = "agent-default-model";
14
13
  const PROVIDERS_CONFIG_ID = "llm-providers-ui";
15
- var MainSettingsConflictError = class extends Error {
16
- name = "MainSettingsConflictError";
17
- };
18
14
  const SUBAGENT_SETTINGS_FIELDS = Object.freeze({
19
15
  mode: "subagentMode",
20
16
  provider: "subagentProvider",
@@ -43,6 +39,11 @@ window.__ModuleLoader__.load({
43
39
  provider: "compactProvider",
44
40
  model: "compactModel"
45
41
  });
42
+ /** Automatic session-title naming route; both unset means the official default (the session's own logged route). */
43
+ const TITLE_SETTINGS_FIELDS = Object.freeze({
44
+ provider: "titleProvider",
45
+ model: "titleModel"
46
+ });
46
47
  function deriveSubagentSettings(settings) {
47
48
  return {
48
49
  mode: settings.subagentMode,
@@ -75,6 +76,12 @@ window.__ModuleLoader__.load({
75
76
  ...settings.compactModel === void 0 ? {} : { model: settings.compactModel }
76
77
  };
77
78
  }
79
+ function deriveTitleSettings(settings) {
80
+ return {
81
+ ...settings.titleProvider === void 0 ? {} : { provider: settings.titleProvider },
82
+ ...settings.titleModel === void 0 ? {} : { model: settings.titleModel }
83
+ };
84
+ }
78
85
  /** Whether both fields of a model route form a complete pair; an incomplete pair stays off. */
79
86
  function routePairConfigured(route) {
80
87
  return route !== void 0 && (route.provider ?? "").trim() !== "" && (route.model ?? "").trim() !== "";
@@ -88,7 +95,9 @@ window.__ModuleLoader__.load({
88
95
  "autoReviewModel",
89
96
  "subagentProvider",
90
97
  "subagentModel",
91
- "subagentReasoningEffort"
98
+ "subagentReasoningEffort",
99
+ "titleProvider",
100
+ "titleModel"
92
101
  ]);
93
102
  /** Own data property, so a literal '__proto__' provider id can never touch a prototype. */
94
103
  function setKey(target, key, value) {
@@ -151,62 +160,6 @@ window.__ModuleLoader__.load({
151
160
  if (!Object.prototype.hasOwnProperty.call(groups, provider)) return void 0;
152
161
  return groups[provider];
153
162
  }
154
- /** A complete pinned pair; either half missing leaves the capability at its default. */
155
- function pair(group, providerField, modelField) {
156
- const provider = group[providerField];
157
- const model = group[modelField];
158
- return present(provider) && present(model) ? {
159
- provider,
160
- model
161
- } : void 0;
162
- }
163
- /**
164
- * Ordered writes applying one group with full-replacement semantics: pinned
165
- * capabilities land as pairs, everything else reverts to its default state
166
- * (compaction/review follow the session model; Subagent follows official
167
- * inherit). The mode write comes last so a fixed route never publishes with
168
- * the previous provider's fields still half-written.
169
- */
170
- function providerGroupWrites(group) {
171
- const compact = pair(group, "compactProvider", "compactModel");
172
- const review = pair(group, "autoReviewProvider", "autoReviewModel");
173
- const subagent = pair(group, "subagentProvider", "subagentModel");
174
- const effort = subagent !== void 0 && present(group.subagentReasoningEffort) ? group.subagentReasoningEffort : void 0;
175
- return [
176
- {
177
- field: "compactProvider",
178
- value: compact?.provider
179
- },
180
- {
181
- field: "compactModel",
182
- value: compact?.model
183
- },
184
- {
185
- field: "autoReviewProvider",
186
- value: review?.provider
187
- },
188
- {
189
- field: "autoReviewModel",
190
- value: review?.model
191
- },
192
- {
193
- field: "subagentProvider",
194
- value: subagent?.provider
195
- },
196
- {
197
- field: "subagentModel",
198
- value: subagent?.model
199
- },
200
- {
201
- field: "subagentReasoningEffort",
202
- value: effort
203
- },
204
- {
205
- field: "subagentMode",
206
- mode: subagent !== void 0 ? "fixed" : "follow-main"
207
- }
208
- ];
209
- }
210
163
  /** Storage entry for one provider's group; absent or blank route fields become ''. */
211
164
  function entryOf(provider, group) {
212
165
  return {
@@ -217,7 +170,9 @@ window.__ModuleLoader__.load({
217
170
  autoReviewModel: group.autoReviewModel ?? "",
218
171
  subagentProvider: group.subagentProvider ?? "",
219
172
  subagentModel: group.subagentModel ?? "",
220
- subagentReasoningEffort: group.subagentReasoningEffort ?? ""
173
+ subagentReasoningEffort: group.subagentReasoningEffort ?? "",
174
+ titleProvider: group.titleProvider ?? "",
175
+ titleModel: group.titleModel ?? ""
221
176
  };
222
177
  }
223
178
  /**
@@ -234,19 +189,6 @@ window.__ModuleLoader__.load({
234
189
  }
235
190
  return Object.entries(groups).map(([key, value]) => entryOf(key, value));
236
191
  }
237
- /**
238
- * Apply a saved group's routes to the live flat settings.
239
- * @returns true when a group existed and every write settled; false when the
240
- * provider has no group (nothing was written). A rejected write propagates.
241
- */
242
- async function applyProviderGroupTo(sink, groups, provider) {
243
- const group = groupOf(groups, provider);
244
- if (group === void 0) return false;
245
- for (const write of providerGroupWrites(group)) if (write.field === "subagentMode") await sink.set("subagentMode", write.mode);
246
- else if (write.value === void 0) await sink.unset(write.field);
247
- else await sink.set(write.field, write.value);
248
- return true;
249
- }
250
192
  //#endregion
251
193
  //#region src/client/derived-config-form.ts
252
194
  function deriveConfigForm(source, project, fields) {
@@ -267,11 +209,28 @@ window.__ModuleLoader__.load({
267
209
  const sourceField = fields[field];
268
210
  if (!(operation === "set" ? await source.set(sourceField, value) : await source.unset(sourceField))) throw new Error("settings-rejected");
269
211
  };
212
+ /**
213
+ * Batch write: map every view field through the same `fields` table `set`/`unset`
214
+ * use, so one atomic source mutate can carry a whole row's worth of edits under
215
+ * a single revision fence instead of N independent fenced writes.
216
+ */
217
+ const mutate = async (ops, expectedRevision) => {
218
+ const mapped = ops.map((op) => op.value === void 0 ? {
219
+ op: "unset",
220
+ path: [fields[op.field]]
221
+ } : {
222
+ op: "set",
223
+ path: [fields[op.field]],
224
+ value: op.value
225
+ });
226
+ if (!await source.mutate(mapped, expectedRevision)) throw new Error("settings-rejected");
227
+ };
270
228
  return {
271
229
  getSnapshot,
272
230
  subscribe: (listener) => source.subscribe(listener),
273
231
  set: (field, value) => write("set", field, value),
274
- unset: (field) => write("unset", field)
232
+ unset: (field) => write("unset", field),
233
+ mutate
275
234
  };
276
235
  }
277
236
  //#endregion
@@ -604,65 +563,20 @@ window.__ModuleLoader__.load({
604
563
  models
605
564
  };
606
565
  }
607
- function expectedMainRevision(mirror, accepted) {
608
- return Math.max(mirror, accepted ?? mirror);
609
- }
610
- function acceptedRevisionAfterFailure(accepted, error) {
611
- return error instanceof MainSettingsConflictError ? void 0 : accepted;
612
- }
613
- function deriveMainChoices(groups, draft) {
614
- const providers = groups.map((group) => ({
615
- id: group.id,
616
- name: group.name
617
- }));
618
- if (draft !== void 0 && !providers.some((option) => option.id === draft.provider)) providers.push({
619
- id: draft.provider,
620
- name: draft.provider
621
- });
622
- const group = groups.find((item) => item.id === draft?.provider);
623
- const models = (group?.models ?? []).map((model) => ({
624
- id: model.id,
625
- name: model.name
626
- }));
627
- if (draft !== void 0 && !models.some((option) => option.id === draft.model)) models.push({
628
- id: draft.model,
629
- name: draft.model
630
- });
631
- const efforts = ((group?.models.find((item) => item.id === draft?.model))?.reasoning?.efforts ?? []).map((effort) => ({
632
- id: effort.id,
633
- name: effort.name
634
- }));
635
- if (draft?.reasoningEffort !== void 0 && !efforts.some((option) => option.id === draft.reasoningEffort)) efforts.push({
636
- id: draft.reasoningEffort,
637
- name: draft.reasoningEffort
638
- });
639
- return {
640
- providers,
641
- models,
642
- efforts
643
- };
644
- }
645
- function useModelSwitchSettingsController(input) {
646
- const main = input.useMainSettings((value) => value);
647
- const subagent = input.useSubagentSettings((value) => value);
648
- const [draft, setDraft] = (0, react.useState)(main.value);
566
+ /** Load the model catalog backing every route editor, reloading on provider-order changes. */
567
+ function useSettingsCatalog(input) {
649
568
  const [groups, setGroups] = (0, react.useState)([]);
650
- const [busy, setBusy] = (0, react.useState)(false);
651
- const [message, setMessage] = (0, react.useState)();
652
- const locked = (0, react.useRef)(false);
653
- const acceptedRevision = (0, react.useRef)();
654
- (0, react.useEffect)(() => {
655
- setDraft(main.value);
656
- setMessage(void 0);
657
- if (main.revision !== void 0 && (acceptedRevision.current === void 0 || main.revision > acceptedRevision.current)) acceptedRevision.current = main.revision;
658
- }, [main.revision, main.value]);
569
+ const [catalogError, setCatalogError] = (0, react.useState)();
659
570
  (0, react.useEffect)(() => {
660
571
  let live = true;
661
572
  const load = () => {
662
573
  input.loadCatalog().then((value) => {
663
- if (live) setGroups(value);
574
+ if (live) {
575
+ setGroups(value);
576
+ setCatalogError(void 0);
577
+ }
664
578
  }).catch(() => {
665
- if (live) setMessage(input.t("catalogFailed"));
579
+ if (live) setCatalogError(input.t("catalogFailed"));
666
580
  });
667
581
  };
668
582
  load();
@@ -678,63 +592,9 @@ window.__ModuleLoader__.load({
678
592
  input.subscribeProviderOrder,
679
593
  input.t
680
594
  ]);
681
- const { providers, models, efforts } = deriveMainChoices(groups, draft);
682
- const disabled = main.status !== "ready" || !main.writable || draft === void 0 || busy || draft.provider.trim() === "" || draft.model.trim() === "";
683
- const setProvider = (provider) => setDraft((current) => {
684
- if (current === void 0) return current;
685
- const first = groups.find((item) => item.id === provider)?.models[0];
686
- return selectRouteModel(groups, provider, first?.id ?? current.model);
687
- });
688
- const setModel = (id) => setDraft((current) => current === void 0 ? current : selectRouteModel(groups, current.provider, id));
689
- const setReasoningEffort = (value) => setDraft((current) => {
690
- if (current === void 0) return current;
691
- const next = { ...current };
692
- if (value === "") delete next.reasoningEffort;
693
- else next.reasoningEffort = value;
694
- return next;
695
- });
696
- const reset = () => {
697
- setDraft(main.value);
698
- setMessage(void 0);
699
- };
700
- const save = async () => {
701
- if (disabled || locked.current || draft === void 0) return;
702
- const mirrorRevision = main.revision;
703
- if (mirrorRevision === void 0) {
704
- setMessage(input.t("requestFailed"));
705
- return;
706
- }
707
- const expectedRevision = expectedMainRevision(mirrorRevision, acceptedRevision.current);
708
- locked.current = true;
709
- setBusy(true);
710
- setMessage(void 0);
711
- try {
712
- acceptedRevision.current = await input.saveMain(draft, expectedRevision);
713
- setMessage(input.t("saved"));
714
- } catch (error) {
715
- acceptedRevision.current = acceptedRevisionAfterFailure(acceptedRevision.current, error);
716
- setMessage(error instanceof Error ? error.message : input.t("requestFailed"));
717
- } finally {
718
- locked.current = false;
719
- setBusy(false);
720
- }
721
- };
722
595
  return {
723
- main,
724
- subagent,
725
- draft,
726
596
  groups,
727
- providers,
728
- models,
729
- efforts,
730
- busy,
731
- message,
732
- disabled,
733
- setProvider,
734
- setModel,
735
- setReasoningEffort,
736
- reset,
737
- save
597
+ catalogError
738
598
  };
739
599
  }
740
600
  //#endregion
@@ -843,18 +703,6 @@ window.__ModuleLoader__.load({
843
703
  strokeLinejoin: "round"
844
704
  })
845
705
  });
846
- if (kind === "main") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
847
- width: "18",
848
- height: "18",
849
- viewBox: "0 0 18 18",
850
- fill: "none",
851
- "aria-hidden": "true",
852
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
853
- d: "M3 4.5h12v8H8l-3.5 2v-2H3v-8Z",
854
- stroke: "currentColor",
855
- strokeLinejoin: "round"
856
- })
857
- });
858
706
  if (kind === "subagent") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
859
707
  width: "18",
860
708
  height: "18",
@@ -943,6 +791,23 @@ window.__ModuleLoader__.load({
943
791
  strokeLinejoin: "round"
944
792
  })]
945
793
  });
794
+ if (kind === "sessionTitle") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
795
+ width: "18",
796
+ height: "18",
797
+ viewBox: "0 0 18 18",
798
+ fill: "none",
799
+ "aria-hidden": "true",
800
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
801
+ d: "M3 4.5h7.5l4.2 4.2-5.7 5.7L3 9.8V4.5Z",
802
+ stroke: "currentColor",
803
+ strokeLinejoin: "round"
804
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
805
+ cx: "6.2",
806
+ cy: "7.2",
807
+ r: "1.1",
808
+ stroke: "currentColor"
809
+ })]
810
+ });
946
811
  return null;
947
812
  }
948
813
  function RouteCard({ title, summary, hint, icon, open, onToggle, disabled = false, badge, badgeWarn = false, trailing, expandable = true, children }) {
@@ -1067,17 +932,34 @@ window.__ModuleLoader__.load({
1067
932
  ]
1068
933
  });
1069
934
  }
935
+ /**
936
+ * One editable row draft.
937
+ *
938
+ * Only a PRISTINE draft follows the accepted snapshot. A draft the user has
939
+ * edited is never overwritten by an incoming snapshot — another tab's write, or
940
+ * the recovery read that follows a refused write, would otherwise discard the
941
+ * typed values and make the page look like it rolled back on its own. The user
942
+ * either saves the draft or cancels it back to the accepted value.
943
+ * @param snapshot - the accepted form snapshot for this row.
944
+ * @returns the draft, its setter, and a reset that re-adopts the accepted value.
945
+ */
1070
946
  function useDraft(snapshot) {
1071
947
  const [draft, setDraft] = (0, react.useState)(snapshot.value);
948
+ const pristine = (0, react.useRef)(true);
1072
949
  (0, react.useEffect)(() => {
950
+ if (!pristine.current) return;
1073
951
  setDraft(snapshot.value);
1074
952
  }, [snapshot.revision, snapshot.value]);
1075
953
  return [
1076
954
  draft,
1077
- setDraft,
1078
- () => {
955
+ (0, react.useCallback)((value) => {
956
+ pristine.current = false;
957
+ setDraft(value);
958
+ }, []),
959
+ (0, react.useCallback)(() => {
960
+ pristine.current = true;
1079
961
  setDraft(snapshot.value);
1080
- }
962
+ }, [snapshot.value])
1081
963
  ];
1082
964
  }
1083
965
  function routeName(groups, route) {
@@ -1091,6 +973,22 @@ window.__ModuleLoader__.load({
1091
973
  const matched = matchCatalogModel(groups.find((group) => group.id === route.provider)?.models ?? [], route.model);
1092
974
  return matched?.effort ?? matched?.model.reasoning?.defaultEffort;
1093
975
  }
976
+ /**
977
+ * The effort caption for the subagent summary: a stored effort is the pinned
978
+ * one ("已固定 · High"); an unset effort follows the provider default and says
979
+ * so ("Provider 默认 · High"), using the same effort-name lookup the picker does.
980
+ */
981
+ function subagentEffortSegment(t, matched, pinned, defaultEffort) {
982
+ const effortName = (id) => matched?.model.reasoning?.efforts.find((level) => level.id === id)?.name ?? id;
983
+ if (pinned !== "") return compact(t("effortPinned"), effortName(pinned));
984
+ return defaultEffort === void 0 ? t("providerDefaultShort") : compact(t("providerDefaultShort"), effortName(defaultEffort));
985
+ }
986
+ /** The catalog head route; capability switches pin here when turned On. */
987
+ function firstCatalogRoute(groups) {
988
+ const provider = groups[0];
989
+ const model = provider?.models[0];
990
+ return provider === void 0 || model === void 0 ? void 0 : selectRouteModel(groups, provider.id, model.id);
991
+ }
1094
992
  function capabilityChoices(groups, route, providers, kind) {
1095
993
  const choices = deriveRouteChoices(groups, route, providers);
1096
994
  if (kind === "image" && route?.provider === "grok") {
@@ -1111,12 +1009,13 @@ window.__ModuleLoader__.load({
1111
1009
  return choices;
1112
1010
  }
1113
1011
  function ModelSwitchSettings(props) {
1114
- const controller = useModelSwitchSettingsController(props);
1115
- const { main, subagent, draft, groups } = controller;
1012
+ const { groups, catalogError } = useSettingsCatalog(props);
1013
+ const subagent = props.useSubagentSettings((value) => value);
1116
1014
  const search = props.useSearchSettings((value) => value);
1117
1015
  const image = props.useImageSettings((value) => value);
1118
1016
  const autoReview = props.useAutoReviewSettings((value) => value);
1119
1017
  const compaction = props.useCompactionSettings((value) => value);
1018
+ const sessionTitle = props.useTitleSettings((value) => value);
1120
1019
  const switchSettings = props.useSwitchSettings((value) => value);
1121
1020
  const [open, setOpen] = (0, react.useState)();
1122
1021
  const [subagentDraft, setSubagentDraft, resetSubagent] = useDraft(subagent);
@@ -1124,6 +1023,7 @@ window.__ModuleLoader__.load({
1124
1023
  const [imageDraft, setImageDraft, resetImage] = useDraft(image);
1125
1024
  const [autoReviewDraft, setAutoReviewDraft, resetAutoReview] = useDraft(autoReview);
1126
1025
  const [compactionDraft, setCompactionDraft, resetCompaction] = useDraft(compaction);
1026
+ const [titleDraft, setTitleDraft, resetTitle] = useDraft(sessionTitle);
1127
1027
  const [busy, setBusy] = (0, react.useState)();
1128
1028
  const [message, setMessage] = (0, react.useState)();
1129
1029
  const [compactBusy, setCompactBusy] = (0, react.useState)(false);
@@ -1169,15 +1069,17 @@ window.__ModuleLoader__.load({
1169
1069
  };
1170
1070
  }, [loadSearchCapabilities]);
1171
1071
  const providerGroups = normalizeProviderGroups(switchSettings.value?.providerGroups);
1172
- /** Seed an unsaved group from the live routes, mirroring how the switches seed from Main. */
1072
+ /**
1073
+ * Seed an unsaved group from the live routes.
1074
+ *
1075
+ * Only the two per-session capabilities are seeded: a group carrying a
1076
+ * compaction or reviewer pin would store a value no consumer reads (see
1077
+ * src/session-route.ts), which is worse than storing nothing.
1078
+ */
1173
1079
  const seedGroupFromActive = () => {
1174
- const compact = compaction.value !== void 0 && routePairConfigured(compaction.value) ? {
1175
- provider: compaction.value.provider ?? "",
1176
- model: compaction.value.model ?? ""
1177
- } : void 0;
1178
- const review = autoReview.value !== void 0 && routePairConfigured(autoReview.value) ? {
1179
- provider: autoReview.value.provider ?? "",
1180
- model: autoReview.value.model ?? ""
1080
+ const title = sessionTitle.value !== void 0 && routePairConfigured(sessionTitle.value) ? {
1081
+ provider: sessionTitle.value.provider ?? "",
1082
+ model: sessionTitle.value.model ?? ""
1181
1083
  } : void 0;
1182
1084
  const fixedSubagent = subagent.value !== void 0 && subagent.value.mode === "fixed" && routePairConfigured(subagent.value) ? {
1183
1085
  provider: subagent.value.provider ?? "",
@@ -1185,13 +1087,9 @@ window.__ModuleLoader__.load({
1185
1087
  reasoningEffort: subagent.value.reasoningEffort
1186
1088
  } : void 0;
1187
1089
  return {
1188
- ...compact === void 0 ? {} : {
1189
- compactProvider: compact.provider,
1190
- compactModel: compact.model
1191
- },
1192
- ...review === void 0 ? {} : {
1193
- autoReviewProvider: review.provider,
1194
- autoReviewModel: review.model
1090
+ ...title === void 0 ? {} : {
1091
+ titleProvider: title.provider,
1092
+ titleModel: title.model
1195
1093
  },
1196
1094
  ...fixedSubagent === void 0 ? {} : {
1197
1095
  subagentProvider: fixedSubagent.provider,
@@ -1208,11 +1106,16 @@ window.__ModuleLoader__.load({
1208
1106
  }
1209
1107
  setGroupDraft(groupOf(providerGroups, groupProvider) ?? seedGroupFromActive());
1210
1108
  };
1109
+ /** Open the editor on the provider in use: the first configured group, else the catalog head. */
1211
1110
  (0, react.useEffect)(() => {
1212
1111
  if (groupProvider !== "") return;
1213
- const initial = controller.draft?.provider;
1112
+ const initial = Object.keys(providerGroups)[0] ?? groups[0]?.id;
1214
1113
  if (initial !== void 0 && initial.trim() !== "") setGroupProvider(initial);
1215
- }, [groupProvider, controller.draft]);
1114
+ }, [
1115
+ groupProvider,
1116
+ providerGroups,
1117
+ groups
1118
+ ]);
1216
1119
  (0, react.useEffect)(() => {
1217
1120
  resyncGroupDraft();
1218
1121
  }, [
@@ -1220,14 +1123,15 @@ window.__ModuleLoader__.load({
1220
1123
  switchSettings.value,
1221
1124
  subagent.value,
1222
1125
  autoReview.value,
1223
- compaction.value
1126
+ compaction.value,
1127
+ sessionTitle.value
1224
1128
  ]);
1225
1129
  if ([
1226
- controller.main,
1227
- controller.subagent,
1130
+ subagent,
1228
1131
  search,
1229
1132
  image,
1230
- switchSettings
1133
+ switchSettings,
1134
+ sessionTitle
1231
1135
  ].some((form) => form.mode === "memory")) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
1232
1136
  className: ModelSwitchSettings_module_css_default.section,
1233
1137
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
@@ -1267,8 +1171,7 @@ window.__ModuleLoader__.load({
1267
1171
  });
1268
1172
  return efforts;
1269
1173
  })();
1270
- const defaultEffort = subagentDraft?.reasoningEffort ?? routeDefaultEffort(groups, subagentRoute);
1271
- const mainEffectiveEffort = draft?.reasoningEffort ?? routeDefaultEffort(groups, draft);
1174
+ const defaultEffort = routeDefaultEffort(groups, subagentRoute);
1272
1175
  const searchLive = searchSnapshot?.capabilities;
1273
1176
  const searchGroups = searchGroupsFromCapabilities(searchLive);
1274
1177
  const searchChoices = capabilityChoices(searchGroups, searchDraft, void 0, "search");
@@ -1278,179 +1181,233 @@ window.__ModuleLoader__.load({
1278
1181
  const autoReviewOn = routePairConfigured(autoReview.value);
1279
1182
  const compactionChoices = deriveRouteChoices(groups, compactionDraft);
1280
1183
  const compactionOn = routePairConfigured(compaction.value);
1184
+ const titleChoices = deriveRouteChoices(groups, titleDraft);
1185
+ const titleOn = routePairConfigured(sessionTitle.value);
1281
1186
  const synced = [
1282
- main,
1283
1187
  subagent,
1284
1188
  search,
1285
1189
  image,
1286
1190
  autoReview,
1287
- compaction
1191
+ compaction,
1192
+ sessionTitle
1288
1193
  ].every((snapshot) => snapshot.status === "ready");
1194
+ /**
1195
+ * Run one row save with a visible outcome that ALWAYS settles.
1196
+ *
1197
+ * A stalled write used to latch the page on "保存中…" forever, because `busy`
1198
+ * was only cleared by the settling promise. The 30s deadline reports the stall
1199
+ * instead; a late result is ignored by the race, so it can never overwrite a
1200
+ * newer draft either.
1201
+ * @param route - the row whose save is running, for busy and message routing.
1202
+ * @param operation - the single fenced write to perform.
1203
+ * @returns whether the write was accepted.
1204
+ */
1289
1205
  const run = async (route, operation) => {
1290
- if (busy !== void 0) return;
1206
+ if (busy !== void 0) return false;
1207
+ const timeoutMs = 3e4;
1291
1208
  setBusy(route);
1292
1209
  setMessage(void 0);
1210
+ let timer;
1293
1211
  try {
1294
- await operation();
1212
+ await Promise.race([operation(), new Promise((_resolve, reject) => {
1213
+ timer = setTimeout(() => reject(new Error(props.t("saveTimeout"))), timeoutMs);
1214
+ })]);
1295
1215
  setMessage({
1296
1216
  route,
1297
1217
  text: props.t("saved")
1298
1218
  });
1219
+ return true;
1299
1220
  } catch (error) {
1300
1221
  setMessage({
1301
1222
  route,
1302
1223
  text: error instanceof Error ? error.message : props.t("requestFailed")
1303
1224
  });
1225
+ return false;
1304
1226
  } finally {
1227
+ if (timer !== void 0) clearTimeout(timer);
1305
1228
  setBusy(void 0);
1306
1229
  }
1307
1230
  };
1308
1231
  const saveSubagent = () => {
1309
1232
  if (subagentDraft === void 0 || subagentDraft.mode !== "fixed") return;
1310
- run("subagent", async () => {
1311
- if (subagent.value?.provider !== subagentDraft.provider) await props.setSubagent("provider", subagentDraft.provider);
1312
- const matched = subagentCatalogModel;
1313
- const nextModel = matched?.model.id ?? subagentDraft.model;
1314
- const nextEffort = subagentDraft.reasoningEffort === "" ? void 0 : subagentDraft.reasoningEffort || matched?.effort;
1315
- if (subagent.value?.model !== nextModel) await props.setSubagent("model", nextModel);
1316
- if (subagent.value?.reasoningEffort !== nextEffort) await props.setSubagent("reasoningEffort", nextEffort);
1317
- });
1233
+ const matched = subagentCatalogModel;
1234
+ const provider = (subagentDraft.provider ?? "").trim();
1235
+ const model = (matched?.model.id ?? subagentDraft.model ?? "").trim();
1236
+ if (provider === "" || model === "") return;
1237
+ const nextEffort = subagentDraft.reasoningEffort === "" ? void 0 : subagentDraft.reasoningEffort || matched?.effort;
1238
+ run("subagent", () => props.saveSubagent([
1239
+ {
1240
+ field: "provider",
1241
+ value: provider
1242
+ },
1243
+ {
1244
+ field: "model",
1245
+ value: model
1246
+ },
1247
+ {
1248
+ field: "reasoningEffort",
1249
+ value: nextEffort
1250
+ }
1251
+ ]));
1318
1252
  };
1319
1253
  const persistSubagentEnabled = (enabled) => {
1320
1254
  if (subagentDraft === void 0 || busy === "subagent" || !subagent.writable) return;
1321
- setSubagentDraft({
1322
- ...subagentDraft,
1323
- mode: subagentModeForEnabled(enabled)
1324
- });
1325
- setBusy("subagent");
1326
- setMessage(void 0);
1327
- props.setSubagent("mode", subagentModeForEnabled(enabled)).then(() => {
1328
- setBusy(void 0);
1329
- setOpen((current) => enabled ? "subagent" : current === "subagent" ? void 0 : current);
1330
- }, (error) => {
1331
- setBusy(void 0);
1332
- resetSubagent();
1333
- setMessage({
1334
- route: "subagent",
1335
- text: error instanceof Error ? error.message : props.t("requestFailed")
1255
+ const mode = subagentModeForEnabled(enabled);
1256
+ run("subagent", () => props.saveSubagent([{
1257
+ field: "mode",
1258
+ value: mode
1259
+ }])).then((accepted) => {
1260
+ if (!accepted) {
1261
+ resetSubagent();
1262
+ return;
1263
+ }
1264
+ setSubagentDraft({
1265
+ ...subagentDraft,
1266
+ mode
1336
1267
  });
1268
+ setOpen((current) => enabled ? "subagent" : current === "subagent" ? void 0 : current);
1337
1269
  });
1338
1270
  };
1339
- const saveCapability = (route, current, next) => {
1271
+ /** Both halves of one pinned capability route, written together in one mutation. */
1272
+ const pairOps = (next) => [{
1273
+ field: "provider",
1274
+ value: next.provider
1275
+ }, {
1276
+ field: "model",
1277
+ value: next.model
1278
+ }];
1279
+ const saveCapability = (route, next) => {
1340
1280
  if (next === void 0) return;
1341
- run(route, async () => {
1342
- if (current.value?.provider !== next.provider) await props.setCapability(route, "provider", next.provider);
1343
- if (current.value?.model !== next.model) await props.setCapability(route, "model", next.model);
1344
- });
1281
+ const provider = (next.provider ?? "").trim();
1282
+ const model = (next.model ?? "").trim();
1283
+ if (provider === "" || model === "") return;
1284
+ run(route, () => props.saveCapability(route, [{
1285
+ field: "provider",
1286
+ value: provider
1287
+ }, {
1288
+ field: "model",
1289
+ value: model
1290
+ }]));
1345
1291
  };
1346
1292
  const saveAutoReview = () => {
1347
- if (autoReviewDraft === void 0) return;
1348
- run("autoReview", async () => {
1349
- if (autoReview.value?.provider !== autoReviewDraft.provider) await props.setCapability("autoReview", "provider", autoReviewDraft.provider);
1350
- if (autoReview.value?.model !== autoReviewDraft.model) await props.setCapability("autoReview", "model", autoReviewDraft.model);
1351
- });
1293
+ if (autoReviewDraft === void 0 || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "") return;
1294
+ run("autoReview", () => props.saveCapability("autoReview", pairOps(autoReviewDraft)));
1352
1295
  };
1353
1296
  const saveCompaction = () => {
1354
- if (compactionDraft === void 0) return;
1355
- run("compaction", async () => {
1356
- if (compaction.value?.provider !== compactionDraft.provider) await props.setCapability("compaction", "provider", compactionDraft.provider);
1357
- if (compaction.value?.model !== compactionDraft.model) await props.setCapability("compaction", "model", compactionDraft.model);
1358
- });
1297
+ if (compactionDraft === void 0 || (compactionDraft.provider ?? "").trim() === "" || (compactionDraft.model ?? "").trim() === "") return;
1298
+ run("compaction", () => props.saveCapability("compaction", pairOps(compactionDraft)));
1299
+ };
1300
+ const saveTitle = () => {
1301
+ if (titleDraft === void 0 || (titleDraft.provider ?? "").trim() === "" || (titleDraft.model ?? "").trim() === "") return;
1302
+ run("sessionTitle", () => props.saveCapability("title", pairOps(titleDraft)));
1359
1303
  };
1360
1304
  /**
1361
1305
  * The reviewer switch: Off unsets both stored fields (follow the session
1362
- * model); On seeds the route from Main so the pin takes effect immediately
1363
- * and stays editable below. A failed write reports, never flips silently.
1306
+ * model); On pins the route to the catalog head so the pin takes effect
1307
+ * immediately and stays editable below. A failed write reports, never
1308
+ * flips silently.
1364
1309
  */
1365
1310
  const persistAutoReviewEnabled = (enabled) => {
1366
1311
  if (busy === "autoReview" || !autoReview.writable || autoReview.value === void 0) return;
1367
1312
  if (!enabled) {
1368
1313
  setOpen((current) => current === "autoReview" ? void 0 : current);
1369
- run("autoReview", async () => {
1370
- if (autoReview.value?.provider !== void 0) await props.setCapability("autoReview", "provider", void 0);
1371
- if (autoReview.value?.model !== void 0) await props.setCapability("autoReview", "model", void 0);
1372
- });
1314
+ run("autoReview", () => props.saveCapability("autoReview", [{
1315
+ field: "provider",
1316
+ value: void 0
1317
+ }, {
1318
+ field: "model",
1319
+ value: void 0
1320
+ }]));
1373
1321
  return;
1374
1322
  }
1375
- const seed = draft;
1376
- if (seed === void 0 || seed.provider.trim() === "" || seed.model.trim() === "") return;
1377
- setBusy("autoReview");
1378
- setMessage(void 0);
1379
- (async () => {
1380
- if (autoReview.value?.provider !== seed.provider) await props.setCapability("autoReview", "provider", seed.provider);
1381
- if (autoReview.value?.model !== seed.model) await props.setCapability("autoReview", "model", seed.model);
1382
- })().then(() => {
1323
+ const seed = firstCatalogRoute(groups);
1324
+ if (seed === void 0) return;
1325
+ run("autoReview", () => props.saveCapability("autoReview", pairOps(seed))).then((accepted) => {
1326
+ if (!accepted) return;
1383
1327
  setAutoReviewDraft({
1384
1328
  provider: seed.provider,
1385
1329
  model: seed.model
1386
1330
  });
1387
- setBusy(void 0);
1388
1331
  setOpen("autoReview");
1389
- }, (error) => {
1390
- setBusy(void 0);
1391
- setMessage({
1392
- route: "autoReview",
1393
- text: error instanceof Error ? error.message : props.t("requestFailed")
1394
- });
1395
1332
  });
1396
1333
  };
1397
1334
  /**
1398
1335
  * The compaction switch: Off unsets both stored fields (official default =
1399
- * the conversation target); On seeds the route from Main so the pin takes
1400
- * effect immediately and stays editable below. A failed write reports,
1401
- * never flips silently.
1336
+ * the conversation target); On pins the route to the catalog head so the
1337
+ * pin takes effect immediately and stays editable below. A failed write
1338
+ * reports, never flips silently.
1402
1339
  */
1403
1340
  const persistCompactionEnabled = (enabled) => {
1404
1341
  if (busy === "compaction" || !compaction.writable || compaction.value === void 0) return;
1405
1342
  if (!enabled) {
1406
1343
  setOpen((current) => current === "compaction" ? void 0 : current);
1407
- run("compaction", async () => {
1408
- if (compaction.value?.provider !== void 0) await props.setCapability("compaction", "provider", void 0);
1409
- if (compaction.value?.model !== void 0) await props.setCapability("compaction", "model", void 0);
1410
- });
1344
+ run("compaction", () => props.saveCapability("compaction", [{
1345
+ field: "provider",
1346
+ value: void 0
1347
+ }, {
1348
+ field: "model",
1349
+ value: void 0
1350
+ }]));
1411
1351
  return;
1412
1352
  }
1413
- const seed = draft;
1414
- if (seed === void 0 || seed.provider.trim() === "" || seed.model.trim() === "") return;
1415
- setBusy("compaction");
1416
- setMessage(void 0);
1417
- (async () => {
1418
- if (compaction.value?.provider !== seed.provider) await props.setCapability("compaction", "provider", seed.provider);
1419
- if (compaction.value?.model !== seed.model) await props.setCapability("compaction", "model", seed.model);
1420
- })().then(() => {
1353
+ const seed = firstCatalogRoute(groups);
1354
+ if (seed === void 0) return;
1355
+ run("compaction", () => props.saveCapability("compaction", pairOps(seed))).then((accepted) => {
1356
+ if (!accepted) return;
1421
1357
  setCompactionDraft({
1422
1358
  provider: seed.provider,
1423
1359
  model: seed.model
1424
1360
  });
1425
- setBusy(void 0);
1426
1361
  setOpen("compaction");
1427
- }, (error) => {
1428
- setBusy(void 0);
1429
- setMessage({
1430
- route: "compaction",
1431
- text: error instanceof Error ? error.message : props.t("requestFailed")
1362
+ });
1363
+ };
1364
+ /**
1365
+ * The session-title switch: Off unsets both stored fields (naming follows
1366
+ * the session's own logged route); On pins the route to the catalog head so
1367
+ * the pin takes effect immediately and stays editable below. A failed write
1368
+ * reports, never flips silently.
1369
+ */
1370
+ const persistTitleEnabled = (enabled) => {
1371
+ if (busy === "sessionTitle" || !sessionTitle.writable || sessionTitle.value === void 0) return;
1372
+ if (!enabled) {
1373
+ setOpen((current) => current === "sessionTitle" ? void 0 : current);
1374
+ run("sessionTitle", () => props.saveCapability("title", [{
1375
+ field: "provider",
1376
+ value: void 0
1377
+ }, {
1378
+ field: "model",
1379
+ value: void 0
1380
+ }]));
1381
+ return;
1382
+ }
1383
+ const seed = firstCatalogRoute(groups);
1384
+ if (seed === void 0) return;
1385
+ run("sessionTitle", () => props.saveCapability("title", pairOps(seed))).then((accepted) => {
1386
+ if (!accepted) return;
1387
+ setTitleDraft({
1388
+ provider: seed.provider,
1389
+ model: seed.model
1432
1390
  });
1391
+ setOpen("sessionTitle");
1433
1392
  });
1434
1393
  };
1435
- const mainSummary = draft === void 0 ? props.t("loading") : compact(routeName(groups, draft), mainEffectiveEffort);
1436
1394
  const subagentRole = subagentDraft?.mode === "fixed" && subagentDraft.provider !== void 0 && subagentDraft.provider !== "" ? props.providerRoleOf?.(subagentDraft.provider) : void 0;
1437
1395
  const subagentOn = subagentDraft?.mode === "fixed";
1438
- const subagentSummary = subagentDraft === void 0 ? props.t("loading") : subagentOn ? compact(routeName(groups, subagentRoute), isAgentRole(subagentRole) ? props.t("agentBadge") : void 0, defaultEffort === void 0 ? props.t("providerDefaultShort") : compact(props.t("providerDefaultShort"), defaultEffort)) : props.t("subagentOff");
1396
+ const subagentSummary = subagentDraft === void 0 ? props.t("loading") : subagentOn ? compact(routeName(groups, subagentRoute), isAgentRole(subagentRole) ? props.t("agentBadge") : void 0, subagentEffortSegment(props.t, subagentCatalogModel, (subagentDraft.reasoningEffort ?? "").trim(), defaultEffort)) : props.t("subagentOff");
1439
1397
  const subagentDisabled = subagent.status !== "ready" || !subagent.writable || subagentDraft === void 0 || busy === "subagent" || !subagentOn || (subagentDraft.provider ?? "").trim() === "" || (subagentDraft.model ?? "").trim() === "";
1440
1398
  const capabilityDisabled = (route, snapshot, next) => snapshot.status !== "ready" || !snapshot.writable || next === void 0 || busy === route || (next.provider ?? "").trim() === "" || (next.model ?? "").trim() === "";
1441
1399
  const autoReviewDisabled = autoReview.status !== "ready" || !autoReview.writable || autoReviewDraft === void 0 || busy === "autoReview" || !autoReviewOn || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "";
1442
1400
  const compactionDisabled = compaction.status !== "ready" || !compaction.writable || compactionDraft === void 0 || busy === "compaction" || !compactionOn || (compactionDraft.provider ?? "").trim() === "" || (compactionDraft.model ?? "").trim() === "";
1401
+ const titleDisabled = sessionTitle.status !== "ready" || !sessionTitle.writable || titleDraft === void 0 || busy === "sessionTitle" || !titleOn || (titleDraft.provider ?? "").trim() === "" || (titleDraft.model ?? "").trim() === "";
1443
1402
  const configuredGroupCount = Object.keys(providerGroups).length;
1444
1403
  const groupControlsDisabled = busy === "providerGroups" || switchSettings.status !== "ready" || !switchSettings.writable;
1445
1404
  const groupProviderChoices = deriveRouteChoices(groups, groupProvider === "" ? void 0 : { provider: groupProvider }).providers.map((choice) => groupOf(providerGroups, choice.id) === void 0 ? choice : {
1446
1405
  ...choice,
1447
1406
  name: choice.name + " ✓"
1448
1407
  });
1449
- const groupCompaction = groupDraft === void 0 ? void 0 : draftPair(groupDraft.compactProvider, groupDraft.compactModel);
1450
- const groupReview = groupDraft === void 0 ? void 0 : draftPair(groupDraft.autoReviewProvider, groupDraft.autoReviewModel);
1408
+ const groupTitle = groupDraft === void 0 ? void 0 : draftPair(groupDraft.titleProvider, groupDraft.titleModel);
1451
1409
  const groupSubagent = groupDraft === void 0 ? void 0 : draftPair(groupDraft.subagentProvider, groupDraft.subagentModel);
1452
- const groupCompactionChoices = deriveRouteChoices(groups, groupCompaction);
1453
- const groupReviewChoices = deriveRouteChoices(groups, groupReview);
1410
+ const groupTitleChoices = deriveRouteChoices(groups, groupTitle);
1454
1411
  const groupSubagentChoices = deriveRouteChoices(groups, groupSubagent);
1455
1412
  const groupSubMatched = matchCatalogModel(groups.find((group) => group.id === (groupDraft?.subagentProvider ?? ""))?.models ?? [], groupDraft?.subagentModel ?? "");
1456
1413
  const groupSubEfforts = (() => {
@@ -1465,41 +1422,50 @@ window.__ModuleLoader__.load({
1465
1422
  });
1466
1423
  return efforts;
1467
1424
  })();
1468
- /** Enable a capability row by pinning it to the Main route, like the switches above seed from Main. */
1425
+ /**
1426
+ * Enable a capability row by pinning it: to that capability's live route
1427
+ * when one is configured, else to the catalog head (same sources the
1428
+ * editor's first-edit seed uses).
1429
+ *
1430
+ * Only the two capabilities with a public per-session seam are editable here
1431
+ * (see src/session-route.ts): compaction records the summarizer that wrote
1432
+ * each summary durably and auto review carries no session identity at its
1433
+ * interception point, so both stay deployment-wide cards.
1434
+ */
1469
1435
  const enableGroupCapability = (kind) => {
1436
+ const active = seedGroupFromActive();
1437
+ const seed = firstCatalogRoute(groups);
1470
1438
  setGroupDraft((draft) => {
1471
- const main = controller.draft;
1472
- if (draft === void 0 || main === void 0) return draft;
1473
- if (kind === "compaction") return {
1474
- ...draft,
1475
- compactProvider: main.provider,
1476
- compactModel: main.model
1477
- };
1478
- if (kind === "autoReview") return {
1479
- ...draft,
1480
- autoReviewProvider: main.provider,
1481
- autoReviewModel: main.model
1482
- };
1439
+ if (draft === void 0) return draft;
1440
+ if (kind === "sessionTitle") {
1441
+ const provider = active.titleProvider ?? seed?.provider;
1442
+ const model = active.titleModel ?? seed?.model;
1443
+ if (provider === void 0 || model === void 0) return draft;
1444
+ return {
1445
+ ...draft,
1446
+ titleProvider: provider,
1447
+ titleModel: model
1448
+ };
1449
+ }
1450
+ const provider = active.subagentProvider ?? seed?.provider;
1451
+ const model = active.subagentModel ?? seed?.model;
1452
+ if (provider === void 0 || model === void 0) return draft;
1453
+ const effort = active.subagentProvider !== void 0 ? active.subagentReasoningEffort : seed?.reasoningEffort;
1483
1454
  return {
1484
1455
  ...draft,
1485
- subagentProvider: main.provider,
1486
- subagentModel: main.model,
1487
- ...main.reasoningEffort === void 0 ? {} : { subagentReasoningEffort: main.reasoningEffort }
1456
+ subagentProvider: provider,
1457
+ subagentModel: model,
1458
+ ...effort === void 0 || effort === "" ? {} : { subagentReasoningEffort: effort }
1488
1459
  };
1489
1460
  });
1490
1461
  };
1491
1462
  const disableGroupCapability = (kind) => {
1492
1463
  setGroupDraft((draft) => {
1493
1464
  if (draft === void 0) return draft;
1494
- if (kind === "compaction") return {
1495
- ...draft,
1496
- compactProvider: "",
1497
- compactModel: ""
1498
- };
1499
- if (kind === "autoReview") return {
1465
+ if (kind === "sessionTitle") return {
1500
1466
  ...draft,
1501
- autoReviewProvider: "",
1502
- autoReviewModel: ""
1467
+ titleProvider: "",
1468
+ titleModel: ""
1503
1469
  };
1504
1470
  return {
1505
1471
  ...draft,
@@ -1532,6 +1498,10 @@ window.__ModuleLoader__.load({
1532
1498
  className: ModelSwitchSettings_module_css_default.saved,
1533
1499
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: ModelSwitchSettings_module_css_default.savedDot }), synced ? props.t("settingsSynced") : props.t("loading")]
1534
1500
  }),
1501
+ catalogError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1502
+ className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
1503
+ children: catalogError
1504
+ }),
1535
1505
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1536
1506
  className: ModelSwitchSettings_module_css_default.group,
1537
1507
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
@@ -1597,150 +1567,92 @@ window.__ModuleLoader__.load({
1597
1567
  }),
1598
1568
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1599
1569
  className: ModelSwitchSettings_module_css_default.group,
1600
- children: [
1601
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1602
- className: ModelSwitchSettings_module_css_default.groupLabel,
1603
- children: props.t("conversationRoutes")
1570
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1571
+ className: ModelSwitchSettings_module_css_default.groupLabel,
1572
+ children: props.t("conversationRoutes")
1573
+ }), props.capabilities.centralSubagentRouting.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1574
+ title: props.t("subagent"),
1575
+ summary: subagentSummary,
1576
+ hint: props.t("subagentHelp"),
1577
+ icon: "subagent",
1578
+ open: open === "subagent" && subagentOn,
1579
+ onToggle: () => {
1580
+ if (subagentOn) toggle("subagent");
1581
+ },
1582
+ expandable: subagentOn,
1583
+ trailing: subagentDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1584
+ checked: subagentOn,
1585
+ disabled: busy === "subagent" || !subagent.writable,
1586
+ label: props.t("subagent"),
1587
+ onChange: persistSubagentEnabled
1604
1588
  }),
1605
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1606
- title: props.t("main"),
1607
- summary: mainSummary,
1608
- icon: "main",
1609
- open: open === "main",
1610
- onToggle: () => {
1611
- toggle("main");
1612
- },
1613
- badge: props.t("defaultBadge"),
1614
- children: draft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1615
- className: ModelSwitchSettings_module_css_default.hint,
1616
- children: props.t("loading")
1617
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1618
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1619
- className: ModelSwitchSettings_module_css_default.formGrid,
1620
- children: [
1621
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1622
- label: props.t("provider"),
1623
- value: draft.provider,
1624
- disabled: controller.busy || !main.writable,
1625
- choices: controller.providers,
1626
- onChange: controller.setProvider
1627
- }),
1628
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1629
- label: props.t("model"),
1630
- value: draft.model,
1631
- disabled: controller.busy || !main.writable,
1632
- choices: controller.models,
1633
- onChange: controller.setModel
1634
- }),
1635
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1636
- label: props.t("effort"),
1637
- value: draft.reasoningEffort ?? "",
1638
- disabled: controller.busy || !main.writable,
1639
- choices: controller.efforts,
1640
- onChange: controller.setReasoningEffort
1641
- })
1642
- ]
1589
+ children: subagentDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1590
+ className: ModelSwitchSettings_module_css_default.hint,
1591
+ children: props.t("loading")
1592
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1593
+ className: ModelSwitchSettings_module_css_default.formGrid,
1594
+ children: [
1595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1596
+ label: props.t("provider"),
1597
+ value: subagentDraft.provider ?? "",
1598
+ disabled: busy === "subagent" || !subagent.writable,
1599
+ choices: subagentChoices.providers,
1600
+ onChange: (provider) => {
1601
+ const first = groups.find((group) => group.id === provider)?.models[0];
1602
+ setSubagentDraft({
1603
+ mode: "fixed",
1604
+ ...selectRouteModel(groups, provider, first?.id ?? subagentDraft.model ?? "")
1605
+ });
1606
+ }
1643
1607
  }),
1644
- !main.writable && main.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1645
- className: ModelSwitchSettings_module_css_default.hint,
1646
- children: props.t("readonly")
1647
- }) : null,
1648
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
1649
- t: props.t,
1650
- busy: controller.busy,
1651
- disabled: controller.disabled,
1652
- ...controller.message === void 0 ? {} : { message: controller.message },
1653
- onCancel: controller.reset,
1654
- onSave: () => {
1655
- controller.save();
1608
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1609
+ label: props.t("model"),
1610
+ value: subagentCatalogModel?.model.id ?? subagentDraft.model ?? "",
1611
+ disabled: busy === "subagent" || !subagent.writable,
1612
+ choices: subagentChoices.models,
1613
+ onChange: (model) => {
1614
+ setSubagentDraft({
1615
+ mode: "fixed",
1616
+ ...selectRouteModel(groups, subagentDraft.provider ?? "", model)
1617
+ });
1618
+ }
1619
+ }),
1620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1621
+ label: props.t("effort"),
1622
+ value: subagentDraft.reasoningEffort || subagentCatalogModel?.effort || "",
1623
+ disabled: busy === "subagent" || !subagent.writable,
1624
+ choices: subagentEfforts,
1625
+ onChange: (effort) => {
1626
+ const matched = subagentCatalogModel;
1627
+ setSubagentDraft({
1628
+ ...subagentDraft,
1629
+ ...matched?.model.id === void 0 ? {} : { model: matched.model.id },
1630
+ reasoningEffort: effort
1631
+ });
1656
1632
  }
1657
1633
  })
1658
- ] })
1659
- }),
1660
- props.capabilities.centralSubagentRouting.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1661
- title: props.t("subagent"),
1662
- summary: subagentSummary,
1663
- hint: props.t("subagentHelp"),
1664
- icon: "subagent",
1665
- open: open === "subagent" && subagentOn,
1666
- onToggle: () => {
1667
- if (subagentOn) toggle("subagent");
1634
+ ]
1635
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
1636
+ t: props.t,
1637
+ busy: busy === "subagent",
1638
+ disabled: subagentDisabled,
1639
+ ...message?.route === "subagent" ? { message: message.text } : {},
1640
+ onCancel: () => {
1641
+ resetSubagent();
1642
+ setMessage(void 0);
1668
1643
  },
1669
- expandable: subagentOn,
1670
- trailing: subagentDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1671
- checked: subagentOn,
1672
- disabled: busy === "subagent" || !subagent.writable,
1673
- label: props.t("subagent"),
1674
- onChange: persistSubagentEnabled
1675
- }),
1676
- children: subagentDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1677
- className: ModelSwitchSettings_module_css_default.hint,
1678
- children: props.t("loading")
1679
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1680
- className: ModelSwitchSettings_module_css_default.formGrid,
1681
- children: [
1682
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1683
- label: props.t("provider"),
1684
- value: subagentDraft.provider ?? "",
1685
- disabled: busy === "subagent" || !subagent.writable,
1686
- choices: subagentChoices.providers,
1687
- onChange: (provider) => {
1688
- const first = groups.find((group) => group.id === provider)?.models[0];
1689
- setSubagentDraft({
1690
- mode: "fixed",
1691
- ...selectRouteModel(groups, provider, first?.id ?? subagentDraft.model ?? "")
1692
- });
1693
- }
1694
- }),
1695
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1696
- label: props.t("model"),
1697
- value: subagentCatalogModel?.model.id ?? subagentDraft.model ?? "",
1698
- disabled: busy === "subagent" || !subagent.writable,
1699
- choices: subagentChoices.models,
1700
- onChange: (model) => {
1701
- setSubagentDraft({
1702
- mode: "fixed",
1703
- ...selectRouteModel(groups, subagentDraft.provider ?? "", model)
1704
- });
1705
- }
1706
- }),
1707
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1708
- label: props.t("effort"),
1709
- value: subagentDraft.reasoningEffort || subagentCatalogModel?.effort || "",
1710
- disabled: busy === "subagent" || !subagent.writable,
1711
- choices: subagentEfforts,
1712
- onChange: (effort) => {
1713
- const matched = subagentCatalogModel;
1714
- setSubagentDraft({
1715
- ...subagentDraft,
1716
- ...matched?.model.id === void 0 ? {} : { model: matched.model.id },
1717
- reasoningEffort: effort
1718
- });
1719
- }
1720
- })
1721
- ]
1722
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
1723
- t: props.t,
1724
- busy: busy === "subagent",
1725
- disabled: subagentDisabled,
1726
- ...message?.route === "subagent" ? { message: message.text } : {},
1727
- onCancel: () => {
1728
- resetSubagent();
1729
- setMessage(void 0);
1730
- },
1731
- onSave: saveSubagent
1732
- })] })
1733
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1734
- title: props.t("subagent"),
1735
- summary: unavailable("centralSubagentRouting"),
1736
- icon: "subagent",
1737
- open: false,
1738
- onToggle: () => {},
1739
- disabled: true,
1740
- badge: props.t("unavailable"),
1741
- badgeWarn: true
1742
- })
1743
- ]
1644
+ onSave: saveSubagent
1645
+ })] })
1646
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1647
+ title: props.t("subagent"),
1648
+ summary: unavailable("centralSubagentRouting"),
1649
+ icon: "subagent",
1650
+ open: false,
1651
+ onToggle: () => {},
1652
+ disabled: true,
1653
+ badge: props.t("unavailable"),
1654
+ badgeWarn: true
1655
+ })]
1744
1656
  }),
1745
1657
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1746
1658
  className: ModelSwitchSettings_module_css_default.group,
@@ -1806,7 +1718,7 @@ window.__ModuleLoader__.load({
1806
1718
  setMessage(void 0);
1807
1719
  },
1808
1720
  onSave: () => {
1809
- saveCapability("search", search, searchDraft);
1721
+ saveCapability("search", searchDraft);
1810
1722
  }
1811
1723
  })
1812
1724
  ] })
@@ -1873,7 +1785,7 @@ window.__ModuleLoader__.load({
1873
1785
  setMessage(void 0);
1874
1786
  },
1875
1787
  onSave: () => {
1876
- saveCapability("image", image, imageDraft);
1788
+ saveCapability("image", imageDraft);
1877
1789
  }
1878
1790
  })
1879
1791
  ] })
@@ -2014,6 +1926,70 @@ window.__ModuleLoader__.load({
2014
1926
  onSave: saveCompaction
2015
1927
  })
2016
1928
  ] })
1929
+ }),
1930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1931
+ title: props.t("sessionTitle"),
1932
+ summary: titleDraft === void 0 ? props.t("loading") : titleOn ? routeName(groups, titleDraft) : props.t("titleFollow"),
1933
+ hint: props.t("titleHelp"),
1934
+ icon: "sessionTitle",
1935
+ open: open === "sessionTitle" && titleOn,
1936
+ onToggle: () => {
1937
+ if (titleOn) toggle("sessionTitle");
1938
+ },
1939
+ expandable: titleOn,
1940
+ trailing: titleDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1941
+ checked: titleOn,
1942
+ disabled: busy === "sessionTitle" || !sessionTitle.writable,
1943
+ label: props.t("sessionTitle"),
1944
+ onChange: persistTitleEnabled
1945
+ }),
1946
+ children: titleDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1947
+ className: ModelSwitchSettings_module_css_default.hint,
1948
+ children: props.t("loading")
1949
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1951
+ className: ModelSwitchSettings_module_css_default.hint,
1952
+ children: props.t("titleRouteHelp")
1953
+ }),
1954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1955
+ className: ModelSwitchSettings_module_css_default.formGrid,
1956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1957
+ label: props.t("provider"),
1958
+ value: titleDraft.provider ?? "",
1959
+ disabled: busy === "sessionTitle" || !sessionTitle.writable,
1960
+ choices: titleChoices.providers,
1961
+ onChange: (provider) => {
1962
+ const first = groups.find((group) => group.id === provider)?.models[0];
1963
+ setTitleDraft({
1964
+ provider,
1965
+ ...first === void 0 ? {} : { model: first.id }
1966
+ });
1967
+ }
1968
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1969
+ label: props.t("model"),
1970
+ value: titleDraft.model ?? "",
1971
+ disabled: busy === "sessionTitle" || !sessionTitle.writable,
1972
+ choices: titleChoices.models,
1973
+ onChange: (model) => {
1974
+ setTitleDraft({
1975
+ ...titleDraft,
1976
+ model
1977
+ });
1978
+ }
1979
+ })]
1980
+ }),
1981
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
1982
+ t: props.t,
1983
+ busy: busy === "sessionTitle",
1984
+ disabled: titleDisabled,
1985
+ ...message?.route === "sessionTitle" ? { message: message.text } : {},
1986
+ onCancel: () => {
1987
+ resetTitle();
1988
+ setMessage(void 0);
1989
+ },
1990
+ onSave: saveTitle
1991
+ })
1992
+ ] })
2017
1993
  })
2018
1994
  ]
2019
1995
  }),
@@ -2057,94 +2033,45 @@ window.__ModuleLoader__.load({
2057
2033
  className: ModelSwitchSettings_module_css_default.rowCopy,
2058
2034
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2059
2035
  className: ModelSwitchSettings_module_css_default.rowTitle,
2060
- children: props.t("compaction")
2036
+ children: props.t("sessionTitle")
2061
2037
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2062
2038
  className: ModelSwitchSettings_module_css_default.hint,
2063
- children: groupCompaction === void 0 ? props.t("compactionFollow") : routeName(groups, groupCompaction)
2039
+ children: groupTitle === void 0 ? props.t("titleFollow") : routeName(groups, groupTitle)
2064
2040
  })]
2065
2041
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
2066
- checked: groupCompaction !== void 0,
2042
+ checked: groupTitle !== void 0,
2067
2043
  disabled: groupControlsDisabled,
2068
- label: props.t("compaction"),
2044
+ label: props.t("sessionTitle"),
2069
2045
  onChange: (value) => {
2070
- if (value) enableGroupCapability("compaction");
2071
- else disableGroupCapability("compaction");
2046
+ if (value) enableGroupCapability("sessionTitle");
2047
+ else disableGroupCapability("sessionTitle");
2072
2048
  }
2073
2049
  })]
2074
2050
  }),
2075
- groupCompaction === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2051
+ groupTitle === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2076
2052
  className: ModelSwitchSettings_module_css_default.formGrid,
2077
2053
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2078
2054
  label: props.t("provider"),
2079
- value: groupCompaction.provider,
2055
+ value: groupTitle.provider,
2080
2056
  disabled: groupControlsDisabled,
2081
- choices: groupCompactionChoices.providers,
2057
+ choices: groupTitleChoices.providers,
2082
2058
  onChange: (provider) => {
2083
2059
  const first = groups.find((item) => item.id === provider)?.models[0];
2084
2060
  setGroupDraft((draft) => draft === void 0 ? draft : {
2085
2061
  ...draft,
2086
- compactProvider: provider,
2087
- compactModel: first?.id ?? ""
2062
+ titleProvider: provider,
2063
+ titleModel: first?.id ?? ""
2088
2064
  });
2089
2065
  }
2090
2066
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2091
2067
  label: props.t("model"),
2092
- value: groupCompaction.model,
2068
+ value: groupTitle.model,
2093
2069
  disabled: groupControlsDisabled,
2094
- choices: groupCompactionChoices.models,
2070
+ choices: groupTitleChoices.models,
2095
2071
  onChange: (model) => {
2096
2072
  setGroupDraft((draft) => draft === void 0 ? draft : {
2097
2073
  ...draft,
2098
- compactModel: model
2099
- });
2100
- }
2101
- })]
2102
- }),
2103
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2104
- className: ModelSwitchSettings_module_css_default.rowCard,
2105
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2106
- className: ModelSwitchSettings_module_css_default.rowCopy,
2107
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2108
- className: ModelSwitchSettings_module_css_default.rowTitle,
2109
- children: props.t("autoReview")
2110
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2111
- className: ModelSwitchSettings_module_css_default.hint,
2112
- children: groupReview === void 0 ? props.t("autoReviewFollow") : routeName(groups, groupReview)
2113
- })]
2114
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
2115
- checked: groupReview !== void 0,
2116
- disabled: groupControlsDisabled,
2117
- label: props.t("autoReview"),
2118
- onChange: (value) => {
2119
- if (value) enableGroupCapability("autoReview");
2120
- else disableGroupCapability("autoReview");
2121
- }
2122
- })]
2123
- }),
2124
- groupReview === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2125
- className: ModelSwitchSettings_module_css_default.formGrid,
2126
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2127
- label: props.t("provider"),
2128
- value: groupReview.provider,
2129
- disabled: groupControlsDisabled,
2130
- choices: groupReviewChoices.providers,
2131
- onChange: (provider) => {
2132
- const first = groups.find((item) => item.id === provider)?.models[0];
2133
- setGroupDraft((draft) => draft === void 0 ? draft : {
2134
- ...draft,
2135
- autoReviewProvider: provider,
2136
- autoReviewModel: first?.id ?? ""
2137
- });
2138
- }
2139
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2140
- label: props.t("model"),
2141
- value: groupReview.model,
2142
- disabled: groupControlsDisabled,
2143
- choices: groupReviewChoices.models,
2144
- onChange: (model) => {
2145
- setGroupDraft((draft) => draft === void 0 ? draft : {
2146
- ...draft,
2147
- autoReviewModel: model
2074
+ titleModel: model
2148
2075
  });
2149
2076
  }
2150
2077
  })]
@@ -2248,22 +2175,21 @@ window.__ModuleLoader__.load({
2248
2175
  const zh$1 = {
2249
2176
  nav: "模型切换",
2250
2177
  title: "模型切换",
2251
- subtitle: "为主会话、子代理和能力工具设置默认模型。修改只影响新请求。",
2178
+ subtitle: "为子代理与能力路由设置固定模型;当前会话模型用输入框旁的选择器直接切换。",
2252
2179
  conversationRoutes: "对话路由",
2253
2180
  capabilityRoutes: "能力路由",
2254
2181
  sendProtection: "发送保护",
2255
2182
  settingsSynced: "设置已同步",
2256
2183
  remoteSettingsUnavailable: "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面打开设置。",
2257
- defaultBadge: "默认",
2258
2184
  cancel: "取消",
2259
2185
  compactOnSwitch: "切换时压缩",
2260
2186
  compactOnSwitchHelp: "发送且换了模型时,先按目标窗口检查上下文;超了就用切换前的模型压一次历史,再继续请求。只选模型、不发送,不会压缩。官方自动压缩和 /compact 不受影响。压缩会产生额外模型用量,摘要可能省略细节。",
2261
- main: "主模型",
2262
2187
  provider: "提供商",
2263
2188
  model: "模型",
2264
2189
  providerDefault: "使用提供商默认值",
2265
2190
  providerDefaultShort: "Provider 默认",
2266
2191
  effort: "推理强度",
2192
+ effortPinned: "已固定",
2267
2193
  save: "保存",
2268
2194
  saving: "保存中…",
2269
2195
  saved: "已保存",
@@ -2277,10 +2203,8 @@ window.__ModuleLoader__.load({
2277
2203
  imageHelp: "统一 generate_image 会调用所选 Codex 或 Grok Adapter;Provider 原有图片工具仍保留。",
2278
2204
  unavailable: "未接入",
2279
2205
  loading: "正在加载设置…",
2280
- readonly: "设置为只读",
2281
2206
  requestFailed: "保存失败",
2282
2207
  catalogFailed: "无法加载模型目录。",
2283
- conflict: "设置已在其他位置更改,请检查最新值后重试。",
2284
2208
  autoReview: "自动审查模型",
2285
2209
  autoReviewFollow: "跟随会话模型",
2286
2210
  autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。",
@@ -2289,14 +2213,18 @@ window.__ModuleLoader__.load({
2289
2213
  compactionFollow: "跟随会话模型",
2290
2214
  compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。",
2291
2215
  compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。",
2216
+ sessionTitle: "会话命名模型",
2217
+ titleFollow: "跟随会话模型",
2218
+ titleHelp: "为官方自动命名(新会话首条消息后生成标题)指定模型;关闭(两个字段都留空)时命名跟随当前会话模型。",
2219
+ titleRouteHelp: "命名请求固定使用这里的 Provider/模型(不带推理强度);关闭时跟随会话模型。保存后立即对下一次命名生效,不会中断正在进行的命名,会话日志记录的也是实际使用的模型。",
2292
2220
  providerGroups: "供应商配置组",
2293
2221
  providerGroupsCard: "按供应商预设路由",
2294
- providerGroupsSummary: "切换主模型供应商时自动应用对应组",
2295
- providerGroupsHelp: "为压缩、子代理、审查按供应商分别保存路由。",
2296
- providerGroupsBodyHelp: "每个供应商保存一组压缩、子代理、审查路由;主模型切换到该供应商时,这三条路由自动写入上方对应卡片。切换到未配置的供应商时现有路由保持不变;应用会覆盖三张卡片的当前值(含手动修改)。",
2222
+ providerGroupsSummary: "使用该供应商的会话自动采用对应组",
2223
+ providerGroupsHelp: "为会话命名与子代理按供应商分别保存路由。",
2224
+ providerGroupsBodyHelp: "每个供应商保存一组会话命名与子代理路由。会话自身的供应商与该组一致时,该会话的命名与子代理按组解析,否则回落到上方卡片里的全局设置。切换某个会话的模型不会写入任何全局设置,因此不同供应商的多个会话互不影响。压缩与自动审查仍是全局卡片:压缩事件要如实记录实际写入摘要的模型,审查的拦截点没有会话身份。",
2297
2225
  providerGroupPick: "选择供应商以编辑其配置组。",
2298
2226
  providerGroupDelete: "删除该组",
2299
- providerGroupApplyFailed: "主模型已保存,但供应商组应用失败:",
2227
+ saveTimeout: "保存超时,请重试",
2300
2228
  "reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
2301
2229
  "reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
2302
2230
  "reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
@@ -2307,22 +2235,21 @@ window.__ModuleLoader__.load({
2307
2235
  const en$1 = {
2308
2236
  nav: "Model Switch",
2309
2237
  title: "Model Switch",
2310
- subtitle: "Set default models for Main, Subagents, and capability tools. Changes affect new requests only.",
2238
+ subtitle: "Pin routes for Subagents and capability tools; switch the active session model through the composer picker.",
2311
2239
  conversationRoutes: "Conversation routes",
2312
2240
  capabilityRoutes: "Capability routes",
2313
2241
  sendProtection: "Send protection",
2314
2242
  settingsSynced: "Settings synced",
2315
2243
  remoteSettingsUnavailable: "Official DSH disables settings reads and writes in non-loopback browsers. Open Settings on the Host loopback URL to edit model routes.",
2316
- defaultBadge: "Default",
2317
2244
  cancel: "Cancel",
2318
2245
  compactOnSwitch: "Compact on switch",
2319
2246
  compactOnSwitchHelp: "When a sent message uses a new model, check the target window first. If needed, compact history with the previous model, then continue. Choosing a model does not compact. Official automatic compaction and /compact are unchanged. Compaction uses extra model quota and may omit details.",
2320
- main: "Main model",
2321
2247
  provider: "Provider",
2322
2248
  model: "Model",
2323
2249
  providerDefault: "Provider default",
2324
2250
  providerDefaultShort: "Provider default",
2325
2251
  effort: "Reasoning effort",
2252
+ effortPinned: "Pinned",
2326
2253
  save: "Save",
2327
2254
  saving: "Saving…",
2328
2255
  saved: "Saved",
@@ -2336,10 +2263,8 @@ window.__ModuleLoader__.load({
2336
2263
  imageHelp: "The stable generate_image tool calls the selected Codex or Grok Adapter; existing provider image tools remain available.",
2337
2264
  unavailable: "Unavailable",
2338
2265
  loading: "Loading settings…",
2339
- readonly: "Settings are read-only",
2340
2266
  requestFailed: "Save failed",
2341
2267
  catalogFailed: "Could not load the model catalog.",
2342
- conflict: "Settings changed elsewhere. Review the latest values and retry.",
2343
2268
  autoReview: "Auto review model",
2344
2269
  autoReviewFollow: "Follows the session model",
2345
2270
  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.",
@@ -2348,14 +2273,18 @@ window.__ModuleLoader__.load({
2348
2273
  compactionFollow: "Follows the conversation model",
2349
2274
  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.",
2350
2275
  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.",
2276
+ sessionTitle: "Session title model",
2277
+ titleFollow: "Follows the session model",
2278
+ titleHelp: "Pick the model used to name new sessions automatically (right after the first prompt). When off (both fields empty), naming follows the session model.",
2279
+ titleRouteHelp: "Naming requests use exactly this provider/model (no reasoning effort); when off they follow the session model. A saved change applies to the very next title, never interrupts a running one, and the session log records the route actually used.",
2351
2280
  providerGroups: "Provider groups",
2352
2281
  providerGroupsCard: "Per-provider route presets",
2353
- providerGroupsSummary: "Applied automatically when the main model provider switches",
2354
- providerGroupsHelp: "Save compaction, subagent, and review routes per provider.",
2355
- providerGroupsBodyHelp: "Each provider stores one compaction, subagent, and review route group. Switching the main model to that provider writes these three routes into the cards above; switching to a provider without a group leaves the current routes untouched. Applying a group overwrites those three cards, including manual edits.",
2282
+ providerGroupsSummary: "Sessions using that provider adopt this group",
2283
+ providerGroupsHelp: "Save session-naming and subagent routes per provider.",
2284
+ providerGroupsBodyHelp: "Each provider stores one session-naming and subagent route group. A session running on that provider resolves its naming and subagent calls from the group, and falls back to the cards above otherwise. Switching a session’s model writes no global setting, so sessions on different providers never affect each other. Compaction and auto review stay global cards: the compaction event must record the model that actually wrote each summary, and the reviewer gate carries no session identity.",
2356
2285
  providerGroupPick: "Pick a provider to edit its group.",
2357
2286
  providerGroupDelete: "Delete group",
2358
- providerGroupApplyFailed: "Main model saved, but applying the provider group failed: ",
2287
+ saveTimeout: "The save timed out; try again",
2359
2288
  "reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
2360
2289
  "reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
2361
2290
  "reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
@@ -2416,13 +2345,8 @@ window.__ModuleLoader__.load({
2416
2345
  }
2417
2346
  async function approvePlanReview(args) {
2418
2347
  const current = args.current;
2419
- const commit = current !== void 0 && current !== null && current.provider === args.selection.provider && current.model === args.selection.model && current.reasoningEffort === args.selection.reasoningEffort ? {
2420
- committed: true,
2421
- mainDefaultRestored: true
2422
- } : await args.select(args.selection);
2423
- if (!commit.committed) return false;
2348
+ if (!(current !== void 0 && current !== null && current.provider === args.selection.provider && current.model === args.selection.model && current.reasoningEffort === args.selection.reasoningEffort ? { committed: true } : await args.select(args.selection)).committed) return false;
2424
2349
  await args.answer();
2425
- if (!commit.mainDefaultRestored) args.onNotRestored?.(args.selection.model);
2426
2350
  return true;
2427
2351
  }
2428
2352
  function planActionView(state, available, hasExecution) {
@@ -3126,7 +3050,7 @@ window.__ModuleLoader__.load({
3126
3050
  children: mark
3127
3051
  });
3128
3052
  }
3129
- function ComposerPicker({ locked, providerLock = null, agentLocked = false, available, unavailableReason, directory, t, draft, onDraftChange, embedded, tone, resolveInteractionOperations, roleOf }) {
3053
+ function ComposerPicker({ locked, providerLock = null, agentLocked = false, available, directory, t, draft, onDraftChange, embedded, tone, resolveInteractionOperations, roleOf }) {
3130
3054
  const { snapshot: state, getDirectorySnapshot, load, select } = directory;
3131
3055
  const [pane, setPane] = (0, react.useState)("root");
3132
3056
  const [searching, setSearching] = (0, react.useState)(false);
@@ -3209,16 +3133,7 @@ window.__ModuleLoader__.load({
3209
3133
  setQuery("");
3210
3134
  };
3211
3135
  const settleSelection = (outcome) => {
3212
- if (outcome.committed) {
3213
- if (!outcome.mainDefaultRestored) {
3214
- toastSeq.current += 1;
3215
- setToast({
3216
- seq: toastSeq.current,
3217
- text: t("plan.notRestored")
3218
- });
3219
- }
3220
- return;
3221
- }
3136
+ if (outcome.committed) return;
3222
3137
  const message = getDirectorySnapshot().error;
3223
3138
  if (message !== null) {
3224
3139
  toastSeq.current += 1;
@@ -3629,11 +3544,11 @@ window.__ModuleLoader__.load({
3629
3544
  ref: triggerRef,
3630
3545
  type: "button",
3631
3546
  className: ComposerPicker_module_css_default.trigger,
3632
- "aria-label": unavailableReason === void 0 ? triggerAria : `${triggerAria}: ${unavailableReason}`,
3547
+ "aria-label": triggerAria,
3633
3548
  "aria-haspopup": "menu",
3634
3549
  "aria-expanded": open,
3635
3550
  "aria-controls": open ? `${id}-menu` : void 0,
3636
- title: unavailableReason ?? triggerLabel,
3551
+ title: triggerLabel,
3637
3552
  disabled: locked,
3638
3553
  onPointerDown: onTriggerPointerDown,
3639
3554
  onClick: onTriggerClick,
@@ -3858,134 +3773,6 @@ window.__ModuleLoader__.load({
3858
3773
  }, operations);
3859
3774
  }
3860
3775
  //#endregion
3861
- //#region src/client/picker/main-default-restore.ts
3862
- /** Bound on how long the unawaited Host default write is awaited. */
3863
- const DEFAULT_WRITE_WAIT_MS = 750;
3864
- function defaultWait(ms) {
3865
- return new Promise((resolve) => {
3866
- setTimeout(resolve, ms);
3867
- });
3868
- }
3869
- /** A fenced snapshot: ready, Host-synced, writable, with a concrete revision. */
3870
- function fenced(snapshot) {
3871
- if (snapshot.status !== "ready" || snapshot.mode !== "host" || !snapshot.writable) return void 0;
3872
- if (snapshot.value === void 0 || snapshot.revision === void 0) return void 0;
3873
- return {
3874
- revision: snapshot.revision,
3875
- value: snapshot.value
3876
- };
3877
- }
3878
- /** Field writes restoring one complete Main selection; the Host replaces the section. */
3879
- function mainDefaultOps(selection) {
3880
- return [
3881
- {
3882
- op: "set",
3883
- path: ["provider"],
3884
- value: selection.provider
3885
- },
3886
- {
3887
- op: "set",
3888
- path: ["model"],
3889
- value: selection.model
3890
- },
3891
- selection.reasoningEffort === void 0 || selection.reasoningEffort === "" ? {
3892
- op: "unset",
3893
- path: ["reasoningEffort"]
3894
- } : {
3895
- op: "set",
3896
- path: ["reasoningEffort"],
3897
- value: selection.reasoningEffort
3898
- }
3899
- ];
3900
- }
3901
- /** An effort never leaks across a comparison: absent and cleared are the same selection. */
3902
- function sameMainSelection(left, right) {
3903
- if (left === void 0) return false;
3904
- const leftEffort = left.reasoningEffort === void 0 || left.reasoningEffort === "" ? void 0 : left.reasoningEffort;
3905
- const rightEffort = right.reasoningEffort === void 0 || right.reasoningEffort === "" ? void 0 : right.reasoningEffort;
3906
- return left.provider === right.provider && left.model === right.model && leftEffort === rightEffort;
3907
- }
3908
- /** Resolve as soon as the revision leaves `revision`, or after the bounded wait. */
3909
- function waitForAdvance(form, revision, wait, timeoutMs) {
3910
- const current = form.getSnapshot();
3911
- if (current.revision !== revision) return Promise.resolve(current);
3912
- return new Promise((resolve) => {
3913
- let settled = false;
3914
- const finish = (snapshot) => {
3915
- if (settled) return;
3916
- settled = true;
3917
- unsubscribe();
3918
- resolve(snapshot);
3919
- };
3920
- const unsubscribe = form.subscribe(() => {
3921
- const next = form.getSnapshot();
3922
- if (next.revision !== revision) finish(next);
3923
- });
3924
- wait(timeoutMs).then(() => {
3925
- finish(form.getSnapshot());
3926
- });
3927
- });
3928
- }
3929
- /**
3930
- * Restore the Main default captured before a Session-only model switch.
3931
- *
3932
- * Never throws and never blocks a Plan answer: a failed compensation is
3933
- * reported, not enforced, because the Session selection the human asked for is
3934
- * already installed by the time this runs.
3935
- *
3936
- * @param form - the official Main Settings namespace form.
3937
- * @param before - the snapshot captured before the switch.
3938
- * @param options - wait/timeout/attempt overrides for tests.
3939
- * @returns Whether the captured default stands again, and whether a newer edit won.
3940
- */
3941
- async function restoreMainDefault(form, before, options = {}) {
3942
- const captured = fenced(before);
3943
- if (captured === void 0) return {
3944
- restored: true,
3945
- conflict: false
3946
- };
3947
- const wait = options.wait ?? defaultWait;
3948
- const timeoutMs = options.timeoutMs ?? DEFAULT_WRITE_WAIT_MS;
3949
- const attempts = Math.max(1, options.attempts ?? 2);
3950
- await waitForAdvance(form, captured.revision, wait, timeoutMs);
3951
- const observed = fenced(form.getSnapshot());
3952
- if (observed === void 0) return {
3953
- restored: true,
3954
- conflict: false
3955
- };
3956
- if (observed.revision === captured.revision) return {
3957
- restored: true,
3958
- conflict: false
3959
- };
3960
- if (sameMainSelection(observed.value, captured.value)) return {
3961
- restored: true,
3962
- conflict: false
3963
- };
3964
- for (let attempt = 0; attempt < attempts; attempt += 1) {
3965
- const snapshot = fenced(form.getSnapshot());
3966
- if (snapshot === void 0) return {
3967
- restored: false,
3968
- conflict: false
3969
- };
3970
- if (sameMainSelection(snapshot.value, captured.value)) return {
3971
- restored: true,
3972
- conflict: false
3973
- };
3974
- if (await form.mutate(mainDefaultOps(captured.value), snapshot.revision)) return {
3975
- restored: true,
3976
- conflict: false
3977
- };
3978
- if (form.getSnapshot().revision === snapshot.revision) return {
3979
- restored: false,
3980
- conflict: false
3981
- };
3982
- }
3983
- return {
3984
- restored: false,
3985
- conflict: true
3986
- };
3987
- }
3988
- //#endregion
3989
3776
  //#region src/picker/pending-dismissal.ts
3990
3777
  /** Raised when the running Host exposes neither withdrawal verb. */
3991
3778
  var PlanDismissalUnsupportedError = class extends Error {
@@ -4199,12 +3986,6 @@ window.__ModuleLoader__.load({
4199
3986
  throw cause instanceof PlanDismissalUnsupportedError ? new Error(unsupportedMessage, { cause }) : new Error(rejectedMessage, { cause });
4200
3987
  }
4201
3988
  }
4202
- /** Match the model seat's admission guard before letting either picker offer a change. */
4203
- function mainDefaultsUnavailableReason(snapshot, t) {
4204
- if (snapshot.mode === "memory") return t("settings.remoteUnavailable");
4205
- if (snapshot.status === "loading") return t("settings.loading");
4206
- if (snapshot.status !== "ready" || snapshot.mode !== "host" || !snapshot.writable || snapshot.value === void 0 || snapshot.revision === void 0) return t("settings.unavailable");
4207
- }
4208
3989
  /** Inline failed lock-read status; history and log reading stay unaffected. */
4209
3990
  function ProviderLockHint(props) {
4210
3991
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -4220,7 +4001,6 @@ window.__ModuleLoader__.load({
4220
4001
  function PlanReviewCard(props) {
4221
4002
  const snapshot = props.useDirectory((value) => value);
4222
4003
  const order = props.useProviderOrder((value) => value);
4223
- const settingsUnavailableReason = mainDefaultsUnavailableReason((0, react.useSyncExternalStore)(props.subscribeMainDefaults, props.getMainDefaultsSnapshot), props.t);
4224
4004
  const lock = (0, react.useSyncExternalStore)(props.providerLockStore.subscribe, props.providerLockStore.getSnapshot);
4225
4005
  const phase = props.useInput((input) => input.phase);
4226
4006
  const blank = props.useSession((session) => session.blank);
@@ -4256,7 +4036,6 @@ window.__ModuleLoader__.load({
4256
4036
  providerLock,
4257
4037
  agentLocked,
4258
4038
  lockFailed: lock.failed,
4259
- ...settingsUnavailableReason === void 0 ? {} : { settingsUnavailableReason },
4260
4039
  directory: pickerDirectoryViewOrdered(snapshot, props, order, props.catalogRoutes?.() ?? {}),
4261
4040
  t: props.t,
4262
4041
  goalRemote: props.goalRemote,
@@ -4265,7 +4044,7 @@ window.__ModuleLoader__.load({
4265
4044
  ...props.roleOf === void 0 ? {} : { roleOf: props.roleOf }
4266
4045
  }, props.matched.key);
4267
4046
  }
4268
- function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, settingsUnavailableReason, directory, t, goalRemote, planPreview, resolveInteractionOperations, roleOf }) {
4047
+ function PlanReviewState({ matched, review, available, providerLock, agentLocked, lockFailed, directory, t, goalRemote, planPreview, resolveInteractionOperations, roleOf }) {
4269
4048
  const { snapshot, getDirectorySnapshot, load, select } = directory;
4270
4049
  const [execution, setExecution] = (0, react.useState)(snapshot.current ?? void 0);
4271
4050
  const [feedback, setFeedback] = (0, react.useState)("");
@@ -4328,8 +4107,7 @@ window.__ModuleLoader__.load({
4328
4107
  setNotice(null);
4329
4108
  load();
4330
4109
  };
4331
- const settingsBlock = settingsUnavailableReason !== void 0 && execution !== void 0 && (snapshot.current === null || execution.provider !== snapshot.current.provider || execution.model !== snapshot.current.model || execution.reasoningEffort !== snapshot.current.reasoningEffort);
4332
- const executionAllowed = execution !== void 0 && !settingsBlock && runtimeChoiceAllowed(providerLock, agentLocked, execution.provider, snapshot.current?.provider, isAgentRole(roleOf?.(execution.provider)));
4110
+ const executionAllowed = execution !== void 0 && runtimeChoiceAllowed(providerLock, agentLocked, execution.provider, snapshot.current?.provider, isAgentRole(roleOf?.(execution.provider)));
4333
4111
  const action = planActionView({
4334
4112
  busy,
4335
4113
  blocked,
@@ -4341,11 +4119,7 @@ window.__ModuleLoader__.load({
4341
4119
  select,
4342
4120
  selection: model,
4343
4121
  current: snapshot.current,
4344
- answer: () => respondAnswer(matched, review.id, review.approve.label, t("plan.responseRejected"), true),
4345
- onNotRestored: (switched) => {
4346
- setNotice(t("plan.notRestored", { model: switched }));
4347
- console.warn("dsh-model-switch: the Session model changed but the Main default was not restored", switched);
4348
- }
4122
+ answer: () => respondAnswer(matched, review.id, review.approve.label, t("plan.responseRejected"), true)
4349
4123
  })) {
4350
4124
  const message = getDirectorySnapshot().error;
4351
4125
  throw new Error(message === null ? t("plan.modelFailed") : t("error.action", { message }));
@@ -4417,12 +4191,11 @@ window.__ModuleLoader__.load({
4417
4191
  errorLabel: (message) => t("plan.pickerCrash", { message }),
4418
4192
  retryLabel: t("retry"),
4419
4193
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerPicker, {
4420
- locked: busy || blocked || settingsUnavailableReason !== void 0,
4194
+ locked: busy || blocked,
4421
4195
  providerLock,
4422
4196
  agentLocked,
4423
4197
  ...roleOf === void 0 ? {} : { roleOf },
4424
4198
  available,
4425
- ...settingsUnavailableReason === void 0 ? {} : { unavailableReason: settingsUnavailableReason },
4426
4199
  directory,
4427
4200
  t,
4428
4201
  ...resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations },
@@ -4456,7 +4229,7 @@ window.__ModuleLoader__.load({
4456
4229
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4457
4230
  className: PlanReviewCard_module_css_default.feedback,
4458
4231
  role: "status",
4459
- children: action.error ?? notice ?? (settingsBlock ? settingsUnavailableReason : null)
4232
+ children: action.error ?? notice
4460
4233
  }),
4461
4234
  decline !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4462
4235
  className: PlanReviewCard_module_css_default.feedbackRow,
@@ -4578,9 +4351,6 @@ window.__ModuleLoader__.load({
4578
4351
  "thinking.off": "关",
4579
4352
  "effort.providerDefault": "Default",
4580
4353
  "status.loading": "正在刷新模型列表…",
4581
- "settings.remoteUnavailable": "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面选择模型。",
4582
- "settings.loading": "正在加载主模型设置;加载完成后才能切换模型。",
4583
- "settings.unavailable": "主模型设置不可写;请在 Host 的本机页面检查设置状态。",
4584
4354
  "error.action": "模型操作失败:{message}",
4585
4355
  "error.picker": "模型选择器出错:{message}(点击重试)",
4586
4356
  "action.reload": "重新加载",
@@ -4601,7 +4371,6 @@ window.__ModuleLoader__.load({
4601
4371
  "plan.pickerCrash": "执行模型选择器出错:{message}",
4602
4372
  "plan.feedback": "拒绝时的反馈",
4603
4373
  "plan.feedbackPlaceholder": "说明要改什么(可留空)",
4604
- "plan.notRestored": "执行模型已切换,但主模型默认值未能还原({model});请在「设置 → 模型」中确认默认模型。",
4605
4374
  "plan.dismissUnsupported": "当前 DSH 版本既未提供 PendingQuestion.dismiss() 也未提供 cancel(),无法回到聊天;请直接在输入框里发送你的意见。",
4606
4375
  "plan.goalRun": "设置为目标并执行",
4607
4376
  "plan.goalFailed": "设置目标失败:{message}",
@@ -4634,9 +4403,6 @@ window.__ModuleLoader__.load({
4634
4403
  "thinking.off": "Off",
4635
4404
  "effort.providerDefault": "Default",
4636
4405
  "status.loading": "Refreshing model list…",
4637
- "settings.remoteUnavailable": "Official DSH disables settings reads and writes in non-loopback browsers. Select a model on the Host loopback URL.",
4638
- "settings.loading": "Main model settings are loading. Wait until they are ready before switching models.",
4639
- "settings.unavailable": "Main model settings are not writable. Check their status on the Host loopback URL.",
4640
4406
  "error.action": "Model operation failed: {message}",
4641
4407
  "error.picker": "Model picker error: {message} (click to retry)",
4642
4408
  "action.reload": "Reload",
@@ -4657,7 +4423,6 @@ window.__ModuleLoader__.load({
4657
4423
  "plan.pickerCrash": "Execution model picker error: {message}",
4658
4424
  "plan.feedback": "Feedback when declining",
4659
4425
  "plan.feedbackPlaceholder": "Say what to change (optional)",
4660
- "plan.notRestored": "The execution model switched, but the Main default could not be restored ({model}); confirm the default in Settings → Models.",
4661
4426
  "plan.dismissUnsupported": "This DSH Host exposes neither PendingQuestion.dismiss() nor cancel(), so the review cannot return to chat; send your feedback as a message instead.",
4662
4427
  "plan.goalRun": "Set as goal and run",
4663
4428
  "plan.goalFailed": "Could not set the goal: {message}",
@@ -4757,7 +4522,6 @@ window.__ModuleLoader__.load({
4757
4522
  function ModelSeat(props) {
4758
4523
  const directory = props.useDirectory((snapshot) => snapshot);
4759
4524
  const order = props.useProviderOrder((value) => value);
4760
- const settingsUnavailableReason = mainDefaultsUnavailableReason((0, react.useSyncExternalStore)(props.subscribeMainDefaults, props.getMainDefaultsSnapshot), props.t);
4761
4525
  const lock = (0, react.useSyncExternalStore)(props.providerLockStore.subscribe, props.providerLockStore.getSnapshot);
4762
4526
  const phase = props.useInput((input) => input.phase);
4763
4527
  const blank = props.useSession((session) => session.blank);
@@ -4773,12 +4537,11 @@ window.__ModuleLoader__.load({
4773
4537
  ]);
4774
4538
  const providerLock = effectiveProviderLock(lock, directory.current?.provider, active, isAgentRole(props.roleOf?.(directory.current?.provider ?? "")));
4775
4539
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [lock.failed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderLockHint, { t: props.t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComposerPicker, {
4776
- locked: props.locked || settingsUnavailableReason !== void 0 || lock.failed && directory.current === null,
4540
+ locked: props.locked || lock.failed && directory.current === null,
4777
4541
  providerLock,
4778
4542
  agentLocked: agentProviderLocked(blank, providerLock, active),
4779
4543
  ...props.roleOf === void 0 ? {} : { roleOf: props.roleOf },
4780
4544
  available: props.available,
4781
- ...settingsUnavailableReason === void 0 ? {} : { unavailableReason: settingsUnavailableReason },
4782
4545
  directory: pickerDirectoryViewOrdered(directory, props, order, props.catalogRoutes?.() ?? {}),
4783
4546
  t: props.t,
4784
4547
  ...props.resolveInteractionOperations === void 0 ? {} : { resolveInteractionOperations: props.resolveInteractionOperations }
@@ -4803,8 +4566,6 @@ window.__ModuleLoader__.load({
4803
4566
  ], (scope) => {
4804
4567
  const models = scope.modelDirectories;
4805
4568
  const sessions = scope.sessions;
4806
- const mainDefaults = scope.configForms.get(MAIN_DEFAULT_CONFIG_ID);
4807
- const ownedForm = scope.configForms.get(MODEL_SWITCH_CONFIG_ID);
4808
4569
  let directoryService;
4809
4570
  const orderStore = providerOrderStore(scope.configForms.get(PROVIDERS_CONFIG_ID));
4810
4571
  scope.inject(["providerDirectory"], (directoryScope) => {
@@ -4843,8 +4604,6 @@ window.__ModuleLoader__.load({
4843
4604
  refreshProviderLock: () => {
4844
4605
  providerLockStore.refresh();
4845
4606
  },
4846
- subscribeMainDefaults: (listener) => mainDefaults.subscribe(listener),
4847
- getMainDefaultsSnapshot: () => mainDefaults.getSnapshot(),
4848
4607
  goalRemote: () => goalRemoteFrom(scope),
4849
4608
  planPreview,
4850
4609
  hooks: {
@@ -4857,36 +4616,22 @@ window.__ModuleLoader__.load({
4857
4616
  if (available) directory.load().catch(() => {});
4858
4617
  },
4859
4618
  select: async (selection) => {
4860
- const uncommitted = {
4861
- committed: false,
4862
- mainDefaultRestored: true
4863
- };
4864
- if (!available) return uncommitted;
4619
+ if (!available) return { committed: false };
4865
4620
  const state = await providerLockStore.refresh();
4866
4621
  const currentProvider = directory.store.getSnapshot().current?.provider;
4867
4622
  if (!isProviderAllowed(state, selection.provider, currentProvider, {
4868
4623
  ...readSessionState(sessions, sessionId),
4869
4624
  agent: isAgentRole(roleOf(selection.provider)),
4870
4625
  currentAgent: currentProvider !== void 0 && isAgentRole(roleOf(currentProvider))
4871
- })) return uncommitted;
4872
- const defaultBeforeSwitch = mainDefaults.getSnapshot();
4873
- if (defaultBeforeSwitch.status !== "ready" || defaultBeforeSwitch.mode !== "host" || !defaultBeforeSwitch.writable || defaultBeforeSwitch.value === void 0 || defaultBeforeSwitch.revision === void 0) return uncommitted;
4626
+ })) return { committed: false };
4874
4627
  let result;
4875
4628
  try {
4876
4629
  result = await directory.select(selection);
4877
4630
  } catch {
4878
- return uncommitted;
4879
- }
4880
- if (!result.ok) return uncommitted;
4881
- if (selection.provider !== currentProvider) try {
4882
- await applyProviderGroupTo(ownedForm, normalizeProviderGroups(ownedForm.getSnapshot().value?.providerGroups), selection.provider);
4883
- } catch (error) {
4884
- console.warn("dsh-model-switch: provider group apply failed", error);
4631
+ return { committed: false };
4885
4632
  }
4886
- return {
4887
- committed: true,
4888
- mainDefaultRestored: (await restoreMainDefault(mainDefaults, defaultBeforeSwitch)).restored
4889
- };
4633
+ if (!result.ok) return { committed: false };
4634
+ return { committed: true };
4890
4635
  }
4891
4636
  };
4892
4637
  };
@@ -4983,56 +4728,13 @@ window.__ModuleLoader__.load({
4983
4728
  }), "dsh-model-switch: localized Settings section");
4984
4729
  const t = ctx.locale.bind(localeNamespace);
4985
4730
  const remote = ctx.remote;
4986
- const mainForm = ctx.configForms.get(MAIN_DEFAULT_CONFIG_ID);
4987
4731
  const owned = ctx.configForms.get(MODEL_SWITCH_CONFIG_ID);
4988
- const main = deriveConfigForm(mainForm, (value) => value, {
4989
- provider: "provider",
4990
- model: "model",
4991
- reasoningEffort: "reasoningEffort"
4992
- });
4993
4732
  const subagent = deriveConfigForm(owned, deriveSubagentSettings, SUBAGENT_SETTINGS_FIELDS);
4994
4733
  const search = deriveConfigForm(owned, deriveSearchSettings, SEARCH_SETTINGS_FIELDS);
4995
4734
  const image = deriveConfigForm(owned, deriveImageSettings, IMAGE_SETTINGS_FIELDS);
4996
4735
  const autoReview = deriveConfigForm(owned, deriveAutoReviewSettings, AUTO_REVIEW_SETTINGS_FIELDS);
4997
4736
  const compaction = deriveConfigForm(owned, deriveCompactionSettings, COMPACTION_SETTINGS_FIELDS);
4998
- /** Adopt the target provider's saved group; a provider without a group leaves the routes untouched. */
4999
- const applyProviderGroupFor = async (provider) => {
5000
- await applyProviderGroupTo(owned, normalizeProviderGroups(owned.getSnapshot().value?.providerGroups), provider);
5001
- };
5002
- const saveMain = async (next, expectedRevision) => {
5003
- const previousProvider = mainForm.getSnapshot().value?.provider;
5004
- if (!await mainForm.mutate([
5005
- {
5006
- op: "set",
5007
- path: ["provider"],
5008
- value: next.provider
5009
- },
5010
- {
5011
- op: "set",
5012
- path: ["model"],
5013
- value: next.model
5014
- },
5015
- next.reasoningEffort === void 0 || next.reasoningEffort === "" ? {
5016
- op: "unset",
5017
- path: ["reasoningEffort"]
5018
- } : {
5019
- op: "set",
5020
- path: ["reasoningEffort"],
5021
- value: next.reasoningEffort
5022
- }
5023
- ], expectedRevision)) {
5024
- if (mainForm.getSnapshot().revision !== expectedRevision) throw new MainSettingsConflictError(t("conflict"));
5025
- throw new Error("settings-rejected");
5026
- }
5027
- const revision = mainForm.getSnapshot().revision;
5028
- if (revision === void 0) throw new Error("settings unavailable after save");
5029
- if (next.provider.trim() !== "" && next.provider !== previousProvider) try {
5030
- await applyProviderGroupFor(next.provider);
5031
- } catch (error) {
5032
- throw new Error(t("providerGroupApplyFailed") + (error instanceof Error ? error.message : t("requestFailed")));
5033
- }
5034
- return revision;
5035
- };
4737
+ const sessionTitle = deriveConfigForm(owned, deriveTitleSettings, TITLE_SETTINGS_FIELDS);
5036
4738
  const connectionRpc = (() => {
5037
4739
  try {
5038
4740
  const rpc = ctx.get("connection", false)?.rpc;
@@ -5065,20 +4767,18 @@ window.__ModuleLoader__.load({
5065
4767
  inject: () => ({
5066
4768
  t,
5067
4769
  hooks: {
5068
- mainSettings: main,
5069
4770
  subagentSettings: subagent,
5070
4771
  searchSettings: search,
5071
4772
  imageSettings: image,
5072
4773
  autoReviewSettings: autoReview,
5073
4774
  compactionSettings: compaction,
4775
+ titleSettings: sessionTitle,
5074
4776
  switchSettings: owned
5075
4777
  },
5076
4778
  capabilities: RUNTIME_CAPABILITIES,
5077
- saveMain,
5078
- setSubagent: (field, value) => value === void 0 ? subagent.unset(field) : subagent.set(field, value),
5079
- setCapability: (route, field, value) => {
5080
- const scope = route === "search" ? search : route === "image" ? image : route === "compaction" ? compaction : autoReview;
5081
- return value === void 0 ? scope.unset(field) : scope.set(field, value);
4779
+ saveSubagent: (ops) => subagent.mutate(ops),
4780
+ saveCapability: (route, ops) => {
4781
+ return (route === "search" ? search : route === "image" ? image : route === "compaction" ? compaction : route === "title" ? sessionTitle : autoReview).mutate(ops);
5082
4782
  },
5083
4783
  setCompactOnSwitch: async (value) => {
5084
4784
  if (!await owned.set("compactOnSwitch", value)) throw new Error("settings-rejected");