@yancyyu/openhermit 1.6.28 → 1.6.30

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 (177) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
  5. package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
  9. package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
  20. package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
  30. package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
  31. package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
  32. package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
  33. package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
  34. package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
  35. package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
  36. package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
  37. package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
  41. package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
  42. package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
  52. package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +907 -184
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
  93. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  94. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  95. package/src/main/services/team/cliFlavor.ts +54 -0
  96. package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
  97. package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
  98. package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
  99. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
  100. package/src/main/services/teams-mvp/index.ts +3 -0
  101. package/src/main/utils/atomicWrite.ts +72 -0
  102. package/src/main/utils/childProcess.ts +554 -0
  103. package/src/main/utils/cliEnv.ts +54 -0
  104. package/src/main/utils/cliPathMerge.ts +97 -0
  105. package/src/main/utils/pathDecoder.ts +664 -0
  106. package/src/main/utils/pathValidation.ts +432 -0
  107. package/src/main/utils/shellEnv.ts +331 -0
  108. package/src/renderer/App.tsx +5 -0
  109. package/src/renderer/api/httpClient.ts +128 -0
  110. package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
  111. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  112. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  113. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  114. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  115. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  116. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  117. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
  118. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  119. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  120. package/src/renderer/components/layout/PaneContent.tsx +2 -0
  121. package/src/renderer/components/layout/SortableTab.tsx +1 -0
  122. package/src/renderer/components/layout/TabBarActions.tsx +12 -12
  123. package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
  124. package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  126. package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
  127. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  128. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  129. package/src/renderer/components/tasks/TasksView.tsx +343 -0
  130. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  131. package/src/renderer/components/team/TeamDetailView.tsx +55 -98
  132. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  133. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
  134. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
  135. package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
  136. package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
  137. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
  138. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
  139. package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
  140. package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
  141. package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
  142. package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
  143. package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
  144. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
  145. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  146. package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
  147. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  148. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  149. package/src/renderer/store/slices/scheduleSlice.ts +21 -0
  150. package/src/renderer/store/slices/teamSlice.ts +67 -25
  151. package/src/renderer/types/tabs.ts +1 -0
  152. package/src/shared/types/api.ts +58 -0
  153. package/src/shared/types/extensions/index.ts +1 -0
  154. package/src/shared/types/extensions/mcp.ts +2 -0
  155. package/src/shared/types/extensions/plugin.ts +2 -1
  156. package/src/shared/types/extensions/skill.ts +7 -0
  157. package/src/shared/types/team.ts +104 -1
  158. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  159. package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
  160. package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
  161. package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
  162. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
  163. package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
  164. package/dist-renderer/assets/index-BIOJremZ.css +0 -1
  165. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  166. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  167. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  168. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  169. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  170. package/src/features/recent-projects/main/index.ts +0 -3
  171. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  172. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  173. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  174. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  175. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  176. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  177. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -0,0 +1,343 @@
