@ai-matrx/agents 0.59.233 → 0.59.235

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.
@@ -23,9 +23,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
23
23
  var react_exports = {};
24
24
  __export(react_exports, {
25
25
  ModelSettingsPanel: () => ModelSettingsPanel,
26
- SettingRowFrame: () => SettingRowFrame,
27
- SettingRowLabel: () => RowLabel,
28
- formatSettingValue: () => formatSettingValue,
29
26
  useEffectiveSettings: () => useEffectiveSettings,
30
27
  useSettingsEntry: () => useSettingsEntry,
31
28
  useSettingsSelector: () => useSettingsSelector
@@ -210,7 +207,7 @@ function readEffectiveSettings(entry) {
210
207
 
211
208
  // settings/react/ModelSettingsPanel.tsx
212
209
  var import_design_system4 = require("@ai-matrx/design-system");
213
- var import_controls3 = require("@ai-matrx/design-system/controls");
210
+ var import_controls4 = require("@ai-matrx/design-system/controls");
214
211
  var import_react2 = require("@ai-matrx/agents/models/react");
215
212
 
216
213
  // settings/settings-catalogue.ts
@@ -326,6 +323,9 @@ var CATCHALL_EXCLUDED = /* @__PURE__ */ new Set([
326
323
  // model now declares `tools` for the tool-support capability).
327
324
  ...UI_GATE_KEYS
328
325
  ]);
326
+ function settingControl(controls, key) {
327
+ return lookupControl(controls, key);
328
+ }
329
329
  function lookupControl(controls, key) {
330
330
  if (!controls) return null;
331
331
  if (CATCHALL_EXCLUDED.has(key)) {
@@ -868,29 +868,142 @@ function NumberInput({
868
868
  );
869
869
  }
870
870
 
871
- // settings/react/ModelSettingsPanel.tsx
871
+ // settings/react/VoiceSettingControl.tsx
872
+ var import_controls3 = require("@ai-matrx/design-system/controls");
873
+ var import_icons2 = require("@ai-matrx/design-system/icons");
872
874
  var import_jsx_runtime4 = require("react/jsx-runtime");
875
+ function speakersOf(value, voices) {
876
+ if (!Array.isArray(value)) return [];
877
+ return value.map((entry, index) => {
878
+ const record = entry !== null && typeof entry === "object" ? entry : {};
879
+ return {
880
+ name: typeof record.name === "string" ? record.name : `Speaker ${index + 1}`,
881
+ voice: typeof record.voice === "string" ? record.voice : voices[0] ?? ""
882
+ };
883
+ });
884
+ }
885
+ function VoiceSettingControl({ voices, value, multiSpeaker, onChange, idPrefix, disabled = false }) {
886
+ const isMulti = Array.isArray(value);
887
+ const speakers = speakersOf(value, voices);
888
+ const single = typeof value === "string" && value ? value : voices[0] ?? "";
889
+ const max = multiSpeaker?.max !== void 0 && multiSpeaker.max > 0 ? multiSpeaker.max : void 0;
890
+ const canAdd = max === void 0 || speakers.length < max;
891
+ const options = voices.map((voice) => ({ value: voice, label: voice }));
892
+ const voiceSelect = (current, label, pick) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
893
+ import_controls3.Select,
894
+ {
895
+ value: current,
896
+ options: options.some((o) => o.value === current) ? options : [{ value: current, label: current }, ...options],
897
+ onValueChange: pick,
898
+ "aria-label": label,
899
+ disabled,
900
+ className: "min-w-0"
901
+ }
902
+ );
903
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", "data-voice-control": "", children: [
904
+ multiSpeaker || isMulti ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
905
+ import_controls3.SegmentedControl,
906
+ {
907
+ "aria-label": "Voices",
908
+ value: isMulti ? "multi" : "single",
909
+ onValueChange: (next) => {
910
+ if (next === "multi" && !isMulti) {
911
+ onChange(
912
+ [
913
+ { name: "Speaker 1", voice: single },
914
+ { name: "Speaker 2", voice: voices[1] ?? single }
915
+ ],
916
+ true
917
+ );
918
+ } else if (next === "single" && isMulti) {
919
+ onChange(speakers[0]?.voice || voices[0] || "", false);
920
+ }
921
+ },
922
+ data: [
923
+ { value: "single", label: "One voice", disabled },
924
+ { value: "multi", label: "Speakers", disabled: disabled || !multiSpeaker }
925
+ ]
926
+ }
927
+ ) : null,
928
+ !isMulti ? voiceSelect(single, "Voice", (next) => onChange(next, false)) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
929
+ speakers.map((speaker, index) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-w-0 items-center gap-1", "data-speaker": index, children: [
930
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
931
+ import_controls3.Field,
932
+ {
933
+ id: `${idPrefix}-speaker-${index}`,
934
+ "aria-label": `Speaker ${index + 1} name`,
935
+ value: speaker.name,
936
+ onChange: (event) => onChange(
937
+ speakers.map((s, i) => i === index ? { ...s, name: event.target.value } : s),
938
+ true
939
+ ),
940
+ disabled,
941
+ className: "min-w-0 flex-1"
942
+ }
943
+ ),
944
+ voiceSelect(
945
+ speaker.voice,
946
+ `Speaker ${index + 1} voice`,
947
+ (next) => onChange(
948
+ speakers.map((s, i) => i === index ? { ...s, voice: next } : s),
949
+ true
950
+ )
951
+ ),
952
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
953
+ import_controls3.Button,
954
+ {
955
+ variant: "quiet",
956
+ removes: true,
957
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_icons2.XIcon, {}),
958
+ onClick: () => onChange(speakers.filter((_, i) => i !== index), true),
959
+ disabled: disabled || speakers.length <= 2,
960
+ "aria-label": `Remove speaker ${index + 1}`,
961
+ title: "Remove speaker"
962
+ }
963
+ )
964
+ ] }, index)),
965
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
966
+ import_controls3.Button,
967
+ {
968
+ variant: "quiet",
969
+ icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_icons2.PlusIcon, {}),
970
+ onClick: () => onChange([...speakers, { name: `Speaker ${speakers.length + 1}`, voice: voices[0] ?? "" }], true),
971
+ disabled: disabled || !canAdd,
972
+ title: canAdd ? "Add a speaker" : `This model takes up to ${max} speakers`,
973
+ children: "Add speaker"
974
+ }
975
+ ) })
976
+ ] })
977
+ ] });
978
+ }
979
+
980
+ // settings/react/ModelSettingsPanel.tsx
981
+ var import_jsx_runtime5 = require("react/jsx-runtime");
873
982
  function StatusGlyph({ issue, source }) {
874
983
  const title = issue ? issue.message : source ? `Valid \xB7 ${source}` : "Valid";
875
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
984
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
876
985
  "span",
877
986
  {
878
987
  className: (0, import_cn.cn)("inline-flex h-7 w-6 shrink-0 items-center justify-center", issue ? "text-warning" : "text-success"),
879
988
  role: "img",
880
989
  "aria-label": title,
881
990
  title,
882
- children: issue ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TriangleAlertGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CircleCheckGlyph, { className: "h-3.5 w-3.5" })
991
+ children: issue ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TriangleAlertGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CircleCheckGlyph, { className: "h-3.5 w-3.5" })
883
992
  }
