@lobehub/lobehub 2.0.0-next.312 → 2.0.0-next.314

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/apps/desktop/src/main/appBrowsers.ts +4 -1
  3. package/apps/desktop/src/main/controllers/AuthCtr.ts +75 -7
  4. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +15 -3
  5. package/apps/desktop/src/main/core/browser/Browser.ts +14 -4
  6. package/apps/desktop/src/main/core/browser/BrowserManager.ts +7 -2
  7. package/changelog/v1.json +18 -0
  8. package/docs/usage/providers/internlm.mdx +2 -2
  9. package/docs/usage/providers/internlm.zh-CN.mdx +3 -3
  10. package/e2e/src/steps/community/detail-pages.steps.ts +2 -2
  11. package/e2e/src/steps/community/interactions.steps.ts +6 -6
  12. package/e2e/src/steps/hooks.ts +19 -3
  13. package/locales/en-US/error.json +10 -1
  14. package/locales/en-US/subscription.json +1 -1
  15. package/locales/zh-CN/desktop-onboarding.json +5 -0
  16. package/locales/zh-CN/error.json +10 -1
  17. package/locales/zh-CN/subscription.json +1 -1
  18. package/package.json +1 -1
  19. package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +14 -2
  20. package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +275 -1
  21. package/packages/builtin-tool-cloud-sandbox/package.json +1 -0
  22. package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +105 -134
  23. package/packages/builtin-tool-cloud-sandbox/src/executor/index.ts +254 -0
  24. package/packages/builtin-tool-cloud-sandbox/src/index.ts +1 -0
  25. package/packages/builtin-tool-cloud-sandbox/src/types/api.ts +22 -0
  26. package/packages/builtin-tool-cloud-sandbox/src/types/index.ts +4 -0
  27. package/packages/builtin-tool-cloud-sandbox/src/types/params.ts +85 -0
  28. package/packages/builtin-tool-cloud-sandbox/src/types/service.ts +48 -0
  29. package/packages/builtin-tool-cloud-sandbox/src/{types.ts → types/state.ts} +0 -23
  30. package/packages/builtin-tool-memory/src/manifest.ts +5 -5
  31. package/packages/desktop-bridge/src/index.ts +5 -0
  32. package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +1 -1
  33. package/packages/editor-runtime/src/__tests__/EditorRuntime.test.ts +1 -1
  34. package/packages/electron-client-ipc/src/events/index.ts +5 -1
  35. package/packages/electron-client-ipc/src/events/remoteServer.ts +23 -0
  36. package/packages/electron-client-ipc/src/types/window.ts +3 -2
  37. package/packages/memory-user-memory/src/schemas/index.ts +0 -1
  38. package/packages/model-bank/src/modelProviders/internlm.ts +1 -1
  39. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +5 -15
  40. package/packages/model-runtime/src/providers/internlm/index.test.ts +15 -15
  41. package/packages/model-runtime/src/providers/internlm/index.ts +1 -1
  42. package/packages/types/src/tool/intervention.ts +4 -2
  43. package/packages/types/src/user/preference.ts +1 -0
  44. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +6 -3
  45. package/src/app/[variants]/(desktop)/desktop-onboarding/components/OnboardingFooterActions.tsx +38 -0
  46. package/src/app/[variants]/(desktop)/desktop-onboarding/features/DataModeStep.tsx +19 -14
  47. package/src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx +121 -29
  48. package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +19 -14
  49. package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +8 -7
  50. package/src/app/[variants]/(main)/_layout/DesktopAutoOidcOnFirstOpen.tsx +4 -0
  51. package/src/app/manifest.ts +1 -1
  52. package/src/business/server/user.ts +4 -0
  53. package/src/features/Conversation/Messages/Task/Actions/index.tsx +0 -2
  54. package/src/features/Conversation/Messages/Task/index.tsx +1 -1
  55. package/src/features/Conversation/Messages/Tasks/shared/ProcessingState.tsx +0 -2
  56. package/src/features/Electron/titlebar/NavigationBar.tsx +1 -2
  57. package/src/features/NavPanel/components/NavPanelDraggable.tsx +0 -14
  58. package/src/features/ResourceManager/components/Explorer/ItemDropdown/useFileItemDropdown.tsx +4 -3
  59. package/src/features/SharePopover/index.tsx +5 -3
  60. package/src/hooks/useAppOrigin.ts +16 -0
  61. package/src/layout/GlobalProvider/useUserStateRedirect.ts +37 -24
  62. package/src/libs/trusted-client/index.ts +2 -5
  63. package/src/locales/default/desktop-onboarding.ts +5 -0
  64. package/src/locales/default/error.ts +11 -0
  65. package/src/locales/default/subscription.ts +1 -1
  66. package/src/server/manifest.ts +2 -2
  67. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +2 -0
  68. package/src/server/routers/lambda/user.ts +24 -10
  69. package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +3 -0
  70. package/src/server/services/agentRuntime/AgentRuntimeService.ts +8 -5
  71. package/src/server/services/agentRuntime/types.ts +7 -0
  72. package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +3 -0
  73. package/src/server/services/aiAgent/index.ts +10 -4
  74. package/src/server/services/market/index.ts +20 -0
  75. package/src/server/services/sandbox/index.ts +186 -0
  76. package/src/server/services/toolExecution/builtin.ts +12 -18
  77. package/src/server/services/toolExecution/index.ts +1 -1
  78. package/src/server/services/toolExecution/serverRuntimes/cloudSandbox.ts +38 -0
  79. package/src/server/services/toolExecution/serverRuntimes/index.ts +55 -0
  80. package/src/server/services/toolExecution/serverRuntimes/types.ts +14 -0
  81. package/src/server/services/toolExecution/serverRuntimes/webBrowsing.ts +20 -0
  82. package/src/server/services/toolExecution/types.ts +2 -0
  83. package/src/services/{codeInterpreter.ts → cloudSandbox.ts} +3 -3
  84. package/src/services/electron/remoteServer.ts +8 -0
  85. package/src/services/electron/system.ts +5 -5
  86. package/src/store/chat/agents/GroupOrchestration/__tests__/batch-exec-async-tasks.test.ts +626 -0
  87. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +294 -0
  88. package/src/store/chat/slices/plugin/action.test.ts +0 -48
  89. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +0 -131
  90. package/src/store/tool/slices/builtin/executors/index.ts +2 -0
  91. package/src/store/user/slices/settings/selectors/toolIntervention.test.ts +143 -0
  92. package/src/store/user/slices/settings/selectors/toolIntervention.ts +11 -2
  93. 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
+ });