@yancyyu/openhermit 1.6.37 → 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 (205) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.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-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.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-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.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 +30 -50
  58. package/src/main/server.ts +890 -247
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
  62. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  63. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  64. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  65. package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
  66. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  67. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  68. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  69. package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
  70. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  71. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  72. package/src/main/utils/teamProjectResolution.ts +15 -0
  73. package/src/renderer/App.tsx +8 -4
  74. package/src/renderer/api/httpClient.ts +174 -38
  75. package/src/renderer/api/providers.ts +23 -2
  76. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  87. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  88. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  89. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  90. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  91. package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
  92. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  93. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  94. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  95. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  96. package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
  97. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
  98. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  99. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  100. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  101. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  102. package/src/renderer/components/layout/TabBar.tsx +1 -1
  103. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  104. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  105. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  106. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  107. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  108. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  109. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  110. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  111. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  112. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  113. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  114. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  115. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  116. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  117. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  118. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  119. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  120. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  121. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  122. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  123. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  124. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  126. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  127. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  128. package/src/renderer/components/settings/sections/index.ts +0 -1
  129. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  130. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  131. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  132. package/src/renderer/components/splash/splashScene.ts +121 -929
  133. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  134. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  135. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  136. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  137. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  138. package/src/renderer/components/team/TeamDetailView.tsx +325 -114
  139. package/src/renderer/components/team/TeamListView.tsx +108 -123
  140. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  141. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  142. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  143. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  144. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  145. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  146. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  147. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  148. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  149. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  150. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  151. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  152. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  153. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  154. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  155. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  156. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  157. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  158. package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
  159. package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
  160. package/src/renderer/components/team/tools/McpChip.tsx +45 -0
  161. package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
  162. package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
  163. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  164. package/src/renderer/index.css +39 -22
  165. package/src/renderer/index.html +17 -50
  166. package/src/renderer/store/index.ts +2 -1
  167. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  168. package/src/renderer/store/slices/teamSlice.ts +45 -168
  169. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  170. package/src/renderer/utils/memberHelpers.ts +5 -17
  171. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  172. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  173. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  174. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  175. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  176. package/src/shared/types/api.ts +121 -2
  177. package/src/shared/types/ccConnect.ts +2 -0
  178. package/src/shared/types/extensions/api.ts +9 -0
  179. package/src/shared/types/extensions/index.ts +4 -0
  180. package/src/shared/types/extensions/mcp.ts +41 -0
  181. package/src/shared/types/index.ts +3 -0
  182. package/src/shared/types/systemManager.ts +49 -0
  183. package/src/shared/types/team.ts +29 -0
  184. package/src/shared/types/terminal.ts +4 -2
  185. package/src/shared/utils/extensionNormalizers.ts +29 -0
  186. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  187. package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
  188. package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
  189. package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
  190. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
  191. package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
  192. package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
  193. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  194. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
  195. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
  196. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
  197. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
  198. package/src/main/services/extensions/install/McpInstallService.ts +0 -407
  199. package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
  200. package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
  201. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
  202. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
  203. package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
  204. package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
  205. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
@@ -4,7 +4,7 @@
4
4
  * Global catalog data comes from Zustand store.
5
5
  */
6
6
 
7
- import { useCallback, useEffect, useMemo, useState } from 'react';
7
+ import { useCallback, useEffect, useMemo } from 'react';
8
8
 
9
9
  // Stubs for removed codex-account feature
