weir-harness 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/lib/client.capability-badge.js +2 -0
  2. package/lib/client.capability-manager.js +46 -179
  3. package/lib/client.capability-model.js +428 -3
  4. package/lib/client.file-lease-tool-model.js +189 -0
  5. package/lib/client.file-lease-tool-view.js +434 -0
  6. package/lib/client.js +105 -7
  7. package/lib/client.mcp-manager.js +563 -0
  8. package/package.json +1 -1
  9. package/skills/mcp-server-setup/SKILL.md +124 -0
  10. package/src/capabilities/apply-engine.js +18 -4
  11. package/src/capabilities/capability-remote.js +296 -45
  12. package/src/capabilities/initial-selection.js +3 -2
  13. package/src/capabilities/lifecycle-preload.js +69 -11
  14. package/src/capabilities/lifecycle-snapshot.js +127 -39
  15. package/src/capabilities/mcp-admission.js +2 -2
  16. package/src/capabilities/mcp-config.js +344 -0
  17. package/src/capabilities/mcp-credentials.js +180 -0
  18. package/src/capabilities/mcp-diagnostics.js +169 -0
  19. package/src/capabilities/mcp-drain.js +18 -4
  20. package/src/capabilities/mcp-facade.js +40 -9
  21. package/src/capabilities/mcp-gate.js +51 -52
  22. package/src/capabilities/mcp-install-tool.js +276 -0
  23. package/src/capabilities/mcp-manager.js +575 -146
  24. package/src/capabilities/mcp-mount.js +220 -79
  25. package/src/capabilities/mcp-operations.js +359 -0
  26. package/src/capabilities/mcp-process.js +43 -0
  27. package/src/capabilities/mcp-registry.js +74 -60
  28. package/src/capabilities/mcp-runtime.js +103 -0
  29. package/src/capabilities/read-payloads.js +8 -3
  30. package/src/capabilities/skill-applicability.js +185 -0
  31. package/src/capabilities/skill-builtin-migration.js +2 -1
  32. package/src/capabilities/skill-selection-plugin.js +170 -57
  33. package/src/file-lease/client.js +268 -35
  34. package/src/file-lease/index.js +4 -0
  35. package/src/file-lease/plugin.js +239 -15
  36. package/src/file-lease/tools.js +426 -38
  37. package/src/file-lease/watch.js +473 -0
  38. package/src/intent-gate/index.js +29 -12
  39. package/src/intent-gate/matcher.js +36 -2
  40. package/src/lease-broker/protocol.js +3 -2
  41. package/src/lease-broker/server.js +6 -3
  42. package/src/lease-broker/state.js +68 -11
  43. package/src/process/index.js +4 -2
  44. package/src/shared/audit.js +3 -0
  45. package/src/shared/child-scope.js +2 -0
  46. 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.0.0",
3
+ "version": "2.1.1",
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",