dsh-model-switch 0.4.23 → 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/CHANGELOG.md +14 -0
- package/PRODUCT.md +4 -3
- package/README.md +34 -7
- package/README.zh.md +34 -7
- package/cordis.patch.yml +15 -1
- package/docs/screenshots/settings-subagent.png +0 -0
- package/lib/auto-review.js +8 -0
- package/lib/client.js +338 -346
- package/lib/compaction.js +51 -18
- package/lib/index.js +22 -75
- package/lib/provider-group-CsZi7H29.js +94 -0
- package/lib/session-route-h9SAqajc.js +172 -0
- package/lib/session-title.js +217 -0
- package/lib/subagent-runtime.js +20 -17
- package/lib/types/auto-review.d.ts +8 -0
- package/lib/types/client/ModelSwitchSettings.d.ts +8 -3
- package/lib/types/client/derived-config-form.d.ts +14 -0
- package/lib/types/client/locales.d.ts +8 -3
- package/lib/types/client-contract.d.ts +8 -0
- package/lib/types/compaction.d.ts +25 -4
- package/lib/types/host-settings.d.ts +6 -0
- package/lib/types/provider-group.d.ts +20 -36
- package/lib/types/session-route.d.ts +111 -0
- package/lib/types/session-title.d.ts +104 -0
- package/lib/types/subagent-runtime.d.ts +14 -3
- package/package.json +24 -1
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
|
-
|
|
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,6 +1023,7 @@ 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
1029
|
const [compactBusy, setCompactBusy] = (0, react.useState)(false);
|
|
@@ -1070,15 +1069,17 @@ window.__ModuleLoader__.load({
|
|
|
1070
1069
|
};
|
|
1071
1070
|
}, [loadSearchCapabilities]);
|
|
1072
1071
|
const providerGroups = normalizeProviderGroups(switchSettings.value?.providerGroups);
|
|
1073
|
-
/**
|
|
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
|
+
*/
|
|
1074
1079
|
const seedGroupFromActive = () => {
|
|
1075
|
-
const
|
|
1076
|
-
provider:
|
|
1077
|
-
model:
|
|
1078
|
-
} : void 0;
|
|
1079
|
-
const review = autoReview.value !== void 0 && routePairConfigured(autoReview.value) ? {
|
|
1080
|
-
provider: autoReview.value.provider ?? "",
|
|
1081
|
-
model: autoReview.value.model ?? ""
|
|
1080
|
+
const title = sessionTitle.value !== void 0 && routePairConfigured(sessionTitle.value) ? {
|
|
1081
|
+
provider: sessionTitle.value.provider ?? "",
|
|
1082
|
+
model: sessionTitle.value.model ?? ""
|
|
1082
1083
|
} : void 0;
|
|
1083
1084
|
const fixedSubagent = subagent.value !== void 0 && subagent.value.mode === "fixed" && routePairConfigured(subagent.value) ? {
|
|
1084
1085
|
provider: subagent.value.provider ?? "",
|
|
@@ -1086,13 +1087,9 @@ window.__ModuleLoader__.load({
|
|
|
1086
1087
|
reasoningEffort: subagent.value.reasoningEffort
|
|
1087
1088
|
} : void 0;
|
|
1088
1089
|
return {
|
|
1089
|
-
...
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
},
|
|
1093
|
-
...review === void 0 ? {} : {
|
|
1094
|
-
autoReviewProvider: review.provider,
|
|
1095
|
-
autoReviewModel: review.model
|
|
1090
|
+
...title === void 0 ? {} : {
|
|
1091
|
+
titleProvider: title.provider,
|
|
1092
|
+
titleModel: title.model
|
|
1096
1093
|
},
|
|
1097
1094
|
...fixedSubagent === void 0 ? {} : {
|
|
1098
1095
|
subagentProvider: fixedSubagent.provider,
|
|
@@ -1126,13 +1123,15 @@ window.__ModuleLoader__.load({
|
|
|
1126
1123
|
switchSettings.value,
|
|
1127
1124
|
subagent.value,
|
|
1128
1125
|
autoReview.value,
|
|
1129
|
-
compaction.value
|
|
1126
|
+
compaction.value,
|
|
1127
|
+
sessionTitle.value
|
|
1130
1128
|
]);
|
|
1131
1129
|
if ([
|
|
1132
1130
|
subagent,
|
|
1133
1131
|
search,
|
|
1134
1132
|
image,
|
|
1135
|
-
switchSettings
|
|
1133
|
+
switchSettings,
|
|
1134
|
+
sessionTitle
|
|
1136
1135
|
].some((form) => form.mode === "memory")) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
|
|
1137
1136
|
className: ModelSwitchSettings_module_css_default.section,
|
|
1138
1137
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
@@ -1182,83 +1181,125 @@ window.__ModuleLoader__.load({
|
|
|
1182
1181
|
const autoReviewOn = routePairConfigured(autoReview.value);
|
|
1183
1182
|
const compactionChoices = deriveRouteChoices(groups, compactionDraft);
|
|
1184
1183
|
const compactionOn = routePairConfigured(compaction.value);
|
|
1184
|
+
const titleChoices = deriveRouteChoices(groups, titleDraft);
|
|
1185
|
+
const titleOn = routePairConfigured(sessionTitle.value);
|
|
1185
1186
|
const synced = [
|
|
1186
1187
|
subagent,
|
|
1187
1188
|
search,
|
|
1188
1189
|
image,
|
|
1189
1190
|
autoReview,
|
|
1190
|
-
compaction
|
|
1191
|
+
compaction,
|
|
1192
|
+
sessionTitle
|
|
1191
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
|
+
*/
|
|
1192
1205
|
const run = async (route, operation) => {
|
|
1193
|
-
if (busy !== void 0) return;
|
|
1206
|
+
if (busy !== void 0) return false;
|
|
1207
|
+
const timeoutMs = 3e4;
|
|
1194
1208
|
setBusy(route);
|
|
1195
1209
|
setMessage(void 0);
|
|
1210
|
+
let timer;
|
|
1196
1211
|
try {
|
|
1197
|
-
await operation()
|
|
1212
|
+
await Promise.race([operation(), new Promise((_resolve, reject) => {
|
|
1213
|
+
timer = setTimeout(() => reject(new Error(props.t("saveTimeout"))), timeoutMs);
|
|
1214
|
+
})]);
|
|
1198
1215
|
setMessage({
|
|
1199
1216
|
route,
|
|
1200
1217
|
text: props.t("saved")
|
|
1201
1218
|
});
|
|
1219
|
+
return true;
|
|
1202
1220
|
} catch (error) {
|
|
1203
1221
|
setMessage({
|
|
1204
1222
|
route,
|
|
1205
1223
|
text: error instanceof Error ? error.message : props.t("requestFailed")
|
|
1206
1224
|
});
|
|
1225
|
+
return false;
|
|
1207
1226
|
} finally {
|
|
1227
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1208
1228
|
setBusy(void 0);
|
|
1209
1229
|
}
|
|
1210
1230
|
};
|
|
1211
1231
|
const saveSubagent = () => {
|
|
1212
1232
|
if (subagentDraft === void 0 || subagentDraft.mode !== "fixed") return;
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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
|
+
]));
|
|
1221
1252
|
};
|
|
1222
1253
|
const persistSubagentEnabled = (enabled) => {
|
|
1223
1254
|
if (subagentDraft === void 0 || busy === "subagent" || !subagent.writable) return;
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
setMessage({
|
|
1237
|
-
route: "subagent",
|
|
1238
|
-
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
|
|
1239
1267
|
});
|
|
1268
|
+
setOpen((current) => enabled ? "subagent" : current === "subagent" ? void 0 : current);
|
|
1240
1269
|
});
|
|
1241
1270
|
};
|
|
1242
|
-
|
|
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) => {
|
|
1243
1280
|
if (next === void 0) return;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
+
}]));
|
|
1248
1291
|
};
|
|
1249
1292
|
const saveAutoReview = () => {
|
|
1250
|
-
if (autoReviewDraft === void 0) return;
|
|
1251
|
-
run("autoReview",
|
|
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
|
-
});
|
|
1293
|
+
if (autoReviewDraft === void 0 || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "") return;
|
|
1294
|
+
run("autoReview", () => props.saveCapability("autoReview", pairOps(autoReviewDraft)));
|
|
1255
1295
|
};
|
|
1256
1296
|
const saveCompaction = () => {
|
|
1257
|
-
if (compactionDraft === void 0) return;
|
|
1258
|
-
run("compaction",
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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)));
|
|
1262
1303
|
};
|
|
1263
1304
|
/**
|
|
1264
1305
|
* The reviewer switch: Off unsets both stored fields (follow the session
|
|
@@ -1270,32 +1311,24 @@ window.__ModuleLoader__.load({
|
|
|
1270
1311
|
if (busy === "autoReview" || !autoReview.writable || autoReview.value === void 0) return;
|
|
1271
1312
|
if (!enabled) {
|
|
1272
1313
|
setOpen((current) => current === "autoReview" ? void 0 : current);
|
|
1273
|
-
run("autoReview",
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
}
|
|
1314
|
+
run("autoReview", () => props.saveCapability("autoReview", [{
|
|
1315
|
+
field: "provider",
|
|
1316
|
+
value: void 0
|
|
1317
|
+
}, {
|
|
1318
|
+
field: "model",
|
|
1319
|
+
value: void 0
|
|
1320
|
+
}]));
|
|
1277
1321
|
return;
|
|
1278
1322
|
}
|
|
1279
1323
|
const seed = firstCatalogRoute(groups);
|
|
1280
1324
|
if (seed === void 0) return;
|
|
1281
|
-
|
|
1282
|
-
|
|
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(() => {
|
|
1325
|
+
run("autoReview", () => props.saveCapability("autoReview", pairOps(seed))).then((accepted) => {
|
|
1326
|
+
if (!accepted) return;
|
|
1287
1327
|
setAutoReviewDraft({
|
|
1288
1328
|
provider: seed.provider,
|
|
1289
1329
|
model: seed.model
|
|
1290
1330
|
});
|
|
1291
|
-
setBusy(void 0);
|
|
1292
1331
|
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
1332
|
});
|
|
1300
1333
|
};
|
|
1301
1334
|
/**
|
|
@@ -1308,32 +1341,54 @@ window.__ModuleLoader__.load({
|
|
|
1308
1341
|
if (busy === "compaction" || !compaction.writable || compaction.value === void 0) return;
|
|
1309
1342
|
if (!enabled) {
|
|
1310
1343
|
setOpen((current) => current === "compaction" ? void 0 : current);
|
|
1311
|
-
run("compaction",
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1344
|
+
run("compaction", () => props.saveCapability("compaction", [{
|
|
1345
|
+
field: "provider",
|
|
1346
|
+
value: void 0
|
|
1347
|
+
}, {
|
|
1348
|
+
field: "model",
|
|
1349
|
+
value: void 0
|
|
1350
|
+
}]));
|
|
1315
1351
|
return;
|
|
1316
1352
|
}
|
|
1317
1353
|
const seed = firstCatalogRoute(groups);
|
|
1318
1354
|
if (seed === void 0) return;
|
|
1319
|
-
|
|
1320
|
-
|
|
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(() => {
|
|
1355
|
+
run("compaction", () => props.saveCapability("compaction", pairOps(seed))).then((accepted) => {
|
|
1356
|
+
if (!accepted) return;
|
|
1325
1357
|
setCompactionDraft({
|
|
1326
1358
|
provider: seed.provider,
|
|
1327
1359
|
model: seed.model
|
|
1328
1360
|
});
|
|
1329
|
-
setBusy(void 0);
|
|
1330
1361
|
setOpen("compaction");
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
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
|
|
1336
1390
|
});
|
|
1391
|
+
setOpen("sessionTitle");
|
|
1337
1392
|
});
|
|
1338
1393
|
};
|
|
1339
1394
|
const subagentRole = subagentDraft?.mode === "fixed" && subagentDraft.provider !== void 0 && subagentDraft.provider !== "" ? props.providerRoleOf?.(subagentDraft.provider) : void 0;
|
|
@@ -1343,17 +1398,16 @@ window.__ModuleLoader__.load({
|
|
|
1343
1398
|
const capabilityDisabled = (route, snapshot, next) => snapshot.status !== "ready" || !snapshot.writable || next === void 0 || busy === route || (next.provider ?? "").trim() === "" || (next.model ?? "").trim() === "";
|
|
1344
1399
|
const autoReviewDisabled = autoReview.status !== "ready" || !autoReview.writable || autoReviewDraft === void 0 || busy === "autoReview" || !autoReviewOn || (autoReviewDraft.provider ?? "").trim() === "" || (autoReviewDraft.model ?? "").trim() === "";
|
|
1345
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() === "";
|
|
1346
1402
|
const configuredGroupCount = Object.keys(providerGroups).length;
|
|
1347
1403
|
const groupControlsDisabled = busy === "providerGroups" || switchSettings.status !== "ready" || !switchSettings.writable;
|
|
1348
1404
|
const groupProviderChoices = deriveRouteChoices(groups, groupProvider === "" ? void 0 : { provider: groupProvider }).providers.map((choice) => groupOf(providerGroups, choice.id) === void 0 ? choice : {
|
|
1349
1405
|
...choice,
|
|
1350
1406
|
name: choice.name + " ✓"
|
|
1351
1407
|
});
|
|
1352
|
-
const
|
|
1353
|
-
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);
|
|
1354
1409
|
const groupSubagent = groupDraft === void 0 ? void 0 : draftPair(groupDraft.subagentProvider, groupDraft.subagentModel);
|
|
1355
|
-
const
|
|
1356
|
-
const groupReviewChoices = deriveRouteChoices(groups, groupReview);
|
|
1410
|
+
const groupTitleChoices = deriveRouteChoices(groups, groupTitle);
|
|
1357
1411
|
const groupSubagentChoices = deriveRouteChoices(groups, groupSubagent);
|
|
1358
1412
|
const groupSubMatched = matchCatalogModel(groups.find((group) => group.id === (groupDraft?.subagentProvider ?? ""))?.models ?? [], groupDraft?.subagentModel ?? "");
|
|
1359
1413
|
const groupSubEfforts = (() => {
|
|
@@ -1372,30 +1426,25 @@ window.__ModuleLoader__.load({
|
|
|
1372
1426
|
* Enable a capability row by pinning it: to that capability's live route
|
|
1373
1427
|
* when one is configured, else to the catalog head (same sources the
|
|
1374
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.
|
|
1375
1434
|
*/
|
|
1376
1435
|
const enableGroupCapability = (kind) => {
|
|
1377
1436
|
const active = seedGroupFromActive();
|
|
1378
1437
|
const seed = firstCatalogRoute(groups);
|
|
1379
1438
|
setGroupDraft((draft) => {
|
|
1380
1439
|
if (draft === void 0) return draft;
|
|
1381
|
-
if (kind === "
|
|
1382
|
-
const provider = active.
|
|
1383
|
-
const model = active.
|
|
1440
|
+
if (kind === "sessionTitle") {
|
|
1441
|
+
const provider = active.titleProvider ?? seed?.provider;
|
|
1442
|
+
const model = active.titleModel ?? seed?.model;
|
|
1384
1443
|
if (provider === void 0 || model === void 0) return draft;
|
|
1385
1444
|
return {
|
|
1386
1445
|
...draft,
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
if (kind === "autoReview") {
|
|
1392
|
-
const provider = active.autoReviewProvider ?? seed?.provider;
|
|
1393
|
-
const model = active.autoReviewModel ?? seed?.model;
|
|
1394
|
-
if (provider === void 0 || model === void 0) return draft;
|
|
1395
|
-
return {
|
|
1396
|
-
...draft,
|
|
1397
|
-
autoReviewProvider: provider,
|
|
1398
|
-
autoReviewModel: model
|
|
1446
|
+
titleProvider: provider,
|
|
1447
|
+
titleModel: model
|
|
1399
1448
|
};
|
|
1400
1449
|
}
|
|
1401
1450
|
const provider = active.subagentProvider ?? seed?.provider;
|
|
@@ -1413,15 +1462,10 @@ window.__ModuleLoader__.load({
|
|
|
1413
1462
|
const disableGroupCapability = (kind) => {
|
|
1414
1463
|
setGroupDraft((draft) => {
|
|
1415
1464
|
if (draft === void 0) return draft;
|
|
1416
|
-
if (kind === "
|
|
1417
|
-
...draft,
|
|
1418
|
-
compactProvider: "",
|
|
1419
|
-
compactModel: ""
|
|
1420
|
-
};
|
|
1421
|
-
if (kind === "autoReview") return {
|
|
1465
|
+
if (kind === "sessionTitle") return {
|
|
1422
1466
|
...draft,
|
|
1423
|
-
|
|
1424
|
-
|
|
1467
|
+
titleProvider: "",
|
|
1468
|
+
titleModel: ""
|
|
1425
1469
|
};
|
|
1426
1470
|
return {
|
|
1427
1471
|
...draft,
|
|
@@ -1674,7 +1718,7 @@ window.__ModuleLoader__.load({
|
|
|
1674
1718
|
setMessage(void 0);
|
|
1675
1719
|
},
|
|
1676
1720
|
onSave: () => {
|
|
1677
|
-
saveCapability("search",
|
|
1721
|
+
saveCapability("search", searchDraft);
|
|
1678
1722
|
}
|
|
1679
1723
|
})
|
|
1680
1724
|
] })
|
|
@@ -1741,7 +1785,7 @@ window.__ModuleLoader__.load({
|
|
|
1741
1785
|
setMessage(void 0);
|
|
1742
1786
|
},
|
|
1743
1787
|
onSave: () => {
|
|
1744
|
-
saveCapability("image",
|
|
1788
|
+
saveCapability("image", imageDraft);
|
|
1745
1789
|
}
|
|
1746
1790
|
})
|
|
1747
1791
|
] })
|
|
@@ -1882,6 +1926,70 @@ window.__ModuleLoader__.load({
|
|
|
1882
1926
|
onSave: saveCompaction
|
|
1883
1927
|
})
|
|
1884
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
|
+
] })
|
|
1885
1993
|
})
|
|
1886
1994
|
]
|
|
1887
1995
|
}),
|
|
@@ -1925,94 +2033,45 @@ window.__ModuleLoader__.load({
|
|
|
1925
2033
|
className: ModelSwitchSettings_module_css_default.rowCopy,
|
|
1926
2034
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1927
2035
|
className: ModelSwitchSettings_module_css_default.rowTitle,
|
|
1928
|
-
children: props.t("
|
|
2036
|
+
children: props.t("sessionTitle")
|
|
1929
2037
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1930
2038
|
className: ModelSwitchSettings_module_css_default.hint,
|
|
1931
|
-
children:
|
|
2039
|
+
children: groupTitle === void 0 ? props.t("titleFollow") : routeName(groups, groupTitle)
|
|
1932
2040
|
})]
|
|
1933
2041
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
|
|
1934
|
-
checked:
|
|
2042
|
+
checked: groupTitle !== void 0,
|
|
1935
2043
|
disabled: groupControlsDisabled,
|
|
1936
|
-
label: props.t("
|
|
2044
|
+
label: props.t("sessionTitle"),
|
|
1937
2045
|
onChange: (value) => {
|
|
1938
|
-
if (value) enableGroupCapability("
|
|
1939
|
-
else disableGroupCapability("
|
|
2046
|
+
if (value) enableGroupCapability("sessionTitle");
|
|
2047
|
+
else disableGroupCapability("sessionTitle");
|
|
1940
2048
|
}
|
|
1941
2049
|
})]
|
|
1942
2050
|
}),
|
|
1943
|
-
|
|
2051
|
+
groupTitle === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1944
2052
|
className: ModelSwitchSettings_module_css_default.formGrid,
|
|
1945
2053
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1946
2054
|
label: props.t("provider"),
|
|
1947
|
-
value:
|
|
2055
|
+
value: groupTitle.provider,
|
|
1948
2056
|
disabled: groupControlsDisabled,
|
|
1949
|
-
choices:
|
|
2057
|
+
choices: groupTitleChoices.providers,
|
|
1950
2058
|
onChange: (provider) => {
|
|
1951
2059
|
const first = groups.find((item) => item.id === provider)?.models[0];
|
|
1952
2060
|
setGroupDraft((draft) => draft === void 0 ? draft : {
|
|
1953
2061
|
...draft,
|
|
1954
|
-
|
|
1955
|
-
|
|
2062
|
+
titleProvider: provider,
|
|
2063
|
+
titleModel: first?.id ?? ""
|
|
1956
2064
|
});
|
|
1957
2065
|
}
|
|
1958
2066
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1959
2067
|
label: props.t("model"),
|
|
1960
|
-
value:
|
|
2068
|
+
value: groupTitle.model,
|
|
1961
2069
|
disabled: groupControlsDisabled,
|
|
1962
|
-
choices:
|
|
2070
|
+
choices: groupTitleChoices.models,
|
|
1963
2071
|
onChange: (model) => {
|
|
1964
2072
|
setGroupDraft((draft) => draft === void 0 ? draft : {
|
|
1965
2073
|
...draft,
|
|
1966
|
-
|
|
1967
|
-
});
|
|
1968
|
-
}
|
|
1969
|
-
})]
|
|
1970
|
-
}),
|
|
1971
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1972
|
-
className: ModelSwitchSettings_module_css_default.rowCard,
|
|
1973
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1974
|
-
className: ModelSwitchSettings_module_css_default.rowCopy,
|
|
1975
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1976
|
-
className: ModelSwitchSettings_module_css_default.rowTitle,
|
|
1977
|
-
children: props.t("autoReview")
|
|
1978
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1979
|
-
className: ModelSwitchSettings_module_css_default.hint,
|
|
1980
|
-
children: groupReview === void 0 ? props.t("autoReviewFollow") : routeName(groups, groupReview)
|
|
1981
|
-
})]
|
|
1982
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstantSwitch, {
|
|
1983
|
-
checked: groupReview !== void 0,
|
|
1984
|
-
disabled: groupControlsDisabled,
|
|
1985
|
-
label: props.t("autoReview"),
|
|
1986
|
-
onChange: (value) => {
|
|
1987
|
-
if (value) enableGroupCapability("autoReview");
|
|
1988
|
-
else disableGroupCapability("autoReview");
|
|
1989
|
-
}
|
|
1990
|
-
})]
|
|
1991
|
-
}),
|
|
1992
|
-
groupReview === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1993
|
-
className: ModelSwitchSettings_module_css_default.formGrid,
|
|
1994
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
1995
|
-
label: props.t("provider"),
|
|
1996
|
-
value: groupReview.provider,
|
|
1997
|
-
disabled: groupControlsDisabled,
|
|
1998
|
-
choices: groupReviewChoices.providers,
|
|
1999
|
-
onChange: (provider) => {
|
|
2000
|
-
const first = groups.find((item) => item.id === provider)?.models[0];
|
|
2001
|
-
setGroupDraft((draft) => draft === void 0 ? draft : {
|
|
2002
|
-
...draft,
|
|
2003
|
-
autoReviewProvider: provider,
|
|
2004
|
-
autoReviewModel: first?.id ?? ""
|
|
2005
|
-
});
|
|
2006
|
-
}
|
|
2007
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
2008
|
-
label: props.t("model"),
|
|
2009
|
-
value: groupReview.model,
|
|
2010
|
-
disabled: groupControlsDisabled,
|
|
2011
|
-
choices: groupReviewChoices.models,
|
|
2012
|
-
onChange: (model) => {
|
|
2013
|
-
setGroupDraft((draft) => draft === void 0 ? draft : {
|
|
2014
|
-
...draft,
|
|
2015
|
-
autoReviewModel: model
|
|
2074
|
+
titleModel: model
|
|
2016
2075
|
});
|
|
2017
2076
|
}
|
|
2018
2077
|
})]
|
|
@@ -2154,13 +2213,18 @@ window.__ModuleLoader__.load({
|
|
|
2154
2213
|
compactionFollow: "跟随会话模型",
|
|
2155
2214
|
compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。",
|
|
2156
2215
|
compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。",
|
|
2216
|
+
sessionTitle: "会话命名模型",
|
|
2217
|
+
titleFollow: "跟随会话模型",
|
|
2218
|
+
titleHelp: "为官方自动命名(新会话首条消息后生成标题)指定模型;关闭(两个字段都留空)时命名跟随当前会话模型。",
|
|
2219
|
+
titleRouteHelp: "命名请求固定使用这里的 Provider/模型(不带推理强度);关闭时跟随会话模型。保存后立即对下一次命名生效,不会中断正在进行的命名,会话日志记录的也是实际使用的模型。",
|
|
2157
2220
|
providerGroups: "供应商配置组",
|
|
2158
2221
|
providerGroupsCard: "按供应商预设路由",
|
|
2159
|
-
providerGroupsSummary: "
|
|
2160
|
-
providerGroupsHelp: "
|
|
2161
|
-
providerGroupsBodyHelp: "
|
|
2222
|
+
providerGroupsSummary: "使用该供应商的会话自动采用对应组",
|
|
2223
|
+
providerGroupsHelp: "为会话命名与子代理按供应商分别保存路由。",
|
|
2224
|
+
providerGroupsBodyHelp: "每个供应商保存一组会话命名与子代理路由。会话自身的供应商与该组一致时,该会话的命名与子代理按组解析,否则回落到上方卡片里的全局设置。切换某个会话的模型不会写入任何全局设置,因此不同供应商的多个会话互不影响。压缩与自动审查仍是全局卡片:压缩事件要如实记录实际写入摘要的模型,审查的拦截点没有会话身份。",
|
|
2162
2225
|
providerGroupPick: "选择供应商以编辑其配置组。",
|
|
2163
2226
|
providerGroupDelete: "删除该组",
|
|
2227
|
+
saveTimeout: "保存超时,请重试",
|
|
2164
2228
|
"reason.central-subagent-routing": "Alpha.4 没有全局子代理启动路由接口。",
|
|
2165
2229
|
"reason.packaged-preset-roots": "Alpha.4 不支持插件提供额外 preset root。",
|
|
2166
2230
|
"reason.tool-owner-suppression": "Alpha.4 没有工具所有者或来源抑制接口。",
|
|
@@ -2209,13 +2273,18 @@ window.__ModuleLoader__.load({
|
|
|
2209
2273
|
compactionFollow: "Follows the conversation model",
|
|
2210
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.",
|
|
2211
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.",
|
|
2212
2280
|
providerGroups: "Provider groups",
|
|
2213
2281
|
providerGroupsCard: "Per-provider route presets",
|
|
2214
|
-
providerGroupsSummary: "
|
|
2215
|
-
providerGroupsHelp: "Save
|
|
2216
|
-
providerGroupsBodyHelp: "Each provider stores one
|
|
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.",
|
|
2217
2285
|
providerGroupPick: "Pick a provider to edit its group.",
|
|
2218
2286
|
providerGroupDelete: "Delete group",
|
|
2287
|
+
saveTimeout: "The save timed out; try again",
|
|
2219
2288
|
"reason.central-subagent-routing": "Alpha.4 exposes no global Subagent start-routing seam.",
|
|
2220
2289
|
"reason.packaged-preset-roots": "Alpha.4 exposes no plugin-owned preset root.",
|
|
2221
2290
|
"reason.tool-owner-suppression": "Alpha.4 exposes no tool owner or provenance suppression seam.",
|
|
@@ -3704,66 +3773,6 @@ window.__ModuleLoader__.load({
|
|
|
3704
3773
|
}, operations);
|
|
3705
3774
|
}
|
|
3706
3775
|
//#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
3776
|
//#region src/picker/pending-dismissal.ts
|
|
3768
3777
|
/** Raised when the running Host exposes neither withdrawal verb. */
|
|
3769
3778
|
var PlanDismissalUnsupportedError = class extends Error {
|
|
@@ -4557,23 +4566,6 @@ window.__ModuleLoader__.load({
|
|
|
4557
4566
|
], (scope) => {
|
|
4558
4567
|
const models = scope.modelDirectories;
|
|
4559
4568
|
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
4569
|
let directoryService;
|
|
4578
4570
|
const orderStore = providerOrderStore(scope.configForms.get(PROVIDERS_CONFIG_ID));
|
|
4579
4571
|
scope.inject(["providerDirectory"], (directoryScope) => {
|
|
@@ -4604,7 +4596,6 @@ window.__ModuleLoader__.load({
|
|
|
4604
4596
|
};
|
|
4605
4597
|
const roleOf = (key) => readProviderRole(directoryService, key);
|
|
4606
4598
|
const providerLockStore = createProviderLockStore(readLock);
|
|
4607
|
-
if (available) watchGroupsFor(sessionId, directory.store);
|
|
4608
4599
|
return {
|
|
4609
4600
|
available,
|
|
4610
4601
|
roleOf,
|
|
@@ -4743,6 +4734,7 @@ window.__ModuleLoader__.load({
|
|
|
4743
4734
|
const image = deriveConfigForm(owned, deriveImageSettings, IMAGE_SETTINGS_FIELDS);
|
|
4744
4735
|
const autoReview = deriveConfigForm(owned, deriveAutoReviewSettings, AUTO_REVIEW_SETTINGS_FIELDS);
|
|
4745
4736
|
const compaction = deriveConfigForm(owned, deriveCompactionSettings, COMPACTION_SETTINGS_FIELDS);
|
|
4737
|
+
const sessionTitle = deriveConfigForm(owned, deriveTitleSettings, TITLE_SETTINGS_FIELDS);
|
|
4746
4738
|
const connectionRpc = (() => {
|
|
4747
4739
|
try {
|
|
4748
4740
|
const rpc = ctx.get("connection", false)?.rpc;
|
|
@@ -4780,13 +4772,13 @@ window.__ModuleLoader__.load({
|
|
|
4780
4772
|
imageSettings: image,
|
|
4781
4773
|
autoReviewSettings: autoReview,
|
|
4782
4774
|
compactionSettings: compaction,
|
|
4775
|
+
titleSettings: sessionTitle,
|
|
4783
4776
|
switchSettings: owned
|
|
4784
4777
|
},
|
|
4785
4778
|
capabilities: RUNTIME_CAPABILITIES,
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
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);
|
|
4790
4782
|
},
|
|
4791
4783
|
setCompactOnSwitch: async (value) => {
|
|
4792
4784
|
if (!await owned.set("compactOnSwitch", value)) throw new Error("settings-rejected");
|