@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.
- package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
- package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
- package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
- package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
- package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
- package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
- package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
- package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
- package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
- package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
- package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
- package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
- package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
- package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
- package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
- package/dist-renderer/index.html +20 -53
- package/package.json +25 -18
- package/src/main/ipc/extensions.ts +30 -50
- package/src/main/server.ts +890 -247
- package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
- package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
- package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
- package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
- package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
- package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
- package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
- package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
- package/src/main/utils/teamProjectResolution.ts +15 -0
- package/src/renderer/App.tsx +8 -4
- package/src/renderer/api/httpClient.ts +174 -38
- package/src/renderer/api/providers.ts +23 -2
- package/src/renderer/assets/participant-avatars/01.svg +3 -0
- package/src/renderer/assets/participant-avatars/02.svg +3 -0
- package/src/renderer/assets/participant-avatars/03.svg +3 -0
- package/src/renderer/assets/participant-avatars/04.svg +3 -0
- package/src/renderer/assets/participant-avatars/05.svg +3 -0
- package/src/renderer/assets/participant-avatars/06.svg +3 -0
- package/src/renderer/assets/participant-avatars/07.svg +3 -0
- package/src/renderer/assets/participant-avatars/08.svg +3 -0
- package/src/renderer/assets/participant-avatars/09.svg +3 -0
- package/src/renderer/assets/participant-avatars/10.svg +3 -0
- package/src/renderer/assets/participant-avatars/11.svg +3 -0
- package/src/renderer/assets/participant-avatars/12.svg +3 -0
- package/src/renderer/assets/participant-avatars/13.svg +3 -0
- package/src/renderer/components/common/TerminalPane.tsx +213 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
- package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
- package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
- package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
- package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
- package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
- package/src/renderer/components/layout/PaneContent.tsx +8 -1
- package/src/renderer/components/layout/Sidebar.tsx +11 -54
- package/src/renderer/components/layout/SortableTab.tsx +20 -31
- package/src/renderer/components/layout/TabBar.tsx +1 -1
- package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
- package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
- package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
- package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
- package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
- package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
- package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
- package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
- package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
- package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
- package/src/renderer/components/schedules/calendar/index.ts +2 -0
- package/src/renderer/components/schedules/calendar/types.ts +44 -0
- package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
- package/src/renderer/components/settings/SettingsView.tsx +30 -35
- package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
- package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
- package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
- package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
- package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
- package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
- package/src/renderer/components/settings/sections/index.ts +0 -1
- package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
- package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
- package/src/renderer/components/splash/splashScene.ts +121 -929
- package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
- package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
- package/src/renderer/components/tasks/TasksView.tsx +112 -134
- package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
- package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
- package/src/renderer/components/team/TeamDetailView.tsx +325 -114
- package/src/renderer/components/team/TeamListView.tsx +108 -123
- package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
- package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
- package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
- package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
- package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
- package/src/renderer/components/team/members/MemberCard.tsx +14 -47
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
- package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
- package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
- package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
- package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
- package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
- package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
- package/src/renderer/components/team/tools/McpChip.tsx +45 -0
- package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
- package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
- package/src/renderer/index.css +39 -22
- package/src/renderer/index.html +17 -50
- package/src/renderer/store/index.ts +2 -1
- package/src/renderer/store/slices/scheduleSlice.ts +1 -1
- package/src/renderer/store/slices/teamSlice.ts +45 -168
- package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
- package/src/renderer/utils/memberHelpers.ts +5 -17
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
- package/src/renderer/utils/providerSlashCommands.ts +0 -5
- package/src/renderer/utils/scheduleFormatters.ts +3 -1
- package/src/renderer/utils/teamMessageFiltering.ts +14 -1
- package/src/renderer/utils/teamModelAvailability.ts +18 -2
- package/src/shared/types/api.ts +121 -2
- package/src/shared/types/ccConnect.ts +2 -0
- package/src/shared/types/extensions/api.ts +9 -0
- package/src/shared/types/extensions/index.ts +4 -0
- package/src/shared/types/extensions/mcp.ts +41 -0
- package/src/shared/types/index.ts +3 -0
- package/src/shared/types/systemManager.ts +49 -0
- package/src/shared/types/team.ts +29 -0
- package/src/shared/types/terminal.ts +4 -2
- package/src/shared/utils/extensionNormalizers.ts +29 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
- package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
- package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
- package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
- package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
- package/src/main/services/extensions/install/McpInstallService.ts +0 -407
- package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
- package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
- package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
- package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ToolsSection — per-worker capabilities management.
|
|
3
|
+
* MCP uses a cc-switch style model: global templates create project instances.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { api } from '@renderer/api';
|
|
9
|
+
import { confirm } from '@renderer/components/common/ConfirmDialog';
|
|
10
|
+
import { Button } from '@renderer/components/ui/button';
|
|
11
|
+
import { Input } from '@renderer/components/ui/input';
|
|
12
|
+
import { McpLibraryEnableDialog } from '@renderer/components/extensions/mcp/McpLibraryEnableDialog';
|
|
13
|
+
import { useStore } from '@renderer/store';
|
|
14
|
+
import { Plus, RefreshCw, Wrench } from 'lucide-react';
|
|
15
|
+
|
|
16
|
+
import { AddMcpInline } from './AddMcpInline';
|
|
17
|
+
import { AddSkillInline } from './AddSkillInline';
|
|
18
|
+
import { McpChip } from './McpChip';
|
|
19
|
+
import { SkillChip } from './SkillChip';
|
|
20
|
+
|
|
21
|
+
import type { McpLibraryEntry, SkillCatalogItem } from '@shared/types/extensions';
|
|
22
|
+
|
|
23
|
+
interface ToolsSectionProps {
|
|
24
|
+
teamName: string;
|
|
25
|
+
projectPath: string | null;
|
|
26
|
+
harnessType?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function summarizeMcp(entry: McpLibraryEntry): string {
|
|
30
|
+
if (entry.installSpec.type === 'stdio') {
|
|
31
|
+
return `stdio · ${entry.installSpec.npmPackage}${entry.installSpec.npmVersion ? `@${entry.installSpec.npmVersion}` : ''}`;
|
|
32
|
+
}
|
|
33
|
+
return `${entry.installSpec.transportType} · ${entry.installSpec.url}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const ToolsSection = ({
|
|
37
|
+
teamName,
|
|
38
|
+
projectPath,
|
|
39
|
+
harnessType,
|
|
40
|
+
}: ToolsSectionProps): React.JSX.Element => {
|
|
41
|
+
// ── Store selectors ──
|
|
42
|
+
const mcpByPath = useStore((s) => s.mcpInstalledServersByProjectPath);
|
|
43
|
+
const diagnosticsByPath = useStore((s) => s.mcpDiagnosticsByProjectPath);
|
|
44
|
+
const skillsByPath = useStore((s) => s.skillsProjectCatalogByProjectPath);
|
|
45
|
+
const mcpFetchInstalled = useStore((s) => s.mcpFetchInstalled);
|
|
46
|
+
const runMcpDiagnostics = useStore((s) => s.runMcpDiagnostics);
|
|
47
|
+
const fetchSkillsCatalog = useStore((s) => s.fetchSkillsCatalog);
|
|
48
|
+
const fetchSkillDetail = useStore((s) => s.fetchSkillDetail);
|
|
49
|
+
const applySkillImport = useStore((s) => s.applySkillImport);
|
|
50
|
+
const previewSkillImport = useStore((s) => s.previewSkillImport);
|
|
51
|
+
const uninstallMcpServer = useStore((s) => s.uninstallMcpServer);
|
|
52
|
+
const deleteSkill = useStore((s) => s.deleteSkill);
|
|
53
|
+
const skillDetailsById = useStore((s) => s.skillsDetailsById);
|
|
54
|
+
|
|
55
|
+
// ── Local state ──
|
|
56
|
+
const [addingMcp, setAddingMcp] = useState(false);
|
|
57
|
+
const [addingSkill, setAddingSkill] = useState(false);
|
|
58
|
+
const [mcpLibrary, setMcpLibrary] = useState<McpLibraryEntry[]>([]);
|
|
59
|
+
const [userSkills, setUserSkills] = useState<SkillCatalogItem[]>([]);
|
|
60
|
+
const [libraryLoading, setLibraryLoading] = useState(false);
|
|
61
|
+
const [libraryError, setLibraryError] = useState<string | null>(null);
|
|
62
|
+
const [selectedMcpTemplate, setSelectedMcpTemplate] = useState<McpLibraryEntry | null>(null);
|
|
63
|
+
const [importingMcpTemplates, setImportingMcpTemplates] = useState(false);
|
|
64
|
+
const [enablingSkillId, setEnablingSkillId] = useState<string | null>(null);
|
|
65
|
+
const [mcpSearch, setMcpSearch] = useState('');
|
|
66
|
+
const [skillSearch, setSkillSearch] = useState('');
|
|
67
|
+
const [showAllMcp, setShowAllMcp] = useState(false);
|
|
68
|
+
const [showAllSkills, setShowAllSkills] = useState(false);
|
|
69
|
+
const [applyNotice, setApplyNotice] = useState<string | null>(null);
|
|
70
|
+
|
|
71
|
+
// ── Derived data ──
|
|
72
|
+
const mcpServers = useMemo(
|
|
73
|
+
() => (projectPath ? (mcpByPath[projectPath] ?? []) : []),
|
|
74
|
+
[mcpByPath, projectPath]
|
|
75
|
+
);
|
|
76
|
+
// Diagnostics are stored keyed by `getMcpDiagnosticKey(name, scope)`, but the
|
|
77
|
+
// scope is not always present (e.g. the text-mode CLI parser omits it), so the
|
|
78
|
+
// scoped key can't be reconstructed reliably from an installed entry. Index by
|
|
79
|
+
// server name instead — chip rows already assume names are unique per project.
|
|
80
|
+
const diagnosticByName = useMemo(() => {
|
|
81
|
+
const record = projectPath ? (diagnosticsByPath[projectPath] ?? {}) : {};
|
|
82
|
+
return Object.fromEntries(Object.values(record).map((d) => [d.name, d] as const));
|
|
83
|
+
}, [diagnosticsByPath, projectPath]);
|
|
84
|
+
|
|
85
|
+
// Deduplicate skills by name (same skill may appear from multiple roots)
|
|
86
|
+
const skills = useMemo(() => {
|
|
87
|
+
const raw = projectPath ? (skillsByPath[projectPath] ?? []) : [];
|
|
88
|
+
const seen = new Set<string>();
|
|
89
|
+
return raw.filter((skill) => {
|
|
90
|
+
const key = skill.name.toLowerCase();
|
|
91
|
+
if (seen.has(key)) return false;
|
|
92
|
+
seen.add(key);
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
}, [skillsByPath, projectPath]);
|
|
96
|
+
|
|
97
|
+
const installedMcpNames = useMemo(
|
|
98
|
+
() => new Set(mcpServers.map((entry) => entry.name.toLowerCase())),
|
|
99
|
+
[mcpServers]
|
|
100
|
+
);
|
|
101
|
+
const installedSkillNames = useMemo(
|
|
102
|
+
() => new Set(skills.map((skill) => skill.name.toLowerCase())),
|
|
103
|
+
[skills]
|
|
104
|
+
);
|
|
105
|
+
const availableMcpLibrary = useMemo(() => {
|
|
106
|
+
const query = mcpSearch.trim().toLowerCase();
|
|
107
|
+
return mcpLibrary.filter((entry) => {
|
|
108
|
+
if (!query) return true;
|
|
109
|
+
return [entry.name, entry.description ?? '', summarizeMcp(entry)]
|
|
110
|
+
.join(' ')
|
|
111
|
+
.toLowerCase()
|
|
112
|
+
.includes(query);
|
|
113
|
+
});
|
|
114
|
+
}, [mcpLibrary, mcpSearch]);
|
|
115
|
+
const availableUserSkills = useMemo(() => {
|
|
116
|
+
const query = skillSearch.trim().toLowerCase();
|
|
117
|
+
return userSkills.filter((skill) => {
|
|
118
|
+
if (installedSkillNames.has(skill.name.toLowerCase())) return false;
|
|
119
|
+
if (!query) return true;
|
|
120
|
+
return [skill.name, skill.description ?? '', skill.folderName]
|
|
121
|
+
.join(' ')
|
|
122
|
+
.toLowerCase()
|
|
123
|
+
.includes(query);
|
|
124
|
+
});
|
|
125
|
+
}, [installedSkillNames, skillSearch, userSkills]);
|
|
126
|
+
const visibleMcpLibrary = showAllMcp ? availableMcpLibrary : availableMcpLibrary.slice(0, 6);
|
|
127
|
+
const visibleUserSkills = showAllSkills ? availableUserSkills : availableUserSkills.slice(0, 6);
|
|
128
|
+
const refreshLibraries = useCallback(async (): Promise<void> => {
|
|
129
|
+
setLibraryLoading(true);
|
|
130
|
+
setLibraryError(null);
|
|
131
|
+
try {
|
|
132
|
+
const [libraryEntries, globalSkills] = await Promise.all([
|
|
133
|
+
api.mcpRegistry?.libraryList?.() ?? Promise.resolve([]),
|
|
134
|
+
api.skills?.list?.() ?? Promise.resolve([]),
|
|
135
|
+
]);
|
|
136
|
+
setMcpLibrary(libraryEntries);
|
|
137
|
+
setUserSkills(globalSkills);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
setLibraryError(err instanceof Error ? err.message : '加载全局能力库失败');
|
|
140
|
+
} finally {
|
|
141
|
+
setLibraryLoading(false);
|
|
142
|
+
}
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
145
|
+
// ── Fetch data on mount ──
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (projectPath) {
|
|
148
|
+
mcpFetchInstalled(projectPath).catch(() => {});
|
|
149
|
+
runMcpDiagnostics(projectPath).catch(() => {});
|
|
150
|
+
fetchSkillsCatalog(projectPath).catch(() => {});
|
|
151
|
+
}
|
|
152
|
+
void refreshLibraries();
|
|
153
|
+
}, [projectPath, mcpFetchInstalled, runMcpDiagnostics, fetchSkillsCatalog, refreshLibraries]);
|
|
154
|
+
|
|
155
|
+
// ── Handlers ──
|
|
156
|
+
const handleRemoveMcp = useCallback(
|
|
157
|
+
(entry: { name: string; scope: string }) => {
|
|
158
|
+
void (async () => {
|
|
159
|
+
const confirmed = await confirm({
|
|
160
|
+
title: '移除 MCP 项目实例',
|
|
161
|
+
message: `确认从当前项目移除 MCP 实例「${entry.name}」?全局模板不会被删除。`,
|
|
162
|
+
confirmLabel: '移除实例',
|
|
163
|
+
cancelLabel: '取消',
|
|
164
|
+
variant: 'danger',
|
|
165
|
+
});
|
|
166
|
+
if (!confirmed) return;
|
|
167
|
+
uninstallMcpServer('', entry.name, entry.scope, projectPath ?? undefined)
|
|
168
|
+
.then(() => {
|
|
169
|
+
setApplyNotice('MCP 实例已移除;正在运行的数字员工需要重启后才会卸载该能力。');
|
|
170
|
+
})
|
|
171
|
+
.catch(() => {});
|
|
172
|
+
})();
|
|
173
|
+
},
|
|
174
|
+
[uninstallMcpServer, projectPath]
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const handleRemoveSkill = useCallback(
|
|
178
|
+
(skill: { id: string; name: string }) => {
|
|
179
|
+
void (async () => {
|
|
180
|
+
const confirmed = await confirm({
|
|
181
|
+
title: '禁用 Skill',
|
|
182
|
+
message: `确认从当前团队禁用 Skill「${skill.name}」?全局 Skill 仍会保留。`,
|
|
183
|
+
confirmLabel: '禁用',
|
|
184
|
+
cancelLabel: '取消',
|
|
185
|
+
variant: 'danger',
|
|
186
|
+
});
|
|
187
|
+
if (!confirmed) return;
|
|
188
|
+
deleteSkill({ skillId: skill.id, projectPath: projectPath ?? undefined })
|
|
189
|
+
.then(() => {
|
|
190
|
+
setApplyNotice('Skill 已禁用;正在运行的数字员工需要重启后才会卸载该能力。');
|
|
191
|
+
})
|
|
192
|
+
.catch(() => {});
|
|
193
|
+
})();
|
|
194
|
+
},
|
|
195
|
+
[deleteSkill, projectPath]
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const handleMcpTemplateAdded = useCallback(
|
|
199
|
+
(entry: McpLibraryEntry) => {
|
|
200
|
+
setAddingMcp(false);
|
|
201
|
+
setMcpLibrary((prev) => {
|
|
202
|
+
const next = prev.filter((item) => item.id !== entry.id);
|
|
203
|
+
return [entry, ...next];
|
|
204
|
+
});
|
|
205
|
+
setSelectedMcpTemplate(entry);
|
|
206
|
+
void refreshLibraries();
|
|
207
|
+
},
|
|
208
|
+
[refreshLibraries]
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const handleMcpInstanceAdded = useCallback(() => {
|
|
212
|
+
setSelectedMcpTemplate(null);
|
|
213
|
+
if (projectPath) {
|
|
214
|
+
mcpFetchInstalled(projectPath).catch(() => {});
|
|
215
|
+
runMcpDiagnostics(projectPath).catch(() => {});
|
|
216
|
+
}
|
|
217
|
+
void refreshLibraries();
|
|
218
|
+
}, [projectPath, mcpFetchInstalled, runMcpDiagnostics, refreshLibraries]);
|
|
219
|
+
|
|
220
|
+
const handleImportMcpTemplates = useCallback(() => {
|
|
221
|
+
void (async () => {
|
|
222
|
+
if (!api.mcpRegistry?.libraryImport) {
|
|
223
|
+
setLibraryError('MCP 模板导入 API 不可用');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
setImportingMcpTemplates(true);
|
|
227
|
+
setLibraryError(null);
|
|
228
|
+
try {
|
|
229
|
+
const result = await api.mcpRegistry.libraryImport({
|
|
230
|
+
projectPath: projectPath ?? undefined,
|
|
231
|
+
});
|
|
232
|
+
await refreshLibraries();
|
|
233
|
+
setApplyNotice(
|
|
234
|
+
`已从现有 MCP 配置导入模板:新增 ${result.imported.length} 个,跳过 ${result.skipped.length} 个。`
|
|
235
|
+
);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
setLibraryError(err instanceof Error ? err.message : '导入现有 MCP 配置失败');
|
|
238
|
+
} finally {
|
|
239
|
+
setImportingMcpTemplates(false);
|
|
240
|
+
}
|
|
241
|
+
})();
|
|
242
|
+
}, [projectPath, refreshLibraries]);
|
|
243
|
+
|
|
244
|
+
const handleSkillAdded = useCallback(() => {
|
|
245
|
+
setAddingSkill(false);
|
|
246
|
+
if (projectPath) {
|
|
247
|
+
fetchSkillsCatalog(projectPath).catch(() => {});
|
|
248
|
+
}
|
|
249
|
+
void refreshLibraries();
|
|
250
|
+
}, [projectPath, fetchSkillsCatalog, refreshLibraries]);
|
|
251
|
+
|
|
252
|
+
const enableSkillFromLibrary = useCallback(
|
|
253
|
+
async (skill: SkillCatalogItem): Promise<void> => {
|
|
254
|
+
if (!projectPath) return;
|
|
255
|
+
setEnablingSkillId(skill.id);
|
|
256
|
+
try {
|
|
257
|
+
await fetchSkillDetail(skill.id);
|
|
258
|
+
const detail =
|
|
259
|
+
useStore.getState().skillsDetailsById[skill.id] ?? skillDetailsById[skill.id];
|
|
260
|
+
const sourceDir = detail?.item.skillDir ?? skill.skillDir;
|
|
261
|
+
const preview = await previewSkillImport({
|
|
262
|
+
sourceDir,
|
|
263
|
+
scope: 'project',
|
|
264
|
+
rootKind: skill.rootKind,
|
|
265
|
+
projectPath,
|
|
266
|
+
folderName: skill.folderName,
|
|
267
|
+
});
|
|
268
|
+
await applySkillImport({
|
|
269
|
+
sourceDir,
|
|
270
|
+
scope: 'project',
|
|
271
|
+
rootKind: skill.rootKind,
|
|
272
|
+
projectPath,
|
|
273
|
+
folderName: skill.folderName,
|
|
274
|
+
reviewPlanId: preview.planId,
|
|
275
|
+
});
|
|
276
|
+
await fetchSkillsCatalog(projectPath);
|
|
277
|
+
setApplyNotice('Skill 已启用;正在运行的数字员工需要重启后才会加载新能力。');
|
|
278
|
+
} finally {
|
|
279
|
+
setEnablingSkillId(null);
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
[
|
|
283
|
+
applySkillImport,
|
|
284
|
+
fetchSkillDetail,
|
|
285
|
+
fetchSkillsCatalog,
|
|
286
|
+
previewSkillImport,
|
|
287
|
+
projectPath,
|
|
288
|
+
skillDetailsById,
|
|
289
|
+
]
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
if (!projectPath) {
|
|
293
|
+
return (
|
|
294
|
+
<div className="flex items-center gap-2 px-1 py-2 text-xs text-[var(--color-text-muted)]">
|
|
295
|
+
<Wrench size={12} />
|
|
296
|
+
<span>需要关联项目目录才能管理能力</span>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div className="flex flex-col gap-4 px-1 py-2">
|
|
303
|
+
{libraryError ? (
|
|
304
|
+
<div className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2 py-1.5 text-xs text-amber-300">
|
|
305
|
+
{libraryError}
|
|
306
|
+
</div>
|
|
307
|
+
) : null}
|
|
308
|
+
|
|
309
|
+
{applyNotice ? (
|
|
310
|
+
<div className="rounded-md border border-blue-500/30 bg-blue-500/10 px-2 py-1.5 text-xs text-blue-300">
|
|
311
|
+
{applyNotice}
|
|
312
|
+
</div>
|
|
313
|
+
) : null}
|
|
314
|
+
|
|
315
|
+
<div className="flex items-center justify-between gap-2">
|
|
316
|
+
<div className="text-[11px] text-[var(--color-text-muted)]">
|
|
317
|
+
当前项目管理 MCP 实例;全局库只提供可复用模板。先导入/新建模板,再添加为项目实例。
|
|
318
|
+
</div>
|
|
319
|
+
<div className="flex items-center gap-1">
|
|
320
|
+
<Button
|
|
321
|
+
variant="ghost"
|
|
322
|
+
size="sm"
|
|
323
|
+
className="h-6 px-2 text-[11px]"
|
|
324
|
+
disabled={importingMcpTemplates}
|
|
325
|
+
onClick={handleImportMcpTemplates}
|
|
326
|
+
>
|
|
327
|
+
{importingMcpTemplates ? '导入中...' : '导入现有 MCP'}
|
|
328
|
+
</Button>
|
|
329
|
+
<Button
|
|
330
|
+
variant="ghost"
|
|
331
|
+
size="sm"
|
|
332
|
+
className="h-6 px-2 text-[11px]"
|
|
333
|
+
onClick={() => void refreshLibraries()}
|
|
334
|
+
>
|
|
335
|
+
<RefreshCw className={libraryLoading ? 'mr-1 size-3 animate-spin' : 'mr-1 size-3'} />
|
|
336
|
+
刷新
|
|
337
|
+
</Button>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
{/* MCP capabilities */}
|
|
342
|
+
<div className="flex flex-col gap-2 rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.015] p-2">
|
|
343
|
+
<div className="flex items-center justify-between gap-2">
|
|
344
|
+
<div className="flex items-center gap-1.5 text-[11px] font-medium text-[var(--color-text-muted)]">
|
|
345
|
+
<span>MCP 实例</span>
|
|
346
|
+
<span className="text-[10px]">
|
|
347
|
+
已配置实例 {mcpServers.length} · 模板 {mcpLibrary.length}
|
|
348
|
+
</span>
|
|
349
|
+
</div>
|
|
350
|
+
<Button
|
|
351
|
+
variant="ghost"
|
|
352
|
+
size="sm"
|
|
353
|
+
className="h-6 px-2 text-[11px]"
|
|
354
|
+
onClick={() => setAddingMcp(true)}
|
|
355
|
+
>
|
|
356
|
+
<Plus size={10} className="mr-1" />
|
|
357
|
+
新建模板
|
|
358
|
+
</Button>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
362
|
+
{mcpServers.map((entry) => (
|
|
363
|
+
<McpChip
|
|
364
|
+
key={entry.name}
|
|
365
|
+
entry={entry}
|
|
366
|
+
diagnostic={diagnosticByName[entry.name]}
|
|
367
|
+
onRemove={handleRemoveMcp}
|
|
368
|
+
/>
|
|
369
|
+
))}
|
|
370
|
+
{mcpServers.length === 0 ? (
|
|
371
|
+
<span className="text-xs text-[var(--color-text-muted)]">
|
|
372
|
+
当前项目还没有配置 MCP 实例。
|
|
373
|
+
</span>
|
|
374
|
+
) : null}
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
{addingMcp ? (
|
|
378
|
+
<AddMcpInline onAdded={handleMcpTemplateAdded} onCancel={() => setAddingMcp(false)} />
|
|
379
|
+
) : null}
|
|
380
|
+
|
|
381
|
+
<div className="space-y-1.5">
|
|
382
|
+
<div className="text-[10px] uppercase tracking-wide text-[var(--color-text-muted)]">
|
|
383
|
+
从模板添加
|
|
384
|
+
</div>
|
|
385
|
+
<Input
|
|
386
|
+
value={mcpSearch}
|
|
387
|
+
onChange={(event) => setMcpSearch(event.target.value)}
|
|
388
|
+
placeholder="搜索 MCP 模板..."
|
|
389
|
+
className="h-7 text-xs"
|
|
390
|
+
/>
|
|
391
|
+
{availableMcpLibrary.length > 0 ? (
|
|
392
|
+
<div className="flex flex-col gap-1.5">
|
|
393
|
+
{visibleMcpLibrary.map((entry) => (
|
|
394
|
+
<div
|
|
395
|
+
key={entry.id}
|
|
396
|
+
className="flex items-center justify-between gap-2 rounded-md border border-[var(--color-border-subtle)] px-2 py-1.5"
|
|
397
|
+
>
|
|
398
|
+
<div className="min-w-0">
|
|
399
|
+
<div className="truncate text-xs text-[var(--color-text)]">{entry.name}</div>
|
|
400
|
+
<div className="truncate text-[10px] text-[var(--color-text-muted)]">
|
|
401
|
+
{summarizeMcp(entry)}
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
<Button
|
|
405
|
+
variant="outline"
|
|
406
|
+
size="sm"
|
|
407
|
+
className="h-6 px-2 text-[11px]"
|
|
408
|
+
onClick={() => setSelectedMcpTemplate(entry)}
|
|
409
|
+
>
|
|
410
|
+
从模板添加
|
|
411
|
+
</Button>
|
|
412
|
+
</div>
|
|
413
|
+
))}
|
|
414
|
+
{availableMcpLibrary.length > 6 ? (
|
|
415
|
+
<Button
|
|
416
|
+
variant="ghost"
|
|
417
|
+
size="sm"
|
|
418
|
+
className="h-6 justify-start px-2 text-[11px]"
|
|
419
|
+
onClick={() => setShowAllMcp((value) => !value)}
|
|
420
|
+
>
|
|
421
|
+
{showAllMcp ? '收起' : `显示全部 ${availableMcpLibrary.length} 个模板`}
|
|
422
|
+
</Button>
|
|
423
|
+
) : null}
|
|
424
|
+
</div>
|
|
425
|
+
) : (
|
|
426
|
+
<div className="flex flex-col gap-2 rounded-md border border-dashed border-[var(--color-border)] px-2 py-2 text-xs text-[var(--color-text-muted)]">
|
|
427
|
+
<span>全局 MCP 模板库暂无可添加项。可以从现有 MCP 配置导入,或新建模板。</span>
|
|
428
|
+
<div className="flex flex-wrap gap-2">
|
|
429
|
+
<Button
|
|
430
|
+
variant="outline"
|
|
431
|
+
size="sm"
|
|
432
|
+
className="h-6 px-2 text-[11px]"
|
|
433
|
+
disabled={importingMcpTemplates}
|
|
434
|
+
onClick={handleImportMcpTemplates}
|
|
435
|
+
>
|
|
436
|
+
{importingMcpTemplates ? '导入中...' : '导入现有 MCP'}
|
|
437
|
+
</Button>
|
|
438
|
+
<Button
|
|
439
|
+
variant="ghost"
|
|
440
|
+
size="sm"
|
|
441
|
+
className="h-6 px-2 text-[11px]"
|
|
442
|
+
onClick={() => setAddingMcp(true)}
|
|
443
|
+
>
|
|
444
|
+
新建模板
|
|
445
|
+
</Button>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<McpLibraryEnableDialog
|
|
453
|
+
open={Boolean(selectedMcpTemplate)}
|
|
454
|
+
entry={selectedMcpTemplate}
|
|
455
|
+
projectPath={projectPath}
|
|
456
|
+
installedServers={mcpServers}
|
|
457
|
+
harnessType={harnessType}
|
|
458
|
+
onClose={() => setSelectedMcpTemplate(null)}
|
|
459
|
+
onEnabled={handleMcpInstanceAdded}
|
|
460
|
+
/>
|
|
461
|
+
|
|
462
|
+
{/* Skill capabilities */}
|
|
463
|
+
<div className="flex flex-col gap-2 rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.015] p-2">
|
|
464
|
+
<div className="flex items-center justify-between gap-2">
|
|
465
|
+
<div className="flex items-center gap-1.5 text-[11px] font-medium text-[var(--color-text-muted)]">
|
|
466
|
+
<span>Skill 能力</span>
|
|
467
|
+
<span className="text-[10px]">
|
|
468
|
+
已启用 {skills.length} · 全局 {userSkills.length}
|
|
469
|
+
</span>
|
|
470
|
+
</div>
|
|
471
|
+
<Button
|
|
472
|
+
variant="ghost"
|
|
473
|
+
size="sm"
|
|
474
|
+
className="h-6 px-2 text-[11px]"
|
|
475
|
+
onClick={() => setAddingSkill(true)}
|
|
476
|
+
>
|
|
477
|
+
<Plus size={10} className="mr-1" />
|
|
478
|
+
新建 Skill
|
|
479
|
+
</Button>
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
483
|
+
{skills.map((skill) => (
|
|
484
|
+
<SkillChip key={skill.id} skill={skill} onRemove={handleRemoveSkill} />
|
|
485
|
+
))}
|
|
486
|
+
{skills.length === 0 ? (
|
|
487
|
+
<span className="text-xs text-[var(--color-text-muted)]">
|
|
488
|
+
当前团队还没有启用 Skill。
|
|
489
|
+
</span>
|
|
490
|
+
) : null}
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
{addingSkill ? (
|
|
494
|
+
<AddSkillInline
|
|
495
|
+
projectPath={projectPath}
|
|
496
|
+
projectLabel={teamName}
|
|
497
|
+
onAdded={handleSkillAdded}
|
|
498
|
+
onCancel={() => setAddingSkill(false)}
|
|
499
|
+
/>
|
|
500
|
+
) : null}
|
|
501
|
+
|
|
502
|
+
<div className="space-y-1.5">
|
|
503
|
+
<div className="text-[10px] uppercase tracking-wide text-[var(--color-text-muted)]">
|
|
504
|
+
从全局 Skill 启用
|
|
505
|
+
</div>
|
|
506
|
+
<Input
|
|
507
|
+
value={skillSearch}
|
|
508
|
+
onChange={(event) => setSkillSearch(event.target.value)}
|
|
509
|
+
placeholder="搜索 Skill..."
|
|
510
|
+
className="h-7 text-xs"
|
|
511
|
+
/>
|
|
512
|
+
{availableUserSkills.length > 0 ? (
|
|
513
|
+
<div className="flex flex-col gap-1.5">
|
|
514
|
+
{visibleUserSkills.map((skill) => (
|
|
515
|
+
<div
|
|
516
|
+
key={skill.id}
|
|
517
|
+
className="flex items-center justify-between gap-2 rounded-md border border-[var(--color-border-subtle)] px-2 py-1.5"
|
|
518
|
+
>
|
|
519
|
+
<div className="min-w-0">
|
|
520
|
+
<div className="truncate text-xs text-[var(--color-text)]">{skill.name}</div>
|
|
521
|
+
<div className="truncate text-[10px] text-[var(--color-text-muted)]">
|
|
522
|
+
{skill.description || skill.folderName}
|
|
523
|
+
</div>
|
|
524
|
+
</div>
|
|
525
|
+
<Button
|
|
526
|
+
variant="outline"
|
|
527
|
+
size="sm"
|
|
528
|
+
className="h-6 px-2 text-[11px]"
|
|
529
|
+
disabled={enablingSkillId === skill.id}
|
|
530
|
+
onClick={() => void enableSkillFromLibrary(skill)}
|
|
531
|
+
>
|
|
532
|
+
{enablingSkillId === skill.id ? '启用中...' : '启用'}
|
|
533
|
+
</Button>
|
|
534
|
+
</div>
|
|
535
|
+
))}
|
|
536
|
+
{availableUserSkills.length > 6 ? (
|
|
537
|
+
<Button
|
|
538
|
+
variant="ghost"
|
|
539
|
+
size="sm"
|
|
540
|
+
className="h-6 justify-start px-2 text-[11px]"
|
|
541
|
+
onClick={() => setShowAllSkills((value) => !value)}
|
|
542
|
+
>
|
|
543
|
+
{showAllSkills ? '收起' : `显示全部 ${availableUserSkills.length} 个 Skill`}
|
|
544
|
+
</Button>
|
|
545
|
+
) : null}
|
|
546
|
+
</div>
|
|
547
|
+
) : (
|
|
548
|
+
<div className="rounded-md border border-dashed border-[var(--color-border)] px-2 py-2 text-xs text-[var(--color-text-muted)]">
|
|
549
|
+
全局 Skill 暂无可启用项。可以先创建用户级 Skill,再在团队中启用。
|
|
550
|
+
</div>
|
|
551
|
+
)}
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
);
|
|
556
|
+
};
|
|
@@ -4,20 +4,11 @@
|
|
|
4
4
|
* Global catalog caches are in extensionsSlice (Zustand).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useCallback,
|
|
7
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import type { PluginCapability, PluginFilters, PluginSortField } from '@shared/types/extensions';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
McpCatalogItem,
|
|
13
|
-
McpSearchResult,
|
|
14
|
-
PluginCapability,
|
|
15
|
-
PluginFilters,
|
|
16
|
-
PluginSortField,
|
|
17
|
-
} from '@shared/types/extensions';
|
|
18
|
-
|
|
19
|
-
export type ExtensionsSubTab = 'plugins' | 'mcp-servers' | 'skills' | 'env-vars';
|
|
20
|
-
export type SkillsSortState = 'name-asc' | 'recent-desc';
|
|
11
|
+
export type ExtensionsSubTab = 'plugins';
|
|
21
12
|
|
|
22
13
|
interface PluginSortState {
|
|
23
14
|
field: PluginSortField;
|
|
@@ -43,90 +34,6 @@ export function useExtensionsTabState() {
|
|
|
43
34
|
});
|
|
44
35
|
const [selectedPluginId, setSelectedPluginId] = useState<string | null>(null);
|
|
45
36
|
|
|
46
|
-
// ── MCP search (per-tab, calls API directly) ──
|
|
47
|
-
const [mcpSearchQuery, setMcpSearchQuery] = useState('');
|
|
48
|
-
const [mcpSearchResults, setMcpSearchResults] = useState<McpCatalogItem[]>([]);
|
|
49
|
-
const [mcpSearchLoading, setMcpSearchLoading] = useState(false);
|
|
50
|
-
const [mcpSearchWarnings, setMcpSearchWarnings] = useState<string[]>([]);
|
|
51
|
-
const [selectedMcpServerId, setSelectedMcpServerId] = useState<string | null>(null);
|
|
52
|
-
|
|
53
|
-
// ── Skills browse ──
|
|
54
|
-
const [skillsSearchQuery, setSkillsSearchQuery] = useState('');
|
|
55
|
-
const [skillsInstalledOnly] = useState(false);
|
|
56
|
-
const [skillsSort, setSkillsSort] = useState<SkillsSortState>('name-asc');
|
|
57
|
-
const [selectedSkillId, setSelectedSkillId] = useState<string | null>(null);
|
|
58
|
-
|
|
59
|
-
// ── Debounced MCP search ──
|
|
60
|
-
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
61
|
-
const mcpSearchRequestSeqRef = useRef(0);
|
|
62
|
-
|
|
63
|
-
// Cleanup timer on unmount
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
return () => {
|
|
66
|
-
if (searchTimerRef.current) {
|
|
67
|
-
clearTimeout(searchTimerRef.current);
|
|
68
|
-
}
|
|
69
|
-
mcpSearchRequestSeqRef.current += 1;
|
|
70
|
-
};
|
|
71
|
-
}, []);
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
if (activeSubTab !== 'plugins' && selectedPluginId !== null) {
|
|
75
|
-
setSelectedPluginId(null);
|
|
76
|
-
}
|
|
77
|
-
if (activeSubTab !== 'mcp-servers' && selectedMcpServerId !== null) {
|
|
78
|
-
setSelectedMcpServerId(null);
|
|
79
|
-
}
|
|
80
|
-
if (activeSubTab !== 'skills' && selectedSkillId !== null) {
|
|
81
|
-
setSelectedSkillId(null);
|
|
82
|
-
}
|
|
83
|
-
}, [activeSubTab, selectedMcpServerId, selectedPluginId, selectedSkillId]);
|
|
84
|
-
|
|
85
|
-
const mcpSearch = useCallback((query: string) => {
|
|
86
|
-
setMcpSearchQuery(query);
|
|
87
|
-
const requestId = ++mcpSearchRequestSeqRef.current;
|
|
88
|
-
|
|
89
|
-
if (searchTimerRef.current) {
|
|
90
|
-
clearTimeout(searchTimerRef.current);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!query.trim()) {
|
|
94
|
-
setMcpSearchResults([]);
|
|
95
|
-
setMcpSearchWarnings([]);
|
|
96
|
-
setMcpSearchLoading(false);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
setMcpSearchLoading(true);
|
|
101
|
-
|
|
102
|
-
searchTimerRef.current = setTimeout(() => {
|
|
103
|
-
if (!api.mcpRegistry) {
|
|
104
|
-
if (mcpSearchRequestSeqRef.current === requestId) {
|
|
105
|
-
setMcpSearchLoading(false);
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
void api.mcpRegistry.search(query).then(
|
|
111
|
-
(result: McpSearchResult) => {
|
|
112
|
-
if (mcpSearchRequestSeqRef.current !== requestId) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
setMcpSearchResults(result.servers);
|
|
116
|
-
setMcpSearchWarnings(result.warnings);
|
|
117
|
-
setMcpSearchLoading(false);
|
|
118
|
-
},
|
|
119
|
-
() => {
|
|
120
|
-
if (mcpSearchRequestSeqRef.current !== requestId) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
setMcpSearchLoading(false);
|
|
124
|
-
setMcpSearchWarnings(['Search failed']);
|
|
125
|
-
}
|
|
126
|
-
);
|
|
127
|
-
}, 300);
|
|
128
|
-
}, []);
|
|
129
|
-
|
|
130
37
|
// ── Plugin filter helpers ──
|
|
131
38
|
const updatePluginSearch = useCallback((search: string) => {
|
|
132
39
|
setPluginFilters((prev) => ({ ...prev, search }));
|
|
@@ -184,23 +91,5 @@ export function useExtensionsTabState() {
|
|
|
184
91
|
toggleInstalledOnly,
|
|
185
92
|
clearFilters,
|
|
186
93
|
hasActiveFilters,
|
|
187
|
-
|
|
188
|
-
// MCP
|
|
189
|
-
mcpSearchQuery,
|
|
190
|
-
mcpSearch,
|
|
191
|
-
mcpSearchResults,
|
|
192
|
-
mcpSearchLoading,
|
|
193
|
-
mcpSearchWarnings,
|
|
194
|
-
selectedMcpServerId,
|
|
195
|
-
setSelectedMcpServerId,
|
|
196
|
-
|
|
197
|
-
// Skills
|
|
198
|
-
skillsSearchQuery,
|
|
199
|
-
setSkillsSearchQuery,
|
|
200
|
-
skillsInstalledOnly,
|
|
201
|
-
skillsSort,
|
|
202
|
-
setSkillsSort,
|
|
203
|
-
selectedSkillId,
|
|
204
|
-
setSelectedSkillId,
|
|
205
94
|
};
|
|
206
95
|
}
|