1
+ import { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ import { Button } from '@renderer/components/ui/button';
4
+ import { cn } from '@renderer/lib/utils';
5
+ import { useStore } from '@renderer/store';
6
+ import { deriveTaskDisplayId } from '@shared/utils/taskIdentity';
7
+ import { Calendar, CheckCircle2, Circle, Columns3, Loader2, RefreshCw } from 'lucide-react';
8
+ import { useShallow } from 'zustand/react/shallow';
9
+
10
+ import { SchedulesView } from '../schedules/SchedulesView';
11
+
12
+ import type { GlobalTask, TeamTaskStatus } from '@shared/types';
13
+
14
+ type TasksSubTab = 'overview' | 'schedules';
15
+ type OverviewStatus = Extract<TeamTaskStatus, 'pending' | 'in_progress' | 'completed'>;
16
+
17
+ const SUB_TABS: { id: TasksSubTab; label: string; icon: React.ReactNode }[] = [
18
+ { id: 'overview', label: '任务总览', icon: <Columns3 size={14} /> },
19
+ { id: 'schedules', label: '定时任务', icon: <Calendar size={14} /> },
20
+ ];
21
+
22
+ const COLUMNS: {
23
+ id: OverviewStatus;
24
+ title: string;
25
+ icon: React.ReactNode;
26
+ headerBg: string;
27
+ bodyBg: string;
28
+ }[] = [
29
+ {
30
+ id: 'pending',
31
+ title: 'TODO',
32
+ icon: <Circle size={14} className="shrink-0 text-[var(--color-text-muted)]" />,
33
+ headerBg: 'rgba(59, 130, 246, 0.22)',
34
+ bodyBg: 'rgba(59, 130, 246, 0.05)',
35
+ },
36
+ {
37
+ id: 'in_progress',
38
+ title: 'IN PROGRESS',
39
+ icon: <Loader2 size={14} className="shrink-0 text-[var(--color-text-muted)]" />,
40
+ headerBg: 'rgba(234, 179, 8, 0.24)',
41
+ bodyBg: 'rgba(234, 179, 8, 0.06)',
42
+ },
43
+ {
44
+ id: 'completed',
45
+ title: 'DONE',
46
+ icon: <CheckCircle2 size={14} className="shrink-0 text-[var(--color-text-muted)]" />,
47
+ headerBg: 'rgba(34, 197, 94, 0.22)',
48
+ bodyBg: 'rgba(34, 197, 94, 0.05)',
49
+ },
50
+ ];
51
+
52
+ function isOverviewStatus(status: TeamTaskStatus): status is OverviewStatus {
53
+ return status === 'pending' || status === 'in_progress' || status === 'completed';
54
+ }
55
+
56
+ function getTaskUpdatedAt(task: GlobalTask): number {
57
+ const raw = task.updatedAt ?? task.createdAt;
58
+ const time = raw ? new Date(raw).getTime() : 0;
59
+ return Number.isFinite(time) ? time : 0;
60
+ }
61
+
62
+ function buildOptionLabel(value: string | null | undefined, fallback: string): string {
63
+ const trimmed = value?.trim();
64
+ return trimmed && trimmed.length > 0 ? trimmed : fallback;
65
+ }
66
+
67
+ export const TasksView = (): React.JSX.Element => {
68
+ const [activeTab, setActiveTab] = useState<TasksSubTab>('overview');
69
+
70
+ return (
71
+ <div className="flex h-full flex-col">
72
+ <div className="flex items-center border-b border-[var(--color-border)] px-4 pt-2">
73
+ {SUB_TABS.map((tab) => (
74
+ <button
75
+ key={tab.id}
76
+ onClick={() => setActiveTab(tab.id)}
77
+ className={cn(
78
+ 'flex items-center gap-1.5 border-b-2 px-4 pb-2 text-sm font-medium transition-colors',
79
+ activeTab === tab.id
80
+ ? 'border-[var(--color-primary)] text-[var(--color-text)]'
81
+ : 'border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text)]'
82
+ )}
83
+ >
84
+ {tab.icon}
85
+ {tab.label}
86
+ </button>
87
+ ))}
88
+ </div>
89
+ <div className="flex-1 overflow-auto">
90
+ {activeTab === 'overview' && <TaskOverviewPool />}
91
+ {activeTab === 'schedules' && <SchedulesView />}
92
+ </div>
93
+ </div>
94
+ );
95
+ };
96
+
97
+ const TaskOverviewPool = (): React.JSX.Element => {
98
+ const {
99
+ globalTasks,
100
+ globalTasksLoading,
101
+ globalTasksInitialized,
102
+ fetchAllTasks,
103
+ openGlobalTaskDetail,
104
+ } = useStore(
105
+ useShallow((s) => ({
106
+ globalTasks: s.globalTasks,
107
+ globalTasksLoading: s.globalTasksLoading,
108
+ globalTasksInitialized: s.globalTasksInitialized,
109
+ fetchAllTasks: s.fetchAllTasks,
110
+ openGlobalTaskDetail: s.openGlobalTaskDetail,
111
+ }))
112
+ );
113
+ const [teamFilter, setTeamFilter] = useState('all');
114
+ const [statusFilter, setStatusFilter] = useState<'all' | OverviewStatus>('all');
115
+ const [ownerFilter, setOwnerFilter] = useState('all');
116
+
117
+ useEffect(() => {
118
+ void fetchAllTasks();
119
+ }, [fetchAllTasks]);
120
+
121
+ const overviewTasks = useMemo(
122
+ () => globalTasks.filter((task) => isOverviewStatus(task.status) && !task.teamDeleted),
123
+ [globalTasks]
124
+ );
125
+
126
+ const teamOptions = useMemo(
127
+ () =>
128
+ Array.from(
129
+ new Map(overviewTasks.map((task) => [task.teamName, task.teamDisplayName])).entries()
130
+ ).sort((a, b) => a[1].localeCompare(b[1])),
131
+ [overviewTasks]
132
+ );
133
+
134
+ const ownerOptions = useMemo(() => {
135
+ const owners = new Set<string>();
136
+ for (const task of overviewTasks) {
137
+ if (task.owner?.trim()) owners.add(task.owner.trim());
138
+ }
139
+ return Array.from(owners).sort((a, b) => a.localeCompare(b));
140
+ }, [overviewTasks]);
141
+
142
+ const filteredTasks = useMemo(
143
+ () =>
144
+ overviewTasks
145
+ .filter((task) => teamFilter === 'all' || task.teamName === teamFilter)
146
+ .filter((task) => statusFilter === 'all' || task.status === statusFilter)
147
+ .filter((task) => ownerFilter === 'all' || task.owner === ownerFilter)
148
+ .sort((a, b) => getTaskUpdatedAt(b) - getTaskUpdatedAt(a)),
149
+ [overviewTasks, ownerFilter, statusFilter, teamFilter]
150
+ );
151
+
152
+ const grouped = useMemo(() => {
153
+ const map = new Map<OverviewStatus, GlobalTask[]>();
154
+ for (const column of COLUMNS) {
155
+ map.set(column.id, []);
156
+ }
157
+ for (const task of filteredTasks) {
158
+ if (isOverviewStatus(task.status)) {
159
+ map.get(task.status)?.push(task);
160
+ }
161
+ }
162
+ return map;
163
+ }, [filteredTasks]);
164
+
165
+ const clearFilters = useCallback(() => {
166
+ setTeamFilter('all');
167
+ setStatusFilter('all');
168
+ setOwnerFilter('all');
169
+ }, []);
170
+
171
+ if (globalTasksLoading && !globalTasksInitialized) {
172
+ return (
173
+ <div className="flex h-full items-center justify-center text-sm text-[var(--color-text-muted)]">
174
+ 加载团队任务…
175
+ </div>
176
+ );
177
+ }
178
+
179
+ return (
180
+ <div className="flex h-full min-w-0 flex-col gap-3 p-4">
181
+ <div className="flex flex-wrap items-end gap-2">
182
+ <div className="min-w-[180px]">
183
+ <label
184
+ htmlFor="tasks-overview-team-filter"
185
+ className="mb-1 block text-[11px] font-medium text-[var(--color-text-muted)]"
186
+ >
187
+ 团队
188
+ </label>
189
+ <select
190
+ id="tasks-overview-team-filter"
191
+ value={teamFilter}
192
+ onChange={(event) => setTeamFilter(event.target.value)}
193
+ className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
194
+ >
195
+ <option value="all">全部团队</option>
196
+ {teamOptions.map(([teamName, displayName]) => (
197
+ <option key={teamName} value={teamName}>
198
+ {displayName}
199
+ </option>
200
+ ))}
201
+ </select>
202
+ </div>
203
+
204
+ <div className="min-w-[160px]">
205
+ <label
206
+ htmlFor="tasks-overview-status-filter"
207
+ className="mb-1 block text-[11px] font-medium text-[var(--color-text-muted)]"
208
+ >
209
+ 状态
210
+ </label>
211
+ <select
212
+ id="tasks-overview-status-filter"
213
+ value={statusFilter}
214
+ onChange={(event) => setStatusFilter(event.target.value as 'all' | OverviewStatus)}
215
+ className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
216
+ >
217
+ <option value="all">全部状态</option>
218
+ <option value="pending">TODO</option>
219
+ <option value="in_progress">IN PROGRESS</option>
220
+ <option value="completed">DONE</option>
221
+ </select>
222
+ </div>
223
+
224
+ <div className="min-w-[160px]">
225
+ <label
226
+ htmlFor="tasks-overview-owner-filter"
227
+ className="mb-1 block text-[11px] font-medium text-[var(--color-text-muted)]"
228
+ >
229
+ 负责人
230
+ </label>
231
+ <select
232
+ id="tasks-overview-owner-filter"
233
+ value={ownerFilter}
234
+ onChange={(event) => setOwnerFilter(event.target.value)}
235
+ className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
236
+ >
237
+ <option value="all">全部负责人</option>
238
+ {ownerOptions.map((owner) => (
239
+ <option key={owner} value={owner}>
240
+ {owner}
241
+ </option>
242
+ ))}
243
+ </select>
244
+ </div>
245
+
246
+ <Button variant="outline" size="sm" className="h-8 gap-1.5 text-xs" onClick={clearFilters}>
247
+ 清空筛选
248
+ </Button>
249
+ <Button
250
+ variant="ghost"
251
+ size="sm"
252
+ className="ml-auto h-8 gap-1.5 text-xs text-[var(--color-text-muted)]"
253
+ onClick={() => void fetchAllTasks()}
254
+ >
255
+ <RefreshCw size={12} />
256
+ 刷新
257
+ </Button>
258
+ </div>
259
+
260
+ <div className="w-full min-w-0 max-w-full overflow-x-auto overflow-y-hidden pb-6">
261
+ <div className="grid min-w-[900px] grid-cols-3 items-start gap-3">
262
+ {COLUMNS.map((column) => {
263
+ const tasks = grouped.get(column.id) ?? [];
264
+ return (
265
+ <section
266
+ key={column.id}
267
+ className="relative rounded-md"
268
+ style={{ backgroundColor: column.bodyBg }}
269
+ >
270
+ {tasks.length > 0 ? (
271
+ <span className="absolute -right-2 -top-2 z-10 min-w-5 rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0 text-center text-[10px] font-medium leading-5 text-[var(--color-text-secondary)] ring-1 ring-[var(--color-border)]">
272
+ {tasks.length}
273
+ </span>
274
+ ) : null}
275
+ <header
276
+ className="rounded-t-md px-3 py-2"
277
+ style={{ backgroundColor: column.headerBg }}
278
+ >
279
+ <h4 className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text)]">
280
+ {column.icon}
281
+ {column.title}
282
+ </h4>
283
+ </header>
284
+ <div className="flex flex-col gap-1.5 p-2">
285
+ {tasks.length === 0 ? (
286
+ <div className="rounded-md border border-dashed border-[var(--color-border)] p-3 text-xs text-[var(--color-text-muted)]">
287
+ No tasks
288
+ </div>
289
+ ) : (
290
+ tasks.map((task) => (
291
+ <GlobalOverviewTaskCard
292
+ key={`${task.teamName}:${task.id}`}
293
+ task={task}
294
+ onOpen={() => openGlobalTaskDetail(task.teamName, task.id)}
295
+ />
296
+ ))
297
+ )}
298
+ </div>
299
+ </section>
300
+ );
301
+ })}
302
+ </div>
303
+ </div>
304
+ </div>
305
+ );
306
+ };
307
+
308
+ const GlobalOverviewTaskCard = ({
309
+ task,
310
+ onOpen,
311
+ }: {
312
+ task: GlobalTask;
313
+ onOpen: () => void;
314
+ }): React.JSX.Element => {
315
+ const ownerLabel = buildOptionLabel(task.owner, '未分配');
316
+ const dispatchFrom = task.dispatchMeta?.originTeam;
317
+ const dispatchTo = task.dispatchMeta?.targetTeam;
318
+ return (
319
+ <button
320
+ type="button"
321
+ className="relative w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-1.5 py-3 text-left text-xs transition-colors hover:border-[var(--color-border-emphasis)]"
322
+ onClick={onOpen}
323
+ >
324
+ <span className="absolute left-[3px] top-[2px] text-[9px] leading-none text-[var(--color-text-muted)]">
325
+ #{task.displayId ?? deriveTaskDisplayId(task.id)}
326
+ </span>
327
+ <div className="mb-2 pt-[11px]">
328
+ <h5 className="line-clamp-2 text-xs font-medium text-[var(--color-text)]">
329
+ {task.subject}
330
+ </h5>
331
+ {task.dispatchMeta ? (
332
+ <span className="mt-1 inline-flex items-center rounded-full bg-yellow-500/15 px-1.5 py-0.5 text-[10px] font-medium text-yellow-600 dark:text-yellow-400">
333
+ {dispatchFrom} 给 {dispatchTo} 派单
334
+ </span>
335
+ ) : null}
336
+ </div>
337
+ <div className="flex flex-wrap items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
338
+ <span className="rounded bg-white/5 px-1.5 py-0.5">{task.teamDisplayName}</span>
339
+ <span className="rounded bg-white/5 px-1.5 py-0.5">{ownerLabel}</span>
340
+ </div>
341
+ </button>
342
+ );
343
+ };
@@ -0,0 +1,71 @@
1
+ import type { CcAgentType } from '@shared/types/ccConnect';
2
+ import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from '@renderer/components/ui/select';
10
+ import { ALL_AGENT_TYPES, AGENT_TYPE_LABELS } from './HarnessCards';
11
+
12
+ interface HarnessSelectProps {
13
+ value: CcAgentType;
14
+ onChange: (value: CcAgentType) => void;
15
+ className?: string;
16
+ id?: string;
17
+ }
18
+
19
+ const HARNESS_PROVIDER_MAP: Partial<
20
+ Record<CcAgentType, 'anthropic' | 'codex' | 'gemini' | 'opencode'>
21
+ > = {
22
+ claudecode: 'anthropic',
23
+ codex: 'codex',
24
+ gemini: 'gemini',
25
+ opencode: 'opencode',
26
+ };
27
+
28
+ function HarnessIcon({ type, className }: { type: CcAgentType; className?: string }) {
29
+ const providerId = HARNESS_PROVIDER_MAP[type];
30
+ if (providerId) {
31
+ return <ProviderBrandLogo providerId={providerId} className={className} />;
32
+ }
33
+ return <span className={className}>{EMOJI_FALLBACK[type]}</span>;
34
+ }
35
+
36
+ const EMOJI_FALLBACK: Record<CcAgentType, string> = {
37
+ claudecode: '🤖',
38
+ codex: '🔬',
39
+ cursor: '💻',
40
+ gemini: '💎',
41
+ iflow: '🌊',
42
+ kimi: '🌙',
43
+ devin: '🧑‍💻',
44
+ opencode: '🔓',
45
+ qoder: '⚡',
46
+ pi: '🥧',
47
+ acp: '🔗',
48
+ tmux: '🖥️',
49
+ };
50
+
51
+ export function HarnessSelect({ value, onChange, className, id }: HarnessSelectProps) {
52
+ return (
53
+ <Select value={value} onValueChange={(v) => onChange(v as CcAgentType)}>
54
+ <SelectTrigger id={id} className={className}>
55
+ <SelectValue />
56
+ </SelectTrigger>
57
+ <SelectContent>
58
+ {ALL_AGENT_TYPES.map((type) => (
59
+ <SelectItem key={type} value={type}>
60
+ <div className="flex items-center gap-2">
61
+ <HarnessIcon type={type} className="size-4 shrink-0" />
62
+ <span>{AGENT_TYPE_LABELS[type]}</span>
63
+ </div>
64
+ </SelectItem>
65
+ ))}
66
+ </SelectContent>
67
+ </Select>
68
+ );
69
+ }
70
+
71
+ export { HarnessIcon, HARNESS_PROVIDER_MAP, EMOJI_FALLBACK };
@@ -68,6 +68,8 @@ import {
68
68
  Terminal,
69
69
  Trash2,
70
70
  Loader2,
71
+ MessageSquare,
72
+ Shield,
71
73
  Users,
72
74
  } from 'lucide-react';
