weir-harness 1.2.3 → 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.
Files changed (117) hide show
  1. package/cordis.patch.yml +7 -18
  2. package/lib/client.capability-badge.js +2 -0
  3. package/lib/client.capability-manager.js +46 -179
  4. package/lib/client.capability-model.js +428 -3
  5. package/lib/client.file-lease-badge.js +307 -0
  6. package/lib/client.file-lease-tool-model.js +189 -0
  7. package/lib/client.file-lease-tool-view.js +434 -0
  8. package/lib/client.js +124 -36
  9. package/lib/client.mcp-manager.js +563 -0
  10. package/lib/client.settings-page.js +10 -34
  11. package/package.json +2 -2
  12. package/skills/mcp-server-setup/SKILL.md +124 -0
  13. package/src/capabilities/apply-engine.js +18 -4
  14. package/src/capabilities/capability-remote.js +296 -45
  15. package/src/capabilities/initial-selection.js +3 -2
  16. package/src/capabilities/lifecycle-preload.js +69 -11
  17. package/src/capabilities/lifecycle-snapshot.js +127 -39
  18. package/src/capabilities/mcp-admission.js +2 -2
  19. package/src/capabilities/mcp-config.js +344 -0
  20. package/src/capabilities/mcp-credentials.js +180 -0
  21. package/src/capabilities/mcp-diagnostics.js +169 -0
  22. package/src/capabilities/mcp-drain.js +18 -4
  23. package/src/capabilities/mcp-facade.js +3 -3
  24. package/src/capabilities/mcp-gate.js +51 -52
  25. package/src/capabilities/mcp-install-tool.js +276 -0
  26. package/src/capabilities/mcp-manager.js +368 -142
  27. package/src/capabilities/mcp-mount.js +219 -79
  28. package/src/capabilities/mcp-operations.js +359 -0
  29. package/src/capabilities/mcp-process.js +43 -0
  30. package/src/capabilities/mcp-registry.js +74 -60
  31. package/src/capabilities/mcp-runtime.js +103 -0
  32. package/src/capabilities/read-payloads.js +8 -3
  33. package/src/capabilities/skill-applicability.js +185 -0
  34. package/src/capabilities/skill-builtin-migration.js +2 -1
  35. package/src/capabilities/skill-selection-plugin.js +161 -56
  36. package/src/capabilities/store/checked-contract.d.ts +2 -2
  37. package/src/capabilities/store/fs-adapter.js +2 -3
  38. package/src/context-guard/index.js +2 -2
  39. package/src/delegate/supervision-mount.js +5 -5
  40. package/src/delegate/tool.js +2 -10
  41. package/src/file-lease/client.js +1299 -0
  42. package/src/file-lease/index.js +34 -0
  43. package/src/file-lease/lineage.js +207 -0
  44. package/src/file-lease/node-contracts.d.ts +42 -0
  45. package/src/file-lease/plugin.js +1013 -0
  46. package/src/file-lease/singleton.js +155 -0
  47. package/src/file-lease/spawn.js +62 -0
  48. package/src/file-lease/tools.js +847 -0
  49. package/src/file-lease/view.js +144 -0
  50. package/src/file-lease/watch.js +473 -0
  51. package/src/hashline-edit/index.js +76 -42
  52. package/src/hashline-edit/write-tool.js +90 -0
  53. package/src/intent-gate/index.js +29 -12
  54. package/src/intent-gate/matcher.js +36 -2
  55. package/src/lease-broker/entry.js +98 -0
  56. package/src/lease-broker/node-contracts.d.ts +63 -0
  57. package/src/lease-broker/protocol.js +237 -0
  58. package/src/lease-broker/server.js +402 -0
  59. package/src/lease-broker/state.js +482 -0
  60. package/src/lsp/tools.js +137 -27
  61. package/src/process/index.js +4 -2
  62. package/src/settings/index.js +2 -44
  63. package/src/settings/sections.js +2 -24
  64. package/src/shared/audit.js +9 -1
  65. package/src/shared/child-scope.js +2 -0
  66. package/src/todo-driver/index.js +1 -6
  67. package/src/worktree/cards.js +6 -21
  68. package/src/worktree/lane-core.js +1 -19
  69. package/src/worktree/lane-workflows.js +11 -32
  70. package/src/worktree/rules.js +1 -1
  71. package/lib/client.edit-lock-maintenance.js +0 -592
  72. package/lib/client.edit-lock-panel.js +0 -405
  73. package/src/edit-lock/adapter.js +0 -57
  74. package/src/edit-lock/admin-ledger.js +0 -149
  75. package/src/edit-lock/admin-recovery.js +0 -151
  76. package/src/edit-lock/admission.js +0 -82
  77. package/src/edit-lock/binding-attestation.js +0 -29
  78. package/src/edit-lock/binding-guidance.js +0 -72
  79. package/src/edit-lock/binding-machine.js +0 -312
  80. package/src/edit-lock/call-context.js +0 -65
  81. package/src/edit-lock/derived-facts.js +0 -67
  82. package/src/edit-lock/domain.js +0 -165
  83. package/src/edit-lock/domains.js +0 -170
  84. package/src/edit-lock/host.js +0 -149
  85. package/src/edit-lock/incarnations.js +0 -150
  86. package/src/edit-lock/index.js +0 -1303
  87. package/src/edit-lock/inspect.js +0 -219
  88. package/src/edit-lock/lifecycle.js +0 -579
  89. package/src/edit-lock/limits.js +0 -61
  90. package/src/edit-lock/maintenance.js +0 -310
  91. package/src/edit-lock/manager.js +0 -875
  92. package/src/edit-lock/negotiation.js +0 -132
  93. package/src/edit-lock/operation-history.js +0 -214
  94. package/src/edit-lock/peer-client.js +0 -86
  95. package/src/edit-lock/peer-transport.js +0 -76
  96. package/src/edit-lock/peer.js +0 -87
  97. package/src/edit-lock/publisher.js +0 -191
  98. package/src/edit-lock/read-authority.js +0 -101
  99. package/src/edit-lock/recovery.js +0 -139
  100. package/src/edit-lock/remote.js +0 -362
  101. package/src/edit-lock/request-binding.js +0 -42
  102. package/src/edit-lock/request-data.js +0 -40
  103. package/src/edit-lock/reservation.js +0 -301
  104. package/src/edit-lock/reserved-runtime.js +0 -45
  105. package/src/edit-lock/resource-identity.js +0 -130
  106. package/src/edit-lock/runtime.js +0 -145
  107. package/src/edit-lock/settle.js +0 -144
  108. package/src/edit-lock/snapshot.js +0 -175
  109. package/src/edit-lock/stale-sweep.js +0 -120
  110. package/src/edit-lock/state.js +0 -462
  111. package/src/edit-lock/store.js +0 -327
  112. package/src/edit-lock/tool-scope.js +0 -29
  113. package/src/edit-lock/view.js +0 -170
  114. package/src/edit-lock/write-scopes.js +0 -48
  115. package/src/edit-lock/write-tool.js +0 -45
  116. package/src/shared/delegate-edit-lifecycle.js +0 -36
  117. package/src/worktree/authority.js +0 -56
@@ -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
+ });