@ddtcorex/dsh-maestro-config 0.1.0 → 0.1.2
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 +373 -55
- package/lib/index.d.ts +24 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +8 -1
- package/lib/index.js.map +1 -1
- package/lib/types/client/Settings.d.ts +11 -0
- package/lib/types/client/Settings.d.ts.map +1 -0
- package/lib/types/client/index.d.ts +1 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/maestro-card.d.ts +2 -1
- package/lib/types/client/maestro-card.d.ts.map +1 -1
- package/package.json +5 -6
- package/src/client/index.tsx +18 -7
- package/src/client/maestro-card.jsx +305 -31
- package/src/host/index.ts +12 -3
package/lib/client.js
CHANGED
|
@@ -2117,7 +2117,8 @@ var require_browser = __commonJS({
|
|
|
2117
2117
|
// src/client/index.tsx
|
|
2118
2118
|
var index_exports = {};
|
|
2119
2119
|
__export(index_exports, {
|
|
2120
|
-
apply: () => apply
|
|
2120
|
+
apply: () => apply,
|
|
2121
|
+
inject: () => inject
|
|
2121
2122
|
});
|
|
2122
2123
|
module.exports = __toCommonJS(index_exports);
|
|
2123
2124
|
|
|
@@ -2221,6 +2222,29 @@ var codeStyle = {
|
|
|
2221
2222
|
color: "var(--dsw-alias-label-primary)",
|
|
2222
2223
|
wordBreak: "break-all"
|
|
2223
2224
|
};
|
|
2225
|
+
var textareaStyle = {
|
|
2226
|
+
...inputStyle,
|
|
2227
|
+
height: 120,
|
|
2228
|
+
padding: "8px 12px",
|
|
2229
|
+
resize: "vertical"
|
|
2230
|
+
};
|
|
2231
|
+
var tabBarStyle = {
|
|
2232
|
+
display: "flex",
|
|
2233
|
+
gap: 8,
|
|
2234
|
+
borderBottom: "1px solid var(--dsw-alias-border-l2)",
|
|
2235
|
+
marginBottom: 16
|
|
2236
|
+
};
|
|
2237
|
+
var tabButtonStyle = (active) => ({
|
|
2238
|
+
padding: "8px 14px",
|
|
2239
|
+
border: "none",
|
|
2240
|
+
borderBottom: active ? "2px solid var(--dsw-alias-button-primary-fill)" : "2px solid transparent",
|
|
2241
|
+
background: "transparent",
|
|
2242
|
+
color: active ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)",
|
|
2243
|
+
font: "inherit",
|
|
2244
|
+
fontSize: 13,
|
|
2245
|
+
fontWeight: active ? 600 : 400,
|
|
2246
|
+
cursor: "pointer"
|
|
2247
|
+
});
|
|
2224
2248
|
function QrImage({ url, size = 104 }) {
|
|
2225
2249
|
const [dataUrl, setDataUrl] = (0, import_react.useState)(null);
|
|
2226
2250
|
(0, import_react.useEffect)(() => {
|
|
@@ -2299,6 +2323,29 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
2299
2323
|
document.removeEventListener("keydown", onKey);
|
|
2300
2324
|
};
|
|
2301
2325
|
}, [open]);
|
|
2326
|
+
const getModelId = (m) => typeof m === "string" ? m : m.id;
|
|
2327
|
+
const getModelName = (m) => typeof m === "string" ? m : m.name ?? m.id;
|
|
2328
|
+
const selectedModelInfo = (() => {
|
|
2329
|
+
if (!selectedProvider || !value?.model) return null;
|
|
2330
|
+
const raw = (providerGroup?.models ?? []).find((mm) => getModelId(mm) === value.model);
|
|
2331
|
+
if (raw === void 0) return null;
|
|
2332
|
+
if (typeof raw === "string") return { id: raw, supportsReasoning: false, reasoningEfforts: [] };
|
|
2333
|
+
return raw;
|
|
2334
|
+
})();
|
|
2335
|
+
const supportsReasoning = (() => {
|
|
2336
|
+
if (!selectedModelInfo) return false;
|
|
2337
|
+
if (typeof selectedModelInfo.supportsReasoning === "boolean") return selectedModelInfo.supportsReasoning;
|
|
2338
|
+
const efforts = selectedModelInfo.reasoningEfforts ?? selectedModelInfo.reasoning?.efforts?.map((e) => e.id) ?? [];
|
|
2339
|
+
return efforts.filter((e) => e !== "off").length > 0;
|
|
2340
|
+
})();
|
|
2341
|
+
const availableEfforts = (() => {
|
|
2342
|
+
if (!supportsReasoning) return [];
|
|
2343
|
+
const efforts = selectedModelInfo?.reasoningEfforts ?? selectedModelInfo?.reasoning?.efforts?.map((e) => e.id) ?? [];
|
|
2344
|
+
const filtered = efforts.filter((e) => e !== "off" && e !== "");
|
|
2345
|
+
if (filtered.length > 0) return filtered;
|
|
2346
|
+
return ["low", "medium", "high"];
|
|
2347
|
+
})();
|
|
2348
|
+
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
2349
|
const update = (field, newVal) => {
|
|
2303
2350
|
if (newVal === "" && field === "provider") {
|
|
2304
2351
|
onChange(null);
|
|
@@ -2309,8 +2356,9 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
2309
2356
|
const next = { provider: value?.provider ?? "", model: value?.model ?? "", ...value?.reasoningEffort ? { reasoningEffort: value.reasoningEffort } : {} };
|
|
2310
2357
|
if (field === "provider") {
|
|
2311
2358
|
const g = groups.find((x) => x.provider === newVal);
|
|
2359
|
+
const first = g?.models[0];
|
|
2312
2360
|
next.provider = newVal;
|
|
2313
|
-
next.model =
|
|
2361
|
+
next.model = first !== void 0 ? getModelId(first) : "";
|
|
2314
2362
|
} else if (field === "model") {
|
|
2315
2363
|
next.model = newVal;
|
|
2316
2364
|
} else if (field === "reasoningEffort") {
|
|
@@ -2418,7 +2466,8 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
2418
2466
|
(0, import_react.createElement)("span", { style: { display: "flex", alignItems: "center", gap: 8, color: "var(--dsw-alias-label-secondary)" } }, effortLabel, chevronRight)
|
|
2419
2467
|
),
|
|
2420
2468
|
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})` : ""}`)
|
|
2469
|
+
!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})` : ""}`),
|
|
2470
|
+
warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "4px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
|
|
2422
2471
|
),
|
|
2423
2472
|
pane === "model" && (0, import_react.createElement)(
|
|
2424
2473
|
"div",
|
|
@@ -2437,15 +2486,20 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
2437
2486
|
ms.length === 0 ? (0, import_react.createElement)("p", { style: { ...captionStyle, padding: "2px 10px 2px 28px" } }, "No models") : (0, import_react.createElement)(
|
|
2438
2487
|
"div",
|
|
2439
2488
|
{ style: { marginLeft: 12, borderLeft: "1px solid var(--dsw-alias-border-l2)", paddingLeft: 6, display: "flex", flexDirection: "column", gap: 2 } },
|
|
2440
|
-
ms.map((m) =>
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2489
|
+
ms.map((m) => {
|
|
2490
|
+
const mid = getModelId(m);
|
|
2491
|
+
const mname = getModelName(m);
|
|
2492
|
+
const active = value?.provider === p && value?.model === mid;
|
|
2493
|
+
return (0, import_react.createElement)("button", { key: mid, type: "button", style: { ...rowStyle, paddingLeft: 10, background: active ? "var(--dsw-alias-bg-layer-2)" : "transparent" }, onClick: () => {
|
|
2494
|
+
update("model", mid);
|
|
2495
|
+
if (value?.provider !== p) update("provider", p);
|
|
2496
|
+
else {
|
|
2497
|
+
const next = { provider: p, model: mid, ...selectedEffort ? { reasoningEffort: selectedEffort } : {} };
|
|
2498
|
+
onChange(next);
|
|
2499
|
+
setPane("root");
|
|
2500
|
+
}
|
|
2501
|
+
} }, (0, import_react.createElement)("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, mname), check(active));
|
|
2502
|
+
})
|
|
2449
2503
|
)
|
|
2450
2504
|
);
|
|
2451
2505
|
})
|
|
@@ -2455,18 +2509,31 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
2455
2509
|
"div",
|
|
2456
2510
|
null,
|
|
2457
2511
|
(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)(
|
|
2512
|
+
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)(
|
|
2513
|
+
"div",
|
|
2514
|
+
null,
|
|
2515
|
+
(0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 6px" } }, "This model does not support reasoning effort \u2014 using provider default"),
|
|
2516
|
+
(0, import_react.createElement)(
|
|
2517
|
+
"div",
|
|
2518
|
+
{ style: { marginTop: 4 } },
|
|
2519
|
+
[{ 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: () => {
|
|
2520
|
+
update("reasoningEffort", e.id);
|
|
2521
|
+
setPane("root");
|
|
2522
|
+
} }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
|
|
2523
|
+
),
|
|
2524
|
+
warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
|
|
2525
|
+
) : (0, import_react.createElement)(
|
|
2459
2526
|
"div",
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
{
|
|
2464
|
-
{ id: "
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2527
|
+
null,
|
|
2528
|
+
(0, import_react.createElement)(
|
|
2529
|
+
"div",
|
|
2530
|
+
{ style: { marginTop: 4 } },
|
|
2531
|
+
[{ 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: () => {
|
|
2532
|
+
update("reasoningEffort", e.id);
|
|
2533
|
+
setPane("root");
|
|
2534
|
+
} }, (0, import_react.createElement)("span", null, e.label), check(selectedEffort === e.id)))
|
|
2535
|
+
),
|
|
2536
|
+
warning && (0, import_react.createElement)("p", { style: { ...captionStyle, margin: "8px 4px 2px", color: "var(--dsw-alias-state-error-primary)" } }, warning)
|
|
2470
2537
|
)
|
|
2471
2538
|
)
|
|
2472
2539
|
)
|
|
@@ -2553,31 +2620,6 @@ function ToggleField({ label, caption, checked, onChange }) {
|
|
|
2553
2620
|
)
|
|
2554
2621
|
);
|
|
2555
2622
|
}
|
|
2556
|
-
function ReviewHistoryPanel({ rpcCall }) {
|
|
2557
|
-
const [entries, setEntries] = (0, import_react.useState)(null);
|
|
2558
|
-
(0, import_react.useEffect)(() => {
|
|
2559
|
-
rpcCall(MAESTRO_ENDPOINTS.reviewsList, {}).then((res) => {
|
|
2560
|
-
if (res?.ok) setEntries(res.value ?? []);
|
|
2561
|
-
}).catch(() => setEntries([]));
|
|
2562
|
-
}, []);
|
|
2563
|
-
if (entries === null) return (0, import_react.createElement)("p", { style: captionStyle }, "Loading review history\u2026");
|
|
2564
|
-
if (entries.length === 0) return (0, import_react.createElement)("p", { style: captionStyle }, "No reviews recorded yet.");
|
|
2565
|
-
const icon = (entry) => entry.status === "completed" ? "\u2705" : entry.status === "failed" ? "\u26A0\uFE0F" : "\u{1F440}";
|
|
2566
|
-
return (0, import_react.createElement)(
|
|
2567
|
-
"ul",
|
|
2568
|
-
{ style: { listStyle: "none", margin: 0, padding: 0 } },
|
|
2569
|
-
entries.map((entry) => (0, import_react.createElement)(
|
|
2570
|
-
"li",
|
|
2571
|
-
{ key: entry.id, style: { padding: "6px 0", borderBottom: "1px solid var(--dsw-alias-separator-default, #333)", fontSize: 13 } },
|
|
2572
|
-
(0, import_react.createElement)("span", null, `${icon(entry)} ${entry.projectPath} !${entry.mrIid} \xB7 ${entry.mode}${entry.trigger !== "mention" ? ` \xB7 ${entry.trigger}` : ""}`),
|
|
2573
|
-
(0, import_react.createElement)(
|
|
2574
|
-
"div",
|
|
2575
|
-
{ style: captionStyle },
|
|
2576
|
-
`${new Date(entry.startedAt).toLocaleString()}${entry.summary ? ` \u2014 ${entry.summary}` : ""}${entry.error ? ` \u2014 ${entry.error}` : ""}`
|
|
2577
|
-
)
|
|
2578
|
-
))
|
|
2579
|
-
);
|
|
2580
|
-
}
|
|
2581
2623
|
function LanAccess({ proxyStatus, lanPin }) {
|
|
2582
2624
|
const urls = proxyStatus?.lanUrls ?? [];
|
|
2583
2625
|
const [selected, setSelected] = (0, import_react.useState)(0);
|
|
@@ -2674,7 +2716,7 @@ function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePi
|
|
|
2674
2716
|
(0, import_react.createElement)("p", { style: captionStyle }, "Stays the same across tunnel and DSH restarts; use Rotate when you need a new PIN.")
|
|
2675
2717
|
);
|
|
2676
2718
|
}
|
|
2677
|
-
function MaestroSettingsTab({ rpcCall }) {
|
|
2719
|
+
function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
2678
2720
|
const [status, setStatus] = (0, import_react.useState)(null);
|
|
2679
2721
|
const [proxyStatus, setProxyStatus] = (0, import_react.useState)(null);
|
|
2680
2722
|
const [config, setConfig] = (0, import_react.useState)({ tunnelMode: "quick", projectMappings: [] });
|
|
@@ -2686,11 +2728,95 @@ function MaestroSettingsTab({ rpcCall }) {
|
|
|
2686
2728
|
const [lanPinEnabled, setLanPinEnabled] = (0, import_react.useState)(false);
|
|
2687
2729
|
const [lanPin, setLanPin] = (0, import_react.useState)(null);
|
|
2688
2730
|
const [showLanPin, setShowLanPin] = (0, import_react.useState)(false);
|
|
2731
|
+
const [activeTab, setActiveTab] = (0, import_react.useState)("guard");
|
|
2732
|
+
const [guard, setGuard] = (0, import_react.useState)({});
|
|
2733
|
+
const [patternsText, setPatternsText] = (0, import_react.useState)("");
|
|
2734
|
+
const [placeholdersText, setPlaceholdersText] = (0, import_react.useState)("");
|
|
2735
|
+
const [supervisorCfg, setSupervisorCfg] = (0, import_react.useState)({});
|
|
2736
|
+
const [notifierCfg, setNotifierCfg] = (0, import_react.useState)({});
|
|
2689
2737
|
const call = async (endpoint, payload) => {
|
|
2690
2738
|
const res = await rpcCall(endpoint, payload);
|
|
2691
2739
|
if (!res?.ok) throw new Error(res?.error?.message ?? "RPC failed");
|
|
2692
2740
|
return res.value;
|
|
2693
2741
|
};
|
|
2742
|
+
const unwrap = (res) => {
|
|
2743
|
+
if (res && typeof res === "object" && "ok" in res) {
|
|
2744
|
+
if (res.ok) return res.value;
|
|
2745
|
+
throw new Error(res.error?.message ?? "RPC failed");
|
|
2746
|
+
}
|
|
2747
|
+
return res;
|
|
2748
|
+
};
|
|
2749
|
+
const cfgGet = async (domain) => {
|
|
2750
|
+
if (!configRpcCall) throw new Error("config RPC not available");
|
|
2751
|
+
const res = await configRpcCall("get", { domain });
|
|
2752
|
+
return unwrap(res);
|
|
2753
|
+
};
|
|
2754
|
+
const cfgSet = async (domain, patch) => {
|
|
2755
|
+
if (!configRpcCall) throw new Error("config RPC not available");
|
|
2756
|
+
const res = await configRpcCall("set", { domain, patch });
|
|
2757
|
+
return unwrap(res);
|
|
2758
|
+
};
|
|
2759
|
+
const saveGuard = async (patch) => {
|
|
2760
|
+
setError(null);
|
|
2761
|
+
const next = { ...guard, ...patch };
|
|
2762
|
+
if (patch.gitProtection && guard.gitProtection) next.gitProtection = { ...guard.gitProtection, ...patch.gitProtection };
|
|
2763
|
+
setGuard(next);
|
|
2764
|
+
try {
|
|
2765
|
+
await cfgSet("guard", patch);
|
|
2766
|
+
} catch (e) {
|
|
2767
|
+
setError(e.message ?? String(e));
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
const commitBlacklistPatterns = async (text) => {
|
|
2771
|
+
const patterns = text.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
2772
|
+
setError(null);
|
|
2773
|
+
try {
|
|
2774
|
+
await cfgSet("guardBlacklist", { patterns });
|
|
2775
|
+
} catch (e) {
|
|
2776
|
+
setError(e.message ?? String(e));
|
|
2777
|
+
}
|
|
2778
|
+
};
|
|
2779
|
+
const commitPlaceholders = async () => {
|
|
2780
|
+
setError(null);
|
|
2781
|
+
let obj = {};
|
|
2782
|
+
try {
|
|
2783
|
+
obj = placeholdersText.trim() ? JSON.parse(placeholdersText) : {};
|
|
2784
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) throw new Error("placeholders must be JSON object");
|
|
2785
|
+
} catch (e) {
|
|
2786
|
+
setError(`placeholders JSON invalid: ${e.message ?? String(e)}`);
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
try {
|
|
2790
|
+
await cfgSet("guardBlacklist", { placeholders: obj });
|
|
2791
|
+
} catch (e) {
|
|
2792
|
+
setError(e.message ?? String(e));
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
const saveSupervisorCfg = async (patch) => {
|
|
2796
|
+
setError(null);
|
|
2797
|
+
setSupervisorCfg((prev) => ({ ...prev, ...patch }));
|
|
2798
|
+
try {
|
|
2799
|
+
await cfgSet("supervisor", patch);
|
|
2800
|
+
} catch (e) {
|
|
2801
|
+
setError(e.message ?? String(e));
|
|
2802
|
+
}
|
|
2803
|
+
};
|
|
2804
|
+
const saveNotifierCfg = async (patch) => {
|
|
2805
|
+
setError(null);
|
|
2806
|
+
setNotifierCfg((prev) => {
|
|
2807
|
+
const next = { ...prev };
|
|
2808
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
2809
|
+
if (k === "telegram" && typeof v === "object" && v !== null) next.telegram = { ...prev.telegram ?? {}, ...v };
|
|
2810
|
+
else next[k] = v;
|
|
2811
|
+
}
|
|
2812
|
+
return next;
|
|
2813
|
+
});
|
|
2814
|
+
try {
|
|
2815
|
+
await cfgSet("notifier", patch);
|
|
2816
|
+
} catch (e) {
|
|
2817
|
+
setError(e.message ?? String(e));
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2694
2820
|
const refresh = async () => {
|
|
2695
2821
|
try {
|
|
2696
2822
|
setStatus(await call(MAESTRO_ENDPOINTS.status, {}));
|
|
@@ -2704,6 +2830,29 @@ function MaestroSettingsTab({ rpcCall }) {
|
|
|
2704
2830
|
(0, import_react.useEffect)(() => {
|
|
2705
2831
|
call(MAESTRO_ENDPOINTS.getConfig, {}).then((saved) => setConfig((prev) => ({ ...prev, ...saved }))).catch(() => {
|
|
2706
2832
|
});
|
|
2833
|
+
if (configRpcCall) {
|
|
2834
|
+
configRpcCall("get", { domain: "supervisor" }).then((res) => {
|
|
2835
|
+
if (res?.ok && res.value?.model) {
|
|
2836
|
+
setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
|
|
2837
|
+
}
|
|
2838
|
+
}).catch(() => {
|
|
2839
|
+
});
|
|
2840
|
+
Promise.all([
|
|
2841
|
+
cfgGet("guard").catch(() => ({})),
|
|
2842
|
+
cfgGet("guardBlacklist").catch(() => ({ patterns: [], placeholders: {} })),
|
|
2843
|
+
cfgGet("supervisor").catch(() => ({})),
|
|
2844
|
+
cfgGet("notifier").catch(() => ({}))
|
|
2845
|
+
]).then(([g, bl, sup, not]) => {
|
|
2846
|
+
setGuard(g ?? {});
|
|
2847
|
+
const pats = Array.isArray(bl?.patterns) ? bl.patterns : [];
|
|
2848
|
+
const ph = bl?.placeholders && typeof bl.placeholders === "object" ? bl.placeholders : {};
|
|
2849
|
+
setPatternsText(pats.join("\n"));
|
|
2850
|
+
setPlaceholdersText(JSON.stringify(ph, null, 2));
|
|
2851
|
+
setSupervisorCfg(sup ?? {});
|
|
2852
|
+
setNotifierCfg(not ?? {});
|
|
2853
|
+
}).catch(() => {
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2707
2856
|
call(MAESTRO_ENDPOINTS.lanPinStatus, {}).then((value) => {
|
|
2708
2857
|
setLanPinEnabled(value.enabled);
|
|
2709
2858
|
if (value.enabled) setLanPin(value.pin ?? null);
|
|
@@ -2801,6 +2950,13 @@ function MaestroSettingsTab({ rpcCall }) {
|
|
|
2801
2950
|
const saveField = async (field, value) => {
|
|
2802
2951
|
setError(null);
|
|
2803
2952
|
setConfig((prev) => ({ ...prev, [field]: value }));
|
|
2953
|
+
if (field === "supervisorModel" && configRpcCall) {
|
|
2954
|
+
try {
|
|
2955
|
+
const res = await configRpcCall("set", { domain: "supervisor", patch: { model: value } });
|
|
2956
|
+
if (res?.ok) return;
|
|
2957
|
+
} catch (e) {
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2804
2960
|
try {
|
|
2805
2961
|
await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value });
|
|
2806
2962
|
} catch (err) {
|
|
@@ -2913,8 +3069,7 @@ function MaestroSettingsTab({ rpcCall }) {
|
|
|
2913
3069
|
caption: "After a completed review, further pushes to the same MR trigger an automatic quick re-review.",
|
|
2914
3070
|
checked: config.autoRereviewOnPush,
|
|
2915
3071
|
onChange: (checked) => saveField("autoRereviewOnPush", checked)
|
|
2916
|
-
})
|
|
2917
|
-
(0, import_react.createElement)(ReviewHistoryPanel, { rpcCall })
|
|
3072
|
+
})
|
|
2918
3073
|
),
|
|
2919
3074
|
(0, import_react.createElement)(
|
|
2920
3075
|
"div",
|
|
@@ -2930,12 +3085,169 @@ function MaestroSettingsTab({ rpcCall }) {
|
|
|
2930
3085
|
label: "Global review model"
|
|
2931
3086
|
})
|
|
2932
3087
|
),
|
|
3088
|
+
(0, import_react.createElement)(
|
|
3089
|
+
"div",
|
|
3090
|
+
{ style: sectionStyle },
|
|
3091
|
+
(0, import_react.createElement)("h4", { style: headingStyle }, "Supervisor LLM"),
|
|
3092
|
+
(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."),
|
|
3093
|
+
(0, import_react.createElement)(ReviewModelSelector, {
|
|
3094
|
+
value: config.supervisorModel ?? null,
|
|
3095
|
+
catalog,
|
|
3096
|
+
fallbackValue: catalog?.current ?? null,
|
|
3097
|
+
fallbackLabel: "Use DSH default",
|
|
3098
|
+
onChange: (v) => saveField("supervisorModel", v),
|
|
3099
|
+
label: "Supervisor model"
|
|
3100
|
+
})
|
|
3101
|
+
),
|
|
2933
3102
|
(0, import_react.createElement)(
|
|
2934
3103
|
"div",
|
|
2935
3104
|
{ style: sectionStyle },
|
|
2936
3105
|
(0, import_react.createElement)("h4", { style: headingStyle }, "Projects"),
|
|
2937
3106
|
(0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
|
|
2938
3107
|
),
|
|
3108
|
+
// Task 3: Guard/Blacklist/Supervisor/Notifier tabs — data-driven over guard domains
|
|
3109
|
+
(0, import_react.createElement)(
|
|
3110
|
+
"div",
|
|
3111
|
+
{ style: sectionStyle },
|
|
3112
|
+
(0, import_react.createElement)("h4", { style: headingStyle }, "Guard / Blacklist / Supervisor / Notifier"),
|
|
3113
|
+
(0, import_react.createElement)(
|
|
3114
|
+
"div",
|
|
3115
|
+
{ style: tabBarStyle },
|
|
3116
|
+
(0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "guard"), onClick: () => setActiveTab("guard") }, "Guard"),
|
|
3117
|
+
(0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "blacklist"), onClick: () => setActiveTab("blacklist") }, "Blacklist"),
|
|
3118
|
+
(0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "supervisor"), onClick: () => setActiveTab("supervisor") }, "Supervisor"),
|
|
3119
|
+
(0, import_react.createElement)("button", { type: "button", style: tabButtonStyle(activeTab === "notifier"), onClick: () => setActiveTab("notifier") }, "Notifier")
|
|
3120
|
+
),
|
|
3121
|
+
activeTab === "guard" && (0, import_react.createElement)(
|
|
3122
|
+
"div",
|
|
3123
|
+
{ "data-tab": "guard" },
|
|
3124
|
+
(0, import_react.createElement)("p", { style: captionStyle }, "Enforce publish block, git protection and cwd containment."),
|
|
3125
|
+
(0, import_react.createElement)(ToggleField, {
|
|
3126
|
+
label: "publishBlocked",
|
|
3127
|
+
caption: "Block publish-related commands when enabled.",
|
|
3128
|
+
checked: guard.publishBlocked === true,
|
|
3129
|
+
onChange: (v) => saveGuard({ publishBlocked: v })
|
|
3130
|
+
}),
|
|
3131
|
+
(0, import_react.createElement)(ToggleField, {
|
|
3132
|
+
label: "gitProtection.enabled",
|
|
3133
|
+
caption: "Protect pushes to protected branches.",
|
|
3134
|
+
checked: guard.gitProtection?.enabled === true,
|
|
3135
|
+
onChange: (v) => saveGuard({ gitProtection: { enabled: v, branches: guard.gitProtection?.branches ?? ["master", "main"] } })
|
|
3136
|
+
}),
|
|
3137
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "gitProtection.branches (comma separated)"),
|
|
3138
|
+
(0, import_react.createElement)("input", {
|
|
3139
|
+
style: inputStyle,
|
|
3140
|
+
value: (guard.gitProtection?.branches ?? ["master", "main"]).join(", "),
|
|
3141
|
+
placeholder: "master, main",
|
|
3142
|
+
onChange: (e) => {
|
|
3143
|
+
const branches = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3144
|
+
saveGuard({ gitProtection: { enabled: guard.gitProtection?.enabled ?? true, branches } });
|
|
3145
|
+
}
|
|
3146
|
+
}),
|
|
3147
|
+
(0, import_react.createElement)(ToggleField, {
|
|
3148
|
+
label: "cwdContainment",
|
|
3149
|
+
caption: "Contain file operations to the session cwd.",
|
|
3150
|
+
checked: guard.cwdContainment === true,
|
|
3151
|
+
onChange: (v) => saveGuard({ cwdContainment: v })
|
|
3152
|
+
}),
|
|
3153
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "credentialPaths (comma separated)"),
|
|
3154
|
+
(0, import_react.createElement)("input", {
|
|
3155
|
+
style: inputStyle,
|
|
3156
|
+
value: (guard.credentialPaths ?? []).join(", "),
|
|
3157
|
+
placeholder: "~/.config/credentials.yaml, ~/.config/cloudflared",
|
|
3158
|
+
onChange: (e) => {
|
|
3159
|
+
const credentialPaths = e.target.value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
3160
|
+
saveGuard({ credentialPaths });
|
|
3161
|
+
}
|
|
3162
|
+
})
|
|
3163
|
+
),
|
|
3164
|
+
activeTab === "blacklist" && (0, import_react.createElement)(
|
|
3165
|
+
"div",
|
|
3166
|
+
{ "data-tab": "blacklist" },
|
|
3167
|
+
(0, import_react.createElement)("p", { style: captionStyle }, "One pattern per line. These are blocked from being committed or published."),
|
|
3168
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "patterns (one per line)"),
|
|
3169
|
+
(0, import_react.createElement)("textarea", {
|
|
3170
|
+
style: textareaStyle,
|
|
3171
|
+
value: patternsText,
|
|
3172
|
+
placeholder: "example-project\nacme-shop",
|
|
3173
|
+
onChange: (e) => setPatternsText(e.target.value),
|
|
3174
|
+
onBlur: (e) => commitBlacklistPatterns(e.target.value)
|
|
3175
|
+
}),
|
|
3176
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "placeholders JSON"),
|
|
3177
|
+
(0, import_react.createElement)("textarea", {
|
|
3178
|
+
style: { ...textareaStyle, height: 90 },
|
|
3179
|
+
value: placeholdersText,
|
|
3180
|
+
placeholder: '{"example-project":"my-project"}',
|
|
3181
|
+
onChange: (e) => setPlaceholdersText(e.target.value),
|
|
3182
|
+
onBlur: () => commitPlaceholders()
|
|
3183
|
+
}),
|
|
3184
|
+
(0, import_react.createElement)("p", { style: captionStyle }, "Map blocked patterns to their placeholder suggestions."),
|
|
3185
|
+
(0, import_react.createElement)("button", { type: "button", style: { ...secondaryButtonStyle, marginTop: 8 }, onClick: () => {
|
|
3186
|
+
commitBlacklistPatterns(patternsText);
|
|
3187
|
+
commitPlaceholders();
|
|
3188
|
+
} }, "Save Blacklist")
|
|
3189
|
+
),
|
|
3190
|
+
activeTab === "supervisor" && (0, import_react.createElement)(
|
|
3191
|
+
"div",
|
|
3192
|
+
{ "data-tab": "supervisor" },
|
|
3193
|
+
(0, import_react.createElement)("p", { style: captionStyle }, "Background daemon that auto-resumes crashed sessions."),
|
|
3194
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "intervalMs"),
|
|
3195
|
+
(0, import_react.createElement)("input", {
|
|
3196
|
+
type: "number",
|
|
3197
|
+
style: inputStyle,
|
|
3198
|
+
value: supervisorCfg.intervalMs ?? "",
|
|
3199
|
+
placeholder: "5000",
|
|
3200
|
+
onChange: (e) => {
|
|
3201
|
+
const v = e.target.value === "" ? void 0 : Number(e.target.value);
|
|
3202
|
+
saveSupervisorCfg({ intervalMs: v });
|
|
3203
|
+
}
|
|
3204
|
+
}),
|
|
3205
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "downThreshold"),
|
|
3206
|
+
(0, import_react.createElement)("input", {
|
|
3207
|
+
type: "number",
|
|
3208
|
+
style: inputStyle,
|
|
3209
|
+
value: supervisorCfg.downThreshold ?? "",
|
|
3210
|
+
placeholder: "3",
|
|
3211
|
+
onChange: (e) => {
|
|
3212
|
+
const v = e.target.value === "" ? void 0 : Number(e.target.value);
|
|
3213
|
+
saveSupervisorCfg({ downThreshold: v });
|
|
3214
|
+
}
|
|
3215
|
+
}),
|
|
3216
|
+
(0, import_react.createElement)(ToggleField, {
|
|
3217
|
+
label: "autoResumeEnabled",
|
|
3218
|
+
caption: "Automatically resume down sessions.",
|
|
3219
|
+
checked: supervisorCfg.autoResumeEnabled === true,
|
|
3220
|
+
onChange: (v) => saveSupervisorCfg({ autoResumeEnabled: v })
|
|
3221
|
+
})
|
|
3222
|
+
),
|
|
3223
|
+
activeTab === "notifier" && (0, import_react.createElement)(
|
|
3224
|
+
"div",
|
|
3225
|
+
{ "data-tab": "notifier" },
|
|
3226
|
+
(0, import_react.createElement)("p", { style: captionStyle }, "Telegram notifications for Maestro events."),
|
|
3227
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "telegram.botToken"),
|
|
3228
|
+
(0, import_react.createElement)("input", {
|
|
3229
|
+
type: "password",
|
|
3230
|
+
autoComplete: "off",
|
|
3231
|
+
style: inputStyle,
|
|
3232
|
+
value: notifierCfg.telegram?.botToken ?? "",
|
|
3233
|
+
placeholder: "123456:ABC-DEF...",
|
|
3234
|
+
onChange: (e) => saveNotifierCfg({ telegram: { botToken: e.target.value } })
|
|
3235
|
+
}),
|
|
3236
|
+
(0, import_react.createElement)("label", { style: fieldLabelStyle }, "telegram.chatId"),
|
|
3237
|
+
(0, import_react.createElement)("input", {
|
|
3238
|
+
style: inputStyle,
|
|
3239
|
+
value: notifierCfg.telegram?.chatId ?? "",
|
|
3240
|
+
placeholder: "-1001234567890",
|
|
3241
|
+
onChange: (e) => saveNotifierCfg({ telegram: { chatId: e.target.value } })
|
|
3242
|
+
}),
|
|
3243
|
+
(0, import_react.createElement)(ToggleField, {
|
|
3244
|
+
label: "telegram.reviewNotifications",
|
|
3245
|
+
caption: "Also notify about finished reviews.",
|
|
3246
|
+
checked: notifierCfg.telegram?.reviewNotifications === true || notifierCfg.policy?.reviewNotifications === true,
|
|
3247
|
+
onChange: (v) => saveNotifierCfg({ telegram: { reviewNotifications: v } })
|
|
3248
|
+
})
|
|
3249
|
+
)
|
|
3250
|
+
),
|
|
2939
3251
|
error && (0, import_react.createElement)("p", { style: errorStyle }, error)
|
|
2940
3252
|
);
|
|
2941
3253
|
}
|
|
@@ -2979,7 +3291,7 @@ function registerSettingsNavIcon(label, root) {
|
|
|
2979
3291
|
// src/client/index.tsx
|
|
2980
3292
|
var SETTINGS_NAV_CSS2 = `
|
|
2981
3293
|
|
|
2982
|
-
/* maestro: replace the settings-nav fallback gear with the
|
|
3294
|
+
/* maestro: replace the settings-nav fallback gear with the Maestro M-logo glyph */
|
|
2983
3295
|
[${SETTINGS_NAV_MARKER}] > svg:first-child {
|
|
2984
3296
|
display: none;
|
|
2985
3297
|
}
|
|
@@ -2990,10 +3302,11 @@ var SETTINGS_NAV_CSS2 = `
|
|
|
2990
3302
|
width: 16px;
|
|
2991
3303
|
height: 16px;
|
|
2992
3304
|
background: currentColor;
|
|
2993
|
-
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='
|
|
2994
|
-
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='
|
|
3305
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 11 L5 4 L8 9 L11 4 L14 11'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
3306
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 11 L5 4 L8 9 L11 4 L14 11'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
2995
3307
|
}
|
|
2996
3308
|
`;
|
|
3309
|
+
var inject = ["slots", "connection"];
|
|
2997
3310
|
function installNavIconStyle() {
|
|
2998
3311
|
const tag = document.createElement("style");
|
|
2999
3312
|
tag.dataset.plugin = "@ddtcorex/dsh-maestro-config";
|
|
@@ -3012,12 +3325,17 @@ function apply(ctx) {
|
|
|
3012
3325
|
if (!connection?.rpc?.call) return Promise.reject(new Error("RPC not available"));
|
|
3013
3326
|
return connection.rpc.call(MAESTRO_RPC_CHANNEL, endpoint, payload, signal);
|
|
3014
3327
|
};
|
|
3328
|
+
const configRpcCall = (endpoint, payload, signal) => {
|
|
3329
|
+
const connection = ctx.get?.("connection");
|
|
3330
|
+
if (!connection?.rpc?.call) return Promise.reject(new Error("RPC not available"));
|
|
3331
|
+
return connection.rpc.call("/dsh-maestro-config", endpoint, payload, signal);
|
|
3332
|
+
};
|
|
3015
3333
|
ctx.effect(() => registerSettingsNavIcon(() => "Maestro"), "maestro: settings nav icon");
|
|
3016
3334
|
ctx.effect(installNavIconStyle, "maestro: settings nav css");
|
|
3017
3335
|
slots.inject(
|
|
3018
3336
|
"settings.section",
|
|
3019
3337
|
() => slots.register(
|
|
3020
|
-
{ name: "settings.section", id: "maestro", order: 25, label: () => "Maestro", inject: () => ({ rpcCall }) },
|
|
3338
|
+
{ name: "settings.section", id: "maestro", order: 25, label: () => "Maestro", inject: () => ({ rpcCall, configRpcCall }) },
|
|
3021
3339
|
MaestroSettingsTab
|
|
3022
3340
|
)
|
|
3023
3341
|
);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
type RpcResult<T> = {
|
|
3
|
+
ok: true;
|
|
4
|
+
value: T;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
error: {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
details: object;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
2
13
|
import { type MaestroConfigService } from './service.ts';
|
|
3
14
|
export declare const name = "maestro-config";
|
|
4
15
|
export declare const inject: string[];
|
|
5
16
|
declare module '@deepseek-ai/cordis' {
|
|
6
17
|
interface Context {
|
|
7
18
|
maestroConfig: MaestroConfigService;
|
|
19
|
+
connection: {
|
|
20
|
+
rpc: {
|
|
21
|
+
handle: (channel: string, handler: (endpoint: string, payload: unknown) => Promise<RpcResult<unknown>>, opts?: unknown) => () => void;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
8
24
|
}
|
|
9
25
|
}
|
|
10
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Publish maestroConfig over the shared store + loopback RPC for clients.
|
|
28
|
+
* Exposes guard/guardBlacklist/supervisor/notifier domains (Task 1 validators)
|
|
29
|
+
* via generic get/set — validation is delegated to the lib's domain validators.
|
|
30
|
+
* Host also handles '/dsh-maestro-config/get' and '/dsh-maestro-config/set'
|
|
31
|
+
* style calls through the single channel with endpoint dispatch.
|
|
32
|
+
*/
|
|
11
33
|
export declare function apply(ctx: Context): void;
|
|
34
|
+
export {};
|
|
12
35
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,KAAK,SAAS,CAAC,CAAC,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAA;AAErH,OAAO,EAA8B,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAEpF,eAAO,MAAM,IAAI,mBAAmB,CAAA;AACpC,eAAO,MAAM,MAAM,UAAiB,CAAA;AAIpC,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,aAAa,EAAE,oBAAoB,CAAA;QACnC,UAAU,EAAE;YAAE,GAAG,EAAE;gBAAE,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,MAAM,IAAI,CAAA;aAAE,CAAA;SAAE,CAAA;KAC/J;CACF;AAmBD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAwBxC"}
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,13 @@ function fail(message) {
|
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Publish maestroConfig over the shared store + loopback RPC for clients.
|
|
22
|
+
* Exposes guard/guardBlacklist/supervisor/notifier domains (Task 1 validators)
|
|
23
|
+
* via generic get/set — validation is delegated to the lib's domain validators.
|
|
24
|
+
* Host also handles '/dsh-maestro-config/get' and '/dsh-maestro-config/set'
|
|
25
|
+
* style calls through the single channel with endpoint dispatch.
|
|
26
|
+
*/
|
|
21
27
|
export function apply(ctx) {
|
|
22
28
|
const svc = createMaestroConfigService();
|
|
23
29
|
ctx.provide('maestroConfig', svc);
|
|
@@ -29,6 +35,7 @@ export function apply(ctx) {
|
|
|
29
35
|
if (endpoint === 'get') {
|
|
30
36
|
if (typeof body.domain !== 'string')
|
|
31
37
|
return fail('domain (string) is required');
|
|
38
|
+
// guard / guardBlacklist / supervisor / notifier are all valid domains here
|
|
32
39
|
return ok(await svc.get(body.domain));
|
|
33
40
|
}
|
|
34
41
|
if (endpoint === 'set') {
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/host/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,0BAA0B,EAA6B,MAAM,cAAc,CAAA;AAEpF,MAAM,CAAC,MAAM,IAAI,GAAG,gBAAgB,CAAA;AACpC,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,CAAA;AAEpC,MAAM,WAAW,GAAG,qBAAqB,CAAA;AASzC,SAAS,EAAE,CAAI,KAAQ;IACrB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AAC5B,CAAC;AAED,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO;QACL,EAAE,EAAE,KAAK;QACT,KAAK,EAAE;YACL,IAAI,EAAE,aAAa;YACnB,OAAO;YACP,sEAAsE;YACtE,qEAAqE;YACrE,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAuC;SACxE;KACF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,GAAY;IAChC,MAAM,GAAG,GAAG,0BAA0B,EAAE,CAAA;IACxC,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;IACjC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CACd,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAgB,EAA+B,EAAE;QAC/G,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAwC,CAAA;QACnE,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACxB,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;QACjD,CAAC;QACD,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;YACvB,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC,6BAA6B,CAAC,CAAA;YAC/E,4EAA4E;YAC5E,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;QACvC,CAAC;QACD,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;YACvB,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC7F,OAAO,IAAI,CAAC,iDAAiD,CAAC,CAAA;YAChE,CAAC;YACD,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;YACtC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAA;QACjB,CAAC;QACD,OAAO,IAAI,CAAC,qBAAqB,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACtD,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAC9B,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type RpcCall = (endpoint: string, payload?: unknown, signal?: AbortSignal) => Promise<unknown>;
|
|
2
|
+
export declare function Settings({ configRpcCall }: {
|
|
3
|
+
configRpcCall: RpcCall;
|
|
4
|
+
}): import("react").DetailedReactHTMLElement<{
|
|
5
|
+
'data-maestro-guard-settings': string;
|
|
6
|
+
style: {
|
|
7
|
+
maxWidth: number;
|
|
8
|
+
};
|
|
9
|
+
}, HTMLElement>;
|
|
10
|
+
export default Settings;
|
|
11
|
+
//# sourceMappingURL=Settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../../src/client/Settings.tsx"],"names":[],"mappings":"AAIA,KAAK,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAiF9F,wBAAgB,QAAQ,CAAC,EAAE,aAAa,EAAE,EAAE;IAAE,aAAa,EAAE,OAAO,CAAA;CAAE;;;;;gBAiRrE;AAGD,eAAe,QAAQ,CAAA"}
|
|
@@ -2,6 +2,7 @@ interface ClientCtx {
|
|
|
2
2
|
get?(name: string): unknown;
|
|
3
3
|
effect(fn: () => () => void, label?: string): unknown;
|
|
4
4
|
}
|
|
5
|
+
export declare const inject: readonly ["slots", "connection"];
|
|
5
6
|
export declare function apply(ctx: ClientCtx): void;
|
|
6
7
|
export {};
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.tsx"],"names":[],"mappings":"AAwCA,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;AAED,eAAO,MAAM,MAAM,kCAAmC,CAAA;AAatD,wBAAgB,KAAK,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CA+B1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maestro-card.d.ts","sourceRoot":"","sources":["../../../src/client/maestro-card.jsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"maestro-card.d.ts","sourceRoot":"","sources":["../../../src/client/maestro-card.jsx"],"names":[],"mappings":"AAweA;;;;;;;;gBA0dC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddtcorex/dsh-maestro-config",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
@@ -33,16 +34,14 @@
|
|
|
33
34
|
},
|
|
34
35
|
"peerDependencies": {
|
|
35
36
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
36
|
-
"@deepseek-ai/dsh-client-connection": "0.1.
|
|
37
|
-
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.8"
|
|
37
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@ddtcorex/dsh-maestro-config-lib": "^0.1.
|
|
40
|
+
"@ddtcorex/dsh-maestro-config-lib": "^0.1.2"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
44
|
-
"@deepseek-ai/dsh-client-connection": "0.1.
|
|
45
|
-
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.8",
|
|
44
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
|
|
46
45
|
"@types/node": "^22.0.0",
|
|
47
46
|
"@types/qrcode": "^1.5.6",
|
|
48
47
|
"@types/react": "~18.3.1",
|
package/src/client/index.tsx
CHANGED
|
@@ -7,12 +7,13 @@ import { registerSettingsNavIcon, SETTINGS_NAV_MARKER } from './settings-nav-ico
|
|
|
7
7
|
* DSH 0.1.x gives external settings sections a generic gear and exposes no
|
|
8
8
|
* icon field in the settings.section contract (mirrors dsh-better-sidebar):
|
|
9
9
|
* the marker only claims this plugin's localized row and this CSS paints the
|
|
10
|
-
*
|
|
11
|
-
* hover/active colors at the shell's 16px icon rhythm.
|
|
10
|
+
* Maestro M-logo glyph as a currentColor mask so it follows native nav
|
|
11
|
+
* hover/active colors at the shell's 16px icon rhythm. The path matches the
|
|
12
|
+
* sidebar MaestroTrigger (trigger.tsx#MaestroLogo) — M2 11 L5 4 L8 9 L11 4 L14 11.
|
|
12
13
|
*/
|
|
13
14
|
const SETTINGS_NAV_CSS = `
|
|
14
15
|
|
|
15
|
-
/* maestro: replace the settings-nav fallback gear with the
|
|
16
|
+
/* maestro: replace the settings-nav fallback gear with the Maestro M-logo glyph */
|
|
16
17
|
[${SETTINGS_NAV_MARKER}] > svg:first-child {
|
|
17
18
|
display: none;
|
|
18
19
|
}
|
|
@@ -23,8 +24,8 @@ const SETTINGS_NAV_CSS = `
|
|
|
23
24
|
width: 16px;
|
|
24
25
|
height: 16px;
|
|
25
26
|
background: currentColor;
|
|
26
|
-
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='
|
|
27
|
-
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='
|
|
27
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 11 L5 4 L8 9 L11 4 L14 11'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
28
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2 11 L5 4 L8 9 L11 4 L14 11'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
28
29
|
}
|
|
29
30
|
`
|
|
30
31
|
|
|
@@ -42,6 +43,8 @@ interface ClientCtx {
|
|
|
42
43
|
effect(fn: () => () => void, label?: string): unknown
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
export const inject = ['slots', 'connection'] as const
|
|
47
|
+
|
|
45
48
|
function installNavIconStyle(): () => void {
|
|
46
49
|
const tag = document.createElement('style')
|
|
47
50
|
tag.dataset.plugin = '@ddtcorex/dsh-maestro-config'
|
|
@@ -65,6 +68,14 @@ export function apply(ctx: ClientCtx): void {
|
|
|
65
68
|
if (!connection?.rpc?.call) return Promise.reject(new Error('RPC not available'))
|
|
66
69
|
return connection.rpc.call(MAESTRO_RPC_CHANNEL, endpoint, payload, signal)
|
|
67
70
|
}
|
|
71
|
+
// Generic config RPC for supervisor (independent of review — works when review not installed)
|
|
72
|
+
const configRpcCall: RpcCall = (endpoint, payload, signal) => {
|
|
73
|
+
const connection = ctx.get?.('connection') as
|
|
74
|
+
| { rpc: { call(ch: string, ep: string, p?: unknown, s?: AbortSignal): Promise<unknown> } }
|
|
75
|
+
| undefined
|
|
76
|
+
if (!connection?.rpc?.call) return Promise.reject(new Error('RPC not available'))
|
|
77
|
+
return connection.rpc.call('/dsh-maestro-config', endpoint, payload, signal)
|
|
78
|
+
}
|
|
68
79
|
|
|
69
80
|
// Reversible effects: nav-row marker observer + owned style tag.
|
|
70
81
|
ctx.effect(() => registerSettingsNavIcon(() => 'Maestro'), 'maestro: settings nav icon')
|
|
@@ -72,8 +83,8 @@ export function apply(ctx: ClientCtx): void {
|
|
|
72
83
|
|
|
73
84
|
slots.inject('settings.section', () =>
|
|
74
85
|
slots.register(
|
|
75
|
-
{ name: 'settings.section', id: 'maestro', order: 25, label: () => 'Maestro', inject: () => ({ rpcCall }) },
|
|
76
|
-
MaestroSettingsTab,
|
|
86
|
+
{ name: 'settings.section', id: 'maestro', order: 25, label: () => 'Maestro', inject: () => ({ rpcCall, configRpcCall }) },
|
|
87
|
+
MaestroSettingsTab as unknown as (props: { rpcCall: RpcCall }) => unknown,
|
|
77
88
|
),
|
|
78
89
|
)
|
|
79
90
|
}
|
|
@@ -86,6 +86,32 @@ const codeStyle = {
|
|
|
86
86
|
wordBreak: 'break-all',
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
const textareaStyle = {
|
|
90
|
+
...inputStyle,
|
|
91
|
+
height: 120,
|
|
92
|
+
padding: '8px 12px',
|
|
93
|
+
resize: 'vertical',
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const tabBarStyle = {
|
|
97
|
+
display: 'flex',
|
|
98
|
+
gap: 8,
|
|
99
|
+
borderBottom: '1px solid var(--dsw-alias-border-l2)',
|
|
100
|
+
marginBottom: 16,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const tabButtonStyle = (active) => ({
|
|
104
|
+
padding: '8px 14px',
|
|
105
|
+
border: 'none',
|
|
106
|
+
borderBottom: active ? '2px solid var(--dsw-alias-button-primary-fill)' : '2px solid transparent',
|
|
107
|
+
background: 'transparent',
|
|
108
|
+
color: active ? 'var(--dsw-alias-label-primary)' : 'var(--dsw-alias-label-secondary)',
|
|
109
|
+
font: 'inherit',
|
|
110
|
+
fontSize: 13,
|
|
111
|
+
fontWeight: active ? 600 : 400,
|
|
112
|
+
cursor: 'pointer',
|
|
113
|
+
})
|
|
114
|
+
|
|
89
115
|
/** QR code centered in a light tile with an even scanner-friendly quiet zone. */
|
|
90
116
|
function QrImage({ url, size = 104 }) {
|
|
91
117
|
const [dataUrl, setDataUrl] = useState(null)
|
|
@@ -147,10 +173,35 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
147
173
|
document.addEventListener('keydown', onKey)
|
|
148
174
|
return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey) }
|
|
149
175
|
}, [open])
|
|
176
|
+
const getModelId = (m) => typeof m === 'string' ? m : m.id
|
|
177
|
+
const getModelName = (m) => typeof m === 'string' ? m : (m.name ?? m.id)
|
|
178
|
+
const selectedModelInfo = (() => {
|
|
179
|
+
if (!selectedProvider || !value?.model) return null
|
|
180
|
+
const raw = (providerGroup?.models ?? []).find(mm => getModelId(mm) === value.model)
|
|
181
|
+
if (raw === undefined) return null
|
|
182
|
+
if (typeof raw === 'string') return { id: raw, supportsReasoning: false, reasoningEfforts: [] }
|
|
183
|
+
return raw
|
|
184
|
+
})()
|
|
185
|
+
const supportsReasoning = (() => {
|
|
186
|
+
if (!selectedModelInfo) return false
|
|
187
|
+
if (typeof selectedModelInfo.supportsReasoning === 'boolean') return selectedModelInfo.supportsReasoning
|
|
188
|
+
const efforts = selectedModelInfo.reasoningEfforts ?? selectedModelInfo.reasoning?.efforts?.map(e => e.id) ?? []
|
|
189
|
+
return efforts.filter(e => e !== 'off').length > 0
|
|
190
|
+
})()
|
|
191
|
+
const availableEfforts = (() => {
|
|
192
|
+
if (!supportsReasoning) return []
|
|
193
|
+
const efforts = selectedModelInfo?.reasoningEfforts ?? selectedModelInfo?.reasoning?.efforts?.map(e => e.id) ?? []
|
|
194
|
+
const filtered = efforts.filter(e => e !== 'off' && e !== '')
|
|
195
|
+
if (filtered.length > 0) return filtered
|
|
196
|
+
return ['low', 'medium', 'high']
|
|
197
|
+
})()
|
|
198
|
+
const warning = selectedEffort !== '' && !supportsReasoning && selectedModelInfo !== null
|
|
199
|
+
? `⚠️ This model does not support reasoning effort "${selectedEffort}" — reviews will fail. Clear effort or choose a reasoning-capable model.`
|
|
200
|
+
: null
|
|
150
201
|
const update = (field, newVal) => {
|
|
151
202
|
if (newVal === '' && field === 'provider') { onChange(null); setOpen(false); setPane('root'); return }
|
|
152
203
|
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 =
|
|
204
|
+
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
205
|
else if (field === 'model') { next.model = newVal }
|
|
155
206
|
else if (field === 'reasoningEffort') { if (newVal === '') delete next.reasoningEffort; else next.reasoningEffort = newVal }
|
|
156
207
|
if (!next.provider || !next.model) { onChange(null) } else { onChange(next) }
|
|
@@ -235,6 +286,7 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
235
286
|
),
|
|
236
287
|
value && h('p', { style: { ...captionStyle, margin: '8px 4px 2px' } }, `Selected: ${value.provider} / ${value.model}${value.reasoningEffort ? ` (${value.reasoningEffort})` : ''}`),
|
|
237
288
|
!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})` : ''}`),
|
|
289
|
+
warning && h('p', { style: { ...captionStyle, margin: '4px 4px 2px', color: 'var(--dsw-alias-state-error-primary)' } }, warning),
|
|
238
290
|
),
|
|
239
291
|
pane === 'model' && h('div', null,
|
|
240
292
|
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 +299,31 @@ function ReviewModelSelector({ value, catalog, fallbackValue, fallbackLabel, onC
|
|
|
247
299
|
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
300
|
ms.length === 0 ? h('p', { style: { ...captionStyle, padding: '2px 10px 2px 28px' } }, 'No models') :
|
|
249
301
|
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 =>
|
|
302
|
+
ms.map(m => {
|
|
303
|
+
const mid = getModelId(m)
|
|
304
|
+
const mname = getModelName(m)
|
|
305
|
+
const active = value?.provider === p && value?.model === mid
|
|
306
|
+
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))
|
|
307
|
+
})),
|
|
251
308
|
)
|
|
252
309
|
}),
|
|
253
310
|
),
|
|
254
311
|
),
|
|
255
312
|
pane === 'effort' && h('div', null,
|
|
256
313
|
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('
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{ id: '
|
|
262
|
-
|
|
263
|
-
|
|
314
|
+
selectedModelInfo === null ? h('p', { style: { ...captionStyle, margin: '8px 4px 2px' } }, 'Select a model first to configure effort.') :
|
|
315
|
+
!supportsReasoning ? h('div', null,
|
|
316
|
+
h('p', { style: { ...captionStyle, margin: '8px 4px 6px' } }, 'This model does not support reasoning effort — using provider default'),
|
|
317
|
+
h('div', { style: { marginTop: 4 } },
|
|
318
|
+
[{ 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))),
|
|
319
|
+
),
|
|
320
|
+
warning && h('p', { style: { ...captionStyle, margin: '8px 4px 2px', color: 'var(--dsw-alias-state-error-primary)' } }, warning),
|
|
321
|
+
) :
|
|
322
|
+
h('div', null,
|
|
323
|
+
h('div', { style: { marginTop: 4 } },
|
|
324
|
+
[{ 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))),
|
|
325
|
+
),
|
|
326
|
+
warning && h('p', { style: { ...captionStyle, margin: '8px 4px 2px', color: 'var(--dsw-alias-state-error-primary)' } }, warning),
|
|
264
327
|
),
|
|
265
328
|
),
|
|
266
329
|
),
|
|
@@ -336,26 +399,6 @@ function ToggleField({ label, caption, checked, onChange }) {
|
|
|
336
399
|
)
|
|
337
400
|
}
|
|
338
401
|
|
|
339
|
-
/** Newest-first list of recorded review runs from the host's reviews.json. */
|
|
340
|
-
function ReviewHistoryPanel({ rpcCall }) {
|
|
341
|
-
const [entries, setEntries] = useState(null)
|
|
342
|
-
useEffect(() => {
|
|
343
|
-
rpcCall(MAESTRO_ENDPOINTS.reviewsList, {})
|
|
344
|
-
.then(res => { if (res?.ok) setEntries(res.value ?? []) })
|
|
345
|
-
.catch(() => setEntries([]))
|
|
346
|
-
}, [])
|
|
347
|
-
if (entries === null) return h('p', { style: captionStyle }, 'Loading review history…')
|
|
348
|
-
if (entries.length === 0) return h('p', { style: captionStyle }, 'No reviews recorded yet.')
|
|
349
|
-
const icon = entry => entry.status === 'completed' ? '✅' : entry.status === 'failed' ? '⚠️' : '👀'
|
|
350
|
-
return h('ul', { style: { listStyle: 'none', margin: 0, padding: 0 } },
|
|
351
|
-
entries.map(entry => h('li', { key: entry.id, style: { padding: '6px 0', borderBottom: '1px solid var(--dsw-alias-separator-default, #333)', fontSize: 13 } },
|
|
352
|
-
h('span', null, `${icon(entry)} ${entry.projectPath} !${entry.mrIid} · ${entry.mode}${entry.trigger !== 'mention' ? ` · ${entry.trigger}` : ''}`),
|
|
353
|
-
h('div', { style: captionStyle },
|
|
354
|
-
`${new Date(entry.startedAt).toLocaleString()}${entry.summary ? ` — ${entry.summary}` : ''}${entry.error ? ` — ${entry.error}` : ''}`),
|
|
355
|
-
)),
|
|
356
|
-
)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
402
|
/** One selectable LAN address chip + the QR of the currently selected URL. */
|
|
360
403
|
function LanAccess({ proxyStatus, lanPin }) {
|
|
361
404
|
const urls = proxyStatus?.lanUrls ?? []
|
|
@@ -443,7 +486,7 @@ function PublicAccess({ status, pin, showPin, onRevealPin, onHidePin, onRotatePi
|
|
|
443
486
|
)
|
|
444
487
|
}
|
|
445
488
|
|
|
446
|
-
export function MaestroSettingsTab({ rpcCall }) {
|
|
489
|
+
export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
447
490
|
const [status, setStatus] = useState(null)
|
|
448
491
|
const [proxyStatus, setProxyStatus] = useState(null)
|
|
449
492
|
const [config, setConfig] = useState({ tunnelMode: 'quick', projectMappings: [] })
|
|
@@ -455,6 +498,13 @@ export function MaestroSettingsTab({ rpcCall }) {
|
|
|
455
498
|
const [lanPinEnabled, setLanPinEnabled] = useState(false)
|
|
456
499
|
const [lanPin, setLanPin] = useState(null)
|
|
457
500
|
const [showLanPin, setShowLanPin] = useState(false)
|
|
501
|
+
// Task 3: Guard/Blacklist/Supervisor/Notifier tabs state
|
|
502
|
+
const [activeTab, setActiveTab] = useState('guard')
|
|
503
|
+
const [guard, setGuard] = useState({})
|
|
504
|
+
const [patternsText, setPatternsText] = useState('')
|
|
505
|
+
const [placeholdersText, setPlaceholdersText] = useState('')
|
|
506
|
+
const [supervisorCfg, setSupervisorCfg] = useState({})
|
|
507
|
+
const [notifierCfg, setNotifierCfg] = useState({})
|
|
458
508
|
|
|
459
509
|
const call = async (endpoint, payload) => {
|
|
460
510
|
const res = await rpcCall(endpoint, payload)
|
|
@@ -462,6 +512,60 @@ export function MaestroSettingsTab({ rpcCall }) {
|
|
|
462
512
|
return res.value
|
|
463
513
|
}
|
|
464
514
|
|
|
515
|
+
// Helpers for guard/supervisor/notifier domains via generic config RPC (Task 3)
|
|
516
|
+
const unwrap = (res) => {
|
|
517
|
+
if (res && typeof res === 'object' && 'ok' in res) {
|
|
518
|
+
if (res.ok) return res.value
|
|
519
|
+
throw new Error(res.error?.message ?? 'RPC failed')
|
|
520
|
+
}
|
|
521
|
+
return res
|
|
522
|
+
}
|
|
523
|
+
const cfgGet = async (domain) => {
|
|
524
|
+
if (!configRpcCall) throw new Error('config RPC not available')
|
|
525
|
+
const res = await configRpcCall('get', { domain })
|
|
526
|
+
return unwrap(res)
|
|
527
|
+
}
|
|
528
|
+
const cfgSet = async (domain, patch) => {
|
|
529
|
+
if (!configRpcCall) throw new Error('config RPC not available')
|
|
530
|
+
const res = await configRpcCall('set', { domain, patch })
|
|
531
|
+
return unwrap(res)
|
|
532
|
+
}
|
|
533
|
+
const saveGuard = async (patch) => {
|
|
534
|
+
setError(null)
|
|
535
|
+
const next = { ...guard, ...patch }
|
|
536
|
+
if (patch.gitProtection && guard.gitProtection) next.gitProtection = { ...guard.gitProtection, ...patch.gitProtection }
|
|
537
|
+
setGuard(next)
|
|
538
|
+
try { await cfgSet('guard', patch) } catch (e) { setError(e.message ?? String(e)) }
|
|
539
|
+
}
|
|
540
|
+
const commitBlacklistPatterns = async (text) => {
|
|
541
|
+
const patterns = text.split('\n').map(s => s.trim()).filter(Boolean)
|
|
542
|
+
setError(null)
|
|
543
|
+
try { await cfgSet('guardBlacklist', { patterns }) } catch (e) { setError(e.message ?? String(e)) }
|
|
544
|
+
}
|
|
545
|
+
const commitPlaceholders = async () => {
|
|
546
|
+
setError(null)
|
|
547
|
+
let obj = {}
|
|
548
|
+
try { obj = placeholdersText.trim() ? JSON.parse(placeholdersText) : {}; if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) throw new Error('placeholders must be JSON object') } catch (e) { setError(`placeholders JSON invalid: ${e.message ?? String(e)}`); return }
|
|
549
|
+
try { await cfgSet('guardBlacklist', { placeholders: obj }) } catch (e) { setError(e.message ?? String(e)) }
|
|
550
|
+
}
|
|
551
|
+
const saveSupervisorCfg = async (patch) => {
|
|
552
|
+
setError(null)
|
|
553
|
+
setSupervisorCfg(prev => ({ ...prev, ...patch }))
|
|
554
|
+
try { await cfgSet('supervisor', patch) } catch (e) { setError(e.message ?? String(e)) }
|
|
555
|
+
}
|
|
556
|
+
const saveNotifierCfg = async (patch) => {
|
|
557
|
+
setError(null)
|
|
558
|
+
setNotifierCfg(prev => {
|
|
559
|
+
const next = { ...prev }
|
|
560
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
561
|
+
if (k === 'telegram' && typeof v === 'object' && v !== null) next.telegram = { ...(prev.telegram ?? {}), ...v }
|
|
562
|
+
else next[k] = v
|
|
563
|
+
}
|
|
564
|
+
return next
|
|
565
|
+
})
|
|
566
|
+
try { await cfgSet('notifier', patch) } catch (e) { setError(e.message ?? String(e)) }
|
|
567
|
+
}
|
|
568
|
+
|
|
465
569
|
const refresh = async () => {
|
|
466
570
|
try { setStatus(await call(MAESTRO_ENDPOINTS.status, {})) } catch { /* transient failure, ignore */ }
|
|
467
571
|
try { setProxyStatus(await call(MAESTRO_ENDPOINTS.proxyStatus, {})) } catch { /* proxy row may be starting */ }
|
|
@@ -474,6 +578,31 @@ export function MaestroSettingsTab({ rpcCall }) {
|
|
|
474
578
|
call(MAESTRO_ENDPOINTS.getConfig, {})
|
|
475
579
|
.then(saved => setConfig(prev => ({ ...prev, ...saved })))
|
|
476
580
|
.catch(() => { /* first run, no config saved yet — keep defaults */ })
|
|
581
|
+
// Supervisor model may be stored via generic config service when review not installed — try fallback
|
|
582
|
+
if (configRpcCall) {
|
|
583
|
+
configRpcCall('get', { domain: 'supervisor' })
|
|
584
|
+
.then(res => {
|
|
585
|
+
if (res?.ok && res.value?.model) {
|
|
586
|
+
setConfig(prev => ({ ...prev, supervisorModel: res.value.model }))
|
|
587
|
+
}
|
|
588
|
+
})
|
|
589
|
+
.catch(() => { /* supervisor domain not yet set or config service unavailable */ })
|
|
590
|
+
// Task 3: load Guard/Blacklist/Supervisor/Notifier domains
|
|
591
|
+
Promise.all([
|
|
592
|
+
cfgGet('guard').catch(() => ({})),
|
|
593
|
+
cfgGet('guardBlacklist').catch(() => ({ patterns: [], placeholders: {} })),
|
|
594
|
+
cfgGet('supervisor').catch(() => ({})),
|
|
595
|
+
cfgGet('notifier').catch(() => ({})),
|
|
596
|
+
]).then(([g, bl, sup, not]) => {
|
|
597
|
+
setGuard(g ?? {})
|
|
598
|
+
const pats = Array.isArray(bl?.patterns) ? bl.patterns : []
|
|
599
|
+
const ph = bl?.placeholders && typeof bl.placeholders === 'object' ? bl.placeholders : {}
|
|
600
|
+
setPatternsText(pats.join('\n'))
|
|
601
|
+
setPlaceholdersText(JSON.stringify(ph, null, 2))
|
|
602
|
+
setSupervisorCfg(sup ?? {})
|
|
603
|
+
setNotifierCfg(not ?? {})
|
|
604
|
+
}).catch(() => {})
|
|
605
|
+
}
|
|
477
606
|
call(MAESTRO_ENDPOINTS.lanPinStatus, {})
|
|
478
607
|
.then(value => { setLanPinEnabled(value.enabled); if (value.enabled) setLanPin(value.pin ?? null) })
|
|
479
608
|
.catch(() => { /* host without the LAN PIN endpoints — keep the row hidden */ })
|
|
@@ -559,6 +688,16 @@ export function MaestroSettingsTab({ rpcCall }) {
|
|
|
559
688
|
const saveField = async (field, value) => {
|
|
560
689
|
setError(null)
|
|
561
690
|
setConfig(prev => ({ ...prev, [field]: value }))
|
|
691
|
+
// Supervisor model can be saved via generic config service when review not installed (independent install)
|
|
692
|
+
if (field === 'supervisorModel' && configRpcCall) {
|
|
693
|
+
try {
|
|
694
|
+
const res = await configRpcCall('set', { domain: 'supervisor', patch: { model: value } })
|
|
695
|
+
if (res?.ok) return
|
|
696
|
+
// Fall through to review RPC if generic set fails
|
|
697
|
+
} catch (e) {
|
|
698
|
+
// Fall through to review RPC
|
|
699
|
+
}
|
|
700
|
+
}
|
|
562
701
|
try {
|
|
563
702
|
await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value })
|
|
564
703
|
} catch (err) {
|
|
@@ -663,7 +802,6 @@ export function MaestroSettingsTab({ rpcCall }) {
|
|
|
663
802
|
checked: config.autoRereviewOnPush,
|
|
664
803
|
onChange: checked => saveField('autoRereviewOnPush', checked),
|
|
665
804
|
}),
|
|
666
|
-
h(ReviewHistoryPanel, { rpcCall }),
|
|
667
805
|
),
|
|
668
806
|
|
|
669
807
|
h('div', { style: sectionStyle },
|
|
@@ -679,11 +817,147 @@ export function MaestroSettingsTab({ rpcCall }) {
|
|
|
679
817
|
}),
|
|
680
818
|
),
|
|
681
819
|
|
|
820
|
+
h('div', { style: sectionStyle },
|
|
821
|
+
h('h4', { style: headingStyle }, 'Supervisor LLM'),
|
|
822
|
+
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.'),
|
|
823
|
+
h(ReviewModelSelector, {
|
|
824
|
+
value: config.supervisorModel ?? null,
|
|
825
|
+
catalog,
|
|
826
|
+
fallbackValue: catalog?.current ?? null,
|
|
827
|
+
fallbackLabel: 'Use DSH default',
|
|
828
|
+
onChange: v => saveField('supervisorModel', v),
|
|
829
|
+
label: 'Supervisor model',
|
|
830
|
+
}),
|
|
831
|
+
),
|
|
832
|
+
|
|
682
833
|
h('div', { style: sectionStyle },
|
|
683
834
|
h('h4', { style: headingStyle }, 'Projects'),
|
|
684
835
|
h(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: mappings => saveField('projectMappings', mappings), catalog, globalReviewModel: config.reviewModel ?? null }),
|
|
685
836
|
),
|
|
686
837
|
|
|
838
|
+
// Task 3: Guard/Blacklist/Supervisor/Notifier tabs — data-driven over guard domains
|
|
839
|
+
h('div', { style: sectionStyle },
|
|
840
|
+
h('h4', { style: headingStyle }, 'Guard / Blacklist / Supervisor / Notifier'),
|
|
841
|
+
h('div', { style: tabBarStyle },
|
|
842
|
+
h('button', { type: 'button', style: tabButtonStyle(activeTab === 'guard'), onClick: () => setActiveTab('guard') }, 'Guard'),
|
|
843
|
+
h('button', { type: 'button', style: tabButtonStyle(activeTab === 'blacklist'), onClick: () => setActiveTab('blacklist') }, 'Blacklist'),
|
|
844
|
+
h('button', { type: 'button', style: tabButtonStyle(activeTab === 'supervisor'), onClick: () => setActiveTab('supervisor') }, 'Supervisor'),
|
|
845
|
+
h('button', { type: 'button', style: tabButtonStyle(activeTab === 'notifier'), onClick: () => setActiveTab('notifier') }, 'Notifier'),
|
|
846
|
+
),
|
|
847
|
+
activeTab === 'guard' && h('div', { 'data-tab': 'guard' },
|
|
848
|
+
h('p', { style: captionStyle }, 'Enforce publish block, git protection and cwd containment.'),
|
|
849
|
+
h(ToggleField, {
|
|
850
|
+
label: 'publishBlocked',
|
|
851
|
+
caption: 'Block publish-related commands when enabled.',
|
|
852
|
+
checked: guard.publishBlocked === true,
|
|
853
|
+
onChange: v => saveGuard({ publishBlocked: v }),
|
|
854
|
+
}),
|
|
855
|
+
h(ToggleField, {
|
|
856
|
+
label: 'gitProtection.enabled',
|
|
857
|
+
caption: 'Protect pushes to protected branches.',
|
|
858
|
+
checked: guard.gitProtection?.enabled === true,
|
|
859
|
+
onChange: v => saveGuard({ gitProtection: { enabled: v, branches: guard.gitProtection?.branches ?? ['master', 'main'] } }),
|
|
860
|
+
}),
|
|
861
|
+
h('label', { style: fieldLabelStyle }, 'gitProtection.branches (comma separated)'),
|
|
862
|
+
h('input', {
|
|
863
|
+
style: inputStyle,
|
|
864
|
+
value: (guard.gitProtection?.branches ?? ['master', 'main']).join(', '),
|
|
865
|
+
placeholder: 'master, main',
|
|
866
|
+
onChange: e => {
|
|
867
|
+
const branches = e.target.value.split(',').map(s => s.trim()).filter(Boolean)
|
|
868
|
+
saveGuard({ gitProtection: { enabled: guard.gitProtection?.enabled ?? true, branches } })
|
|
869
|
+
},
|
|
870
|
+
}),
|
|
871
|
+
h(ToggleField, {
|
|
872
|
+
label: 'cwdContainment',
|
|
873
|
+
caption: 'Contain file operations to the session cwd.',
|
|
874
|
+
checked: guard.cwdContainment === true,
|
|
875
|
+
onChange: v => saveGuard({ cwdContainment: v }),
|
|
876
|
+
}),
|
|
877
|
+
h('label', { style: fieldLabelStyle }, 'credentialPaths (comma separated)'),
|
|
878
|
+
h('input', {
|
|
879
|
+
style: inputStyle,
|
|
880
|
+
value: (guard.credentialPaths ?? []).join(', '),
|
|
881
|
+
placeholder: '~/.config/credentials.yaml, ~/.config/cloudflared',
|
|
882
|
+
onChange: e => {
|
|
883
|
+
const credentialPaths = e.target.value.split(',').map(s => s.trim()).filter(Boolean)
|
|
884
|
+
saveGuard({ credentialPaths })
|
|
885
|
+
},
|
|
886
|
+
}),
|
|
887
|
+
),
|
|
888
|
+
activeTab === 'blacklist' && h('div', { 'data-tab': 'blacklist' },
|
|
889
|
+
h('p', { style: captionStyle }, 'One pattern per line. These are blocked from being committed or published.'),
|
|
890
|
+
h('label', { style: fieldLabelStyle }, 'patterns (one per line)'),
|
|
891
|
+
h('textarea', {
|
|
892
|
+
style: textareaStyle,
|
|
893
|
+
value: patternsText,
|
|
894
|
+
placeholder: 'example-project\nacme-shop',
|
|
895
|
+
onChange: e => setPatternsText(e.target.value),
|
|
896
|
+
onBlur: e => commitBlacklistPatterns(e.target.value),
|
|
897
|
+
}),
|
|
898
|
+
h('label', { style: fieldLabelStyle }, 'placeholders JSON'),
|
|
899
|
+
h('textarea', {
|
|
900
|
+
style: { ...textareaStyle, height: 90 },
|
|
901
|
+
value: placeholdersText,
|
|
902
|
+
placeholder: '{"example-project":"my-project"}',
|
|
903
|
+
onChange: e => setPlaceholdersText(e.target.value),
|
|
904
|
+
onBlur: () => commitPlaceholders(),
|
|
905
|
+
}),
|
|
906
|
+
h('p', { style: captionStyle }, 'Map blocked patterns to their placeholder suggestions.'),
|
|
907
|
+
h('button', { type: 'button', style: { ...secondaryButtonStyle, marginTop: 8 }, onClick: () => { commitBlacklistPatterns(patternsText); commitPlaceholders() } }, 'Save Blacklist'),
|
|
908
|
+
),
|
|
909
|
+
activeTab === 'supervisor' && h('div', { 'data-tab': 'supervisor' },
|
|
910
|
+
h('p', { style: captionStyle }, 'Background daemon that auto-resumes crashed sessions.'),
|
|
911
|
+
h('label', { style: fieldLabelStyle }, 'intervalMs'),
|
|
912
|
+
h('input', {
|
|
913
|
+
type: 'number',
|
|
914
|
+
style: inputStyle,
|
|
915
|
+
value: supervisorCfg.intervalMs ?? '',
|
|
916
|
+
placeholder: '5000',
|
|
917
|
+
onChange: e => { const v = e.target.value === '' ? undefined : Number(e.target.value); saveSupervisorCfg({ intervalMs: v }) },
|
|
918
|
+
}),
|
|
919
|
+
h('label', { style: fieldLabelStyle }, 'downThreshold'),
|
|
920
|
+
h('input', {
|
|
921
|
+
type: 'number',
|
|
922
|
+
style: inputStyle,
|
|
923
|
+
value: supervisorCfg.downThreshold ?? '',
|
|
924
|
+
placeholder: '3',
|
|
925
|
+
onChange: e => { const v = e.target.value === '' ? undefined : Number(e.target.value); saveSupervisorCfg({ downThreshold: v }) },
|
|
926
|
+
}),
|
|
927
|
+
h(ToggleField, {
|
|
928
|
+
label: 'autoResumeEnabled',
|
|
929
|
+
caption: 'Automatically resume down sessions.',
|
|
930
|
+
checked: supervisorCfg.autoResumeEnabled === true,
|
|
931
|
+
onChange: v => saveSupervisorCfg({ autoResumeEnabled: v }),
|
|
932
|
+
}),
|
|
933
|
+
),
|
|
934
|
+
activeTab === 'notifier' && h('div', { 'data-tab': 'notifier' },
|
|
935
|
+
h('p', { style: captionStyle }, 'Telegram notifications for Maestro events.'),
|
|
936
|
+
h('label', { style: fieldLabelStyle }, 'telegram.botToken'),
|
|
937
|
+
h('input', {
|
|
938
|
+
type: 'password',
|
|
939
|
+
autoComplete: 'off',
|
|
940
|
+
style: inputStyle,
|
|
941
|
+
value: notifierCfg.telegram?.botToken ?? '',
|
|
942
|
+
placeholder: '123456:ABC-DEF...',
|
|
943
|
+
onChange: e => saveNotifierCfg({ telegram: { botToken: e.target.value } }),
|
|
944
|
+
}),
|
|
945
|
+
h('label', { style: fieldLabelStyle }, 'telegram.chatId'),
|
|
946
|
+
h('input', {
|
|
947
|
+
style: inputStyle,
|
|
948
|
+
value: notifierCfg.telegram?.chatId ?? '',
|
|
949
|
+
placeholder: '-1001234567890',
|
|
950
|
+
onChange: e => saveNotifierCfg({ telegram: { chatId: e.target.value } }),
|
|
951
|
+
}),
|
|
952
|
+
h(ToggleField, {
|
|
953
|
+
label: 'telegram.reviewNotifications',
|
|
954
|
+
caption: 'Also notify about finished reviews.',
|
|
955
|
+
checked: notifierCfg.telegram?.reviewNotifications === true || notifierCfg.policy?.reviewNotifications === true,
|
|
956
|
+
onChange: v => saveNotifierCfg({ telegram: { reviewNotifications: v } }),
|
|
957
|
+
}),
|
|
958
|
+
),
|
|
959
|
+
),
|
|
960
|
+
|
|
687
961
|
error && h('p', { style: errorStyle }, error),
|
|
688
962
|
)
|
|
689
963
|
}
|
package/src/host/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {} from '@deepseek-ai/dsh-client-connection'
|
|
2
1
|
import type { Context } from '@deepseek-ai/cordis'
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
type RpcResult<T> = { ok: true; value: T } | { ok: false; error: { code: string; message: string; details: object } }
|
|
4
|
+
type RpcErrorDetailsMap = { 'bad-request': { issues: object[] } }
|
|
4
5
|
import { createMaestroConfigService, type MaestroConfigService } from './service.ts'
|
|
5
6
|
|
|
6
7
|
export const name = 'maestro-config'
|
|
@@ -11,6 +12,7 @@ const RPC_CHANNEL = '/dsh-maestro-config'
|
|
|
11
12
|
declare module '@deepseek-ai/cordis' {
|
|
12
13
|
interface Context {
|
|
13
14
|
maestroConfig: MaestroConfigService
|
|
15
|
+
connection: { rpc: { handle: (channel: string, handler: (endpoint: string, payload: unknown) => Promise<RpcResult<unknown>>, opts?: unknown) => () => void } }
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -31,7 +33,13 @@ function fail(message: string): RpcResult<never> {
|
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Publish maestroConfig over the shared store + loopback RPC for clients.
|
|
38
|
+
* Exposes guard/guardBlacklist/supervisor/notifier domains (Task 1 validators)
|
|
39
|
+
* via generic get/set — validation is delegated to the lib's domain validators.
|
|
40
|
+
* Host also handles '/dsh-maestro-config/get' and '/dsh-maestro-config/set'
|
|
41
|
+
* style calls through the single channel with endpoint dispatch.
|
|
42
|
+
*/
|
|
35
43
|
export function apply(ctx: Context): void {
|
|
36
44
|
const svc = createMaestroConfigService()
|
|
37
45
|
ctx.provide('maestroConfig', svc)
|
|
@@ -43,6 +51,7 @@ export function apply(ctx: Context): void {
|
|
|
43
51
|
}
|
|
44
52
|
if (endpoint === 'get') {
|
|
45
53
|
if (typeof body.domain !== 'string') return fail('domain (string) is required')
|
|
54
|
+
// guard / guardBlacklist / supervisor / notifier are all valid domains here
|
|
46
55
|
return ok(await svc.get(body.domain))
|
|
47
56
|
}
|
|
48
57
|
if (endpoint === 'set') {
|