73
75
  import { useShallow } from 'zustand/react/shallow';
@@ -95,6 +97,7 @@ const ProjectEditorOverlay = lazy(() =>
95
97
  import { MemberList } from './members/MemberList';
96
98
  import { MessagesPanel } from './messages/MessagesPanel';
97
99
  import { ChangeReviewDialog } from './review/ChangeReviewDialog';
100
+ import { ProjectEnvPanel } from '../extensions/env/ProjectEnvPanel';
98
101
  import {
99
102
  getTeamPendingRepliesState,
100
103
  setTeamPendingRepliesState,
@@ -879,6 +882,7 @@ export const TeamDetailView = ({
879
882
  const [removeMemberConfirm, setRemoveMemberConfirm] = useState<string | null>(null);
880
883
  const [updatingRoleLoading, setUpdatingRoleLoading] = useState(false);
881
884
  const [editDialogOpen, setEditDialogOpen] = useState(false);
885
+ const [envDialogOpen, setEnvDialogOpen] = useState(false);
882
886
  const [savedLaunchRequest, setSavedLaunchRequest] = useState<TeamLaunchRequest | null>(null);
883
887
  useEffect(() => {
884
888
  if (!editDialogOpen || !teamName) return;
@@ -1591,6 +1595,24 @@ export const TeamDetailView = ({
1591
1595
  openLaunchDialog('relaunch');
1592
1596
  }, [openLaunchDialog]);
1593
1597
 
1598
+ const handleStartCcConnectTeam = useCallback(() => {
1599
+ void (async () => {
1600
+ if (!data?.config.projectPath) {
1601
+ openLaunchDialog('launch');
1602
+ return;
1603
+ }
1604
+ await api.ccSettings.restart();
1605
+ await api.teams.launchTeam({
1606
+ teamName,
1607
+ cwd: data.config.projectPath,
1608
+ });
1609
+ window.setTimeout(() => {
1610
+ void fetchTeams();
1611
+ void selectTeam(teamName);
1612
+ }, 1500);
1613
+ })();
1614
+ }, [data?.config.projectPath, fetchTeams, openLaunchDialog, selectTeam, teamName]);
1615
+
1594
1616
  const handleLaunchDialogSubmit = useCallback(
1595
1617
  async (request: TeamLaunchRequest): Promise<void> => {
1596
1618
  await launchTeam(request);
@@ -1904,11 +1926,8 @@ export const TeamDetailView = ({
1904
1926
  pendingRepliesByMember,
1905
1927
  onPendingReplyChange: setPendingRepliesByMember,
1906
1928
  onMemberClick: handleSelectMember,
1907
- onTaskClick: handleOpenTask,
1908
- onCreateTaskFromMessage: handleCreateTaskFromMessage,
1909
1929
  onReplyToMessage: handleReplyToMessage,
1910
1930
  onRestartTeam: handleRestartTeam,
1911
- onTaskIdClick: handleTaskIdClick,
1912
1931
  inlineScrollContainerRef: contentRef,
1913
1932
  showPositionControls: false,
1914
1933
  }),
@@ -1917,12 +1936,9 @@ export const TeamDetailView = ({
1917
1936
  data?.config.leadSessionId,
1918
1937
  data?.isAlive,
1919
1938
  data?.tasks,
1920
- handleCreateTaskFromMessage,
1921
- handleOpenTask,
1922
1939
  handleReplyToMessage,
1923
1940
  handleRestartTeam,
1924
1941
  handleSelectMember,
1925
- handleTaskIdClick,
1926
1942
  pendingRepliesByMember,
1927
1943
  teamName,
1928
1944
  teamSessionIds,
@@ -2142,6 +2158,21 @@ export const TeamDetailView = ({
2142
2158
  </div>
2143
2159
  </div>
2144
2160
  <div className="flex shrink-0 items-center gap-1.5">
2161
+ {data.config.projectPath && (
2162
+ <Tooltip>
2163
+ <TooltipTrigger asChild>
2164
+ <Button
2165
+ variant="ghost"
2166
+ size="sm"
2167
+ className="h-7 gap-1 px-2 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text)]"
2168
+ onClick={() => setEnvDialogOpen(true)}
2169
+ >
2170
+ <Shield size={12} />
2171
+ </Button>
2172
+ </TooltipTrigger>
2173
+ <TooltipContent side="bottom">环境变量</TooltipContent>
2174
+ </Tooltip>
2175
+ )}
2145
2176
  <Tooltip>
2146
2177
  <TooltipTrigger asChild>
2147
2178
  <Button
@@ -2245,10 +2276,7 @@ export const TeamDetailView = ({
2245
2276
  </div>
2246
2277
 
2247
2278
  {!data.isAlive && !isTeamProvisioning ? (
2248
- <TeamOfflineStatusBanner
2249
- teamName={teamName}
2250
- onLaunch={() => openLaunchDialog('launch')}
2251
- />
2279
+ <TeamOfflineStatusBanner teamName={teamName} onLaunch={handleStartCcConnectTeam} />
2252
2280
  ) : null}
2253
2281
 
2254
2282
  <div ref={provisioningBannerRef}>
@@ -2276,16 +2304,12 @@ export const TeamDetailView = ({
2276
2304
  <TeamMemberListBridge
2277
2305
  teamName={teamName}
2278
2306
  members={membersWithLiveBranches}
2279
- memberTaskCounts={memberTaskCounts}
2280
- taskMap={taskMap}
2281
2307
  pendingRepliesByMember={pendingRepliesByMember}
2282
2308
  isTeamAlive={data.isAlive}
2283
2309
  isTeamProvisioning={isTeamProvisioning}
2284
2310
  launchParams={launchParams}
2285
2311
  onMemberClick={handleSelectMember}
2286
2312
  onSendMessage={handleSendMessageToMember}
2287
- onAssignTask={handleAssignTaskToMember}
2288
- onOpenTask={handleOpenTaskById}
2289
2313
  onRestartMember={handleRestartMember}
2290
2314
  onSkipMemberForLaunch={handleSkipMemberForLaunch}
2291
2315
  />
@@ -2296,6 +2320,11 @@ export const TeamDetailView = ({
2296
2320
  title="外部派单"
2297
2321
  icon={<Columns3 size={14} />}
2298
2322
  badge={filteredTasks.length}
2323
+ headerExtra={
2324
+ <span className="ml-1.5 rounded bg-amber-500/15 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-500">
2325
+ Beta
2326
+ </span>
2327
+ }
2299
2328
  defaultOpen
2300
2329
  forceOpen={kanbanSearch.trim().length > 0}
2301
2330
  action={
@@ -2412,7 +2441,6 @@ export const TeamDetailView = ({
2412
2441
  const task = data?.tasks.find((t) => t.id === taskId);
2413
2442
  await updateTaskStatus(teamName, taskId, 'pending');
2414
2443
 
2415
- // Notify assignee directly via inbox — they'll see it immediately
2416
2444
  if (task?.owner) {
2417
2445
  try {
2418
2446
  await api.teams.sendMessage(teamName, {
@@ -2425,7 +2453,6 @@ export const TeamDetailView = ({
2425
2453
  }
2426
2454
  }
2427
2455
 
2428
- // Also notify team lead so they can reassign/coordinate
2429
2456
  if (data?.isAlive) {
2430
2457
  try {
2431
2458
  const ownerSuffix = task?.owner
@@ -2556,16 +2583,7 @@ export const TeamDetailView = ({
2556
2583
  setReplyQuote(undefined);
2557
2584
  setSendDialogOpen(true);
2558
2585
  }}
2559
- onAssignTask={() => {
2560
- const name = selectedMember?.name ?? '';
2561
- closeSelectedMemberDialog();
2562
- openCreateTaskDialog('', '', name);
2563
- }}
2564
2586
  onRestartMember={handleRestartMember}
2565
- onTaskClick={(task) => {
2566
- closeSelectedMemberDialog();
2567
- setSelectedTask(task);
2568
- }}
2569
2587
  onUpdateRole={async (memberName, role) => {
2570
2588
  setUpdatingRoleLoading(true);
2571
2589
  try {
@@ -2598,22 +2616,6 @@ export const TeamDetailView = ({
2598
2616
  }}
2599
2617
  />
2600
2618
 
2601
- <CreateTaskDialog
2602
- open={createTaskDialog.open}
2603
- teamName={teamName}
2604
- members={activeMembers}
2605
- tasks={data.tasks}
2606
- isTeamAlive={data.isAlive && !isTeamProvisioning}
2607
- defaultSubject={createTaskDialog.defaultSubject}
2608
- defaultDescription={createTaskDialog.defaultDescription}
2609
- defaultOwner={createTaskDialog.defaultOwner}
2610
- defaultStartImmediately={createTaskDialog.defaultStartImmediately}
2611
- defaultChip={createTaskDialog.defaultChip}
2612
- onClose={closeCreateTaskDialog}
2613
- onSubmit={handleCreateTask}
2614
- submitting={creatingTask}
2615
- />
2616
-
2617
2619
  <EditTeamDialog
2618
2620
  open={editDialogOpen}
2619
2621
  teamName={teamName}
@@ -2670,6 +2672,18 @@ export const TeamDetailView = ({
2670
2672
  onRestartTeam={handleRestartTeamFromEdit}
2671
2673
  />
2672
2674
 
2675
+ <Dialog open={envDialogOpen} onOpenChange={setEnvDialogOpen}>
2676
+ <DialogContent className="max-h-[80vh] max-w-lg overflow-y-auto">
2677
+ <DialogHeader>
2678
+ <DialogTitle>项目环境变量</DialogTitle>
2679
+ <DialogDescription>
2680
+ 管理当前项目所需的环境变量,供 MCP 和 Skills 使用。
2681
+ </DialogDescription>
2682
+ </DialogHeader>
2683
+ <ProjectEnvPanel projectPath={data.config.projectPath ?? null} />
2684
+ </DialogContent>
2685
+ </Dialog>
2686
+
2673
2687
  <Dialog
2674
2688
  open={removeMemberConfirm !== null}
2675
2689
  onOpenChange={(open) => {
@@ -2806,63 +2820,6 @@ export const TeamDetailView = ({
2806
2820
  }}
2807
2821
  />
2808
2822
 
2809
- <TaskDetailDialog
2810
- open={selectedTask !== null}
2811
- task={selectedTask}
2812
- teamName={teamName}
2813
- kanbanTaskState={
2814
- selectedTask ? data?.kanbanState.tasks[selectedTask.id] : undefined
2815
- }
2816
- taskMap={taskMap}
2817
- members={activeMembers}
2818
- onClose={() => setSelectedTask(null)}
2819
- onScrollToTask={(taskId) => {
2820
- setSelectedTask(null);
2821
- const el = document.querySelector(`[data-task-id="${taskId}"]`);
2822
- if (el) {
2823
- el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
2824
- el.classList.remove('kanban-card-focus-pulse');
2825
- void (el as HTMLElement).offsetWidth;
2826
- el.classList.add('kanban-card-focus-pulse');
2827
- el.addEventListener(
2828
- 'animationend',
2829
- () => el.classList.remove('kanban-card-focus-pulse'),
2830
- { once: true }
2831
- );
2832
- }
2833
- }}
2834
- onOwnerChange={(taskId, owner) => {
2835
- void (async () => {
2836
- try {
2837
- await updateTaskOwner(teamName, taskId, owner);
2838
- } catch {
2839
- // error via store
2840
- }
2841
- })();
2842
- }}
2843
- onViewChanges={handleViewChangesForFile}
2844
- onOpenInEditor={(filePath) => {
2845
- const { revealFileInEditor } = useStore.getState();
2846
- revealFileInEditor(filePath);
2847
- }}
2848
- onDeleteTask={handleDeleteTask}
2849
- />
2850
-
2851
- <TrashDialog
2852
- open={trashOpen}
2853
- tasks={deletedTasks}
2854
- onClose={() => setTrashOpen(false)}
2855
- onRestore={(taskId) => {
2856
- void (async () => {
2857
- try {
2858
- await restoreTask(teamName, taskId);
2859
- } catch {
2860
- // error via store
2861
- }
2862
- })();
2863
- }}
2864
- />
2865
-
2866
2823
  <ChangeReviewDialog
2867
2824
  open={reviewDialogState.open}
2868
2825
  onOpenChange={(open) =>