@yancyyu/openhermit 1.6.28 → 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.
- package/dist-renderer/assets/ProjectEditorOverlay-CQm6jUR1.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-h0WDfifv.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CgG_tjgX.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-DwPTU9lP.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-7nIrGRzY.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-BYhA6Ev2.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-BVpZUGDg.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-DsdreMQ9.js} +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-CcoAs7Jd.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-CGGAOoXd.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-FhpTEPvD.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DoYySbm1.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-e9l2tGHG.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DeiXVTCy.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-DC2UJLJM.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-BHFD9eZI.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +1 -0
- package/dist-renderer/assets/clone-Dm-k63Yr.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-BdybQraU.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-DdF3pwM3.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-B9Ldd3nh.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-XEqkrbpu.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-CipwtY59.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-BB-2ISGo.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B8XmJ0u2.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-D-8XglBb.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DL4ChakD.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-BiFNoBjP.js} +1 -1
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-6m1ZAymG.js} +1 -1
- package/dist-renderer/assets/index-BhellmRb.css +1 -0
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BowUl0Jb.js} +540 -536
- package/dist-renderer/assets/{index-CtlzGepK.js → index-Dp3kJTEe.js} +1 -1
- package/dist-renderer/assets/{index-COZPUWJW.js → index-TOpt_T7A.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-qNBNjW4K.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-vAykq1H1.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-DRIBfHDi.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-BOMiigU4.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-DDxeyjod.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-DNANbrI4.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-DxEJi1yT.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-nBfGriW8.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-Din5j6sV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-DMVK2BEQ.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-6SC94Gg_.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-CD2gghhu.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-BnhkN7nZ.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-Bn8XdYX-.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-1b6sI1_g.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-CNs3RPoa.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-B8o5J2f3.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/server.ts +699 -179
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +880 -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/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +67 -0
- 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/TaskBusSection.tsx +129 -79
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/TeamDetailView.tsx +20 -98
- 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 +5 -1
- 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 +72 -2
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +59 -23
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/team.ts +104 -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/dist-renderer/assets/treemap-GDKQZRPO-CVd5GNDw.js +0 -162
|
@@ -211,6 +211,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
211
211
|
messagesState,
|
|
212
212
|
loadOlderTeamMessages,
|
|
213
213
|
refreshTeamMessagesHead,
|
|
214
|
+
addOptimisticTeamMessage,
|
|
214
215
|
} = useStore(
|
|
215
216
|
useShallow((s) => ({
|
|
216
217
|
sendTeamMessage: s.sendTeamMessage,
|
|
@@ -226,6 +227,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
226
227
|
messagesState: teamName ? s.teamMessagesByName[teamName] : undefined,
|
|
227
228
|
loadOlderTeamMessages: s.loadOlderTeamMessages,
|
|
228
229
|
refreshTeamMessagesHead: s.refreshTeamMessagesHead,
|
|
230
|
+
addOptimisticTeamMessage: s.addOptimisticTeamMessage,
|
|
229
231
|
}))
|
|
230
232
|
);
|
|
231
233
|
const bootstrapHeadRefreshAttemptedForTeamRef = useRef<string | null>(null);
|
|
@@ -534,7 +536,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
534
536
|
const sessionScopedMessages = useMemo(() => {
|
|
535
537
|
const newestFirst = (items: InboxMessage[]) =>
|
|
536
538
|
[...items].sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
537
|
-
if (!selectedSessionKey) return
|
|
539
|
+
if (!selectedSessionKey) return newestFirst(effectiveMessages);
|
|
538
540
|
if (selectedSession && !selectedIsHermitLocalSession) {
|
|
539
541
|
if (!selectedSessionDetail) {
|
|
540
542
|
return [];
|
|
@@ -731,12 +733,77 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
731
733
|
fromMember: 'user',
|
|
732
734
|
toTeam,
|
|
733
735
|
text,
|
|
736
|
+
sessionKey:
|
|
737
|
+
selectedSessionKey && selectedSessionKey !== '__unassigned__'
|
|
738
|
+
? selectedSessionKey
|
|
739
|
+
: undefined,
|
|
734
740
|
taskRefs,
|
|
735
741
|
actionMode,
|
|
736
742
|
summary,
|
|
737
743
|
});
|
|
738
744
|
},
|
|
739
|
-
[teamName, sendCrossTeamMessage]
|
|
745
|
+
[teamName, selectedSessionKey, sendCrossTeamMessage]
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
const handleDispatchTaskToTeam = useCallback(
|
|
749
|
+
async (toTeam: string, subject: string, description: string) => {
|
|
750
|
+
const now = Date.now();
|
|
751
|
+
const optimisticMessageId = `optimistic-cross-team-${now}`;
|
|
752
|
+
addOptimisticTeamMessage(teamName, {
|
|
753
|
+
from: 'user',
|
|
754
|
+
to: toTeam,
|
|
755
|
+
text: `@${toTeam} ${subject}`,
|
|
756
|
+
timestamp: new Date(now).toISOString(),
|
|
757
|
+
read: true,
|
|
758
|
+
messageId: optimisticMessageId,
|
|
759
|
+
source: 'cross_team_sent',
|
|
760
|
+
session:
|
|
761
|
+
selectedSessionKey && selectedSessionKey !== '__unassigned__'
|
|
762
|
+
? { key: selectedSessionKey }
|
|
763
|
+
: undefined,
|
|
764
|
+
});
|
|
765
|
+
try {
|
|
766
|
+
await sendCrossTeamMessage({
|
|
767
|
+
fromTeam: teamName,
|
|
768
|
+
fromMember: 'user',
|
|
769
|
+
toTeam,
|
|
770
|
+
text: description,
|
|
771
|
+
messageId: optimisticMessageId,
|
|
772
|
+
});
|
|
773
|
+
} catch (error) {
|
|
774
|
+
const rawMessage = error instanceof Error ? error.message : '跨团队任务派发失败';
|
|
775
|
+
const readableMessage = rawMessage.includes('Redis not configured')
|
|
776
|
+
? '无法派发给其他团队:Redis 未配置或未连接。请先在设置里开启团队总线并配置 Redis。'
|
|
777
|
+
: rawMessage.includes('Distributed collaboration is not enabled')
|
|
778
|
+
? '无法派发给其他团队:团队总线/分布式团队协作未开启。请先在设置里开启。'
|
|
779
|
+
: `无法派发给 ${toTeam}:${rawMessage}`;
|
|
780
|
+
addOptimisticTeamMessage(teamName, {
|
|
781
|
+
from: 'system',
|
|
782
|
+
to: 'user',
|
|
783
|
+
text: readableMessage,
|
|
784
|
+
timestamp: new Date(Date.now()).toISOString(),
|
|
785
|
+
read: true,
|
|
786
|
+
messageId: `optimistic-cross-team-error-${Date.now()}`,
|
|
787
|
+
source: 'system_notification',
|
|
788
|
+
});
|
|
789
|
+
window.dispatchEvent(new CustomEvent('collab:refresh'));
|
|
790
|
+
await refreshTeamMessagesHead(teamName);
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
window.dispatchEvent(new CustomEvent('collab:refresh'));
|
|
794
|
+
await refreshTeamMessagesHead(teamName);
|
|
795
|
+
window.setTimeout(() => {
|
|
796
|
+
void refreshTeamMessagesHead(teamName);
|
|
797
|
+
}, 300);
|
|
798
|
+
return true;
|
|
799
|
+
},
|
|
800
|
+
[
|
|
801
|
+
addOptimisticTeamMessage,
|
|
802
|
+
teamName,
|
|
803
|
+
refreshTeamMessagesHead,
|
|
804
|
+
selectedSessionKey,
|
|
805
|
+
sendCrossTeamMessage,
|
|
806
|
+
]
|
|
740
807
|
);
|
|
741
808
|
|
|
742
809
|
const moveToInline = useCallback(() => {
|
|
@@ -869,6 +936,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
869
936
|
onSessionChange={setSelectedSessionKey}
|
|
870
937
|
textareaRef={composerTextareaRef}
|
|
871
938
|
onSend={handleSend}
|
|
939
|
+
onDispatchTask={handleDispatchTaskToTeam}
|
|
872
940
|
/>
|
|
873
941
|
<StatusBlock
|
|
874
942
|
members={members}
|
|
@@ -1065,6 +1133,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
1065
1133
|
onSessionChange={setSelectedSessionKey}
|
|
1066
1134
|
textareaRef={composerTextareaRef}
|
|
1067
1135
|
onSend={handleSend}
|
|
1136
|
+
onDispatchTask={handleDispatchTaskToTeam}
|
|
1068
1137
|
/>
|
|
1069
1138
|
<StatusBlock
|
|
1070
1139
|
members={members}
|
|
@@ -1352,6 +1421,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
1352
1421
|
onSessionChange={setSelectedSessionKey}
|
|
1353
1422
|
textareaRef={composerTextareaRef}
|
|
1354
1423
|
onSend={handleSend}
|
|
1424
|
+
onDispatchTask={handleDispatchTaskToTeam}
|
|
1355
1425
|
/>
|
|
1356
1426
|
</div>
|
|
1357
1427
|
</div>
|
|
@@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { computePendingCrossTeamReplies } from '@renderer/utils/crossTeamPendingReplies';
|
|
4
4
|
import { ChevronRight } from 'lucide-react';
|
|
5
5
|
|
|
6
|
-
import { ActiveTasksBlock } from '../activity/ActiveTasksBlock';
|
|
7
6
|
import { PendingRepliesBlock } from '../activity/PendingRepliesBlock';
|
|
8
7
|
|
|
9
8
|
import type { InboxMessage, ResolvedTeamMember, TeamTaskWithKanban } from '@shared/types';
|
|
@@ -29,13 +28,10 @@ interface StatusBlockProps {
|
|
|
29
28
|
*/
|
|
30
29
|
export const StatusBlock = ({
|
|
31
30
|
members,
|
|
32
|
-
tasks,
|
|
33
31
|
messages,
|
|
34
32
|
pendingRepliesByMember,
|
|
35
|
-
position,
|
|
36
33
|
layout = 'overlay',
|
|
37
34
|
onMemberClick,
|
|
38
|
-
onTaskClick,
|
|
39
35
|
}: StatusBlockProps): React.JSX.Element | null => {
|
|
40
36
|
const [collapsed, setCollapsed] = useState(false);
|
|
41
37
|
const [nowMs, setNowMs] = useState(() => Date.now());
|
|
@@ -50,21 +46,11 @@ export const StatusBlock = ({
|
|
|
50
46
|
);
|
|
51
47
|
return hasMemberPendingReplies || pendingCrossTeamReplies.length > 0;
|
|
52
48
|
}, [members, pendingRepliesByMember, pendingCrossTeamReplies.length]);
|
|
53
|
-
const hasActiveTasks = useMemo(() => {
|
|
54
|
-
const tMap = new Map(tasks.map((t) => [t.id, t]));
|
|
55
|
-
return members.some((m) => {
|
|
56
|
-
if (!m.currentTaskId) return false;
|
|
57
|
-
const task = tMap.get(m.currentTaskId);
|
|
58
|
-
if (task && (task.reviewState === 'approved' || task.status === 'completed')) return false;
|
|
59
|
-
return true;
|
|
60
|
-
});
|
|
61
|
-
}, [members, tasks]);
|
|
62
49
|
|
|
63
50
|
/** Whether the Status block has any visible items. */
|
|
64
51
|
const hasItems = useMemo(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}, [hasActiveTasks, hasPendingReplies]);
|
|
52
|
+
return hasPendingReplies;
|
|
53
|
+
}, [hasPendingReplies]);
|
|
68
54
|
|
|
69
55
|
// Only run the 1-second timer when the block actually has content to show.
|
|
70
56
|
useEffect(() => {
|
|
@@ -111,14 +97,6 @@ export const StatusBlock = ({
|
|
|
111
97
|
onMemberClick={onMemberClick}
|
|
112
98
|
/>
|
|
113
99
|
) : null}
|
|
114
|
-
<ActiveTasksBlock
|
|
115
|
-
members={members}
|
|
116
|
-
tasks={tasks}
|
|
117
|
-
defaultCollapsed={position === 'sidebar'}
|
|
118
|
-
headerRight={!hasPendingReplies ? flowInlineToggle : undefined}
|
|
119
|
-
onMemberClick={onMemberClick}
|
|
120
|
-
onTaskClick={onTaskClick}
|
|
121
|
-
/>
|
|
122
100
|
</div>
|
|
123
101
|
)}
|
|
124
102
|
</>
|
|
@@ -8,7 +8,7 @@ export const ScheduleEmptyState = (): React.JSX.Element => (
|
|
|
8
8
|
<div className="space-y-1">
|
|
9
9
|
<p className="text-xs font-medium text-[var(--color-text-secondary)]">暂无定时计划</p>
|
|
10
10
|
<p className="text-[11px] text-[var(--color-text-muted)]">
|
|
11
|
-
创建计划后,可按 cron
|
|
11
|
+
创建计划后,可按 cron 时间自动运行团队。
|
|
12
12
|
</p>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
@@ -1069,7 +1069,6 @@ export const MentionableTextarea = React.forwardRef<HTMLTextAreaElement, Mention
|
|
|
1069
1069
|
const rotatingTips = React.useMemo(
|
|
1070
1070
|
() => [
|
|
1071
1071
|
'Tips:输入 @ 可提及成员、团队或文件,输入 # 可引用任务。',
|
|
1072
|
-
'Tips:输入“创建任务”可以把事项加入任务看板。',
|
|
1073
1072
|
'Tips:不要把所有工作都堆给团队负责人,可以让负责人分配给合适的成员。',
|
|
1074
1073
|
...extraTips,
|
|
1075
1074
|
],
|
|
@@ -39,6 +39,9 @@ export interface ScheduleSlice {
|
|
|
39
39
|
|
|
40
40
|
/** Open a standalone Schedules tab (or focus existing) */
|
|
41
41
|
openSchedulesTab(): void;
|
|
42
|
+
|
|
43
|
+
/** Open the standalone task overview tab. */
|
|
44
|
+
openTasksTab(): void;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
// =============================================================================
|
|
@@ -246,4 +249,22 @@ export const createScheduleSlice: StateCreator<AppState, [], [], ScheduleSlice>
|
|
|
246
249
|
// Ensure schedules are fresh when opening
|
|
247
250
|
void get().fetchSchedules();
|
|
248
251
|
},
|
|
252
|
+
|
|
253
|
+
openTasksTab: () => {
|
|
254
|
+
const state = get();
|
|
255
|
+
const focusedPane = state.paneLayout.panes.find((p) => p.id === state.paneLayout.focusedPaneId);
|
|
256
|
+
const existingTab = focusedPane?.tabs.find((tab) => tab.type === 'tasks');
|
|
257
|
+
if (existingTab) {
|
|
258
|
+
state.setActiveTab(existingTab.id);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
state.openTab({
|
|
263
|
+
type: 'tasks',
|
|
264
|
+
label: '任务',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
void get().fetchAllTasks();
|
|
268
|
+
void get().fetchSchedules();
|
|
269
|
+
},
|
|
249
270
|
});
|
|
@@ -2067,6 +2067,7 @@ export interface TeamSlice {
|
|
|
2067
2067
|
delayMs?: number
|
|
2068
2068
|
) => void;
|
|
2069
2069
|
sendTeamMessage: (teamName: string, request: SendMessageRequest) => Promise<SendMessageResult>;
|
|
2070
|
+
addOptimisticTeamMessage: (teamName: string, message: InboxMessage) => void;
|
|
2070
2071
|
crossTeamTargets: {
|
|
2071
2072
|
teamName: string;
|
|
2072
2073
|
displayName: string;
|
|
@@ -3988,6 +3989,39 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|
|
3988
3989
|
},
|
|
3989
3990
|
|
|
3990
3991
|
sendTeamMessage: async (teamName: string, request: SendMessageRequest) => {
|
|
3992
|
+
const optimisticMessageId =
|
|
3993
|
+
request.messageId ?? `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3994
|
+
const optimisticTimestamp = request.timestamp ?? nowIso();
|
|
3995
|
+
const requestWithMessageId: SendMessageRequest = {
|
|
3996
|
+
...request,
|
|
3997
|
+
messageId: optimisticMessageId,
|
|
3998
|
+
timestamp: optimisticTimestamp,
|
|
3999
|
+
};
|
|
4000
|
+
const optimisticMessage: InboxMessage = {
|
|
4001
|
+
from: requestWithMessageId.from ?? 'user',
|
|
4002
|
+
to: requestWithMessageId.to ?? requestWithMessageId.member,
|
|
4003
|
+
text: requestWithMessageId.text,
|
|
4004
|
+
timestamp: optimisticTimestamp,
|
|
4005
|
+
read: true,
|
|
4006
|
+
taskRefs: requestWithMessageId.taskRefs?.length ? requestWithMessageId.taskRefs : undefined,
|
|
4007
|
+
actionMode: requestWithMessageId.actionMode,
|
|
4008
|
+
summary: requestWithMessageId.summary,
|
|
4009
|
+
color: requestWithMessageId.color,
|
|
4010
|
+
messageId: optimisticMessageId,
|
|
4011
|
+
relayOfMessageId: requestWithMessageId.relayOfMessageId,
|
|
4012
|
+
source: requestWithMessageId.source ?? 'user_sent',
|
|
4013
|
+
attachments: requestWithMessageId.attachments?.length
|
|
4014
|
+
? requestWithMessageId.attachments
|
|
4015
|
+
: undefined,
|
|
4016
|
+
leadSessionId: requestWithMessageId.leadSessionId,
|
|
4017
|
+
conversationId: requestWithMessageId.conversationId,
|
|
4018
|
+
replyToConversationId: requestWithMessageId.replyToConversationId,
|
|
4019
|
+
toolSummary: requestWithMessageId.toolSummary,
|
|
4020
|
+
toolCalls: requestWithMessageId.toolCalls,
|
|
4021
|
+
messageKind: requestWithMessageId.messageKind,
|
|
4022
|
+
slashCommand: requestWithMessageId.slashCommand,
|
|
4023
|
+
commandOutput: requestWithMessageId.commandOutput,
|
|
4024
|
+
};
|
|
3991
4025
|
set({
|
|
3992
4026
|
sendingMessage: true,
|
|
3993
4027
|
sendMessageError: null,
|
|
@@ -3995,35 +4029,25 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|
|
3995
4029
|
sendMessageDebugDetails: null,
|
|
3996
4030
|
lastSendMessageResult: null,
|
|
3997
4031
|
});
|
|
4032
|
+
set((state) => ({
|
|
4033
|
+
teamMessagesByName: {
|
|
4034
|
+
...state.teamMessagesByName,
|
|
4035
|
+
[teamName]: upsertOptimisticTeamMessage(
|
|
4036
|
+
getTeamMessagesCacheEntry(state, teamName),
|
|
4037
|
+
optimisticMessage
|
|
4038
|
+
),
|
|
4039
|
+
},
|
|
4040
|
+
}));
|
|
3998
4041
|
try {
|
|
3999
4042
|
const result = await unwrapIpc('team:sendMessage', () =>
|
|
4000
|
-
api.teams.sendMessage(teamName,
|
|
4043
|
+
api.teams.sendMessage(teamName, requestWithMessageId)
|
|
4001
4044
|
);
|
|
4002
4045
|
const runtimeDeliveryFailed =
|
|
4003
4046
|
result.runtimeDelivery?.attempted === true && result.runtimeDelivery.delivered === false;
|
|
4004
4047
|
const runtimeDeliveryDiagnostics = buildOpenCodeRuntimeDeliveryDiagnostics(result);
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4007
|
-
to: request.to ?? request.member,
|
|
4008
|
-
text: request.text,
|
|
4009
|
-
timestamp: request.timestamp ?? nowIso(),
|
|
4010
|
-
read: true,
|
|
4011
|
-
taskRefs: request.taskRefs?.length ? request.taskRefs : undefined,
|
|
4012
|
-
actionMode: request.actionMode,
|
|
4013
|
-
summary: request.summary,
|
|
4014
|
-
color: request.color,
|
|
4048
|
+
const confirmedOptimisticMessage: InboxMessage = {
|
|
4049
|
+
...optimisticMessage,
|
|
4015
4050
|
messageId: result.messageId,
|
|
4016
|
-
relayOfMessageId: request.relayOfMessageId,
|
|
4017
|
-
source: request.source ?? 'user_sent',
|
|
4018
|
-
attachments: request.attachments?.length ? request.attachments : undefined,
|
|
4019
|
-
leadSessionId: request.leadSessionId,
|
|
4020
|
-
conversationId: request.conversationId,
|
|
4021
|
-
replyToConversationId: request.replyToConversationId,
|
|
4022
|
-
toolSummary: request.toolSummary,
|
|
4023
|
-
toolCalls: request.toolCalls,
|
|
4024
|
-
messageKind: request.messageKind,
|
|
4025
|
-
slashCommand: request.slashCommand,
|
|
4026
|
-
commandOutput: request.commandOutput,
|
|
4027
4051
|
};
|
|
4028
4052
|
set((state) => ({
|
|
4029
4053
|
sendingMessage: false,
|
|
@@ -4035,7 +4059,7 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|
|
4035
4059
|
...state.teamMessagesByName,
|
|
4036
4060
|
[teamName]: upsertOptimisticTeamMessage(
|
|
4037
4061
|
getTeamMessagesCacheEntry(state, teamName),
|
|
4038
|
-
|
|
4062
|
+
confirmedOptimisticMessage
|
|
4039
4063
|
),
|
|
4040
4064
|
},
|
|
4041
4065
|
}));
|
|
@@ -4053,6 +4077,18 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|
|
4053
4077
|
}
|
|
4054
4078
|
},
|
|
4055
4079
|
|
|
4080
|
+
addOptimisticTeamMessage: (teamName: string, message: InboxMessage) => {
|
|
4081
|
+
set((state) => ({
|
|
4082
|
+
teamMessagesByName: {
|
|
4083
|
+
...state.teamMessagesByName,
|
|
4084
|
+
[teamName]: upsertOptimisticTeamMessage(
|
|
4085
|
+
getTeamMessagesCacheEntry(state, teamName),
|
|
4086
|
+
message
|
|
4087
|
+
),
|
|
4088
|
+
},
|
|
4089
|
+
}));
|
|
4090
|
+
},
|
|
4091
|
+
|
|
4056
4092
|
fetchCrossTeamTargets: async () => {
|
|
4057
4093
|
set({ crossTeamTargetsLoading: true });
|
|
4058
4094
|
try {
|
package/src/shared/types/api.ts
CHANGED
|
@@ -46,6 +46,7 @@ import type {
|
|
|
46
46
|
BoardTaskExactLogSummariesResponse,
|
|
47
47
|
BoardTaskLogStreamResponse,
|
|
48
48
|
BoardTaskLogStreamSummary,
|
|
49
|
+
CollabTask,
|
|
49
50
|
CreateTaskRequest,
|
|
50
51
|
CrossTeamMessage,
|
|
51
52
|
CrossTeamSendRequest,
|
|
@@ -723,6 +724,33 @@ export interface CrossTeamAPI {
|
|
|
723
724
|
getOutbox: (teamName: string) => Promise<CrossTeamMessage[]>;
|
|
724
725
|
}
|
|
725
726
|
|
|
727
|
+
// =============================================================================
|
|
728
|
+
// Collaboration Board API
|
|
729
|
+
// =============================================================================
|
|
730
|
+
|
|
731
|
+
export interface CollabBoardAPI {
|
|
732
|
+
getBoard: () => Promise<{ tasks: CollabTask[] }>;
|
|
733
|
+
getTask: (dispatchId: string) => Promise<{ task: CollabTask }>;
|
|
734
|
+
getEvents: (dispatchId: string) => Promise<{ events: import('./team').CollabTaskEvent[] }>;
|
|
735
|
+
accept: (teamSlug: string, dispatchId: string) => Promise<{ ok: boolean; taskId: string }>;
|
|
736
|
+
reject: (teamSlug: string, dispatchId: string, reason?: string) => Promise<{ ok: boolean }>;
|
|
737
|
+
deliver: (teamSlug: string, dispatchId: string, result: string) => Promise<{ ok: boolean }>;
|
|
738
|
+
approve: (teamSlug: string, dispatchId: string) => Promise<{ ok: boolean }>;
|
|
739
|
+
revision: (teamSlug: string, dispatchId: string, feedback: string) => Promise<{ ok: boolean }>;
|
|
740
|
+
dispatch: (
|
|
741
|
+
fromTeam: string,
|
|
742
|
+
toTeam: string,
|
|
743
|
+
subject: string,
|
|
744
|
+
opts?: {
|
|
745
|
+
description?: string;
|
|
746
|
+
deadlineMinutes?: number;
|
|
747
|
+
needsHumanReview?: boolean;
|
|
748
|
+
messageId?: string;
|
|
749
|
+
sessionKey?: string;
|
|
750
|
+
}
|
|
751
|
+
) => Promise<{ ok: boolean; dispatchId: string; status: string; message: string }>;
|
|
752
|
+
}
|
|
753
|
+
|
|
726
754
|
// =============================================================================
|
|
727
755
|
// Schedule API
|
|
728
756
|
// =============================================================================
|
|
@@ -1060,6 +1088,7 @@ export interface ElectronAPI extends RecentProjectsElectronApi {
|
|
|
1060
1088
|
|
|
1061
1089
|
// Cross-Team Communication API
|
|
1062
1090
|
crossTeam: CrossTeamAPI;
|
|
1091
|
+
collab: CollabBoardAPI;
|
|
1063
1092
|
|
|
1064
1093
|
// Review API
|
|
1065
1094
|
review: ReviewAPI;
|
package/src/shared/types/team.ts
CHANGED
|
@@ -133,6 +133,9 @@ export type TeamReviewState = 'none' | 'review' | 'needsFix' | 'approved';
|
|
|
133
133
|
|
|
134
134
|
export type DispatchStatus =
|
|
135
135
|
| 'dispatched'
|
|
136
|
+
| 'pending_accept'
|
|
137
|
+
| 'accepted'
|
|
138
|
+
| 'rejected'
|
|
136
139
|
| 'received'
|
|
137
140
|
| 'in_progress'
|
|
138
141
|
| 'completed'
|
|
@@ -148,6 +151,15 @@ export interface DispatchMeta {
|
|
|
148
151
|
receivedAt?: string;
|
|
149
152
|
completedAt?: string;
|
|
150
153
|
remoteTaskId?: string;
|
|
154
|
+
deadline?: string;
|
|
155
|
+
acceptedAt?: string;
|
|
156
|
+
rejectedAt?: string;
|
|
157
|
+
rejectionReason?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface AgentCapability {
|
|
161
|
+
skill: string;
|
|
162
|
+
description: string;
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
export interface DiscoverableTeam {
|
|
@@ -156,6 +168,9 @@ export interface DiscoverableTeam {
|
|
|
156
168
|
location: 'local' | 'remote';
|
|
157
169
|
status: 'online' | 'offline';
|
|
158
170
|
collaboration: boolean;
|
|
171
|
+
capabilities?: AgentCapability[];
|
|
172
|
+
description?: string;
|
|
173
|
+
harness?: string;
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
export interface TaskBusConfig {
|
|
@@ -166,8 +181,10 @@ export interface TaskBusConfig {
|
|
|
166
181
|
password?: string;
|
|
167
182
|
db?: number;
|
|
168
183
|
};
|
|
184
|
+
collaboration?: boolean;
|
|
169
185
|
telemetry?: {
|
|
170
186
|
enabled: boolean;
|
|
187
|
+
uploadEnabled?: boolean;
|
|
171
188
|
/** Data source platform. Currently only 'claudecode'. */
|
|
172
189
|
platform: 'claudecode';
|
|
173
190
|
};
|
|
@@ -185,6 +202,8 @@ export interface TaskDispatchPayload {
|
|
|
185
202
|
promptTaskRefs?: string[];
|
|
186
203
|
};
|
|
187
204
|
dispatchedAt: string;
|
|
205
|
+
deadline?: string;
|
|
206
|
+
needsHumanReview?: boolean;
|
|
188
207
|
}
|
|
189
208
|
|
|
190
209
|
export interface TaskStatusUpdate {
|
|
@@ -203,6 +222,81 @@ export interface TaskAckPayload {
|
|
|
203
222
|
timestamp: string;
|
|
204
223
|
}
|
|
205
224
|
|
|
225
|
+
export interface TaskHandshakeResponse {
|
|
226
|
+
dispatchId: string;
|
|
227
|
+
type: 'task_accept' | 'task_reject' | 'task_deliver' | 'task_approve' | 'task_revision';
|
|
228
|
+
fromTeam: string;
|
|
229
|
+
toTeam: string;
|
|
230
|
+
remoteTaskId?: string;
|
|
231
|
+
reason?: string;
|
|
232
|
+
result?: string;
|
|
233
|
+
feedback?: string;
|
|
234
|
+
acceptedAt?: string;
|
|
235
|
+
rejectedAt?: string;
|
|
236
|
+
deliveredAt?: string;
|
|
237
|
+
approvedAt?: string;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Collaboration Board — global cross-team task view
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
export type CollabTaskStatus =
|
|
245
|
+
| 'pending_accept'
|
|
246
|
+
| 'accepted'
|
|
247
|
+
| 'delivered'
|
|
248
|
+
| 'approved'
|
|
249
|
+
| 'revision'
|
|
250
|
+
| 'rejected'
|
|
251
|
+
| 'failed';
|
|
252
|
+
|
|
253
|
+
export type CollabTaskEventType =
|
|
254
|
+
| 'task_sent'
|
|
255
|
+
| 'task_accepted'
|
|
256
|
+
| 'task_rejected'
|
|
257
|
+
| 'task_delivered'
|
|
258
|
+
| 'revision_requested'
|
|
259
|
+
| 'task_approved'
|
|
260
|
+
| 'task_failed';
|
|
261
|
+
|
|
262
|
+
export interface CollabTaskEvent {
|
|
263
|
+
eventId: string;
|
|
264
|
+
dispatchId: string;
|
|
265
|
+
version: number;
|
|
266
|
+
type: CollabTaskEventType;
|
|
267
|
+
actor: {
|
|
268
|
+
type: 'user' | 'team' | 'agent' | 'system';
|
|
269
|
+
id: string;
|
|
270
|
+
};
|
|
271
|
+
payload?: Record<string, unknown>;
|
|
272
|
+
createdAt: string;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface CollabTask {
|
|
276
|
+
id: string;
|
|
277
|
+
dispatchId: string;
|
|
278
|
+
subject: string;
|
|
279
|
+
description?: string;
|
|
280
|
+
fromTeam: string;
|
|
281
|
+
fromTeamDisplay: string;
|
|
282
|
+
toTeam: string;
|
|
283
|
+
toTeamDisplay: string;
|
|
284
|
+
status: CollabTaskStatus;
|
|
285
|
+
version?: number;
|
|
286
|
+
reason?: string;
|
|
287
|
+
result?: string;
|
|
288
|
+
feedback?: string;
|
|
289
|
+
deadline?: string;
|
|
290
|
+
needsHumanReview: boolean;
|
|
291
|
+
revisionCount: number;
|
|
292
|
+
createdAt: string;
|
|
293
|
+
updatedAt: string;
|
|
294
|
+
acceptedAt?: string;
|
|
295
|
+
rejectedAt?: string;
|
|
296
|
+
deliveredAt?: string;
|
|
297
|
+
approvedAt?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
206
300
|
export interface TaskWorkInterval {
|
|
207
301
|
/** ISO timestamp when task entered in_progress */
|
|
208
302
|
startedAt: string;
|
|
@@ -863,7 +957,15 @@ export type TeamLaunchAggregateState =
|
|
|
863
957
|
| 'partial_skipped';
|
|
864
958
|
export type PersistedTeamLaunchPhase = 'active' | 'finished' | 'reconciled';
|
|
865
959
|
|
|
866
|
-
export type KanbanColumnId =
|
|
960
|
+
export type KanbanColumnId =
|
|
961
|
+
| 'todo'
|
|
962
|
+
| 'in_progress'
|
|
963
|
+
| 'done'
|
|
964
|
+
| 'review'
|
|
965
|
+
| 'approved'
|
|
966
|
+
| 'pending_accept'
|
|
967
|
+
| 'delivered'
|
|
968
|
+
| 'revision';
|
|
867
969
|
|
|
868
970
|
export interface KanbanTaskState {
|
|
869
971
|
column: Extract<KanbanColumnId, 'review' | 'approved'>;
|
|
@@ -1699,6 +1801,7 @@ export interface CrossTeamSendRequest {
|
|
|
1699
1801
|
toTeam: string;
|
|
1700
1802
|
timestamp?: string;
|
|
1701
1803
|
messageId?: string;
|
|
1804
|
+
sessionKey?: string;
|
|
1702
1805
|
conversationId?: string;
|
|
1703
1806
|
replyToConversationId?: string;
|
|
1704
1807
|
text: string;
|