@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,234 @@
1
+ import { Cron } from 'croner';
2
+ import {
3
+ addDays,
4
+ endOfDay,
5
+ endOfWeek,
6
+ isBefore,
7
+ startOfDay,
8
+ startOfWeek,
9
+ } from 'date-fns';
10
+
11
+ import type { Schedule } from '@shared/types';
12
+
13
+ import type { CalendarOccurrence, CalendarViewMode, WeekRange } from './types';
14
+
15
+ // =============================================================================
16
+ // Config
17
+ // =============================================================================
18
+
19
+ /** Max cron hits to enumerate per schedule per range */
20
+ const MAX_OCCURRENCES_PER_SCHEDULE = 200;
21
+
22
+ /** Default visual duration for each event block (minutes) */
23
+ const DEFAULT_DURATION_MINUTES = 30;
24
+
25
+ /** Week starts on Monday */
26
+ const WEEK_STARTS_ON = 1 as const;
27
+
28
+ // =============================================================================
29
+ // Date range computation
30
+ // =============================================================================
31
+
32
+ export function getViewRange(mode: CalendarViewMode, referenceDate: Date): { start: Date; end: Date } {
33
+ const base = startOfDay(referenceDate);
34
+ switch (mode) {
35
+ case 'day':
36
+ return { start: base, end: endOfDay(referenceDate) };
37
+ case 'week':
38
+ return {
39
+ start: startOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
40
+ end: endOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
41
+ };
42
+ case 'month': {
43
+ const monthStart = new Date(base.getFullYear(), base.getMonth(), 1);
44
+ const gridStart = startOfWeek(monthStart, { weekStartsOn: WEEK_STARTS_ON });
45
+ const nextMonth = new Date(base.getFullYear(), base.getMonth() + 1, 1);
46
+ const lastDay = addDays(nextMonth, -1);
47
+ const gridEnd = endOfWeek(lastDay, { weekStartsOn: WEEK_STARTS_ON });
48
+ return { start: gridStart, end: gridEnd };
49
+ }
50
+ }
51
+ }
52
+
53
+ export function getWeekRange(referenceDate: Date): WeekRange {
54
+ const base = startOfDay(referenceDate);
55
+ return {
56
+ start: startOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
57
+ end: endOfWeek(base, { weekStartsOn: WEEK_STARTS_ON }),
58
+ };
59
+ }
60
+
61
+ // =============================================================================
62
+ // Core occurrence computation
63
+ // =============================================================================
64
+
65
+ interface TeamColorResolver {
66
+ getTeamColor: (teamName: string) => string;
67
+ getTeamDisplayName: (teamName: string) => string;
68
+ }
69
+
70
+ export function computeCalendarOccurrences(
71
+ schedules: Schedule[],
72
+ rangeStart: Date,
73
+ rangeEnd: Date,
74
+ resolvers: TeamColorResolver,
75
+ ): CalendarOccurrence[] {
76
+ if (schedules.length === 0) return [];
77
+
78
+ const allOccurrences: CalendarOccurrence[] = [];
79
+
80
+ for (const schedule of schedules) {
81
+ if (schedule.status === 'disabled') continue;
82
+
83
+ const rawDates = enumerateCronInRange(
84
+ schedule.cronExpression,
85
+ schedule.timezone,
86
+ rangeStart,
87
+ rangeEnd,
88
+ );
89
+
90
+ for (const date of rawDates) {
91
+ allOccurrences.push({
92
+ scheduleId: schedule.id,
93
+ teamName: schedule.teamName,
94
+ label: schedule.label || '',
95
+ cronDescription: '',
96
+ status: schedule.status,
97
+ date,
98
+ hour: date.getHours(),
99
+ minute: date.getMinutes(),
100
+ durationMinutes: DEFAULT_DURATION_MINUTES,
101
+ color: resolvers.getTeamColor(schedule.teamName),
102
+ teamDisplayName: resolvers.getTeamDisplayName(schedule.teamName),
103
+ column: 0,
104
+ totalColumns: 1,
105
+ });
106
+ }
107
+ }
108
+
109
+ resolveOverlaps(allOccurrences);
110
+ return allOccurrences;
111
+ }
112
+
113
+ // =============================================================================
114
+ // Cron enumeration
115
+ // =============================================================================
116
+
117
+ function enumerateCronInRange(
118
+ cronExpression: string,
119
+ timezone: string,
120
+ rangeStart: Date,
121
+ rangeEnd: Date,
122
+ ): Date[] {
123
+ try {
124
+ const job = new Cron(cronExpression.trim(), { timezone, paused: true });
125
+ const raw = job.nextRuns(MAX_OCCURRENCES_PER_SCHEDULE, rangeStart);
126
+ const results: Date[] = [];
127
+ for (const d of raw) {
128
+ const dt = d instanceof Date ? d : new Date(d);
129
+ if (isBefore(rangeEnd, dt)) break;
130
+ if (!isBefore(dt, rangeStart)) {
131
+ results.push(dt);
132
+ }
133
+ }
134
+ return results;
135
+ } catch {
136
+ return [];
137
+ }
138
+ }
139
+
140
+ // =============================================================================
141
+ // Overlap resolution — greedy column assignment within each day
142
+ // =============================================================================
143
+
144
+ function resolveOverlaps(occurrences: CalendarOccurrence[]): void {
145
+ // Group by day key (YYYY-MM-DD)
146
+ const byDay = new Map<string, CalendarOccurrence[]>();
147
+ for (const occ of occurrences) {
148
+ const key = dayKey(occ.date);
149
+ const list = byDay.get(key);
150
+ if (list) list.push(occ);
151
+ else byDay.set(key, [occ]);
152
+ }
153
+
154
+ for (const dayOccurrences of byDay.values()) {
155
+ if (dayOccurrences.length > 1) {
156
+ resolveOverlapsInDay(dayOccurrences);
157
+ }
158
+ }
159
+ }
160
+
161
+ function resolveOverlapsInDay(occurrences: CalendarOccurrence[]): void {
162
+ const sorted = [...occurrences].sort(
163
+ (a, b) => a.hour * 60 + a.minute - (b.hour * 60 + b.minute),
164
+ );
165
+
166
+ // --- Pass 1: greedy column assignment ---
167
+ // columnEndTimes[col] = minute-of-day when that column becomes free
168
+ const columnEndTimes: number[] = [];
169
+
170
+ for (const occ of sorted) {
171
+ const startMin = occ.hour * 60 + occ.minute;
172
+ const endMin = startMin + occ.durationMinutes;
173
+
174
+ let assignedCol = -1;
175
+ for (let col = 0; col < columnEndTimes.length; col++) {
176
+ if (columnEndTimes[col] <= startMin) {
177
+ assignedCol = col;
178
+ break;
179
+ }
180
+ }
181
+ if (assignedCol === -1) {
182
+ assignedCol = columnEndTimes.length;
183
+ columnEndTimes.push(0);
184
+ }
185
+ columnEndTimes[assignedCol] = endMin;
186
+ occ.column = assignedCol;
187
+ }
188
+
189
+ // --- Pass 2: propagate max totalColumns to all events in each overlap group ---
190
+ // Build overlap graph, then for each connected component set totalColumns = max column + 1
191
+ const visited = new Set<CalendarOccurrence>();
192
+
193
+ for (const occ of sorted) {
194
+ if (visited.has(occ)) continue;
195
+
196
+ // BFS to find all transitively overlapping events
197
+ const component: CalendarOccurrence[] = [];
198
+ const queue = [occ];
199
+ visited.add(occ);
200
+
201
+ while (queue.length > 0) {
202
+ const current = queue.pop()!;
203
+ component.push(current);
204
+ const curStart = current.hour * 60 + current.minute;
205
+ const curEnd = curStart + current.durationMinutes;
206
+
207
+ for (const other of sorted) {
208
+ if (visited.has(other)) continue;
209
+ const otherStart = other.hour * 60 + other.minute;
210
+ const otherEnd = otherStart + other.durationMinutes;
211
+ if (otherStart < curEnd && otherEnd > curStart) {
212
+ visited.add(other);
213
+ queue.push(other);
214
+ }
215
+ }
216
+ }
217
+
218
+ const maxCol = Math.max(...component.map((o) => o.column));
219
+ for (const o of component) {
220
+ o.totalColumns = maxCol + 1;
221
+ }
222
+ }
223
+ }
224
+
225
+ // =============================================================================
226
+ // Helpers
227
+ // =============================================================================
228
+
229
+ function dayKey(date: Date): string {
230
+ const y = date.getFullYear();
231
+ const m = String(date.getMonth() + 1).padStart(2, '0');
232
+ const d = String(date.getDate()).padStart(2, '0');
233
+ return `${y}-${m}-${d}`;
234
+ }
@@ -0,0 +1,2 @@
1
+ export { ScheduleCalendarBoard } from './ScheduleCalendarBoard';
2
+ export type { CalendarViewMode, CalendarOccurrence, WeekRange } from './types';
@@ -0,0 +1,44 @@
1
+ import type { ScheduleStatus } from '@shared/types';
2
+
3
+ // =============================================================================
4
+ // Calendar view mode
5
+ // =============================================================================
6
+
7
+ export type CalendarViewMode = 'day' | 'week' | 'month';
8
+
9
+ // =============================================================================
10
+ // Calendar occurrence — a single cron fire event on the calendar
11
+ // =============================================================================
12
+
13
+ export interface CalendarOccurrence {
14
+ scheduleId: string;
15
+ teamName: string;
16
+ label: string;
17
+ cronDescription: string;
18
+ status: ScheduleStatus;
19
+ /** The Date of this specific cron occurrence */
20
+ date: Date;
21
+ /** Hour of day (0-23) for positioning */
22
+ hour: number;
23
+ /** Minute within the hour (0-59) */
24
+ minute: number;
25
+ /** Visual block height in minutes — defaults to 30 */
26
+ durationMinutes: number;
27
+ /** Team color (CSS color string) */
28
+ color: string;
29
+ /** Team display name */
30
+ teamDisplayName: string;
31
+ /** Column index for side-by-side stacking (computed by overlap algorithm) */
32
+ column: number;
33
+ /** Total number of overlapping columns in this slot */
34
+ totalColumns: number;
35
+ }
36
+
37
+ // =============================================================================
38
+ // Date range helpers
39
+ // =============================================================================
40
+
41
+ export interface WeekRange {
42
+ start: Date;
43
+ end: Date;
44
+ }
@@ -6,11 +6,11 @@ import {
6
6
  TooltipProvider,
7
7
  TooltipTrigger,
8
8
  } from '@renderer/components/ui/tooltip';
