@yancyyu/openhermit 1.6.38 → 1.6.39

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 (188) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-ZEDfZyHb.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CIhniz70.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-cKAW4Q8I.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-YmNsoDXW.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DHEls2sX.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-Bpwf1Sbg.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-B0IaQ4w5.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-DLk-hcFc.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-1XRmX_Zm.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-1waH1DAD.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-BqpZBtrN.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Bly7vVym.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-Ci2QWBAs.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-YCqFW7d-.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-B0xGXInl.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
  20. package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DxcFNQKT.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DPo_RfZY.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-U3hQsFe4.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-OrwrAy0V.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CXATPWVw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-B0e8AfMF.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CXfzA4jJ.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CMr08qVl.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-vYFHpPmy.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-DOe5j8dH.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-BySQS7AB.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-V7dAKPqd.js → index-Bu2R-Se7.js} +587 -705
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-CzWxVCRL.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-VJ-MM9xa.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-B2Dy7M2G.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-C_okzZXP.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D_WubR0B.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-w9ca-1TI.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-Jg9p6_pN.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-B-z3y17c.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-D-RTX5UW.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-CDQmHOYP.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D_odsQL7.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BRsmYWSA.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-ChNE_BOV.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-C8FtpwKc.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-DmLCzNcc.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-WJBm4bhu.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
  51. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
  52. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BXs_hOJs.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-o04MA0G9.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-Czj69XRd.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
  55. package/dist-renderer/index.html +20 -53
  56. package/package.json +25 -18
  57. package/src/main/ipc/extensions.ts +2 -1
  58. package/src/main/server.ts +873 -221
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +2 -5
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  62. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  63. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  64. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  65. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  66. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  67. package/src/main/services/teams-mvp/TaskDispatchService.ts +5 -6
  68. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  69. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  70. package/src/main/utils/teamProjectResolution.ts +15 -0
  71. package/src/renderer/App.tsx +8 -4
  72. package/src/renderer/api/httpClient.ts +68 -18
  73. package/src/renderer/api/providers.ts +23 -2
  74. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  75. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  76. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  87. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  88. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  89. package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -125
  90. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  91. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  92. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  93. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  94. package/src/renderer/components/extensions/plugins/PluginCard.tsx +6 -6
  95. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +34 -21
  96. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  97. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  98. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  99. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  100. package/src/renderer/components/layout/TabBar.tsx +1 -1
  101. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  102. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  103. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  104. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  105. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  106. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  107. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  108. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  109. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  110. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  111. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  112. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  113. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  114. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  115. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  116. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  117. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  118. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  119. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  120. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  121. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  122. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  123. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  124. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  125. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  126. package/src/renderer/components/settings/sections/index.ts +0 -1
  127. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  128. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  129. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  130. package/src/renderer/components/splash/splashScene.ts +121 -929
  131. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  132. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  133. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  134. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  135. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  136. package/src/renderer/components/team/TeamDetailView.tsx +319 -123
  137. package/src/renderer/components/team/TeamListView.tsx +108 -123
  138. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  139. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  140. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  141. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  142. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  143. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  144. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  145. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  146. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  147. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  148. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  149. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  150. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  151. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  152. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  153. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  154. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  155. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  156. package/src/renderer/components/team/tools/AddMcpInline.tsx +27 -17
  157. package/src/renderer/components/team/tools/McpChip.tsx +6 -3
  158. package/src/renderer/components/team/tools/SkillChip.tsx +2 -2
  159. package/src/renderer/components/team/tools/ToolsSection.tsx +418 -70
  160. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  161. package/src/renderer/index.css +39 -22
  162. package/src/renderer/index.html +17 -50
  163. package/src/renderer/store/index.ts +2 -1
  164. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  165. package/src/renderer/store/slices/teamSlice.ts +45 -168
  166. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  167. package/src/renderer/utils/memberHelpers.ts +5 -17
  168. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  169. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  170. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  171. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  172. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  173. package/src/shared/types/api.ts +121 -2
  174. package/src/shared/types/ccConnect.ts +2 -0
  175. package/src/shared/types/index.ts +3 -0
  176. package/src/shared/types/systemManager.ts +49 -0
  177. package/src/shared/types/team.ts +29 -0
  178. package/src/shared/types/terminal.ts +4 -2
  179. package/src/shared/utils/extensionNormalizers.ts +15 -8
  180. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  181. package/dist-renderer/assets/ProjectEditorOverlay-lJZi-9Hp.js +0 -52
  182. package/dist-renderer/assets/channel-yIlSKy0e.js +0 -1
  183. package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +0 -1
  184. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +0 -1
  185. package/dist-renderer/assets/clone-BTNuUva-.js +0 -1
  186. package/dist-renderer/assets/index-Bi6nrZ4z.css +0 -1
  187. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  188. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-_m6iPPUR.js +0 -1
