@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
@@ -0,0 +1,163 @@
1
+ /**
2
+ * FolderBrowser — directory browser dialog for the console path input.
3
+ * Extracted from ProjectPathSelector so the SystemManagerView can reuse it.
4
+ */
5
+ import React, { useState } from 'react';
6
+
7
+ import { api } from '@renderer/api';
8
+ import { Button } from '@renderer/components/ui/button';
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from '@renderer/components/ui/dialog';
16
+ import { Input } from '@renderer/components/ui/input';
17
+ import { Check, ChevronLeft, ChevronRight, Folder, FolderOpen, Loader2 } from 'lucide-react';
18
+
19
+ interface FolderBrowserProps {
20
+ value: string;
21
+ onChange: (path: string) => void;
22
+ }
23
+
24
+ export const FolderBrowser = ({ value, onChange }: FolderBrowserProps): React.JSX.Element => {
25
+ const [open, setOpen] = useState(false);
26
+ const [currentPath, setCurrentPath] = useState(value || '');
27
+ const [dirs, setDirs] = useState<string[]>([]);
28
+ const [loading, setLoading] = useState(false);
29
+ const [error, setError] = useState<string | null>(null);
30
+
31
+ const browse = async (dirPath: string) => {
32
+ setLoading(true);
33
+ setError(null);
34
+ try {
35
+ const result = await api.config.browseFolders(dirPath || undefined);
36
+ setCurrentPath(result.path);
37
+ setDirs(result.dirs);
38
+ } catch (err) {
39
+ setError(err instanceof Error ? err.message : '无法访问目录');
40
+ setDirs([]);
41
+ } finally {
42
+ setLoading(false);
43
+ }
44
+ };
45
+
46
+ const handleOpen = () => {
47
+ setOpen(true);
48
+ void browse(value || '');
49
+ };
50
+
51
+ const handleSelect = (dir: string) => {
52
+ onChange(dir);
53
+ setOpen(false);
54
+ };
55
+
56
+ const handleConfirm = () => {
57
+ if (currentPath) {
58
+ onChange(currentPath);
59
+ }
60
+ setOpen(false);
61
+ };
62
+
63
+ const handleNavigateUp = () => {
64
+ if (currentPath) {
65
+ const parent = currentPath.split('/').slice(0, -1).join('/') || '/';
66
+ void browse(parent);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <>
72
+ <Button
73
+ size="sm"
74
+ variant="outline"
75
+ className="h-8 shrink-0 border-[var(--color-border)]"
76
+ onClick={handleOpen}
77
+ title="浏览目录"
78
+ >
79
+ <FolderOpen size={13} />
80
+ </Button>
81
+
82
+ <Dialog
83
+ open={open}
84
+ onOpenChange={(o) => {
85
+ if (!o) setOpen(false);
86
+ }}
87
+ >
88
+ <DialogContent className="max-w-md">
89
+ <DialogHeader>
90
+ <DialogTitle>选择工作目录</DialogTitle>
91
+ </DialogHeader>
92
+
93
+ {/* Path breadcrumb + manual input */}
94
+ <div className="flex items-center gap-1 text-xs text-[var(--color-text-muted)]">
95
+ <button
96
+ type="button"
97
+ className="shrink-0 hover:text-[var(--color-text)]"
98
+ onClick={handleNavigateUp}
99
+ disabled={!currentPath || currentPath === '/'}
100
+ >
101
+ <ChevronLeft size={14} />
102
+ </button>
103
+ <Input
104
+ className="h-7 flex-1 font-mono text-xs"
105
+ value={currentPath}
106
+ onChange={(e) => setCurrentPath(e.target.value)}
107
+ onKeyDown={(e) => {
108
+ if (e.key === 'Enter') void browse(currentPath);
109
+ }}
110
+ placeholder="/path/to/directory"
111
+ />
112
+ </div>
113
+
114
+ {/* Directory list */}
115
+ <div className="max-h-64 overflow-auto rounded-md border border-[var(--color-border)]">
116
+ {loading && (
117
+ <div className="flex items-center justify-center gap-2 py-6 text-xs text-[var(--color-text-muted)]">
118
+ <Loader2 className="size-4 animate-spin" />
119
+ 加载中…
120
+ </div>
121
+ )}
122
+ {error && <div className="px-3 py-4 text-xs text-red-400">{error}</div>}
123
+ {!loading && !error && dirs.length === 0 && (
124
+ <div className="px-3 py-4 text-xs text-[var(--color-text-muted)]">
125
+ 此目录下没有子目录。可手动输入路径后按 Enter。
126
+ </div>
127
+ )}
128
+ {!loading && !error && dirs.length > 0 && (
129
+ <ul className="divide-y divide-[var(--color-border)]">
130
+ {dirs.map((dir) => (
131
+ <li key={dir}>
132
+ <button
133
+ type="button"
134
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-xs hover:bg-[var(--color-surface-raised)]"
135
+ onClick={() => void browse(dir)}
136
+ onDoubleClick={() => handleSelect(dir)}
137
+ >
138
+ <Folder size={14} className="shrink-0 text-[var(--color-text-muted)]" />
139
+ <span className="truncate">{dir}</span>
140
+ <ChevronRight
141
+ size={14}
142
+ className="ml-auto shrink-0 text-[var(--color-text-muted)]"
143
+ />
144
+ </button>
145
+ </li>
146
+ ))}
147
+ </ul>
148
+ )}
149
+ </div>
150
+
151
+ <DialogFooter>
152
+ <Button variant="outline" onClick={() => setOpen(false)}>
153
+ 取消
154
+ </Button>
155
+ <Button onClick={handleConfirm} disabled={!currentPath}>
156
+ 选择此目录
157
+ </Button>
158
+ </DialogFooter>
159
+ </DialogContent>
160
+ </Dialog>
161
+ </>
162
+ );
163
+ };
@@ -0,0 +1,351 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+
3
+ import '@xterm/xterm/css/xterm.css';
4
+
5
+ import { FitAddon } from '@xterm/addon-fit';
6
+ import { WebLinksAddon } from '@xterm/addon-web-links';
7
+ import { Terminal } from '@xterm/xterm';
8
+ import { api } from '@renderer/api';
9
+ import { useStore } from '@renderer/store';
10
+ import { Button } from '@renderer/components/ui/button';
11
+ import { Input } from '@renderer/components/ui/input';
12
+ import { SYSTEM_MANAGER_DISPLAY_NAME } from '@shared/types/team';
13
+ import type {
14
+ SystemManagerConfig,
15
+ SystemManagerStatus,
16
+ WorkflowPromptSummary,
17
+ } from '@shared/types/systemManager';
18
+ import { Loader2, RefreshCw, TerminalSquare } from 'lucide-react';
19
+
20
+ import { FolderBrowser } from './FolderBrowser';
21
+
22
+ interface SystemManagerViewProps {
23
+ isPaneFocused?: boolean;
24
+ }
25
+
26
+ function formatPathForTitle(pathValue: string): string {
27
+ const home = typeof process !== 'undefined' ? process.env.HOME : undefined;
28
+ if (home && pathValue.startsWith(home)) return `~${pathValue.slice(home.length)}`;
29
+ return pathValue;
30
+ }
31
+
32
+ function joinPath(basePath: string, childPath: string): string {
33
+ const trimmedBase = basePath.replace(/[\\/]+$/, '');
34
+ return `${trimmedBase}/${childPath}`;
35
+ }
36
+
37
+ export const SystemManagerView = ({
38
+ isPaneFocused: _isPaneFocused = false,
39
+ }: SystemManagerViewProps): React.JSX.Element => {
40
+ const terminalHostRef = useRef<HTMLDivElement | null>(null);
41
+ const terminalRef = useRef<Terminal | null>(null);
42
+ const fitAddonRef = useRef<FitAddon | null>(null);
43
+ const ptyIdRef = useRef<string | null>(null);
44
+ const autoStartedRef = useRef(false);
45
+ const startClaudeRef = useRef<((workDirOverride?: string) => Promise<void>) | null>(null);
46
+
47
+ const [status, setStatus] = useState<SystemManagerStatus | null>(null);
48
+ const [config, setConfig] = useState<SystemManagerConfig | null>(null);
49
+ const [workDirInput, setWorkDirInput] = useState('');
50
+ const [workflowPrompts, setWorkflowPrompts] = useState<WorkflowPromptSummary[]>([]);
51
+ const [warnings, setWarnings] = useState<string[]>([]);
52
+ const [loading, setLoading] = useState(true);
53
+ const [starting, setStarting] = useState(false);
54
+ const [running, setRunning] = useState(false);
55
+ const [error, setError] = useState<string | null>(null);
56
+ const fetchTeams = useStore((state) => state.fetchTeams);
57
+
58
+ const writeTerminalLine = useCallback((line: string) => {
59
+ terminalRef.current?.writeln(`\x1b[90m${line}\x1b[0m`);
60
+ }, []);
61
+
62
+ const fitTerminal = useCallback(() => {
63
+ try {
64
+ fitAddonRef.current?.fit();
65
+ if (ptyIdRef.current && terminalRef.current) {
66
+ api.terminal.resize(ptyIdRef.current, terminalRef.current.cols, terminalRef.current.rows);
67
+ }
68
+ } catch {
69
+ // xterm fit can throw when the element is not measurable yet.
70
+ }
71
+ }, []);
72
+
73
+ useEffect(() => {
74
+ const host = terminalHostRef.current;
75
+ if (!host) return;
76
+
77
+ const term = new Terminal({
78
+ cursorBlink: true,
79
+ convertEol: true,
80
+ fontFamily: 'JetBrains Mono, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
81
+ fontSize: 13,
82
+ lineHeight: 1.28,
83
+ theme: {
84
+ background: 'var(--color-surface)',
85
+ foreground: 'var(--color-text)',
86
+ cursor: 'var(--color-text)',
87
+ selectionBackground: 'var(--color-border-emphasis)',
88
+ black: 'var(--color-surface-sidebar)',
89
+ red: '#f87171',
90
+ green: '#86efac',
91
+ yellow: '#fde68a',
92
+ blue: 'var(--color-accent)',
93
+ magenta: '#d8b4fe',
94
+ cyan: '#67e8f9',
95
+ white: 'var(--color-text)',
96
+ },
97
+ });
98
+ const fitAddon = new FitAddon();
99
+ term.loadAddon(fitAddon);
100
+ term.loadAddon(new WebLinksAddon());
101
+ term.open(host);
102
+ terminalRef.current = term;
103
+ fitAddonRef.current = fitAddon;
104
+ fitTerminal();
105
+ term.writeln('\x1b[90m# Hermit 控制台 · 本地 Claude Code PTY\x1b[0m');
106
+ term.writeln('\x1b[90m# 点击 Start Claude 等价于在当前目录运行 claude\x1b[0m');
107
+ term.writeln('');
108
+
109
+ const dataDispose = api.terminal.onData((_event, ptyId, data) => {
110
+ if (ptyId === ptyIdRef.current) term.write(data);
111
+ });
112
+ const exitDispose = api.terminal.onExit((_event, ptyId, exitCode) => {
113
+ if (ptyId === ptyIdRef.current) {
114
+ setRunning(false);
115
+ ptyIdRef.current = null;
116
+ term.writeln(`\r\n\x1b[90m[claude exited with code ${exitCode}]\x1b[0m`);
117
+ }
118
+ });
119
+ const inputDispose = term.onData((data) => {
120
+ if (ptyIdRef.current) api.terminal.write(ptyIdRef.current, data);
121
+ });
122
+ const resizeObserver = new ResizeObserver(() => fitTerminal());
123
+ resizeObserver.observe(host);
124
+
125
+ return () => {
126
+ dataDispose();
127
+ exitDispose();
128
+ inputDispose.dispose();
129
+ resizeObserver.disconnect();
130
+ if (ptyIdRef.current) {
131
+ void api.terminal.kill(ptyIdRef.current).catch(() => {
132
+ // Component is unmounting; there is no safe UI surface for this lifecycle error.
133
+ });
134
+ }
135
+ term.dispose();
136
+ terminalRef.current = null;
137
+ fitAddonRef.current = null;
138
+ ptyIdRef.current = null;
139
+ };
140
+ }, [fitTerminal]);
141
+
142
+ const load = useCallback(async (): Promise<SystemManagerConfig | null> => {
143
+ setLoading(true);
144
+ setError(null);
145
+ try {
146
+ const [nextStatus, nextConfig] = await Promise.all([
147
+ api.systemManager.getStatus(),
148
+ api.systemManager.getConfig(),
149
+ ]);
150
+ setStatus(nextStatus);
151
+ setConfig(nextConfig);
152
+ setWorkDirInput(nextConfig.selectedWorkDir);
153
+ const candidateFolders = [
154
+ nextConfig.workflowFolder,
155
+ joinPath(nextConfig.selectedWorkDir, 'workflows'),
156
+ ].filter((folder): folder is string => Boolean(folder));
157
+ let loadedWorkflow = false;
158
+ for (const folder of candidateFolders) {
159
+ try {
160
+ const workflowResult = await api.systemManager.listWorkflowPrompts(folder);
161
+ setConfig((current) =>
162
+ current ? { ...current, workflowFolder: workflowResult.folder } : current
163
+ );
164
+ setWorkflowPrompts(workflowResult.prompts);
165
+ setWarnings(workflowResult.warnings);
166
+ loadedWorkflow = true;
167
+ break;
168
+ } catch {
169
+ // Common commands are optional; missing folders should not interrupt opening the console.
170
+ }
171
+ }
172
+ if (!loadedWorkflow) {
173
+ setWorkflowPrompts([]);
174
+ setWarnings([]);
175
+ }
176
+ return nextConfig;
177
+ } catch (err) {
178
+ setError(err instanceof Error ? err.message : String(err));
179
+ return null;
180
+ } finally {
181
+ setLoading(false);
182
+ }
183
+ }, []);
184
+
185
+ const stopClaude = useCallback(async (): Promise<boolean> => {
186
+ const ptyId = ptyIdRef.current;
187
+ if (!ptyId) {
188
+ setRunning(false);
189
+ return true;
190
+ }
191
+
192
+ try {
193
+ await api.terminal.kill(ptyId);
194
+ if (ptyIdRef.current === ptyId) {
195
+ ptyIdRef.current = null;
196
+ setRunning(false);
197
+ writeTerminalLine('[stopped]');
198
+ }
199
+ return true;
200
+ } catch (err) {
201
+ const message = err instanceof Error ? err.message : String(err);
202
+ setError(message);
203
+ writeTerminalLine(`[failed to stop claude] ${message}`);
204
+ return false;
205
+ }
206
+ }, [writeTerminalLine]);
207
+
208
+ const startClaude = useCallback(async (workDirOverride?: string) => {
209
+ if (starting) return;
210
+ setStarting(true);
211
+ setError(null);
212
+ try {
213
+ const stopped = await stopClaude();
214
+ if (!stopped) return;
215
+ const nextConfig = await api.systemManager.updateConfig({
216
+ selectedWorkDir: workDirOverride ?? workDirInput,
217
+ });
218
+ setConfig(nextConfig);
219
+ void fetchTeams();
220
+ terminalRef.current?.clear();
221
+ writeTerminalLine(`# cd ${nextConfig.selectedWorkDir}`);
222
+ writeTerminalLine('$ claude');
223
+ const ptyId = await api.terminal.spawn({
224
+ cwd: nextConfig.selectedWorkDir,
225
+ cols: terminalRef.current?.cols ?? 120,
226
+ rows: terminalRef.current?.rows ?? 34,
227
+ });
228
+ ptyIdRef.current = ptyId;
229
+ setRunning(true);
230
+ terminalRef.current?.focus();
231
+ } catch (err) {
232
+ const message = err instanceof Error ? err.message : String(err);
233
+ setError(message);
234
+ writeTerminalLine(`[failed to start claude] ${message}`);
235
+ } finally {
236
+ setStarting(false);
237
+ }
238
+ }, [fetchTeams, starting, stopClaude, workDirInput, writeTerminalLine]);
239
+
240
+ useEffect(() => {
241
+ startClaudeRef.current = startClaude;
242
+ }, [startClaude]);
243
+
244
+ useEffect(() => {
245
+ void load().then((nextConfig) => {
246
+ if (nextConfig && !autoStartedRef.current) {
247
+ autoStartedRef.current = true;
248
+ void startClaudeRef.current?.(nextConfig.selectedWorkDir);
249
+ }
250
+ });
251
+ }, [load]);
252
+
253
+ const refreshConsole = useCallback(async () => {
254
+ await load();
255
+ await startClaude(workDirInput || undefined);
256
+ }, [load, startClaude, workDirInput]);
257
+
258
+ const runWorkflowPrompt = useCallback(
259
+ async (prompt: WorkflowPromptSummary) => {
260
+ if (!config?.workflowFolder) return;
261
+ if (!ptyIdRef.current) {
262
+ await startClaude();
263
+ }
264
+ const ptyId = ptyIdRef.current;
265
+ if (!ptyId) return;
266
+ const result = await api.systemManager.readWorkflowPrompt(config.workflowFolder, prompt.id);
267
+ writeTerminalLine(`$ # workflow: ${prompt.label}`);
268
+ api.terminal.write(ptyId, `${result.content}\r`);
269
+ },
270
+ [config?.workflowFolder, startClaude, writeTerminalLine]
271
+ );
272
+
273
+ const titlePath = useMemo(
274
+ () =>
275
+ formatPathForTitle(config?.selectedWorkDir ?? (workDirInput || status?.defaultWorkDir || '')),
276
+ [config?.selectedWorkDir, status?.defaultWorkDir, workDirInput]
277
+ );
278
+
279
+ return (
280
+ <div className="flex size-full flex-col bg-[var(--color-surface)] p-4 text-[var(--color-text)]">
281
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] shadow-2xl shadow-black/20">
282
+ <div className="flex min-h-12 items-center gap-3 border-b border-[var(--color-border)] bg-[var(--color-surface-raised)] px-4 py-2">
283
+ <div className="flex shrink-0 items-center gap-2">
284
+ <span className="size-3 rounded-full bg-[#ff5f57]" />
285
+ <span className="size-3 rounded-full bg-[#febc2e]" />
286
+ <span className="size-3 rounded-full bg-[#28c840]" />
287
+ </div>
288
+ <div className="flex shrink-0 items-center gap-2 font-mono text-xs text-[var(--color-text-secondary)]">
289
+ <TerminalSquare size={14} className="text-[var(--color-text-muted)]" />
290
+ {SYSTEM_MANAGER_DISPLAY_NAME}
291
+ </div>
292
+ <Input
293
+ value={workDirInput}
294
+ onChange={(event) => setWorkDirInput(event.target.value)}
295
+ onKeyDown={(e) => { if (e.key === 'Enter') void refreshConsole(); }}
296
+ className="h-8 min-w-[220px] flex-1 border-[var(--color-border)] bg-[var(--color-surface)] font-mono text-xs text-[var(--color-text)]"
297
+ placeholder={titlePath || '工作目录'}
298
+ />
299
+ <FolderBrowser value={workDirInput} onChange={setWorkDirInput} />
300
+ <div className="shrink-0 text-[11px] text-[var(--color-text-muted)]">
301
+ {running ? 'claude running' : (status?.localStatus ?? 'starting')}
302
+ </div>
303
+ <Button
304
+ size="sm"
305
+ variant="outline"
306
+ className="h-8 shrink-0 border-[var(--color-border)]"
307
+ disabled={starting}
308
+ onClick={() => void refreshConsole()}
309
+ >
310
+ {starting ? <Loader2 size={13} className="animate-spin" /> : <RefreshCw size={13} />}
311
+ 刷新
312
+ </Button>
313
+ </div>
314
+
315
+ <div className="min-h-0 flex-1 bg-[var(--color-surface)] p-2">
316
+ <div
317
+ ref={terminalHostRef}
318
+ className="size-full overflow-hidden rounded-lg bg-[var(--color-surface)]"
319
+ />
320
+ </div>
321
+
322
+ {(workflowPrompts.length || warnings.length || error || loading) && (
323
+ <div className="border-t border-[var(--color-border)] bg-[var(--color-surface)] px-4 py-3">
324
+ {workflowPrompts.length ? (
325
+ <div className="flex flex-wrap gap-2">
326
+ {workflowPrompts.map((prompt) => (
327
+ <button
328
+ key={prompt.id}
329
+ type="button"
330
+ className="rounded-full border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-3 py-1 font-mono text-[11px] text-[var(--color-text-secondary)] hover:border-[var(--color-border-emphasis)] hover:text-[var(--color-text)]"
331
+ disabled={starting}
332
+ onClick={() => void runWorkflowPrompt(prompt)}
333
+ >
334
+ {prompt.label}
335
+ </button>
336
+ ))}
337
+ </div>
338
+ ) : null}
339
+ {warnings.length ? (
340
+ <div className="mt-2 text-xs text-amber-300">{warnings.join(';')}</div>
341
+ ) : null}
342
+ {error ? <div className="mt-2 text-xs text-red-300">{error}</div> : null}
343
+ {loading ? (
344
+ <div className="mt-2 text-xs text-[var(--color-text-muted)]">加载控制台配置中...</div>
345
+ ) : null}
346
+ </div>
347
+ )}
348
+ </div>
349
+ </div>
350
+ );
351
+ };