884
993
  );
885
994
  }
886
- function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
995
+ function changeTogether(target, changes, controls) {
996
+ if (target.changeMany) target.changeMany(changes);
997
+ else for (const [key, value] of Object.entries(changes)) target.change(key, value, controls[key] ?? null);
998
+ }
999
+ function ModelSettingRow({ row, control, target, idPrefix, disabled, multiSpeaker }) {
887
1000
  const read = target.read(row.key, control);
888
1001
  const resetLabel = target.resetLabel ?? "Reset to agent";
889
- const reset = read.changedHere && target.resetToInherited ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
890
- import_controls3.Button,
1002
+ const reset = read.changedHere && target.resetToInherited ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1003
+ import_controls4.Button,
891
1004
  {
892
1005
  variant: "quiet",
893
- icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RotateCcwGlyph, {}),
1006
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RotateCcwGlyph, {}),
894
1007
  onClick: () => target.resetToInherited?.(row.key),
895
1008
  disabled,
896
1009
  "aria-label": `${resetLabel}: ${row.label}`,
@@ -899,20 +1012,20 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
899
1012
  }
900
1013
  ) : null;
901
1014
  if (read.state === "bound") {
902
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1015
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
903
1016
  SettingRowFrame,
904
1017
  {
905
1018
  rowKey: row.key,
906
1019
  state: "bound",
907
- leading: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_design_system4.Checkbox, { checked: true, disabled: true, "aria-label": `${row.label} is a run input` }),
908
- label: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowLabel, { children: row.label }),
909
- actions: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1020
+ leading: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_design_system4.Checkbox, { checked: true, disabled: true, "aria-label": `${row.label} is a run input` }),
1021
+ label: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowLabel, { children: row.label }),
1022
+ actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
910
1023
  reset,
