dsh-model-switch 0.4.23 → 0.4.25

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
@@ -39,6 +39,11 @@ window.__ModuleLoader__.load({
39
39
  provider: "compactProvider",
40
40
  model: "compactModel"
41
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
+ });
42
47
  function deriveSubagentSettings(settings) {
43
48
  return {
44
49
  mode: settings.subagentMode,
@@ -71,6 +76,12 @@ window.__ModuleLoader__.load({
71
76
  ...settings.compactModel === void 0 ? {} : { model: settings.compactModel }
72
77
  };
73
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
+ }
74
85
  /** Whether both fields of a model route form a complete pair; an incomplete pair stays off. */
75
86
  function routePairConfigured(route) {
76
87
  return route !== void 0 && (route.provider ?? "").trim() !== "" && (route.model ?? "").trim() !== "";
@@ -84,7 +95,9 @@ window.__ModuleLoader__.load({
84
95
  "autoReviewModel",
85
96
  "subagentProvider",
86
97
  "subagentModel",
87
- "subagentReasoningEffort"
98
+ "subagentReasoningEffort",
99
+ "titleProvider",
100
+ "titleModel"
88
101
  ]);
89
102
  /** Own data property, so a literal '__proto__' provider id can never touch a prototype. */
90
103
  function setKey(target, key, value) {
@@ -147,62 +160,6 @@ window.__ModuleLoader__.load({
147
160
  if (!Object.prototype.hasOwnProperty.call(groups, provider)) return void 0;
148
161
  return groups[provider];
149
162
  }
150
- /** A complete pinned pair; either half missing leaves the capability at its default. */
151
- function pair(group, providerField, modelField) {
152
- const provider = group[providerField];
153
- const model = group[modelField];
154
- return present(provider) && present(model) ? {
155
- provider,
156
- model
157
- } : void 0;
158
- }
159
- /**
160
- * Ordered writes applying one group with full-replacement semantics: pinned
161
- * capabilities land as pairs, everything else reverts to its default state
162
- * (compaction/review follow the session model; Subagent follows official
163
- * inherit). The mode write comes last so a fixed route never publishes with
164
- * the previous provider's fields still half-written.
165
- */
166
- function providerGroupWrites(group) {
167
- const compact = pair(group, "compactProvider", "compactModel");
168
- const review = pair(group, "autoReviewProvider", "autoReviewModel");
169
- const subagent = pair(group, "subagentProvider", "subagentModel");
170
- const effort = subagent !== void 0 && present(group.subagentReasoningEffort) ? group.subagentReasoningEffort : void 0;
171
- return [
172
- {
173
- field: "compactProvider",
174
- value: compact?.provider
175
- },
176
- {
177
- field: "compactModel",
178
- value: compact?.model
179
- },
180
- {
181
- field: "autoReviewProvider",
182
- value: review?.provider
183
- },
184
- {
185
- field: "autoReviewModel",
186
- value: review?.model
187
- },
188
- {
189
- field: "subagentProvider",
190
- value: subagent?.provider
191
- },
192
- {
193
- field: "subagentModel",
194
- value: subagent?.model
195
- },
196
- {
197
- field: "subagentReasoningEffort",
198
- value: effort
199
- },
200
- {
201
- field: "subagentMode",
202
- mode: subagent !== void 0 ? "fixed" : "follow-main"
203
- }
204
- ];
205
- }
206
163
  /** Storage entry for one provider's group; absent or blank route fields become ''. */
207
164
  function entryOf(provider, group) {
208
165
  return {
@@ -213,7 +170,9 @@ window.__ModuleLoader__.load({
213
170
  autoReviewModel: group.autoReviewModel ?? "",
214
171
  subagentProvider: group.subagentProvider ?? "",
215
172
  subagentModel: group.subagentModel ?? "",
216
- subagentReasoningEffort: group.subagentReasoningEffort ?? ""
173
+ subagentReasoningEffort: group.subagentReasoningEffort ?? "",
174
+ titleProvider: group.titleProvider ?? "",
175
+ titleModel: group.titleModel ?? ""
217
176
  };
218
177
  }
219
178
  /**
@@ -230,19 +189,6 @@ window.__ModuleLoader__.load({
230
189
  }
231
190
  return Object.entries(groups).map(([key, value]) => entryOf(key, value));
232
191
  }
233
- /**
234
- * Apply a saved group's routes to the live flat settings.
235
- * @returns true when a group existed and every write settled; false when the
236
- * provider has no group (nothing was written). A rejected write propagates.
237
- */
238
- async function applyProviderGroupTo(sink, groups, provider) {
239
- const group = groupOf(groups, provider);
240
- if (group === void 0) return false;
241
- for (const write of providerGroupWrites(group)) if (write.field === "subagentMode") await sink.set("subagentMode", write.mode);
242
- else if (write.value === void 0) await sink.unset(write.field);
243
- else await sink.set(write.field, write.value);
244
- return true;
245
- }
246
192
  //#endregion
247
193
  //#region src/client/derived-config-form.ts
248
194
  function deriveConfigForm(source, project, fields) {
@@ -263,11 +209,28 @@ window.__ModuleLoader__.load({
263
209
  const sourceField = fields[field];
264
210
  if (!(operation === "set" ? await source.set(sourceField, value) : await source.unset(sourceField))) throw new Error("settings-rejected");
265
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
+ };
266
228
  return {
267
229
  getSnapshot,
268
230
  subscribe: (listener) => source.subscribe(listener),
269
231
  set: (field, value) => write("set", field, value),
270
- unset: (field) => write("unset", field)
232
+ unset: (field) => write("unset", field),
233
+ mutate
271
234
  };
272
235
  }
273
236
  //#endregion
@@ -828,6 +791,23 @@ window.__ModuleLoader__.load({
828
791
  strokeLinejoin: "round"
829
792
  })]
830
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
+ });
831
811
  return null;
832
812
  }
833
813
  function RouteCard({ title, summary, hint, icon, open, onToggle, disabled = false, badge, badgeWarn = false, trailing, expandable = true, children }) {
@@ -952,17 +932,34 @@ window.__ModuleLoader__.load({
952
932
  ]
953
933
  });
954
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
+ */
955
946
  function useDraft(snapshot) {
956
947
  const [draft, setDraft] = (0, react.useState)(snapshot.value);
948
+ const pristine = (0, react.useRef)(true);
957
949
  (0, react.useEffect)(() => {
950
+ if (!pristine.current) return;
958
951
  setDraft(snapshot.value);
959
952
  }, [snapshot.revision, snapshot.value]);
960
953
  return [
961
954
  draft,
962
- setDraft,
963
- () => {
955
+ (0, react.useCallback)((value) => {
956
+ pristine.current = false;
957
+ setDraft(value);
958
+ }, []),
959
+ (0, react.useCallback)(() => {
960
+ pristine.current = true;
964
961
  setDraft(snapshot.value);
965
- }
962
+ }, [snapshot.value])
966
963
  ];
967
964
  }
968
965
  function routeName(groups, route) {
@@ -1018,6 +1015,7 @@ window.__ModuleLoader__.load({
1018
1015
  const image = props.useImageSettings((value) => value);
1019
1016
  const autoReview = props.useAutoReviewSettings((value) => value);
1020
1017
  const compaction = props.useCompactionSettings((value) => value);
1018
+ const sessionTitle = props.useTitleSettings((value) => value);
1021
1019
  const switchSettings = props.useSwitchSettings((value) => value);
1022
1020
  const [open, setOpen] = (0, react.useState)();
1023
1021
  const [subagentDraft, setSubagentDraft, resetSubagent] = useDraft(subagent);
@@ -1025,10 +1023,9 @@ window.__ModuleLoader__.load({
1025
1023
  const [imageDraft, setImageDraft, resetImage] = useDraft(image);
1026
1024
  const [autoReviewDraft, setAutoReviewDraft, resetAutoReview] = useDraft(autoReview);
1027
1025
  const [compactionDraft, setCompactionDraft, resetCompaction] = useDraft(compaction);
1026
+ const [titleDraft, setTitleDraft, resetTitle] = useDraft(sessionTitle);
1028
1027
  const [busy, setBusy] = (0, react.useState)();
1029
1028
  const [message, setMessage] = (0, react.useState)();
1030
- const [compactBusy, setCompactBusy] = (0, react.useState)(false);
1031
- const [compactError, setCompactError] = (0, react.useState)();
1032
1029
  const [groupProvider, setGroupProvider] = (0, react.useState)("");
1033
1030
  const [groupDraft, setGroupDraft] = (0, react.useState)(void 0);
1034
1031
  const loadSearchCapabilities = props.loadCapabilities;
@@ -1070,29 +1067,43 @@ window.__ModuleLoader__.load({
1070
1067
  };
1071
1068
  }, [loadSearchCapabilities]);
1072
1069
  const providerGroups = normalizeProviderGroups(switchSettings.value?.providerGroups);
1073
- /** Seed an unsaved group from the live routes. */
1070
+ /**
1071
+ * Seed an unsaved group from the live routes.
1072
+ *
1073
+ * All four per-session capabilities (compaction, autoReview, sessionTitle,
1074
+ * subagent) are seeded from their active deployment-wide cards so a newly
1075
+ * edited provider group starts from the currently active configuration.
1076
+ */
1074
1077
  const seedGroupFromActive = () => {
1075
- const compact = compaction.value !== void 0 && routePairConfigured(compaction.value) ? {
1078
+ const compactionPin = compaction.value !== void 0 && routePairConfigured(compaction.value) ? {
1076
1079
  provider: compaction.value.provider ?? "",
1077
1080
  model: compaction.value.model ?? ""
1078
1081
  } : void 0;
1079
- const review = autoReview.value !== void 0 && routePairConfigured(autoReview.value) ? {
1082
+ const reviewPin = autoReview.value !== void 0 && routePairConfigured(autoReview.value) ? {
1080
1083
  provider: autoReview.value.provider ?? "",
1081
1084
  model: autoReview.value.model ?? ""
1082
1085
  } : void 0;
1086
+ const title = sessionTitle.value !== void 0 && routePairConfigured(sessionTitle.value) ? {
1087
+ provider: sessionTitle.value.provider ?? "",
1088
+ model: sessionTitle.value.model ?? ""
1089
+ } : void 0;
1083
1090
  const fixedSubagent = subagent.value !== void 0 && subagent.value.mode === "fixed" && routePairConfigured(subagent.value) ? {
1084
1091
  provider: subagent.value.provider ?? "",
1085
1092
  model: subagent.value.model ?? "",
1086
1093
  reasoningEffort: subagent.value.reasoningEffort
1087
1094
  } : void 0;
1088
1095
  return {
1089
- ...compact === void 0 ? {} : {
1090
- compactProvider: compact.provider,
1091
- compactModel: compact.model
1096
+ ...compactionPin === void 0 ? {} : {
1097
+ compactProvider: compactionPin.provider,
1098
+ compactModel: compactionPin.model
1092
1099
  },
1093
- ...review === void 0 ? {} : {
1094
- autoReviewProvider: review.provider,
1095
- autoReviewModel: review.model
1100
+ ...reviewPin === void 0 ? {} : {
1101
+ autoReviewProvider: reviewPin.provider,
1102
+ autoReviewModel: reviewPin.model
1103
+ },
1104
+ ...title === void 0 ? {} : {
1105
+ titleProvider: title.provider,
1106
+ titleModel: title.model
1096
1107
  },
1097
1108
  ...fixedSubagent === void 0 ? {} : {
1098
1109
  subagentProvider: fixedSubagent.provider,
@@ -1126,13 +1137,15 @@ window.__ModuleLoader__.load({
1126
1137
  switchSettings.value,
1127
1138
  subagent.value,
1128
1139
  autoReview.value,
1129
- compaction.value
1140
+ compaction.value,
1141
+ sessionTitle.value
1130
1142
  ]);
1131
1143
  if ([
1132
1144
  subagent,
1133
1145
  search,
1134
1146
  image,
1135
- switchSettings
1147
+ switchSettings,
1148
+ sessionTitle
1136
1149
  ].some((form) => form.mode === "memory")) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
1137
1150
  className: ModelSwitchSettings_module_css_default.section,
1138
1151
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
@@ -1182,83 +1195,126 @@ window.__ModuleLoader__.load({
1182
1195
  const autoReviewOn = routePairConfigured(autoReview.value);
1183
1196
  const compactionChoices = deriveRouteChoices(groups, compactionDraft);
1184
1197
  const compactionOn = routePairConfigured(compaction.value);
1198
+ const titleChoices = deriveRouteChoices(groups, titleDraft);
1199
+ const titleOn = routePairConfigured(sessionTitle.value);
1200
+ const catalogSeed = firstCatalogRoute(groups);
1185
1201
  const synced = [
1186
1202
  subagent,
1187
1203
  search,
1188
1204
  image,
1189
1205
  autoReview,
1190
- compaction
1206
+ compaction,
1207
+ sessionTitle
1191
1208
  ].every((snapshot) => snapshot.status === "ready");
1209
+ /**
1210
+ * Run one row save with a visible outcome that ALWAYS settles.
1211
+ *
1212
+ * A stalled write used to latch the page on "保存中…" forever, because `busy`
1213
+ * was only cleared by the settling promise. The 30s deadline reports the stall
1214
+ * instead; a late result is ignored by the race, so it can never overwrite a
1215
+ * newer draft either.
1216
+ * @param route - the row whose save is running, for busy and message routing.
1217
+ * @param operation - the single fenced write to perform.
1218
+ * @returns whether the write was accepted.
1219
+ */
1192
1220
  const run = async (route, operation) => {
1193
- if (busy !== void 0) return;
1221
+ if (busy !== void 0) return false;
1222
+ const timeoutMs = 3e4;
1194
1223
  setBusy(route);
1195
1224
  setMessage(void 0);
1225
+ let timer;
1196
1226
  try {
1197
- await operation();
1227
+ await Promise.race([operation(), new Promise((_resolve, reject) => {
1228
+ timer = setTimeout(() => reject(new Error(props.t("saveTimeout"))), timeoutMs);
1229
+ })]);
1198
1230
  setMessage({
1199
1231
  route,
1200
1232
  text: props.t("saved")
1201
1233
  });
1234
+ return true;
1202
1235
  } catch (error) {
1203
1236
  setMessage({
1204
1237
  route,
1205
1238
  text: error instanceof Error ? error.message : props.t("requestFailed")
1206
1239
  });
1240
+ return false;
1207
1241
  } finally {
1242
+ if (timer !== void 0) clearTimeout(timer);
1208
1243
  setBusy(void 0);
1209
1244
  }
1210
1245
  };
1211
1246
  const saveSubagent = () => {
1212
1247
  if (subagentDraft === void 0 || subagentDraft.mode !== "fixed") return;
1213
- run("subagent", async () => {
1214
- if (subagent.value?.provider !== subagentDraft.provider) await props.setSubagent("provider", subagentDraft.provider);
1215
- const matched = subagentCatalogModel;
1216
- const nextModel = matched?.model.id ?? subagentDraft.model;
1217
- const nextEffort = subagentDraft.reasoningEffort === "" ? void 0 : subagentDraft.reasoningEffort || matched?.effort;
1218
- if (subagent.value?.model !== nextModel) await props.setSubagent("model", nextModel);
1219
- if (subagent.value?.reasoningEffort !== nextEffort) await props.setSubagent("reasoningEffort", nextEffort);
1220
- });
1248
+ const matched = subagentCatalogModel;
1249
+ const provider = (subagentDraft.provider ?? "").trim();
1250
+ const model = (matched?.model.id ?? subagentDraft.model ?? "").trim();
1251
+ if (provider === "" || model === "") return;
1252
+ const nextEffort = subagentDraft.reasoningEffort === "" ? void 0 : subagentDraft.reasoningEffort || matched?.effort;
1253
+ run("subagent", () => props.saveSubagent([
1254
+ {
1255
+ field: "provider",
1256
+ value: provider
1257
+ },
1258
+ {
1259
+ field: "model",
1260
+ value: model
1261
+ },
1262
+ {
1263
+ field: "reasoningEffort",
1264
+ value: nextEffort
1265
+ }
1266
+ ]));
1221
1267
  };
1222
1268
  const persistSubagentEnabled = (enabled) => {
1223
- if (subagentDraft === void 0 || busy === "subagent" || !subagent.writable) return;
1224
- setSubagentDraft({
1225
- ...subagentDraft,
1226
- mode: subagentModeForEnabled(enabled)
1227
- });
1228
- setBusy("subagent");
1229
- setMessage(void 0);
1230
- props.setSubagent("mode", subagentModeForEnabled(enabled)).then(() => {
1231
- setBusy(void 0);
1232
- setOpen((current) => enabled ? "subagent" : current === "subagent" ? void 0 : current);
1233
- }, (error) => {
1234
- setBusy(void 0);
1235
- resetSubagent();
1236
- setMessage({
1237
- route: "subagent",
1238
- text: error instanceof Error ? error.message : props.t("requestFailed")
1269
+ if (subagentDraft === void 0 || busy !== void 0 || !subagent.writable) return;
1270
+ const mode = subagentModeForEnabled(enabled);
1271
+ run("subagent", () => props.saveSubagent([{
1272
+ field: "mode",
1273
+ value: mode
1274
+ }])).then((accepted) => {
1275
+ if (!accepted) {
1276
+ resetSubagent();
1277
+ return;
1278
+ }
1279
+ setSubagentDraft({
1280
+ ...subagentDraft,
1281
+ mode
1239
1282
  });
1283
+ setOpen((current) => enabled ? "subagent" : current === "subagent" ? void 0 : current);
1240
1284
  });
1241
1285
  };
1242
- const saveCapability = (route, current, next) => {
1286
+ /** Both halves of one pinned capability route, written together in one mutation. */
1287
+ const pairOps = (next) => [{
1288
+ field: "provider",
1289
+ value: next.provider
1290
+ }, {
1291
+ field: "model",
1292
+ value: next.model
1293
+ }];
1294
+ const saveCapability = (route, next) => {
1243
1295
  if (next === void 0) return;
1244
- run(route, async () => {
1245
- if (current.value?.provider !== next.provider) await props.setCapability(route, "provider", next.provider);
1246
- if (current.value?.model !== next.model) await props.setCapability(route, "model", next.model);
1247
- });
1296
+ const provider = (next.provider ?? "").trim();
1297
+ const model = (next.model ?? "").trim();
1298
+ if (provider === "" || model === "") return;
1299
+ run(route, () => props.saveCapability(route, [{
1300
+ field: "provider",
1301
+ value: provider
1302
+ }, {
1303
+ field: "model",
1304
+ value: model
1305
+ }]));
1248
1306
  };
1249
1307
  const saveAutoReview = () => {
1250
- if (autoReviewDraft === void 0) return;
1251
- run("autoReview", async () => {
1252
- if (autoReview.value?.provider !== autoReviewDraft.provider) await props.setCapability("autoReview", "provider", autoReviewDraft.provider);
1253
- if (autoReview.value?.model !== autoReviewDraft.model) await props.setCapability("autoReview", "model", autoReviewDraft.model);
1254
- });
1308
+ if (autoReviewDraft === void 0 || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "") return;
1309
+ run("autoReview", () => props.saveCapability("autoReview", pairOps(autoReviewDraft)));
1255
1310
  };
1256
1311
  const saveCompaction = () => {
1257
- if (compactionDraft === void 0) return;
1258
- run("compaction", async () => {
1259
- if (compaction.value?.provider !== compactionDraft.provider) await props.setCapability("compaction", "provider", compactionDraft.provider);
1260
- if (compaction.value?.model !== compactionDraft.model) await props.setCapability("compaction", "model", compactionDraft.model);
1261
- });
1312
+ if (compactionDraft === void 0 || (compactionDraft.provider ?? "").trim() === "" || (compactionDraft.model ?? "").trim() === "") return;
1313
+ run("compaction", () => props.saveCapability("compaction", pairOps(compactionDraft)));
1314
+ };
1315
+ const saveTitle = () => {
1316
+ if (titleDraft === void 0 || (titleDraft.provider ?? "").trim() === "" || (titleDraft.model ?? "").trim() === "") return;
1317
+ run("sessionTitle", () => props.saveCapability("title", pairOps(titleDraft)));
1262
1318
  };
1263
1319
  /**
1264
1320
  * The reviewer switch: Off unsets both stored fields (follow the session
@@ -1267,35 +1323,26 @@ window.__ModuleLoader__.load({
1267
1323
  * flips silently.
1268
1324
  */
1269
1325
  const persistAutoReviewEnabled = (enabled) => {
1270
- if (busy === "autoReview" || !autoReview.writable || autoReview.value === void 0) return;
1326
+ if (busy !== void 0 || !autoReview.writable || autoReview.value === void 0) return;
1271
1327
  if (!enabled) {
1272
1328
  setOpen((current) => current === "autoReview" ? void 0 : current);
1273
- run("autoReview", async () => {
1274
- if (autoReview.value?.provider !== void 0) await props.setCapability("autoReview", "provider", void 0);
1275
- if (autoReview.value?.model !== void 0) await props.setCapability("autoReview", "model", void 0);
1276
- });
1329
+ run("autoReview", () => props.saveCapability("autoReview", [{
1330
+ field: "provider",
1331
+ value: void 0
1332
+ }, {
1333
+ field: "model",
1334
+ value: void 0
1335
+ }]));
1277
1336
  return;
1278
1337
  }
1279
- const seed = firstCatalogRoute(groups);
1280
- if (seed === void 0) return;
1281
- setBusy("autoReview");
1282
- setMessage(void 0);
1283
- (async () => {
1284
- if (autoReview.value?.provider !== seed.provider) await props.setCapability("autoReview", "provider", seed.provider);
1285
- if (autoReview.value?.model !== seed.model) await props.setCapability("autoReview", "model", seed.model);
1286
- })().then(() => {
1338
+ if (catalogSeed === void 0) return;
1339
+ run("autoReview", () => props.saveCapability("autoReview", pairOps(catalogSeed))).then((accepted) => {
1340
+ if (!accepted) return;
1287
1341
  setAutoReviewDraft({
1288
- provider: seed.provider,
1289
- model: seed.model
1342
+ provider: catalogSeed.provider,
1343
+ model: catalogSeed.model
1290
1344
  });
1291
- setBusy(void 0);
1292
1345
  setOpen("autoReview");
1293
- }, (error) => {
1294
- setBusy(void 0);
1295
- setMessage({
1296
- route: "autoReview",
1297
- text: error instanceof Error ? error.message : props.t("requestFailed")
1298
- });
1299
1346
  });
1300
1347
  };
1301
1348
  /**
@@ -1305,55 +1352,82 @@ window.__ModuleLoader__.load({
1305
1352
  * reports, never flips silently.
1306
1353
  */
1307
1354
  const persistCompactionEnabled = (enabled) => {
1308
- if (busy === "compaction" || !compaction.writable || compaction.value === void 0) return;
1355
+ if (busy !== void 0 || !compaction.writable || compaction.value === void 0) return;
1309
1356
  if (!enabled) {
1310
1357
  setOpen((current) => current === "compaction" ? void 0 : current);
1311
- run("compaction", async () => {
1312
- if (compaction.value?.provider !== void 0) await props.setCapability("compaction", "provider", void 0);
1313
- if (compaction.value?.model !== void 0) await props.setCapability("compaction", "model", void 0);
1314
- });
1358
+ run("compaction", () => props.saveCapability("compaction", [{
1359
+ field: "provider",
1360
+ value: void 0
1361
+ }, {
1362
+ field: "model",
1363
+ value: void 0
1364
+ }]));
1315
1365
  return;
1316
1366
  }
1317
- const seed = firstCatalogRoute(groups);
1318
- if (seed === void 0) return;
1319
- setBusy("compaction");
1320
- setMessage(void 0);
1321
- (async () => {
1322
- if (compaction.value?.provider !== seed.provider) await props.setCapability("compaction", "provider", seed.provider);
1323
- if (compaction.value?.model !== seed.model) await props.setCapability("compaction", "model", seed.model);
1324
- })().then(() => {
1367
+ if (catalogSeed === void 0) return;
1368
+ run("compaction", () => props.saveCapability("compaction", pairOps(catalogSeed))).then((accepted) => {
1369
+ if (!accepted) return;
1325
1370
  setCompactionDraft({
1326
- provider: seed.provider,
1327
- model: seed.model
1371
+ provider: catalogSeed.provider,
1372
+ model: catalogSeed.model
1328
1373
  });
1329
- setBusy(void 0);
1330
1374
  setOpen("compaction");
1331
- }, (error) => {
1332
- setBusy(void 0);
1333
- setMessage({
1334
- route: "compaction",
1335
- text: error instanceof Error ? error.message : props.t("requestFailed")
1375
+ });
1376
+ };
1377
+ /**
1378
+ * The session-title switch: Off unsets both stored fields (naming follows
1379
+ * the session's own logged route); On pins the route to the catalog head so
1380
+ * the pin takes effect immediately and stays editable below. A failed write
1381
+ * reports, never flips silently.
1382
+ */
1383
+ const persistTitleEnabled = (enabled) => {
1384
+ if (busy !== void 0 || !sessionTitle.writable || sessionTitle.value === void 0) return;
1385
+ if (!enabled) {
1386
+ setOpen((current) => current === "sessionTitle" ? void 0 : current);
1387
+ run("sessionTitle", () => props.saveCapability("title", [{
1388
+ field: "provider",
1389
+ value: void 0
1390
+ }, {
1391
+ field: "model",
1392
+ value: void 0
1393
+ }]));
1394
+ return;
1395
+ }
1396
+ if (catalogSeed === void 0) return;
1397
+ run("sessionTitle", () => props.saveCapability("title", pairOps(catalogSeed))).then((accepted) => {
1398
+ if (!accepted) return;
1399
+ setTitleDraft({
1400
+ provider: catalogSeed.provider,
1401
+ model: catalogSeed.model
1336
1402
  });
1403
+ setOpen("sessionTitle");
1337
1404
  });
1338
1405
  };
1339
1406
  const subagentRole = subagentDraft?.mode === "fixed" && subagentDraft.provider !== void 0 && subagentDraft.provider !== "" ? props.providerRoleOf?.(subagentDraft.provider) : void 0;
1340
1407
  const subagentOn = subagentDraft?.mode === "fixed";
1341
1408
  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");
1342
- const subagentDisabled = subagent.status !== "ready" || !subagent.writable || subagentDraft === void 0 || busy === "subagent" || !subagentOn || (subagentDraft.provider ?? "").trim() === "" || (subagentDraft.model ?? "").trim() === "";
1343
- const capabilityDisabled = (route, snapshot, next) => snapshot.status !== "ready" || !snapshot.writable || next === void 0 || busy === route || (next.provider ?? "").trim() === "" || (next.model ?? "").trim() === "";
1344
- const autoReviewDisabled = autoReview.status !== "ready" || !autoReview.writable || autoReviewDraft === void 0 || busy === "autoReview" || !autoReviewOn || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "";
1345
- const compactionDisabled = compaction.status !== "ready" || !compaction.writable || compactionDraft === void 0 || busy === "compaction" || !compactionOn || (compactionDraft.provider ?? "").trim() === "" || (compactionDraft.model ?? "").trim() === "";
1409
+ const subagentSwitchDisabled = busy !== void 0 || !subagent.writable || subagentDraft === void 0;
1410
+ const autoReviewSwitchDisabled = busy !== void 0 || !autoReview.writable || autoReview.value === void 0 || !autoReviewOn && catalogSeed === void 0;
1411
+ const compactionSwitchDisabled = busy !== void 0 || !compaction.writable || compaction.value === void 0 || !compactionOn && catalogSeed === void 0;
1412
+ const titleSwitchDisabled = busy !== void 0 || !sessionTitle.writable || sessionTitle.value === void 0 || !titleOn && catalogSeed === void 0;
1413
+ const subagentDisabled = subagent.status !== "ready" || !subagent.writable || subagentDraft === void 0 || busy !== void 0 || !subagentOn || (subagentDraft.provider ?? "").trim() === "" || (subagentDraft.model ?? "").trim() === "";
1414
+ const capabilityDisabled = (_route, snapshot, next) => snapshot.status !== "ready" || !snapshot.writable || next === void 0 || busy !== void 0 || (next.provider ?? "").trim() === "" || (next.model ?? "").trim() === "";
1415
+ const autoReviewDisabled = autoReview.status !== "ready" || !autoReview.writable || autoReviewDraft === void 0 || busy !== void 0 || !autoReviewOn || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "";
1416
+ const compactionDisabled = compaction.status !== "ready" || !compaction.writable || compactionDraft === void 0 || busy !== void 0 || !compactionOn || (compactionDraft.provider ?? "").trim() === "" || (compactionDraft.model ?? "").trim() === "";
1417
+ const titleDisabled = sessionTitle.status !== "ready" || !sessionTitle.writable || titleDraft === void 0 || busy !== void 0 || !titleOn || (titleDraft.provider ?? "").trim() === "" || (titleDraft.model ?? "").trim() === "";
1346
1418
  const configuredGroupCount = Object.keys(providerGroups).length;
1347
- const groupControlsDisabled = busy === "providerGroups" || switchSettings.status !== "ready" || !switchSettings.writable;
1419
+ const groupControlsDisabled = busy !== void 0 || switchSettings.status !== "ready" || !switchSettings.writable;
1348
1420
  const groupProviderChoices = deriveRouteChoices(groups, groupProvider === "" ? void 0 : { provider: groupProvider }).providers.map((choice) => groupOf(providerGroups, choice.id) === void 0 ? choice : {
1349
1421
  ...choice,
1350
1422
  name: choice.name + " ✓"
1351
1423
  });
1352
1424
  const groupCompaction = groupDraft === void 0 ? void 0 : draftPair(groupDraft.compactProvider, groupDraft.compactModel);
1353
- const groupReview = groupDraft === void 0 ? void 0 : draftPair(groupDraft.autoReviewProvider, groupDraft.autoReviewModel);
1425
+ const groupAutoReview = groupDraft === void 0 ? void 0 : draftPair(groupDraft.autoReviewProvider, groupDraft.autoReviewModel);
1426
+ const groupTitle = groupDraft === void 0 ? void 0 : draftPair(groupDraft.titleProvider, groupDraft.titleModel);
1354
1427
  const groupSubagent = groupDraft === void 0 ? void 0 : draftPair(groupDraft.subagentProvider, groupDraft.subagentModel);
1355
1428
  const groupCompactionChoices = deriveRouteChoices(groups, groupCompaction);
1356
- const groupReviewChoices = deriveRouteChoices(groups, groupReview);
1429
+ const groupAutoReviewChoices = deriveRouteChoices(groups, groupAutoReview);
1430
+ const groupTitleChoices = deriveRouteChoices(groups, groupTitle);
1357
1431
  const groupSubagentChoices = deriveRouteChoices(groups, groupSubagent);
1358
1432
  const groupSubMatched = matchCatalogModel(groups.find((group) => group.id === (groupDraft?.subagentProvider ?? ""))?.models ?? [], groupDraft?.subagentModel ?? "");
1359
1433
  const groupSubEfforts = (() => {
@@ -1369,18 +1443,16 @@ window.__ModuleLoader__.load({
1369
1443
  return efforts;
1370
1444
  })();
1371
1445
  /**
1372
- * Enable a capability row by pinning it: to that capability's live route
1373
- * when one is configured, else to the catalog head (same sources the
1374
- * editor's first-edit seed uses).
1446
+ * Enable a capability row inside a provider group by pinning it: to that
1447
+ * capability's live route when one is configured, else to the catalog head.
1375
1448
  */
1376
1449
  const enableGroupCapability = (kind) => {
1377
1450
  const active = seedGroupFromActive();
1378
- const seed = firstCatalogRoute(groups);
1379
1451
  setGroupDraft((draft) => {
1380
1452
  if (draft === void 0) return draft;
1381
1453
  if (kind === "compaction") {
1382
- const provider = active.compactProvider ?? seed?.provider;
1383
- const model = active.compactModel ?? seed?.model;
1454
+ const provider = active.compactProvider ?? catalogSeed?.provider;
1455
+ const model = active.compactModel ?? catalogSeed?.model;
1384
1456
  if (provider === void 0 || model === void 0) return draft;
1385
1457
  return {
1386
1458
  ...draft,
@@ -1389,8 +1461,8 @@ window.__ModuleLoader__.load({
1389
1461
  };
1390
1462
  }
1391
1463
  if (kind === "autoReview") {
1392
- const provider = active.autoReviewProvider ?? seed?.provider;
1393
- const model = active.autoReviewModel ?? seed?.model;
1464
+ const provider = active.autoReviewProvider ?? catalogSeed?.provider;
1465
+ const model = active.autoReviewModel ?? catalogSeed?.model;
1394
1466
  if (provider === void 0 || model === void 0) return draft;
1395
1467
  return {
1396
1468
  ...draft,
@@ -1398,10 +1470,20 @@ window.__ModuleLoader__.load({
1398
1470
  autoReviewModel: model
1399
1471
  };
1400
1472
  }
1401
- const provider = active.subagentProvider ?? seed?.provider;
1402
- const model = active.subagentModel ?? seed?.model;
1473
+ if (kind === "sessionTitle") {
1474
+ const provider = active.titleProvider ?? catalogSeed?.provider;
1475
+ const model = active.titleModel ?? catalogSeed?.model;
1476
+ if (provider === void 0 || model === void 0) return draft;
1477
+ return {
1478
+ ...draft,
1479
+ titleProvider: provider,
1480
+ titleModel: model
1481
+ };
1482
+ }
1483
+ const provider = active.subagentProvider ?? catalogSeed?.provider;
1484
+ const model = active.subagentModel ?? catalogSeed?.model;
1403
1485
  if (provider === void 0 || model === void 0) return draft;
1404
- const effort = active.subagentProvider !== void 0 ? active.subagentReasoningEffort : seed?.reasoningEffort;
1486
+ const effort = active.subagentProvider !== void 0 ? active.subagentReasoningEffort : catalogSeed?.reasoningEffort;
1405
1487
  return {
1406
1488
  ...draft,
1407
1489
  subagentProvider: provider,
@@ -1423,6 +1505,11 @@ window.__ModuleLoader__.load({
1423
1505
  autoReviewProvider: "",
1424
1506
  autoReviewModel: ""
1425
1507
  };
1508
+ if (kind === "sessionTitle") return {
1509
+ ...draft,
1510
+ titleProvider: "",
1511
+ titleModel: ""
1512
+ };
1426
1513
  return {
1427
1514
  ...draft,
1428
1515
  subagentProvider: "",
@@ -1458,69 +1545,6 @@ window.__ModuleLoader__.load({
1458
1545
  className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
1459
1546
  children: catalogError
1460
1547
  }),
1461
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1462
- className: ModelSwitchSettings_module_css_default.group,
1463
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1464
- className: ModelSwitchSettings_module_css_default.groupLabel,
1465
- children: props.t("sendProtection")
1466
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1467
- className: ModelSwitchSettings_module_css_default.rowCard,
1468
- children: [
1469
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1470
- className: ModelSwitchSettings_module_css_default.routeIcon,
1471
- "aria-hidden": "true",
1472
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
1473
- width: "18",
1474
- height: "18",
1475
- viewBox: "0 0 18 18",
1476
- fill: "none",
1477
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1478
- d: "M4 5.5h10M4 9h7M4 12.5h5",
1479
- stroke: "currentColor",
1480
- strokeLinecap: "round"
1481
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1482
- d: "M13 11.5v3.5l2-1.6",
1483
- stroke: "currentColor",
1484
- strokeLinecap: "round",
1485
- strokeLinejoin: "round"
1486
- })]
1487
- })
1488
- }),
1489
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1490
- className: ModelSwitchSettings_module_css_default.rowCopy,
1491
- children: [
1492
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1493
- className: ModelSwitchSettings_module_css_default.rowTitle,
1494
- children: props.t("compactOnSwitch")
1495
- }),
1496
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1497
- className: ModelSwitchSettings_module_css_default.hint,
1498
- children: props.t("compactOnSwitchHelp")
1499
- }),
1500
- compactError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1501
- className: cx(ModelSwitchSettings_module_css_default.hint, ModelSwitchSettings_module_css_default.message),
1502
- children: compactError
1503
- })
1504
- ]
1505
- }),
1506
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1507
- checked: switchSettings.value?.compactOnSwitch !== false,
1508
- disabled: !switchSettings.writable || compactBusy,
1509
- label: props.t("compactOnSwitch"),
1510
- onChange: (value) => {
1511
- setCompactBusy(true);
1512
- setCompactError(void 0);
1513
- props.setCompactOnSwitch(value).then(() => {
1514
- setCompactBusy(false);
1515
- }, (error) => {
1516
- setCompactBusy(false);
1517
- setCompactError(error instanceof Error ? error.message : props.t("requestFailed"));
1518
- });
1519
- }
1520
- })
1521
- ]
1522
- })]
1523
- }),
1524
1548
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1525
1549
  className: ModelSwitchSettings_module_css_default.group,
1526
1550
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
@@ -1538,7 +1562,7 @@ window.__ModuleLoader__.load({
1538
1562
  expandable: subagentOn,
1539
1563
  trailing: subagentDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1540
1564
  checked: subagentOn,
1541
- disabled: busy === "subagent" || !subagent.writable,
1565
+ disabled: subagentSwitchDisabled,
1542
1566
  label: props.t("subagent"),
1543
1567
  onChange: persistSubagentEnabled
1544
1568
  }),
@@ -1551,7 +1575,7 @@ window.__ModuleLoader__.load({
1551
1575
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1552
1576
  label: props.t("provider"),
1553
1577
  value: subagentDraft.provider ?? "",
1554
- disabled: busy === "subagent" || !subagent.writable,
1578
+ disabled: busy !== void 0 || !subagent.writable,
1555
1579
  choices: subagentChoices.providers,
1556
1580
  onChange: (provider) => {
1557
1581
  const first = groups.find((group) => group.id === provider)?.models[0];
@@ -1564,7 +1588,7 @@ window.__ModuleLoader__.load({
1564
1588
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1565
1589
  label: props.t("model"),
1566
1590
  value: subagentCatalogModel?.model.id ?? subagentDraft.model ?? "",
1567
- disabled: busy === "subagent" || !subagent.writable,
1591
+ disabled: busy !== void 0 || !subagent.writable,
1568
1592
  choices: subagentChoices.models,
1569
1593
  onChange: (model) => {
1570
1594
  setSubagentDraft({
@@ -1576,7 +1600,7 @@ window.__ModuleLoader__.load({
1576
1600
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1577
1601
  label: props.t("effort"),
1578
1602
  value: subagentDraft.reasoningEffort || subagentCatalogModel?.effort || "",
1579
- disabled: busy === "subagent" || !subagent.writable,
1603
+ disabled: busy !== void 0 || !subagent.writable,
1580
1604
  choices: subagentEfforts,
1581
1605
  onChange: (effort) => {
1582
1606
  const matched = subagentCatalogModel;
@@ -1642,7 +1666,7 @@ window.__ModuleLoader__.load({
1642
1666
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1643
1667
  label: props.t("provider"),
1644
1668
  value: searchDraft.provider,
1645
- disabled: busy === "search" || !search.writable || searchError !== void 0,
1669
+ disabled: busy !== void 0 || !search.writable || searchError !== void 0,
1646
1670
  choices: searchChoices.providers,
1647
1671
  onChange: (provider) => {
1648
1672
  const first = searchGroups.find((group) => group.id === provider)?.models[0];
@@ -1654,7 +1678,7 @@ window.__ModuleLoader__.load({
1654
1678
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1655
1679
  label: props.t("model"),
1656
1680
  value: searchDraft.model,
1657
- disabled: busy === "search" || !search.writable || searchError !== void 0,
1681
+ disabled: busy !== void 0 || !search.writable || searchError !== void 0,
1658
1682
  choices: searchChoices.models,
1659
1683
  onChange: (model) => {
1660
1684
  setSearchDraft({
@@ -1674,7 +1698,7 @@ window.__ModuleLoader__.load({
1674
1698
  setMessage(void 0);
1675
1699
  },
1676
1700
  onSave: () => {
1677
- saveCapability("search", search, searchDraft);
1701
+ saveCapability("search", searchDraft);
1678
1702
  }
1679
1703
  })
1680
1704
  ] })
@@ -1709,7 +1733,7 @@ window.__ModuleLoader__.load({
1709
1733
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1710
1734
  label: props.t("provider"),
1711
1735
  value: imageDraft.provider,
1712
- disabled: busy === "image" || !image.writable,
1736
+ disabled: busy !== void 0 || !image.writable,
1713
1737
  choices: imageChoices.providers,
1714
1738
  onChange: (provider) => {
1715
1739
  const model = provider === "grok" ? "grok-imagine-image-quality" : groups.find((group) => group.id === provider)?.models[0]?.id;
@@ -1721,7 +1745,7 @@ window.__ModuleLoader__.load({
1721
1745
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1722
1746
  label: props.t("model"),
1723
1747
  value: imageDraft.model,
1724
- disabled: busy === "image" || !image.writable,
1748
+ disabled: busy !== void 0 || !image.writable,
1725
1749
  choices: imageChoices.models,
1726
1750
  onChange: (model) => {
1727
1751
  setImageDraft({
@@ -1741,7 +1765,7 @@ window.__ModuleLoader__.load({
1741
1765
  setMessage(void 0);
1742
1766
  },
1743
1767
  onSave: () => {
1744
- saveCapability("image", image, imageDraft);
1768
+ saveCapability("image", imageDraft);
1745
1769
  }
1746
1770
  })
1747
1771
  ] })
@@ -1767,7 +1791,7 @@ window.__ModuleLoader__.load({
1767
1791
  expandable: autoReviewOn,
1768
1792
  trailing: autoReviewDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1769
1793
  checked: autoReviewOn,
1770
- disabled: busy === "autoReview" || !autoReview.writable,
1794
+ disabled: autoReviewSwitchDisabled,
1771
1795
  label: props.t("autoReview"),
1772
1796
  onChange: persistAutoReviewEnabled
1773
1797
  }),
@@ -1784,7 +1808,7 @@ window.__ModuleLoader__.load({
1784
1808
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1785
1809
  label: props.t("provider"),
1786
1810
  value: autoReviewDraft.provider ?? "",
1787
- disabled: busy === "autoReview" || !autoReview.writable,
1811
+ disabled: busy !== void 0 || !autoReview.writable,
1788
1812
  choices: autoReviewChoices.providers,
1789
1813
  onChange: (provider) => {
1790
1814
  const first = groups.find((group) => group.id === provider)?.models[0];
@@ -1796,7 +1820,7 @@ window.__ModuleLoader__.load({
1796
1820
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1797
1821
  label: props.t("model"),
1798
1822
  value: autoReviewDraft.model ?? "",
1799
- disabled: busy === "autoReview" || !autoReview.writable,
1823
+ disabled: busy !== void 0 || !autoReview.writable,
1800
1824
  choices: autoReviewChoices.models,
1801
1825
  onChange: (model) => {
1802
1826
  setAutoReviewDraft({
@@ -1831,7 +1855,7 @@ window.__ModuleLoader__.load({
1831
1855
  expandable: compactionOn,
1832
1856
  trailing: compactionDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1833
1857
  checked: compactionOn,
1834
- disabled: busy === "compaction" || !compaction.writable,
1858
+ disabled: compactionSwitchDisabled,
1835
1859
  label: props.t("compaction"),
1836
1860
  onChange: persistCompactionEnabled
1837
1861
  }),
@@ -1848,7 +1872,7 @@ window.__ModuleLoader__.load({
1848
1872
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1849
1873
  label: props.t("provider"),
1850
1874
  value: compactionDraft.provider ?? "",
1851
- disabled: busy === "compaction" || !compaction.writable,
1875
+ disabled: busy !== void 0 || !compaction.writable,
1852
1876
  choices: compactionChoices.providers,
1853
1877
  onChange: (provider) => {
1854
1878
  const first = groups.find((group) => group.id === provider)?.models[0];
@@ -1860,7 +1884,7 @@ window.__ModuleLoader__.load({
1860
1884
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1861
1885
  label: props.t("model"),
1862
1886
  value: compactionDraft.model ?? "",
1863
- disabled: busy === "compaction" || !compaction.writable,
1887
+ disabled: busy !== void 0 || !compaction.writable,
1864
1888
  choices: compactionChoices.models,
1865
1889
  onChange: (model) => {
1866
1890
  setCompactionDraft({
@@ -1882,6 +1906,70 @@ window.__ModuleLoader__.load({
1882
1906
  onSave: saveCompaction
1883
1907
  })
1884
1908
  ] })
1909
+ }),
1910
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RouteCard, {
1911
+ title: props.t("sessionTitle"),
1912
+ summary: titleDraft === void 0 ? props.t("loading") : titleOn ? routeName(groups, titleDraft) : props.t("titleFollow"),
1913
+ hint: props.t("titleHelp"),
1914
+ icon: "sessionTitle",
1915
+ open: open === "sessionTitle" && titleOn,
1916
+ onToggle: () => {
1917
+ if (titleOn) toggle("sessionTitle");
1918
+ },
1919
+ expandable: titleOn,
1920
+ trailing: titleDraft === void 0 ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1921
+ checked: titleOn,
1922
+ disabled: titleSwitchDisabled,
1923
+ label: props.t("sessionTitle"),
1924
+ onChange: persistTitleEnabled
1925
+ }),
1926
+ children: titleDraft === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1927
+ className: ModelSwitchSettings_module_css_default.hint,
1928
+ children: props.t("loading")
1929
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1931
+ className: ModelSwitchSettings_module_css_default.hint,
1932
+ children: props.t("titleRouteHelp")
1933
+ }),
1934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1935
+ className: ModelSwitchSettings_module_css_default.formGrid,
1936
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1937
+ label: props.t("provider"),
1938
+ value: titleDraft.provider ?? "",
1939
+ disabled: busy !== void 0 || !sessionTitle.writable,
1940
+ choices: titleChoices.providers,
1941
+ onChange: (provider) => {
1942
+ const first = groups.find((group) => group.id === provider)?.models[0];
1943
+ setTitleDraft({
1944
+ provider,
1945
+ ...first === void 0 ? {} : { model: first.id }
1946
+ });
1947
+ }
1948
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1949
+ label: props.t("model"),
1950
+ value: titleDraft.model ?? "",
1951
+ disabled: busy !== void 0 || !sessionTitle.writable,
1952
+ choices: titleChoices.models,
1953
+ onChange: (model) => {
1954
+ setTitleDraft({
1955
+ ...titleDraft,
1956
+ model
1957
+ });
1958
+ }
1959
+ })]
1960
+ }),
1961
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Actions, {
1962
+ t: props.t,
1963
+ busy: busy === "sessionTitle",
1964
+ disabled: titleDisabled,
1965
+ ...message?.route === "sessionTitle" ? { message: message.text } : {},
1966
+ onCancel: () => {
1967
+ resetTitle();
1968
+ setMessage(void 0);
1969
+ },
1970
+ onSave: saveTitle
1971
+ })
1972
+ ] })
1885
1973
  })
1886
1974
  ]
1887
1975
  }),
@@ -1932,7 +2020,7 @@ window.__ModuleLoader__.load({
1932
2020
  })]
1933
2021
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1934
2022
  checked: groupCompaction !== void 0,
1935
- disabled: groupControlsDisabled,
2023
+ disabled: groupControlsDisabled || groupCompaction === void 0 && catalogSeed === void 0 && !compactionOn,
1936
2024
  label: props.t("compaction"),
1937
2025
  onChange: (value) => {
1938
2026
  if (value) enableGroupCapability("compaction");
@@ -1977,11 +2065,11 @@ window.__ModuleLoader__.load({
1977
2065
  children: props.t("autoReview")
1978
2066
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1979
2067
  className: ModelSwitchSettings_module_css_default.hint,
1980
- children: groupReview === void 0 ? props.t("autoReviewFollow") : routeName(groups, groupReview)
2068
+ children: groupAutoReview === void 0 ? props.t("autoReviewFollow") : routeName(groups, groupAutoReview)
1981
2069
  })]
1982
2070
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
1983
- checked: groupReview !== void 0,
1984
- disabled: groupControlsDisabled,
2071
+ checked: groupAutoReview !== void 0,
2072
+ disabled: groupControlsDisabled || groupAutoReview === void 0 && catalogSeed === void 0 && !autoReviewOn,
1985
2073
  label: props.t("autoReview"),
1986
2074
  onChange: (value) => {
1987
2075
  if (value) enableGroupCapability("autoReview");
@@ -1989,13 +2077,13 @@ window.__ModuleLoader__.load({
1989
2077
  }
1990
2078
  })]
1991
2079
  }),
1992
- groupReview === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2080
+ groupAutoReview === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1993
2081
  className: ModelSwitchSettings_module_css_default.formGrid,
1994
2082
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
1995
2083
  label: props.t("provider"),
1996
- value: groupReview.provider,
2084
+ value: groupAutoReview.provider,
1997
2085
  disabled: groupControlsDisabled,
1998
- choices: groupReviewChoices.providers,
2086
+ choices: groupAutoReviewChoices.providers,
1999
2087
  onChange: (provider) => {
2000
2088
  const first = groups.find((item) => item.id === provider)?.models[0];
2001
2089
  setGroupDraft((draft) => draft === void 0 ? draft : {
@@ -2006,9 +2094,9 @@ window.__ModuleLoader__.load({
2006
2094
  }
2007
2095
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2008
2096
  label: props.t("model"),
2009
- value: groupReview.model,
2097
+ value: groupAutoReview.model,
2010
2098
  disabled: groupControlsDisabled,
2011
- choices: groupReviewChoices.models,
2099
+ choices: groupAutoReviewChoices.models,
2012
2100
  onChange: (model) => {
2013
2101
  setGroupDraft((draft) => draft === void 0 ? draft : {
2014
2102
  ...draft,
@@ -2017,6 +2105,55 @@ window.__ModuleLoader__.load({
2017
2105
  }
2018
2106
  })]
2019
2107
  }),
2108
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2109
+ className: ModelSwitchSettings_module_css_default.rowCard,
2110
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2111
+ className: ModelSwitchSettings_module_css_default.rowCopy,
2112
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2113
+ className: ModelSwitchSettings_module_css_default.rowTitle,
2114
+ children: props.t("sessionTitle")
2115
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2116
+ className: ModelSwitchSettings_module_css_default.hint,
2117
+ children: groupTitle === void 0 ? props.t("titleFollow") : routeName(groups, groupTitle)
2118
+ })]
2119
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
2120
+ checked: groupTitle !== void 0,
2121
+ disabled: groupControlsDisabled || groupTitle === void 0 && catalogSeed === void 0 && !titleOn,
2122
+ label: props.t("sessionTitle"),
2123
+ onChange: (value) => {
2124
+ if (value) enableGroupCapability("sessionTitle");
2125
+ else disableGroupCapability("sessionTitle");
2126
+ }
2127
+ })]
2128
+ }),
2129
+ groupTitle === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2130
+ className: ModelSwitchSettings_module_css_default.formGrid,
2131
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2132
+ label: props.t("provider"),
2133
+ value: groupTitle.provider,
2134
+ disabled: groupControlsDisabled,
2135
+ choices: groupTitleChoices.providers,
2136
+ onChange: (provider) => {
2137
+ const first = groups.find((item) => item.id === provider)?.models[0];
2138
+ setGroupDraft((draft) => draft === void 0 ? draft : {
2139
+ ...draft,
2140
+ titleProvider: provider,
2141
+ titleModel: first?.id ?? ""
2142
+ });
2143
+ }
2144
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2145
+ label: props.t("model"),
2146
+ value: groupTitle.model,
2147
+ disabled: groupControlsDisabled,
2148
+ choices: groupTitleChoices.models,
2149
+ onChange: (model) => {
2150
+ setGroupDraft((draft) => draft === void 0 ? draft : {
2151
+ ...draft,
2152
+ titleModel: model
2153
+ });
2154
+ }
2155
+ })]
2156
+ }),
2020
2157
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2021
2158
  className: ModelSwitchSettings_module_css_default.rowCard,
2022
2159
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2030,7 +2167,7 @@ window.__ModuleLoader__.load({
2030
2167
  })]
2031
2168
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
2032
2169
  checked: groupSubagent !== void 0,
2033
- disabled: groupControlsDisabled,
2170
+ disabled: groupControlsDisabled || groupSubagent === void 0 && catalogSeed === void 0 && !(subagent.value?.mode === "fixed" && routePairConfigured(subagent.value)),
2034
2171
  label: props.t("subagent"),
2035
2172
  onChange: (value) => {
2036
2173
  if (value) enableGroupCapability("subagent");
@@ -2119,12 +2256,9 @@ window.__ModuleLoader__.load({
2119
2256
  subtitle: "为子代理与能力路由设置固定模型;当前会话模型用输入框旁的选择器直接切换。",
2120
2257
  conversationRoutes: "对话路由",
2121
2258
  capabilityRoutes: "能力路由",
2122
- sendProtection: "发送保护",
2123
2259
  settingsSynced: "设置已同步",
2124
2260
  remoteSettingsUnavailable: "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面打开设置。",
2125
2261
  cancel: "取消",
2126
- compactOnSwitch: "切换时压缩",
2127
- compactOnSwitchHelp: "发送且换了模型时,先按目标窗口检查上下文;超了就用切换前的模型压一次历史,再继续请求。只选模型、不发送,不会压缩。官方自动压缩和 /compact 不受影响。压缩会产生额外模型用量,摘要可能省略细节。",
2128
2262
  provider: "提供商",
2129
2263
  model: "模型",
2130
2264
  providerDefault: "使用提供商默认值",
@@ -2154,13 +2288,18 @@ window.__ModuleLoader__.load({
2154
2288
  compactionFollow: "跟随会话模型",
2155
2289
  compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。",
2156
2290
  compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。",
2291
+ sessionTitle: "会话命名模型",
2292
+ titleFollow: "跟随会话模型",
2293
+ titleHelp: "为官方自动命名(新会话首条消息后生成标题)指定模型;关闭(两个字段都留空)时命名跟随当前会话模型。",
2294
+ titleRouteHelp: "命名请求固定使用这里的 Provider/模型(不带推理强度);关闭时跟随会话模型。保存后立即对下一次命名生效,不会中断正在进行的命名,会话日志记录的也是实际使用的模型。",
2157
2295
  providerGroups: "供应商配置组",
2158
2296
  providerGroupsCard: "按供应商预设路由",
2159
- providerGroupsSummary: "会话模型切换到该供应商时自动应用对应组",
2160
- providerGroupsHelp: "为压缩、子代理、审查按供应商分别保存路由。",
2161
- providerGroupsBodyHelp: "每个供应商保存一组压缩、子代理、审查路由;本会话的模型切换到该供应商时(无论通过插件的模型选择器还是 TUI 等其他界面),这三条路由自动写入上方对应卡片。切换到未配置的供应商时现有路由保持不变;应用会覆盖三张卡片的当前值(含手动修改)。",
2297
+ providerGroupsSummary: "使用该供应商的会话自动采用对应组",
2298
+ providerGroupsHelp: "为压缩、自动审查、会话命名与子代理按供应商分别保存路由。",
2299
+ providerGroupsBodyHelp: "每个供应商保存一组压缩、自动审查、会话命名与子代理路由。会话自身的供应商与该组一致时,这四项能力按组实时解析,未固定项回落到上方卡片里的全局设置。切换某个会话的模型不会写入任何全局设置,因此不同供应商的多个会话并行时互不影响,会话中切换供应商后下一次调用也会立即跟随新供应商的配置组。",
2162
2300
  providerGroupPick: "选择供应商以编辑其配置组。",
2163
2301
  providerGroupDelete: "删除该组",
2302
+ saveTimeout: "保存超时,请重试",
2164
2303
  "reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
2165
2304
  "reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
2166
2305
  "reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
@@ -2174,12 +2313,9 @@ window.__ModuleLoader__.load({
2174
2313
  subtitle: "Pin routes for Subagents and capability tools; switch the active session model through the composer picker.",
2175
2314
  conversationRoutes: "Conversation routes",
2176
2315
  capabilityRoutes: "Capability routes",
2177
- sendProtection: "Send protection",
2178
2316
  settingsSynced: "Settings synced",
2179
2317
  remoteSettingsUnavailable: "Official DSH disables settings reads and writes in non-loopback browsers. Open Settings on the Host loopback URL to edit model routes.",
2180
2318
  cancel: "Cancel",
2181
- compactOnSwitch: "Compact on switch",
2182
- 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.",
2183
2319
  provider: "Provider",
2184
2320
  model: "Model",
2185
2321
  providerDefault: "Provider default",
@@ -2209,13 +2345,18 @@ window.__ModuleLoader__.load({
2209
2345
  compactionFollow: "Follows the conversation model",
2210
2346
  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.",
2211
2347
  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.",
2348
+ sessionTitle: "Session title model",
2349
+ titleFollow: "Follows the session model",
2350
+ 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.",
2351
+ 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.",
2212
2352
  providerGroups: "Provider groups",
2213
2353
  providerGroupsCard: "Per-provider route presets",
2214
- providerGroupsSummary: "Applied automatically when this session switches to that provider",
2215
- providerGroupsHelp: "Save compaction, subagent, and review routes per provider.",
2216
- providerGroupsBodyHelp: "Each provider stores one compaction, subagent, and review route group. When this session switches to that provider — through the plugin picker or any other surface such as the TUI — these three routes are written 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.",
2354
+ providerGroupsSummary: "Sessions using that provider adopt this group",
2355
+ providerGroupsHelp: "Save compaction, auto-review, session-naming, and subagent routes per provider.",
2356
+ providerGroupsBodyHelp: "Each provider stores one route group for compaction, auto review, session naming, and subagents. A session running on that provider resolves all four capabilities from the group and falls back to the cards above when a row is off. Switching a session’s model writes no global setting, so parallel sessions on different providers never affect each other, and mid-session provider switches take effect on the very next call.",
2217
2357
  providerGroupPick: "Pick a provider to edit its group.",
2218
2358
  providerGroupDelete: "Delete group",
2359
+ saveTimeout: "The save timed out; try again",
2219
2360
  "reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
2220
2361
  "reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
2221
2362
  "reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
@@ -3704,66 +3845,6 @@ window.__ModuleLoader__.load({
3704
3845
  }, operations);
3705
3846
  }
3706
3847
  //#endregion
3707
- //#region src/client/picker/provider-group-watch.ts
3708
- /**
3709
- * Watch one directory store and apply groups on provider changes.
3710
- *
3711
- * - The first observed provider (or the one already loaded) is the baseline
3712
- * and never triggers a write, so mounting and initial load stay silent.
3713
- * - Applies are serialized: a change arriving mid-apply coalesces to the
3714
- * latest target and runs once the current apply finishes, so two writes
3715
- * never interleave pair writes.
3716
- * - `current` being null (catalog still loading) is ignored, not a change.
3717
- * @param options - the store to watch and the apply to run.
3718
- * @returns the watch handle.
3719
- */
3720
- function createProviderGroupWatch(options) {
3721
- const onFailure = options.onFailure ?? ((error) => {
3722
- console.warn("dsh-model-switch: provider group apply failed", error);
3723
- });
3724
- let baselined = false;
3725
- let lastSeen;
3726
- const initial = options.source.getSnapshot().current;
3727
- if (initial !== null) {
3728
- baselined = true;
3729
- lastSeen = initial.provider;
3730
- }
3731
- let running = false;
3732
- let pending;
3733
- const run = async (provider) => {
3734
- running = true;
3735
- try {
3736
- await options.apply(provider);
3737
- } catch (error) {
3738
- onFailure(error);
3739
- } finally {
3740
- running = false;
3741
- if (pending !== void 0) {
3742
- const next = pending;
3743
- pending = void 0;
3744
- run(next);
3745
- }
3746
- }
3747
- };
3748
- const check = () => {
3749
- const current = options.source.getSnapshot().current;
3750
- if (current === null) return;
3751
- if (!baselined) {
3752
- baselined = true;
3753
- lastSeen = current.provider;
3754
- return;
3755
- }
3756
- if (current.provider === lastSeen) return;
3757
- lastSeen = current.provider;
3758
- if (running) {
3759
- pending = current.provider;
3760
- return;
3761
- }
3762
- run(current.provider);
3763
- };
3764
- return { dispose: options.source.subscribe(check) };
3765
- }
3766
- //#endregion
3767
3848
  //#region src/picker/pending-dismissal.ts
3768
3849
  /** Raised when the running Host exposes neither withdrawal verb. */
3769
3850
  var PlanDismissalUnsupportedError = class extends Error {
@@ -4557,23 +4638,6 @@ window.__ModuleLoader__.load({
4557
4638
  ], (scope) => {
4558
4639
  const models = scope.modelDirectories;
4559
4640
  const sessions = scope.sessions;
4560
- const ownedForm = scope.configForms.get(MODEL_SWITCH_CONFIG_ID);
4561
- const groupWatches = /* @__PURE__ */ new Map();
4562
- const watchGroupsFor = (sessionId, source) => {
4563
- const key = String(sessionId);
4564
- if (groupWatches.has(key)) return;
4565
- const watch = createProviderGroupWatch({
4566
- source,
4567
- apply: async (provider) => {
4568
- await applyProviderGroupTo(ownedForm, normalizeProviderGroups(ownedForm.getSnapshot().value?.providerGroups), provider);
4569
- }
4570
- });
4571
- groupWatches.set(key, watch.dispose);
4572
- };
4573
- scope.effect(() => () => {
4574
- for (const dispose of groupWatches.values()) dispose();
4575
- groupWatches.clear();
4576
- });
4577
4641
  let directoryService;
4578
4642
  const orderStore = providerOrderStore(scope.configForms.get(PROVIDERS_CONFIG_ID));
4579
4643
  scope.inject(["providerDirectory"], (directoryScope) => {
@@ -4604,7 +4668,6 @@ window.__ModuleLoader__.load({
4604
4668
  };
4605
4669
  const roleOf = (key) => readProviderRole(directoryService, key);
4606
4670
  const providerLockStore = createProviderLockStore(readLock);
4607
- if (available) watchGroupsFor(sessionId, directory.store);
4608
4671
  return {
4609
4672
  available,
4610
4673
  roleOf,
@@ -4743,6 +4806,7 @@ window.__ModuleLoader__.load({
4743
4806
  const image = deriveConfigForm(owned, deriveImageSettings, IMAGE_SETTINGS_FIELDS);
4744
4807
  const autoReview = deriveConfigForm(owned, deriveAutoReviewSettings, AUTO_REVIEW_SETTINGS_FIELDS);
4745
4808
  const compaction = deriveConfigForm(owned, deriveCompactionSettings, COMPACTION_SETTINGS_FIELDS);
4809
+ const sessionTitle = deriveConfigForm(owned, deriveTitleSettings, TITLE_SETTINGS_FIELDS);
4746
4810
  const connectionRpc = (() => {
4747
4811
  try {
4748
4812
  const rpc = ctx.get("connection", false)?.rpc;
@@ -4780,16 +4844,13 @@ window.__ModuleLoader__.load({
4780
4844
  imageSettings: image,
4781
4845
  autoReviewSettings: autoReview,
4782
4846
  compactionSettings: compaction,
4847
+ titleSettings: sessionTitle,
4783
4848
  switchSettings: owned
4784
4849
  },
4785
4850
  capabilities: RUNTIME_CAPABILITIES,
4786
- setSubagent: (field, value) => value === void 0 ? subagent.unset(field) : subagent.set(field, value),
4787
- setCapability: (route, field, value) => {
4788
- const scope = route === "search" ? search : route === "image" ? image : route === "compaction" ? compaction : autoReview;
4789
- return value === void 0 ? scope.unset(field) : scope.set(field, value);
4790
- },
4791
- setCompactOnSwitch: async (value) => {
4792
- if (!await owned.set("compactOnSwitch", value)) throw new Error("settings-rejected");
4851
+ saveSubagent: (ops) => subagent.mutate(ops),
4852
+ saveCapability: (route, ops) => {
4853
+ return (route === "search" ? search : route === "image" ? image : route === "compaction" ? compaction : route === "title" ? sessionTitle : autoReview).mutate(ops);
4793
4854
  },
4794
4855
  saveProviderGroup: async (provider, group) => {
4795
4856
  const current = owned.getSnapshot().value?.providerGroups;