@yancyyu/openhermit 1.6.27 → 1.6.29

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 (101) hide show
  1. package/README.md +7 -1
  2. package/bin/hermit.mjs +2 -2
  3. package/dist-renderer/assets/ProjectEditorOverlay-CQm6jUR1.js +52 -0
  4. package/dist-renderer/assets/{TeamGraphOverlay-DVq8rt6_.js → TeamGraphOverlay-h0WDfifv.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-ZbF0pKvS.js → _basePickBy-CgG_tjgX.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-BBLBOeXc.js → _baseUniq-DwPTU9lP.js} +1 -1
  7. package/dist-renderer/assets/{arc-wGaEgkCf.js → arc-7nIrGRzY.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BpMkdC35.js → architectureDiagram-VXUJARFQ-BYhA6Ev2.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-C8Z1xhG4.js → blockDiagram-VD42YOAC-BVpZUGDg.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CJmlw9LA.js → c4Diagram-YG6GDRKO-DsdreMQ9.js} +1 -1
  11. package/dist-renderer/assets/channel-C0SqeFU7.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-CHPHiRPP.js → chunk-4BX2VUAB-CcoAs7Jd.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-DyVohOQb.js → chunk-55IACEB6-CGGAOoXd.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-p5bffh_R.js → chunk-B4BG7PRW-FhpTEPvD.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-BnfGPSUu.js → chunk-DI55MBZ5-DoYySbm1.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-B6SCKseX.js → chunk-FMBD7UC4-e9l2tGHG.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-L12RvLBR.js → chunk-QN33PNHL-DeiXVTCy.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-DeH1Kxge.js → chunk-QZHKN3VN-DC2UJLJM.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-BWnjzSlI.js → chunk-TZMSLE5B-BHFD9eZI.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +1 -0
  22. package/dist-renderer/assets/clone-Dm-k63Yr.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BtzoT5fu.js → cose-bilkent-S5V4N54A-BdybQraU.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-CBBvuoUD.js → dagre-6UL2VRFP-DdF3pwM3.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-Be9BAKws.js → diagram-PSM6KHXK-B9Ldd3nh.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-BDS4PI_i.js → diagram-QEK2KX5R-XEqkrbpu.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-2Rameaq7.js → diagram-S2PKOQOG-CipwtY59.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CSIzCEZD.js → erDiagram-Q2GNP2WA-BB-2ISGo.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-ForEIVM5.js → flowDiagram-NV44I4VS-B8XmJ0u2.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-BJrli_xr.js → ganttDiagram-JELNMOA3-D-8XglBb.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-C_4GuLno.js → gitGraphDiagram-V2S2FVAM-DL4ChakD.js} +1 -1
  32. package/dist-renderer/assets/{graph-B1EAT_gw.js → graph-BiFNoBjP.js} +1 -1
  33. package/dist-renderer/assets/{index-eKRmS5kI.js → index-6m1ZAymG.js} +1 -1
  34. package/dist-renderer/assets/index-BhellmRb.css +1 -0
  35. package/dist-renderer/assets/{index-DYdseEwc.js → index-BowUl0Jb.js} +518 -514
  36. package/dist-renderer/assets/{index-DR602dwJ.js → index-Dp3kJTEe.js} +1 -1
  37. package/dist-renderer/assets/{index-Dwr5wu5x.js → index-TOpt_T7A.js} +1 -1
  38. package/dist-renderer/assets/{index-DOA_jbYb.js → index-qNBNjW4K.js} +1 -1
  39. package/dist-renderer/assets/{index-k4tnOFC5.js → index-vAykq1H1.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DjI0uaMz.js → infoDiagram-HS3SLOUP-DRIBfHDi.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-jQ6Thae-.js → journeyDiagram-XKPGCS4Q-BOMiigU4.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CKw6InbL.js → kanban-definition-3W4ZIXB7-DDxeyjod.js} +1 -1
  43. package/dist-renderer/assets/{layout-Dad20y3V.js → layout-DNANbrI4.js} +1 -1
  44. package/dist-renderer/assets/{linear-vMgo_2Cv.js → linear-DxEJi1yT.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-DYp6YoHL.js → mindmap-definition-VGOIOE7T-nBfGriW8.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BytBecG9.js → pieDiagram-ADFJNKIX-Din5j6sV.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-RUaspLsc.js → quadrantDiagram-AYHSOK5B-DMVK2BEQ.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-rR2B1Use.js → requirementDiagram-UZGBJVZJ-6SC94Gg_.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BJi5qYhq.js → sankeyDiagram-TZEHDZUN-CD2gghhu.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BM-wggUb.js → sequenceDiagram-WL72ISMW-BnhkN7nZ.js} +1 -1
  51. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BqmcVjnj.js → stateDiagram-FKZM4ZOC-Bn8XdYX-.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-By3JDVbB.js → stateDiagram-v2-4FDKWEC3-1b6sI1_g.js} +1 -1
  53. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-szH0GUyk.js → timeline-definition-IT6M3QCI-CNs3RPoa.js} +1 -1
  54. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +162 -0
  55. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-dwDpvw0w.js → xychartDiagram-PRI3JC2R-B8o5J2f3.js} +1 -1
  56. package/dist-renderer/index.html +2 -2
  57. package/package.json +1 -1
  58. package/src/main/server.ts +800 -163
  59. package/src/main/services/session-intelligence/SessionUsageParser.ts +446 -0
  60. package/src/main/services/session-intelligence/UsageTelemetryService.ts +252 -0
  61. package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
  62. package/src/main/services/teams-mvp/TaskDispatchService.ts +880 -95
  63. package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
  64. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
  65. package/src/main/services/teams-mvp/index.ts +3 -0
  66. package/src/renderer/App.tsx +5 -0
  67. package/src/renderer/api/httpClient.ts +67 -0
  68. package/src/renderer/components/dashboard/DashboardView.tsx +6 -105
  69. package/src/renderer/components/layout/PaneContent.tsx +2 -0
  70. package/src/renderer/components/layout/SortableTab.tsx +1 -0
  71. package/src/renderer/components/layout/TabBarActions.tsx +12 -12
  72. package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
  73. package/src/renderer/components/settings/SettingsTabs.tsx +2 -2
  74. package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
  75. package/src/renderer/components/settings/sections/TaskBusSection.tsx +511 -81
  76. package/src/renderer/components/tasks/TasksView.tsx +343 -0
  77. package/src/renderer/components/team/TeamDetailView.tsx +20 -98
  78. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
  79. package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
  80. package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
  81. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
  82. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
  83. package/src/renderer/components/team/kanban/KanbanBoard.tsx +5 -1
  84. package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
  85. package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
  86. package/src/renderer/components/team/messages/MessagesPanel.tsx +72 -2
  87. package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
  88. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
  89. package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
  90. package/src/renderer/store/slices/scheduleSlice.ts +21 -0
  91. package/src/renderer/store/slices/teamSlice.ts +59 -23
  92. package/src/renderer/types/tabs.ts +1 -0
  93. package/src/shared/types/api.ts +29 -0
  94. package/src/shared/types/team.ts +109 -1
  95. package/dist-renderer/assets/ProjectEditorOverlay-BBwYdXPv.js +0 -57
  96. package/dist-renderer/assets/channel-DJUrwVrK.js +0 -1
  97. package/dist-renderer/assets/classDiagram-2ON5EDUG-blc3DrH7.js +0 -1
  98. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-blc3DrH7.js +0 -1
  99. package/dist-renderer/assets/clone-BftjWakJ.js +0 -1
  100. package/dist-renderer/assets/index-CWpFqEvz.css +0 -1
  101. package/dist-renderer/assets/treemap-GDKQZRPO-BCMlh-Ex.js +0 -162