911
- target.unbind ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
912
- import_controls3.Button,
1024
+ target.unbind ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1025
+ import_controls4.Button,
913
1026
  {
914
1027
  variant: "quiet",
915
- icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UnlinkGlyph, {}),
1028
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(UnlinkGlyph, {}),
916
1029
  onClick: () => target.unbind?.(row.key, control),
917
1030
  disabled,
918
1031
  "aria-label": `Make ${row.label} a fixed setting`,
@@ -920,9 +1033,9 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
920
1033
  }
921
1034
  ) : null
922
1035
  ] }),
923
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "flex min-h-7 min-w-0 items-center gap-2", "data-bound-control": row.key, children: [
924
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_controls3.Chip, { tone: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VariableGlyph, {}), label: `{{${read.variableName}}}` }),
925
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate type-meta text-muted-foreground", children: read.value === void 0 || read.value === null || read.value === "" ? "Model default" : formatSettingValue(read.value) })
1036
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "flex min-h-7 min-w-0 items-center gap-2", "data-bound-control": row.key, children: [
1037
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls4.Chip, { tone: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VariableGlyph, {}), label: `{{${read.variableName}}}` }),
1038
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "truncate type-meta text-muted-foreground", children: read.value === void 0 || read.value === null || read.value === "" ? "Model default" : formatSettingValue(read.value) })
926
1039
  ] })
927
1040
  }
928
1041
  );
@@ -932,12 +1045,12 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
932
1045
  const isOff = isSet && isOffValue(value, control);
933
1046
  const checkboxId = `${idPrefix}-setting-${row.key}`;
934
1047
  const canBind = Boolean(target.bind) && (target.canBind?.(row.key) ?? true);
935
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1048
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
936
1049
  SettingRowFrame,
937
1050
  {
938
1051
  rowKey: row.key,
939
1052
  state: isOff ? "off" : read.state,
940
- leading: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1053
+ leading: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
941
1054
  import_design_system4.Checkbox,
942
1055
  {
943
1056
  id: checkboxId,
@@ -946,14 +1059,14 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
946
1059
  onCheckedChange: (next) => next === true ? target.turnOn(row.key, control) : target.clear(row.key)
947
1060
  }
948
1061
  ),
949
- label: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowLabel, { htmlFor: checkboxId, muted: !isSet, children: row.label }),
950
- actions: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
951
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StatusGlyph, { issue: read.issue, source: read.source }),
952
- canBind ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
953
- import_controls3.Button,
1062
+ label: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowLabel, { htmlFor: checkboxId, muted: !isSet, children: row.label }),
1063
+ actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(StatusGlyph, { issue: read.issue, source: read.source }),
1065
+ canBind ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1066
+ import_controls4.Button,
954
1067
  {
955
1068
  variant: "quiet",
956
- icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VariableGlyph, {}),
1069
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VariableGlyph, {}),
957
1070
  onClick: () => target.bind?.(row.key, control),
958
1071
  disabled,
959
1072
  "aria-label": `Make ${row.label} a run input`,
@@ -961,11 +1074,11 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
961
1074
  "data-bind-control": row.key
962
1075
  }
963
1076
  ) : null,
964
- isSet && read.issue?.fixable && target.fix ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
965
- import_controls3.Button,
1077
+ isSet && read.issue?.fixable && target.fix ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1078
+ import_controls4.Button,
966
1079
  {
967
1080
  variant: "quiet",
968
- icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WrenchGlyph, {}),
1081
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(WrenchGlyph, {}),
969
1082
  onClick: () => target.fix?.(row.key),
970
1083
  disabled,
971
1084
  "aria-label": `Fix ${row.label} automatically`,
