@lobehub/lobehub 2.0.0-next.329 → 2.0.0-next.330
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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/groupOrchestration/types.ts +3 -2
- package/packages/builtin-tool-group-management/src/client/Render/ExecuteTasks/index.tsx +117 -0
- package/packages/builtin-tool-group-management/src/client/Render/index.ts +3 -0
- package/packages/builtin-tool-group-management/src/executor.test.ts +5 -0
- package/packages/builtin-tool-group-management/src/executor.ts +33 -0
- package/packages/builtin-tool-group-management/src/manifest.ts +47 -46
- package/packages/builtin-tool-group-management/src/systemRole.ts +23 -30
- package/packages/types/src/tool/builtin.ts +46 -0
- package/src/features/Conversation/Messages/Tasks/TaskItem/ClientTaskItem.tsx +115 -0
- package/src/features/Conversation/Messages/Tasks/TaskItem/ServerTaskItem.tsx +92 -0
- package/src/features/Conversation/Messages/Tasks/TaskItem/TaskTitle.tsx +98 -5
- package/src/features/Conversation/Messages/Tasks/TaskItem/index.tsx +10 -65
- package/src/features/Conversation/Messages/Tasks/TaskItem/useClientTaskStats.ts +82 -0
- package/src/features/Conversation/Messages/Tool/preload.ts +18 -0
- package/src/store/chat/slices/aiAgent/actions/groupOrchestration.ts +43 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +7 -2
- package/src/store/tool/slices/builtin/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.330](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.329...v2.0.0-next.330)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-21**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix multi agent tasks issue.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix multi agent tasks issue, closes [#11672](https://github.com/lobehub/lobe-chat/issues/11672) ([9de773b](https://github.com/lobehub/lobe-chat/commit/9de773b))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.329](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.328...v2.0.0-next.329)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-21**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.330",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -118,10 +118,11 @@ export interface ExecutorResultSupervisorDecided {
|
|
|
118
118
|
* - 'speak': Call a single agent
|
|
119
119
|
* - 'broadcast': Call multiple agents in parallel
|
|
120
120
|
* - 'delegate': Delegate to another agent
|
|
121
|
-
* - 'execute_task': Execute
|
|
121
|
+
* - 'execute_task': Execute a single async task
|
|
122
|
+
* - 'execute_tasks': Execute multiple async tasks in parallel
|
|
122
123
|
* - 'finish': End the orchestration
|
|
123
124
|
*/
|
|
124
|
-
decision: 'speak' | 'broadcast' | 'delegate' | 'execute_task' | 'finish';
|
|
125
|
+
decision: 'speak' | 'broadcast' | 'delegate' | 'execute_task' | 'execute_tasks' | 'finish';
|
|
125
126
|
/**
|
|
126
127
|
* Parameters for the decision
|
|
127
128
|
*/
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_AVATAR } from '@lobechat/const';
|
|
4
|
+
import type { AgentGroupMember, BuiltinRenderProps } from '@lobechat/types';
|
|
5
|
+
import { Avatar, Flexbox, Text } from '@lobehub/ui';
|
|
6
|
+
import { createStaticStyles, useTheme } from 'antd-style';
|
|
7
|
+
import { Clock } from 'lucide-react';
|
|
8
|
+
import { memo, useMemo } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
|
|
11
|
+
import { useAgentGroupStore } from '@/store/agentGroup';
|
|
12
|
+
import { agentGroupSelectors } from '@/store/agentGroup/selectors';
|
|
13
|
+
|
|
14
|
+
import type { ExecuteTasksParams } from '../../../types';
|
|
15
|
+
|
|
16
|
+
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
17
|
+
container: css`
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
gap: 12px;
|
|
21
|
+
padding-block: 12px;
|
|
22
|
+
`,
|
|
23
|
+
taskCard: css`
|
|
24
|
+
padding: 12px;
|
|
25
|
+
border-radius: 8px;
|
|
26
|
+
background: ${cssVar.colorFillQuaternary};
|
|
27
|
+
`,
|
|
28
|
+
taskContent: css`
|
|
29
|
+
padding-block: 8px;
|
|
30
|
+
padding-inline: 12px;
|
|
31
|
+
border-radius: ${cssVar.borderRadius};
|
|
32
|
+
background: ${cssVar.colorFillTertiary};
|
|
33
|
+
`,
|
|
34
|
+
taskHeader: css`
|
|
35
|
+
font-size: 13px;
|
|
36
|
+
font-weight: 500;
|
|
37
|
+
color: ${cssVar.colorText};
|
|
38
|
+
`,
|
|
39
|
+
timeout: css`
|
|
40
|
+
font-size: 12px;
|
|
41
|
+
color: ${cssVar.colorTextTertiary};
|
|
42
|
+
`,
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* ExecuteTasks Render component for Group Management tool
|
|
47
|
+
* Read-only display of multiple task execution requests
|
|
48
|
+
*/
|
|
49
|
+
const ExecuteTasksRender = memo<BuiltinRenderProps<ExecuteTasksParams>>(({ args }) => {
|
|
50
|
+
const { t } = useTranslation('tool');
|
|
51
|
+
const theme = useTheme();
|
|
52
|
+
const { tasks } = args || {};
|
|
53
|
+
|
|
54
|
+
// Get active group ID and agents from store
|
|
55
|
+
const activeGroupId = useAgentGroupStore(agentGroupSelectors.activeGroupId);
|
|
56
|
+
const groupAgents = useAgentGroupStore((s) =>
|
|
57
|
+
activeGroupId ? agentGroupSelectors.getGroupAgents(activeGroupId)(s) : [],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Get agent details for each task
|
|
61
|
+
const tasksWithAgents = useMemo(() => {
|
|
62
|
+
if (!tasks?.length || !groupAgents.length) return [];
|
|
63
|
+
return tasks.map((task) => ({
|
|
64
|
+
...task,
|
|
65
|
+
agent: groupAgents.find((agent) => agent.id === task.agentId) as AgentGroupMember | undefined,
|
|
66
|
+
}));
|
|
67
|
+
}, [tasks, groupAgents]);
|
|
68
|
+
|
|
69
|
+
if (!tasksWithAgents.length) return null;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className={styles.container}>
|
|
73
|
+
{tasksWithAgents.map((task, index) => {
|
|
74
|
+
const timeoutMinutes = task.timeout ? Math.round(task.timeout / 60_000) : 30;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className={styles.taskCard} key={task.agentId || index}>
|
|
78
|
+
<Flexbox gap={12}>
|
|
79
|
+
{/* Header: Agent info + Timeout */}
|
|
80
|
+
<Flexbox align={'center'} gap={12} horizontal justify={'space-between'}>
|
|
81
|
+
<Flexbox align={'center'} flex={1} gap={8} horizontal style={{ minWidth: 0 }}>
|
|
82
|
+
<Avatar
|
|
83
|
+
avatar={task.agent?.avatar || DEFAULT_AVATAR}
|
|
84
|
+
background={task.agent?.backgroundColor || theme.colorBgContainer}
|
|
85
|
+
shape={'square'}
|
|
86
|
+
size={20}
|
|
87
|
+
/>
|
|
88
|
+
<span className={styles.taskHeader}>
|
|
89
|
+
{task.title || task.agent?.title || 'Task'}
|
|
90
|
+
</span>
|
|
91
|
+
</Flexbox>
|
|
92
|
+
<Flexbox align="center" className={styles.timeout} gap={4} horizontal>
|
|
93
|
+
<Clock size={14} />
|
|
94
|
+
<span>
|
|
95
|
+
{timeoutMinutes}{' '}
|
|
96
|
+
{t('agentGroupManagement.executeTask.intervention.timeoutUnit')}
|
|
97
|
+
</span>
|
|
98
|
+
</Flexbox>
|
|
99
|
+
</Flexbox>
|
|
100
|
+
|
|
101
|
+
{/* Task content (read-only) */}
|
|
102
|
+
{task.instruction && (
|
|
103
|
+
<Text className={styles.taskContent} style={{ margin: 0 }}>
|
|
104
|
+
{task.instruction}
|
|
105
|
+
</Text>
|
|
106
|
+
)}
|
|
107
|
+
</Flexbox>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
ExecuteTasksRender.displayName = 'ExecuteTasksRender';
|
|
116
|
+
|
|
117
|
+
export default ExecuteTasksRender;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GroupManagementApiName } from '../../types';
|
|
2
2
|
import BroadcastRender from './Broadcast';
|
|
3
3
|
import ExecuteTaskRender from './ExecuteTask';
|
|
4
|
+
import ExecuteTasksRender from './ExecuteTasks';
|
|
4
5
|
import SpeakRender from './Speak';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -9,9 +10,11 @@ import SpeakRender from './Speak';
|
|
|
9
10
|
export const GroupManagementRenders = {
|
|
10
11
|
[GroupManagementApiName.broadcast]: BroadcastRender,
|
|
11
12
|
[GroupManagementApiName.executeAgentTask]: ExecuteTaskRender,
|
|
13
|
+
[GroupManagementApiName.executeAgentTasks]: ExecuteTasksRender,
|
|
12
14
|
[GroupManagementApiName.speak]: SpeakRender,
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
export { default as BroadcastRender } from './Broadcast';
|
|
16
18
|
export { default as ExecuteTaskRender } from './ExecuteTask';
|
|
19
|
+
export { default as ExecuteTasksRender } from './ExecuteTasks';
|
|
17
20
|
export { default as SpeakRender } from './Speak';
|
|
@@ -75,6 +75,7 @@ describe('GroupManagementExecutor', () => {
|
|
|
75
75
|
triggerBroadcast: vi.fn(),
|
|
76
76
|
triggerDelegate: vi.fn(),
|
|
77
77
|
triggerExecuteTask: vi.fn(),
|
|
78
|
+
triggerExecuteTasks: vi.fn(),
|
|
78
79
|
triggerSpeak,
|
|
79
80
|
},
|
|
80
81
|
'supervisor-agent',
|
|
@@ -122,6 +123,7 @@ describe('GroupManagementExecutor', () => {
|
|
|
122
123
|
triggerBroadcast: vi.fn(),
|
|
123
124
|
triggerDelegate: vi.fn(),
|
|
124
125
|
triggerExecuteTask: vi.fn(),
|
|
126
|
+
triggerExecuteTasks: vi.fn(),
|
|
125
127
|
triggerSpeak,
|
|
126
128
|
},
|
|
127
129
|
'supervisor-agent',
|
|
@@ -171,6 +173,7 @@ describe('GroupManagementExecutor', () => {
|
|
|
171
173
|
triggerBroadcast,
|
|
172
174
|
triggerDelegate: vi.fn(),
|
|
173
175
|
triggerExecuteTask: vi.fn(),
|
|
176
|
+
triggerExecuteTasks: vi.fn(),
|
|
174
177
|
triggerSpeak: vi.fn(),
|
|
175
178
|
},
|
|
176
179
|
'supervisor-agent',
|
|
@@ -238,6 +241,7 @@ describe('GroupManagementExecutor', () => {
|
|
|
238
241
|
triggerBroadcast: vi.fn(),
|
|
239
242
|
triggerDelegate,
|
|
240
243
|
triggerExecuteTask: vi.fn(),
|
|
244
|
+
triggerExecuteTasks: vi.fn(),
|
|
241
245
|
triggerSpeak: vi.fn(),
|
|
242
246
|
},
|
|
243
247
|
'supervisor-agent',
|
|
@@ -308,6 +312,7 @@ describe('GroupManagementExecutor', () => {
|
|
|
308
312
|
triggerBroadcast: vi.fn(),
|
|
309
313
|
triggerDelegate: vi.fn(),
|
|
310
314
|
triggerExecuteTask,
|
|
315
|
+
triggerExecuteTasks: vi.fn(),
|
|
311
316
|
triggerSpeak: vi.fn(),
|
|
312
317
|
},
|
|
313
318
|
'supervisor-agent',
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
CreateWorkflowParams,
|
|
12
12
|
DelegateParams,
|
|
13
13
|
ExecuteTaskParams,
|
|
14
|
+
ExecuteTasksParams,
|
|
14
15
|
GroupManagementApiName,
|
|
15
16
|
GroupManagementIdentifier,
|
|
16
17
|
InterruptParams,
|
|
@@ -153,6 +154,38 @@ class GroupManagementExecutor extends BaseExecutor<typeof GroupManagementApiName
|
|
|
153
154
|
};
|
|
154
155
|
};
|
|
155
156
|
|
|
157
|
+
executeAgentTasks = async (
|
|
158
|
+
params: ExecuteTasksParams,
|
|
159
|
+
ctx: BuiltinToolContext,
|
|
160
|
+
): Promise<BuiltinToolResult> => {
|
|
161
|
+
// Register afterCompletion callback to trigger parallel task execution after AgentRuntime completes
|
|
162
|
+
// This follows the same pattern as executeAgentTask - trigger mode, not blocking
|
|
163
|
+
if (ctx.groupOrchestration && ctx.agentId && ctx.registerAfterCompletion) {
|
|
164
|
+
ctx.registerAfterCompletion(() =>
|
|
165
|
+
ctx.groupOrchestration!.triggerExecuteTasks({
|
|
166
|
+
skipCallSupervisor: params.skipCallSupervisor,
|
|
167
|
+
supervisorAgentId: ctx.agentId!,
|
|
168
|
+
tasks: params.tasks,
|
|
169
|
+
toolMessageId: ctx.messageId,
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const agentIds = params.tasks.map((t) => t.agentId).join(', ');
|
|
175
|
+
|
|
176
|
+
// Returns stop: true to indicate the supervisor should stop and let the tasks execute
|
|
177
|
+
return {
|
|
178
|
+
content: `Triggered ${params.tasks.length} parallel tasks for agents: ${agentIds}.`,
|
|
179
|
+
state: {
|
|
180
|
+
skipCallSupervisor: params.skipCallSupervisor,
|
|
181
|
+
tasks: params.tasks,
|
|
182
|
+
type: 'executeAgentTasks',
|
|
183
|
+
},
|
|
184
|
+
stop: true,
|
|
185
|
+
success: true,
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
|
|
156
189
|
interrupt = async (
|
|
157
190
|
params: InterruptParams,
|
|
158
191
|
_ctx: BuiltinToolContext,
|
|
@@ -121,52 +121,53 @@ export const GroupManagementManifest: BuiltinToolManifest = {
|
|
|
121
121
|
type: 'object',
|
|
122
122
|
},
|
|
123
123
|
},
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
},
|
|
124
|
+
// TODO: Enable executeAgentTasks when ready
|
|
125
|
+
// {
|
|
126
|
+
// description:
|
|
127
|
+
// 'Assign multiple tasks to different agents to run in parallel. Each agent works independently in their own context. Use this when you need multiple agents to work on different parts of a problem simultaneously.',
|
|
128
|
+
// name: GroupManagementApiName.executeAgentTasks,
|
|
129
|
+
// humanIntervention: 'required',
|
|
130
|
+
// parameters: {
|
|
131
|
+
// properties: {
|
|
132
|
+
// tasks: {
|
|
133
|
+
// description: 'Array of tasks, each assigned to a specific agent.',
|
|
134
|
+
// items: {
|
|
135
|
+
// properties: {
|
|
136
|
+
// agentId: {
|
|
137
|
+
// description: 'The ID of the agent to execute this task.',
|
|
138
|
+
// type: 'string',
|
|
139
|
+
// },
|
|
140
|
+
// title: {
|
|
141
|
+
// description: 'Brief title describing what this task does (shown in UI).',
|
|
142
|
+
// type: 'string',
|
|
143
|
+
// },
|
|
144
|
+
// instruction: {
|
|
145
|
+
// description:
|
|
146
|
+
// 'Detailed instruction/prompt for the task execution. Be specific about expected deliverables.',
|
|
147
|
+
// type: 'string',
|
|
148
|
+
// },
|
|
149
|
+
// timeout: {
|
|
150
|
+
// description:
|
|
151
|
+
// 'Optional timeout in milliseconds for this task (default: 1800000, 30 minutes).',
|
|
152
|
+
// type: 'number',
|
|
153
|
+
// },
|
|
154
|
+
// },
|
|
155
|
+
// required: ['agentId', 'title', 'instruction'],
|
|
156
|
+
// type: 'object',
|
|
157
|
+
// },
|
|
158
|
+
// type: 'array',
|
|
159
|
+
// },
|
|
160
|
+
// skipCallSupervisor: {
|
|
161
|
+
// default: false,
|
|
162
|
+
// description:
|
|
163
|
+
// 'If true, the orchestration will end after all tasks complete, without calling the supervisor again.',
|
|
164
|
+
// type: 'boolean',
|
|
165
|
+
// },
|
|
166
|
+
// },
|
|
167
|
+
// required: ['tasks'],
|
|
168
|
+
// type: 'object',
|
|
169
|
+
// },
|
|
170
|
+
// },
|
|
170
171
|
{
|
|
171
172
|
description:
|
|
172
173
|
'Interrupt a running agent task. Use this to stop a task that is taking too long or is no longer needed.',
|
|
@@ -155,8 +155,7 @@ When a user's request is broad or unclear, ask 1-2 focused questions to understa
|
|
|
155
155
|
- **broadcast**: Multiple agents respond in parallel in group context
|
|
156
156
|
|
|
157
157
|
**Task Execution (Independent Context, With Tools):**
|
|
158
|
-
- **executeAgentTask**: Assign a
|
|
159
|
-
- **executeAgentTasks**: Assign multiple tasks to different agents in parallel (each with isolated context)
|
|
158
|
+
- **executeAgentTask**: Assign a task to one agent in isolated context
|
|
160
159
|
- **interrupt**: Stop a running task
|
|
161
160
|
|
|
162
161
|
**Flow Control:**
|
|
@@ -176,19 +175,17 @@ Analysis: Opinion-based, no tools needed
|
|
|
176
175
|
Action: broadcast to [Architect, DevOps, Backend] - share perspectives
|
|
177
176
|
\`\`\`
|
|
178
177
|
|
|
179
|
-
### Pattern 2: Independent Research (
|
|
180
|
-
When
|
|
178
|
+
### Pattern 2: Independent Research (Task)
|
|
179
|
+
When an agent needs to research/work independently using their tools.
|
|
181
180
|
|
|
182
181
|
\`\`\`
|
|
183
|
-
User: "Research the pros and cons of React
|
|
184
|
-
Analysis: Requires web search,
|
|
185
|
-
Action:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
{ agentId: "tech-analyst", title: "Research Svelte", instruction: "Research Svelte ecosystem, performance benchmarks, community size, and typical use cases. Provide pros and cons." }
|
|
191
|
-
]
|
|
182
|
+
User: "Research the pros and cons of React"
|
|
183
|
+
Analysis: Requires web search, agent works independently
|
|
184
|
+
Action: executeAgentTask to frontend expert
|
|
185
|
+
executeAgentTask({
|
|
186
|
+
agentId: "frontend-expert",
|
|
187
|
+
title: "Research React",
|
|
188
|
+
task: "Research React ecosystem, performance benchmarks, community size, and typical use cases. Provide pros and cons."
|
|
192
189
|
})
|
|
193
190
|
\`\`\`
|
|
194
191
|
|
|
@@ -211,28 +208,25 @@ When you need facts first, then discussion.
|
|
|
211
208
|
User: "Should we migrate to Kubernetes? Research and discuss."
|
|
212
209
|
Analysis: First gather facts (tools), then discuss (no tools)
|
|
213
210
|
Action:
|
|
214
|
-
1.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
]
|
|
211
|
+
1. executeAgentTask({
|
|
212
|
+
agentId: "devops",
|
|
213
|
+
title: "K8s Adoption Research",
|
|
214
|
+
task: "Research Kubernetes adoption best practices for our scale. Include migration complexity, resource requirements, operational overhead, and security considerations."
|
|
219
215
|
})
|
|
220
216
|
2. [Wait for results]
|
|
221
217
|
3. broadcast: "Based on the research, share your recommendations"
|
|
222
218
|
\`\`\`
|
|
223
219
|
|
|
224
|
-
### Pattern 5:
|
|
225
|
-
When
|
|
220
|
+
### Pattern 5: Implementation Task
|
|
221
|
+
When an agent needs to create deliverables using their tools.
|
|
226
222
|
|
|
227
223
|
\`\`\`
|
|
228
|
-
User: "
|
|
229
|
-
Analysis:
|
|
230
|
-
Action:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
{ agentId: "frontend-dev", title: "Implement Page", instruction: "Implement the landing page using React. Include responsive design, animations, and SEO-friendly markup." }
|
|
235
|
-
]
|
|
224
|
+
User: "Write the landing page copy"
|
|
225
|
+
Analysis: Agent produces artifacts using their tools
|
|
226
|
+
Action: executeAgentTask({
|
|
227
|
+
agentId: "copywriter",
|
|
228
|
+
title: "Write Copy",
|
|
229
|
+
task: "Write compelling landing page copy for [product]. Include headline, subheadline, feature descriptions, and CTA text."
|
|
236
230
|
})
|
|
237
231
|
\`\`\`
|
|
238
232
|
</workflow_patterns>
|
|
@@ -243,8 +237,7 @@ Action: executeAgentTasks({
|
|
|
243
237
|
- broadcast: \`agentIds\` (array), \`instruction\` (optional shared guidance)
|
|
244
238
|
|
|
245
239
|
**Task Execution:**
|
|
246
|
-
- executeAgentTask: \`agentId\`, \`task\` (clear deliverable description), \`timeout\` (optional, default 30min)
|
|
247
|
-
- executeAgentTasks: \`tasks\` (array of {agentId, title, instruction, timeout?}) - **Use this for parallel task execution across multiple agents**
|
|
240
|
+
- executeAgentTask: \`agentId\`, \`title\`, \`task\` (clear deliverable description), \`timeout\` (optional, default 30min)
|
|
248
241
|
- interrupt: \`taskId\`
|
|
249
242
|
|
|
250
243
|
**Flow Control:**
|
|
@@ -452,6 +452,47 @@ export interface TriggerExecuteTaskParams extends GroupOrchestrationBaseParams {
|
|
|
452
452
|
toolMessageId: string;
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Task item for triggerExecuteTasks callback
|
|
457
|
+
*/
|
|
458
|
+
export interface TriggerExecuteTaskItem {
|
|
459
|
+
/**
|
|
460
|
+
* The agent ID to execute this task
|
|
461
|
+
*/
|
|
462
|
+
agentId: string;
|
|
463
|
+
/**
|
|
464
|
+
* Detailed instruction/prompt for the task execution
|
|
465
|
+
*/
|
|
466
|
+
instruction: string;
|
|
467
|
+
/**
|
|
468
|
+
* Optional timeout in milliseconds for this specific task
|
|
469
|
+
*/
|
|
470
|
+
timeout?: number;
|
|
471
|
+
/**
|
|
472
|
+
* Brief title describing what this task does (shown in UI)
|
|
473
|
+
*/
|
|
474
|
+
title: string;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Params for triggerExecuteTasks callback (multiple tasks)
|
|
479
|
+
*/
|
|
480
|
+
export interface TriggerExecuteTasksParams extends GroupOrchestrationBaseParams {
|
|
481
|
+
/**
|
|
482
|
+
* If true, the orchestration will end after all tasks complete,
|
|
483
|
+
* without calling the supervisor again.
|
|
484
|
+
*/
|
|
485
|
+
skipCallSupervisor?: boolean;
|
|
486
|
+
/**
|
|
487
|
+
* Array of tasks to execute, each assigned to a specific agent
|
|
488
|
+
*/
|
|
489
|
+
tasks: TriggerExecuteTaskItem[];
|
|
490
|
+
/**
|
|
491
|
+
* The tool message ID that triggered the tasks
|
|
492
|
+
*/
|
|
493
|
+
toolMessageId: string;
|
|
494
|
+
}
|
|
495
|
+
|
|
455
496
|
/**
|
|
456
497
|
* Group Orchestration callbacks for group management tools
|
|
457
498
|
* These callbacks are used to trigger the next phase in multi-agent orchestration
|
|
@@ -472,6 +513,11 @@ export interface GroupOrchestrationCallbacks {
|
|
|
472
513
|
*/
|
|
473
514
|
triggerExecuteTask: (params: TriggerExecuteTaskParams) => Promise<void>;
|
|
474
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Trigger async execution of multiple tasks in parallel
|
|
518
|
+
*/
|
|
519
|
+
triggerExecuteTasks: (params: TriggerExecuteTasksParams) => Promise<void>;
|
|
520
|
+
|
|
475
521
|
/**
|
|
476
522
|
* Trigger speak to a specific agent
|
|
477
523
|
*/
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AccordionItem, Block, Text } from '@lobehub/ui';
|
|
4
|
+
import { memo, useMemo, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { ThreadStatus } from '@/types/index';
|
|
7
|
+
import type { UIChatMessage } from '@/types/index';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
CompletedState,
|
|
11
|
+
ErrorState,
|
|
12
|
+
InitializingState,
|
|
13
|
+
ProcessingState,
|
|
14
|
+
isProcessingStatus,
|
|
15
|
+
} from '../shared';
|
|
16
|
+
import TaskTitle, { type TaskMetrics } from './TaskTitle';
|
|
17
|
+
import { useClientTaskStats } from './useClientTaskStats';
|
|
18
|
+
|
|
19
|
+
interface ClientTaskItemProps {
|
|
20
|
+
item: UIChatMessage;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ClientTaskItem = memo<ClientTaskItemProps>(({ item }) => {
|
|
24
|
+
const { id, content, metadata, taskDetail } = item;
|
|
25
|
+
const [expanded, setExpanded] = useState(false);
|
|
26
|
+
|
|
27
|
+
const title = taskDetail?.title || metadata?.taskTitle;
|
|
28
|
+
const instruction = metadata?.instruction;
|
|
29
|
+
const status = taskDetail?.status;
|
|
30
|
+
|
|
31
|
+
const isProcessing = isProcessingStatus(status);
|
|
32
|
+
const isCompleted = status === ThreadStatus.Completed;
|
|
33
|
+
const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
|
|
34
|
+
const isInitializing = !taskDetail || !status;
|
|
35
|
+
|
|
36
|
+
// Fetch client task stats when processing
|
|
37
|
+
const clientStats = useClientTaskStats({
|
|
38
|
+
enabled: isProcessing,
|
|
39
|
+
threadId: taskDetail?.threadId,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Build metrics for TaskTitle
|
|
43
|
+
const metrics: TaskMetrics | undefined = useMemo(() => {
|
|
44
|
+
if (isProcessing) {
|
|
45
|
+
return {
|
|
46
|
+
isLoading: clientStats.isLoading,
|
|
47
|
+
startTime: clientStats.startTime,
|
|
48
|
+
steps: clientStats.steps,
|
|
49
|
+
toolCalls: clientStats.toolCalls,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (isCompleted || isError) {
|
|
53
|
+
return {
|
|
54
|
+
duration: taskDetail?.duration,
|
|
55
|
+
steps: taskDetail?.totalSteps,
|
|
56
|
+
toolCalls: taskDetail?.totalToolCalls,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}, [
|
|
61
|
+
isProcessing,
|
|
62
|
+
isCompleted,
|
|
63
|
+
isError,
|
|
64
|
+
clientStats,
|
|
65
|
+
taskDetail?.duration,
|
|
66
|
+
taskDetail?.totalSteps,
|
|
67
|
+
taskDetail?.totalToolCalls,
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<AccordionItem
|
|
72
|
+
expand={expanded}
|
|
73
|
+
itemKey={id}
|
|
74
|
+
onExpandChange={setExpanded}
|
|
75
|
+
paddingBlock={4}
|
|
76
|
+
paddingInline={4}
|
|
77
|
+
title={<TaskTitle metrics={metrics} status={status} title={title} />}
|
|
78
|
+
>
|
|
79
|
+
<Block gap={16} padding={12} style={{ marginBlock: 8 }} variant={'outlined'}>
|
|
80
|
+
{instruction && (
|
|
81
|
+
<Block padding={12}>
|
|
82
|
+
<Text fontSize={13} type={'secondary'}>
|
|
83
|
+
{instruction}
|
|
84
|
+
</Text>
|
|
85
|
+
</Block>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{/* Initializing State - no taskDetail yet */}
|
|
89
|
+
{isInitializing && <InitializingState />}
|
|
90
|
+
|
|
91
|
+
{/* Processing State */}
|
|
92
|
+
{!isInitializing && isProcessing && taskDetail && (
|
|
93
|
+
<ProcessingState messageId={id} taskDetail={taskDetail} variant="compact" />
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{/* Error State */}
|
|
97
|
+
{!isInitializing && isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
|
|
98
|
+
|
|
99
|
+
{/* Completed State */}
|
|
100
|
+
{!isInitializing && isCompleted && taskDetail && (
|
|
101
|
+
<CompletedState
|
|
102
|
+
content={content}
|
|
103
|
+
expanded={expanded}
|
|
104
|
+
taskDetail={taskDetail}
|
|
105
|
+
variant="compact"
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
</Block>
|
|
109
|
+
</AccordionItem>
|
|
110
|
+
);
|
|
111
|
+
}, Object.is);
|
|
112
|
+
|
|
113
|
+
ClientTaskItem.displayName = 'ClientTaskItem';
|
|
114
|
+
|
|
115
|
+
export default ClientTaskItem;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AccordionItem, Block, Text } from '@lobehub/ui';
|
|
4
|
+
import { memo, useMemo, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
import { ThreadStatus } from '@/types/index';
|
|
7
|
+
import type { UIChatMessage } from '@/types/index';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
CompletedState,
|
|
11
|
+
ErrorState,
|
|
12
|
+
InitializingState,
|
|
13
|
+
ProcessingState,
|
|
14
|
+
isProcessingStatus,
|
|
15
|
+
} from '../shared';
|
|
16
|
+
import TaskTitle, { type TaskMetrics } from './TaskTitle';
|
|
17
|
+
|
|
18
|
+
interface ServerTaskItemProps {
|
|
19
|
+
item: UIChatMessage;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ServerTaskItem = memo<ServerTaskItemProps>(({ item }) => {
|
|
23
|
+
const { id, content, metadata, taskDetail } = item;
|
|
24
|
+
const [expanded, setExpanded] = useState(false);
|
|
25
|
+
|
|
26
|
+
const title = taskDetail?.title || metadata?.taskTitle;
|
|
27
|
+
const instruction = metadata?.instruction;
|
|
28
|
+
const status = taskDetail?.status;
|
|
29
|
+
|
|
30
|
+
const isProcessing = isProcessingStatus(status);
|
|
31
|
+
const isCompleted = status === ThreadStatus.Completed;
|
|
32
|
+
const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
|
|
33
|
+
const isInitializing = !taskDetail || !status;
|
|
34
|
+
|
|
35
|
+
// Build metrics for TaskTitle (only for completed/error states)
|
|
36
|
+
const metrics: TaskMetrics | undefined = useMemo(() => {
|
|
37
|
+
if (isCompleted || isError) {
|
|
38
|
+
return {
|
|
39
|
+
duration: taskDetail?.duration,
|
|
40
|
+
steps: taskDetail?.totalSteps,
|
|
41
|
+
toolCalls: taskDetail?.totalToolCalls,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}, [isCompleted, isError, taskDetail?.duration, taskDetail?.totalSteps, taskDetail?.totalToolCalls]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<AccordionItem
|
|
49
|
+
expand={expanded}
|
|
50
|
+
itemKey={id}
|
|
51
|
+
onExpandChange={setExpanded}
|
|
52
|
+
paddingBlock={4}
|
|
53
|
+
paddingInline={4}
|
|
54
|
+
title={<TaskTitle metrics={metrics} status={status} title={title} />}
|
|
55
|
+
>
|
|
56
|
+
<Block gap={16} padding={12} style={{ marginBlock: 8 }} variant={'outlined'}>
|
|
57
|
+
{instruction && (
|
|
58
|
+
<Block padding={12}>
|
|
59
|
+
<Text fontSize={13} type={'secondary'}>
|
|
60
|
+
{instruction}
|
|
61
|
+
</Text>
|
|
62
|
+
</Block>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{/* Initializing State - no taskDetail yet */}
|
|
66
|
+
{isInitializing && <InitializingState />}
|
|
67
|
+
|
|
68
|
+
{/* Processing State */}
|
|
69
|
+
{!isInitializing && isProcessing && taskDetail && (
|
|
70
|
+
<ProcessingState messageId={id} taskDetail={taskDetail} variant="compact" />
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
{/* Error State */}
|
|
74
|
+
{!isInitializing && isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
|
|
75
|
+
|
|
76
|
+
{/* Completed State */}
|
|
77
|
+
{!isInitializing && isCompleted && taskDetail && (
|
|
78
|
+
<CompletedState
|
|
79
|
+
content={content}
|
|
80
|
+
expanded={expanded}
|
|
81
|
+
taskDetail={taskDetail}
|
|
82
|
+
variant="compact"
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
</Block>
|
|
86
|
+
</AccordionItem>
|
|
87
|
+
);
|
|
88
|
+
}, Object.is);
|
|
89
|
+
|
|
90
|
+
ServerTaskItem.displayName = 'ServerTaskItem';
|
|
91
|
+
|
|
92
|
+
export default ServerTaskItem;
|
|
@@ -2,15 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
import { Block, Flexbox, Icon, Text } from '@lobehub/ui';
|
|
4
4
|
import { cssVar } from 'antd-style';
|
|
5
|
-
import { ListChecksIcon, XIcon } from 'lucide-react';
|
|
6
|
-
import { memo } from 'react';
|
|
5
|
+
import { Footprints, ListChecksIcon, Wrench, XIcon } from 'lucide-react';
|
|
6
|
+
import { memo, useEffect, useState } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
7
8
|
|
|
8
9
|
import NeuralNetworkLoading from '@/components/NeuralNetworkLoading';
|
|
9
10
|
import { ThreadStatus } from '@/types/index';
|
|
10
11
|
|
|
11
|
-
import { isProcessingStatus } from '../shared';
|
|
12
|
+
import { formatDuration, formatElapsedTime, isProcessingStatus } from '../shared';
|
|
13
|
+
|
|
14
|
+
export interface TaskMetrics {
|
|
15
|
+
/** Task duration in milliseconds (for completed tasks) */
|
|
16
|
+
duration?: number;
|
|
17
|
+
/** Whether metrics are still loading */
|
|
18
|
+
isLoading?: boolean;
|
|
19
|
+
/** Start time timestamp for elapsed time calculation */
|
|
20
|
+
startTime?: number;
|
|
21
|
+
/** Number of execution steps/blocks */
|
|
22
|
+
steps?: number;
|
|
23
|
+
/** Total tool calls count */
|
|
24
|
+
toolCalls?: number;
|
|
25
|
+
}
|
|
12
26
|
|
|
13
27
|
interface TaskTitleProps {
|
|
28
|
+
/** Metrics to display (steps, tool calls, elapsed time) */
|
|
29
|
+
metrics?: TaskMetrics;
|
|
14
30
|
status?: ThreadStatus;
|
|
15
31
|
title?: string;
|
|
16
32
|
}
|
|
@@ -54,13 +70,90 @@ const TaskStatusIndicator = memo<{ status?: ThreadStatus }>(({ status }) => {
|
|
|
54
70
|
|
|
55
71
|
TaskStatusIndicator.displayName = 'TaskStatusIndicator';
|
|
56
72
|
|
|
57
|
-
|
|
73
|
+
interface MetricsDisplayProps {
|
|
74
|
+
metrics: TaskMetrics;
|
|
75
|
+
status?: ThreadStatus;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const MetricsDisplay = memo<MetricsDisplayProps>(({ metrics, status }) => {
|
|
79
|
+
const { t } = useTranslation('chat');
|
|
80
|
+
const { steps, toolCalls, startTime, duration, isLoading } = metrics;
|
|
81
|
+
const [elapsedTime, setElapsedTime] = useState(0);
|
|
82
|
+
|
|
83
|
+
const isProcessing = status ? isProcessingStatus(status) : false;
|
|
84
|
+
|
|
85
|
+
// Calculate initial elapsed time
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
if (startTime && isProcessing) {
|
|
88
|
+
setElapsedTime(Math.max(0, Date.now() - startTime));
|
|
89
|
+
}
|
|
90
|
+
}, [startTime, isProcessing]);
|
|
91
|
+
|
|
92
|
+
// Timer for updating elapsed time every second (only when processing)
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (!startTime || !isProcessing) return;
|
|
95
|
+
|
|
96
|
+
const timer = setInterval(() => {
|
|
97
|
+
setElapsedTime(Math.max(0, Date.now() - startTime));
|
|
98
|
+
}, 1000);
|
|
99
|
+
|
|
100
|
+
return () => clearInterval(timer);
|
|
101
|
+
}, [startTime, isProcessing]);
|
|
102
|
+
|
|
103
|
+
// Don't show metrics if loading or no data
|
|
104
|
+
if (isLoading) return null;
|
|
105
|
+
|
|
106
|
+
const hasSteps = steps !== undefined && steps > 0;
|
|
107
|
+
const hasToolCalls = toolCalls !== undefined && toolCalls > 0;
|
|
108
|
+
const hasTime = isProcessing ? startTime !== undefined : duration !== undefined;
|
|
109
|
+
|
|
110
|
+
// Don't render if no metrics to show
|
|
111
|
+
if (!hasSteps && !hasToolCalls && !hasTime) return null;
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Flexbox align="center" gap={8} horizontal>
|
|
115
|
+
{/* Steps */}
|
|
116
|
+
{hasSteps && (
|
|
117
|
+
<Flexbox align="center" gap={2} horizontal>
|
|
118
|
+
<Icon color={cssVar.colorTextTertiary} icon={Footprints} size={12} />
|
|
119
|
+
<Text fontSize={12} type="secondary">
|
|
120
|
+
{steps}
|
|
121
|
+
</Text>
|
|
122
|
+
</Flexbox>
|
|
123
|
+
)}
|
|
124
|
+
{/* Tool calls */}
|
|
125
|
+
{hasToolCalls && (
|
|
126
|
+
<Flexbox align="center" gap={2} horizontal>
|
|
127
|
+
<Icon color={cssVar.colorTextTertiary} icon={Wrench} size={12} />
|
|
128
|
+
<Text fontSize={12} type="secondary">
|
|
129
|
+
{toolCalls}
|
|
130
|
+
</Text>
|
|
131
|
+
</Flexbox>
|
|
132
|
+
)}
|
|
133
|
+
{/* Time */}
|
|
134
|
+
{hasTime && (
|
|
135
|
+
<Text fontSize={12} type="secondary">
|
|
136
|
+
{isProcessing
|
|
137
|
+
? formatElapsedTime(elapsedTime)
|
|
138
|
+
: duration
|
|
139
|
+
? t('task.metrics.duration', { duration: formatDuration(duration) })
|
|
140
|
+
: null}
|
|
141
|
+
</Text>
|
|
142
|
+
)}
|
|
143
|
+
</Flexbox>
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
MetricsDisplay.displayName = 'MetricsDisplay';
|
|
148
|
+
|
|
149
|
+
const TaskTitle = memo<TaskTitleProps>(({ title, status, metrics }) => {
|
|
58
150
|
return (
|
|
59
|
-
<Flexbox align=
|
|
151
|
+
<Flexbox align="center" gap={6} horizontal>
|
|
60
152
|
<TaskStatusIndicator status={status} />
|
|
61
153
|
<Text ellipsis fontSize={14}>
|
|
62
154
|
{title}
|
|
63
155
|
</Text>
|
|
156
|
+
{metrics && <MetricsDisplay metrics={metrics} status={status} />}
|
|
64
157
|
</Flexbox>
|
|
65
158
|
);
|
|
66
159
|
});
|
|
@@ -1,81 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { memo
|
|
3
|
+
import isEqual from 'fast-deep-equal';
|
|
4
|
+
import { memo } from 'react';
|
|
5
5
|
|
|
6
|
-
import { ThreadStatus } from '@/types/index';
|
|
7
6
|
import type { UIChatMessage } from '@/types/index';
|
|
8
7
|
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
ErrorState,
|
|
12
|
-
InitializingState,
|
|
13
|
-
ProcessingState,
|
|
14
|
-
isProcessingStatus,
|
|
15
|
-
} from '../shared';
|
|
16
|
-
import TaskTitle from './TaskTitle';
|
|
8
|
+
import ClientTaskItem from './ClientTaskItem';
|
|
9
|
+
import ServerTaskItem from './ServerTaskItem';
|
|
17
10
|
|
|
18
11
|
interface TaskItemProps {
|
|
19
12
|
item: UIChatMessage;
|
|
20
13
|
}
|
|
21
14
|
|
|
22
15
|
const TaskItem = memo<TaskItemProps>(({ item }) => {
|
|
23
|
-
const
|
|
24
|
-
const [expanded, setExpanded] = useState(false);
|
|
16
|
+
const isClientMode = item.taskDetail?.clientMode;
|
|
25
17
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
if (isClientMode) {
|
|
19
|
+
return <ClientTaskItem item={item} />;
|
|
20
|
+
}
|
|
29
21
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const isCompleted = status === ThreadStatus.Completed;
|
|
34
|
-
const isError = status === ThreadStatus.Failed || status === ThreadStatus.Cancel;
|
|
35
|
-
const isInitializing = !taskDetail || !status;
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<AccordionItem
|
|
39
|
-
expand={expanded}
|
|
40
|
-
itemKey={id}
|
|
41
|
-
onExpandChange={setExpanded}
|
|
42
|
-
paddingBlock={4}
|
|
43
|
-
paddingInline={4}
|
|
44
|
-
title={<TaskTitle status={status} title={title} />}
|
|
45
|
-
>
|
|
46
|
-
<Block gap={16} padding={12} style={{ marginBlock: 8 }} variant={'outlined'}>
|
|
47
|
-
{instruction && (
|
|
48
|
-
<Block padding={12}>
|
|
49
|
-
<Text fontSize={13} type={'secondary'}>
|
|
50
|
-
{instruction}
|
|
51
|
-
</Text>
|
|
52
|
-
</Block>
|
|
53
|
-
)}
|
|
54
|
-
|
|
55
|
-
{/* Initializing State - no taskDetail yet */}
|
|
56
|
-
{isInitializing && <InitializingState />}
|
|
57
|
-
|
|
58
|
-
{/* Processing State */}
|
|
59
|
-
{!isInitializing && isProcessing && taskDetail && (
|
|
60
|
-
<ProcessingState messageId={id} taskDetail={taskDetail} variant="compact" />
|
|
61
|
-
)}
|
|
62
|
-
|
|
63
|
-
{/* Error State */}
|
|
64
|
-
{!isInitializing && isError && taskDetail && <ErrorState taskDetail={taskDetail} />}
|
|
65
|
-
|
|
66
|
-
{/* Completed State */}
|
|
67
|
-
{!isInitializing && isCompleted && taskDetail && (
|
|
68
|
-
<CompletedState
|
|
69
|
-
content={content}
|
|
70
|
-
expanded={expanded}
|
|
71
|
-
taskDetail={taskDetail}
|
|
72
|
-
variant="compact"
|
|
73
|
-
/>
|
|
74
|
-
)}
|
|
75
|
-
</Block>
|
|
76
|
-
</AccordionItem>
|
|
77
|
-
);
|
|
78
|
-
}, Object.is);
|
|
22
|
+
return <ServerTaskItem item={item} />;
|
|
23
|
+
}, isEqual);
|
|
79
24
|
|
|
80
25
|
TaskItem.displayName = 'TaskItem';
|
|
81
26
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useChatStore } from '@/store/chat';
|
|
6
|
+
import { displayMessageSelectors } from '@/store/chat/selectors';
|
|
7
|
+
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
8
|
+
|
|
9
|
+
export interface ClientTaskStats {
|
|
10
|
+
isLoading: boolean;
|
|
11
|
+
startTime?: number;
|
|
12
|
+
steps: number;
|
|
13
|
+
toolCalls: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UseClientTaskStatsOptions {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
threadId?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook to fetch thread messages and compute task statistics for client mode tasks.
|
|
23
|
+
* Used in TaskItem to display progress metrics (steps, tool calls, elapsed time).
|
|
24
|
+
*/
|
|
25
|
+
export const useClientTaskStats = ({
|
|
26
|
+
threadId,
|
|
27
|
+
enabled = true,
|
|
28
|
+
}: UseClientTaskStatsOptions): ClientTaskStats => {
|
|
29
|
+
const [activeAgentId, activeTopicId, useFetchMessages] = useChatStore((s) => [
|
|
30
|
+
s.activeAgentId,
|
|
31
|
+
s.activeTopicId,
|
|
32
|
+
s.useFetchMessages,
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const threadContext = useMemo(
|
|
36
|
+
() => ({
|
|
37
|
+
agentId: activeAgentId,
|
|
38
|
+
scope: 'thread' as const,
|
|
39
|
+
threadId,
|
|
40
|
+
topicId: activeTopicId,
|
|
41
|
+
}),
|
|
42
|
+
[activeAgentId, activeTopicId, threadId],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const threadMessageKey = useMemo(
|
|
46
|
+
() => (threadId ? messageMapKey(threadContext) : null),
|
|
47
|
+
[threadId, threadContext],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Fetch thread messages (skip when disabled or no threadId)
|
|
51
|
+
useFetchMessages(threadContext, !enabled || !threadId);
|
|
52
|
+
|
|
53
|
+
// Get thread messages from store using selector
|
|
54
|
+
const threadMessages = useChatStore((s) =>
|
|
55
|
+
threadMessageKey
|
|
56
|
+
? displayMessageSelectors.getDisplayMessagesByKey(threadMessageKey)(s)
|
|
57
|
+
: undefined,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Compute stats from thread messages
|
|
61
|
+
return useMemo(() => {
|
|
62
|
+
if (!threadMessages || !enabled) {
|
|
63
|
+
return { isLoading: true, steps: 0, toolCalls: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Find the assistantGroup message which contains the children blocks
|
|
67
|
+
const assistantGroupMessage = threadMessages.find((item) => item.role === 'assistantGroup');
|
|
68
|
+
const blocks = assistantGroupMessage?.children ?? [];
|
|
69
|
+
|
|
70
|
+
// Calculate stats
|
|
71
|
+
const steps = blocks.length;
|
|
72
|
+
const toolCalls = blocks.reduce((sum, block) => sum + (block.tools?.length || 0), 0);
|
|
73
|
+
const startTime = assistantGroupMessage?.createdAt;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
isLoading: false,
|
|
77
|
+
startTime,
|
|
78
|
+
steps,
|
|
79
|
+
toolCalls,
|
|
80
|
+
};
|
|
81
|
+
}, [threadMessages, enabled]);
|
|
82
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preload Tool Render components to avoid Suspense flash on first expand
|
|
3
|
+
*
|
|
4
|
+
* These components are dynamically imported in Tool/Tool/index.tsx.
|
|
5
|
+
* By preloading them when tool calls are detected, we can avoid
|
|
6
|
+
* the loading skeleton flash when user first expands the tool result.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
let preloaded = false;
|
|
10
|
+
|
|
11
|
+
export const preloadToolRenderComponents = () => {
|
|
12
|
+
if (preloaded) return;
|
|
13
|
+
preloaded = true;
|
|
14
|
+
|
|
15
|
+
// Preload Detail and Debug components (dynamic imports in Tool/Tool/index.tsx)
|
|
16
|
+
import('../AssistantGroup/Tool/Detail');
|
|
17
|
+
import('../AssistantGroup/Tool/Debug');
|
|
18
|
+
};
|
|
@@ -73,6 +73,12 @@ export interface GroupOrchestrationAction {
|
|
|
73
73
|
*/
|
|
74
74
|
triggerExecuteTask: GroupOrchestrationCallbacks['triggerExecuteTask'];
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Trigger execute tasks - called by executeTasks tool when supervisor decides to execute multiple async tasks in parallel
|
|
78
|
+
* This starts the group orchestration loop with supervisor_decided result
|
|
79
|
+
*/
|
|
80
|
+
triggerExecuteTasks: GroupOrchestrationCallbacks['triggerExecuteTasks'];
|
|
81
|
+
|
|
76
82
|
/**
|
|
77
83
|
* Enable polling for task status
|
|
78
84
|
* Used by ProcessingState component to poll for real-time task updates
|
|
@@ -240,6 +246,42 @@ export const groupOrchestrationSlice: StateCreator<
|
|
|
240
246
|
});
|
|
241
247
|
},
|
|
242
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Trigger execute tasks - Entry point when supervisor calls executeTasks tool
|
|
251
|
+
* Creates a supervisor_decided result with decision='execute_tasks' and starts orchestration
|
|
252
|
+
*/
|
|
253
|
+
triggerExecuteTasks: async (params) => {
|
|
254
|
+
const { supervisorAgentId, tasks, toolMessageId, skipCallSupervisor } = params;
|
|
255
|
+
log(
|
|
256
|
+
'[triggerExecuteTasks] Starting orchestration with execute_tasks: supervisorAgentId=%s, tasks=%d, toolMessageId=%s, skipCallSupervisor=%s',
|
|
257
|
+
supervisorAgentId,
|
|
258
|
+
tasks.length,
|
|
259
|
+
toolMessageId,
|
|
260
|
+
skipCallSupervisor,
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const groupId = get().activeGroupId;
|
|
264
|
+
if (!groupId) {
|
|
265
|
+
log('[triggerExecuteTasks] No active group, skipping');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Start orchestration loop with supervisor_decided result (decision=execute_tasks)
|
|
270
|
+
await get().internal_execGroupOrchestration({
|
|
271
|
+
groupId,
|
|
272
|
+
supervisorAgentId,
|
|
273
|
+
topicId: get().activeTopicId,
|
|
274
|
+
initialResult: {
|
|
275
|
+
type: 'supervisor_decided',
|
|
276
|
+
payload: {
|
|
277
|
+
decision: 'execute_tasks',
|
|
278
|
+
params: { tasks, toolMessageId },
|
|
279
|
+
skipCallSupervisor: skipCallSupervisor ?? false,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
|
|
243
285
|
/**
|
|
244
286
|
* Get group orchestration callbacks
|
|
245
287
|
* These are the action methods that tools can call to trigger orchestration
|
|
@@ -250,6 +292,7 @@ export const groupOrchestrationSlice: StateCreator<
|
|
|
250
292
|
triggerBroadcast: get().triggerBroadcast,
|
|
251
293
|
triggerDelegate: get().triggerDelegate,
|
|
252
294
|
triggerExecuteTask: get().triggerExecuteTask,
|
|
295
|
+
triggerExecuteTasks: get().triggerExecuteTasks,
|
|
253
296
|
};
|
|
254
297
|
},
|
|
255
298
|
|
|
@@ -736,8 +736,13 @@ export const streamingExecutor: StateCreator<
|
|
|
736
736
|
// After parallel tool batch completes, refresh messages to ensure all tool results are synced
|
|
737
737
|
// This fixes the race condition where each tool's replaceMessages may overwrite others
|
|
738
738
|
// REMEMBER: There is no test for it (too hard to add), if you want to change it , ask @arvinxx first
|
|
739
|
-
if (
|
|
740
|
-
|
|
739
|
+
if (
|
|
740
|
+
result.nextContext?.phase &&
|
|
741
|
+
['tasks_batch_result', 'tools_batch_result'].includes(result.nextContext?.phase)
|
|
742
|
+
) {
|
|
743
|
+
log(
|
|
744
|
+
`[internal_execAgentRuntime] ${result.nextContext?.phase} completed, refreshing messages to sync state`,
|
|
745
|
+
);
|
|
741
746
|
await get().refreshMessages(context);
|
|
742
747
|
}
|
|
743
748
|
|