9
- import { Bot, Info, PlugZap, Settings, Share2, Wrench } from 'lucide-react';
9
+ import { Bot, Info, Settings, Share2, Wrench } from 'lucide-react';
10
10
 
11
11
  import type { LucideIcon } from 'lucide-react';
12
12
 
13
- export type SettingsSection = 'general' | 'channels' | 'harness' | 'task-bus' | 'advanced';
13
+ export type SettingsSection = 'general' | 'harness' | 'task-bus' | 'advanced';
14
14
 
15
15
  interface SettingsTabsProps {
16
16
  activeSection: SettingsSection;
@@ -31,30 +31,23 @@ const tabs: TabConfig[] = [
31
31
  icon: Settings,
32
32
  description: '主题、语言、显示密度和启动行为等核心应用偏好。',
33
33
  },
34
- {
35
- id: 'channels',
36
- label: '渠道',
37
- icon: PlugZap,
38
- description:
39
- '管理飞书、微信、Telegram 等消息平台的接入配置。每个 cc-connect 项目可绑定一个或多个渠道。',
40
- },
41
34
  {
42
35
  id: 'harness',
43
36
  label: 'Harness',
44
37
  icon: Bot,
45
- description: '管理 AI Agent 运行时(12 种)的 Provider 配置、API Key、端点和 CLI 安装状态。',
38
+ description: '管理 AI Agent 运行时的 Provider 配置、API Key、端点和 CLI 安装状态。',
46
39
  },
47
40
  {
48
41
  id: 'task-bus',
49
42
  label: '团队总线',
50
43
  icon: Share2,
51
- description: '配置 Redis 消息总线实现跨主机团队协作,以及团队使用数据的采集和上报。',
44
+ description: '配置 Redis 消息总线实现跨主机团队协作,以及使用数据采集和上报。',
52
45
  },
53
46
  {
54
47
  id: 'advanced',
55
48
  label: '高级',
56
49
  icon: Wrench,
57
- description: '高级选项:导出/导入配置、重置默认值和编辑原始配置。',
50
+ description: '导出/导入配置、重置默认值和编辑原始配置。',
58
51
  },
59
52
  ];