@@ -973,12 +1086,12 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
973
1086
  }
974
1087
  ) : null,
975
1088
  reset,
976
- isSet ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
977
- import_controls3.Button,
1089
+ isSet ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1090
+ import_controls4.Button,
978
1091
  {
979
1092
  variant: "quiet",
980
1093
  removes: true,
981
- icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Trash2Glyph, {}),
1094
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Trash2Glyph, {}),
982
1095
  onClick: () => target.clear(row.key),
983
1096
  disabled,
984
1097
  "aria-label": `Clear ${row.label} to not set`,
@@ -987,8 +1100,18 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
987
1100
  }
988
1101
  ) : null
989
1102
  ] }),
990
- children: isSet ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
991
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1103
+ children: isSet && row.key === "tts_voice" && control.enum?.length && (multiSpeaker || Array.isArray(value)) ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1104
+ VoiceSettingControl,
1105
+ {
1106
+ voices: control.enum.map(String),
1107
+ value,
1108
+ multiSpeaker: multiSpeaker ? { max: multiSpeaker.max } : null,
1109
+ onChange: (voice, multi) => changeTogether(target, { tts_voice: voice, multi_speaker: multi }, { tts_voice: control, multi_speaker: multiSpeaker }),
1110
+ idPrefix: `${idPrefix}-voice`,
1111
+ disabled
1112
+ }
1113
+ ) : isSet ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1114
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
992
1115
  SettingControlInput,
993
1116
  {
994
1117
  settingKey: row.key,
@@ -999,40 +1122,40 @@ function ModelSettingRow({ row, control, target, idPrefix, disabled }) {
999
1122
  id: `${idPrefix}-control-${row.key}`
1000
1123
  }
1001
1124
  ),
1002
- read.issue ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "pt-1 type-meta text-warning-ink", children: read.issue.message }) : null
1003
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowText, { children: read.modelDefault !== void 0 && read.modelDefault !== null ? `Not set \xB7 Model default: ${formatSettingValue(read.modelDefault)}` : "Not set" })
1125
+ read.issue ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "pt-1 type-meta text-warning-ink", children: read.issue.message }) : null
1126
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowText, { children: read.modelDefault !== void 0 && read.modelDefault !== null ? `Not set \xB7 Model default: ${formatSettingValue(read.modelDefault)}` : "Not set" })
1004
1127
  }
1005
1128
  );
1006
1129
  }
1007
1130
  function ExtraSettingRow({ rowKey, label, value, kind, onClear, onReset, resetLabel = "Reset to agent", disabled = false }) {
1008
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1131
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1009
1132
  SettingRowFrame,
1010
1133
  {
1011
1134
  rowKey,
1012
1135
  state: kind,
1013
- label: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowLabel, { children: label }),
1014
- actions: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1015
- kind === "translated" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_controls3.Chip, { tone: "sky", label: "Translated", title: "Kept as set and sent as this model's closest equivalent" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_controls3.Chip, { tone: "warning", label: "Not on this model", title: "This model has no such setting" }),
1016
- onReset ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_controls3.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RotateCcwGlyph, {}), onClick: onReset, disabled, "aria-label": `${resetLabel}: ${label}`, title: resetLabel, "data-setting-reset": rowKey }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_controls3.Button, { variant: "quiet", removes: true, icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Trash2Glyph, {}), onClick: onClear, disabled, "aria-label": `Clear ${label}`, title: "Clear to not set", "data-setting-clear": rowKey })
1136
+ label: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowLabel, { children: label }),
1137
+ actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1138
+ kind === "translated" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls4.Chip, { tone: "sky", label: "Translated", title: "Kept as set and sent as this model's closest equivalent" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls4.Chip, { tone: "warning", label: "Not on this model", title: "This model has no such setting" }),
1139
+ onReset ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls4.Button, { variant: "quiet", icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RotateCcwGlyph, {}), onClick: onReset, disabled, "aria-label": `${resetLabel}: ${label}`, title: resetLabel, "data-setting-reset": rowKey }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_controls4.Button, { variant: "quiet", removes: true, icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Trash2Glyph, {}), onClick: onClear, disabled, "aria-label": `Clear ${label}`, title: "Clear to not set", "data-setting-clear": rowKey })
1017
1140
  ] }),
