@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.
- package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +907 -184
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +128 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +55 -98
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +67 -25
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +58 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/types/team.ts +104 -1
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
- package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
- package/dist-renderer/assets/index-BIOJremZ.css +0 -1
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- 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) =>
|