@yancyyu/openhermit 1.6.29 → 1.6.31

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 (157) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
  5. package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
  9. package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
  20. package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
  30. package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
  31. package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
  32. package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
  33. package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
  34. package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
  35. package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
  36. package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
  37. package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
  41. package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
  42. package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
  52. package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +209 -6
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  93. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  94. package/src/main/services/team/cliFlavor.ts +54 -0
  95. package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
  96. package/src/main/utils/atomicWrite.ts +72 -0
  97. package/src/main/utils/childProcess.ts +554 -0
  98. package/src/main/utils/cliEnv.ts +54 -0
  99. package/src/main/utils/cliPathMerge.ts +97 -0
  100. package/src/main/utils/pathDecoder.ts +664 -0
  101. package/src/main/utils/pathValidation.ts +432 -0
  102. package/src/main/utils/shellEnv.ts +331 -0
  103. package/src/renderer/api/httpClient.ts +61 -0
  104. package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
  105. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  106. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  107. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  108. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  109. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  110. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  111. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
  112. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  113. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  114. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  115. package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
  116. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  117. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  118. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  119. package/src/renderer/components/team/TeamDetailView.tsx +74 -123
  120. package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
  121. package/src/renderer/components/team/TeamListView.tsx +7 -32
  122. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  123. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
  124. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
  125. package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
  126. package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
  127. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  128. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  129. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  130. package/src/renderer/store/slices/teamSlice.ts +8 -2
  131. package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
  132. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
  133. package/src/shared/types/api.ts +29 -0
  134. package/src/shared/types/extensions/index.ts +1 -0
  135. package/src/shared/types/extensions/mcp.ts +2 -0
  136. package/src/shared/types/extensions/plugin.ts +2 -1
  137. package/src/shared/types/extensions/skill.ts +7 -0
  138. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  139. package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
  140. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
  141. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
  142. package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
  143. package/dist-renderer/assets/index-BhellmRb.css +0 -1
  144. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
  145. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  146. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  147. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  148. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  149. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  150. package/src/features/recent-projects/main/index.ts +0 -3
  151. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  152. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  153. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  154. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  155. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  156. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  157. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -15,6 +15,12 @@ import {
15
15
  DialogHeader,
16
16
  DialogTitle,
17
17
  } from '@renderer/components/ui/dialog';
18
+ import {
19
+ Tooltip,
20
+ TooltipContent,
21
+ TooltipProvider,
22
+ TooltipTrigger,
23
+ } from '@renderer/components/ui/tooltip';
18
24
  import { Input } from '@renderer/components/ui/input';
19
25
  import { Label } from '@renderer/components/ui/label';