1018
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowText, { mono: value !== void 0 && value !== null, children: value === void 0 || value === null ? "Not set" : isOffValue(value, null) ? "Off" : formatSettingValue(value) })
1141
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowText, { mono: value !== void 0 && value !== null, children: value === void 0 || value === null ? "Not set" : isOffValue(value, null) ? "Off" : formatSettingValue(value) })
1019
1142
  }
1020
1143
  );
1021
1144
  }
1022
1145
  function ModelRow({ model, resetLabel, idPrefix, disabled }) {
1023
1146
  const off = disabled || model.disabled === true;
1024
1147
  const oneMessageId = `${idPrefix}-model-one-message`;
1025
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1148
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1026
1149
  SettingRowFrame,
1027
1150
  {
1028
1151
  rowKey: "model",
1029
1152
  state: model.value ? "set" : "unset",
1030
- label: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RowLabel, { children: "Model" }),
1031
- actions: model.changedHere && model.resetToInherited ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1032
- import_controls3.Button,
1153
+ label: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RowLabel, { children: "Model" }),
1154
+ actions: model.changedHere && model.resetToInherited ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1155
+ import_controls4.Button,
1033
1156
  {
1034
1157
  variant: "quiet",
1035
- icon: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RotateCcwGlyph, {}),
1158
+ icon: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RotateCcwGlyph, {}),
1036
1159
  onClick: () => model.resetToInherited?.(),
1037
1160
  disabled: off,
1038
1161
  "aria-label": `${resetLabel}: Model`,
@@ -1040,8 +1163,8 @@ function ModelRow({ model, resetLabel, idPrefix, disabled }) {
1040
1163
  "data-setting-reset": "model"
1041
1164
  }
1042
1165
  ) : null,
1043
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-7 min-w-0 flex-wrap items-center gap-x-2", children: [
1044
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1166
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-h-7 min-w-0 flex-wrap items-center gap-x-2", children: [
1167
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1045
1168
  import_react2.ModelListDropdown,
1046
1169
  {
1047
1170
  "aria-label": "Model",
@@ -1058,9 +1181,9 @@ function ModelRow({ model, resetLabel, idPrefix, disabled }) {
1058
1181
  ...model.disabledTitle ? { disabledTitle: model.disabledTitle } : {}
1059
1182
  }
1060
1183
  ),
