@ddtcorex/dsh-maestro-config 0.1.0 → 0.1.1

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
@@ -2299,6 +2299,29 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2299
2299
  document.removeEventListener("keydown", onKey);
2300
2300
  };
2301
2301
  }, [open]);
2302
+ const getModelId = (m) => typeof m === "string" ? m : m.id;
2303
+ const getModelName = (m) => typeof m === "string" ? m : m.name ?? m.id;
2304
+ const selectedModelInfo = (() => {
2305
+ if (!selectedProvider || !value?.model) return null;
2306
+ const raw = (providerGroup?.models ?? []).find((mm) => getModelId(mm) === value.model);
2307
+ if (raw === void 0) return null;
2308
+ if (typeof raw === "string") return { id: raw, supportsReasoning: false, reasoningEfforts: [] };
2309
+ return raw;
2310
+ })();
2311
+ const supportsReasoning = (() => {
2312
+ if (!selectedModelInfo) return false;
2313
+ if (typeof selectedModelInfo.supportsReasoning === "boolean") return selectedModelInfo.supportsReasoning;
2314
+ const efforts = selectedModelInfo.reasoningEfforts ?? selectedModelInfo.reasoning?.efforts?.map((e) => e.id) ?? [];
2315
+ return efforts.filter((e) => e !== "off").length > 0;
2316
+ })();
2317
+ const availableEfforts = (() => {
2318
+ if (!supportsReasoning) return [];
2319
+ const efforts = selectedModelInfo?.reasoningEfforts ?? selectedModelInfo?.reasoning?.efforts?.map((e) => e.id) ?? [];
2320
+ const filtered = efforts.filter((e) => e !== "off" && e !== "");
2321
+ if (filtered.length > 0) return filtered;
2322
+ return ["low", "medium", "high"];
2323
+ })();
2324
+ const warning = selectedEffort !== "" && !supportsReasoning && selectedModelInfo !== null ? `\u26A0\uFE0F This model does not support reasoning effort "${selectedEffort}" \u2014 reviews will fail. Clear effort or choose a reasoning-capable model.` : null;
2302
2325
  const update = (field, newVal) => {
2303
2326
  if (newVal === "" && field === "provider") {
2304
2327
  onChange(null);
@@ -2309,8 +2332,9 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2309
2332
  const next = { provider: value?.provider ?? "", model: value?.model ?? "", ...value?.reasoningEffort ? { reasoningEffort: value.reasoningEffort } : {} };
2310
2333
  if (field === "provider") {
2311
2334
  const g = groups.find((x) => x.provider === newVal);
2335
+ const first = g?.models[0];
2312
2336
  next.provider = newVal;
2313
- next.model = g?.models[0] ?? "";
2337
+ next.model = first !== void 0 ? getModelId(first) : "";
2314
2338
  } else if (field === "model") {
2315
2339
  next.model = newVal;
2316
2340
  } else if (field === "reasoningEffort") {
@@ -2418,7 +2442,8 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2418
2442
  (0, import_react.createElement)("span", { style: { display: "flex", alignItems: "center", gap: 8, color: "var(--dsw-alias-label-secondary)" } }, effortLabel, chevronRight)
2419
2443
  ),
2420
2444
  value && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `Selected: ${value.provider} / ${value.model}${value.reasoningEffort ? ` (${value.reasoningEffort})` : ""}`),
2421
- !value && effectiveFallback && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `${effectiveFallbackLabel === "Use Global" ? "Using Global" : "Using DSH default"}: ${effectiveFallback.provider} / ${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` (${effectiveFallback.reasoningEffort})` : ""}`)
2445
+ !value && effectiveFallback && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, `${effectiveFallbackLabel === "Use Global" ? "Using Global" : "Using DSH default"}: ${effectiveFallback.provider} / ${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` (${effectiveFallback.reasoningEffort})` : ""}`),
2446
+ warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "4px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2422
2447
  ),