@@ -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
+ };
@@ -68,6 +68,7 @@ import {
68
68
  Terminal,
69
69
  Trash2,
70
70
  Loader2,
71
+ MessageSquare,
71
72
  Users,
72
73
  } from 'lucide-react';
73
74
  import { useShallow } from 'zustand/react/shallow';
@@ -1591,6 +1592,24 @@ export const TeamDetailView = ({
1591
1592
  openLaunchDialog('relaunch');
1592
1593
  }, [openLaunchDialog]);
1593
1594
 
1595
+ const handleStartCcConnectTeam = useCallback(() => {
1596
+ void (async () => {
1597
+ if (!data?.config.projectPath) {
1598
+ openLaunchDialog('launch');
1599
+ return;
1600
+ }
1601
+ await api.ccSettings.restart();
1602
+ await api.teams.launchTeam({
1603
+ teamName,
1604
+ cwd: data.config.projectPath,
1605
+ });
1606
+ window.setTimeout(() => {
1607
+ void fetchTeams();
1608
+ void selectTeam(teamName);
1609
+ }, 1500);
1610
+ })();
1611
+ }, [data?.config.projectPath, fetchTeams, openLaunchDialog, selectTeam, teamName]);
1612
+
1594
1613
  const handleLaunchDialogSubmit = useCallback(
1595
1614
  async (request: TeamLaunchRequest): Promise<void> => {
1596
1615
  await launchTeam(request);
@@ -1904,11 +1923,8 @@ export const TeamDetailView = ({
1904
1923
  pendingRepliesByMember,
1905
1924
  onPendingReplyChange: setPendingRepliesByMember,
1906
1925
  onMemberClick: handleSelectMember,
1907
- onTaskClick: handleOpenTask,
1908
- onCreateTaskFromMessage: handleCreateTaskFromMessage,
1909
1926
  onReplyToMessage: handleReplyToMessage,
1910
1927
  onRestartTeam: handleRestartTeam,
1911
- onTaskIdClick: handleTaskIdClick,
1912
1928
  inlineScrollContainerRef: contentRef,
1913
1929
  showPositionControls: false,
1914
1930
  }),
@@ -1917,12 +1933,9 @@ export const TeamDetailView = ({
1917
1933
  data?.config.leadSessionId,
1918
1934
  data?.isAlive,
1919
1935
  data?.tasks,
1920
- handleCreateTaskFromMessage,
1921
- handleOpenTask,
1922
1936
  handleReplyToMessage,
1923
1937
  handleRestartTeam,
1924
1938
  handleSelectMember,
1925
- handleTaskIdClick,
1926
1939
  pendingRepliesByMember,
1927
1940
  teamName,
1928
1941
  teamSessionIds,
@@ -2245,10 +2258,7 @@ export const TeamDetailView = ({
2245
2258
  </div>
2246
2259
 
2247
2260
  {!data.isAlive && !isTeamProvisioning ? (
2248
- <TeamOfflineStatusBanner
2249
- teamName={teamName}
2250
- onLaunch={() => openLaunchDialog('launch')}
2251
- />
2261
+ <TeamOfflineStatusBanner teamName={teamName} onLaunch={handleStartCcConnectTeam} />
2252
2262
  ) : null}
2253
2263
 
2254
2264
  <div ref={provisioningBannerRef}>
@@ -2276,16 +2286,12 @@ export const TeamDetailView = ({
2276
2286
  <TeamMemberListBridge
2277
2287
  teamName={teamName}
2278
2288
  members={membersWithLiveBranches}
2279
- memberTaskCounts={memberTaskCounts}
2280
- taskMap={taskMap}
2281
2289
  pendingRepliesByMember={pendingRepliesByMember}
2282
2290
  isTeamAlive={data.isAlive}
2283
2291
  isTeamProvisioning={isTeamProvisioning}
2284
2292
  launchParams={launchParams}
2285
2293
  onMemberClick={handleSelectMember}
2286
2294
  onSendMessage={handleSendMessageToMember}
2287
- onAssignTask={handleAssignTaskToMember}
2288
- onOpenTask={handleOpenTaskById}
2289
2295
  onRestartMember={handleRestartMember}
2290
2296
  onSkipMemberForLaunch={handleSkipMemberForLaunch}
2291
2297
  />
@@ -2412,7 +2418,6 @@ export const TeamDetailView = ({
2412
2418
  const task = data?.tasks.find((t) => t.id === taskId);
2413
2419
  await updateTaskStatus(teamName, taskId, 'pending');
2414
2420
 
2415
- // Notify assignee directly via inbox — they'll see it immediately
2416
2421
  if (task?.owner) {
2417
2422
  try {
2418
2423
  await api.teams.sendMessage(teamName, {
@@ -2425,7 +2430,6 @@ export const TeamDetailView = ({
2425
2430
  }
2426
2431
  }
2427
2432
 
2428
- // Also notify team lead so they can reassign/coordinate
2429
2433
  if (data?.isAlive) {
2430
2434
  try {
2431
2435
  const ownerSuffix = task?.owner
@@ -2556,16 +2560,7 @@ export const TeamDetailView = ({
2556
2560
  setReplyQuote(undefined);
2557
2561
  setSendDialogOpen(true);
2558
2562
  }}
2559
- onAssignTask={() => {
2560
- const name = selectedMember?.name ?? '';
2561
- closeSelectedMemberDialog();
2562
- openCreateTaskDialog('', '', name);
2563
- }}
2564
2563
  onRestartMember={handleRestartMember}
2565
- onTaskClick={(task) => {
2566
- closeSelectedMemberDialog();
2567
- setSelectedTask(task);
2568
- }}
2569
2564
  onUpdateRole={async (memberName, role) => {
2570
2565
  setUpdatingRoleLoading(true);
2571
2566
  try {
@@ -2598,22 +2593,6 @@ export const TeamDetailView = ({
2598
2593
  }}
2599
2594
  />
2600
2595
 
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
2596
  <EditTeamDialog
2618
2597
  open={editDialogOpen}
2619
2598
  teamName={teamName}
@@ -2806,63 +2785,6 @@ export const TeamDetailView = ({
2806
2785
  }}
2807
2786
  />
2808
2787
 
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
2788
  <ChangeReviewDialog
2867
2789
  open={reviewDialogState.open}
2868
2790
  onOpenChange={(open) =>
@@ -2058,7 +2058,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
2058
2058
  ) : effectiveTeamName ? (
2059
2059
  `为团队“${effectiveTeamName}”创建自动运行计划`
2060
2060
  ) : (
2061
- '创建团队任务自动执行计划'
2061
+ '创建团队自动运行计划'
2062
2062
  );
2063
2063
 
2064
2064
  const submitLabel = isLaunchMode
@@ -16,7 +16,6 @@ import {
16
16
  FilePlus,
17
17
  FolderOpen,
18
18
  FolderPlus,
19
- ListTodo,
20
19
  MessageSquare,
21
20
  Pencil,
22
21
  Trash2,
@@ -39,8 +38,6 @@ interface EditorContextMenuProps {
39
38
  onNewFolder: (parentDir: string) => void;
40
39
  onDelete: (path: string) => void;
41
40
  onRename: (path: string) => void;
42
- /** Trigger "Create Task" with a file mention (files only, not directories) */
43
- onCreateTask?: (filePath: string) => void;
44
41
  /** Trigger "Write Teammate" with a file mention (files only, not directories) */
45
42
  onSendMessage?: (filePath: string) => void;
46
43
  }
@@ -56,7 +53,6 @@ export const EditorContextMenu = ({
56
53
  onNewFolder,
57
54
  onDelete,
58
55
  onRename,
59
- onCreateTask,
60
56
  onSendMessage,
61
57
  }: EditorContextMenuProps): React.ReactElement => {
62
58
  const [target, setTarget] = useState<TargetEntry | null>(null);
@@ -183,27 +179,16 @@ export const EditorContextMenu = ({
183
179
  )}
184
180
 
185
181
  {/* Team actions — file only */}
186
- {target && !target.isDir && (onCreateTask || onSendMessage) && (
182
+ {target && !target.isDir && onSendMessage && (
187
183
  <>
188
184
  <ContextMenu.Separator className="my-1 h-px bg-border" />
189
- {onSendMessage && (
190
- <ContextMenu.Item
191
- className="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-xs text-text outline-none hover:bg-surface-raised focus:bg-surface-raised"
192
- onSelect={() => onSendMessage(target.path)}
193
- >
194
- <MessageSquare className="size-3.5 text-text-muted" />
195
- Write Teammate
196
- </ContextMenu.Item>
197
- )}
198
- {onCreateTask && (
199
- <ContextMenu.Item
200
- className="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-xs text-text outline-none hover:bg-surface-raised focus:bg-surface-raised"
201
- onSelect={() => onCreateTask(target.path)}
202
- >
203
- <ListTodo className="size-3.5 text-text-muted" />
204
- Create Task
205
- </ContextMenu.Item>
206
- )}
185
+ <ContextMenu.Item
186
+ className="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-xs text-text outline-none hover:bg-surface-raised focus:bg-surface-raised"
187
+ onSelect={() => onSendMessage(target.path)}
188
+ >
189
+ <MessageSquare className="size-3.5 text-text-muted" />
190
+ Write Teammate
191
+ </ContextMenu.Item>
207
192
  </>
208
193
  )}
209
194
  </ContextMenu.Content>
@@ -54,8 +54,6 @@ import type { FileTreeEntry, GitFileStatusType } from '@shared/types/editor';
54
54
  interface EditorFileTreeProps {
55
55
  selectedFilePath: string | null;
56
56
  onFileSelect: (filePath: string) => void;
57
- /** Trigger "Create Task" with a file mention from context menu */
58
- onCreateTask?: (filePath: string) => void;
59
57
  /** Trigger "Write Teammate" with a file mention from context menu */
60
58
  onSendMessage?: (filePath: string) => void;
61
59
  }
@@ -91,7 +89,6 @@ let fileTreeRenderCount = 0;
91
89
  export const EditorFileTree = ({
92
90
  selectedFilePath,
93
91
  onFileSelect,
94
- onCreateTask,
95
92
  onSendMessage,
96
93
  }: EditorFileTreeProps): React.ReactElement => {
97
94
  fileTreeRenderCount++;
@@ -452,7 +449,6 @@ export const EditorFileTree = ({
452
449
  onNewFolder={handleNewFolder}
453
450
  onDelete={handleDelete}
454
451
  onRename={handleRename}
455
- onCreateTask={onCreateTask}
456
452
  onSendMessage={onSendMessage}
457
453
  >
458
454
  <DndContext
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { Button } from '@renderer/components/ui/button';
9
9
  import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
10
- import { ListTodo, MessageSquare } from 'lucide-react';
10
+ import { MessageSquare } from 'lucide-react';
11
11
 
12
12
  import type { EditorSelectionInfo } from '@shared/types/editor';
13
13
 
@@ -20,7 +20,6 @@ interface EditorSelectionMenuProps {
20
20
  /** Bounding rect of the editor content container (for viewport → container conversion) */
21
21
  containerRect: DOMRect;
22
22
  onSendMessage: () => void;
23
- onCreateTask: () => void;
24
23
  }
25
24
 
26
25
  // =============================================================================
@@ -39,7 +38,6 @@ export const EditorSelectionMenu = ({
39
38
  info,
40
39
  containerRect,
41
40
  onSendMessage,
42
- onCreateTask,
43
41
  }: EditorSelectionMenuProps): React.ReactElement | null => {
44
42
  if (!info.text.trim()) return null;
45
43
 
@@ -71,11 +69,6 @@ export const EditorSelectionMenu = ({
71
69
  label="Write Teammate"
72
70
  onClick={onSendMessage}
73
71
  />
74
- <MenuButton
75
- icon={<ListTodo className="size-3.5" />}
76
- label="Create Task"
77
- onClick={onCreateTask}
78
- />
79
72
  </div>
80
73
  );
81
74
  };