60
53
 
@@ -66,51 +59,53 @@ export const SettingsTabs = ({
66
59
 
67
60
  return (
68
61
  <TooltipProvider>
69
- <div className="border-b border-border pb-0">
70
- <div className="inline-flex h-9 items-center gap-1 rounded-t-lg bg-[var(--color-surface-raised)] p-1 text-[var(--color-text-muted)]">
71
- {visibleTabs.map((tab) => {
72
- const Icon = tab.icon;
73
- const isActive = activeSection === tab.id;
62
+ <div className="flex items-center" style={{ backgroundColor: 'var(--color-surface-raised)' }}>
63
+ {visibleTabs.map((tab, index) => {
64
+ const Icon = tab.icon;
65
+ const isActive = activeSection === tab.id;
74
66
 
75
- return (
76
- <button
77
- key={tab.id}
78
- onClick={() => onSectionChange(tab.id)}
79
- className={`relative inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-md px-3 py-1 pr-7 text-sm font-medium transition-all ${
80
- isActive
81
- ? 'bg-[var(--color-surface)] text-[var(--color-text)] shadow-sm'
82
- : 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
83
- }`}
84
- >
85
- <Icon className="size-3.5" />
86
- {tab.label}
67
+ return (
68
+ <button
69
+ key={tab.id}
70
+ onClick={() => onSectionChange(tab.id)}
71
+ className={`flex items-center gap-1.5 px-3 py-2 text-xs transition-colors ${
72
+ isActive
73
+ ? 'font-medium'
74
+ : 'text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]'
75
+ }`}
76
+ style={{
77
+ borderLeft: index > 0 ? '1px solid var(--color-border-subtle)' : undefined,
78
+ color: isActive ? '#67e8f9' : undefined,
79
+ }}
80
+ >
81
+ <Icon className={`size-3 ${isActive ? 'opacity-90' : 'opacity-40'}`} />
82
+ {tab.label}
87
83
 
88
- <Tooltip>
89
- <TooltipTrigger asChild>
90
- <span
91
- role="button"
92
- tabIndex={0}
93
- aria-label={`What is ${tab.label}?`}
94
- onClick={(event) => event.stopPropagation()}
95
- onMouseDown={(event) => event.stopPropagation()}
96
- onKeyDown={(event) => {
97
- if (event.key === 'Enter' || event.key === ' ') {
98
- event.stopPropagation();
99
- }
100
- }}
101
- className="size-4.5 absolute right-1.5 top-0.5 z-10 inline-flex items-center justify-center rounded-full text-text-muted transition-colors hover:bg-[var(--color-surface-raised)] hover:text-text"
102
- >
103
- <Info className="size-3" />
104
- </span>
105
- </TooltipTrigger>
106
- <TooltipContent className="max-w-64 text-pretty text-xs leading-relaxed">
107
- {tab.description}
108
- </TooltipContent>
109
- </Tooltip>
110
- </button>
111
- );
112
- })}
113
- </div>
84
+ <Tooltip>
85
+ <TooltipTrigger asChild>
86
+ <span
87
+ role="button"
88
+ tabIndex={0}
89
+ aria-label={`What is ${tab.label}?`}
90
+ onClick={(event) => event.stopPropagation()}
91
+ onMouseDown={(event) => event.stopPropagation()}
92
+ onKeyDown={(event) => {
93
+ if (event.key === 'Enter' || event.key === ' ') {
94
+ event.stopPropagation();
95
+ }
96
+ }}
97
+ className="ml-0.5 inline-flex items-center justify-center rounded-full text-[var(--color-text-muted)] opacity-0 transition-opacity hover:opacity-100"
98
+ >
99
+ <Info className="size-2.5" />
100
+ </span>
101
+ </TooltipTrigger>
102
+ <TooltipContent className="max-w-64 text-xs leading-relaxed">
103
+ {tab.description}
104
+ </TooltipContent>
105
+ </Tooltip>
106
+ </button>
107
+ );
108
+ })}
114
109
  </div>
115
110
  </TooltipProvider>
116
111
  );
@@ -1,6 +1,6 @@
1
1
  /**
2
- * SettingsView - Main settings panel with all app configuration options.
3
- * Provides UI for managing runtime, channels, and advanced options.
2
+ * SettingsView - Main settings panel.
3
+ * Terminal-style layout matching the control console aesthetic.
4
4
  */
5
5
 
6
6
  import { useEffect, useState } from 'react';
@@ -10,7 +10,7 @@ import { Loader2 } from 'lucide-react';
10
10
  import { useShallow } from 'zustand/react/shallow';
11
11
 
12
12
  import { useSettingsConfig, useSettingsHandlers } from './hooks';
13
- import { AdvancedSection, GeneralSection, HarnessSection, PlatformsSection } from './sections';
13
+ import { AdvancedSection, GeneralSection, HarnessSection } from './sections';
14
14
  import { TaskBusSection } from './sections/TaskBusSection';
15
15
  import { type SettingsSection, SettingsTabs } from './SettingsTabs';
16
16
 
@@ -23,17 +23,15 @@ export const SettingsView = (): React.JSX.Element | null => {
23
23
  }))