1061
- model.oneMessage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { htmlFor: oneMessageId, className: "flex min-h-7 items-center gap-1 type-secondary text-muted-foreground", title: "Use this model for the next message, then go back", children: [
1062
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1063
- import_controls3.Switch,
1184
+ model.oneMessage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { htmlFor: oneMessageId, className: "flex min-h-7 items-center gap-1 type-secondary text-muted-foreground", title: "Use this model for the next message, then go back", children: [
1185
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1186
+ import_controls4.Switch,
1064
1187
  {
1065
1188
  id: oneMessageId,
1066
1189
  checked: model.oneMessage.on,
@@ -1081,7 +1204,6 @@ function ModelSettingsPanel({
1081
1204
  idPrefix,
1082
1205
  model,
1083
1206
  notices,
1084
- renderRow,
1085
1207
  capabilities,
1086
1208
  emptyNote,
1087
1209
  disabled = false
@@ -1089,7 +1211,8 @@ function ModelSettingsPanel({
1089
1211
  const groups = controls ? buildSettingsRows(controls, values).filter((group) => group.rows.length > 0) : [];
1090
1212
  const extras = extraSettingKeys(controls, values, target.changedKeys);
1091
1213
  const resetLabel = target.resetLabel ?? "Reset to agent";
1092
- const extraRow = (key, kind) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1214
+ const multiSpeaker = settingControl(controls, "multi_speaker");
1215
+ const extraRow = (key, kind) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1093
1216
  ExtraSettingRow,
1094
1217
  {
1095
1218
  rowKey: key,
@@ -1103,29 +1226,28 @@ function ModelSettingsPanel({
1103
1226
  },
1104
1227
  key
1105
1228
  );
1106
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { "data-model-settings-panel": "", className: "@container flex min-w-0 flex-col", children: [
1107
- model ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModelRow, { model, resetLabel, idPrefix, disabled }) : null,
1229
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { "data-model-settings-panel": "", className: "@container flex min-w-0 flex-col", children: [
1230
+ model ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ModelRow, { model, resetLabel, idPrefix, disabled }) : null,
1108
1231
  notices,
1109
1232
  extras.unsupported.map((key) => extraRow(key, "unsupported")),
1110
- extras.translated.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SettingsGroupSection, { id: "translated", title: "Translated for this model", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { "data-translated-settings": "", children: extras.translated.map((key) => extraRow(key, "translated")) }) }) : null,
1111
- groups.length === 0 && emptyNote ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "py-2 type-secondary text-muted-foreground", children: emptyNote }) : null,
1112
- groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SettingsGroupSection, { id: group.id, title: group.label || void 0, children: group.rows.map((row) => {
1113
- const custom = renderRow?.(row);
1114
- if (custom !== void 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: custom }, row.key);
1233
+ extras.translated.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SettingsGroupSection, { id: "translated", title: "Translated for this model", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { "data-translated-settings": "", children: extras.translated.map((key) => extraRow(key, "translated")) }) }) : null,
1234
+ groups.length === 0 && emptyNote ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "py-2 type-secondary text-muted-foreground", children: emptyNote }) : null,
1235
+ groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SettingsGroupSection, { id: group.id, title: group.label || void 0, children: group.rows.map((row) => {
1115
1236
  if (!row.control) return null;
1116
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1237
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1117
1238
  ModelSettingRow,
1118
1239
  {
1119
1240
  row,
1120
1241
  control: row.control,
1121
1242
  target,
1122
1243
  idPrefix,
1123
- disabled
1244
+ disabled,
1245
+ multiSpeaker
1124
1246
  },
1125
1247
  row.key
1126
1248
  );
1127
1249
  }) }, group.id)),
1128
- capabilities ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1250
+ capabilities ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1129
1251
  InputCapabilitiesEditor,
1130
1252
  {
1131
1253
  values: capabilities.values,
@@ -167,49 +167,6 @@ interface SettingsStore {
167
167
  cancelModelSwitch(agentId: string): void;
168
168
  }
169
169
 
170
- /**
171
- * Model Settings Catalog — the single source of truth for the STANDARD
172
- * settings list, and the one chokepoint that decides which keys it contains.
173
- *
174
- * ── The model (two distinct surfaces) ────────────────────────────────────────
175
- * 1. STANDARD list — ONLY the keys the selected model explicitly declares in
176
- * its `controls` config (i.e. the parameters this model actually supports).
177
- * `buildSettingsRows()` returns exactly these, grouped + ordered. Catalog
178
- * keys the model does not declare are NOT shown here.
179
- * 2. CAUTION surface — settings the agent has SET that the model does NOT
180
- * support. These are never hidden: they are flagged as "not valid for this
181
- * model" and made repairable by the validation layer (the IssueTable, fed
182
- * by the `unsupported_by_model` rule). They do NOT belong in the standard
183
- * list — that is exactly the "showing all possible keys" bug.
184
- *
185
- * The catalog exists so the standard list's labels, grouping and order live
186
- * in ONE place (previously copy-pasted as `textModelSettings` /
187
- * `booleanSettings` / ... into three components, each filtering by
188
- * `getControl(key)` inline — which drifted and regressed). Components map over
189
- * `buildSettingsRows()`; they must not filter by model themselves (ESLint
190
- * enforces). The original recurring bug — set values silently dropped when a
191
- * model didn't support them — is fixed by the CAUTION surface, not by dumping
192
- * every key into the standard list.
193
- *
194
- * Guardrail: `settings-catalogue.test.ts` asserts the standard list contains
195
- * ONLY supported keys (never the full catalog), and `unsupported-by-model.
196
- * test.ts` asserts set-but-unsupported keys surface in the caution layer.
197
- */
198
-
199
- type SettingsGroupId = "text" | "featureFlags" | "imageVideo" | "audio" | "other";
200
- interface SettingsRow {
201
- key: string;
202
- label: string;
203
- group: SettingsGroupId;
204
- /** The model's control definition for this key. Standard rows are always
205
- * supported, so this is present; typed nullable for consumer convenience. */
206
- control: ControlDefinition | null;
207
- /** Always true for rows returned by buildSettingsRows (standard = supported). */
208
- supported: boolean;
209
- /** True when the current settings hold a non-null value for this key. */
210
- hasValue: boolean;
211
- }
212
-
213
170
  /**
214
171
  * UI Gates — canonical model-gated UI flags.
215
172
  *
@@ -303,6 +260,11 @@ interface SettingsWriteTarget {
303
260
  read(key: string, control: ControlDefinition | null): SettingRowRead;
304
261
  /** The person edited the control. */
305
262
  change(key: string, value: unknown, control: ControlDefinition | null): void;
263
+ /**
264
+ * Several settings that are one choice, written together (the Voice row's `tts_voice` +
265
+ * `multi_speaker`). Without it the panel calls `change` once per key.
266
+ */
267
+ changeMany?(changes: Record<string, unknown>): void;
306
268
  /** The person ticked an unset row (store `startingValueFor`). */
307
269
  turnOn(key: string, control: ControlDefinition): void;
308
270
  /** The person cleared the row back to "not set". */
@@ -383,41 +345,13 @@ interface ModelSettingsPanelProps {
383
345
  model?: ModelRowTarget | undefined;
384
346
  /** Drawn between the model row and the groups: notices, the issue list. */
385
347
  notices?: ReactNode;
386
- /** A row the host draws itself (the multi-speaker voice editor); `undefined` = the standard row. */
387
- renderRow?: (row: SettingsRow) => ReactNode | undefined;
388
348
  /** The input capabilities group, under the settings groups. */
389
349
  capabilities?: CapabilitiesWriteTarget | undefined;
390
350
  /** Said where the rows would be when the model declares none (or no model is chosen). */
391
351
  emptyNote?: ReactNode;
392
352
  disabled?: boolean;
393
353
  }
394
- declare function ModelSettingsPanel({ controls, values, target, idPrefix, model, notices, renderRow, capabilities, emptyNote, disabled, }: ModelSettingsPanelProps): React.JSX.Element;
395
-
396
- /** A catalog value as the person reads it ("On", "auto", "1024"). */
397
- declare function formatSettingValue(value: unknown): string;
398
- interface SettingRowFrameProps {
399
- /** `data-setting-row` — the setting key (or a host row name such as "model"). */
400
- rowKey: string;
401
- /** `data-setting-state` — unset | set | off | bound | translated | … */
402
- state?: string;
403
- /** The 16px leading column: the row's checkbox, or an icon. */
404
- leading?: ReactNode;
405
- label: ReactNode;
406
- /** The control, or the row's value text. */
407
- children?: ReactNode;
408
- /** Fixed-width icons on the right: status, then actions. */
409
- actions?: ReactNode;
410
- }
411
- /**
412
- * The one row geometry every line of the panel uses — setting rows, the model row, the
413
- * translated rows, the voice row and the input capabilities — so their edges line up.
414
- */
415
- declare function SettingRowFrame({ rowKey, state, leading, label, children, actions }: SettingRowFrameProps): React.JSX.Element;
416
- declare function RowLabel({ htmlFor, muted, children }: {
417
- htmlFor?: string;
418
- muted?: boolean;
419
- children: ReactNode;
420
- }): React.JSX.Element;
354
+ declare function ModelSettingsPanel({ controls, values, target, idPrefix, model, notices, capabilities, emptyNote, disabled, }: ModelSettingsPanelProps): React.JSX.Element;
421
355
 
422
356
  /** Subscribe to a slice of a settings store. `select` must return a stable value for unchanged state. */
423
357
  declare function useSettingsSelector<T>(store: SettingsStore, select: (state: AgentSettingsState) => T): T;
@@ -426,4 +360,4 @@ declare function useSettingsEntry(store: SettingsStore, agentId: string | null |
426
360
  /** Defaults merged with overrides for one agent; recomputed only when the entry changes. */
427
361
  declare function useEffectiveSettings(store: SettingsStore, agentId: string | null | undefined): Partial<AgentSettings>;
428
362
 
429
- export { ModelSettingsPanel, type ModelSettingsPanelProps, SettingRowFrame, type SettingRowFrameProps, RowLabel as SettingRowLabel, formatSettingValue, useEffectiveSettings, useSettingsEntry, useSettingsSelector };
363
+ export { ModelSettingsPanel, type ModelSettingsPanelProps, useEffectiveSettings, useSettingsEntry, useSettingsSelector };