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
package/cordis.patch.yml CHANGED
@@ -40,12 +40,6 @@
40
40
  worktreeAutoSetup: true
41
41
  worktreeWatchTimeoutMinutes: 360
42
42
  worktreeAutoApprove: auto-clean
43
- editLockEnabled: true
44
- # Message-driven auto-resume (edit-lock-auto-resume-on-message): a genuine
45
- # user message resumes a stopped session and confirms its retained files.
46
- # Read live per message, so toggling applies without a restart.
47
- editLockAutoResume: true
48
- editLockStaleSweep: true
49
43
  robashEnabled: true
50
44
  # The five whitelist tables are NOT declared here on purpose. A patch
51
45
  # row's `config` is replaced WHOLE rather than deep-merged, so a default
@@ -185,10 +179,6 @@
185
179
  compaction: true
186
180
  toolResultPruner: true
187
181
  weirCompactionCoordinator: true
188
- # 编辑锁服务:提供者 edit-lock 与消费者 todo-driver / hashline-edit /
189
- # lsp 同在本组。预设服务必须 isolate,否则预设加载以
190
- # "Preset services require isolate realms" 失败。
191
- weirEditLock: true
192
182
  # Worktree lanes service: provider (worktree) and consumer
193
183
  # (delegate) share this realm (S24 / S26 S-A).
194
184
  weirWorktreeLanes: true
@@ -241,10 +231,10 @@
241
231
  # F4 todo 续推:空转续推 + 用户打断/供应商错误语义 + 逃生舱
242
232
  - id: todo-driver
243
233
  name: 'weir-harness/todo-driver'
244
- # 编辑锁(默认开):显式保存 false 后重启才关闭;须在 hashline-edit / lsp 前挂载。
245
- # 两个消费者挂载时捕获隔离组内的锁服务。
246
- - id: edit-lock
247
- name: 'weir-harness/edit-lock'
234
+ # 文件租约(编辑锁新实现):进程内共享一个租约 client,发布五个所有权
235
+ # 工具;不发布 cordis 服务,因此不需要 isolate 组,也不再约束行序。
236
+ - id: file-lease
237
+ name: 'weir-harness/file-lease'
248
238
  # F7 锚点编辑:read 锚点增强 + fail-closed hash_edit 工具
249
239
  - id: hashline-edit
250
240
  name: 'weir-harness/hashline-edit'
@@ -378,7 +368,6 @@
378
368
  compaction: true
379
369
  toolResultPruner: true
380
370
  weirCompactionCoordinator: true
381
- weirEditLock: true
382
371
  weirWorktreeLanes: true
383
372
  weirBlackboard: true
384
373
  config:
@@ -418,9 +407,9 @@
418
407
  name: 'weir-harness/blackboard'
419
408
  - id: todo-driver
420
409
  name: 'weir-harness/todo-driver'
421
- # Must precede hashline-edit / lsp (mount-time capture).
422
- - id: edit-lock
423
- name: 'weir-harness/edit-lock'
410
+ # File leases (the edit-lock rebuild): no service, no ordering constraint.
411
+ - id: file-lease
412
+ name: 'weir-harness/file-lease'
424
413
  - id: hashline-edit
425
414
  name: 'weir-harness/hashline-edit'
426
415
  config:
@@ -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,