24
24
  );
25
25
 
26
- // Consume pending section (avoid setState during render)
27
26
  useEffect(() => {
28
27
  if (pendingSettingsSection) {
29
28
  const nextSection: SettingsSection =
30
- pendingSettingsSection === 'channels' ||
31
29
  pendingSettingsSection === 'harness' ||
32
30
  pendingSettingsSection === 'task-bus' ||
33
31
  pendingSettingsSection === 'advanced'
34
32
  ? pendingSettingsSection
35
33
  : 'general';
36
- // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional sync on prop change
34
+ // eslint-disable-next-line react-hooks/set-state-in-effect
37
35
  setActiveSection(nextSection);
38
36
  clearPendingSettingsSection();
39
37
  }
@@ -61,39 +59,37 @@ export const SettingsView = (): React.JSX.Element | null => {
61
59
  updateConfig,
62
60
  });
63
61
 
64
- // Loading state
65
62
  if (loading) {
66
63
  return (
67
64
  <div
68
- className="flex flex-1 items-center justify-center"
65
+ className="flex flex-1 items-center justify-center font-mono"
69
66
  style={{ backgroundColor: 'var(--color-surface)' }}
70
67
  >
71
68
  <div className="flex items-center gap-3" style={{ color: 'var(--color-text-muted)' }}>
72
- <Loader2 className="size-5 animate-spin" />
73
- <span>正在加载设置...</span>
69
+ <Loader2 className="size-4 animate-spin" />
70
+ <span className="text-xs">loading settings...</span>
74
71
  </div>
75
72
  </div>
76
73
  );
77
74
  }
78
75
 
79
- // Error state
80
76
  if (error && !config) {
81
77
  return (
82
78
  <div
83
- className="flex flex-1 items-center justify-center"
79
+ className="flex flex-1 items-center justify-center font-mono"
84
80
  style={{ backgroundColor: 'var(--color-surface)' }}
85
81
  >
86
82
  <div className="text-center">
87
- <p className="mb-4 text-red-400">{error}</p>
83
+ <p className="mb-4 text-xs text-red-400">{error}</p>
88
84
  <button
89
85
  onClick={() => window.location.reload()}
90
- className="rounded-md px-4 py-2 transition-colors"
86
+ className="rounded border px-3 py-1.5 text-xs transition-colors"
91
87
  style={{
92
- backgroundColor: 'var(--color-surface-raised)',
93
- color: 'var(--color-text-secondary)',
88
+ borderColor: 'var(--color-border)',
89
+ color: 'var(--color-text-muted)',
94
90
  }}
95
91
  >
96
- 重试
92
+ retry
97
93
  </button>
98
94
  </div>
99
95
  </div>
@@ -104,27 +100,28 @@ export const SettingsView = (): React.JSX.Element | null => {
104
100
 
105
101
  return (
106
102
  <div className="flex-1 overflow-auto" style={{ backgroundColor: 'var(--color-surface)' }}>
107
- <div className="mx-auto max-w-2xl px-6 py-8">
108
- {/* Header */}
109
- <div className="mb-6">
110
- <h1 className="text-lg font-medium" style={{ color: 'var(--color-text)' }}>
111
- 设置
112
- </h1>
113
- <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
114
- 管理应用偏好设置
115
- </p>
103
+ {/* Clean container */}
104
+ <div
105
+ className="mx-auto flex min-h-full max-w-3xl flex-col"
106
+ style={{
107
+ backgroundColor: 'var(--color-surface)',
108
+ }}
109
+ >
110
+ {/* Tabs area */}
111
+ <div
112
+ className="border-b"
113
+ style={{
114
+ borderColor: 'var(--color-border-subtle)',
115
+ }}
116
+ >
117
+ <SettingsTabs activeSection={activeSection} onSectionChange={setActiveSection} />
116
118
  {error && (
117
- <div className="mt-4 rounded-md border border-red-500/20 bg-red-500/10 px-3 py-2">
118
- <p className="text-sm text-red-400">{error}</p>
119
- </div>
119
+ <div className="px-4 pb-2 text-[10px] text-red-400">{error}</div>
120
120
  )}
121
121
  </div>
122
122
 
123
- {/* Tabs */}
124
- <SettingsTabs activeSection={activeSection} onSectionChange={setActiveSection} />
125
-
126
123
  {/* Content */}
127
- <div className="mt-4">
124
+ <div className="flex-1 overflow-y-auto px-6 py-5">
128
125
  {activeSection === 'general' && (
129
126
  <GeneralSection
130
127
  safeConfig={safeConfig}
@@ -135,8 +132,6 @@ export const SettingsView = (): React.JSX.Element | null => {
135
132
  />
136
133
  )}
137
134
 
138
- {activeSection === 'channels' && <PlatformsSection />}
139
-
140
135
  {activeSection === 'harness' && <HarnessSection />}
141
136
 
142
137
  {activeSection === 'task-bus' && <TaskBusSection />}
@@ -14,11 +14,15 @@ export const SettingsSectionHeader = ({
14
14
  }: SettingsSectionHeaderProps): React.JSX.Element => {
15
15
  return (
16
16
  <h3
17
- className="mb-2 mt-6 flex items-center gap-1.5 text-xs font-medium uppercase tracking-widest first:mt-0"
17
+ className="mb-3 mt-7 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.12em] first:mt-0"
18
18
  style={{ color: 'var(--color-text-muted)' }}
19
19
  >
20
20
  {icon}
21
21
  {title}
22
+ <div
23
+ className="ml-1 h-px flex-1"
24
+ style={{ backgroundColor: 'var(--color-border-subtle)' }}
25
+ />
22
26
  </h3>
23
27
  );
24
28
  };
@@ -57,7 +57,7 @@ export const SettingsSelect = <T extends string | number>({
57
57
  type="button"
58
58
  onClick={() => !disabled && setIsOpen(!isOpen)}
59
59
  disabled={disabled}
60
- className={`flex h-8 items-center justify-between gap-2 rounded-md border bg-transparent px-2 text-sm transition-all duration-150 focus:outline-none focus:ring-1 focus:ring-zinc-700 ${fullWidth ? 'w-full' : 'min-w-[140px]'} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} ${isOpen ? 'ring-1 ring-zinc-700' : ''} `}
60
+ className={`flex h-8 items-center justify-between gap-2 rounded-md border bg-transparent px-2 text-sm transition-all duration-150 focus:outline-none focus:ring-1 focus:ring-[var(--color-border-emphasis)] ${fullWidth ? 'w-full' : 'min-w-[140px]'} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} ${isOpen ? 'ring-1 ring-[var(--color-border-emphasis)]' : ''} `}
61
61
  style={{
62
62
  color: 'var(--color-text-secondary)',
63
63
  borderColor: isOpen ? 'var(--color-border)' : 'var(--color-border-subtle)',
@@ -85,12 +85,14 @@ export const SettingsSelect = <T extends string | number>({
85
85
  type="button"
86
86
  onClick={() => handleSelect(option.value)}
87
87
  className={`flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm transition-colors duration-100 ${
88
- value === option.value ? 'bg-indigo-500/10 text-indigo-400' : 'hover:bg-white/5'
88
+ value === option.value
89
+ ? 'bg-cyan-500/10 text-cyan-300'
90
+ : 'hover:bg-white/5'
89
91
  } `}
90
92
  style={value !== option.value ? { color: 'var(--color-text-secondary)' } : undefined}
91
93
  >
92
94
  <span className="whitespace-nowrap">{option.label}</span>
93
- {value === option.value && <Check className="size-4 shrink-0 text-indigo-400" />}
95
+ {value === option.value && <Check className="size-4 shrink-0 text-cyan-400" />}
94
96
  </button>
95
97
  ))}
96
98
  </div>
@@ -27,9 +27,9 @@ export const SettingsToggle = ({
27
27
  aria-checked={enabled}
28
28
  disabled={disabled}
29
29
  onClick={handleClick}
30
- className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-[#141416]"
30
+ className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-[var(--color-text)]/20 focus:ring-offset-2 focus:ring-offset-[var(--color-surface)]"
31
31
  style={{
32
- backgroundColor: enabled ? '#6366f1' : '#3f3f46',
32
+ backgroundColor: enabled ? '#0891b2' : '#3f3f46',
33
33
  opacity: disabled ? 0.5 : 1,
34
34
  cursor: disabled ? 'not-allowed' : 'pointer',
35
35
  }}