2423
2448
  pane === "model" && (0, import_react.createElement)(
2424
2449
  "div",
@@ -2437,15 +2462,20 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2437
2462
  ms.length === 0 ? (0, import_react.createElement)("p", { style: { ...captionStyle, padding: "2px 10px 2px 28px" } }, "No models") : (0, import_react.createElement)(
2438
2463
  "div",
2439
2464
  { style: { marginLeft: 12, borderLeft: "1px solid var(--dsw-alias-border-l2)", paddingLeft: 6, display: "flex", flexDirection: "column", gap: 2 } },
2440
- ms.map((m) => (0, import_react.createElement)("button", { key: m, type: "button", style: { ...rowStyle, paddingLeft: 10, background: value?.provider === p && value?.model === m ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2441
- update("model", m);
2442
- if (value?.provider !== p) update("provider", p);
2443
- else {
2444
- const next = { provider: p, model: m, ...selectedEffort ? { reasoningEffort: selectedEffort } : {} };
2445
- onChange(next);
2446
- setPane("root");
2447
- }
2448
- } }, (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, m), check(value?.provider === p && value?.model === m)))
2465
+ ms.map((m) => {
2466
+ const mid = getModelId(m);
2467
+ const mname = getModelName(m);
2468
+ const active = value?.provider === p && value?.model === mid;
2469
+ return (0, import_react.createElement)("button", { key: mid, type: "button", style: { ...rowStyle, paddingLeft: 10, background: active ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2470
+ update("model", mid);
2471
+ if (value?.provider !== p) update("provider", p);
2472
+ else {
2473
+ const next = { provider: p, model: mid, ...selectedEffort ? { reasoningEffort: selectedEffort } : {} };
2474
+ onChange(next);
2475
+ setPane("root");
2476
+ }
2477
+ } }, (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, mname), check(active));
2478
+ })
2449
2479
  )
2450
2480
  );
2451
2481
  })
@@ -2455,18 +2485,31 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
2455
2485
  "div",
2456
2486
  null,
2457
2487
  (0, import_react.createElement)("button", { type: "button", style: { ...rowStyle, color: "var(--dsw-alias-label-secondary)" }, onClick: () => setPane("root") }, (0, import_react.createElement)("span", null, "\u2190 Back"), (0, import_react.createElement)("span", { style: { fontSize: 12 } }, "Effort")),
