@lobehub/lobehub 2.0.0-next.312 → 2.0.0-next.313
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 +26 -0
- package/apps/desktop/src/main/controllers/AuthCtr.ts +75 -7
- package/changelog/v1.json +9 -0
- package/docs/usage/providers/internlm.mdx +2 -2
- package/docs/usage/providers/internlm.zh-CN.mdx +3 -3
- package/locales/en-US/error.json +10 -1
- package/locales/en-US/subscription.json +1 -1
- package/locales/zh-CN/desktop-onboarding.json +5 -0
- package/locales/zh-CN/error.json +10 -1
- package/locales/zh-CN/subscription.json +1 -1
- package/package.json +1 -1
- package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +14 -2
- package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +275 -1
- package/packages/builtin-tool-cloud-sandbox/package.json +1 -0
- package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +105 -134
- package/packages/builtin-tool-cloud-sandbox/src/executor/index.ts +254 -0
- package/packages/builtin-tool-cloud-sandbox/src/index.ts +1 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/api.ts +22 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/index.ts +4 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/params.ts +85 -0
- package/packages/builtin-tool-cloud-sandbox/src/types/service.ts +48 -0
- package/packages/builtin-tool-cloud-sandbox/src/{types.ts → types/state.ts} +0 -23
- package/packages/builtin-tool-memory/src/manifest.ts +5 -5
- package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +1 -1
- package/packages/editor-runtime/src/__tests__/EditorRuntime.test.ts +1 -1
- package/packages/electron-client-ipc/src/events/index.ts +5 -1
- package/packages/electron-client-ipc/src/events/remoteServer.ts +23 -0
- package/packages/memory-user-memory/src/schemas/index.ts +0 -1
- package/packages/model-bank/src/modelProviders/internlm.ts +1 -1
- package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +5 -15
- package/packages/model-runtime/src/providers/internlm/index.test.ts +15 -15
- package/packages/model-runtime/src/providers/internlm/index.ts +1 -1
- package/packages/types/src/tool/intervention.ts +4 -2
- package/packages/types/src/user/preference.ts +1 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx +84 -26
- package/src/app/[variants]/(main)/_layout/DesktopAutoOidcOnFirstOpen.tsx +4 -0
- package/src/business/server/user.ts +4 -0
- package/src/features/Conversation/Messages/Task/Actions/index.tsx +0 -2
- package/src/features/Conversation/Messages/Task/index.tsx +1 -1
- package/src/features/Conversation/Messages/Tasks/shared/ProcessingState.tsx +0 -2
- package/src/features/NavPanel/components/NavPanelDraggable.tsx +0 -14
- package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -3
- package/src/features/SharePopover/index.tsx +5 -3
- package/src/hooks/useAppOrigin.ts +16 -0
- package/src/layout/GlobalProvider/useUserStateRedirect.ts +37 -24
- package/src/libs/trusted-client/index.ts +2 -5
- package/src/locales/default/desktop-onboarding.ts +5 -0
- package/src/locales/default/error.ts +11 -0
- package/src/locales/default/subscription.ts +1 -1
- package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -0
- package/src/server/routers/lambda/user.ts +24 -10
- package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +3 -0
- package/src/server/services/agentRuntime/AgentRuntimeService.ts +8 -5
- package/src/server/services/agentRuntime/types.ts +7 -0
- package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +3 -0
- package/src/server/services/aiAgent/index.ts +10 -4
- package/src/server/services/market/index.ts +7 -0
- package/src/server/services/sandbox/index.ts +120 -0
- package/src/server/services/toolExecution/builtin.ts +12 -18
- package/src/server/services/toolExecution/index.ts +1 -1
- package/src/server/services/toolExecution/serverRuntimes/cloudSandbox.ts +31 -0
- package/src/server/services/toolExecution/serverRuntimes/index.ts +55 -0
- package/src/server/services/toolExecution/serverRuntimes/types.ts +14 -0
- package/src/server/services/toolExecution/serverRuntimes/webBrowsing.ts +20 -0
- package/src/server/services/toolExecution/types.ts +2 -0
- package/src/services/{codeInterpreter.ts → cloudSandbox.ts} +3 -3
- package/src/services/electron/remoteServer.ts +8 -0
- package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +626 -0
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +294 -0
- package/src/store/chat/slices/plugin/action.test.ts +0 -48
- package/src/store/chat/slices/plugin/actions/pluginTypes.ts +0 -131
- package/src/store/tool/slices/builtin/executors/index.ts +2 -0
- package/src/store/user/slices/settings/selectors/toolIntervention.test.ts +143 -0
- package/src/store/user/slices/settings/selectors/toolIntervention.ts +11 -2
- package/packages/memory-user-memory/src/schemas/jsonSchemas.ts +0 -37
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import type { AgentState } from '@lobechat/agent-runtime';
|
|
2
|
+
import { ThreadStatus } from '@lobechat/types';
|
|
3
|
+
import { nanoid } from '@lobechat/utils';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { aiAgentService } from '@/services/aiAgent';
|
|
7
|
+
import type { ChatStore } from '@/store/chat/store';
|
|
8
|
+
|
|
9
|
+
import { createGroupOrchestrationExecutors } from '../createGroupOrchestrationExecutors';
|
|
10
|
+
|
|
11
|
+
vi.mock('@/services/aiAgent', () => ({
|
|
12
|
+
aiAgentService: {
|
|
13
|
+
execSubAgentTask: vi.fn(),
|
|
14
|
+
getSubAgentTaskStatus: vi.fn(),
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper to create a mock ExecSubAgentTaskResult
|
|
20
|
+
*/
|
|
21
|
+
const createMockExecResult = (overrides: Record<string, any> = {}) => ({
|
|
22
|
+
assistantMessageId: `assistant_${nanoid()}`,
|
|
23
|
+
operationId: `op_${nanoid()}`,
|
|
24
|
+
success: true,
|
|
25
|
+
threadId: `thread_${nanoid()}`,
|
|
26
|
+
...overrides,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const TEST_IDS = {
|
|
30
|
+
AGENT_1_ID: 'test-agent-1-id',
|
|
31
|
+
AGENT_2_ID: 'test-agent-2-id',
|
|
32
|
+
GROUP_ID: 'test-group-id',
|
|
33
|
+
OPERATION_ID: 'test-operation-id',
|
|
34
|
+
ORCHESTRATION_OPERATION_ID: 'test-orchestration-operation-id',
|
|
35
|
+
SUPERVISOR_AGENT_ID: 'test-supervisor-agent-id',
|
|
36
|
+
TOOL_MESSAGE_ID: 'test-tool-message-id',
|
|
37
|
+
TOPIC_ID: 'test-topic-id',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a minimal mock store for group orchestration executor tests
|
|
42
|
+
*/
|
|
43
|
+
const createMockStore = (overrides: Partial<ChatStore> = {}): ChatStore => {
|
|
44
|
+
const operations: Record<string, any> = {
|
|
45
|
+
[TEST_IDS.OPERATION_ID]: {
|
|
46
|
+
abortController: new AbortController(),
|
|
47
|
+
context: {},
|
|
48
|
+
id: TEST_IDS.OPERATION_ID,
|
|
49
|
+
status: 'running',
|
|
50
|
+
type: 'agent',
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
dbMessagesMap: {},
|
|
56
|
+
internal_dispatchMessage: vi.fn(),
|
|
57
|
+
internal_execAgentRuntime: vi.fn().mockResolvedValue(undefined),
|
|
58
|
+
messagesMap: {},
|
|
59
|
+
operations,
|
|
60
|
+
optimisticCreateMessage: vi.fn().mockImplementation(async () => ({
|
|
61
|
+
id: `msg_${nanoid()}`,
|
|
62
|
+
messages: [],
|
|
63
|
+
})),
|
|
64
|
+
optimisticUpdateMessageContent: vi.fn().mockResolvedValue(undefined),
|
|
65
|
+
startOperation: vi.fn().mockImplementation((config) => {
|
|
66
|
+
const operationId = `op_${nanoid()}`;
|
|
67
|
+
const abortController = new AbortController();
|
|
68
|
+
operations[operationId] = {
|
|
69
|
+
abortController,
|
|
70
|
+
context: config.context || {},
|
|
71
|
+
id: operationId,
|
|
72
|
+
status: 'running',
|
|
73
|
+
type: config.type,
|
|
74
|
+
};
|
|
75
|
+
return { abortController, operationId };
|
|
76
|
+
}),
|
|
77
|
+
...overrides,
|
|
78
|
+
} as unknown as ChatStore;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create initial agent state for testing
|
|
83
|
+
*/
|
|
84
|
+
const createInitialState = (overrides: Partial<AgentState> = {}): AgentState => {
|
|
85
|
+
return {
|
|
86
|
+
cost: {
|
|
87
|
+
calculatedAt: new Date().toISOString(),
|
|
88
|
+
currency: 'USD',
|
|
89
|
+
llm: { byModel: [], currency: 'USD', total: 0 },
|
|
90
|
+
tools: { byTool: [], currency: 'USD', total: 0 },
|
|
91
|
+
total: 0,
|
|
92
|
+
},
|
|
93
|
+
createdAt: new Date().toISOString(),
|
|
94
|
+
lastModified: new Date().toISOString(),
|
|
95
|
+
maxSteps: 10,
|
|
96
|
+
messages: [],
|
|
97
|
+
operationId: TEST_IDS.OPERATION_ID,
|
|
98
|
+
status: 'running',
|
|
99
|
+
stepCount: 0,
|
|
100
|
+
toolManifestMap: {},
|
|
101
|
+
usage: {
|
|
102
|
+
humanInteraction: {
|
|
103
|
+
approvalRequests: 0,
|
|
104
|
+
promptRequests: 0,
|
|
105
|
+
selectRequests: 0,
|
|
106
|
+
totalWaitingTimeMs: 0,
|
|
107
|
+
},
|
|
108
|
+
llm: { apiCalls: 0, processingTimeMs: 0, tokens: { input: 0, output: 0, total: 0 } },
|
|
109
|
+
tools: { byTool: [], totalCalls: 0, totalTimeMs: 0 },
|
|
110
|
+
},
|
|
111
|
+
userInterventionConfig: { allowList: [], approvalMode: 'auto' },
|
|
112
|
+
...overrides,
|
|
113
|
+
} as AgentState;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
describe('createGroupOrchestrationExecutors', () => {
|
|
117
|
+
describe('batch_exec_async_tasks executor', () => {
|
|
118
|
+
beforeEach(() => {
|
|
119
|
+
vi.clearAllMocks();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should return error result when no valid context (missing groupId or topicId)', async () => {
|
|
123
|
+
const mockStore = createMockStore();
|
|
124
|
+
|
|
125
|
+
const executors = createGroupOrchestrationExecutors({
|
|
126
|
+
get: () => mockStore,
|
|
127
|
+
messageContext: {
|
|
128
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
129
|
+
scope: 'group',
|
|
130
|
+
// Missing topicId
|
|
131
|
+
},
|
|
132
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
133
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
137
|
+
|
|
138
|
+
const result = await batchExecTasksExecutor(
|
|
139
|
+
{
|
|
140
|
+
payload: {
|
|
141
|
+
tasks: [
|
|
142
|
+
{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' },
|
|
143
|
+
{ agentId: TEST_IDS.AGENT_2_ID, task: 'Task 2', title: 'Task 2 Title' },
|
|
144
|
+
],
|
|
145
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
146
|
+
},
|
|
147
|
+
type: 'batch_exec_async_tasks',
|
|
148
|
+
},
|
|
149
|
+
createInitialState(),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
expect(result.result?.type).toBe('tasks_completed');
|
|
153
|
+
expect((result.result?.payload as any).results).toHaveLength(2);
|
|
154
|
+
expect((result.result?.payload as any).results[0].success).toBe(false);
|
|
155
|
+
expect((result.result?.payload as any).results[0].error).toBe('No valid context available');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should create task messages for all tasks in parallel', async () => {
|
|
159
|
+
const mockStore = createMockStore();
|
|
160
|
+
|
|
161
|
+
// Mock execSubAgentTask to return success
|
|
162
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
163
|
+
createMockExecResult({ threadId: 'thread-1' }),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Mock getSubAgentTaskStatus to return completed immediately
|
|
167
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
168
|
+
result: 'Task completed',
|
|
169
|
+
status: 'completed',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const executors = createGroupOrchestrationExecutors({
|
|
173
|
+
get: () => mockStore,
|
|
174
|
+
messageContext: {
|
|
175
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
176
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
177
|
+
scope: 'group',
|
|
178
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
179
|
+
},
|
|
180
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
181
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
185
|
+
|
|
186
|
+
await batchExecTasksExecutor(
|
|
187
|
+
{
|
|
188
|
+
payload: {
|
|
189
|
+
tasks: [
|
|
190
|
+
{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' },
|
|
191
|
+
{ agentId: TEST_IDS.AGENT_2_ID, task: 'Task 2', title: 'Task 2 Title' },
|
|
192
|
+
],
|
|
193
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
194
|
+
},
|
|
195
|
+
type: 'batch_exec_async_tasks',
|
|
196
|
+
},
|
|
197
|
+
createInitialState(),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Should create 2 task messages
|
|
201
|
+
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledTimes(2);
|
|
202
|
+
|
|
203
|
+
// Verify first task message creation
|
|
204
|
+
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
|
205
|
+
expect.objectContaining({
|
|
206
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
207
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
208
|
+
metadata: { instruction: 'Task 1', taskTitle: 'Task 1 Title' },
|
|
209
|
+
parentId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
210
|
+
role: 'task',
|
|
211
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
212
|
+
}),
|
|
213
|
+
expect.objectContaining({ operationId: TEST_IDS.OPERATION_ID }),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// Verify second task message creation
|
|
217
|
+
expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
|
|
218
|
+
expect.objectContaining({
|
|
219
|
+
agentId: TEST_IDS.AGENT_2_ID,
|
|
220
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
221
|
+
metadata: { instruction: 'Task 2', taskTitle: 'Task 2 Title' },
|
|
222
|
+
parentId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
223
|
+
role: 'task',
|
|
224
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
225
|
+
}),
|
|
226
|
+
expect.objectContaining({ operationId: TEST_IDS.OPERATION_ID }),
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should call execSubAgentTask for each task', async () => {
|
|
231
|
+
const mockStore = createMockStore();
|
|
232
|
+
let messageIdCounter = 0;
|
|
233
|
+
|
|
234
|
+
vi.mocked(mockStore.optimisticCreateMessage).mockImplementation(async () => ({
|
|
235
|
+
id: `msg_${++messageIdCounter}`,
|
|
236
|
+
messages: [],
|
|
237
|
+
}));
|
|
238
|
+
|
|
239
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
240
|
+
createMockExecResult({ threadId: 'thread-1' }),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
244
|
+
result: 'Task completed',
|
|
245
|
+
status: 'completed',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const executors = createGroupOrchestrationExecutors({
|
|
249
|
+
get: () => mockStore,
|
|
250
|
+
messageContext: {
|
|
251
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
252
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
253
|
+
scope: 'group',
|
|
254
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
255
|
+
},
|
|
256
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
257
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
261
|
+
|
|
262
|
+
await batchExecTasksExecutor(
|
|
263
|
+
{
|
|
264
|
+
payload: {
|
|
265
|
+
tasks: [
|
|
266
|
+
{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' },
|
|
267
|
+
{ agentId: TEST_IDS.AGENT_2_ID, task: 'Task 2', title: 'Task 2 Title' },
|
|
268
|
+
],
|
|
269
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
270
|
+
},
|
|
271
|
+
type: 'batch_exec_async_tasks',
|
|
272
|
+
},
|
|
273
|
+
createInitialState(),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Should call execSubAgentTask for both tasks
|
|
277
|
+
expect(aiAgentService.execSubAgentTask).toHaveBeenCalledTimes(2);
|
|
278
|
+
|
|
279
|
+
expect(aiAgentService.execSubAgentTask).toHaveBeenCalledWith(
|
|
280
|
+
expect.objectContaining({
|
|
281
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
282
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
283
|
+
instruction: 'Task 1',
|
|
284
|
+
title: 'Task 1 Title',
|
|
285
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
expect(aiAgentService.execSubAgentTask).toHaveBeenCalledWith(
|
|
290
|
+
expect.objectContaining({
|
|
291
|
+
agentId: TEST_IDS.AGENT_2_ID,
|
|
292
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
293
|
+
instruction: 'Task 2',
|
|
294
|
+
title: 'Task 2 Title',
|
|
295
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should return tasks_completed result with all task results', async () => {
|
|
301
|
+
const mockStore = createMockStore();
|
|
302
|
+
|
|
303
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
304
|
+
createMockExecResult({ threadId: 'thread-1' }),
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
308
|
+
result: 'Task completed successfully',
|
|
309
|
+
status: 'completed',
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const executors = createGroupOrchestrationExecutors({
|
|
313
|
+
get: () => mockStore,
|
|
314
|
+
messageContext: {
|
|
315
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
316
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
317
|
+
scope: 'group',
|
|
318
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
319
|
+
},
|
|
320
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
321
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
325
|
+
|
|
326
|
+
const result = await batchExecTasksExecutor(
|
|
327
|
+
{
|
|
328
|
+
payload: {
|
|
329
|
+
tasks: [
|
|
330
|
+
{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' },
|
|
331
|
+
{ agentId: TEST_IDS.AGENT_2_ID, task: 'Task 2', title: 'Task 2 Title' },
|
|
332
|
+
],
|
|
333
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
334
|
+
},
|
|
335
|
+
type: 'batch_exec_async_tasks',
|
|
336
|
+
},
|
|
337
|
+
createInitialState(),
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
expect(result.result?.type).toBe('tasks_completed');
|
|
341
|
+
expect((result.result?.payload as any).results).toHaveLength(2);
|
|
342
|
+
expect((result.result?.payload as any).results[0]).toMatchObject({
|
|
343
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
344
|
+
result: 'Task completed successfully',
|
|
345
|
+
success: true,
|
|
346
|
+
});
|
|
347
|
+
expect((result.result?.payload as any).results[1]).toMatchObject({
|
|
348
|
+
agentId: TEST_IDS.AGENT_2_ID,
|
|
349
|
+
result: 'Task completed successfully',
|
|
350
|
+
success: true,
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should handle task creation failure', async () => {
|
|
355
|
+
const mockStore = createMockStore();
|
|
356
|
+
|
|
357
|
+
// First task message creation fails
|
|
358
|
+
vi.mocked(mockStore.optimisticCreateMessage)
|
|
359
|
+
.mockResolvedValueOnce(undefined) // First task fails
|
|
360
|
+
.mockResolvedValueOnce({ id: 'msg_2', messages: [] }); // Second task succeeds
|
|
361
|
+
|
|
362
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
363
|
+
createMockExecResult({ threadId: 'thread-2' }),
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
367
|
+
result: 'Task completed',
|
|
368
|
+
status: 'completed',
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const executors = createGroupOrchestrationExecutors({
|
|
372
|
+
get: () => mockStore,
|
|
373
|
+
messageContext: {
|
|
374
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
375
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
376
|
+
scope: 'group',
|
|
377
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
378
|
+
},
|
|
379
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
380
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
384
|
+
|
|
385
|
+
const result = await batchExecTasksExecutor(
|
|
386
|
+
{
|
|
387
|
+
payload: {
|
|
388
|
+
tasks: [
|
|
389
|
+
{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' },
|
|
390
|
+
{ agentId: TEST_IDS.AGENT_2_ID, task: 'Task 2', title: 'Task 2 Title' },
|
|
391
|
+
],
|
|
392
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
393
|
+
},
|
|
394
|
+
type: 'batch_exec_async_tasks',
|
|
395
|
+
},
|
|
396
|
+
createInitialState(),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
expect(result.result?.type).toBe('tasks_completed');
|
|
400
|
+
// First task should fail due to message creation failure
|
|
401
|
+
expect((result.result?.payload as any).results[0]).toMatchObject({
|
|
402
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
403
|
+
error: 'Failed to create task message',
|
|
404
|
+
success: false,
|
|
405
|
+
});
|
|
406
|
+
// Second task should succeed
|
|
407
|
+
expect((result.result?.payload as any).results[1]).toMatchObject({
|
|
408
|
+
agentId: TEST_IDS.AGENT_2_ID,
|
|
409
|
+
success: true,
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('should handle execSubAgentTask failure', async () => {
|
|
414
|
+
const mockStore = createMockStore();
|
|
415
|
+
|
|
416
|
+
vi.mocked(aiAgentService.execSubAgentTask)
|
|
417
|
+
.mockResolvedValueOnce(
|
|
418
|
+
createMockExecResult({
|
|
419
|
+
error: 'Backend error',
|
|
420
|
+
success: false,
|
|
421
|
+
}),
|
|
422
|
+
)
|
|
423
|
+
.mockResolvedValueOnce(createMockExecResult({ threadId: 'thread-2' }));
|
|
424
|
+
|
|
425
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
426
|
+
result: 'Task completed',
|
|
427
|
+
status: 'completed',
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const executors = createGroupOrchestrationExecutors({
|
|
431
|
+
get: () => mockStore,
|
|
432
|
+
messageContext: {
|
|
433
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
434
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
435
|
+
scope: 'group',
|
|
436
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
437
|
+
},
|
|
438
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
439
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
443
|
+
|
|
444
|
+
const result = await batchExecTasksExecutor(
|
|
445
|
+
{
|
|
446
|
+
payload: {
|
|
447
|
+
tasks: [
|
|
448
|
+
{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' },
|
|
449
|
+
{ agentId: TEST_IDS.AGENT_2_ID, task: 'Task 2', title: 'Task 2 Title' },
|
|
450
|
+
],
|
|
451
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
452
|
+
},
|
|
453
|
+
type: 'batch_exec_async_tasks',
|
|
454
|
+
},
|
|
455
|
+
createInitialState(),
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(result.result?.type).toBe('tasks_completed');
|
|
459
|
+
// First task should fail
|
|
460
|
+
expect((result.result?.payload as any).results[0]).toMatchObject({
|
|
461
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
462
|
+
error: 'Backend error',
|
|
463
|
+
success: false,
|
|
464
|
+
});
|
|
465
|
+
// Second task should succeed
|
|
466
|
+
expect((result.result?.payload as any).results[1]).toMatchObject({
|
|
467
|
+
agentId: TEST_IDS.AGENT_2_ID,
|
|
468
|
+
success: true,
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should handle task failure status', async () => {
|
|
473
|
+
const mockStore = createMockStore();
|
|
474
|
+
|
|
475
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
476
|
+
createMockExecResult({ threadId: 'thread-1' }),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
480
|
+
error: 'Task execution error',
|
|
481
|
+
status: 'failed',
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const executors = createGroupOrchestrationExecutors({
|
|
485
|
+
get: () => mockStore,
|
|
486
|
+
messageContext: {
|
|
487
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
488
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
489
|
+
scope: 'group',
|
|
490
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
491
|
+
},
|
|
492
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
493
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
497
|
+
|
|
498
|
+
const result = await batchExecTasksExecutor(
|
|
499
|
+
{
|
|
500
|
+
payload: {
|
|
501
|
+
tasks: [{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' }],
|
|
502
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
503
|
+
},
|
|
504
|
+
type: 'batch_exec_async_tasks',
|
|
505
|
+
},
|
|
506
|
+
createInitialState(),
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
expect(result.result?.type).toBe('tasks_completed');
|
|
510
|
+
expect((result.result?.payload as any).results[0]).toMatchObject({
|
|
511
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
512
|
+
error: 'Task execution error',
|
|
513
|
+
success: false,
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should handle operation cancellation', async () => {
|
|
518
|
+
const mockStore = createMockStore();
|
|
519
|
+
|
|
520
|
+
// Set operation to cancelled
|
|
521
|
+
mockStore.operations[TEST_IDS.OPERATION_ID].status = 'cancelled';
|
|
522
|
+
|
|
523
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
524
|
+
createMockExecResult({ threadId: 'thread-1' }),
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// This should not be called since operation is cancelled
|
|
528
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
529
|
+
status: 'processing',
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
const executors = createGroupOrchestrationExecutors({
|
|
533
|
+
get: () => mockStore,
|
|
534
|
+
messageContext: {
|
|
535
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
536
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
537
|
+
scope: 'group',
|
|
538
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
539
|
+
},
|
|
540
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
541
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
545
|
+
|
|
546
|
+
const result = await batchExecTasksExecutor(
|
|
547
|
+
{
|
|
548
|
+
payload: {
|
|
549
|
+
tasks: [{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' }],
|
|
550
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
551
|
+
},
|
|
552
|
+
type: 'batch_exec_async_tasks',
|
|
553
|
+
},
|
|
554
|
+
createInitialState(),
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
expect(result.newState.status).toBe('done');
|
|
558
|
+
expect((result.result?.payload as any).results[0]).toMatchObject({
|
|
559
|
+
agentId: TEST_IDS.AGENT_1_ID,
|
|
560
|
+
error: 'Operation cancelled',
|
|
561
|
+
success: false,
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('should dispatch taskDetail update when task status includes taskDetail', async () => {
|
|
566
|
+
const mockStore = createMockStore();
|
|
567
|
+
const messageId = 'msg_1';
|
|
568
|
+
|
|
569
|
+
vi.mocked(mockStore.optimisticCreateMessage).mockResolvedValue({
|
|
570
|
+
id: messageId,
|
|
571
|
+
messages: [],
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
vi.mocked(aiAgentService.execSubAgentTask).mockResolvedValue(
|
|
575
|
+
createMockExecResult({ threadId: 'thread-1' }),
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
// Return completed status with taskDetail in first poll
|
|
579
|
+
const taskDetail = {
|
|
580
|
+
status: ThreadStatus.Completed,
|
|
581
|
+
threadId: 'thread-1',
|
|
582
|
+
title: 'Done',
|
|
583
|
+
};
|
|
584
|
+
vi.mocked(aiAgentService.getSubAgentTaskStatus).mockResolvedValue({
|
|
585
|
+
result: 'Task completed',
|
|
586
|
+
status: 'completed',
|
|
587
|
+
taskDetail,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
const executors = createGroupOrchestrationExecutors({
|
|
591
|
+
get: () => mockStore,
|
|
592
|
+
messageContext: {
|
|
593
|
+
agentId: TEST_IDS.GROUP_ID,
|
|
594
|
+
groupId: TEST_IDS.GROUP_ID,
|
|
595
|
+
scope: 'group',
|
|
596
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
597
|
+
},
|
|
598
|
+
orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
|
|
599
|
+
supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const batchExecTasksExecutor = executors.batch_exec_async_tasks!;
|
|
603
|
+
|
|
604
|
+
await batchExecTasksExecutor(
|
|
605
|
+
{
|
|
606
|
+
payload: {
|
|
607
|
+
tasks: [{ agentId: TEST_IDS.AGENT_1_ID, task: 'Task 1', title: 'Task 1 Title' }],
|
|
608
|
+
toolMessageId: TEST_IDS.TOOL_MESSAGE_ID,
|
|
609
|
+
},
|
|
610
|
+
type: 'batch_exec_async_tasks',
|
|
611
|
+
},
|
|
612
|
+
createInitialState(),
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// Should dispatch message update with taskDetail
|
|
616
|
+
expect(mockStore.internal_dispatchMessage).toHaveBeenCalledWith(
|
|
617
|
+
expect.objectContaining({
|
|
618
|
+
id: messageId,
|
|
619
|
+
type: 'updateMessage',
|
|
620
|
+
value: { taskDetail },
|
|
621
|
+
}),
|
|
622
|
+
expect.objectContaining({ operationId: TEST_IDS.OPERATION_ID }),
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
});
|