@yancyyu/openhermit 1.6.38 → 1.6.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-ZEDfZyHb.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-CIhniz70.js → _basePickBy-B7kSYPxr.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-cKAW4Q8I.js → _baseUniq-CnjxqwAk.js} +1 -1
  5. package/dist-renderer/assets/{arc-YmNsoDXW.js → arc-CLeZuINP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DHEls2sX.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-Bpwf1Sbg.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-B0IaQ4w5.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
  9. package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-DLk-hcFc.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-1XRmX_Zm.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-1waH1DAD.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-BqpZBtrN.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-Bly7vVym.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-Ci2QWBAs.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-YCqFW7d-.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-B0xGXInl.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
  20. package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DxcFNQKT.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DPo_RfZY.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-U3hQsFe4.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-OrwrAy0V.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-CXATPWVw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-B0e8AfMF.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-CXfzA4jJ.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CMr08qVl.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-vYFHpPmy.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
  30. package/dist-renderer/assets/{graph-DOe5j8dH.js → graph-CsoEwUhS.js} +1 -1
  31. package/dist-renderer/assets/{index-BySQS7AB.js → index-BWPWmJNo.js} +1 -1
  32. package/dist-renderer/assets/{index-V7dAKPqd.js → index-Bu2R-Se7.js} +587 -705
  33. package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
  34. package/dist-renderer/assets/{index-CzWxVCRL.js → index-D-3KgskL.js} +1 -1
  35. package/dist-renderer/assets/{index-VJ-MM9xa.js → index-DGEBzLNT.js} +1 -1
  36. package/dist-renderer/assets/{index-B2Dy7M2G.js → index-NhHNs2Oo.js} +1 -1
  37. package/dist-renderer/assets/{index-C_okzZXP.js → index-h17WuEyf.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D_WubR0B.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-w9ca-1TI.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-Jg9p6_pN.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
  41. package/dist-renderer/assets/{layout-B-z3y17c.js → layout-C3XWrpwo.js} +1 -1
  42. package/dist-renderer/assets/{linear-D-RTX5UW.js → linear-OEEcn8KN.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-CDQmHOYP.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-D_odsQL7.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BRsmYWSA.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-ChNE_BOV.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-C8FtpwKc.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-DmLCzNcc.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
  49. package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
  50. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-WJBm4bhu.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
  51. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
  52. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BXs_hOJs.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
  53. package/dist-renderer/assets/{treemap-GDKQZRPO-o04MA0G9.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
  54. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-Czj69XRd.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
  55. package/dist-renderer/index.html +20 -53
  56. package/package.json +25 -18
  57. package/src/main/ipc/extensions.ts +2 -1
  58. package/src/main/server.ts +873 -221
  59. package/src/main/services/extensions/ExtensionFacadeService.ts +2 -5
  60. package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
  61. package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
  62. package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
  63. package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
  64. package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
  65. package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
  66. package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
  67. package/src/main/services/teams-mvp/TaskDispatchService.ts +5 -6
  68. package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
  69. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
  70. package/src/main/utils/teamProjectResolution.ts +15 -0
  71. package/src/renderer/App.tsx +8 -4
  72. package/src/renderer/api/httpClient.ts +68 -18
  73. package/src/renderer/api/providers.ts +23 -2
  74. package/src/renderer/assets/participant-avatars/01.svg +3 -0
  75. package/src/renderer/assets/participant-avatars/02.svg +3 -0
  76. package/src/renderer/assets/participant-avatars/03.svg +3 -0
  77. package/src/renderer/assets/participant-avatars/04.svg +3 -0
  78. package/src/renderer/assets/participant-avatars/05.svg +3 -0
  79. package/src/renderer/assets/participant-avatars/06.svg +3 -0
  80. package/src/renderer/assets/participant-avatars/07.svg +3 -0
  81. package/src/renderer/assets/participant-avatars/08.svg +3 -0
  82. package/src/renderer/assets/participant-avatars/09.svg +3 -0
  83. package/src/renderer/assets/participant-avatars/10.svg +3 -0
  84. package/src/renderer/assets/participant-avatars/11.svg +3 -0
  85. package/src/renderer/assets/participant-avatars/12.svg +3 -0
  86. package/src/renderer/assets/participant-avatars/13.svg +3 -0
  87. package/src/renderer/components/common/TerminalPane.tsx +213 -0
  88. package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
  89. package/src/renderer/components/extensions/ExtensionStoreView.tsx +6 -125
  90. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  91. package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
  92. package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
  93. package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
  94. package/src/renderer/components/extensions/plugins/PluginCard.tsx +6 -6
  95. package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +34 -21
  96. package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
  97. package/src/renderer/components/layout/PaneContent.tsx +8 -1
  98. package/src/renderer/components/layout/Sidebar.tsx +11 -54
  99. package/src/renderer/components/layout/SortableTab.tsx +20 -31
  100. package/src/renderer/components/layout/TabBar.tsx +1 -1
  101. package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
  102. package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
  103. package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
  104. package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
  105. package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
  106. package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
  107. package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
  108. package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
  109. package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
  110. package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
  111. package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
  112. package/src/renderer/components/schedules/calendar/index.ts +2 -0
  113. package/src/renderer/components/schedules/calendar/types.ts +44 -0
  114. package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
  115. package/src/renderer/components/settings/SettingsView.tsx +30 -35
  116. package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
  117. package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
  118. package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
  119. package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
  120. package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
  121. package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
  122. package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
  123. package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
  124. package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
  125. package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
  126. package/src/renderer/components/settings/sections/index.ts +0 -1
  127. package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
  128. package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
  129. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
  130. package/src/renderer/components/splash/splashScene.ts +121 -929
  131. package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
  132. package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
  133. package/src/renderer/components/tasks/TasksView.tsx +112 -134
  134. package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
  135. package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
  136. package/src/renderer/components/team/TeamDetailView.tsx +319 -123
  137. package/src/renderer/components/team/TeamListView.tsx +108 -123
  138. package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
  139. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
  140. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
  141. package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
  142. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
  143. package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
  144. package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
  145. package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
  146. package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
  147. package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
  148. package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
  149. package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
  150. package/src/renderer/components/team/members/MemberCard.tsx +14 -47
  151. package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
  152. package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
  153. package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
  154. package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
  155. package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
  156. package/src/renderer/components/team/tools/AddMcpInline.tsx +27 -17
  157. package/src/renderer/components/team/tools/McpChip.tsx +6 -3
  158. package/src/renderer/components/team/tools/SkillChip.tsx +2 -2
  159. package/src/renderer/components/team/tools/ToolsSection.tsx +418 -70
  160. package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
  161. package/src/renderer/index.css +39 -22
  162. package/src/renderer/index.html +17 -50
  163. package/src/renderer/store/index.ts +2 -1
  164. package/src/renderer/store/slices/scheduleSlice.ts +1 -1
  165. package/src/renderer/store/slices/teamSlice.ts +45 -168
  166. package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
  167. package/src/renderer/utils/memberHelpers.ts +5 -17
  168. package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
  169. package/src/renderer/utils/providerSlashCommands.ts +0 -5
  170. package/src/renderer/utils/scheduleFormatters.ts +3 -1
  171. package/src/renderer/utils/teamMessageFiltering.ts +14 -1
  172. package/src/renderer/utils/teamModelAvailability.ts +18 -2
  173. package/src/shared/types/api.ts +121 -2
  174. package/src/shared/types/ccConnect.ts +2 -0
  175. package/src/shared/types/index.ts +3 -0
  176. package/src/shared/types/systemManager.ts +49 -0
  177. package/src/shared/types/team.ts +29 -0
  178. package/src/shared/types/terminal.ts +4 -2
  179. package/src/shared/utils/extensionNormalizers.ts +15 -8
  180. package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
  181. package/dist-renderer/assets/ProjectEditorOverlay-lJZi-9Hp.js +0 -52
  182. package/dist-renderer/assets/channel-yIlSKy0e.js +0 -1
  183. package/dist-renderer/assets/classDiagram-2ON5EDUG-24fHez0s.js +0 -1
  184. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-24fHez0s.js +0 -1
  185. package/dist-renderer/assets/clone-BTNuUva-.js +0 -1
  186. package/dist-renderer/assets/index-Bi6nrZ4z.css +0 -1
  187. package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
  188. package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-_m6iPPUR.js +0 -1
@@ -0,0 +1,233 @@
1
+ import { spawn as spawnChild } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { EventEmitter } from 'node:events';
4
+ import { stat } from 'node:fs/promises';
5
+
6
+ import type { PtySpawnOptions } from '@shared/types/terminal';
7
+ import * as pty from 'node-pty';
8
+
9
+ const PYTHON_PTY_BRIDGE = String.raw`
10
+ import errno
11
+ import os
12
+ import pty
13
+ import select
14
+ import signal
15
+ import sys
16
+
17
+ cmd = sys.argv[1:]
18
+ child_pid = None
19
+
20
+
21
+ def terminate(signum, _frame):
22
+ if child_pid:
23
+ try:
24
+ os.kill(child_pid, signum)
25
+ except OSError:
26
+ pass
27
+ sys.exit(128 + signum)
28
+
29
+
30
+ signal.signal(signal.SIGTERM, terminate)
31
+ signal.signal(signal.SIGINT, terminate)
32
+
33
+ child_pid, fd = pty.fork()
34
+ if child_pid == 0:
35
+ os.execvpe(cmd[0], cmd, os.environ)
36
+
37
+ while True:
38
+ try:
39
+ readable, _, _ = select.select([fd, sys.stdin.fileno()], [], [])
40
+ except OSError:
41
+ break
42
+
43
+ if fd in readable:
44
+ try:
45
+ data = os.read(fd, 4096)
46
+ except OSError:
47
+ break
48
+ if not data:
49
+ break
50
+ os.write(sys.stdout.fileno(), data)
51
+
52
+ if sys.stdin.fileno() in readable:
53
+ try:
54
+ data = os.read(sys.stdin.fileno(), 4096)
55
+ except OSError as err:
56
+ if err.errno == errno.EIO:
57
+ data = b''
58
+ else:
59
+ raise
60
+ if data:
61
+ os.write(fd, data)
62
+
63
+ try:
64
+ _, status = os.waitpid(child_pid, 0)
65
+ except ChildProcessError:
66
+ sys.exit(0)
67
+ sys.exit(os.waitstatus_to_exitcode(status))
68
+ `;
69
+
70
+ interface ManagedProcess {
71
+ id: string;
72
+ pid: number;
73
+ command: string;
74
+ args: string[];
75
+ cwd: string;
76
+ createdAt: string;
77
+ write(data: string): void;
78
+ resize(cols: number, rows: number): void;
79
+ kill(signal?: NodeJS.Signals): void;
80
+ }
81
+
82
+ const TERMINAL_KILL_TIMEOUT_MS = 1_500;
83
+ const TERMINAL_FORCE_KILL_TIMEOUT_MS = 1_500;
84
+
85
+ export type TerminalDataEvent = { ptyId: string; data: string };
86
+ export type TerminalExitEvent = { ptyId: string; exitCode: number };
87
+
88
+ export class SystemManagerPtyService extends EventEmitter {
89
+ private readonly sessions = new Map<string, ManagedProcess>();
90
+
91
+ async spawn(options: PtySpawnOptions = {}): Promise<string> {
92
+ const command = options.command || 'claude';
93
+ const args = options.args ?? [];
94
+ const cwd = options.cwd || process.cwd();
95
+ const cwdStat = await stat(cwd);
96
+ if (!cwdStat.isDirectory()) {
97
+ throw new Error(`cwd 不是有效目录: ${cwd}`);
98
+ }
99
+
100
+ const id = `pty-${randomUUID()}`;
101
+ const env = {
102
+ ...process.env,
103
+ TERM: 'xterm-256color',
104
+ COLORTERM: 'truecolor',
105
+ ...(options.env ?? {}),
106
+ } as Record<string, string>;
107
+
108
+ try {
109
+ const proc = pty.spawn(command, args, {
110
+ name: 'xterm-256color',
111
+ cols: options.cols ?? 120,
112
+ rows: options.rows ?? 34,
113
+ cwd,
114
+ env,
115
+ });
116
+
117
+ this.sessions.set(id, {
118
+ id,
119
+ pid: proc.pid,
120
+ command,
121
+ args,
122
+ cwd,
123
+ createdAt: new Date().toISOString(),
124
+ write: (data) => proc.write(data),
125
+ resize: (cols, rows) => proc.resize(Math.max(20, cols), Math.max(5, rows)),
126
+ kill: (signal) => proc.kill(signal),
127
+ });
128
+
129
+ proc.onData((data) => {
130
+ this.emit('data', { ptyId: id, data } satisfies TerminalDataEvent);
131
+ });
132
+ proc.onExit(({ exitCode }) => {
133
+ if (!this.sessions.delete(id)) return;
134
+ this.emit('exit', { ptyId: id, exitCode } satisfies TerminalExitEvent);
135
+ });
136
+ } catch (err) {
137
+ const child = spawnChild('python3', ['-u', '-c', PYTHON_PTY_BRIDGE, command, ...args], {
138
+ cwd,
139
+ env,
140
+ stdio: 'pipe',
141
+ });
142
+ this.sessions.set(id, {
143
+ id,
144
+ pid: child.pid ?? -1,
145
+ command,
146
+ args,
147
+ cwd,
148
+ createdAt: new Date().toISOString(),
149
+ write: (data) => child.stdin.write(data),
150
+ resize: () => {},
151
+ kill: (signal) => child.kill(signal),
152
+ });
153
+ this.emit('data', {
154
+ ptyId: id,
155
+ data: `[Hermit] node-pty unavailable (${err instanceof Error ? err.message : String(err)}); using python PTY fallback.\r\n`,
156
+ } satisfies TerminalDataEvent);
157
+ child.stdout.on('data', (data) => {
158
+ this.emit('data', { ptyId: id, data: data.toString() } satisfies TerminalDataEvent);
159
+ });
160
+ child.stderr.on('data', (data) => {
161
+ this.emit('data', { ptyId: id, data: data.toString() } satisfies TerminalDataEvent);
162
+ });
163
+ child.on('error', (error) => {
164
+ if (!this.sessions.delete(id)) return;
165
+ this.emit('data', {
166
+ ptyId: id,
167
+ data: `[Hermit] failed to start process: ${error.message}\r\n`,
168
+ } satisfies TerminalDataEvent);
169
+ this.emit('exit', { ptyId: id, exitCode: 1 } satisfies TerminalExitEvent);
170
+ });
171
+ child.on('exit', (exitCode) => {
172
+ if (!this.sessions.delete(id)) return;
173
+ this.emit('exit', { ptyId: id, exitCode: exitCode ?? 0 } satisfies TerminalExitEvent);
174
+ });
175
+ }
176
+
177
+ return id;
178
+ }
179
+
180
+ write(ptyId: string, data: string): void {
181
+ const session = this.sessions.get(ptyId);
182
+ if (!session) throw new Error(`PTY 不存在: ${ptyId}`);
183
+ session.write(data);
184
+ }
185
+
186
+ resize(ptyId: string, cols: number, rows: number): void {
187
+ const session = this.sessions.get(ptyId);
188
+ if (!session) return;
189
+ session.resize(cols, rows);
190
+ }
191
+
192
+ async kill(ptyId: string): Promise<void> {
193
+ const session = this.sessions.get(ptyId);
194
+ if (!session) return;
195
+
196
+ await new Promise<void>((resolve) => {
197
+ let settled = false;
198
+ let forceTimer: ReturnType<typeof setTimeout> | null = null;
199
+ const finish = (): void => {
200
+ if (settled) return;
201
+ settled = true;
202
+ if (forceTimer) clearTimeout(forceTimer);
203
+ this.off('exit', onExit);
204
+ resolve();
205
+ };
206
+ const onExit = (event: TerminalExitEvent): void => {
207
+ if (event.ptyId === ptyId) finish();
208
+ };
209
+
210
+ this.on('exit', onExit);
211
+ session.kill();
212
+ forceTimer = setTimeout(() => {
213
+ if (!this.sessions.has(ptyId)) {
214
+ finish();
215
+ return;
216
+ }
217
+ session.kill('SIGKILL');
218
+ }, TERMINAL_KILL_TIMEOUT_MS);
219
+ setTimeout(() => {
220
+ if (this.sessions.delete(ptyId)) {
221
+ this.emit('exit', { ptyId, exitCode: 0 } satisfies TerminalExitEvent);
222
+ }
223
+ finish();
224
+ }, TERMINAL_KILL_TIMEOUT_MS + TERMINAL_FORCE_KILL_TIMEOUT_MS);
225
+ });
226
+ }
227
+
228
+ killAll(): void {
229
+ for (const id of [...this.sessions.keys()]) {
230
+ void this.kill(id);
231
+ }
232
+ }
233
+ }
@@ -0,0 +1,75 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile, readdir, stat } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ import type {
7
+ WorkflowPromptContentResponse,
8
+ WorkflowPromptListResponse,
9
+ WorkflowPromptSummary,
10
+ } from '@shared/types/systemManager';
11
+
12
+ const SUPPORTED_EXTENSIONS = new Set(['.md', '.txt', '.prompt', '.workflow']);
13
+ const MAX_PROMPT_BYTES = 256 * 1024;
14
+
15
+ function expandHome(input: string): string {
16
+ const normalized = input.trim().replace(/^~/, '~');
17
+ if (normalized === '~') return os.homedir();
18
+ if (normalized.startsWith('~/')) return path.join(os.homedir(), normalized.slice(2));
19
+ return normalized;
20
+ }
21
+
22
+ function promptId(filePath: string): string {
23
+ return createHash('sha256').update(filePath).digest('hex').slice(0, 16);
24
+ }
25
+
26
+ function labelFromFilename(filename: string): string {
27
+ return path.basename(filename, path.extname(filename)).replace(/[-_]+/g, ' ').trim() || filename;
28
+ }
29
+
30
+ export class WorkflowPromptService {
31
+ async list(folderInput: string): Promise<WorkflowPromptListResponse> {
32
+ const folder = path.resolve(expandHome(folderInput));
33
+ const folderStat = await stat(folder);
34
+ if (!folderStat.isDirectory()) {
35
+ throw new Error(`workflow folder 不是有效目录: ${folder}`);
36
+ }
37
+
38
+ const warnings: string[] = [];
39
+ const prompts: WorkflowPromptSummary[] = [];
40
+ const entries = await readdir(folder, { withFileTypes: true });
41
+
42
+ for (const entry of entries) {
43
+ if (!entry.isFile() || entry.name.startsWith('.')) continue;
44
+ const ext = path.extname(entry.name).toLowerCase();
45
+ if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
46
+ const filePath = path.join(folder, entry.name);
47
+ const fileStat = await stat(filePath);
48
+ if (fileStat.size > MAX_PROMPT_BYTES) {
49
+ warnings.push(`${entry.name} 超过 256 KiB,已跳过`);
50
+ continue;
51
+ }
52
+ prompts.push({
53
+ id: promptId(filePath),
54
+ label: labelFromFilename(entry.name),
55
+ filename: entry.name,
56
+ path: filePath,
57
+ sizeBytes: fileStat.size,
58
+ updatedAt: fileStat.mtime.toISOString(),
59
+ });
60
+ }
61
+
62
+ prompts.sort((a, b) => a.filename.localeCompare(b.filename));
63
+ return { folder, prompts, warnings };
64
+ }
65
+
66
+ async read(folderInput: string, id: string): Promise<WorkflowPromptContentResponse> {
67
+ const list = await this.list(folderInput);
68
+ const prompt = list.prompts.find((item) => item.id === id || item.filename === id);
69
+ if (!prompt) {
70
+ throw new Error(`未找到 workflow: ${id}`);
71
+ }
72
+ const content = await readFile(prompt.path, 'utf-8');
73
+ return { prompt, content };
74
+ }
75
+ }
@@ -776,11 +776,11 @@ export class TaskDispatchService {
776
776
  redis = new ioredis.default(opts);
777
777
  redisSub = new ioredis.default(opts);
778
778
 
779
- redis.on('error', (err: Error) => {
780
- console.warn('[TaskDispatchService] Redis error:', err.message);
779
+ redis.on('error', () => {
780
+ /* handled by task bus connection status */
781
781
  });
782
- redisSub.on('error', (err: Error) => {
783
- console.warn('[TaskDispatchService] Redis subscriber error:', err.message);
782
+ redisSub.on('error', () => {
783
+ /* handled by task bus connection status */
784
784
  });
785
785
 
786
786
  await redis.connect();
@@ -796,8 +796,7 @@ export class TaskDispatchService {
796
796
  this.startConsumers();
797
797
  this.startResponseConsumers();
798
798
  this.subscribeStatus();
799
- } catch (err) {
800
- console.warn('[TaskDispatchService] Redis connect failed:', err);
799
+ } catch {
801
800
  this.collabBoard.setRedis(null);
802
801
  redis?.disconnect();
803
802
  redisSub?.disconnect();
@@ -45,6 +45,35 @@ function removeManagedTeamInstructions(content: string): string {
45
45
  return next.replace(/\n{3,}/g, '\n\n').trimEnd();
46
46
  }
47
47
 
48
+ async function injectHermitTasksMcpConfig(workDir: string): Promise<void> {
49
+ const settingsPath = path.join(workDir, '.claude', 'settings.json');
50
+ let settings: Record<string, unknown> = {};
51
+ try {
52
+ const raw = await fs.promises.readFile(settingsPath, 'utf8');
53
+ settings = JSON.parse(raw) as Record<string, unknown>;
54
+ } catch (err) {
55
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
56
+ throw err;
57
+ }
58
+ }
59
+
60
+ const existingMcpServers =
61
+ settings.mcpServers && typeof settings.mcpServers === 'object'
62
+ ? (settings.mcpServers as Record<string, unknown>)
63
+ : {};
64
+ const port = process.env.PORT ?? '5680';
65
+ settings.mcpServers = {
66
+ ...existingMcpServers,
67
+ 'hermit-tasks': {
68
+ type: 'sse',
69
+ url: `http://127.0.0.1:${port}/mcp`,
70
+ },
71
+ };
72
+
73
+ await fs.promises.mkdir(path.dirname(settingsPath), { recursive: true });
74
+ await fs.promises.writeFile(settingsPath, JSON.stringify(settings, null, 2));
75
+ }
76
+
48
77
  export class TeamProvisioningService {
49
78
  private readonly workspace: TeamWorkspaceService;
50
79
 
@@ -68,12 +97,16 @@ export class TeamProvisioningService {
68
97
  * 4. 触发 cc-connect restart 激活 project
69
98
  */
70
99
  async createTeam(
71
- input: CreateTeamInput & { createCcProject?: boolean }
100
+ input: CreateTeamInput & { createCcProject?: boolean; injectInstructions?: boolean }
72
101
  ): Promise<{ slug: string; manifest: TeamManifest }> {
73
- const { createCcProject = true, ...workspaceInput } = input;
102
+ const { createCcProject = true, injectInstructions = true, ...workspaceInput } = input;
74
103
 
75
104
  const { slug, manifest } = await this.workspace.createTeam(workspaceInput);
76
105
 
106
+ if (injectInstructions && manifest.harness === 'claudecode') {
107
+ await injectHermitTasksMcpConfig(manifest.workDir);
108
+ }
109
+
77
110
  if (createCcProject) {
78
111
  try {
79
112
  const platformType = manifest.platform ?? 'bridge';
@@ -118,6 +151,7 @@ export class TeamProvisioningService {
118
151
  Pick<
119
152
  TeamManifest,
120
153
  | 'displayName'
154
+ | 'bindProject'
121
155
  | 'color'
122
156
  | 'description'
123
157
  | 'collaboration'
@@ -130,7 +164,10 @@ export class TeamProvisioningService {
130
164
  | 'injectSender'
131
165
  | 'managedSources'
132
166
  | 'disabledCommands'
167
+ | 'platform'
168
+ | 'platformOptions'
133
169
  | 'platformAllowFrom'
170
+ | 'platformAllowChat'
134
171
  >
135
172
  >
136
173
  ): Promise<TeamManifest> {
@@ -45,6 +45,8 @@ export interface TeamManifest {
45
45
  managedSources?: string;
46
46
  disabledCommands?: string[];
47
47
  platformAllowFrom?: Record<string, string>;
48
+ /** 群聊允许的 chat ID(按平台) */
49
+ platformAllowChat?: Record<string, string>;
48
50
  pendingDelete?: boolean;
49
51
  restartRequired?: boolean;
50
52
  /**
@@ -159,9 +161,9 @@ async function readJson<T>(p: string, fallback: T): Promise<T> {
159
161
  try {
160
162
  const raw = await fs.promises.readFile(p, 'utf8');
161
163
  return JSON.parse(raw) as T;
162
- } catch (err) {
163
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') return fallback;
164
- throw err;
164
+ } catch {
165
+ // File missing or corrupted JSON return fallback silently
166
+ return fallback;
165
167
  }
166
168
  }
167
169
 
@@ -185,6 +187,17 @@ export class TeamWorkspaceService {
185
187
  return match?.slug ?? teamSlug;
186
188
  }
187
189
 
190
+ private async createUniqueStorageSlug(displayName: string): Promise<string> {
191
+ const baseSlug = toSlug(displayName);
192
+ let slug = baseSlug;
193
+ let suffix = 2;
194
+ while (await pathExists(path.join(teamRoot(slug), 'team.json'))) {
195
+ slug = `${baseSlug}-${suffix}`;
196
+ suffix += 1;
197
+ }
198
+ return slug;
199
+ }
200
+
188
201
  async createTeam(
189
202
  input: CreateTeamInput
190
203
  ): Promise<{ slug: string; root: string; manifest: TeamManifest }> {
@@ -192,7 +205,7 @@ export class TeamWorkspaceService {
192
205
  if (!input.bindProject) throw new Error('bindProject is required');
193
206
  if (!input.workDir) throw new Error('workDir is required');
194
207
 
195
- const slug = toSlug(input.bindProject);
208
+ const slug = await this.createUniqueStorageSlug(input.displayName);
196
209
  const root = teamRoot(slug);
197
210
 
198
211
  await fs.promises.mkdir(root, { recursive: true });
@@ -263,6 +276,7 @@ export class TeamWorkspaceService {
263
276
  const out: TeamManifest[] = [];
264
277
  for (const e of entries) {
265
278
  if (!e.isDirectory()) continue;
279
+ if (e.name.startsWith('.')) continue;
266
280
  try {
267
281
  out.push(await this.readTeamManifest(e.name));
268
282
  } catch {
@@ -278,6 +292,7 @@ export class TeamWorkspaceService {
278
292
  Pick<
279
293
  TeamManifest,
280
294
  | 'displayName'
295
+ | 'bindProject'
281
296
  | 'color'
282
297
  | 'description'
283
298
  | 'collaboration'
@@ -290,7 +305,10 @@ export class TeamWorkspaceService {
290
305
  | 'injectSender'
291
306
  | 'managedSources'
292
307
  | 'disabledCommands'
308
+ | 'platform'
309
+ | 'platformOptions'
293
310
  | 'platformAllowFrom'
311
+ | 'platformAllowChat'
294
312
  | 'pendingDelete'
295
313
  | 'restartRequired'
296
314
  >
@@ -0,0 +1,15 @@
1
+ import type { TeamManifest } from '@main/services/teams-mvp/TeamWorkspaceService';
2
+
3
+ export async function resolveCcProjectName(
4
+ routeTeamName: string,
5
+ readTeamManifest: (teamName: string) => Promise<TeamManifest>
6
+ ): Promise<string> {
7
+ try {
8
+ const manifest = await readTeamManifest(routeTeamName);
9
+ const bindProject = manifest.bindProject?.trim();
10
+ if (bindProject) return bindProject;
11
+ } catch {
12
+ // routeTeamName may already be a cc-connect project name.
13
+ }
14
+ return routeTeamName;
15
+ }
@@ -41,6 +41,7 @@ function buildPathForTab(activeTab: Tab | null): string {
41
41
  }
42
42
  switch (activeTab.type) {
43
43
  case 'team':
44
+ if (activeTab.teamName === 'system-manager') return '/system-manager';
44
45
  return activeTab.teamName
45
46
  ? `/team/${encodeURIComponent(activeTab.teamName)}`
46
47
  : DEFAULT_APP_PATH;
@@ -142,6 +143,9 @@ function useTabPathPersistence() {
142
143
  case 'teams':
143
144
  state.openTeamsTab();
144
145
  break;
146
+ case 'system-manager':
147
+ void state.openSystemManager();
148
+ break;
145
149
  case 'settings':
146
150
  state.openSettingsTab();
147
151
  break;
@@ -208,11 +212,11 @@ declare global {
208
212
  }
209
213
  }
210
214
 
211
- const SPLASH_MIN_DURATION_MS = 1600;
212
- const SPLASH_ENHANCED_HOLD_MS = 600;
215
+ const SPLASH_MIN_DURATION_MS = 2800;
216
+ const SPLASH_ENHANCED_HOLD_MS = 800;
213
217
  const SPLASH_FADE_MS = 480;
214
- const SPLASH_REDUCED_MIN_DURATION_MS = 320;
215
- const SPLASH_REDUCED_HOLD_MS = 120;
218
+ const SPLASH_REDUCED_MIN_DURATION_MS = 600;
219
+ const SPLASH_REDUCED_HOLD_MS = 200;
216
220
  const SPLASH_REDUCED_FADE_MS = 180;
217
221
  const SPLASH_AVATAR_READY_MAX_WAIT_MS = 900;
218
222
  const SPLASH_REDUCED_AVATAR_READY_MAX_WAIT_MS = 160;
@@ -82,6 +82,7 @@ import type {
82
82
  TeamLaunchRequest,
83
83
  TeamLaunchResponse,
84
84
  TeamMemberActivityMeta,
85
+ SystemManagerSummary,
85
86
  TeamProvisioningModelVerificationMode,
86
87
  TeamProvisioningPrepareResult,
87
88
  TeamProvisioningProgress,
@@ -141,6 +142,7 @@ import type {
141
142
  TaskChangeSetV2,
142
143
  } from '@shared/types/review';
143
144
  import type { ApplyReviewRequest } from '@shared/types/review';
145
+ import type { SystemManagerAPI } from '@shared/types/systemManager';
144
146
  import type { TerminalAPI } from '@shared/types/terminal';
145
147
  import type { CliArgsValidationResult } from '@shared/utils/cliArgsParser';
146
148
 
@@ -171,7 +173,6 @@ export class HttpAPIClient implements ElectronAPI {
171
173
  };
172
174
  }
173
175
 
174
-
175
176
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- event callbacks have varying signatures
176
177
  private addEventListener(channel: string, callback: (...args: any[]) => void): () => void {
177
178
  this.initEventSource();
@@ -409,7 +410,7 @@ export class HttpAPIClient implements ElectronAPI {
409
410
  await this.patch('/api/cc-settings', patch);
410
411
  },
411
412
  restart: async (): Promise<void> => {
412
- await this.post('/api/cc-restart', {});
413
+ await this.postLong('/api/cc-restart', {});
413
414
  },
414
415
  reload: async (): Promise<void> => {
415
416
  await this.post('/api/cc-reload', {});
@@ -1093,6 +1094,8 @@ export class HttpAPIClient implements ElectronAPI {
1093
1094
 
1094
1095
  teams: TeamsAPI = {
1095
1096
  list: async (): Promise<TeamSummary[]> => this.get<TeamSummary[]>('/api/teams'),
1097
+ ensureSystemManager: async (): Promise<SystemManagerSummary> =>
1098
+ this.post<SystemManagerSummary>('/api/system-manager/ensure'),
1096
1099
  getData: async (teamName: string): Promise<TeamViewSnapshot> =>
1097
1100
  this.get<TeamViewSnapshot>(`/api/teams/${encodeURIComponent(teamName)}/data`),
1098
1101
  getTaskChangePresence: async (): Promise<
@@ -1450,14 +1453,27 @@ export class HttpAPIClient implements ElectronAPI {
1450
1453
  return this.get(`/api/teams/${encodeURIComponent(teamName)}/lead-context`);
1451
1454
  },
1452
1455
  getMemberSpawnStatuses: async (teamName: string) => {
1453
- return this.get<MemberSpawnStatusesSnapshot>(
1454
- `/api/teams/${encodeURIComponent(teamName)}/member-spawn-statuses`
1455
- );
1456
+ try {
1457
+ return await this.get<MemberSpawnStatusesSnapshot>(
1458
+ `/api/teams/${encodeURIComponent(teamName)}/member-spawn-statuses`
1459
+ );
1460
+ } catch {
1461
+ return { statuses: {}, runId: null };
1462
+ }
1456
1463
  },
1457
1464
  getTeamAgentRuntime: async (teamName: string) => {
1458
- return this.get<TeamAgentRuntimeSnapshot>(
1459
- `/api/teams/${encodeURIComponent(teamName)}/agent-runtime`
1460
- );
1465
+ try {
1466
+ return await this.get<TeamAgentRuntimeSnapshot>(
1467
+ `/api/teams/${encodeURIComponent(teamName)}/agent-runtime`
1468
+ );
1469
+ } catch {
1470
+ return {
1471
+ teamName,
1472
+ updatedAt: new Date().toISOString(),
1473
+ runId: null,
1474
+ members: {},
1475
+ };
1476
+ }
1461
1477
  },
1462
1478
  restartMember: async (teamName: string, memberName: string): Promise<void> => {
1463
1479
  await this.post(
@@ -1886,7 +1902,7 @@ export class HttpAPIClient implements ElectronAPI {
1886
1902
  p != null &&
1887
1903
  typeof p === 'object' &&
1888
1904
  !Array.isArray(p) &&
1889
- typeof (p).providerId === 'string'
1905
+ typeof p.providerId === 'string'
1890
1906
  );
1891
1907
 
1892
1908
  // When the backend reports no provider capability data, fall back to a
@@ -2281,18 +2297,52 @@ export class HttpAPIClient implements ElectronAPI {
2281
2297
  };
2282
2298
 
2283
2299
  // ---------------------------------------------------------------------------
2284
- // Terminal (not available in browser mode)
2300
+ // System Manager / Control Console
2301
+ // ---------------------------------------------------------------------------
2302
+
2303
+ systemManager: SystemManagerAPI = {
2304
+ getStatus: () => this.get('/api/system-manager/status'),
2305
+ getConfig: () => this.get('/api/system-manager/config'),
2306
+ updateConfig: (patch) => this.put('/api/system-manager/config', patch),
2307
+ listWorkflowPrompts: (folder) => this.post('/api/system-manager/workflows/list', { folder }),
2308
+ readWorkflowPrompt: (_folder, id) => this.post('/api/system-manager/workflows/read', { id }),
2309
+ };
2310
+
2311
+ // ---------------------------------------------------------------------------
2312
+ // Terminal (HTTP/SSE-backed browser mode)
2285
2313
  // ---------------------------------------------------------------------------
2286
2314
 
2287
2315
  terminal: TerminalAPI = {
2288
- spawn: async (): Promise<string> => {
2289
- throw new Error('Terminal not available in browser mode');
2290
- },
2291
- write: () => {},
2292
- resize: () => {},
2293
- kill: () => {},
2294
- onData: (): (() => void) => () => {},
2295
- onExit: (): (() => void) => () => {},
2316
+ spawn: async (options = {}): Promise<string> => {
2317
+ const result = await this.post<{ ptyId: string }>('/api/terminal/spawn', options);
2318
+ return result.ptyId;
2319
+ },
2320
+ write: (ptyId: string, data: string): void => {
2321
+ void this.post(`/api/terminal/${encodeURIComponent(ptyId)}/write`, { data });
2322
+ },
2323
+ resize: (ptyId: string, cols: number, rows: number): void => {
2324
+ void this.post(`/api/terminal/${encodeURIComponent(ptyId)}/resize`, { cols, rows });
2325
+ },
2326
+ kill: async (ptyId: string): Promise<void> => {
2327
+ await this.del(`/api/terminal/${encodeURIComponent(ptyId)}`);
2328
+ },
2329
+ openExternal: async (options: { command: string; args?: string[]; cwd?: string }): Promise<void> => {
2330
+ await this.post('/api/terminal/open-external', options);
2331
+ },
2332
+ onData: (callback): (() => void) =>
2333
+ this.addEventListener('terminal:data', (data: unknown) => {
2334
+ const event = data as { ptyId?: string; data?: string };
2335
+ if (event.ptyId && typeof event.data === 'string') {
2336
+ callback(null, event.ptyId, event.data);
2337
+ }
2338
+ }),
2339
+ onExit: (callback): (() => void) =>
2340
+ this.addEventListener('terminal:exit', (data: unknown) => {
2341
+ const event = data as { ptyId?: string; exitCode?: number };
2342
+ if (event.ptyId && typeof event.exitCode === 'number') {
2343
+ callback(null, event.ptyId, event.exitCode);
2344
+ }
2345
+ }),
2296
2346
  };
2297
2347
 
2298
2348
  // ---------------------------------------------------------------------------