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
@@ -233,6 +233,8 @@ window.__ModuleLoader__.load({
233
233
  // would sit on the perpetual loading surface (12.2).
234
234
  fetchListing: props.fetchListing, fetchConditions: props.fetchConditions,
235
235
  fetchReceipt: props.fetchReceipt, applySelection: props.applySelection, mcpAdd: props.mcpAdd,
236
+ mcpPreview: props.mcpPreview, mcpMutate: props.mcpMutate, mcpBatch: props.mcpBatch,
237
+ McpManagerView: props.McpManagerView, McpManagerBoundary: props.McpManagerBoundary,
236
238
  // The preset/workspace-default verbs and the presets-chunk
237
239
  // loader travel the same prop bridge (D3); an old composition
238
240
  // root leaves them undefined and the view degrades to its
@@ -541,46 +541,55 @@ window.__ModuleLoader__.load({
541
541
  const [draft, setDraft] = react.useState(null);
542
542
  const [receiptState, setReceiptState] = react.useState(null);
543
543
  const receiptRequest = react.useRef(0);
544
+ const listingRequest = react.useRef(0);
544
545
  const applying = react.useRef(false);
545
546
  const [commit, setCommit] = react.useState(null);
546
- // Managed MCP add form (8.1/8.2): per-field errors are derived by the
547
- // pure model (mcpAddErrorsOf) and shown after the first submit
548
- // attempt; the hook lives with the others — a hook after an early
549
- // return crashes with React #310.
550
- const [addForm, setAddForm] = react.useState({ open: false, identity: "", label: "", command: "", args: "", showErrors: false, error: null, busy: false });
551
547
 
552
- const refreshReceipt = () => {
553
- if (typeof props.fetchReceipt !== "function") return;
548
+ const refreshReceipt = async () => {
549
+ if (typeof props.fetchReceipt !== "function") return null;
554
550
  const request = ++receiptRequest.current;
555
- props.fetchReceipt(props.sessionId).then((receipt) => {
556
- if (request !== receiptRequest.current) return;
557
- setReceiptState(model.badgeStateOf(receipt, null));
558
- setDraft(current => model.draftAfterRefresh(current, receipt));
559
- }).catch(() => {
551
+ try {
552
+ const receipt = await props.fetchReceipt(props.sessionId);
553
+ if (request === receiptRequest.current) {
554
+ setReceiptState(model.badgeStateOf(receipt, null));
555
+ setDraft(current => model.draftAfterRefresh(current, receipt));
556
+ }
557
+ return receipt;
558
+ } catch {
560
559
  if (request === receiptRequest.current) {
561
560
  setReceiptState(null);
562
561
  setDraft(current => model.draftAfterRefresh(current, null));
563
562
  }
564
- });
563
+ return null;
564
+ }
565
565
  };
566
- const refreshAll = () => {
567
- if (typeof props.fetchListing === "function") {
568
- props.fetchListing(props.sessionId).then((data) => setListing(data ?? { error: true })).catch(() => setListing({ error: true }));
566
+ const refreshListing = async () => {
567
+ if (typeof props.fetchListing !== "function") return null;
568
+ const request = ++listingRequest.current;
569
+ try {
570
+ const data = await props.fetchListing(props.sessionId);
571
+ const folded = model.mcpListingAfterRead(listing, data ?? { error: true });
572
+ if (request === listingRequest.current) setListing(folded);
573
+ return folded;
574
+ } catch {
575
+ const folded = model.mcpListingAfterRead(listing, { error: true });
576
+ if (request === listingRequest.current) setListing(folded);
577
+ return folded;
569
578
  }
570
- refreshReceipt();
579
+ };
580
+ const refreshAll = async () => {
581
+ const [freshListing] = await Promise.all([refreshListing(), refreshReceipt()]);
582
+ return { listing: freshListing };
571
583
  };
572
584
  react.useEffect(() => {
573
585
  let alive = true;
574
- if (typeof props.fetchListing === "function") {
575
- // A failed or absent verb settles into an explicit error
576
- // state — never a perpetual loading panel (12.2).
577
- props.fetchListing(props.sessionId).then((data) => { if (alive) setListing(data ?? { error: true }); }).catch(() => { if (alive) setListing({ error: true }); });
578
- }
586
+ setListing(null);
587
+ setConditions(null);
588
+ void refreshAll();
579
589
  if (typeof props.fetchConditions === "function") {
580
590
  props.fetchConditions(props.sessionId).then((data) => { if (alive) setConditions(data); }).catch(() => {});
581
591
  }
582
- refreshReceipt();
583
- return () => { alive = false; ++receiptRequest.current; };
592
+ return () => { alive = false; ++receiptRequest.current; ++listingRequest.current; };
584
593
  }, [props.sessionId]);
585
594
  const toggle = (kind, name) => {
586
595
  if (draft && commit?.phase !== "submitting") { setDraft(model.draftToggle(draft, kind, name)); setCommit(null); }
@@ -623,31 +632,6 @@ window.__ModuleLoader__.load({
623
632
  setDraft(model.draftFromPreset(document, draft, resolveName));
624
633
  setCommit(null);
625
634
  };
626
- // Managed MCP add form (8.1/8.2): registers a stdio server and mounts
627
- // it immediately via /capabilities mcp-add. Client-side per-field
628
- // validation first; the server's own status still surfaces verbatim.
629
- const addErrors = model.mcpAddErrorsOf(addForm);
630
- const submitAdd = () => {
631
- if (typeof props.mcpAdd !== "function" || addForm.busy) return;
632
- if (Object.keys(addErrors).length > 0) {
633
- setAddForm({ ...addForm, showErrors: true, error: null });
634
- return;
635
- }
636
- setAddForm({ ...addForm, busy: true, error: null });
637
- props.mcpAdd(props.sessionId, {
638
- identity: addForm.identity.trim(),
639
- label: addForm.label.trim() || addForm.identity.trim(),
640
- command: addForm.command.trim(),
641
- args: addForm.args.trim() ? addForm.args.trim().split(/\s+/) : [],
642
- }).then((result) => {
643
- if (result && result.status === "registered") {
644
- setAddForm({ open: false, identity: "", label: "", command: "", args: "", showErrors: false, error: null, busy: false });
645
- refreshAll();
646
- } else {
647
- setAddForm({ ...addForm, busy: false, error: (result && result.status) || "add failed" });
648
- }
649
- }).catch((cause) => setAddForm({ ...addForm, busy: false, error: String(cause) }));
650
- };
651
635
  const frameStyle = props.shell === "sidebar" ? S.frame.sidebar : S.frame.popover;
652
636
  const conditionState = model.managerConditionState(conditions?.conditions ?? conditions);
653
637
  // Loading: row-height skeleton blocks (D1) with the loading text kept
@@ -734,61 +718,8 @@ window.__ModuleLoader__.load({
734
718
  .replace("{name}", item.name).replace("{sources}", item.candidates.map(sourceOf).join(", "))),
735
719
  ...unknown.map(item => t("capability.unknownLegacyName", "{name}: no matching Skill in the current listing. Refresh the listing or remove this legacy name.").replace("{name}", item.name))
736
720
  ];
737
- const draftHas = (kind, name) => draft !== null && draft[kind].includes(name);
738
721
  const selectionDisabled = draft === null || commit?.phase === "submitting";
739
- const fieldError = (code) => code === "invalid"
740
- ? t("capability.mcp.errorIdentityInvalid", "Letters, digits, \"-\" and \"_\" only; start with a letter or digit.")
741
- : t("capability.mcp.errorRequired", "Required.");
742
- const addField = (key, placeholder, errorCode) => react_jsx_runtime.jsxs("div", {
743
- children: [
744
- react_jsx_runtime.jsx("input", {
745
- style: S.form.input,
746
- placeholder,
747
- value: addForm[key],
748
- "aria-invalid": errorCode ? true : undefined,
749
- ...ring,
750
- onChange: (event) => setAddForm({ ...addForm, [key]: event.target.value })
751
- }),
752
- errorCode ? react_jsx_runtime.jsx("div", { style: S.form.error, children: fieldError(errorCode) }) : null
753
- ]
754
- }, key);
755
- const addBlock = tab === "mcp" ? react_jsx_runtime.jsxs("div", { style: S.form.wrap, children: [
756
- !addForm.open
757
- ? react_jsx_runtime.jsx("button", {
758
- type: "button",
759
- style: S.form.addButton,
760
- ...ring,
761
- ...hover(HOVER_BG, "none"),
762
- onClick: () => setAddForm({ ...addForm, open: true, showErrors: false, error: null }),
763
- children: t("capability.mcp.add", "+ Add managed MCP server")
764
- })
765
- : react_jsx_runtime.jsxs("div", { style: S.form.box, children: [
766
- react_jsx_runtime.jsx("div", { style: S.form.title, children: t("capability.mcp.addTitle", "Add managed MCP server") }),
767
- addField("identity", t("capability.mcp.identity", "identity (e.g. my-docs)"), addForm.showErrors ? addErrors.identity : undefined),
768
- addField("label", t("capability.mcp.label", "label (display name, optional)"), undefined),
769
- addField("command", t("capability.mcp.command", "command (e.g. npx)"), addForm.showErrors ? addErrors.command : undefined),
770
- addField("args", t("capability.mcp.args", "args, space-separated (optional)"), undefined),
771
- react_jsx_runtime.jsxs("div", { style: S.form.actions, children: [
772
- react_jsx_runtime.jsx("button", {
773
- type: "button",
774
- style: addForm.busy ? { ...S.form.primary, ...S.footer.disabled } : S.form.primary,
775
- ...ring,
776
- disabled: addForm.busy,
777
- onClick: submitAdd,
778
- children: addForm.busy ? t("capability.mcp.adding", "Adding…") : t("capability.mcp.addConfirm", "Add")
779
- }),
780
- react_jsx_runtime.jsx("button", {
781
- type: "button",
782
- style: S.form.ghost,
783
- ...ring,
784
- ...hover(HOVER_BG, "none"),
785
- onClick: () => setAddForm({ ...addForm, open: false, showErrors: false, error: null }),
786
- children: t("capability.mcp.cancel", "Cancel")
787
- }),
788
- addForm.error ? react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.warn }, children: String(addForm.error) }) : null
789
- ] })
790
- ] })
791
- ] }) : null;
722
+ const addBlock = null;
792
723
  const skillRow = (skill, index) => {
793
724
  const selected = skillKeys.has(model.skillEntryKeyOf(skill.identity ?? skill.name));
794
725
  const selectedConflict = selectedKeys.has(model.skillEntryKeyOf(skill.identity));
@@ -820,45 +751,6 @@ window.__ModuleLoader__.load({
820
751
  ]
821
752
  }, `${model.skillIdentityKeyOf(skill.identity) ?? `${skill.scopeKey}:${skill.name}`}:${index}`);
822
753
  };
823
- const mcpRow = (server) => {
824
- const name = server.identity ?? server.serverName ?? "unknown";
825
- const selected = draftHas("mcpServers", name);
826
- const rest = selected ? tint(ACCENT, 8) : "transparent";
827
- const mounted = server.state === "mounted";
828
- return react_jsx_runtime.jsxs("label", {
829
- style: { ...S.row.base, background: rest },
830
- ...ring,
831
- ...hover(selected ? tint(ACCENT, 12) : HOVER_BG, rest),
832
- children: [
833
- react_jsx_runtime.jsx("input", {
834
- type: "checkbox",
835
- style: S.row.checkbox,
836
- checked: selected,
837
- disabled: selectionDisabled,
838
- "aria-label": name,
839
- onChange: () => toggle("mcpServers", name)
840
- }),
841
- react_jsx_runtime.jsxs("span", { style: { ...S.row.nameLine, flex: 1 }, children: [
842
- react_jsx_runtime.jsx("span", { style: S.row.name, title: name, children: name }),
843
- react_jsx_runtime.jsx("span", {
844
- style: { ...S.tag.base, ...(mounted ? S.tag.ok : S.tag.neutral) },
845
- children: mounted ? t("capability.mcp.stateMounted", "mounted") : t("capability.mcp.stateRegistered", "registered")
846
- })
847
- ] })
848
- ]
849
- }, name);
850
- };
851
- const unmanagedRow = (server) => {
852
- const name = server.serverName ?? "unknown";
853
- return react_jsx_runtime.jsxs("div", {
854
- style: { ...S.row.base, alignItems: "center", cursor: "default" },
855
- title: t("capability.mcp.unmanagedHint", "Configured outside Weir; the selection cannot govern it."),
856
- children: [
857
- react_jsx_runtime.jsx("span", { style: { ...S.row.name, flex: 1 }, children: name }),
858
- react_jsx_runtime.jsx("span", { style: { ...S.tag.base, ...S.tag.neutral }, children: t("capability.unmanaged", "unmanaged") })
859
- ]
860
- }, name);
861
- };
862
754
  const legacyActions = [...ambiguous, ...unknown].map(item => react_jsx_runtime.jsxs("div", { style: S.list.card, children: [
863
755
  react_jsx_runtime.jsx("div", { style: S.form.title, children: t("capability.unresolvedNameTitle", "Choose a source for legacy name {name}").replace("{name}", item.name) }),
864
756
  react_jsx_runtime.jsxs("div", { style: S.form.actions, children: [
@@ -883,43 +775,18 @@ window.__ModuleLoader__.load({
883
775
  }, group.key))), ...legacyActions]
884
776
  })
885
777
  : tab === "mcp"
886
- ? react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
887
- children: [
888
- partition.mcpManaged.length > 0
889
- ? react_jsx_runtime.jsxs("div", {
890
- style: S.list.card,
891
- children: [
892
- react_jsx_runtime.jsxs("div", {
893
- style: S.list.cardHead,
894
- children: [
895
- react_jsx_runtime.jsx("span", { children: t("capability.mcp.groupManaged", "Weir managed") }),
896
- react_jsx_runtime.jsx("span", { style: S.list.count, children: String(partition.mcpManaged.length) })
897
- ]
898
- }),
899
- ...partition.mcpManaged.map(mcpRow)
900
- ]
901
- }, "managed")
902
- : null,
903
- partition.mcpUnmanaged.length > 0
904
- ? react_jsx_runtime.jsxs("div", {
905
- style: S.list.card,
906
- children: [
907
- react_jsx_runtime.jsxs("div", {
908
- style: S.list.cardHead,
909
- children: [
910
- react_jsx_runtime.jsx("span", { children: t("capability.mcp.groupUnmanaged", "Unmanaged") }),
911
- react_jsx_runtime.jsx("span", { style: S.list.count, children: String(partition.mcpUnmanaged.length) })
912
- ]
913
- }),
914
- ...partition.mcpUnmanaged.map(unmanagedRow)
915
- ]
916
- }, "unmanaged")
917
- : null,
918
- partition.mcpManaged.length === 0 && partition.mcpUnmanaged.length === 0
919
- ? react_jsx_runtime.jsx(StatePanel, { icon: "blocks", title: t("capability.mcp.none", "No MCP servers configured.") })
920
- : null
921
- ]
922
- })
778
+ ? (() => {
779
+ if (typeof props.McpManagerView !== "function") return react_jsx_runtime.jsx(StatePanel, { icon: "warn", color: WARN_TEXT, title: t("capability.mcp.unavailableView", "MCP management is unavailable on this client.") });
780
+ const content = react_jsx_runtime.jsx(props.McpManagerView, {
781
+ sessionId: props.sessionId, model, t, listing,
782
+ managed: partition.mcpManaged, unmanaged: partition.mcpUnmanaged,
783
+ draft, toggle, selectionDisabled, refreshAll,
784
+ mcpPreview: props.mcpPreview, mcpMutate: props.mcpMutate, mcpBatch: props.mcpBatch,
785
+ });
786
+ return typeof props.McpManagerBoundary === "function"
787
+ ? react_jsx_runtime.jsx(props.McpManagerBoundary, { t, children: content })
788
+ : content;
789
+ })()
923
790
  : react_jsx_runtime.jsx(LazyCapabilityPresets, {
924
791
  sessionId: props.sessionId,
925
792
  model,