weir-harness 2.0.0 → 2.1.0
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.capability-badge.js +2 -0
- package/lib/client.capability-manager.js +46 -179
- package/lib/client.capability-model.js +428 -3
- package/lib/client.file-lease-tool-model.js +189 -0
- package/lib/client.file-lease-tool-view.js +434 -0
- package/lib/client.js +105 -7
- package/lib/client.mcp-manager.js +563 -0
- package/package.json +1 -1
- package/skills/mcp-server-setup/SKILL.md +124 -0
- package/src/capabilities/apply-engine.js +18 -4
- package/src/capabilities/capability-remote.js +296 -45
- package/src/capabilities/initial-selection.js +3 -2
- package/src/capabilities/lifecycle-preload.js +69 -11
- package/src/capabilities/lifecycle-snapshot.js +127 -39
- package/src/capabilities/mcp-admission.js +2 -2
- package/src/capabilities/mcp-config.js +344 -0
- package/src/capabilities/mcp-credentials.js +180 -0
- package/src/capabilities/mcp-diagnostics.js +169 -0
- package/src/capabilities/mcp-drain.js +18 -4
- package/src/capabilities/mcp-facade.js +3 -3
- package/src/capabilities/mcp-gate.js +51 -52
- package/src/capabilities/mcp-install-tool.js +276 -0
- package/src/capabilities/mcp-manager.js +368 -142
- package/src/capabilities/mcp-mount.js +219 -79
- package/src/capabilities/mcp-operations.js +359 -0
- package/src/capabilities/mcp-process.js +43 -0
- package/src/capabilities/mcp-registry.js +74 -60
- package/src/capabilities/mcp-runtime.js +103 -0
- package/src/capabilities/read-payloads.js +8 -3
- package/src/capabilities/skill-applicability.js +185 -0
- package/src/capabilities/skill-builtin-migration.js +2 -1
- package/src/capabilities/skill-selection-plugin.js +161 -56
- package/src/file-lease/client.js +268 -35
- package/src/file-lease/index.js +4 -0
- package/src/file-lease/plugin.js +239 -15
- package/src/file-lease/tools.js +426 -38
- package/src/file-lease/watch.js +473 -0
- package/src/intent-gate/index.js +29 -12
- package/src/intent-gate/matcher.js +36 -2
- package/src/lease-broker/protocol.js +3 -2
- package/src/lease-broker/server.js +6 -3
- package/src/lease-broker/state.js +68 -11
- package/src/process/index.js +4 -2
- package/src/shared/audit.js +3 -0
- package/src/shared/child-scope.js +2 -0
- package/src/worktree/rules.js +1 -1
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "weir-harness",
|
|
3
|
+
chunk: "client.mcp-manager.js",
|
|
4
|
+
factory: (require) => {
|
|
5
|
+
var module = { exports: {} };
|
|
6
|
+
var exports = module.exports;
|
|
7
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
|
|
11
|
+
const ACCENT = "var(--dsw-alias-state-business-primary, #4176e6)";
|
|
12
|
+
const SUCCESS = "var(--dsw-alias-state-success-primary, #22c55e)";
|
|
13
|
+
const WARN = "var(--dsw-alias-state-warn-primary, #f59e0b)";
|
|
14
|
+
const DANGER = "var(--dsw-alias-state-error-primary, #ef4444)";
|
|
15
|
+
const LABEL = "var(--dsw-alias-label-primary, inherit)";
|
|
16
|
+
const MUTED = "var(--dsw-alias-label-tertiary, #81858c)";
|
|
17
|
+
const BORDER = "var(--dsw-alias-border-l2, rgba(127,127,127,.18))";
|
|
18
|
+
const HOVER = "var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.10))";
|
|
19
|
+
const CARD = "var(--dsw-alias-bg-base, #fff)";
|
|
20
|
+
const OVERLAY = "var(--dsw-alias-bg-overlay, #fff)";
|
|
21
|
+
const tint = (color, percent) => `color-mix(in srgb, ${color} ${percent}%, transparent)`;
|
|
22
|
+
const S = {
|
|
23
|
+
view: { position: "relative", display: "flex", flexDirection: "column", gap: "6px", minWidth: 0 },
|
|
24
|
+
toolbar: { display: "flex", gap: "5px", alignItems: "center", flexWrap: "wrap" },
|
|
25
|
+
card: { display: "flex", flexDirection: "column", gap: "5px", padding: "7px", margin: "5px 0", background: CARD, border: `1px solid ${BORDER}`, borderRadius: "var(--dsw-radius-md, 8px)" },
|
|
26
|
+
row: { display: "flex", gap: "6px", alignItems: "flex-start", flexWrap: "wrap" },
|
|
27
|
+
name: { color: LABEL, fontWeight: 600, overflowWrap: "anywhere" },
|
|
28
|
+
meta: { color: MUTED, fontSize: "11px", lineHeight: "15px", overflowWrap: "anywhere" },
|
|
29
|
+
input: { width: "100%", boxSizing: "border-box", padding: "4px 7px", color: LABEL, background: CARD, border: `1px solid ${BORDER}`, borderRadius: "var(--dsw-radius-sm, 6px)", fontSize: "12px", lineHeight: "16px" },
|
|
30
|
+
field: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 },
|
|
31
|
+
error: { color: DANGER, fontSize: "11px", lineHeight: "15px" },
|
|
32
|
+
warning: { color: WARN, fontSize: "11px", lineHeight: "15px" },
|
|
33
|
+
button: { padding: "3px 8px", color: LABEL, background: "none", border: `1px solid ${BORDER}`, borderRadius: "var(--dsw-radius-sm, 6px)", cursor: "pointer", fontSize: "11px", lineHeight: "15px" },
|
|
34
|
+
primary: { padding: "3px 10px", color: "var(--dsw-alias-bg-base, #fff)", background: ACCENT, border: `1px solid ${ACCENT}`, borderRadius: "var(--dsw-radius-sm, 6px)", cursor: "pointer", fontSize: "11px", lineHeight: "15px", fontWeight: 600 },
|
|
35
|
+
disabled: { opacity: 0.5, cursor: "not-allowed" },
|
|
36
|
+
tag: { display: "inline-flex", alignItems: "center", width: "fit-content", padding: "0 5px", border: `1px solid ${BORDER}`, borderRadius: "var(--dsw-radius-sm, 6px)", color: MUTED, fontSize: "10px", lineHeight: "14px", whiteSpace: "nowrap" },
|
|
37
|
+
pre: { margin: 0, padding: "6px", maxHeight: "180px", overflow: "auto", whiteSpace: "pre-wrap", overflowWrap: "anywhere", color: LABEL, background: HOVER, borderRadius: "var(--dsw-radius-sm, 6px)", fontSize: "10px", lineHeight: "14px" },
|
|
38
|
+
dialogShade: { position: "absolute", inset: 0, zIndex: 4, display: "flex", alignItems: "center", justifyContent: "center", padding: "12px", background: "var(--dsw-alias-bg-scrim, rgba(0,0,0,.28))" },
|
|
39
|
+
dialog: { display: "flex", flexDirection: "column", gap: "8px", width: "100%", padding: "12px", color: LABEL, background: OVERLAY, border: `1px solid ${BORDER}`, borderRadius: "var(--dsw-radius-lg, 12px)", boxShadow: "var(--dsw-elevation-prominent, 0 8px 24px rgba(0,0,0,.14))" },
|
|
40
|
+
};
|
|
41
|
+
const ring = {
|
|
42
|
+
onFocus: (event) => { if (event?.currentTarget?.style) { event.currentTarget.style.outline = "2px solid var(--dsw-alias-focus-ring, currentColor)"; event.currentTarget.style.outlineOffset = "1px"; } },
|
|
43
|
+
onBlur: (event) => { if (event?.currentTarget?.style) { event.currentTarget.style.outline = "none"; event.currentTarget.style.outlineOffset = ""; } },
|
|
44
|
+
};
|
|
45
|
+
const button = (label, props = {}) => react_jsx_runtime.jsx("button", {
|
|
46
|
+
type: "button", ...ring, ...props,
|
|
47
|
+
style: { ...(props.primary ? S.primary : S.button), ...(props.disabled ? S.disabled : {}), ...(props.style ?? {}) },
|
|
48
|
+
children: label,
|
|
49
|
+
});
|
|
50
|
+
const tag = (label, hue) => react_jsx_runtime.jsx("span", { style: { ...S.tag, ...(hue ? { color: hue, border: `1px solid ${hue}`, background: tint(hue, 10) } : {}) }, children: label });
|
|
51
|
+
|
|
52
|
+
class McpManagerBoundary extends react.Component {
|
|
53
|
+
constructor(props) { super(props); this.state = { failed: false }; }
|
|
54
|
+
static getDerivedStateFromError() { return { failed: true }; }
|
|
55
|
+
render() {
|
|
56
|
+
if (this.state.failed) return react_jsx_runtime.jsx("div", { role: "alert", style: S.error, children: this.props.t("capability.mcp.boundary", "MCP management could not be rendered. Other capability controls remain available.") });
|
|
57
|
+
return this.props.children;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function McpManagerView(props) {
|
|
62
|
+
const model = props.model;
|
|
63
|
+
const t = props.t;
|
|
64
|
+
const listing = props.listing ?? {};
|
|
65
|
+
const managed = props.managed ?? [];
|
|
66
|
+
const unmanaged = props.unmanaged ?? [];
|
|
67
|
+
const [editor, setEditor] = react.useState(null);
|
|
68
|
+
const [confirmation, setConfirmation] = react.useState(null);
|
|
69
|
+
const [notice, setNotice] = react.useState(null);
|
|
70
|
+
const [pending, setPending] = react.useState(null);
|
|
71
|
+
const origins = react.useRef({});
|
|
72
|
+
const cancelButton = react.useRef(null);
|
|
73
|
+
const confirmButton = react.useRef(null);
|
|
74
|
+
react.useEffect(() => { if (confirmation) cancelButton.current?.focus?.(); }, [confirmation]);
|
|
75
|
+
|
|
76
|
+
const originRef = (id) => (element) => { if (element) origins.current[id] = element; };
|
|
77
|
+
const closeConfirmation = (restore = true) => {
|
|
78
|
+
const origin = confirmation?.origin;
|
|
79
|
+
setConfirmation(null);
|
|
80
|
+
if (restore && origin) origins.current[origin]?.focus?.();
|
|
81
|
+
};
|
|
82
|
+
const confirmationKeys = (event) => {
|
|
83
|
+
if (event.key === "Escape") { event.preventDefault(); closeConfirmation(true); return; }
|
|
84
|
+
if (event.key !== "Tab") return;
|
|
85
|
+
const first = cancelButton.current, second = confirmButton.current;
|
|
86
|
+
if (!first || !second) return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
if (event.target === first) second.focus?.(); else first.focus?.();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const refresh = async () => {
|
|
92
|
+
if (typeof props.refreshAll !== "function") return null;
|
|
93
|
+
const result = await props.refreshAll();
|
|
94
|
+
return result?.listing ?? null;
|
|
95
|
+
};
|
|
96
|
+
const resultError = (result, fallback) => result?.error?.message ?? result?.error?.code ?? result?.status ?? fallback;
|
|
97
|
+
const clearSecrets = (draft) => ({
|
|
98
|
+
...draft,
|
|
99
|
+
url: draft.urlAction === "replace" && model.mcpSensitiveUrl(draft.url) ? "" : draft.url,
|
|
100
|
+
envRows: (draft.envRows ?? []).map(row => ({ ...row, value: "", action: row.configured ? "keep" : row.action })),
|
|
101
|
+
headerRows: (draft.headerRows ?? []).map(row => ({ ...row, value: "", action: row.configured ? "keep" : row.action })),
|
|
102
|
+
secretArgRows: (draft.secretArgRows ?? []).map(row => ({ ...row, value: "", action: row.configured ? "keep" : row.action })),
|
|
103
|
+
});
|
|
104
|
+
const openForm = (server, source = listing) => {
|
|
105
|
+
setNotice(null);
|
|
106
|
+
setEditor({ kind: "form", draft: model.mcpEditorFromServer(server, source?.mcpRegistryRevision ?? 0), advanced: false, showErrors: false, busy: false, preview: null, result: null });
|
|
107
|
+
};
|
|
108
|
+
const openJson = (text = "") => {
|
|
109
|
+
setNotice(null);
|
|
110
|
+
setEditor({ kind: "json", text, busy: false, showErrors: false, preview: null, result: null });
|
|
111
|
+
};
|
|
112
|
+
const updateDraft = (key, value) => setEditor(current => ({ ...current, preview: null, result: null, draft: { ...current.draft, [key]: value } }));
|
|
113
|
+
const updateRows = (field, index, patch) => setEditor(current => ({
|
|
114
|
+
...current, preview: null, result: null,
|
|
115
|
+
draft: { ...current.draft, [field]: current.draft[field].map((row, at) => at === index ? { ...row, ...patch } : row) },
|
|
116
|
+
}));
|
|
117
|
+
const addRow = (field) => setEditor(current => ({
|
|
118
|
+
...current,
|
|
119
|
+
draft: { ...current.draft, [field]: [...current.draft[field], field === "secretArgRows" ? { index: "0", action: "replace", source: "value", ref: "", prefix: "", value: "", configured: false } : { key: "", action: "replace", source: "value", ref: "", prefix: "", value: "", configured: false }] },
|
|
120
|
+
}));
|
|
121
|
+
const removeRow = (field, index) => setEditor(current => ({
|
|
122
|
+
...current, preview: null,
|
|
123
|
+
draft: { ...current.draft, [field]: current.draft[field][index].configured
|
|
124
|
+
? current.draft[field].map((row, at) => at === index ? { ...row, action: "remove", value: "" } : row)
|
|
125
|
+
: current.draft[field].filter((_, at) => at !== index) },
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const previewForm = async () => {
|
|
129
|
+
if (!editor || editor.kind !== "form" || editor.busy || typeof props.mcpPreview !== "function") return;
|
|
130
|
+
const errors = model.mcpEditorErrorsOf(editor.draft);
|
|
131
|
+
if (Object.keys(errors).length > 0) { setEditor({ ...editor, showErrors: true, result: null }); return; }
|
|
132
|
+
let config;
|
|
133
|
+
try { config = model.mcpConfigFromEditor(editor.draft); }
|
|
134
|
+
catch (cause) { setEditor({ ...editor, showErrors: true, result: { status: "validation-failed", error: { message: cause.message } } }); return; }
|
|
135
|
+
setEditor({ ...editor, busy: true, result: null });
|
|
136
|
+
try {
|
|
137
|
+
const response = await props.mcpPreview(props.sessionId, { action: "preview", mode: editor.draft.mode, identity: editor.draft.identity, config, surface: "ui" });
|
|
138
|
+
setEditor(current => current?.kind !== "form" ? current : response?.status === "preview"
|
|
139
|
+
? { ...current, busy: false, preview: { response, config, secretBearing: model.mcpEditorHasSecrets(current.draft) }, result: null }
|
|
140
|
+
: { ...current, busy: false, result: response ?? { status: "validation-failed" }, preview: null });
|
|
141
|
+
} catch (cause) { setEditor(current => ({ ...current, busy: false, result: { status: "call-failed", error: { message: String(cause) } }, preview: null })); }
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const executeForm = async (namespaceConfirmed = false) => {
|
|
145
|
+
if (!editor?.preview || editor.busy || typeof props.mcpMutate !== "function") return;
|
|
146
|
+
const draft = editor.draft;
|
|
147
|
+
const currentName = draft.serverName || draft.identity;
|
|
148
|
+
if (!namespaceConfirmed && draft.mode === "edit" && draft.originalServerName !== currentName) {
|
|
149
|
+
setConfirmation({ kind: "namespace", identity: draft.identity, from: draft.originalServerName, to: currentName, origin: "form-confirm" });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { response, config } = editor.preview;
|
|
153
|
+
setEditor({ ...editor, busy: true, result: null });
|
|
154
|
+
try {
|
|
155
|
+
const result = await props.mcpMutate(props.sessionId, {
|
|
156
|
+
action: draft.mode, identity: draft.identity, config,
|
|
157
|
+
expectedRevision: response.registryRevision,
|
|
158
|
+
targetFingerprint: response.targets?.[0]?.fingerprint ?? null,
|
|
159
|
+
confirmNamespaceChange: namespaceConfirmed,
|
|
160
|
+
});
|
|
161
|
+
if (result?.status === "mounted") { setNotice(result); setEditor(null); await refresh(); return; }
|
|
162
|
+
setEditor(current => ({
|
|
163
|
+
...current, busy: false, result,
|
|
164
|
+
preview: ["revision-conflict", "conflict", "owner-changed", "indeterminate"].includes(result?.status) ? null : current.preview,
|
|
165
|
+
draft: clearSecrets(current.draft),
|
|
166
|
+
}));
|
|
167
|
+
if (result?.saved) await refresh();
|
|
168
|
+
} catch (cause) { setEditor(current => ({ ...current, busy: false, result: { status: "call-failed", error: { message: String(cause) } }, draft: clearSecrets(current.draft) })); }
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const testForm = async () => {
|
|
172
|
+
if (!editor?.preview || editor.busy || typeof props.mcpMutate !== "function") return;
|
|
173
|
+
const { response, config } = editor.preview;
|
|
174
|
+
setEditor({ ...editor, busy: true, result: null });
|
|
175
|
+
try {
|
|
176
|
+
const result = await props.mcpMutate(props.sessionId, {
|
|
177
|
+
action: "test", mode: editor.draft.mode, identity: editor.draft.identity, config,
|
|
178
|
+
expectedRevision: response.registryRevision, targetFingerprint: response.targets?.[0]?.fingerprint ?? null,
|
|
179
|
+
});
|
|
180
|
+
setEditor(current => ({ ...current, busy: false, result, draft: clearSecrets(current.draft) }));
|
|
181
|
+
} catch (cause) { setEditor(current => ({ ...current, busy: false, result: { status: "call-failed", error: { message: String(cause) } }, draft: clearSecrets(current.draft) })); }
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const previewJson = async () => {
|
|
185
|
+
if (!editor || editor.kind !== "json" || editor.busy || typeof props.mcpPreview !== "function") return;
|
|
186
|
+
const guarded = model.mcpJsonGuard(editor.text);
|
|
187
|
+
if (!guarded.ok) { setEditor({ ...editor, showErrors: true, result: { status: "validation-failed", error: { code: guarded.code, message: guarded.code } }, preview: null }); return; }
|
|
188
|
+
setEditor({ ...editor, busy: true, result: null });
|
|
189
|
+
try {
|
|
190
|
+
const response = await props.mcpPreview(props.sessionId, { action: "preview", mode: "add", document: editor.text, surface: "ui" });
|
|
191
|
+
setEditor(current => current?.kind !== "json" ? current : response?.status === "preview"
|
|
192
|
+
? { ...current, busy: false, preview: { response, secretBearing: model.mcpDocumentHasSecrets(current.text) || model.mcpPreviewHasSecrets(response) }, result: null }
|
|
193
|
+
: { ...current, busy: false, preview: null, result: response });
|
|
194
|
+
} catch (cause) { setEditor(current => ({ ...current, busy: false, preview: null, result: { status: "call-failed", error: { message: String(cause) } } })); }
|
|
195
|
+
};
|
|
196
|
+
const executeJson = async () => {
|
|
197
|
+
if (!editor?.preview || editor.busy || typeof props.mcpBatch !== "function") return;
|
|
198
|
+
setConfirmation({ kind: "batch", count: editor.preview.response.plan?.length ?? 0, digest: editor.preview.response.digest, origin: "batch-confirm" });
|
|
199
|
+
};
|
|
200
|
+
const executeBatch = async () => {
|
|
201
|
+
closeConfirmation(false);
|
|
202
|
+
setEditor(current => ({ ...current, busy: true, result: null }));
|
|
203
|
+
try {
|
|
204
|
+
const result = await props.mcpBatch(props.sessionId, { mode: "add", document: editor.text, surface: "ui" });
|
|
205
|
+
setEditor(current => ({ ...current, busy: false, result, preview: null }));
|
|
206
|
+
if (result?.status === "mounted") { setNotice(result); setEditor(null); }
|
|
207
|
+
await refresh();
|
|
208
|
+
} catch (cause) { setEditor(current => ({ ...current, busy: false, result: { status: "call-failed", error: { message: String(cause) } }, preview: null })); }
|
|
209
|
+
};
|
|
210
|
+
const retryItem = async (item) => {
|
|
211
|
+
if (item.saved) {
|
|
212
|
+
const latest = await refresh();
|
|
213
|
+
const source = latest ?? listing;
|
|
214
|
+
const server = (source?.mcpServers ?? []).find(row => row.identity === item.identity);
|
|
215
|
+
if (server) openForm(server, source);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const entry = model.mcpDocumentEntryOf(editor.text, item.identity);
|
|
219
|
+
if (entry) openJson(JSON.stringify(entry, null, 2));
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const rowOperation = async (action, server) => {
|
|
223
|
+
if (pending || typeof props.mcpMutate !== "function") return;
|
|
224
|
+
setPending(`${action}:${server.identity}`);
|
|
225
|
+
setNotice(null);
|
|
226
|
+
try {
|
|
227
|
+
const result = await props.mcpMutate(props.sessionId, {
|
|
228
|
+
action, identity: server.identity, expectedRevision: listing.mcpRegistryRevision ?? 0,
|
|
229
|
+
targetFingerprint: server.fingerprint,
|
|
230
|
+
...(action === "setDefault" ? { enabled: !server.defaultEnabled } : {}),
|
|
231
|
+
});
|
|
232
|
+
setNotice(result);
|
|
233
|
+
await refresh();
|
|
234
|
+
} catch (cause) { setNotice({ status: "call-failed", error: { message: String(cause) } }); }
|
|
235
|
+
finally { setPending(null); }
|
|
236
|
+
};
|
|
237
|
+
const confirmAction = async () => {
|
|
238
|
+
if (confirmation?.kind === "remove") {
|
|
239
|
+
const server = managed.find(row => row.identity === confirmation.identity);
|
|
240
|
+
closeConfirmation(false);
|
|
241
|
+
if (server) await rowOperation("remove", server);
|
|
242
|
+
} else if (confirmation?.kind === "namespace") {
|
|
243
|
+
closeConfirmation(false);
|
|
244
|
+
await executeForm(true);
|
|
245
|
+
} else if (confirmation?.kind === "batch") await executeBatch();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const field = (key, label, options = {}) => {
|
|
249
|
+
const error = editor?.showErrors ? model.mcpEditorErrorsOf(editor.draft)[key] : undefined;
|
|
250
|
+
return react_jsx_runtime.jsxs("label", { style: S.field, children: [
|
|
251
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: label }),
|
|
252
|
+
react_jsx_runtime.jsx("input", {
|
|
253
|
+
style: S.input, value: editor.draft[key] ?? "", placeholder: label, "aria-label": label,
|
|
254
|
+
type: options.type ?? "text", disabled: editor.busy || (key === "identity" && editor.draft.mode === "edit"),
|
|
255
|
+
"aria-invalid": error ? true : undefined, ...ring,
|
|
256
|
+
onChange: event => updateDraft(key, event.target.value),
|
|
257
|
+
}),
|
|
258
|
+
error ? react_jsx_runtime.jsx("span", { style: S.error, children: `${label}: ${error}` }) : null,
|
|
259
|
+
] }, key);
|
|
260
|
+
};
|
|
261
|
+
const secretRows = (field, title) => {
|
|
262
|
+
const rows = editor.draft[field] ?? [];
|
|
263
|
+
const errors = editor.showErrors ? model.mcpEditorErrorsOf(editor.draft) : {};
|
|
264
|
+
return react_jsx_runtime.jsxs("div", { style: S.field, children: [
|
|
265
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: title }),
|
|
266
|
+
...rows.map((row, index) => {
|
|
267
|
+
const key = field === "secretArgRows" ? row.index : row.key;
|
|
268
|
+
const rowError = errors[`${field === "secretArgRows" ? "args" : field === "envRows" ? "env" : "headers"}.${index}`];
|
|
269
|
+
return react_jsx_runtime.jsxs("div", { style: { ...S.card, gap: "4px" }, children: [
|
|
270
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
271
|
+
react_jsx_runtime.jsx("input", {
|
|
272
|
+
style: { ...S.input, flex: 1 }, value: key ?? "", disabled: editor.busy || row.configured,
|
|
273
|
+
"aria-label": field === "secretArgRows" ? t("capability.mcp.secretArgIndex", "argument index") : t("capability.mcp.secretKey", "secret key"),
|
|
274
|
+
onChange: event => updateRows(field, index, field === "secretArgRows" ? { index: event.target.value } : { key: event.target.value }),
|
|
275
|
+
}),
|
|
276
|
+
react_jsx_runtime.jsxs("select", {
|
|
277
|
+
style: { ...S.input, width: "auto" }, value: row.action, disabled: editor.busy,
|
|
278
|
+
"aria-label": t("capability.mcp.secretAction", "secret action"),
|
|
279
|
+
onChange: event => updateRows(field, index, { action: event.target.value }),
|
|
280
|
+
children: [
|
|
281
|
+
react_jsx_runtime.jsx("option", { value: "keep", children: t("capability.mcp.keep", "keep") }),
|
|
282
|
+
react_jsx_runtime.jsx("option", { value: "replace", children: t("capability.mcp.replace", "replace") }),
|
|
283
|
+
react_jsx_runtime.jsx("option", { value: "remove", children: t("capability.mcp.removeSecret", "remove") }),
|
|
284
|
+
],
|
|
285
|
+
}),
|
|
286
|
+
react_jsx_runtime.jsxs("select", {
|
|
287
|
+
style: { ...S.input, width: "auto" }, value: row.source, disabled: editor.busy || row.action === "remove",
|
|
288
|
+
"aria-label": t("capability.mcp.secretSource", "secret source"),
|
|
289
|
+
onChange: event => updateRows(field, index, { source: event.target.value, action: row.configured ? "replace" : row.action }),
|
|
290
|
+
children: [
|
|
291
|
+
react_jsx_runtime.jsx("option", { value: "value", children: t("capability.mcp.secretValueSource", "new value") }),
|
|
292
|
+
react_jsx_runtime.jsx("option", { value: "ref", children: t("capability.mcp.secretRefSource", "credential ref") }),
|
|
293
|
+
],
|
|
294
|
+
}),
|
|
295
|
+
button(t("capability.mcp.removeRow", "Remove row"), { disabled: editor.busy, onClick: () => removeRow(field, index) }),
|
|
296
|
+
] }),
|
|
297
|
+
row.source === "ref"
|
|
298
|
+
? react_jsx_runtime.jsx("input", {
|
|
299
|
+
style: S.input, value: row.ref ?? "", disabled: editor.busy || row.action === "keep" || row.action === "remove",
|
|
300
|
+
placeholder: "CREDENTIAL_REF", "aria-label": t("capability.mcp.ref", "credential reference"), ...ring,
|
|
301
|
+
onChange: event => updateRows(field, index, { ref: event.target.value, action: row.configured ? "replace" : row.action }),
|
|
302
|
+
})
|
|
303
|
+
: react_jsx_runtime.jsx("input", {
|
|
304
|
+
style: S.input, value: row.value ?? "", type: "password", autoComplete: "off",
|
|
305
|
+
disabled: editor.busy || row.action === "keep" || row.action === "remove",
|
|
306
|
+
placeholder: row.configured ? t("capability.mcp.secretBlankKeep", "blank keeps the configured secret") : t("capability.mcp.secretValue", "secret value"),
|
|
307
|
+
"aria-label": t("capability.mcp.secretValue", "secret value"), ...ring,
|
|
308
|
+
onChange: event => updateRows(field, index, { value: event.target.value, action: "replace" }),
|
|
309
|
+
}),
|
|
310
|
+
react_jsx_runtime.jsx("input", {
|
|
311
|
+
style: S.input, value: row.prefix ?? "", disabled: editor.busy || row.action === "remove",
|
|
312
|
+
placeholder: t("capability.mcp.prefix", "optional non-secret prefix"), "aria-label": t("capability.mcp.prefix", "optional non-secret prefix"), ...ring,
|
|
313
|
+
onChange: event => updateRows(field, index, { prefix: event.target.value, prefixDirty: true, action: row.configured && row.action === "keep" ? "replace" : row.action }),
|
|
314
|
+
}),
|
|
315
|
+
rowError ? react_jsx_runtime.jsx("span", { style: S.error, children: rowError }) : null,
|
|
316
|
+
] }, `${field}:${index}`);
|
|
317
|
+
}),
|
|
318
|
+
button(t("capability.mcp.addSecret", "+ Add secret/reference"), { disabled: editor.busy, onClick: () => addRow(field) }),
|
|
319
|
+
] });
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const resultView = (result) => {
|
|
323
|
+
if (!result) return null;
|
|
324
|
+
const failed = result.status !== "mounted" && result.status !== "saved" && result.status !== "removed" && result.status !== "recovered";
|
|
325
|
+
return react_jsx_runtime.jsxs("div", { role: failed ? "alert" : "status", style: { ...S.card, borderColor: failed ? DANGER : SUCCESS }, children: [
|
|
326
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
327
|
+
tag(result.status, failed ? DANGER : SUCCESS),
|
|
328
|
+
result.saved === true ? tag(t("capability.mcp.saved", "saved")) : null,
|
|
329
|
+
result.notAttempted === true || result.status === "not-attempted" ? tag(t("capability.mcp.notAttempted", "not attempted"), WARN) : null,
|
|
330
|
+
result.generation != null ? tag(`generation ${result.generation}`) : null,
|
|
331
|
+
result.defaultRemoved === true ? tag(t("capability.mcp.defaultRemoved", "default removed"), WARN) : null,
|
|
332
|
+
] }),
|
|
333
|
+
result.discovery ? react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.discovery", "Discovery: {count} tool(s)").replace("{count}", String(result.discovery.toolCount ?? "?")) }) : null,
|
|
334
|
+
result.error ? react_jsx_runtime.jsxs("div", { style: S.error, children: [
|
|
335
|
+
react_jsx_runtime.jsx("div", { children: result.error.message ?? result.error.code }),
|
|
336
|
+
result.error.hint ? react_jsx_runtime.jsx("div", { children: result.error.hint }) : null,
|
|
337
|
+
] }) : null,
|
|
338
|
+
result.atomic === false ? react_jsx_runtime.jsx("span", { style: S.warning, children: t("capability.mcp.nonAtomic", "Batch results are per server; no atomic rollback is claimed.") }) : null,
|
|
339
|
+
] });
|
|
340
|
+
};
|
|
341
|
+
const previewView = (preview, secretBearing) => {
|
|
342
|
+
if (!preview) return null;
|
|
343
|
+
const response = preview.response ?? preview;
|
|
344
|
+
const unsupported = model.mcpPreviewSupportOf(response, secretBearing ?? preview.secretBearing);
|
|
345
|
+
const targets = response.targets ?? [];
|
|
346
|
+
return react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
347
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.planDigest", "Plan digest: {digest}").replace("{digest}", response.digest ?? "unknown") }),
|
|
348
|
+
react_jsx_runtime.jsx("pre", { style: S.pre, children: JSON.stringify(response.plan ?? [], null, 2) }),
|
|
349
|
+
...targets.filter(target => target.conflict || target.namespaceConflict).map(target => react_jsx_runtime.jsx("span", { style: S.error, children: `${target.identity}: ${target.conflict ?? target.namespaceConflict}` }, target.identity)),
|
|
350
|
+
...unsupported.map(code => react_jsx_runtime.jsx("span", { style: S.error, children: t(`capability.mcp.${code}`, code) }, code)),
|
|
351
|
+
] });
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const formEditor = () => {
|
|
355
|
+
const draft = editor.draft;
|
|
356
|
+
const errors = editor.showErrors ? model.mcpEditorErrorsOf(draft) : {};
|
|
357
|
+
const namespaceChanged = draft.mode === "edit" && draft.originalServerName !== (draft.serverName || draft.identity);
|
|
358
|
+
const unsupported = editor.preview ? model.mcpPreviewSupportOf(editor.preview.response, editor.preview.secretBearing) : [];
|
|
359
|
+
const conflict = editor.preview?.response?.targets?.[0]?.conflict || editor.preview?.response?.targets?.[0]?.namespaceConflict;
|
|
360
|
+
return react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
361
|
+
react_jsx_runtime.jsxs("div", { style: S.toolbar, children: [
|
|
362
|
+
react_jsx_runtime.jsx("strong", { style: S.name, children: draft.mode === "edit" ? t("capability.mcp.editTitle", "Edit MCP server") : t("capability.mcp.addTitle", "Add managed MCP server") }),
|
|
363
|
+
tag(draft.transport),
|
|
364
|
+
] }),
|
|
365
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
366
|
+
react_jsx_runtime.jsxs("select", {
|
|
367
|
+
style: { ...S.input, width: "auto" }, value: draft.transport, disabled: editor.busy || draft.mode === "edit",
|
|
368
|
+
"aria-label": t("capability.mcp.transport", "transport"),
|
|
369
|
+
onChange: event => updateDraft("transport", event.target.value),
|
|
370
|
+
children: [
|
|
371
|
+
react_jsx_runtime.jsx("option", { value: "stdio", children: "stdio" }),
|
|
372
|
+
react_jsx_runtime.jsx("option", { value: "streamable-http", children: "streamable-http" }),
|
|
373
|
+
],
|
|
374
|
+
}),
|
|
375
|
+
button(editor.advanced ? t("capability.mcp.basic", "Basic") : t("capability.mcp.advanced", "Advanced"), { disabled: editor.busy, "aria-expanded": editor.advanced, onClick: () => setEditor({ ...editor, advanced: !editor.advanced }) }),
|
|
376
|
+
] }),
|
|
377
|
+
field("identity", t("capability.mcp.identity", "identity (e.g. my-docs)")),
|
|
378
|
+
field("label", t("capability.mcp.label", "label (display name, optional)")),
|
|
379
|
+
field("serverName", t("capability.mcp.serverName", "public server name")),
|
|
380
|
+
draft.transport === "stdio" ? react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, { children: [
|
|
381
|
+
field("command", t("capability.mcp.command", "command (e.g. npx)")),
|
|
382
|
+
field("argsText", t("capability.mcp.argsJson", "args (JSON array or space-separated)")),
|
|
383
|
+
editor.advanced ? field("cwd", t("capability.mcp.cwd", "working directory (optional)")) : null,
|
|
384
|
+
] }) : react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, { children: [
|
|
385
|
+
draft.urlMode === "legacy" && draft.urlAction === "keep"
|
|
386
|
+
? react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
387
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.sensitiveConfigured", "Sensitive URL configured; its value is hidden.") }),
|
|
388
|
+
button(t("capability.mcp.replace", "replace"), { disabled: editor.busy, onClick: () => setEditor({ ...editor, preview: null, draft: { ...draft, urlMode: "literal", urlAction: "replace", url: "" } }) }),
|
|
389
|
+
] })
|
|
390
|
+
: react_jsx_runtime.jsxs("div", { style: S.field, children: [
|
|
391
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
392
|
+
react_jsx_runtime.jsxs("select", {
|
|
393
|
+
style: { ...S.input, width: "auto" }, value: draft.urlMode, disabled: editor.busy,
|
|
394
|
+
"aria-label": t("capability.mcp.urlMode", "URL source"),
|
|
395
|
+
onChange: event => setEditor({ ...editor, preview: null, draft: { ...draft, urlMode: event.target.value, urlAction: "replace" } }),
|
|
396
|
+
children: [
|
|
397
|
+
react_jsx_runtime.jsx("option", { value: "literal", children: t("capability.mcp.urlLiteral", "URL") }),
|
|
398
|
+
react_jsx_runtime.jsx("option", { value: "ref", children: t("capability.mcp.secretRefSource", "credential ref") }),
|
|
399
|
+
],
|
|
400
|
+
}),
|
|
401
|
+
draft.urlAction === "keep" ? tag(t("capability.mcp.keep", "keep")) : null,
|
|
402
|
+
] }),
|
|
403
|
+
draft.urlMode === "ref"
|
|
404
|
+
? react_jsx_runtime.jsx("input", { style: S.input, value: draft.urlRef, disabled: editor.busy, placeholder: "PRIVATE_MCP_URL", "aria-label": t("capability.mcp.urlRef", "URL credential reference"), ...ring, onChange: event => setEditor({ ...editor, preview: null, draft: { ...draft, urlRef: event.target.value, urlAction: "replace" } }) })
|
|
405
|
+
: react_jsx_runtime.jsx("input", { style: S.input, value: draft.url, disabled: editor.busy, placeholder: "https://example.test/mcp", "aria-label": t("capability.mcp.url", "MCP URL"), ...ring, onChange: event => setEditor({ ...editor, preview: null, draft: { ...draft, url: event.target.value, urlAction: "replace" } }) }),
|
|
406
|
+
errors.url ? react_jsx_runtime.jsx("span", { style: S.error, children: errors.url }) : null,
|
|
407
|
+
] }),
|
|
408
|
+
] }),
|
|
409
|
+
editor.advanced ? react_jsx_runtime.jsxs("div", { style: S.field, children: [
|
|
410
|
+
field("toolCallTimeoutMs", t("capability.mcp.timeout", "per-call timeout (ms)"), { type: "number" }),
|
|
411
|
+
field("maxInstructionBytes", t("capability.mcp.instructionLimit", "instruction limit (bytes)"), { type: "number" }),
|
|
412
|
+
draft.transport === "stdio" ? secretRows("envRows", t("capability.mcp.env", "environment secrets / references")) : secretRows("headerRows", t("capability.mcp.headers", "HTTP header secrets / references")),
|
|
413
|
+
draft.transport === "stdio" ? secretRows("secretArgRows", t("capability.mcp.secretArgs", "secret argument values / references")) : null,
|
|
414
|
+
react_jsx_runtime.jsxs("label", { style: S.row, children: [
|
|
415
|
+
react_jsx_runtime.jsx("input", { type: "checkbox", checked: draft.reconnectEnabled, disabled: editor.busy, onChange: event => setEditor({ ...editor, preview: null, draft: { ...draft, reconnectConfigured: true, reconnectEnabled: event.target.checked } }) }),
|
|
416
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.reconnect", "enable reconnect") }),
|
|
417
|
+
] }),
|
|
418
|
+
field("reconnectInitialDelayMs", t("capability.mcp.reconnectInitial", "reconnect initial delay (ms)"), { type: "number" }),
|
|
419
|
+
field("reconnectMaxDelayMs", t("capability.mcp.reconnectMax", "reconnect maximum delay (ms)"), { type: "number" }),
|
|
420
|
+
field("reconnectMaxAttempts", t("capability.mcp.reconnectAttempts", "reconnect maximum attempts"), { type: "number" }),
|
|
421
|
+
errors.reconnect ? react_jsx_runtime.jsx("span", { style: S.error, children: errors.reconnect }) : null,
|
|
422
|
+
] }) : null,
|
|
423
|
+
namespaceChanged ? react_jsx_runtime.jsx("span", { style: S.warning, children: t("capability.mcp.namespacePending", "The public namespace changes; separate confirmation is required.") }) : null,
|
|
424
|
+
previewView(editor.preview),
|
|
425
|
+
resultView(editor.result),
|
|
426
|
+
react_jsx_runtime.jsxs("div", { style: S.toolbar, children: [
|
|
427
|
+
button(t("capability.mcp.preview", "Preview"), { disabled: editor.busy, onClick: previewForm }),
|
|
428
|
+
editor.preview ? button(t("capability.mcp.confirm", "Confirm"), { primary: true, disabled: editor.busy || unsupported.length > 0 || Boolean(conflict), ref: originRef("form-confirm"), onClick: () => executeForm(false) }) : null,
|
|
429
|
+
editor.preview ? button(t("capability.mcp.test", "Test"), { disabled: editor.busy || unsupported.length > 0 || Boolean(conflict), onClick: testForm }) : null,
|
|
430
|
+
button(t("capability.mcp.cancel", "Cancel"), { disabled: editor.busy, onClick: () => setEditor(null) }),
|
|
431
|
+
] }),
|
|
432
|
+
] });
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const jsonEditor = () => {
|
|
436
|
+
const unsupported = editor.preview ? model.mcpPreviewSupportOf(editor.preview.response, editor.preview.secretBearing) : [];
|
|
437
|
+
const items = model.mcpBatchItemsOf(editor.result);
|
|
438
|
+
return react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
439
|
+
react_jsx_runtime.jsx("strong", { style: S.name, children: t("capability.mcp.pasteTitle", "Paste MCP JSON (single or mcpServers batch)") }),
|
|
440
|
+
react_jsx_runtime.jsx("textarea", {
|
|
441
|
+
style: { ...S.input, minHeight: "110px", fontFamily: "var(--dsw-font-mono, monospace)" },
|
|
442
|
+
value: editor.text, disabled: editor.busy, "aria-label": t("capability.mcp.pasteLabel", "MCP JSON document"), ...ring,
|
|
443
|
+
onChange: event => setEditor({ ...editor, text: event.target.value, preview: null, result: null }),
|
|
444
|
+
}),
|
|
445
|
+
editor.showErrors && model.mcpJsonGuard(editor.text).code === "document-too-large" ? react_jsx_runtime.jsx("span", { style: S.error, children: t("capability.mcp.tooLarge", "JSON exceeds the 1 MiB limit; no preview or write occurred.") }) : null,
|
|
446
|
+
previewView(editor.preview),
|
|
447
|
+
resultView(editor.result),
|
|
448
|
+
items.length > 0 ? react_jsx_runtime.jsxs("div", { style: S.field, children: items.map(item => react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
449
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
450
|
+
react_jsx_runtime.jsx("strong", { style: S.name, children: item.identity }),
|
|
451
|
+
tag(item.status, item.succeeded ? SUCCESS : DANGER),
|
|
452
|
+
item.saved ? tag(t("capability.mcp.saved", "saved")) : null,
|
|
453
|
+
item.notAttempted ? tag(t("capability.mcp.notAttempted", "not attempted"), WARN) : null,
|
|
454
|
+
] }),
|
|
455
|
+
item.error ? react_jsx_runtime.jsx("span", { style: S.error, children: item.error.message ?? item.error.code }) : null,
|
|
456
|
+
!item.succeeded ? button(t("capability.mcp.retryEdit", "Retry / edit"), { onClick: () => retryItem(item) }) : null,
|
|
457
|
+
] }, item.identity)) }) : null,
|
|
458
|
+
react_jsx_runtime.jsxs("div", { style: S.toolbar, children: [
|
|
459
|
+
button(t("capability.mcp.preview", "Preview"), { disabled: editor.busy, onClick: previewJson }),
|
|
460
|
+
editor.preview ? button(t("capability.mcp.confirmBatch", "Confirm batch"), { primary: true, disabled: editor.busy || unsupported.length > 0, ref: originRef("batch-confirm"), onClick: executeJson }) : null,
|
|
461
|
+
button(t("capability.mcp.cancel", "Cancel"), { disabled: editor.busy, onClick: () => setEditor(null) }),
|
|
462
|
+
] }),
|
|
463
|
+
] });
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const serverRow = (server) => {
|
|
467
|
+
const identity = server.identity;
|
|
468
|
+
const selected = props.draft?.mcpServers?.includes(identity) === true;
|
|
469
|
+
const state = model.mcpStateOf(server);
|
|
470
|
+
const discovery = server.discovery?.toolCount;
|
|
471
|
+
// Row lifecycle actions stay disabled while an editor action (Preview/
|
|
472
|
+
// Confirm/Test) is in flight; the Host serializes same-identity work too.
|
|
473
|
+
const rowBusy = pending !== null || editor?.busy === true;
|
|
474
|
+
// Residue exists when cleanup failed or could not be confirmed; the
|
|
475
|
+
// Host's mcp 'recover' action sweeps it and strictly remounts.
|
|
476
|
+
const residue = state === "cleanup-failed" || state === "cleanup-unknown" || server.lastMount?.diagnostic?.recovery?.action === "recover-residue";
|
|
477
|
+
return react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
478
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
479
|
+
react_jsx_runtime.jsx("input", {
|
|
480
|
+
type: "checkbox", checked: selected, disabled: props.selectionDisabled || pending !== null,
|
|
481
|
+
"aria-label": t("capability.mcp.selectSession", "Select {identity} for this session").replace("{identity}", identity),
|
|
482
|
+
onChange: () => props.toggle("mcpServers", identity),
|
|
483
|
+
}),
|
|
484
|
+
react_jsx_runtime.jsx("strong", { style: S.name, children: server.label ?? identity }),
|
|
485
|
+
tag(state, state === "mounted" ? SUCCESS : state === "degraded" || state === "conflict" ? DANGER : undefined),
|
|
486
|
+
server.defaultEnabled ? tag(t("capability.mcp.default", "default")) : null,
|
|
487
|
+
selected ? tag(t("capability.mcp.selected", "selected")) : null,
|
|
488
|
+
] }),
|
|
489
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: `${identity} · ${server.serverName ?? identity} · ${server.transport ?? "unknown"}` }),
|
|
490
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: `incarnation ${server.incarnation ?? "legacy"} · generation ${server.generation ?? "?"}` }),
|
|
491
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: discovery == null ? t("capability.mcp.toolsUnknown", "Tools: unknown") : t("capability.mcp.toolsCount", "Tools: {count}").replace("{count}", String(discovery)) }),
|
|
492
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.liveUnknown", "Live reconnect/process/stderr: unknown") }),
|
|
493
|
+
server.lastMount ? react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.lastMount", "Last mount: {status} / generation {generation}").replace("{status}", server.lastMount.status ?? "unknown").replace("{generation}", String(server.lastMount.generation ?? "?")) }) : null,
|
|
494
|
+
server.lastTest ? react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.lastTest", "Last test: {status}").replace("{status}", server.lastTest.status ?? "unknown") }) : null,
|
|
495
|
+
server.lastMount?.diagnostic?.message ? react_jsx_runtime.jsx("span", { style: S.error, children: server.lastMount.diagnostic.message }) : null,
|
|
496
|
+
server.lastTest?.diagnostic?.message ? react_jsx_runtime.jsx("span", { style: S.error, children: server.lastTest.diagnostic.message }) : null,
|
|
497
|
+
(server.startErrors ?? []).map((error, index) => react_jsx_runtime.jsx("span", { style: S.error, children: error.reason }, index)),
|
|
498
|
+
react_jsx_runtime.jsxs("div", { style: S.toolbar, children: [
|
|
499
|
+
button(t("capability.mcp.edit", "Edit"), { disabled: rowBusy, onClick: () => openForm(server) }),
|
|
500
|
+
button(t("capability.mcp.restart", "Restart"), { disabled: rowBusy, onClick: () => rowOperation("restart", server) }),
|
|
501
|
+
button(server.defaultEnabled ? t("capability.mcp.defaultOff", "Disable default") : t("capability.mcp.defaultOn", "Enable default"), { disabled: rowBusy, onClick: () => rowOperation("setDefault", server) }),
|
|
502
|
+
residue ? button(t("capability.mcp.recover", "Recover"), { disabled: rowBusy, onClick: () => rowOperation("recover", server) }) : null,
|
|
503
|
+
button(t("capability.mcp.remove", "Remove"), { disabled: rowBusy, ref: originRef(`remove:${identity}`), onClick: () => setConfirmation({ kind: "remove", identity, origin: `remove:${identity}` }) }),
|
|
504
|
+
pending?.endsWith(`:${identity}`) ? react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.pending", "Pending…") }) : null,
|
|
505
|
+
] }),
|
|
506
|
+
] }, identity);
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const dialog = confirmation ? react_jsx_runtime.jsx("div", { style: S.dialogShade, children: react_jsx_runtime.jsxs("div", {
|
|
510
|
+
role: "dialog", "aria-modal": "true", "aria-labelledby": "weir-mcp-confirm-title", style: S.dialog, onKeyDown: confirmationKeys,
|
|
511
|
+
children: [
|
|
512
|
+
react_jsx_runtime.jsx("strong", { id: "weir-mcp-confirm-title", style: S.name, children: confirmation.kind === "remove" ? t("capability.mcp.removeTitle", "Remove MCP server?") : confirmation.kind === "namespace" ? t("capability.mcp.namespaceTitle", "Change public namespace?") : t("capability.mcp.batchTitle", "Install this MCP batch?") }),
|
|
513
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: confirmation.kind === "remove"
|
|
514
|
+
? t("capability.mcp.removeConfirm", "Remove {identity}, close admission, await disposal, and remove its new-session default. Draft and accepted session selections are not rewritten.").replace("{identity}", confirmation.identity)
|
|
515
|
+
: confirmation.kind === "namespace"
|
|
516
|
+
? t("capability.mcp.namespaceConfirm", "Change public serverName from {from} to {to}? Existing tool names and calls change after the strict replacement is ready.").replace("{from}", confirmation.from).replace("{to}", confirmation.to)
|
|
517
|
+
: t("capability.mcp.batchConfirm", "Probe and install {count} server(s) independently using digest {digest}. Results may be partial; there is no atomic rollback.").replace("{count}", String(confirmation.count)).replace("{digest}", confirmation.digest ?? "unknown") }),
|
|
518
|
+
react_jsx_runtime.jsxs("div", { style: S.toolbar, children: [
|
|
519
|
+
button(t("capability.mcp.cancel", "Cancel"), { ref: cancelButton, onClick: () => closeConfirmation(true) }),
|
|
520
|
+
button(confirmation.kind === "remove" ? t("capability.mcp.removeConfirmButton", "Remove server") : t("capability.mcp.confirm", "Confirm"), { primary: true, ref: confirmButton, onClick: confirmAction }),
|
|
521
|
+
] }),
|
|
522
|
+
],
|
|
523
|
+
}) }) : null;
|
|
524
|
+
|
|
525
|
+
return react_jsx_runtime.jsxs("div", { style: S.view, children: [
|
|
526
|
+
react_jsx_runtime.jsxs("div", { style: S.toolbar, children: [
|
|
527
|
+
button(t("capability.mcp.add", "+ Add managed MCP server"), { disabled: editor !== null, onClick: () => openForm(null) }),
|
|
528
|
+
button(t("capability.mcp.pasteJson", "Paste JSON"), { disabled: editor !== null, onClick: () => openJson() }),
|
|
529
|
+
button(t("capability.mcp.refresh", "Refresh"), { disabled: pending !== null, onClick: refresh }),
|
|
530
|
+
listing.stale ? tag(t("capability.mcp.stale", "stale — last good inventory"), WARN) : null,
|
|
531
|
+
] }),
|
|
532
|
+
listing.mcpUnavailable ? react_jsx_runtime.jsx("div", { role: "alert", style: S.error, children: t("capability.mcp.unavailableRegistry", "MCP registry unavailable: {status}. Last-good rows, when present, are stale.").replace("{status}", listing.mcpUnavailable.status ?? "unknown") }) : null,
|
|
533
|
+
(listing.mcpWarnings ?? []).map((warning, index) => react_jsx_runtime.jsx("span", { style: S.warning, children: String(warning) }, index)),
|
|
534
|
+
notice ? resultView(notice) : null,
|
|
535
|
+
editor?.kind === "form" ? formEditor() : editor?.kind === "json" ? jsonEditor() : null,
|
|
536
|
+
managed.length > 0 ? react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
537
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
538
|
+
react_jsx_runtime.jsx("strong", { style: S.name, children: t("capability.mcp.groupManaged", "Weir managed") }),
|
|
539
|
+
tag(String(managed.length)),
|
|
540
|
+
] }),
|
|
541
|
+
...managed.map(serverRow),
|
|
542
|
+
] }) : null,
|
|
543
|
+
unmanaged.length > 0 ? react_jsx_runtime.jsxs("div", { style: S.card, children: [
|
|
544
|
+
react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
545
|
+
react_jsx_runtime.jsx("strong", { style: S.name, children: t("capability.mcp.groupUnmanaged", "Unmanaged") }),
|
|
546
|
+
tag(String(unmanaged.length)),
|
|
547
|
+
] }),
|
|
548
|
+
...unmanaged.map(server => react_jsx_runtime.jsxs("div", { style: S.row, children: [
|
|
549
|
+
react_jsx_runtime.jsx("span", { style: S.name, children: server.serverName ?? "unknown" }),
|
|
550
|
+
tag(t("capability.unmanaged", "unmanaged")),
|
|
551
|
+
react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.unmanagedHint", "Configured outside Weir; read-only in this iteration.") }),
|
|
552
|
+
] }, server.serverName ?? "unknown")),
|
|
553
|
+
] }) : null,
|
|
554
|
+
managed.length === 0 && unmanaged.length === 0 && !listing.mcpUnavailable ? react_jsx_runtime.jsx("span", { style: S.meta, children: t("capability.mcp.none", "No MCP servers configured.") }) : null,
|
|
555
|
+
dialog,
|
|
556
|
+
] });
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
exports.McpManagerBoundary = McpManagerBoundary;
|
|
560
|
+
exports.McpManagerView = McpManagerView;
|
|
561
|
+
return module.exports;
|
|
562
|
+
}
|
|
563
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weir-harness",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Weir \u2014 orchestrator-centric DSH agent preset: intent gate, category delegation, todo driver, pull-based background jobs, auto-compaction, hashline edit.",
|
|
6
6
|
"license": "MIT",
|