10
10
  function useCodexAccountSnapshot(_opts: { enabled: boolean; includeRateLimits?: boolean }) {
@@ -31,32 +31,16 @@ import { useStore } from '@renderer/store';
31
31
  import { createLoadingMultimodelCliStatus } from '@renderer/store/slices/cliInstallerSlice';
32
32
  import {
33
33
  filterExtensionStoreProviders,
34
- formatCliExtensionCapabilityStatus,
35
34
  getVisibleMultimodelProviders,
36
35
  isMultimodelRuntimeStatus,
37
36
  } from '@renderer/utils/multimodelProviderVisibility';
38
37
  import { resolveProjectPathById } from '@renderer/utils/projectLookup';
39
38
  import { refreshCliStatusForCurrentMode } from '@renderer/utils/refreshCliStatus';
40
39
  import { getRuntimeDisplayName } from '@renderer/utils/runtimeDisplayName';
41
- import { getCliProviderExtensionCapabilities } from '@shared/utils/providerExtensionCapabilities';
42
- import {
43
- AlertTriangle,
44
- BookOpen,
45
- Info,
46
- Loader2,
47
- Plus,
48
- Puzzle,
49
- RefreshCw,
50
- Server,
51
- Sliders,
52
- } from 'lucide-react';
40
+ import { AlertTriangle, Info, Loader2, Puzzle, RefreshCw } from 'lucide-react';
53
41
  import { useShallow } from 'zustand/react/shallow';
54
42
 
55
- import { CustomMcpServerDialog } from './mcp/CustomMcpServerDialog';
56
- import { EnvVarPanel } from './env/EnvVarPanel';
57
- import { McpServersPanel } from './mcp/McpServersPanel';
58
43
  import { PluginsPanel } from './plugins/PluginsPanel';
59
- import { SkillsPanel } from './skills/SkillsPanel';
60
44
  import { StoreExtensionToast } from './common/ExtensionToast';
61
45
  import { ExtensionsSubTabTrigger } from './ExtensionsSubTabTrigger';
62
46
 
@@ -87,7 +71,7 @@ const ProviderCapabilityCardSkeleton = ({
87
71
  </Badge>
88
72
  </div>
89
73
  <div className="mt-2 flex flex-wrap gap-1.5">
90
- {Array.from({ length: 3 }, (_, index) => (
74
+ {Array.from({ length: 4 }, (_, index) => (
91
75
  <span
92
76
  key={index}
93
77
  className="h-7 w-28 animate-pulse rounded-md border border-border bg-surface"
@@ -124,37 +108,13 @@ const EXTENSION_SUB_TABS = [
124
108
  icon: Puzzle,
125
109
  description: 'Claude Code 私有扩展,增强运行时的能力与集成。',
126
110
  },
127
- {
128
- value: 'mcp-servers' as const,
129
- label: 'MCP 服务器',
130
- icon: Server,
131
- description: '连接外部工具和应用,让运行时可以读取数据或执行本应用之外的操作。',
132
- },
133
- {
134
- value: 'skills' as const,
135
- label: '技能',
136
- icon: BookOpen,
137
- description: '面向常见任务的可复用指令,帮助运行时更稳定地处理重复工作。',
138
- },
139
- {
140
- value: 'env-vars' as const,
141
- label: '环境变量',
142
- icon: Sliders,
143
- description: '管理运行时环境变量,启动 agent 时自动注入。',
144
- },
145
111
  ] as const;
146
112
 
147
113
  export const ExtensionStoreView = (): React.JSX.Element => {
148
- const tabId = useTabIdOptional();
149
114
  const {
150
115
  bootstrapCliStatus,
151
116
  fetchCliStatus,
152
117
  fetchPluginCatalog,
153
- fetchSkillsCatalog,
154
- mcpBrowse,
155
- mcpFetchInstalled,
156
- mcpBrowseLoading,
157
- skillsLoading,
158
118
  cliStatus,
159
119
  cliStatusLoading,
160
120
  cliProviderStatusLoading,
@@ -168,12 +128,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
168
128
  bootstrapCliStatus: s.bootstrapCliStatus,
169
129
  fetchCliStatus: s.fetchCliStatus,
170
130
  fetchPluginCatalog: s.fetchPluginCatalog,
171
- fetchSkillsCatalog: s.fetchSkillsCatalog,
172
131
  pluginCatalog: s.pluginCatalog,
173
- mcpBrowse: s.mcpBrowse,
174
- mcpFetchInstalled: s.mcpFetchInstalled,
175
- mcpBrowseLoading: s.mcpBrowseLoading,
176
- skillsLoading: s.skillsLoading,
177
132
  cliStatus: s.cliStatus,
178
133
  cliStatusLoading: s.cliStatusLoading,
179
134
  cliProviderStatusLoading: s.cliProviderStatusLoading,
@@ -229,6 +184,9 @@ export const ExtensionStoreView = (): React.JSX.Element => {
229
184
  const runtimeDisplayName = getRuntimeDisplayName(effectiveCliStatus, multimodelEnabled);
230
185
  const cliInstalled = effectiveCliStatus?.installed ?? true;
231
186
  const hasOngoingSessions = sessions.some((sess) => sess.isOngoing);
187
+
188
+ const tabState = useExtensionsTabState();
189
+ const tabId = useTabIdOptional();
232
190
  const extensionsTabProjectId = useStore((s) =>
233
191
  tabId
234
192
  ? (s.paneLayout.panes.flatMap((pane) => pane.tabs).find((tab) => tab.id === tabId)
@@ -236,14 +194,11 @@ export const ExtensionStoreView = (): React.JSX.Element => {
236
194
  : null
237
195
  );
238
196
 
239
- const tabState = useExtensionsTabState();
240
- const [customMcpDialogOpen, setCustomMcpDialogOpen] = useState(false);
241
197
  const resolvedProject = useMemo(
242
198
  () => resolveProjectPathById(extensionsTabProjectId, projects, repositoryGroups),
243
199
  [extensionsTabProjectId, projects, repositoryGroups]
244
200
  );
245
201
  const projectPath = resolvedProject?.path ?? null;
246
- const projectLabel = resolvedProject?.name ?? null;
247
202
  const subTabs = EXTENSION_SUB_TABS;
248
203
 
249
204
  useEffect(() => {
@@ -254,42 +209,21 @@ export const ExtensionStoreView = (): React.JSX.Element => {
254
209
  });
255
210
  }, [bootstrapCliStatus, fetchCliStatus, multimodelEnabled]);
256
211
 
257
- // Fetch MCP installed state on mount
258
- useEffect(() => {
259
- void mcpFetchInstalled(projectPath ?? undefined);
260
- }, [mcpFetchInstalled, projectPath]);
261
-
262
212
  // Fetch Plugin catalog on mount / project change
263
213
  useEffect(() => {
264
214
  void fetchPluginCatalog(projectPath ?? undefined);
265
215
  }, [fetchPluginCatalog, projectPath]);
266
216
 
267
- // Fetch Skills catalog on mount / project change
268
- useEffect(() => {
269
- void fetchSkillsCatalog(projectPath ?? undefined);
270
- }, [fetchSkillsCatalog, projectPath]);
271
-
272
- // Refresh all data (MCP + skills + runtime status)
217
+ // Refresh all data
273
218
  const handleRefresh = useCallback(() => {
274
219
  void refreshCliStatusForCurrentMode({
275
220
  multimodelEnabled,
276
221
  bootstrapCliStatus,
277
222
  fetchCliStatus,
278
223
  });
279
- void mcpBrowse(); // re-fetch first page
280
- void mcpFetchInstalled(projectPath ?? undefined);
281
- void fetchSkillsCatalog(projectPath ?? undefined);
282
- }, [
283
- bootstrapCliStatus,
284
- fetchCliStatus,
285
- fetchSkillsCatalog,
286
- multimodelEnabled,
287
- mcpBrowse,
288
- mcpFetchInstalled,
289
- projectPath,
290
- ]);
224
+ }, [bootstrapCliStatus, fetchCliStatus, multimodelEnabled]);
291
225
 
292
- const isRefreshing = effectiveCliStatusLoading || mcpBrowseLoading || skillsLoading;
226
+ const isRefreshing = effectiveCliStatusLoading;
293
227
  const cliStatusBanner = useMemo(() => {
294
228
  const providers = effectiveCliStatus?.providers ?? [];
295
229
  const visibleProviders = filterExtensionStoreProviders(
@@ -309,7 +243,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
309
243
  <div>
310
244
  <p className="text-sm font-medium text-text">正在检查扩展运行时可用性</p>
311
245
  <p className="mt-0.5 text-xs text-text-muted">
312
- 扩展需要配置好的运行时来管理 MCP 服务器、技能和提供商连接。
246
+ 扩展需要配置好的运行时来管理插件和提供商连接。
313
247
  </p>
314
248
  </div>
315
249
  </div>
@@ -366,101 +300,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
366
300
  );
367
301
  }
368
302
 
369
- if (isMultimodel) {
370
- return (
371
- <div className="bg-surface/70 mx-4 mt-3 rounded-md border border-border px-4 py-3">
372
- <div className="flex items-start gap-3">
373
- <Info className="mt-0.5 size-4 shrink-0 text-text-secondary" />
374
- <div className="min-w-0 flex-1">
375
- <p className="text-sm font-medium text-text">多模型运行时能力</p>
376
- <p className="mt-0.5 text-xs text-text-muted">
377
- 不同区域支持的提供商可能不同。只有运行时明确声明支持时,MCP 与技能能力才会显示。
378
- </p>
379
- </div>
380
- </div>
381
- {visibleProviders.length > 0 && (
382
- <div className="mt-3 grid gap-2 md:grid-cols-2">
383
- {visibleProviders.map((provider) => {
384
- const providerLoading = cliProviderStatusLoading[provider.providerId] === true;
385
- if (
386
- isProviderCapabilityCardLoading(provider, providerLoading) ||
387
- isCodexSnapshotPending(provider, codexSnapshotPending)
388
- ) {
389
- return (
390
- <ProviderCapabilityCardSkeleton
391
- key={provider.providerId}
392
- providerId={provider.providerId}
393
- displayName={provider.displayName}
394
- />
395
- );
396
- }
397
-
398
- const statusTone = provider.authenticated
399
- ? 'border-emerald-500/30 bg-emerald-500/5 text-emerald-300'
400
- : provider.supported
401
- ? 'border-amber-500/30 bg-amber-500/5 text-amber-300'
402
- : 'border-border bg-surface-raised text-text-muted';
403
- const statusLabel = provider.authenticated
404
- ? '已连接'
405
- : provider.supported
406
- ? '需要设置'
407
- : '不支持';
408
- const extensionCapabilities = getCliProviderExtensionCapabilities(provider);
409
-
410
- return (
411
- <div
412
- key={provider.providerId}
413
- className={`rounded-md border px-3 py-2 ${statusTone}`}
414
- >
415
- <div className="flex items-center justify-between gap-2">
416
- <div className="min-w-0">
417
- <p className="inline-flex items-center gap-2 text-sm font-medium">
418
- <ProviderBrandLogo
419
- providerId={provider.providerId}
420
- className="size-4 shrink-0"
421
- />
422
- <span>{provider.displayName}</span>
423
- </p>
424
- <p className="truncate text-[11px] text-text-muted">
425
- {provider.statusMessage ?? provider.backend?.label ?? '可配置'}
426
- </p>
427
- </div>
428
- <Badge variant="outline" className="shrink-0">
429
- {statusLabel}
430
- </Badge>
431
- </div>
432
- <div className="mt-2 flex flex-wrap gap-1.5 text-[11px]">
433
- <Badge variant="secondary">
434
- MCP: {formatCliExtensionCapabilityStatus(extensionCapabilities.mcp.status)}
435
- </Badge>
436
- <Badge variant="secondary">
437
- 技能:{extensionCapabilities.skills.ownership}
438
- </Badge>
439
- </div>
440
- </div>
441
- );
442
- })}
443
- </div>
444
- )}
445
- </div>
446
- );
447
- }
448
-
449
- return (
450
- <div className="mx-4 mt-3 flex items-start gap-3 rounded-md border border-emerald-500/30 bg-emerald-500/5 px-4 py-3">
451
- <Info className="mt-0.5 size-4 shrink-0 text-emerald-300" />
452
- <div>
453
- <p className="text-sm font-medium text-emerald-300">{runtimeDisplayName} 已就绪</p>
454
- <p className="mt-0.5 text-xs text-text-muted">
455
- 可以从此页面管理 MCP 服务器与技能
456
- {effectiveCliStatus.installedVersion
457
- ? `,使用 ${runtimeDisplayName} ${effectiveCliStatus.installedVersion}`
458
- : ''}
459
- .
460
- </p>
461
- </div>
462
- </div>
463
- );
303
+ return null;
464
304
  }, [
465
305
  cliProviderStatusLoading,
466
306
  codexSnapshotPending,
@@ -470,7 +310,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
470
310
  ]);
471
311
 
472
312
  // Browser mode guard
473
- if (!api.plugins && !api.mcpRegistry && !api.skills) {
313
+ if (!api.plugins) {
474
314
  return (
475
315
  <div className="flex flex-1 items-center justify-center">
476
316
  <div className="text-center">
@@ -535,17 +375,6 @@ export const ExtensionStoreView = (): React.JSX.Element => {
535
375
  />
536
376
  ))}
537
377
  </TabsList>
538
- {tabState.activeSubTab === 'mcp-servers' && (
539
- <Button
540
- variant="outline"
541
- size="sm"
542
- onClick={() => setCustomMcpDialogOpen(true)}
543
- className="mb-1 whitespace-nowrap"
544
- >
545
- <Plus className="mr-1 size-3.5" />
546
- 添加自定义
547
- </Button>
548
- )}
549
378
  </div>
550
379
 
551
380
  <TabsContent value="plugins" className="mt-0 pt-4">
@@ -566,45 +395,7 @@ export const ExtensionStoreView = (): React.JSX.Element => {
566
395
  cliStatusLoading={effectiveCliStatusLoading}
567
396
  />
568
397
  </TabsContent>
569
-
570
- <TabsContent value="mcp-servers" className="mt-0 pt-4">
571
- <McpServersPanel
572
- projectPath={projectPath}
573
- mcpSearchQuery={tabState.mcpSearchQuery}
574
- mcpSearch={tabState.mcpSearch}
575
- mcpSearchResults={tabState.mcpSearchResults}
576
- mcpSearchLoading={tabState.mcpSearchLoading}
577
- mcpSearchWarnings={tabState.mcpSearchWarnings}
578
- selectedMcpServerId={tabState.selectedMcpServerId}
579
- setSelectedMcpServerId={tabState.setSelectedMcpServerId}
580
- cliStatus={effectiveCliStatus}
581
- cliStatusLoading={effectiveCliStatusLoading}
582
- />
583
- </TabsContent>
584
-
585
- <TabsContent value="skills" className="mt-0 pt-4">
586
- <SkillsPanel
587
- projectPath={projectPath}
588
- projectLabel={projectLabel}
589
- skillsSearchQuery={tabState.skillsSearchQuery}
590
- setSkillsSearchQuery={tabState.setSkillsSearchQuery}
591
- skillsSort={tabState.skillsSort}
592
- setSkillsSort={tabState.setSkillsSort}
593
- selectedSkillId={tabState.selectedSkillId}
594
- setSelectedSkillId={tabState.setSelectedSkillId}
595
- />
596
- </TabsContent>
597
-
598
- <TabsContent value="env-vars" className="mt-0 pt-4">
599
- <EnvVarPanel projectPath={projectPath} />
600
- </TabsContent>
601
398
  </Tabs>
602
-
603
- {/* Custom MCP server dialog (lifted to store view level) */}
604
- <CustomMcpServerDialog
605
- open={customMcpDialogOpen}
606
- onClose={() => setCustomMcpDialogOpen(false)}
607
- />
608
399
  </div>
609
400
  </div>
610
401
  </div>
@@ -5,7 +5,7 @@ import { Info } from 'lucide-react';
5
5
  import type { LucideIcon } from 'lucide-react';
6
6
 
7
7
  interface ExtensionsSubTabTriggerProps {
8
- value: 'plugins' | 'mcp-servers' | 'skills' | 'env-vars';
8
+ value: 'plugins';
9
9
  label: string;
10
10
  description: string;
11
11
  icon: LucideIcon;
@@ -0,0 +1,305 @@
1
+ /**
2
+ * McpLibraryEnableDialog — enables a global MCP template as a project instance.
3
+ */
4
+
5
+ import { useEffect, useMemo, useState } from 'react';
6
+
7
+ import { Button } from '@renderer/components/ui/button';
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ } from '@renderer/components/ui/dialog';
15
+ import { Input } from '@renderer/components/ui/input';
16
+ import { Label } from '@renderer/components/ui/label';
17
+ import { useStore } from '@renderer/store';
18
+ import { Plus, Server, Trash2 } from 'lucide-react';
19
+
20
+ import type { InstalledMcpEntry, McpHeaderDef, McpLibraryEntry } from '@shared/types/extensions';
21
+
22
+ interface McpLibraryEnableDialogProps {
23
+ open: boolean;
24
+ entry: McpLibraryEntry | null;
25
+ projectPath: string | null;
26
+ installedServers?: InstalledMcpEntry[];
27
+ harnessType?: string;
28
+ onClose: () => void;
29
+ onEnabled: () => void;
30
+ }
31
+
32
+ interface EnvRow {
33
+ key: string;
34
+ value: string;
35
+ }
36
+
37
+ const SERVER_NAME_RE = /^[\w.-]{1,100}$/;
38
+
39
+ function summarizeMcp(entry: McpLibraryEntry): string {
40
+ if (entry.installSpec.type === 'stdio') {
41
+ return `stdio · ${entry.installSpec.npmPackage}${entry.installSpec.npmVersion ? `@${entry.installSpec.npmVersion}` : ''}`;
42
+ }
43
+ return `${entry.installSpec.transportType} · ${entry.installSpec.url}`;
44
+ }
45
+
46
+ function envRowsFromRecord(record?: Record<string, string>): EnvRow[] {
47
+ return Object.entries(record ?? {}).map(([key, value]) => ({ key, value }));
48
+ }
49
+
50
+ function envRecordFromRows(rows: EnvRow[]): Record<string, string> {
51
+ return Object.fromEntries(
52
+ rows.map((row) => [row.key.trim(), row.value]).filter(([key]) => key.length > 0)
53
+ );
54
+ }
55
+
56
+ function cleanHeaders(headers: McpHeaderDef[]): McpHeaderDef[] {
57
+ return headers
58
+ .map((header) => ({ ...header, key: header.key.trim() }))
59
+ .filter((header) => header.key && header.value);
60
+ }
61
+
62
+ export const McpLibraryEnableDialog = ({
63
+ open,
64
+ entry,
65
+ projectPath,
66
+ installedServers = [],
67
+ harnessType,
68
+ onClose,
69
+ onEnabled,
70
+ }: McpLibraryEnableDialogProps): React.JSX.Element => {
71
+ const installCustomMcpServer = useStore((s) => s.installCustomMcpServer);
72
+ const mcpFetchInstalled = useStore((s) => s.mcpFetchInstalled);
73
+ const runMcpDiagnostics = useStore((s) => s.runMcpDiagnostics);
74
+
75
+ const [serverName, setServerName] = useState('');
76
+ const [envRows, setEnvRows] = useState<EnvRow[]>([]);
77
+ const [headers, setHeaders] = useState<McpHeaderDef[]>([]);
78
+ const [installing, setInstalling] = useState(false);
79
+ const [error, setError] = useState<string | null>(null);
80
+
81
+ const installedNames = useMemo(
82
+ () => new Set(installedServers.map((server) => server.name.toLowerCase())),
83
+ [installedServers]
84
+ );
85
+ const trimmedName = serverName.trim();
86
+ const nameExists = Boolean(trimmedName) && installedNames.has(trimmedName.toLowerCase());
87
+ const canSubmit = Boolean(entry && projectPath && trimmedName && !nameExists && !installing);
88
+
89
+ useEffect(() => {
90
+ if (!open || !entry) return;
91
+ setServerName(entry.name);
92
+ setEnvRows(envRowsFromRecord(entry.envValues));
93
+ setHeaders(entry.headers ?? []);
94
+ setError(null);
95
+ setInstalling(false);
96
+ }, [entry, open]);
97
+
98
+ const addEnvRow = (): void => setEnvRows((prev) => [...prev, { key: '', value: '' }]);
99
+ const removeEnvRow = (index: number): void =>
100
+ setEnvRows((prev) => prev.filter((_, i) => i !== index));
101
+ const updateEnvRow = (index: number, field: keyof EnvRow, value: string): void =>
102
+ setEnvRows((prev) => prev.map((row, i) => (i === index ? { ...row, [field]: value } : row)));
103
+
104
+ const addHeader = (): void => setHeaders((prev) => [...prev, { key: '', value: '' }]);
105
+ const removeHeader = (index: number): void =>
106
+ setHeaders((prev) => prev.filter((_, i) => i !== index));
107
+ const updateHeader = (index: number, field: 'key' | 'value', value: string): void =>
108
+ setHeaders((prev) =>
109
+ prev.map((header, i) => (i === index ? { ...header, [field]: value } : header))
110
+ );
111
+
112
+ const handleEnable = async (): Promise<void> => {
113
+ if (!entry || !projectPath) return;
114
+
115
+ if (!trimmedName) {
116
+ setError('实例名称不能为空');
117
+ return;
118
+ }
119
+ if (!SERVER_NAME_RE.test(trimmedName)) {
120
+ setError('实例名称只能包含字母、数字、下划线、短横线和点号,最多 100 个字符');
121
+ return;
122
+ }
123
+ if (nameExists) {
124
+ setError('当前项目已存在同名实例,请改用其他实例名');
125
+ return;
126
+ }
127
+
128
+ setInstalling(true);
129
+ setError(null);
130
+ try {
131
+ await installCustomMcpServer({
132
+ serverName: trimmedName,
133
+ scope: 'project',
134
+ projectPath,
135
+ installSpec: entry.installSpec,
136
+ envValues: envRecordFromRows(envRows),
137
+ headers: cleanHeaders(headers),
138
+ harnessType: harnessType ?? 'claudecode',
139
+ });
140
+ await Promise.all([mcpFetchInstalled(projectPath), runMcpDiagnostics(projectPath)]);
141
+ onEnabled();
142
+ } catch (err) {
143
+ setError(err instanceof Error ? err.message : '添加项目实例失败');
144
+ } finally {
145
+ setInstalling(false);
146
+ }
147
+ };
148
+
149
+ return (
150
+ <Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && !installing && onClose()}>
151
+ <DialogContent className="max-h-[85vh] w-full max-w-2xl overflow-y-auto">
152
+ <DialogHeader>
153
+ <div className="flex items-center gap-2">
154
+ <div className="flex size-8 items-center justify-center rounded-lg border border-border bg-surface-raised">
155
+ <Server className="size-4 text-text-muted" />
156
+ </div>
157
+ <div>
158
+ <DialogTitle>添加 MCP 项目实例</DialogTitle>
159
+ <DialogDescription>
160
+ 从全局模板创建当前项目专属实例,可覆盖实例名、环境变量和请求头。
161
+ </DialogDescription>
162
+ </div>
163
+ </div>
164
+ </DialogHeader>
165
+
166
+ {entry ? (
167
+ <div className="space-y-4">
168
+ <div className="bg-surface-raised/40 rounded-md border border-border px-3 py-2">
169
+ <div className="text-xs font-medium text-text">模板:{entry.name}</div>
170
+ <div className="mt-1 truncate font-mono text-[11px] text-text-muted">
171
+ {summarizeMcp(entry)}
172
+ </div>
173
+ </div>
174
+
175
+ <div className="space-y-1.5">
176
+ <Label htmlFor="mcp-enable-instance-name" className="text-xs">
177
+ 项目实例名称
178
+ </Label>
179
+ <Input
180
+ id="mcp-enable-instance-name"
181
+ value={serverName}
182
+ onChange={(event) => setServerName(event.target.value)}
183
+ placeholder={entry.name}
184
+ className="h-8 font-mono text-sm"
185
+ autoFocus
186
+ />
187
+ <p
188
+ className={
189
+ nameExists ? 'text-[11px] text-amber-300' : 'text-[11px] text-text-muted'
190
+ }
191
+ >
192
+ {nameExists
193
+ ? '当前项目已存在同名实例,请改用其他实例名。'
194
+ : '实例名只写入当前项目,可与模板名不同。'}
195
+ </p>
196
+ </div>
197
+
198
+ <div className="space-y-2 rounded-lg border border-border p-3">
199
+ <div className="flex items-center justify-between gap-2">
200
+ <div>
201
+ <Label className="text-xs">项目环境变量</Label>
202
+ <p className="text-[11px] text-text-muted">
203
+ 已预填模板默认值,可按当前项目覆盖。
204
+ </p>
205
+ </div>
206
+ <Button variant="ghost" size="sm" onClick={addEnvRow} className="h-7 px-2 text-xs">
207
+ <Plus className="mr-1 size-3" />
208
+ 添加
209
+ </Button>
210
+ </div>
211
+ {envRows.length > 0 ? (
212
+ <div className="space-y-2">
213
+ {envRows.map((row, index) => (
214
+ <div key={index} className="flex items-center gap-2">
215
+ <Input
216
+ value={row.key}
217
+ onChange={(event) => updateEnvRow(index, 'key', event.target.value)}
218
+ className="h-7 w-40 font-mono text-xs"
219
+ placeholder="ENV_NAME"
220
+ />
221
+ <Input
222
+ value={row.value}
223
+ onChange={(event) => updateEnvRow(index, 'value', event.target.value)}
224
+ className="h-7 flex-1 text-xs"
225
+ placeholder="value"
226
+ />
227
+ <Button
228
+ variant="ghost"
229
+ size="icon"
230
+ className="size-7 text-red-400 hover:bg-red-500/10"
231
+ onClick={() => removeEnvRow(index)}
232
+ >
233
+ <Trash2 className="size-3" />
234
+ </Button>
235
+ </div>
236
+ ))}
237
+ </div>
238
+ ) : (
239
+ <p className="text-[11px] text-text-muted">此模板没有环境变量默认值。</p>
240
+ )}
241
+ </div>
242
+
243
+ <div className="space-y-2 rounded-lg border border-border p-3">
244
+ <div className="flex items-center justify-between gap-2">
245
+ <div>
246
+ <Label className="text-xs">项目请求头</Label>
247
+ <p className="text-[11px] text-text-muted">HTTP/SSE 实例可用,stdio 通常忽略。</p>
248
+ </div>
249
+ <Button variant="ghost" size="sm" onClick={addHeader} className="h-7 px-2 text-xs">
250
+ <Plus className="mr-1 size-3" />
251
+ 添加
252
+ </Button>
253
+ </div>
254
+ {headers.length > 0 ? (
255
+ <div className="space-y-2">
256
+ {headers.map((header, index) => (
257
+ <div key={index} className="flex items-center gap-2">
258
+ <Input
259
+ value={header.key}
260
+ onChange={(event) => updateHeader(index, 'key', event.target.value)}
261
+ className="h-7 w-40 text-xs"
262
+ placeholder="Header-Name"
263
+ />
264
+ <Input
265
+ value={header.value}
266
+ onChange={(event) => updateHeader(index, 'value', event.target.value)}
267
+ className="h-7 flex-1 text-xs"
268
+ placeholder="value"
269
+ />
270
+ <Button
271
+ variant="ghost"
272
+ size="icon"
273
+ className="size-7 text-red-400 hover:bg-red-500/10"
274
+ onClick={() => removeHeader(index)}
275
+ >
276
+ <Trash2 className="size-3" />
277
+ </Button>
278
+ </div>
279
+ ))}
280
+ </div>
281
+ ) : (
282
+ <p className="text-[11px] text-text-muted">此模板没有请求头默认值。</p>
283
+ )}
284
+ </div>
285
+
286
+ {error && (
287
+ <div className="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
288
+ {error}
289
+ </div>
290
+ )}
291
+
292
+ <div className="flex justify-end gap-2 pt-2">
293
+ <Button variant="ghost" size="sm" onClick={onClose} disabled={installing}>
294
+ 取消
295
+ </Button>
296
+ <Button size="sm" disabled={!canSubmit} onClick={() => void handleEnable()}>
297
+ {installing ? '添加中...' : '添加到当前项目'}
298
+ </Button>
299
+ </div>
300
+ </div>
301
+ ) : null}
302
+ </DialogContent>
303
+ </Dialog>
304
+ );
305
+ };