@@ -0,0 +1,418 @@
1
+ /**
2
+ * McpLibraryEntryDialog — create or edit reusable global MCP server definitions.
3
+ */
4
+
5
+ import { useEffect, useMemo, useRef, useState } from 'react';
6
+
7
+ import { api } from '@renderer/api';
8
+ import { Button } from '@renderer/components/ui/button';
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogDescription,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '@renderer/components/ui/dialog';
16
+ import { Input } from '@renderer/components/ui/input';
17
+ import { Label } from '@renderer/components/ui/label';
18
+ import {
19
+ Select,
20
+ SelectContent,
21
+ SelectItem,
22
+ SelectTrigger,
23
+ SelectValue,
24
+ } from '@renderer/components/ui/select';
25
+ import { Textarea } from '@renderer/components/ui/textarea';
26
+ import { Plus, Server, Trash2 } from 'lucide-react';
27
+
28
+ import type {
29
+ McpHeaderDef,
30
+ McpInstallSpec,
31
+ McpLibraryEntry,
32
+ McpLibraryUpsertRequest,
33
+ } from '@shared/types/extensions';
34
+
35
+ type TransportMode = 'stdio' | 'http';
36
+ type HttpTransport = 'streamable-http' | 'sse' | 'http';
37
+
38
+ type EnvRow = { key: string; value: string };
39
+
40
+ const HTTP_TRANSPORT_OPTIONS: { value: HttpTransport; label: string }[] = [
41
+ { value: 'streamable-http', label: 'Streamable HTTP' },
42
+ { value: 'sse', label: 'SSE' },
43
+ { value: 'http', label: 'HTTP' },
44
+ ];
45
+
46
+ interface McpLibraryEntryDialogProps {
47
+ open: boolean;
48
+ entry: McpLibraryEntry | null;
49
+ onClose: () => void;
50
+ onSaved: (entry: McpLibraryEntry) => void;
51
+ }
52
+
53
+ function envRowsFromRecord(values?: Record<string, string>): EnvRow[] {
54
+ return Object.entries(values ?? {}).map(([key, value]) => ({ key, value }));
55
+ }
56
+
57
+ function envRecordFromRows(rows: EnvRow[]): Record<string, string> | undefined {
58
+ const entries = rows
59
+ .map((row) => [row.key.trim(), row.value] as const)
60
+ .filter(([key]) => key.length > 0);
61
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
62
+ }
63
+
64
+ export const McpLibraryEntryDialog = ({
65
+ open,
66
+ entry,
67
+ onClose,
68
+ onSaved,
69
+ }: McpLibraryEntryDialogProps): React.JSX.Element => {
70
+ const [name, setName] = useState('');
71
+ const [description, setDescription] = useState('');
72
+ const [transportMode, setTransportMode] = useState<TransportMode>('stdio');
73
+ const [npmPackage, setNpmPackage] = useState('');
74
+ const [npmVersion, setNpmVersion] = useState('');
75
+ const [httpUrl, setHttpUrl] = useState('');
76
+ const [httpTransport, setHttpTransport] = useState<HttpTransport>('streamable-http');
77
+ const [envRows, setEnvRows] = useState<EnvRow[]>([]);
78
+ const [headers, setHeaders] = useState<McpHeaderDef[]>([]);
79
+ const [saving, setSaving] = useState(false);
80
+ const [error, setError] = useState<string | null>(null);
81
+ const wasOpenRef = useRef(false);
82
+
83
+ useEffect(() => {
84
+ const justOpened = open && !wasOpenRef.current;
85
+ if (justOpened) {
86
+ setName(entry?.name ?? '');
87
+ setDescription(entry?.description ?? '');
88
+ setEnvRows(envRowsFromRecord(entry?.envValues));
89
+ setHeaders(entry?.headers ?? []);
90
+ setError(null);
91
+ setSaving(false);
92
+
93
+ if (!entry || entry.installSpec.type === 'stdio') {
94
+ setTransportMode('stdio');
95
+ setNpmPackage(entry?.installSpec.type === 'stdio' ? entry.installSpec.npmPackage : '');
96
+ setNpmVersion(
97
+ entry?.installSpec.type === 'stdio' ? (entry.installSpec.npmVersion ?? '') : ''
98
+ );
99
+ setHttpUrl('');
100
+ setHttpTransport('streamable-http');
101
+ } else {
102
+ setTransportMode('http');
103
+ setNpmPackage('');
104
+ setNpmVersion('');
105
+ setHttpUrl(entry.installSpec.url);
106
+ setHttpTransport(entry.installSpec.transportType);
107
+ }
108
+ }
109
+ wasOpenRef.current = open;
110
+ }, [entry, open]);
111
+
112
+ const canSubmit = useMemo(() => {
113
+ if (saving || !name.trim()) return false;
114
+ return transportMode === 'stdio' ? Boolean(npmPackage.trim()) : Boolean(httpUrl.trim());
115
+ }, [httpUrl, name, npmPackage, saving, transportMode]);
116
+
117
+ const addEnvRow = (): void => setEnvRows((prev) => [...prev, { key: '', value: '' }]);
118
+ const removeEnvRow = (index: number): void =>
119
+ setEnvRows((prev) => prev.filter((_, i) => i !== index));
120
+ const updateEnvRow = (index: number, field: keyof EnvRow, value: string): void =>
121
+ setEnvRows((prev) => prev.map((row, i) => (i === index ? { ...row, [field]: value } : row)));
122
+
123
+ const addHeader = (): void => setHeaders((prev) => [...prev, { key: '', value: '' }]);
124
+ const removeHeader = (index: number): void =>
125
+ setHeaders((prev) => prev.filter((_, i) => i !== index));
126
+ const updateHeader = (index: number, field: 'key' | 'value', value: string): void =>
127
+ setHeaders((prev) =>
128
+ prev.map((header, i) => (i === index ? { ...header, [field]: value } : header))
129
+ );
130
+
131
+ const handleSave = async (): Promise<void> => {
132
+ if (!api.mcpRegistry?.libraryUpsert) {
133
+ setError('MCP 能力库 API 不可用');
134
+ return;
135
+ }
136
+
137
+ const trimmedName = name.trim();
138
+ if (!trimmedName) {
139
+ setError('服务器名称不能为空');
140
+ return;
141
+ }
142
+
143
+ let installSpec: McpInstallSpec;
144
+ if (transportMode === 'stdio') {
145
+ if (!npmPackage.trim()) {
146
+ setError('npm 包名不能为空');
147
+ return;
148
+ }
149
+ installSpec = {
150
+ type: 'stdio',
151
+ npmPackage: npmPackage.trim(),
152
+ npmVersion: npmVersion.trim() || undefined,
153
+ };
154
+ } else {
155
+ if (!httpUrl.trim()) {
156
+ setError('服务器 URL 不能为空');
157
+ return;
158
+ }
159
+ installSpec = {
160
+ type: 'http',
161
+ url: httpUrl.trim(),
162
+ transportType: httpTransport,
163
+ };
164
+ }
165
+
166
+ const request: McpLibraryUpsertRequest = {
167
+ id: entry?.id,
168
+ name: trimmedName,
169
+ description: description.trim() || undefined,
170
+ installSpec,
171
+ envValues: envRecordFromRows(envRows),
172
+ headers: headers
173
+ .map((header) => ({ ...header, key: header.key.trim() }))
174
+ .filter((header) => header.key && header.value),
175
+ };
176
+
177
+ setSaving(true);
178
+ setError(null);
179
+ try {
180
+ const saved = await api.mcpRegistry.libraryUpsert(request);
181
+ onSaved(saved);
182
+ } catch (err) {
183
+ setError(err instanceof Error ? err.message : '保存 MCP 定义失败');
184
+ } finally {
185
+ setSaving(false);
186
+ }
187
+ };
188
+
189
+ return (
190
+ <Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && onClose()}>
191
+ <DialogContent className="max-h-[85vh] w-full max-w-2xl overflow-y-auto">
192
+ <DialogHeader>
193
+ <div className="flex items-center gap-2">
194
+ <div className="flex size-8 items-center justify-center rounded-lg border border-border bg-surface-raised">
195
+ <Server className="size-4 text-text-muted" />
196
+ </div>
197
+ <div>
198
+ <DialogTitle>{entry ? '编辑 MCP 定义' : '添加 MCP 定义'}</DialogTitle>
199
+ <DialogDescription>保存到全局能力库后,可在不同团队项目中复用。</DialogDescription>
200
+ </div>
201
+ </div>
202
+ </DialogHeader>
203
+
204
+ <div className="space-y-4">
205
+ <div className="space-y-1.5">
206
+ <Label htmlFor="mcp-library-name" className="text-xs">
207
+ 服务器名称
208
+ </Label>
209
+ <Input
210
+ id="mcp-library-name"
211
+ value={name}
212
+ onChange={(event) => setName(event.target.value)}
213
+ placeholder="my-server"
214
+ className="h-8 text-sm"
215
+ autoFocus
216
+ />
217
+ </div>
218
+
219
+ <div className="space-y-1.5">
220
+ <Label htmlFor="mcp-library-description" className="text-xs">
221
+ 描述(可选)
222
+ </Label>
223
+ <Textarea
224
+ id="mcp-library-description"
225
+ value={description}
226
+ onChange={(event) => setDescription(event.target.value)}
227
+ placeholder="这个 MCP 服务器的用途"
228
+ className="min-h-20 text-sm"
229
+ />
230
+ </div>
231
+
232
+ <div className="space-y-1.5">
233
+ <Label className="text-xs">传输方式</Label>
234
+ <div className="flex gap-2">
235
+ <Button
236
+ type="button"
237
+ variant={transportMode === 'stdio' ? 'default' : 'outline'}
238
+ size="sm"
239
+ onClick={() => setTransportMode('stdio')}
240
+ >
241
+ Stdio (npm)
242
+ </Button>
243
+ <Button
244
+ type="button"
245
+ variant={transportMode === 'http' ? 'default' : 'outline'}
246
+ size="sm"
247
+ onClick={() => setTransportMode('http')}
248
+ >
249
+ HTTP / SSE
250
+ </Button>
251
+ </div>
252
+ </div>
253
+
254
+ {transportMode === 'stdio' ? (
255
+ <div className="grid gap-3 sm:grid-cols-[1fr_160px]">
256
+ <div className="space-y-1.5">
257
+ <Label htmlFor="mcp-library-npm" className="text-xs">
258
+ npm 包
259
+ </Label>
260
+ <Input
261
+ id="mcp-library-npm"
262
+ value={npmPackage}
263
+ onChange={(event) => setNpmPackage(event.target.value)}
264
+ placeholder="@example/mcp-server"
265
+ className="h-8 font-mono text-sm"
266
+ />
267
+ </div>
268
+ <div className="space-y-1.5">
269
+ <Label htmlFor="mcp-library-version" className="text-xs">
270
+ 版本(可选)
271
+ </Label>
272
+ <Input
273
+ id="mcp-library-version"
274
+ value={npmVersion}
275
+ onChange={(event) => setNpmVersion(event.target.value)}
276
+ placeholder="latest"
277
+ className="h-8 text-sm"
278
+ />
279
+ </div>
280
+ </div>
281
+ ) : (
282
+ <div className="grid gap-3 sm:grid-cols-[1fr_180px]">
283
+ <div className="space-y-1.5">
284
+ <Label htmlFor="mcp-library-url" className="text-xs">
285
+ 服务器 URL
286
+ </Label>
287
+ <Input
288
+ id="mcp-library-url"
289
+ value={httpUrl}
290
+ onChange={(event) => setHttpUrl(event.target.value)}
291
+ placeholder="https://api.example.com/mcp"
292
+ className="h-8 text-sm"
293
+ />
294
+ </div>
295
+ <div className="space-y-1.5">
296
+ <Label className="text-xs">传输类型</Label>
297
+ <Select
298
+ value={httpTransport}
299
+ onValueChange={(value) => setHttpTransport(value as HttpTransport)}
300
+ >
301
+ <SelectTrigger className="h-8 text-sm">
302
+ <SelectValue />
303
+ </SelectTrigger>
304
+ <SelectContent>
305
+ {HTTP_TRANSPORT_OPTIONS.map((option) => (
306
+ <SelectItem key={option.value} value={option.value}>
307
+ {option.label}
308
+ </SelectItem>
309
+ ))}
310
+ </SelectContent>
311
+ </Select>
312
+ </div>
313
+ </div>
314
+ )}
315
+
316
+ <div className="space-y-2 rounded-lg border border-border p-3">
317
+ <div className="flex items-center justify-between gap-2">
318
+ <div>
319
+ <Label className="text-xs">环境变量默认值</Label>
320
+ <p className="text-[11px] text-text-muted">仅保存非敏感默认值,需要时可留空。</p>
321
+ </div>
322
+ <Button variant="ghost" size="sm" onClick={addEnvRow} className="h-7 px-2 text-xs">
323
+ <Plus className="mr-1 size-3" />
324
+ 添加
325
+ </Button>
326
+ </div>
327
+ {envRows.length > 0 && (
328
+ <div className="space-y-2">
329
+ {envRows.map((row, index) => (
330
+ <div key={index} className="flex items-center gap-2">
331
+ <Input
332
+ value={row.key}
333
+ onChange={(event) => updateEnvRow(index, 'key', event.target.value)}
334
+ className="h-7 w-40 font-mono text-xs"
335
+ placeholder="ENV_NAME"
336
+ />
337
+ <Input
338
+ value={row.value}
339
+ onChange={(event) => updateEnvRow(index, 'value', event.target.value)}
340
+ className="h-7 flex-1 text-xs"
341
+ placeholder="value"
342
+ />
343
+ <Button
344
+ variant="ghost"
345
+ size="icon"
346
+ className="size-7 text-red-400 hover:bg-red-500/10"
347
+ onClick={() => removeEnvRow(index)}
348
+ >
349
+ <Trash2 className="size-3" />
350
+ </Button>
351
+ </div>
352
+ ))}
353
+ </div>
354
+ )}
355
+ </div>
356
+
357
+ <div className="space-y-2 rounded-lg border border-border p-3">
358
+ <div className="flex items-center justify-between gap-2">
359
+ <div>
360
+ <Label className="text-xs">请求头</Label>
361
+ <p className="text-[11px] text-text-muted">
362
+ HTTP/SSE 服务器可使用,stdio 会保留但安装时通常忽略。
363
+ </p>
364
+ </div>
365
+ <Button variant="ghost" size="sm" onClick={addHeader} className="h-7 px-2 text-xs">
366
+ <Plus className="mr-1 size-3" />
367
+ 添加
368
+ </Button>
369
+ </div>
370
+ {headers.length > 0 && (
371
+ <div className="space-y-2">
372
+ {headers.map((header, index) => (
373
+ <div key={index} className="flex items-center gap-2">
374
+ <Input
375
+ value={header.key}
376
+ onChange={(event) => updateHeader(index, 'key', event.target.value)}
377
+ className="h-7 w-40 text-xs"
378
+ placeholder="Header-Name"
379
+ />
380
+ <Input
381
+ value={header.value}
382
+ onChange={(event) => updateHeader(index, 'value', event.target.value)}
383
+ className="h-7 flex-1 text-xs"
384
+ placeholder="value"
385
+ />
386
+ <Button
387
+ variant="ghost"
388
+ size="icon"
389
+ className="size-7 text-red-400 hover:bg-red-500/10"
390
+ onClick={() => removeHeader(index)}
391
+ >
392
+ <Trash2 className="size-3" />
393
+ </Button>
394
+ </div>
395
+ ))}
396
+ </div>
397
+ )}
398
+ </div>
399
+
400
+ {error && (
401
+ <div className="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
402
+ {error}
403
+ </div>
404
+ )}
405
+
406
+ <div className="flex justify-end gap-2 pt-2">
407
+ <Button variant="ghost" size="sm" onClick={onClose} disabled={saving}>
408
+ 取消
409
+ </Button>
410
+ <Button size="sm" disabled={!canSubmit} onClick={() => void handleSave()}>
411
+ {saving ? '保存中...' : '保存'}
412
+ </Button>
413
+ </div>
414
+ </div>
415
+ </DialogContent>
416
+ </Dialog>
417
+ );
418
+ };