Skip to content

Commit 9de773b

Browse files
arvinxxclaude
andauthored
🐛 fix: fix multi agent tasks issue (lobehub#11672)
* improve run multi tasks ui * improve group mode * 🐛 fix: remove unused isCompleted variable in TaskTitle 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8443904 commit 9de773b

File tree

17 files changed

+744
-150
lines changed

17 files changed

+744
-150
lines changed

packages/agent-runtime/src/groupOrchestration/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,11 @@ export interface ExecutorResultSupervisorDecided {
118118
* - 'speak': Call a single agent
119119
* - 'broadcast': Call multiple agents in parallel
120120
* - 'delegate': Delegate to another agent
121-
* - 'execute_task': Execute an async task
121+
* - 'execute_task': Execute a single async task
122+
* - 'execute_tasks': Execute multiple async tasks in parallel
122123
* - 'finish': End the orchestration
123124
*/
124-
decision: 'speak' | 'broadcast' | 'delegate' | 'execute_task' | 'finish';
125+
decision: 'speak' | 'broadcast' | 'delegate' | 'execute_task' | 'execute_tasks' | 'finish';
125126
/**
126127
* Parameters for the decision
127128
*/
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

packages/builtin-tool-group-management/src/client/Render/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { GroupManagementApiName } from '../../types';
22
import BroadcastRender from './Broadcast';
33
import ExecuteTaskRender from './ExecuteTask';
4+
import ExecuteTasksRender from './ExecuteTasks';
45
import SpeakRender from './Speak';
56

67
/**
@@ -9,9 +10,11 @@ import SpeakRender from './Speak';
910
export const GroupManagementRenders = {
1011
[GroupManagementApiName.broadcast]: BroadcastRender,
1112
[GroupManagementApiName.executeAgentTask]: ExecuteTaskRender,
13+
[GroupManagementApiName.executeAgentTasks]: ExecuteTasksRender,
1214
[GroupManagementApiName.speak]: SpeakRender,
1315
};
1416

1517
export { default as BroadcastRender } from './Broadcast';
1618
export { default as ExecuteTaskRender } from './ExecuteTask';
19+
export { default as ExecuteTasksRender } from './ExecuteTasks';
1720
export { default as SpeakRender } from './Speak';

packages/builtin-tool-group-management/src/executor.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ describe('GroupManagementExecutor', () => {
7575
triggerBroadcast: vi.fn(),
7676
triggerDelegate: vi.fn(),
7777
triggerExecuteTask: vi.fn(),
78+
triggerExecuteTasks: vi.fn(),
7879
triggerSpeak,
7980
},
8081
'supervisor-agent',
@@ -122,6 +123,7 @@ describe('GroupManagementExecutor', () => {
122123
triggerBroadcast: vi.fn(),
123124
triggerDelegate: vi.fn(),
124125
triggerExecuteTask: vi.fn(),
126+
triggerExecuteTasks: vi.fn(),
125127
triggerSpeak,
126128
},
127129
'supervisor-agent',
@@ -171,6 +173,7 @@ describe('GroupManagementExecutor', () => {
171173
triggerBroadcast,
172174
triggerDelegate: vi.fn(),
173175
triggerExecuteTask: vi.fn(),
176+
triggerExecuteTasks: vi.fn(),
174177
triggerSpeak: vi.fn(),
175178
},
176179
'supervisor-agent',
@@ -238,6 +241,7 @@ describe('GroupManagementExecutor', () => {
238241
triggerBroadcast: vi.fn(),
239242
triggerDelegate,
240243
triggerExecuteTask: vi.fn(),
244+
triggerExecuteTasks: vi.fn(),
241245
triggerSpeak: vi.fn(),
242246
},
243247
'supervisor-agent',
@@ -308,6 +312,7 @@ describe('GroupManagementExecutor', () => {
308312
triggerBroadcast: vi.fn(),
309313
triggerDelegate: vi.fn(),
310314
triggerExecuteTask,
315+
triggerExecuteTasks: vi.fn(),
311316
triggerSpeak: vi.fn(),
312317
},
313318
'supervisor-agent',

packages/builtin-tool-group-management/src/executor.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
CreateWorkflowParams,
1212
DelegateParams,
1313
ExecuteTaskParams,
14+
ExecuteTasksParams,
1415
GroupManagementApiName,
1516
GroupManagementIdentifier,
1617
InterruptParams,
@@ -153,6 +154,38 @@ class GroupManagementExecutor extends BaseExecutor<typeof GroupManagementApiName
153154
};
154155
};
155156

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+
156189
interrupt = async (
157190
params: InterruptParams,
158191
_ctx: BuiltinToolContext,

packages/builtin-tool-group-management/src/manifest.ts

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -121,52 +121,53 @@ export const GroupManagementManifest: BuiltinToolManifest = {
121121
type: 'object',
122122
},
123123
},
124-
{
125-
description:
126-
'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.',
127-
name: GroupManagementApiName.executeAgentTasks,
128-
humanIntervention: 'required',
129-
parameters: {
130-
properties: {
131-
tasks: {
132-
description: 'Array of tasks, each assigned to a specific agent.',
133-
items: {
134-
properties: {
135-
agentId: {
136-
description: 'The ID of the agent to execute this task.',
137-
type: 'string',
138-
},
139-
title: {
140-
description: 'Brief title describing what this task does (shown in UI).',
141-
type: 'string',
142-
},
143-
instruction: {
144-
description:
145-
'Detailed instruction/prompt for the task execution. Be specific about expected deliverables.',
146-
type: 'string',
147-
},
148-
timeout: {
149-
description:
150-
'Optional timeout in milliseconds for this task (default: 1800000, 30 minutes).',
151-
type: 'number',
152-
},
153-
},
154-
required: ['agentId', 'title', 'instruction'],
155-
type: 'object',
156-
},
157-
type: 'array',
158-
},
159-
skipCallSupervisor: {
160-
default: false,
161-
description:
162-
'If true, the orchestration will end after all tasks complete, without calling the supervisor again.',
163-
type: 'boolean',
164-
},
165-
},
166-
required: ['tasks'],
167-
type: 'object',
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+
// },
170171
{
171172
description:
172173
'Interrupt a running agent task. Use this to stop a task that is taking too long or is no longer needed.',

0 commit comments

Comments
 (0)