20
26
  import {
@@ -26,6 +32,7 @@ import {
26
32
  } from '@renderer/components/ui/select';
27
33
  import { useStore } from '@renderer/store';
28
34
  import {
35
+ getExtensionActionDisableReason,
29
36
  getMcpInstallationSummaryLabel,
30
37
  getMcpOperationKey,
31
38
  getPreferredMcpInstallationEntry,
@@ -37,9 +44,10 @@ import {
37
44
  isProjectScopedMcpScope,
38
45
  isSharedMcpScope,
39
46
  } from '@shared/utils/mcpScopes';
40
- import { ExternalLink, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react';
47
+ import { Check, ExternalLink, Loader2, Lock, Plus, Star, Trash2, Wrench } from 'lucide-react';
41
48
 
42
49
  import { InstallButton } from '../common/InstallButton';
50
+ import { HarnessSelector } from '../common/HarnessSelector';
43
51
  import { SourceBadge } from '../common/SourceBadge';
44
52
 
45
53
  import type { CliInstallationStatus } from '@shared/types';
@@ -98,6 +106,7 @@ export const McpServerDetailDialog = ({
98
106
  );
99
107
 
100
108
  const [serverName, setServerName] = useState('');
109
+ const [harnessType, setHarnessType] = useState('claudecode');
101
110
  const [envValues, setEnvValues] = useState<Record<string, string>>({});
102
111
  const [headers, setHeaders] = useState<McpHeaderDef[]>([]);
103
112
  const [imgError, setImgError] = useState(false);
@@ -254,6 +263,18 @@ export const McpServerDetailDialog = ({
254
263
  missingRequiredEnvVars ||
255
264
  missingRequiredHeaders ||
256
265
  scopeRequiresProjectPath;
266
+ const installCliDisableReason = getExtensionActionDisableReason({
267
+ isInstalled: false,
268
+ cliStatus,
269
+ cliStatusLoading: cliStatusLoading ?? false,
270
+ section: 'mcp',
271
+ });
272
+ const uninstallCliDisableReason = getExtensionActionDisableReason({
273
+ isInstalled: true,
274
+ cliStatus,
275
+ cliStatusLoading: cliStatusLoading ?? false,
276
+ section: 'mcp',
277
+ });
257
278
  const diagnosticBadgeClass =
258
279
  diagnostic?.status === 'connected'
259
280
  ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-400'
@@ -271,6 +292,7 @@ export const McpServerDetailDialog = ({
271
292
  projectPath: isProjectScopedMcpScope(scope) ? (projectPath ?? undefined) : undefined,
272
293
  envValues,
273
294
  headers,
295
+ harnessType,
274
296
  });
275
297
  };
276
298
 
@@ -493,6 +515,14 @@ export const McpServerDetailDialog = ({
493
515
  </Select>
494
516
  </div>
495
517
 
518
+ {/* Harness selector */}
519
+ <HarnessSelector
520
+ capability="mcp"
521
+ value={harnessType}
522
+ onChange={setHarnessType}
523
+ disabled={isInstalledForScope}
524
+ />
525
+
496
526
  {/* Environment variables */}
497
527
  {server.envVars.length > 0 && (
498
528
  <div className="space-y-1.5">
@@ -588,20 +618,86 @@ export const McpServerDetailDialog = ({
588
618
  </div>
589
619
  )}
590
620
 
591
- {/* Install/Uninstall button */}
592
- <div className="flex justify-end pt-1">
593
- <InstallButton
594
- state={installProgress}
595
- isInstalled={isInstalledForScope}
596
- section="mcp"
597
- cliStatus={cliStatus}
598
- cliStatusLoading={cliStatusLoading}
599
- onInstall={handleInstall}
600
- onUninstall={handleUninstall}
601
- disabled={installDisabled}
602
- size="default"
603
- errorMessage={installError}
604
- />
621
+ {/* Install / Save & Restart / Uninstall */}
622
+ <div className="flex items-center justify-end gap-2 pt-1">
623
+ {isInstalledForScope ? (
624
+ <>
625
+ <Button
626
+ variant="ghost"
627
+ size="sm"
628
+ className="border-red-500/30 text-red-400 hover:bg-red-500/10"
629
+ data-testid="uninstall-button"
630
+ onClick={handleUninstall}
631
+ disabled={Boolean(uninstallCliDisableReason)}
632
+ >
633
+ <Trash2 className="size-3.5" />
634
+ <span className="ml-1.5">卸载</span>
635
+ </Button>
636
+ {installProgress === 'pending' ? (
637
+ <Button size="default" disabled>
638
+ <Loader2 className="size-3.5 animate-spin" />
639
+ <span className="ml-1.5">保存并重启中...</span>
640
+ </Button>
641
+ ) : installProgress === 'success' ? (
642
+ <Button size="default" disabled className="text-green-400">
643
+ <Check className="size-3.5" />
644
+ <span className="ml-1.5">完成</span>
645
+ </Button>
646
+ ) : installProgress === 'error' ? (
647
+ <div className="flex max-w-64 flex-col items-end gap-1">
648
+ <TooltipProvider>
649
+ <Tooltip>
650
+ <TooltipTrigger asChild>
651
+ <span tabIndex={0}>
652
+ <Button
653
+ size="default"
654
+ variant="outline"
655
+ className="border-red-500/30 text-red-400 hover:bg-red-500/10"
656
+ onClick={handleInstall}
657
+ disabled={installDisabled || Boolean(installCliDisableReason)}
658
+ >
659
+ 保存并重启
660
+ </Button>
661
+ </span>
662
+ </TooltipTrigger>
663
+ {installError && (
664
+ <TooltipContent className="max-w-64 text-red-300">
665
+ {installError}
666
+ </TooltipContent>
667
+ )}
668
+ </Tooltip>
669
+ </TooltipProvider>
670
+ {installError && (
671
+ <p className="text-right text-[11px] leading-4 text-red-300">
672
+ {installError}
673
+ </p>
674
+ )}
675
+ </div>
676
+ ) : (
677
+ <Button
678
+ size="default"
679
+ data-testid="save-restart-button"
680
+ onClick={handleInstall}
681
+ disabled={installDisabled || Boolean(installCliDisableReason)}
682
+ >
683
+ 保存并重启
684
+ </Button>
685
+ )}
686
+ </>
687
+ ) : (
688
+ <InstallButton
689
+ state={installProgress}
690
+ isInstalled={false}
691
+ section="mcp"
692
+ cliStatus={cliStatus}
693
+ cliStatusLoading={cliStatusLoading}
694
+ onInstall={handleInstall}
695
+ onUninstall={handleUninstall}
696
+ disabled={installDisabled}
697
+ size="default"
698
+ errorMessage={installError}
699
+ />
700
+ )}
605
701
  </div>
606
702
  </div>
607
703
  )}
@@ -256,7 +256,7 @@ export const McpServersPanel = ({
256
256
  }
257
257
  };
258
258
 
259
- // Sort displayed servers
259
+ // Sort displayed catalog servers
260
260
  const displayServers = useMemo(() => sortMcpServers(rawServers, mcpSort), [rawServers, mcpSort]);
261
261
  const runtimeLabel = getRuntimeDisplayName(cliStatus, true);
262
262
 
@@ -472,6 +472,56 @@ export const McpServersPanel = ({
472
472
  </div>
473
473
  ))}
474
474
 
475
+ {/* Installed servers (catalog-style cards for custom installs) */}
476
+ {installedServers.length > 0 && (
477
+ <div className="space-y-3">
478
+ <div className="flex items-center justify-between gap-3">
479
+ <p className="text-sm font-medium text-text">已安装</p>
480
+ <Badge variant="secondary" className="font-normal">
481
+ {installedServers.length}
482
+ </Badge>
483
+ </div>
484
+ <div className="mcp-servers-grid grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
485
+ {installedServers.map((entry) => {
486
+ // Find matching catalog item
487
+ const catalogMatch = browseCatalog.find(
488
+ (c) => sanitizeMcpServerName(c.name) === entry.name.toLowerCase()
489
+ );
490
+ const fakeItem: McpCatalogItem = catalogMatch ?? {
491
+ id: `custom:${entry.name}`,
492
+ name: entry.name,
493
+ description: entry.transport === 'http' ? 'HTTP/SSE 服务器' : 'Stdio 服务器',
494
+ source: 'official',
495
+ installSpec: null,
496
+ envVars: [],
497
+ requiresAuth: false,
498
+ tools: [],
499
+ };
500
+ const diagnostic =
501
+ mcpDiagnostics[getMcpDiagnosticKey(entry.name, entry.scope)] ??
502
+ mcpDiagnostics[getMcpDiagnosticKey(entry.name)] ??
503
+ mcpDiagnostics[entry.name] ??
504
+ null;
505
+
506
+ return (
507
+ <McpServerCard
508
+ key={`${entry.name}-${entry.scope}`}
509
+ server={fakeItem}
510
+ isInstalled={true}
511
+ installedEntry={entry}
512
+ installedEntries={[entry]}
513
+ diagnostic={diagnostic}
514
+ diagnosticsLoading={mcpDiagnosticsLoading}
515
+ onClick={setSelectedMcpServerId}
516
+ cliStatus={cliStatus}
517
+ cliStatusLoading={cliStatusLoading}
518
+ />
519
+ );
520
+ })}
521
+ </div>
522
+ </div>
523
+ )}
524
+
475
525
  {/* Empty state */}
476
526
  {!isLoading && displayServers.length === 0 && (
477
527
  <div className="flex flex-col items-center gap-3 rounded-sm border border-dashed border-border px-8 py-16">
@@ -23,7 +23,6 @@ import {
23
23
  Info,
24
24
  Plus,
25
25
  Search,
26
- Trash2,
27
26
  } from 'lucide-react';
28
27
  import { useShallow } from 'zustand/react/shallow';
29
28
 
@@ -35,12 +34,7 @@ import { SkillImportDialog } from './SkillImportDialog';
35
34
  import { resolveSkillProjectPath } from './skillProjectUtils';
36
35
 
37
36
  import type { SkillsSortState } from '@renderer/hooks/useExtensionsTabState';
38
- import type {
39
- SkillCatalogItem,
40
- SkillDetail,
41
- SkillSource,
42
- SkillValidationIssue,
43
- } from '@shared/types/extensions';
37
+ import type { SkillCatalogItem, SkillDetail, SkillValidationIssue } from '@shared/types/extensions';
44
38
 
45
39
  const SUCCESS_BANNER_MS = 2500;
46
40
  const NEW_SKILL_HIGHLIGHT_MS = 4000;
@@ -141,10 +135,6 @@ export const SkillsPanel = ({
141
135
  const [quickFilter, setQuickFilter] = useState<SkillsQuickFilter>('all');
142
136
  const [successMessage, setSuccessMessage] = useState<string | null>(null);
143
137
  const [highlightedSkillId, setHighlightedSkillId] = useState<string | null>(null);
144
- const [skillSources, setSkillSources] = useState<SkillSource[]>([]);
145
- const [newSkillSourceUrl, setNewSkillSourceUrl] = useState('');
146
- const [skillSourceLoading, setSkillSourceLoading] = useState(false);
147
- const [skillSourceError, setSkillSourceError] = useState<string | null>(null);
148
138
  const selectedSkillIdRef = useRef<string | null>(selectedSkillId);
149
139
  const selectedSkillItemRef = useRef<SkillCatalogItem | null>(null);
150
140
  selectedSkillIdRef.current = selectedSkillId;
@@ -221,55 +211,6 @@ export const SkillsPanel = ({
221
211
  };
222
212
  }, [fetchSkillDetail, fetchSkillsCatalog, projectPath]);
223
213
 
224
- useEffect(() => {
225
- if (!api.skills) return;
226
- void api.skills
227
- .listSources()
228
- .then((snapshot) => setSkillSources(snapshot.sources))
229
- .catch(() => undefined);
230
- }, []);
231
-
232
- const saveAndRefreshSkillSources = async (sources: SkillSource[]): Promise<void> => {
233
- if (!api.skills) return;
234
- setSkillSourceLoading(true);
235
- setSkillSourceError(null);
236
- try {
237
- const saved = await api.skills.saveSources(sources);
238
- setSkillSources(saved.sources);
239
- const refreshed = await api.skills.refreshSources();
240
- setSkillSources(refreshed.sources);
241
- await fetchSkillsCatalog(projectPath ?? undefined);
242
- setSuccessMessage('Skills 源已同步到 ~/.hermit/skills');
243
- } catch (error) {
244
- setSkillSourceError(error instanceof Error ? error.message : '同步 Skills 源失败');
245
- } finally {
246
- setSkillSourceLoading(false);
247
- }
248
- };
249
-
250
- const handleAddSkillSource = async (): Promise<void> => {
251
- const url = newSkillSourceUrl.trim();
252
- if (!url) return;
253
- const id =
254
- url
255
- .replace(/\.git$/, '')
256
- .split(/[/:]/)
257
- .filter(Boolean)
258
- .slice(-2)
259
- .join('-')
260
- .toLowerCase()
261
- .replace(/[^a-z0-9._-]+/g, '-') || `source-${Date.now().toString(36)}`;
262
- await saveAndRefreshSkillSources([
263
- ...skillSources,
264
- { id, name: id, url, enabled: true, branch: 'main' },
265
- ]);
266
- setNewSkillSourceUrl('');
267
- };
268
-
269
- const handleRemoveSkillSource = async (sourceId: string): Promise<void> => {
270
- await saveAndRefreshSkillSources(skillSources.filter((source) => source.id !== sourceId));
271
- };
272
-
273
214
  const visibleSkills = useMemo(() => {
274
215
  const q = skillsSearchQuery.trim().toLowerCase();
275
216
  const filteredByQuery = q
@@ -307,71 +248,6 @@ export const SkillsPanel = ({
307
248
 
308
249
  return (
309
250
  <div className="flex flex-col gap-4">
310
- <div className="rounded-md border border-blue-500/30 bg-blue-500/5 px-4 py-3 text-sm text-blue-300">
311
- 全局技能由 `~/.hermit/skills` 管理,并在团队启动前投影到各 runtime 的全局 skills
312
- 目录。项目技能直接安装到你选择的项目 runtime 目录。
313
- </div>
314
- <div className="bg-surface-raised/20 rounded-xl border border-border p-4">
315
- <div className="flex flex-col gap-3 lg:flex-row lg:items-end">
316
- <div className="min-w-0 flex-1 space-y-1">
317
- <h3 className="text-sm font-semibold text-text">全局 Skills 源</h3>
318
- <p className="text-xs text-text-muted">
319
- Git 源会同步到 `~/.hermit/skills`,随后由 Hermit 投影给 Claude、Cursor、Codex
320
- 等运行时。
321
- </p>
322
- <p className="text-xs text-text-muted">
323
- 打开面板只读取本地缓存;只有点击“添加并同步”或“刷新源”才会访问 GitHub 更新。
324
- </p>
325
- <input
326
- value={newSkillSourceUrl}
327
- onChange={(event) => setNewSkillSourceUrl(event.target.value)}
328
- placeholder="https://github.com/yancyuu/HermitSkills"
329
- className="mt-2 h-8 w-full rounded-md border border-border bg-surface px-2 text-xs text-text"
330
- />
331
- </div>
332
- <Button
333
- variant="outline"
334
- size="sm"
335
- disabled={skillSourceLoading || !newSkillSourceUrl.trim()}
336
- onClick={() => void handleAddSkillSource()}
337
- >
338
- {skillSourceLoading ? '同步中...' : '添加并同步'}
339
- </Button>
340
- <Button
341
- variant="outline"
342
- size="sm"
343
- disabled={skillSourceLoading || skillSources.length === 0}
344
- onClick={() => void saveAndRefreshSkillSources(skillSources)}
345
- >
346
- 刷新源
347
- </Button>
348
- </div>
349
- {skillSourceError ? <p className="mt-2 text-xs text-red-400">{skillSourceError}</p> : null}
350
- {skillSources.length > 0 ? (
351
- <div className="mt-3 flex flex-wrap gap-1.5">
352
- {skillSources.map((source) => (
353
- <span
354
- key={source.id}
355
- className="inline-flex max-w-full items-center gap-1 rounded bg-surface-raised px-1.5 py-0.5 text-[10px] text-text-muted"
356
- title={source.url}
357
- >
358
- <span className="max-w-44 truncate">
359
- {source.name}
360
- {source.lastError ? ' · 同步失败' : ''}
361
- </span>
362
- <button
363
- type="button"
364
- className="-mr-0.5 inline-flex size-4 shrink-0 items-center justify-center rounded hover:bg-red-500/10 hover:text-red-300"
365
- onClick={() => void handleRemoveSkillSource(source.id)}
366
- aria-label={`删除 Skills 源 ${source.name}`}
367
- >
368
- <Trash2 size={10} />
369
- </button>
370
- </span>
371
- ))}
372
- </div>
373
- ) : null}
374
- </div>
375
251
  <div className="bg-surface-raised/20 rounded-xl border border-border p-4">
376
252
  <div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
377
253
  <div className="min-w-0 flex-1 space-y-1 xl:max-w-2xl">
@@ -387,7 +263,6 @@ export const SkillsPanel = ({
387
263
  </p>
388
264
  <p className="max-w-2xl text-xs leading-5 text-text-muted">
389
265
  需要到处生效的习惯请使用个人技能;只对当前代码库有意义的流程请使用项目技能。
390
- 全局技能从 Hermit 源统一投影;项目技能直接写入所选项目 runtime 目录。
391
266
  </p>
392
267
  </div>
393
268
 
@@ -10,6 +10,7 @@ import { useCallback, useEffect, useState } from 'react';
10
10
  import { providersApi } from '@renderer/api/providers';
11
11
  import { ALL_AGENT_TYPES, AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
12
12
  import type { CcAgentType } from '@renderer/components/team/HarnessCards';
13
+ import { HarnessIcon } from '@renderer/components/team/HarnessSelect';
13
14
  import { cn } from '@renderer/lib/utils';
14
15
  import { OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
15
16
  import { CheckCircle2 } from 'lucide-react';
@@ -115,12 +116,7 @@ export const HarnessSection = (): React.JSX.Element => {
115
116
  background: 'var(--color-surface-raised)',
116
117
  }}
117
118
  >
118
- <div
119
- className={cn(
120
- 'h-2 w-2 shrink-0 rounded-full',
121
- covered ? 'bg-green-500' : 'bg-gray-500'
122
- )}
123
- />
119
+ <HarnessIcon type={type} className="size-5 shrink-0" />
124
120
  <div className="min-w-0 flex-1">
125
121
  <p
126
122
  className="truncate text-xs font-medium"
@@ -230,24 +230,34 @@ export function TaskBusSection(): React.JSX.Element {
230
230
  .finally(() => setLoading(false));
231
231
 
232
232
  // Restore telemetry status + Redis connection state on mount
233
- fetch('/api/telemetry/status')
233
+ fetch('/api/settings/task-bus')
234
234
  .then((r) => r.json())
235
- .then((s: TelemetryStatus) => {
236
- if (s.connected) setConnected(true);
237
- if ('sessions' in s && s.sessions > 0) setTelemetryStatus(s);
235
+ .then((busConfig: TaskBusConfig) => {
236
+ if (busConfig.enabled) {
237
+ fetch('/api/telemetry/status')
238
+ .then((r) => r.json())
239
+ .then((s: TelemetryStatus) => {
240
+ if (s.connected) setConnected(true);
241
+ if ('sessions' in s && s.sessions > 0) setTelemetryStatus(s);
242
+ })
243
+ .catch(() => {});
244
+ }
238
245
  })
239
246
  .catch(() => {});
240
247
 
241
248
  const poll = setInterval(() => {
242
- if (collectionEnabled) {
249
+ if (enabled && collectionEnabled) {
243
250
  fetch('/api/telemetry/status')
244
251
  .then((r) => r.json())
245
- .then((s: TelemetryStatus) => setTelemetryStatus(s))
252
+ .then((s: TelemetryStatus) => {
253
+ setTelemetryStatus(s);
254
+ setConnected(s.connected === true);
255
+ })
246
256
  .catch(() => {});
247
257
  }
248
258
  }, 30000);
249
259
  return () => clearInterval(poll);
250
- }, [collectionEnabled]);
260
+ }, [enabled, collectionEnabled]);
251
261
 
252
262
  const buildConfig = (
253
263
  overrides: Partial<{
@@ -402,6 +402,29 @@ const SessionRow = ({
402
402
  };
403
403
  }, [historyLimit, isExpanded, session.teamName, session.id]);
404
404
 
405
+ // While a live session stays expanded, keep its detail fresh with a silent
406
+ // refetch (no skeleton flash) so newly arrived messages show without needing
407
+ // to collapse and reopen.
408
+ useEffect(() => {
409
+ if (!isExpanded || !session.live) {
410
+ return;
411
+ }
412
+ const intervalId = window.setInterval(() => {
413
+ if (document.visibilityState !== 'visible') {
414
+ return;
415
+ }
416
+ void (async () => {
417
+ try {
418
+ const d = await api.teams.getSessionDetail(session.teamName, session.id, historyLimit);
419
+ setDetail(d);
420
+ } catch {
421
+ // silent — transient fetch failures are retried on the next tick
422
+ }
423
+ })();
424
+ }, REFRESH_INTERVAL_MS);
425
+ return () => window.clearInterval(intervalId);
426
+ }, [isExpanded, session.live, session.teamName, session.id, historyLimit]);
427
+
405
428
  const handleLoadMoreHistory = useCallback(() => {
406
429
  if (loadingDetail || loadingMoreHistory || !hasMoreHistory) {
407
430
  return;
@@ -217,13 +217,7 @@ const TeamWorkspace = ({
217
217
  <button
218
218
  key={entry.name}
219
219
  type="button"
220
- className={`flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors ${
221
- entry.isDirectory
222
- ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]'
223
- : onFileClick
224
- ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]'
225
- : 'hover:bg-[var(--color-surface-raised)]/50 cursor-default'
226
- }`}
220
+ className="flex w-full cursor-pointer items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors hover:bg-[var(--color-surface-raised)]"
227
221
  onClick={() => handleEntryClick(entry)}
228
222
  >
229
223
  {entry.isDirectory ? (
@@ -0,0 +1,71 @@
1
+ import type { CcAgentType } from '@shared/types/ccConnect';
2
+ import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from '@renderer/components/ui/select';
10
+ import { ALL_AGENT_TYPES, AGENT_TYPE_LABELS } from './HarnessCards';
11
+
12
+ interface HarnessSelectProps {
13
+ value: CcAgentType;
14
+ onChange: (value: CcAgentType) => void;
15
+ className?: string;
16
+ id?: string;
17
+ }
18
+
19
+ const HARNESS_PROVIDER_MAP: Partial<
20
+ Record<CcAgentType, 'anthropic' | 'codex' | 'gemini' | 'opencode'>
21
+ > = {
22
+ claudecode: 'anthropic',
23
+ codex: 'codex',
24
+ gemini: 'gemini',
25
+ opencode: 'opencode',
26
+ };
27
+
28
+ function HarnessIcon({ type, className }: { type: CcAgentType; className?: string }) {
29
+ const providerId = HARNESS_PROVIDER_MAP[type];
30
+ if (providerId) {
31
+ return <ProviderBrandLogo providerId={providerId} className={className} />;
32
+ }
33
+ return <span className={className}>{EMOJI_FALLBACK[type]}</span>;
34
+ }
35
+
36
+ const EMOJI_FALLBACK: Record<CcAgentType, string> = {
37
+ claudecode: '🤖',
38
+ codex: '🔬',
39
+ cursor: '💻',
40
+ gemini: '💎',
41
+ iflow: '🌊',
42
+ kimi: '🌙',
43
+ devin: '🧑‍💻',
44
+ opencode: '🔓',
45
+ qoder: '⚡',
46
+ pi: '🥧',
47
+ acp: '🔗',
48
+ tmux: '🖥️',
49
+ };
50
+
51
+ export function HarnessSelect({ value, onChange, className, id }: HarnessSelectProps) {
52
+ return (
53
+ <Select value={value} onValueChange={(v) => onChange(v as CcAgentType)}>
54
+ <SelectTrigger id={id} className={className}>
55
+ <SelectValue />
56
+ </SelectTrigger>
57
+ <SelectContent>
58
+ {ALL_AGENT_TYPES.map((type) => (
59
+ <SelectItem key={type} value={type}>
60
+ <div className="flex items-center gap-2">
61
+ <HarnessIcon type={type} className="size-4 shrink-0" />
62
+ <span>{AGENT_TYPE_LABELS[type]}</span>
63
+ </div>
64
+ </SelectItem>
65
+ ))}
66
+ </SelectContent>
67
+ </Select>
68
+ );
69
+ }
70
+
71
+ export { HarnessIcon, HARNESS_PROVIDER_MAP, EMOJI_FALLBACK };