2458
- (0, import_react.createElement)(
2488
+ selectedModelInfo === null ? (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px" } }, "Select a model first to configure effort.") : !supportsReasoning ? (0, import_react.createElement)(
2489
+ "div",
2490
+ null,
2491
+ (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 6px" } }, "This model does not support reasoning effort \u2014 using provider default"),
2492
+ (0, import_react.createElement)(
2493
+ "div",
2494
+ { style: { marginTop: 4 } },
2495
+ [{ id: "", label: "Default effort" }].map((e) => (0, import_react.createElement)("button", { key: e.id || "default", type: "button", style: { ...rowStyle, background: selectedEffort === e.id ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2496
+ update("reasoningEffort", e.id);
2497
+ setPane("root");
2498
+ } }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
2499
+ ),
2500
+ warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2501
+ ) : (0, import_react.createElement)(
2459
2502
  "div",
2460
- { style: { marginTop: 4 } },
2461
- [
2462
- { id: "", label: "Default effort" },
2463
- { id: "low", label: "low" },
2464
- { id: "medium", label: "medium" },
2465
- { id: "high", label: "high" }
2466
- ].map((e) => (0, import_react.createElement)("button", { key: e.id || "default", type: "button", style: { ...rowStyle, background: selectedEffort === e.id ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2467
- update("reasoningEffort", e.id);
2468
- setPane("root");
2469
- } }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
2503
+ null,
2504
+ (0, import_react.createElement)(
2505
+ "div",
2506
+ { style: { marginTop: 4 } },
2507
+ [{ id: "", label: "Default effort" }, ...availableEfforts.map((id) => ({ id, label: id }))].map((e) => (0, import_react.createElement)("button", { key: e.id || "default", type: "button", style: { ...rowStyle, background: selectedEffort === e.id ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
2508
+ update("reasoningEffort", e.id);
2509
+ setPane("root");
2510
+ } }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
2511
+ ),
2512
+ warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
2470
2513
  )
2471
2514
  )
2472
2515
  )
@@ -2674,7 +2717,7 @@ function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePi
2674
2717
  (0, import_react.createElement)("p", { style: captionStyle }, "Stays the same across tunnel and DSH restarts; use Rotate when you need a new PIN.")
2675
2718
  );
2676
2719
  }
2677
- function MaestroSettingsTab({ rpcCall }) {
2720
+ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
2678
2721
  const [status, setStatus] = (0, import_react.useState)(null);
2679
2722
  const [proxyStatus, setProxyStatus] = (0, import_react.useState)(null);
2680
2723
  const [config, setConfig] = (0, import_react.useState)({ tunnelMode: "quick", projectMappings: [] });
@@ -2704,6 +2747,14 @@ function MaestroSettingsTab({ rpcCall }) {
2704
2747
  (0, import_react.useEffect)(() => {
2705
2748
  call(MAESTRO_ENDPOINTS.getConfig, {}).then((saved) => setConfig((prev) => ({ ...prev, ...saved }))).catch(() => {
2706
2749
  });
2750
+ if (configRpcCall) {
2751
+ configRpcCall("get", { domain: "supervisor" }).then((res) => {
2752
+ if (res?.ok && res.value?.model) {
2753
+ setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
2754
+ }
2755
+ }).catch(() => {
2756
+ });
2757
+ }
2707
2758
  call(MAESTRO_ENDPOINTS.lanPinStatus, {}).then((value) => {
2708
2759
  setLanPinEnabled(value.enabled);
2709
2760
  if (value.enabled) setLanPin(value.pin ?? null);
@@ -2801,6 +2852,13 @@ function MaestroSettingsTab({ rpcCall }) {
2801
2852
  const saveField = async (field, value) => {
2802
2853
  setError(null);
2803
2854
  setConfig((prev) => ({ ...prev, [field]: value }));
2855
+ if (field === "supervisorModel" && configRpcCall) {
2856
+ try {
2857
+ const res = await configRpcCall("set", { domain: "supervisor", patch: { model: value } });
2858
+ if (res?.ok) return;
2859
+ } catch (e) {
2860
+ }
2861
+ }
2804
2862
  try {
2805
2863
  await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value });
2806
2864
  } catch (err) {
@@ -2930,6 +2988,20 @@ function MaestroSettingsTab({ rpcCall }) {
2930
2988
  label: "Global review model"
2931
2989
  })
2932
2990
  ),
2991
+ (0, import_react.createElement)(
2992
+ "div",
2993
+ { style: sectionStyle },
2994
+ (0, import_react.createElement)("h4", { style: headingStyle }, "Supervisor LLM"),
2995
+ (0, import_react.createElement)("p", { style: captionStyle }, "Model used by the supervisor debug-agent to auto-fix DSH Web crashes. Empty = DSH default (or Review model if set). Uses the same provider catalog as Review."),
2996
+ (0, import_react.createElement)(ReviewModelSelector, {
2997
+ value: config.supervisorModel ?? null,
2998
+ catalog,
2999
+ fallbackValue: catalog?.current ?? null,
3000
+ fallbackLabel: "Use DSH default",
3001
+ onChange: (v) => saveField("supervisorModel", v),
3002
+ label: "Supervisor model"
3003
+ })
3004
+ ),
2933
3005
  (0, import_react.createElement)(
2934
3006
  "div",
2935
3007
  { style: sectionStyle },
@@ -3012,12 +3084,17 @@ function apply(ctx) {
3012
3084
  if (!connection?.rpc?.call) return Promise.reject(new Error("RPC not available"));
3013
3085
  return connection.rpc.call(MAESTRO_RPC_CHANNEL, endpoint, payload, signal);
3014
3086
  };
3087
+ const configRpcCall = (endpoint, payload, signal) => {
3088
+ const connection = ctx.get?.("connection");
3089
+ if (!connection?.rpc?.call) return Promise.reject(new Error("RPC not available"));
3090
+ return connection.rpc.call("/dsh-maestro-config", endpoint, payload, signal);
3091
+ };
3015
3092
  ctx.effect(() => registerSettingsNavIcon(() => "Maestro"), "maestro: settings nav icon");
3016
3093
  ctx.effect(installNavIconStyle, "maestro: settings nav css");
3017
3094
  slots.inject(
3018
3095
  "settings.section",
3019
3096
  () => slots.register(
3020
- { name: "settings.section", id: "maestro", order: 25, label: () => "Maestro", inject: () => ({ rpcCall }) },
3097
+ { name: "settings.section", id: "maestro", order: 25, label: () => "Maestro", inject: () => ({ rpcCall, configRpcCall }) },
3021
3098
  MaestroSettingsTab
3022
3099
  )
3023
3100
  );
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAuCA,UAAU,SAAS;IACjB,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,MAAM,CAAC,EAAE,EAAE,MAAM,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACtD;AAaD,wBAAgB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAuB1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAuCA,UAAU,SAAS;IACjB,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,MAAM,CAAC,EAAE,EAAE,MAAM,MAAM,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACtD;AAaD,wBAAgB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CA+B1C"}
@@ -1,5 +1,6 @@
1
- export function MaestroSettingsTab({ rpcCall }: {
1
+ export function MaestroSettingsTab({ rpcCall, configRpcCall }: {
2
2
  rpcCall: any;
3
+ configRpcCall: any;
3
4
  }): import("react").DetailedReactHTMLElement<{
4
5
  'data-maestro-settings-card': string;
5
6
  style: {
@@ -1 +1 @@
1
- {"version":3,"file":"maestro-card.d.ts","sourceRoot":"","sources":["../../../src/client/maestro-card.jsx"],"names":[],"mappings":"AA6bA;;;;;;;gBAmPC"}
1
+ {"version":3,"file":"maestro-card.d.ts","sourceRoot":"","sources":["../../../src/client/maestro-card.jsx"],"names":[],"mappings":"AAkeA;;;;;;;;gBAoRC"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@ddtcorex/dsh-maestro-config",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
+ "private": false,
4
5
  "description": "Maestro Config — shared settings service for the dsh-maestro-* suite over the single namespaced store (~/.dsh/maestro/settings.json)",
5
6
  "type": "module",
6
7
  "main": "./lib/index.js",
@@ -37,7 +38,7 @@
37
38
  "@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.8"
38
39
  },
39
40
  "dependencies": {
40
- "@ddtcorex/dsh-maestro-config-lib": "^0.1.0"
41
+ "@ddtcorex/dsh-maestro-config-lib": "^0.1.1"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@deepseek-ai/cordis": "^4.0.1",
@@ -65,6 +65,14 @@ export function apply(ctx: ClientCtx): void {
65
65
  if (!connection?.rpc?.call) return Promise.reject(new Error('RPC not available'))
66
66
  return connection.rpc.call(MAESTRO_RPC_CHANNEL, endpoint, payload, signal)
67
67
  }
68
+ // Generic config RPC for supervisor (independent of review — works when review not installed)
69
+ const configRpcCall: RpcCall = (endpoint, payload, signal) => {
70
+ const connection = ctx.get?.('connection') as
71
+ | { rpc: { call(ch: string, ep: string, p?: unknown, s?: AbortSignal): Promise<unknown> } }
72
+ | undefined
73
+ if (!connection?.rpc?.call) return Promise.reject(new Error('RPC not available'))
74
+ return connection.rpc.call('/dsh-maestro-config', endpoint, payload, signal)
75
+ }
68
76
 
69
77
  // Reversible effects: nav-row marker observer + owned style tag.
70
78
  ctx.effect(() => registerSettingsNavIcon(() => 'Maestro'), 'maestro: settings nav icon')
@@ -72,8 +80,8 @@ export function apply(ctx: ClientCtx): void {
72
80
 
73
81
  slots.inject('settings.section', () =>
74
82
  slots.register(
75
- { name: 'settings.section', id: 'maestro', order: 25, label: () => 'Maestro', inject: () => ({ rpcCall }) },
76
- MaestroSettingsTab,
83
+ { name: 'settings.section', id: 'maestro', order: 25, label: () => 'Maestro', inject: () => ({ rpcCall, configRpcCall }) },
84
+ MaestroSettingsTab as unknown as (props: { rpcCall: RpcCall }) => unknown,
77
85
  ),
78
86
  )
79
87
  }
@@ -147,10 +147,35 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
147
147
  document.addEventListener('keydown', onKey)
148
148
  return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey) }
149
149
  }, [open])
150
+ const getModelId = (m) => typeof m === 'string' ? m : m.id
151
+ const getModelName = (m) => typeof m === 'string' ? m : (m.name ?? m.id)
152
+ const selectedModelInfo = (() => {
153
+ if (!selectedProvider || !value?.model) return null
154
+ const raw = (providerGroup?.models ?? []).find(mm => getModelId(mm) === value.model)
155
+ if (raw === undefined) return null
156
+ if (typeof raw === 'string') return { id: raw, supportsReasoning: false, reasoningEfforts: [] }
157
+ return raw
158
+ })()
159
+ const supportsReasoning = (() => {
160
+ if (!selectedModelInfo) return false
161
+ if (typeof selectedModelInfo.supportsReasoning === 'boolean') return selectedModelInfo.supportsReasoning
162
+ const efforts = selectedModelInfo.reasoningEfforts ?? selectedModelInfo.reasoning?.efforts?.map(e => e.id) ?? []
163
+ return efforts.filter(e => e !== 'off').length > 0
164
+ })()
165
+ const availableEfforts = (() => {
166
+ if (!supportsReasoning) return []
167
+ const efforts = selectedModelInfo?.reasoningEfforts ?? selectedModelInfo?.reasoning?.efforts?.map(e => e.id) ?? []
168
+ const filtered = efforts.filter(e => e !== 'off' && e !== '')
169
+ if (filtered.length > 0) return filtered
170
+ return ['low', 'medium', 'high']
171
+ })()
172
+ const warning = selectedEffort !== '' && !supportsReasoning && selectedModelInfo !== null
173
+ ? `⚠️ This model does not support reasoning effort "${selectedEffort}" — reviews will fail. Clear effort or choose a reasoning-capable model.`
174
+ : null
150
175
  const update = (field, newVal) => {
151
176
  if (newVal === '' && field === 'provider') { onChange(null); setOpen(false); setPane('root'); return }
152
177
  const next = { provider: value?.provider ?? '', model: value?.model ?? '', ...(value?.reasoningEffort ? { reasoningEffort: value.reasoningEffort } : {}) }
153
- if (field === 'provider') { const g = groups.find(x => x.provider === newVal); next.provider = newVal; next.model = g?.models[0] ?? '' }
178
+ if (field === 'provider') { const g = groups.find(x => x.provider === newVal); const first = g?.models[0]; next.provider = newVal; next.model = first !== undefined ? getModelId(first) : '' }
154
179
  else if (field === 'model') { next.model = newVal }
155
180
  else if (field === 'reasoningEffort') { if (newVal === '') delete next.reasoningEffort; else next.reasoningEffort = newVal }
156
181
  if (!next.provider || !next.model) { onChange(null) } else { onChange(next) }
@@ -235,6 +260,7 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
235
260
  ),
236
261
  value && h('p', { style: { ...captionStyle, margin: '8px 4px 2px' } }, `Selected: ${value.provider} / ${value.model}${value.reasoningEffort ? ` (${value.reasoningEffort})` : ''}`),
237
262
  !value && effectiveFallback && h('p', { style: { ...captionStyle, margin: '8px 4px 2px' } }, `${effectiveFallbackLabel === 'Use Global' ? 'Using Global' : 'Using DSH default'}: ${effectiveFallback.provider} / ${effectiveFallback.model}${effectiveFallback.reasoningEffort ? ` (${effectiveFallback.reasoningEffort})` : ''}`),
263
+ warning && h('p', { style: { ...captionStyle, margin: '4px 4px 2px', color: 'var(--dsw-alias-state-error-primary)' } }, warning),
238
264
  ),
239
265
  pane === 'model' && h('div', null,
240
266
  h('button', { type: 'button', style: { ...rowStyle, color: 'var(--dsw-alias-label-secondary)' }, onClick: () => setPane('root') }, h('span', null, '← Back'), h('span', { style: { fontSize: 12 } }, 'Model')),
@@ -247,20 +273,31 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
247
273
  h('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--dsw-alias-label-secondary)', padding: '6px 10px 2px', textTransform: 'uppercase', letterSpacing: 0.4, display: 'flex', alignItems: 'center', gap: 6 } }, h('span', { style: { width: 6, height: 6, borderRadius: 3, background: 'var(--dsw-alias-border-l2)', flex: 'none' } }), g?.name ?? p),
248
274
  ms.length === 0 ? h('p', { style: { ...captionStyle, padding: '2px 10px 2px 28px' } }, 'No models') :
249
275
  h('div', { style: { marginLeft: 12, borderLeft: '1px solid var(--dsw-alias-border-l2)', paddingLeft: 6, display: 'flex', flexDirection: 'column', gap: 2 } },
250
- ms.map(m => h('button', { key: m, type: 'button', style: { ...rowStyle, paddingLeft: 10, background: value?.provider === p && value?.model === m ? 'var(--dsw-alias-bg-layer-2)' : 'transparent' }, onClick: () => { update('model', m); if (value?.provider !== p) update('provider', p); else { const next = { provider: p, model: m, ...(selectedEffort ? { reasoningEffort: selectedEffort } : {}) }; onChange(next); setPane('root') } } }, h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis' } }, m), check(value?.provider === p && value?.model === m)))),
276
+ ms.map(m => {
277
+ const mid = getModelId(m)
278
+ const mname = getModelName(m)
279
+ const active = value?.provider === p && value?.model === mid
280
+ return h('button', { key: mid, type: 'button', style: { ...rowStyle, paddingLeft: 10, background: active ? 'var(--dsw-alias-bg-layer-2)' : 'transparent' }, onClick: () => { update('model', mid); if (value?.provider !== p) update('provider', p); else { const next = { provider: p, model: mid, ...(selectedEffort ? { reasoningEffort: selectedEffort } : {}) }; onChange(next); setPane('root') } } }, h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis' } }, mname), check(active))
281
+ })),
251
282
  )
252
283
  }),
253
284
  ),
254
285
  ),
255
286
  pane === 'effort' && h('div', null,
256
287
  h('button', { type: 'button', style: { ...rowStyle, color: 'var(--dsw-alias-label-secondary)' }, onClick: () => setPane('root') }, h('span', null, '← Back'), h('span', { style: { fontSize: 12 } }, 'Effort')),
257
- h('div', { style: { marginTop: 4 } },
258
- [
259
- { id: '', label: 'Default effort' },
260
- { id: 'low', label: 'low' },
261
- { id: 'medium', label: 'medium' },
262
- { id: 'high', label: 'high' },
263
- ].map(e => h('button', { key: e.id || 'default', type: 'button', style: { ...rowStyle, background: selectedEffort === e.id ? 'var(--dsw-alias-bg-layer-2)' : 'transparent' }, onClick: () => { update('reasoningEffort', e.id); setPane('root') } }, h('span', null, e.label), check(selectedEffort === e.id))),
288
+ selectedModelInfo === null ? h('p', { style: { ...captionStyle, margin: '8px 4px 2px' } }, 'Select a model first to configure effort.') :
289
+ !supportsReasoning ? h('div', null,
290
+ h('p', { style: { ...captionStyle, margin: '8px 4px 6px' } }, 'This model does not support reasoning effort — using provider default'),
291
+ h('div', { style: { marginTop: 4 } },
292
+ [{ id: '', label: 'Default effort' }].map(e => h('button', { key: e.id || 'default', type: 'button', style: { ...rowStyle, background: selectedEffort === e.id ? 'var(--dsw-alias-bg-layer-2)' : 'transparent' }, onClick: () => { update('reasoningEffort', e.id); setPane('root') } }, h('span', null, e.label), check(selectedEffort === e.id))),
293
+ ),
294
+ warning && h('p', { style: { ...captionStyle, margin: '8px 4px 2px', color: 'var(--dsw-alias-state-error-primary)' } }, warning),
295
+ ) :
296
+ h('div', null,
297
+ h('div', { style: { marginTop: 4 } },
298
+ [{ id: '', label: 'Default effort' }, ...availableEfforts.map(id => ({ id, label: id }))].map(e => h('button', { key: e.id || 'default', type: 'button', style: { ...rowStyle, background: selectedEffort === e.id ? 'var(--dsw-alias-bg-layer-2)' : 'transparent' }, onClick: () => { update('reasoningEffort', e.id); setPane('root') } }, h('span', null, e.label), check(selectedEffort === e.id))),
299
+ ),
300
+ warning && h('p', { style: { ...captionStyle, margin: '8px 4px 2px', color: 'var(--dsw-alias-state-error-primary)' } }, warning),
264
301
  ),
265
302
  ),
266
303
  ),
@@ -443,7 +480,7 @@ function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePi
443
480
  )
444
481
  }
445
482
 
446
- export function MaestroSettingsTab({ rpcCall }) {
483
+ export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
447
484
  const [status, setStatus] = useState(null)
448
485
  const [proxyStatus, setProxyStatus] = useState(null)
449
486
  const [config, setConfig] = useState({ tunnelMode: 'quick', projectMappings: [] })
@@ -474,6 +511,16 @@ export function MaestroSettingsTab({ rpcCall }) {
474
511
  call(MAESTRO_ENDPOINTS.getConfig, {})
475
512
  .then(saved => setConfig(prev => ({ ...prev, ...saved })))
476
513
  .catch(() => { /* first run, no config saved yet — keep defaults */ })
514
+ // Supervisor model may be stored via generic config service when review not installed — try fallback
515
+ if (configRpcCall) {
516
+ configRpcCall('get', { domain: 'supervisor' })
517
+ .then(res => {
518
+ if (res?.ok && res.value?.model) {
519
+ setConfig(prev => ({ ...prev, supervisorModel: res.value.model }))
520
+ }
521
+ })
522
+ .catch(() => { /* supervisor domain not yet set or config service unavailable */ })
523
+ }
477
524
  call(MAESTRO_ENDPOINTS.lanPinStatus, {})
478
525
  .then(value => { setLanPinEnabled(value.enabled); if (value.enabled) setLanPin(value.pin ?? null) })
479
526
  .catch(() => { /* host without the LAN PIN endpoints — keep the row hidden */ })
@@ -559,6 +606,16 @@ export function MaestroSettingsTab({ rpcCall }) {
559
606
  const saveField = async (field, value) => {
560
607
  setError(null)
561
608
  setConfig(prev => ({ ...prev, [field]: value }))
609
+ // Supervisor model can be saved via generic config service when review not installed (independent install)
610
+ if (field === 'supervisorModel' && configRpcCall) {
611
+ try {
612
+ const res = await configRpcCall('set', { domain: 'supervisor', patch: { model: value } })
613
+ if (res?.ok) return
614
+ // Fall through to review RPC if generic set fails
615
+ } catch (e) {
616
+ // Fall through to review RPC
617
+ }
618
+ }
562
619
  try {
563
620
  await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value })
564
621
  } catch (err) {
@@ -679,6 +736,19 @@ export function MaestroSettingsTab({ rpcCall }) {
679
736
  }),
680
737
  ),
681
738
 
739
+ h('div', { style: sectionStyle },
740
+ h('h4', { style: headingStyle }, 'Supervisor LLM'),
741
+ h('p', { style: captionStyle }, 'Model used by the supervisor debug-agent to auto-fix DSH Web crashes. Empty = DSH default (or Review model if set). Uses the same provider catalog as Review.'),
742
+ h(ReviewModelSelector, {
743
+ value: config.supervisorModel ?? null,
744
+ catalog,
745
+ fallbackValue: catalog?.current ?? null,
746
+ fallbackLabel: 'Use DSH default',
747
+ onChange: v => saveField('supervisorModel', v),
748
+ label: 'Supervisor model',
749
+ }),
750
+ ),
751
+
682
752
  h('div', { style: sectionStyle },
683
753
  h('h4', { style: headingStyle }, 'Projects'),
684
754
  h(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: mappings => saveField('projectMappings', mappings), catalog, globalReviewModel: config.reviewModel ?? null }),