@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.
- package/README.md +7 -1
- package/bin/hermit.mjs +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-CQm6jUR1.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-DVq8rt6_.js → TeamGraphOverlay-h0WDfifv.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-ZbF0pKvS.js → _basePickBy-CgG_tjgX.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BBLBOeXc.js → _baseUniq-DwPTU9lP.js} +1 -1
- package/dist-renderer/assets/{arc-wGaEgkCf.js → arc-7nIrGRzY.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BpMkdC35.js → architectureDiagram-VXUJARFQ-BYhA6Ev2.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-C8Z1xhG4.js → blockDiagram-VD42YOAC-BVpZUGDg.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CJmlw9LA.js → c4Diagram-YG6GDRKO-DsdreMQ9.js} +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CHPHiRPP.js → chunk-4BX2VUAB-CcoAs7Jd.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-DyVohOQb.js → chunk-55IACEB6-CGGAOoXd.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-p5bffh_R.js → chunk-B4BG7PRW-FhpTEPvD.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-BnfGPSUu.js → chunk-DI55MBZ5-DoYySbm1.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-B6SCKseX.js → chunk-FMBD7UC4-e9l2tGHG.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-L12RvLBR.js → chunk-QN33PNHL-DeiXVTCy.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DeH1Kxge.js → chunk-QZHKN3VN-DC2UJLJM.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BWnjzSlI.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-BtzoT5fu.js → cose-bilkent-S5V4N54A-BdybQraU.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-CBBvuoUD.js → dagre-6UL2VRFP-DdF3pwM3.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-Be9BAKws.js → diagram-PSM6KHXK-B9Ldd3nh.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-BDS4PI_i.js → diagram-QEK2KX5R-XEqkrbpu.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-2Rameaq7.js → diagram-S2PKOQOG-CipwtY59.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CSIzCEZD.js → erDiagram-Q2GNP2WA-BB-2ISGo.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-ForEIVM5.js → flowDiagram-NV44I4VS-B8XmJ0u2.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-BJrli_xr.js → ganttDiagram-JELNMOA3-D-8XglBb.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-C_4GuLno.js → gitGraphDiagram-V2S2FVAM-DL4ChakD.js} +1 -1
- package/dist-renderer/assets/{graph-B1EAT_gw.js → graph-BiFNoBjP.js} +1 -1
- package/dist-renderer/assets/{index-eKRmS5kI.js → index-6m1ZAymG.js} +1 -1
- package/dist-renderer/assets/index-BhellmRb.css +1 -0
- package/dist-renderer/assets/{index-DYdseEwc.js → index-BowUl0Jb.js} +518 -514
- package/dist-renderer/assets/{index-DR602dwJ.js → index-Dp3kJTEe.js} +1 -1
- package/dist-renderer/assets/{index-Dwr5wu5x.js → index-TOpt_T7A.js} +1 -1
- package/dist-renderer/assets/{index-DOA_jbYb.js → index-qNBNjW4K.js} +1 -1
- package/dist-renderer/assets/{index-k4tnOFC5.js → index-vAykq1H1.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DjI0uaMz.js → infoDiagram-HS3SLOUP-DRIBfHDi.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-jQ6Thae-.js → journeyDiagram-XKPGCS4Q-BOMiigU4.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CKw6InbL.js → kanban-definition-3W4ZIXB7-DDxeyjod.js} +1 -1
- package/dist-renderer/assets/{layout-Dad20y3V.js → layout-DNANbrI4.js} +1 -1
- package/dist-renderer/assets/{linear-vMgo_2Cv.js → linear-DxEJi1yT.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-DYp6YoHL.js → mindmap-definition-VGOIOE7T-nBfGriW8.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BytBecG9.js → pieDiagram-ADFJNKIX-Din5j6sV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-RUaspLsc.js → quadrantDiagram-AYHSOK5B-DMVK2BEQ.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-rR2B1Use.js → requirementDiagram-UZGBJVZJ-6SC94Gg_.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BJi5qYhq.js → sankeyDiagram-TZEHDZUN-CD2gghhu.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BM-wggUb.js → sequenceDiagram-WL72ISMW-BnhkN7nZ.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BqmcVjnj.js → stateDiagram-FKZM4ZOC-Bn8XdYX-.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-By3JDVbB.js → stateDiagram-v2-4FDKWEC3-1b6sI1_g.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-szH0GUyk.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-dwDpvw0w.js → xychartDiagram-PRI3JC2R-B8o5J2f3.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/server.ts +800 -163
- package/src/main/services/session-intelligence/SessionUsageParser.ts +446 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +252 -0
- 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/dashboard/DashboardView.tsx +6 -105
- 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/SettingsTabs.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +511 -81
- 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 +109 -1
- package/dist-renderer/assets/ProjectEditorOverlay-BBwYdXPv.js +0 -57
- package/dist-renderer/assets/channel-DJUrwVrK.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-blc3DrH7.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-blc3DrH7.js +0 -1
- package/dist-renderer/assets/clone-BftjWakJ.js +0 -1
- package/dist-renderer/assets/index-CWpFqEvz.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-BCMlh-Ex.js +0 -162
|
@@ -628,12 +628,6 @@ export const ProjectEditorOverlay = ({
|
|
|
628
628
|
<EditorFileTree
|
|
629
629
|
selectedFilePath={activeTabId}
|
|
630
630
|
onFileSelect={handleFileSelect}
|
|
631
|
-
onCreateTask={
|
|
632
|
-
onEditorAction
|
|
633
|
-
? (filePath: string) =>
|
|
634
|
-
onEditorAction(buildFileAction('createTask', filePath, projectPath))
|
|
635
|
-
: undefined
|
|
636
|
-
}
|
|
637
631
|
onSendMessage={
|
|
638
632
|
onEditorAction
|
|
639
633
|
? (filePath: string) =>
|
|
@@ -829,10 +823,6 @@ export const ProjectEditorOverlay = ({
|
|
|
829
823
|
onEditorAction(buildSelectionAction('sendMessage', selectionInfo));
|
|
830
824
|
setSelectionInfo(null);
|
|
831
825
|
}}
|
|
832
|
-
onCreateTask={() => {
|
|
833
|
-
onEditorAction(buildSelectionAction('createTask', selectionInfo));
|
|
834
|
-
setSelectionInfo(null);
|
|
835
|
-
}}
|
|
836
826
|
/>
|
|
837
827
|
)}
|
|
838
828
|
</div>
|
|
@@ -274,7 +274,11 @@ export const KanbanBoard = ({
|
|
|
274
274
|
}: KanbanBoardProps): React.JSX.Element => {
|
|
275
275
|
const boardRef = useRef<HTMLDivElement>(null);
|
|
276
276
|
const scrollRestoreTimeoutsRef = useRef<number[]>([]);
|
|
277
|
-
const [viewMode, setViewMode] = useState<KanbanViewMode>('
|
|
277
|
+
const [viewMode, setViewMode] = useState<KanbanViewMode>('columns');
|
|
278
|
+
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
setViewMode('columns');
|
|
281
|
+
}, []);
|
|
278
282
|
const enableTaskSorting =
|
|
279
283
|
viewMode === 'columns' && !!onColumnOrderChange && sort.field === 'manual';
|
|
280
284
|
|
|
@@ -26,7 +26,6 @@ import { type MemberActivityFilter, type MemberDetailTab } from './memberDetailT
|
|
|
26
26
|
import { MemberLaunchDiagnosticsButton } from './MemberLaunchDiagnosticsButton';
|
|
27
27
|
import { MemberMessagesTab } from './MemberMessagesTab';
|
|
28
28
|
import { MemberStatsTab } from './MemberStatsTab';
|
|
29
|
-
import { MemberTasksTab } from './MemberTasksTab';
|
|
30
29
|
import { MemberWorkspaceTab } from './MemberWorkspaceTab';
|
|
31
30
|
|
|
32
31
|
import type { TeamLaunchParams } from '@renderer/store/slices/teamSlice';
|
|
@@ -56,8 +55,10 @@ interface MemberDetailDialogProps {
|
|
|
56
55
|
launchParams?: TeamLaunchParams;
|
|
57
56
|
onClose: () => void;
|
|
58
57
|
onSendMessage: () => void;
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
/** Deprecated: team tasks UI has been removed, kept for compatibility with older callers/tests. */
|
|
59
|
+
onAssignTask?: () => void;
|
|
60
|
+
/** Deprecated: team tasks UI has been removed, kept for compatibility with older callers/tests. */
|
|
61
|
+
onTaskClick?: (task: TeamTaskWithKanban) => void;
|
|
61
62
|
onRemoveMember?: () => void;
|
|
62
63
|
onRestartMember?: (memberName: string) => Promise<void> | void;
|
|
63
64
|
onUpdateRole?: (memberName: string, role: string | undefined) => Promise<void> | void;
|
|
@@ -83,18 +84,12 @@ export const MemberDetailDialog = ({
|
|
|
83
84
|
launchParams,
|
|
84
85
|
onClose,
|
|
85
86
|
onSendMessage,
|
|
86
|
-
onAssignTask,
|
|
87
|
-
onTaskClick,
|
|
88
87
|
onRemoveMember,
|
|
89
88
|
onRestartMember,
|
|
90
89
|
onUpdateRole,
|
|
91
90
|
updatingRole,
|
|
92
91
|
onViewMemberChanges,
|
|
93
92
|
}: MemberDetailDialogProps): React.JSX.Element | null => {
|
|
94
|
-
const memberTasks = useMemo(
|
|
95
|
-
() => (member ? tasks.filter((t) => t.owner === member.name) : []),
|
|
96
|
-
[tasks, member]
|
|
97
|
-
);
|
|
98
93
|
const memberMessages = useStore((state) =>
|
|
99
94
|
selectMemberMessagesForTeamMember(state, teamName, member?.name ?? null)
|
|
100
95
|
);
|
|
@@ -111,17 +106,9 @@ export const MemberDetailDialog = ({
|
|
|
111
106
|
}).length;
|
|
112
107
|
}, [member, memberMessages, members, tasks, teamName]);
|
|
113
108
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
[memberTasks]
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const completedTasks = useMemo(
|
|
120
|
-
() => memberTasks.filter((t) => t.status === 'completed').length,
|
|
121
|
-
[memberTasks]
|
|
109
|
+
const [activeTab, setActiveTab] = useState<MemberDetailTab>(
|
|
110
|
+
initialTab === 'tasks' ? 'workspace' : initialTab
|
|
122
111
|
);
|
|
123
|
-
|
|
124
|
-
const [activeTab, setActiveTab] = useState<MemberDetailTab>(initialTab);
|
|
125
112
|
const [restarting, setRestarting] = useState(false);
|
|
126
113
|
const [restartError, setRestartError] = useState<string | null>(null);
|
|
127
114
|
|
|
@@ -202,8 +189,8 @@ export const MemberDetailDialog = ({
|
|
|
202
189
|
</DialogHeader>
|
|
203
190
|
|
|
204
191
|
<MemberDetailStats
|
|
205
|
-
totalTasks={
|
|
206
|
-
inProgressTasks={
|
|
192
|
+
totalTasks={0}
|
|
193
|
+
inProgressTasks={0}
|
|
207
194
|
totalTokens={totalTokens}
|
|
208
195
|
statsLoading={statsLoading}
|
|
209
196
|
statsComputedAt={memberStats?.computedAt}
|
|
@@ -217,14 +204,6 @@ export const MemberDetailDialog = ({
|
|
|
217
204
|
className="min-w-0 overflow-hidden"
|
|
218
205
|
>
|
|
219
206
|
<TabsList className="w-full">
|
|
220
|
-
<TabsTrigger value="tasks" className="flex-1 gap-1.5">
|
|
221
|
-
Tasks
|
|
222
|
-
{memberTasks.length > 0 && (
|
|
223
|
-
<span className="rounded-full bg-[var(--color-surface)] px-1.5 text-[10px]">
|
|
224
|
-
{memberTasks.length}
|
|
225
|
-
</span>
|
|
226
|
-
)}
|
|
227
|
-
</TabsTrigger>
|
|
228
207
|
<TabsTrigger value="workspace" className="flex-1 gap-1.5">
|
|
229
208
|
<FolderOpen size={12} />
|
|
230
209
|
Workspace
|
|
@@ -242,9 +221,6 @@ export const MemberDetailDialog = ({
|
|
|
242
221
|
Stats
|
|
243
222
|
</TabsTrigger>
|
|
244
223
|
</TabsList>
|
|
245
|
-
<TabsContent value="tasks">
|
|
246
|
-
<MemberTasksTab tasks={memberTasks} onTaskClick={onTaskClick} />
|
|
247
|
-
</TabsContent>
|
|
248
224
|
<TabsContent value="workspace">
|
|
249
225
|
<MemberWorkspaceTab
|
|
250
226
|
teamName={teamName}
|
|
@@ -260,7 +236,6 @@ export const MemberDetailDialog = ({
|
|
|
260
236
|
members={members}
|
|
261
237
|
tasks={tasks}
|
|
262
238
|
initialFilter={initialActivityFilter}
|
|
263
|
-
onTaskClick={onTaskClick}
|
|
264
239
|
/>
|
|
265
240
|
</TabsContent>
|
|
266
241
|
<TabsContent value="stats">
|
|
@@ -69,6 +69,11 @@ interface MessageComposerProps {
|
|
|
69
69
|
actionMode?: AgentActionMode,
|
|
70
70
|
taskRefs?: TaskRef[]
|
|
71
71
|
) => void;
|
|
72
|
+
onDispatchTask?: (
|
|
73
|
+
toTeam: string,
|
|
74
|
+
subject: string,
|
|
75
|
+
description: string
|
|
76
|
+
) => Promise<boolean | void> | boolean | void;
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
export const MessageComposer = ({
|
|
@@ -86,6 +91,7 @@ export const MessageComposer = ({
|
|
|
86
91
|
onSessionChange,
|
|
87
92
|
textareaRef: externalTextareaRef,
|
|
88
93
|
onSend,
|
|
94
|
+
onDispatchTask,
|
|
89
95
|
}: MessageComposerProps): React.JSX.Element => {
|
|
90
96
|
const internalTextareaRef = useRef<HTMLTextAreaElement>(null);
|
|
91
97
|
const textareaRef = useMemo(() => {
|
|
@@ -223,7 +229,26 @@ export const MessageComposer = ({
|
|
|
223
229
|
? '斜杠命令需要团队负责人在线'
|
|
224
230
|
: null
|
|
225
231
|
: null;
|
|
226
|
-
const
|
|
232
|
+
const teamDispatch = useMemo(() => {
|
|
233
|
+
const match = trimmed.match(/^@([^\s]+)\s+([\s\S]+)$/);
|
|
234
|
+
if (!match || !onDispatchTask) return null;
|
|
235
|
+
const mentioned = match[1];
|
|
236
|
+
const subject = match[2]?.trim();
|
|
237
|
+
if (!mentioned || !subject) return null;
|
|
238
|
+
const targetTeam = teamMentionSuggestions.find((team) => {
|
|
239
|
+
const slug = team.id.startsWith('team:') ? team.id.slice('team:'.length) : team.id;
|
|
240
|
+
return slug === mentioned || team.name === mentioned;
|
|
241
|
+
});
|
|
242
|
+
const slug = targetTeam
|
|
243
|
+
? targetTeam.id.startsWith('team:')
|
|
244
|
+
? targetTeam.id.slice('team:'.length)
|
|
245
|
+
: targetTeam.id
|
|
246
|
+
: mentioned;
|
|
247
|
+
return { slug, subject };
|
|
248
|
+
}, [onDispatchTask, teamMentionSuggestions, trimmed]);
|
|
249
|
+
const canDispatchToTeam =
|
|
250
|
+
teamDispatch !== null && trimmed.length > 0 && trimmed.length <= MAX_TEXT_LENGTH && !sending;
|
|
251
|
+
const canSendRegularMessage =
|
|
227
252
|
recipient.length > 0 &&
|
|
228
253
|
trimmed.length > 0 &&
|
|
229
254
|
trimmed.length <= MAX_TEXT_LENGTH &&
|
|
@@ -231,6 +256,7 @@ export const MessageComposer = ({
|
|
|
231
256
|
!isProvisioning &&
|
|
232
257
|
!attachmentsBlocked &&
|
|
233
258
|
!slashCommandRestrictionReason;
|
|
259
|
+
const canSend = canDispatchToTeam || canSendRegularMessage;
|
|
234
260
|
|
|
235
261
|
// Track whether we initiated a send — clear draft only on confirmed success
|
|
236
262
|
const pendingSendRef = useRef(false);
|
|
@@ -238,9 +264,19 @@ export const MessageComposer = ({
|
|
|
238
264
|
const handleSend = useCallback(() => {
|
|
239
265
|
if (!canSend) return;
|
|
240
266
|
dismissMentionsRef.current?.();
|
|
241
|
-
pendingSendRef.current = true;
|
|
242
267
|
const taskRefs = extractTaskRefsFromText(draft.text, taskSuggestions);
|
|
243
268
|
const serialized = serializeChipsWithText(trimmed, draft.chips);
|
|
269
|
+
|
|
270
|
+
if (teamDispatch && onDispatchTask) {
|
|
271
|
+
void Promise.resolve(
|
|
272
|
+
onDispatchTask(teamDispatch.slug, teamDispatch.subject, serialized)
|
|
273
|
+
).then((dispatched) => {
|
|
274
|
+
if (dispatched !== false) draft.clearDraft();
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
pendingSendRef.current = true;
|
|
244
280
|
onSend(
|
|
245
281
|
recipient,
|
|
246
282
|
serialized,
|
|
@@ -249,7 +285,7 @@ export const MessageComposer = ({
|
|
|
249
285
|
undefined,
|
|
250
286
|
taskRefs
|
|
251
287
|
);
|
|
252
|
-
}, [canSend, recipient, trimmed, onSend, draft
|
|
288
|
+
}, [canSend, recipient, trimmed, onSend, draft, taskSuggestions, teamDispatch, onDispatchTask]);
|
|
253
289
|
|
|
254
290
|
// Clear draft only after send completes successfully (sending: true → false, no error)
|
|
255
291
|
useEffect(() => {
|
|
@@ -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;
|