@ddtcorex/dsh-maestro-config 0.1.1 → 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 +272 -31
- 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.map +1 -1
- package/package.json +4 -6
- package/src/client/index.tsx +8 -5
- package/src/client/maestro-card.jsx +225 -21
- 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)(() => {
|
|
@@ -2596,31 +2620,6 @@ function ToggleField({ label, caption, checked, onChange }) {
|
|
|
2596
2620
|
)
|
|
2597
2621
|
);
|
|
2598
2622
|
}
|
|
2599
|
-
function ReviewHistoryPanel({ rpcCall }) {
|
|
2600
|
-
const [entries, setEntries] = (0, import_react.useState)(null);
|
|
2601
|
-
(0, import_react.useEffect)(() => {
|
|
2602
|
-
rpcCall(MAESTRO_ENDPOINTS.reviewsList, {}).then((res) => {
|
|
2603
|
-
if (res?.ok) setEntries(res.value ?? []);
|
|
2604
|
-
}).catch(() => setEntries([]));
|
|
2605
|
-
}, []);
|
|
2606
|
-
if (entries === null) return (0, import_react.createElement)("p", { style: captionStyle }, "Loading review history\u2026");
|
|
2607
|
-
if (entries.length === 0) return (0, import_react.createElement)("p", { style: captionStyle }, "No reviews recorded yet.");
|
|
2608
|
-
const icon = (entry) => entry.status === "completed" ? "\u2705" : entry.status === "failed" ? "\u26A0\uFE0F" : "\u{1F440}";
|
|
2609
|
-
return (0, import_react.createElement)(
|
|
2610
|
-
"ul",
|
|
2611
|
-
{ style: { listStyle: "none", margin: 0, padding: 0 } },
|
|
2612
|
-
entries.map((entry) => (0, import_react.createElement)(
|
|
2613
|
-
"li",
|
|
2614
|
-
{ key: entry.id, style: { padding: "6px 0", borderBottom: "1px solid var(--dsw-alias-separator-default, #333)", fontSize: 13 } },
|
|
2615
|
-
(0, import_react.createElement)("span", null, `${icon(entry)} ${entry.projectPath} !${entry.mrIid} \xB7 ${entry.mode}${entry.trigger !== "mention" ? ` \xB7 ${entry.trigger}` : ""}`),
|
|
2616
|
-
(0, import_react.createElement)(
|
|
2617
|
-
"div",
|
|
2618
|
-
{ style: captionStyle },
|
|
2619
|
-
`${new Date(entry.startedAt).toLocaleString()}${entry.summary ? ` \u2014 ${entry.summary}` : ""}${entry.error ? ` \u2014 ${entry.error}` : ""}`
|
|
2620
|
-
)
|
|
2621
|
-
))
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
2623
|
function LanAccess({ proxyStatus, lanPin }) {
|
|
2625
2624
|
const urls = proxyStatus?.lanUrls ?? [];
|
|
2626
2625
|
const [selected, setSelected] = (0, import_react.useState)(0);
|
|
@@ -2729,11 +2728,95 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
2729
2728
|
const [lanPinEnabled, setLanPinEnabled] = (0, import_react.useState)(false);
|
|
2730
2729
|
const [lanPin, setLanPin] = (0, import_react.useState)(null);
|
|
2731
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)({});
|
|
2732
2737
|
const call = async (endpoint, payload) => {
|
|
2733
2738
|
const res = await rpcCall(endpoint, payload);
|
|
2734
2739
|
if (!res?.ok) throw new Error(res?.error?.message ?? "RPC failed");
|
|
2735
2740
|
return res.value;
|
|
2736
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
|
+
};
|
|
2737
2820
|
const refresh = async () => {
|
|
2738
2821
|
try {
|
|
2739
2822
|
setStatus(await call(MAESTRO_ENDPOINTS.status, {}));
|
|
@@ -2754,6 +2837,21 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
2754
2837
|
}
|
|
2755
2838
|
}).catch(() => {
|
|
2756
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
|
+
});
|
|
2757
2855
|
}
|
|
2758
2856
|
call(MAESTRO_ENDPOINTS.lanPinStatus, {}).then((value) => {
|
|
2759
2857
|
setLanPinEnabled(value.enabled);
|
|
@@ -2971,8 +3069,7 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
2971
3069
|
caption: "After a completed review, further pushes to the same MR trigger an automatic quick re-review.",
|
|
2972
3070
|
checked: config.autoRereviewOnPush,
|
|
2973
3071
|
onChange: (checked) => saveField("autoRereviewOnPush", checked)
|
|
2974
|
-
})
|
|
2975
|
-
(0, import_react.createElement)(ReviewHistoryPanel, { rpcCall })
|
|
3072
|
+
})
|
|
2976
3073
|
),
|
|
2977
3074
|
(0, import_react.createElement)(
|
|
2978
3075
|
"div",
|
|
@@ -3008,6 +3105,149 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3008
3105
|
(0, import_react.createElement)("h4", { style: headingStyle }, "Projects"),
|
|
3009
3106
|
(0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
|
|
3010
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
|
+
),
|
|
3011
3251
|
error && (0, import_react.createElement)("p", { style: errorStyle }, error)
|
|
3012
3252
|
);
|
|
3013
3253
|
}
|
|
@@ -3051,7 +3291,7 @@ function registerSettingsNavIcon(label, root) {
|
|
|
3051
3291
|
// src/client/index.tsx
|
|
3052
3292
|
var SETTINGS_NAV_CSS2 = `
|
|
3053
3293
|
|
|
3054
|
-
/* maestro: replace the settings-nav fallback gear with the
|
|
3294
|
+
/* maestro: replace the settings-nav fallback gear with the Maestro M-logo glyph */
|
|
3055
3295
|
[${SETTINGS_NAV_MARKER}] > svg:first-child {
|
|
3056
3296
|
display: none;
|
|
3057
3297
|
}
|
|
@@ -3062,10 +3302,11 @@ var SETTINGS_NAV_CSS2 = `
|
|
|
3062
3302
|
width: 16px;
|
|
3063
3303
|
height: 16px;
|
|
3064
3304
|
background: currentColor;
|
|
3065
|
-
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='
|
|
3066
|
-
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;
|
|
3067
3307
|
}
|
|
3068
3308
|
`;
|
|
3309
|
+
var inject = ["slots", "connection"];
|
|
3069
3310
|
function installNavIconStyle() {
|
|
3070
3311
|
const tag = document.createElement("style");
|
|
3071
3312
|
tag.dataset.plugin = "@ddtcorex/dsh-maestro-config";
|
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,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddtcorex/dsh-maestro-config",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Maestro Config — shared settings service for the dsh-maestro-* suite over the single namespaced store (~/.dsh/maestro/settings.json)",
|
|
6
6
|
"type": "module",
|
|
@@ -34,16 +34,14 @@
|
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
37
|
-
"@deepseek-ai/dsh-client-connection": "0.1.
|
|
38
|
-
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.8"
|
|
37
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2"
|
|
39
38
|
},
|
|
40
39
|
"dependencies": {
|
|
41
|
-
"@ddtcorex/dsh-maestro-config-lib": "^0.1.
|
|
40
|
+
"@ddtcorex/dsh-maestro-config-lib": "^0.1.2"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
45
|
-
"@deepseek-ai/dsh-client-connection": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-host-apiproxy": "0.1.0-rc.8",
|
|
44
|
+
"@deepseek-ai/dsh-client-connection": "0.1.1-rc.2",
|
|
47
45
|
"@types/node": "^22.0.0",
|
|
48
46
|
"@types/qrcode": "^1.5.6",
|
|
49
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'
|
|
@@ -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)
|
|
@@ -373,26 +399,6 @@ function ToggleField({ label, caption, checked, onChange }) {
|
|
|
373
399
|
)
|
|
374
400
|
}
|
|
375
401
|
|
|
376
|
-
/** Newest-first list of recorded review runs from the host's reviews.json. */
|
|
377
|
-
function ReviewHistoryPanel({ rpcCall }) {
|
|
378
|
-
const [entries, setEntries] = useState(null)
|
|
379
|
-
useEffect(() => {
|
|
380
|
-
rpcCall(MAESTRO_ENDPOINTS.reviewsList, {})
|
|
381
|
-
.then(res => { if (res?.ok) setEntries(res.value ?? []) })
|
|
382
|
-
.catch(() => setEntries([]))
|
|
383
|
-
}, [])
|
|
384
|
-
if (entries === null) return h('p', { style: captionStyle }, 'Loading review history…')
|
|
385
|
-
if (entries.length === 0) return h('p', { style: captionStyle }, 'No reviews recorded yet.')
|
|
386
|
-
const icon = entry => entry.status === 'completed' ? '✅' : entry.status === 'failed' ? '⚠️' : '👀'
|
|
387
|
-
return h('ul', { style: { listStyle: 'none', margin: 0, padding: 0 } },
|
|
388
|
-
entries.map(entry => h('li', { key: entry.id, style: { padding: '6px 0', borderBottom: '1px solid var(--dsw-alias-separator-default, #333)', fontSize: 13 } },
|
|
389
|
-
h('span', null, `${icon(entry)} ${entry.projectPath} !${entry.mrIid} · ${entry.mode}${entry.trigger !== 'mention' ? ` · ${entry.trigger}` : ''}`),
|
|
390
|
-
h('div', { style: captionStyle },
|
|
391
|
-
`${new Date(entry.startedAt).toLocaleString()}${entry.summary ? ` — ${entry.summary}` : ''}${entry.error ? ` — ${entry.error}` : ''}`),
|
|
392
|
-
)),
|
|
393
|
-
)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
402
|
/** One selectable LAN address chip + the QR of the currently selected URL. */
|
|
397
403
|
function LanAccess({ proxyStatus, lanPin }) {
|
|
398
404
|
const urls = proxyStatus?.lanUrls ?? []
|
|
@@ -492,6 +498,13 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
492
498
|
const [lanPinEnabled, setLanPinEnabled] = useState(false)
|
|
493
499
|
const [lanPin, setLanPin] = useState(null)
|
|
494
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({})
|
|
495
508
|
|
|
496
509
|
const call = async (endpoint, payload) => {
|
|
497
510
|
const res = await rpcCall(endpoint, payload)
|
|
@@ -499,6 +512,60 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
499
512
|
return res.value
|
|
500
513
|
}
|
|
501
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
|
+
|
|
502
569
|
const refresh = async () => {
|
|
503
570
|
try { setStatus(await call(MAESTRO_ENDPOINTS.status, {})) } catch { /* transient failure, ignore */ }
|
|
504
571
|
try { setProxyStatus(await call(MAESTRO_ENDPOINTS.proxyStatus, {})) } catch { /* proxy row may be starting */ }
|
|
@@ -520,6 +587,21 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
520
587
|
}
|
|
521
588
|
})
|
|
522
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(() => {})
|
|
523
605
|
}
|
|
524
606
|
call(MAESTRO_ENDPOINTS.lanPinStatus, {})
|
|
525
607
|
.then(value => { setLanPinEnabled(value.enabled); if (value.enabled) setLanPin(value.pin ?? null) })
|
|
@@ -720,7 +802,6 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
720
802
|
checked: config.autoRereviewOnPush,
|
|
721
803
|
onChange: checked => saveField('autoRereviewOnPush', checked),
|
|
722
804
|
}),
|
|
723
|
-
h(ReviewHistoryPanel, { rpcCall }),
|
|
724
805
|
),
|
|
725
806
|
|
|
726
807
|
h('div', { style: sectionStyle },
|
|
@@ -754,6 +835,129 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
754
835
|
h(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: mappings => saveField('projectMappings', mappings), catalog, globalReviewModel: config.reviewModel ?? null }),
|
|
755
836
|
),
|
|
756
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
|
+
|
|
757
961
|
error && h('p', { style: errorStyle }, error),
|
|
758
962
|
)
|
|
759
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') {
|