@lobehub/lobehub 2.0.0-next.327 → 2.0.0-next.328
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/locales/en-US/chat.json +6 -1
- package/locales/zh-CN/chat.json +5 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/agents/GeneralChatAgent.ts +24 -0
- package/packages/agent-runtime/src/agents/__tests__/GeneralChatAgent.test.ts +210 -0
- package/packages/agent-runtime/src/types/instruction.ts +46 -2
- package/packages/builtin-tool-gtd/src/const.ts +1 -0
- package/packages/builtin-tool-gtd/src/executor/index.ts +38 -21
- package/packages/builtin-tool-gtd/src/manifest.ts +15 -0
- package/packages/builtin-tool-gtd/src/systemRole.ts +33 -1
- package/packages/builtin-tool-gtd/src/types.ts +55 -33
- package/packages/builtin-tool-local-system/src/client/Inspector/ReadLocalFile/index.tsx +1 -0
- package/packages/builtin-tool-local-system/src/client/Inspector/RunCommand/index.tsx +1 -1
- package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +1 -1
- package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +5 -1
- package/packages/builtin-tool-notebook/src/systemRole.ts +27 -7
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +13 -1
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +40 -0
- package/packages/database/src/models/__tests__/messages/message.thread-query.test.ts +134 -1
- package/packages/database/src/models/message.ts +8 -1
- package/packages/database/src/models/thread.ts +1 -1
- package/packages/types/src/message/ui/chat.ts +2 -0
- package/packages/types/src/topic/thread.ts +20 -0
- package/src/components/StreamingMarkdown/index.tsx +10 -43
- package/src/features/Conversation/Messages/AssistantGroup/components/MessageContent.tsx +0 -2
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/CompletedState.tsx +108 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InitializingState.tsx +66 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/InstructionAccordion.tsx +63 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/ProcessingState.tsx +123 -0
- package/src/features/Conversation/Messages/Task/ClientTaskDetail/index.tsx +106 -0
- package/src/features/Conversation/Messages/Task/TaskDetailPanel/index.tsx +1 -0
- package/src/features/Conversation/Messages/Task/index.tsx +11 -6
- package/src/features/Conversation/Messages/Tasks/TaskItem/TaskTitle.tsx +3 -2
- package/src/features/Conversation/Messages/Tasks/shared/InitializingState.tsx +0 -4
- package/src/features/Conversation/Messages/Tasks/shared/utils.ts +22 -1
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +1 -1
- package/src/features/Conversation/components/Thinking/index.tsx +9 -30
- package/src/features/Conversation/store/slices/data/action.ts +2 -3
- package/src/features/NavPanel/components/BackButton.tsx +10 -13
- package/src/features/NavPanel/components/NavPanelDraggable.tsx +4 -0
- package/src/hooks/useAutoScroll.ts +117 -0
- package/src/locales/default/chat.ts +6 -1
- package/src/server/routers/lambda/aiAgent.ts +239 -1
- package/src/server/routers/lambda/thread.ts +2 -0
- package/src/server/services/message/__tests__/index.test.ts +37 -0
- package/src/server/services/message/index.ts +6 -1
- package/src/services/aiAgent.ts +51 -0
- package/src/store/chat/agents/createAgentExecutors.ts +714 -12
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -1
- package/src/store/chat/slices/message/actions/query.ts +33 -1
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +10 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +1 -0
- package/src/store/chat/slices/operation/types.ts +4 -0
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
type AgentInstruction,
|
|
4
4
|
type AgentInstructionCallLlm,
|
|
5
5
|
type AgentInstructionCallTool,
|
|
6
|
+
type AgentInstructionExecClientTask,
|
|
7
|
+
type AgentInstructionExecClientTasks,
|
|
6
8
|
type AgentInstructionExecTask,
|
|
7
9
|
type AgentInstructionExecTasks,
|
|
8
10
|
type AgentRuntimeContext,
|
|
@@ -15,13 +17,17 @@ import {
|
|
|
15
17
|
type TasksBatchResultPayload,
|
|
16
18
|
UsageCounter,
|
|
17
19
|
} from '@lobechat/agent-runtime';
|
|
18
|
-
import
|
|
20
|
+
import { isDesktop } from '@lobechat/const';
|
|
21
|
+
import type { ChatToolPayload, ConversationContext, CreateMessageParams } from '@lobechat/types';
|
|
19
22
|
import debug from 'debug';
|
|
20
23
|
import pMap from 'p-map';
|
|
21
24
|
|
|
22
25
|
import { LOADING_FLAT } from '@/const/message';
|
|
23
26
|
import { aiAgentService } from '@/services/aiAgent';
|
|
27
|
+
import { agentByIdSelectors } from '@/store/agent/selectors';
|
|
28
|
+
import { getAgentStoreState } from '@/store/agent/store';
|
|
24
29
|
import type { ChatStore } from '@/store/chat/store';
|
|
30
|
+
import { messageMapKey } from '@/store/chat/utils/messageMapKey';
|
|
25
31
|
import { sleep } from '@/utils/sleep';
|
|
26
32
|
|
|
27
33
|
const log = debug('lobe-store:agent-executors');
|
|
@@ -571,7 +577,9 @@ export const createAgentExecutors = (context: {
|
|
|
571
577
|
const stateType = result.state?.type;
|
|
572
578
|
|
|
573
579
|
// GTD async tasks need to be passed to Agent for exec_task/exec_tasks instruction
|
|
574
|
-
|
|
580
|
+
// Includes both server-side (execTask/execTasks) and client-side (execClientTask/execClientTasks)
|
|
581
|
+
const execTaskStateTypes = ['execTask', 'execTasks', 'execClientTask', 'execClientTasks'];
|
|
582
|
+
if (execTaskStateTypes.includes(stateType)) {
|
|
575
583
|
log(
|
|
576
584
|
'[%s][call_tool] Detected %s state, passing to Agent for decision',
|
|
577
585
|
sessionLogId,
|
|
@@ -955,7 +963,8 @@ export const createAgentExecutors = (context: {
|
|
|
955
963
|
const taskMessageId = taskMessageResult.id;
|
|
956
964
|
log('[%s] Created task message: %s', taskLogId, taskMessageId);
|
|
957
965
|
|
|
958
|
-
// 2. Create task
|
|
966
|
+
// 2. Create and execute task on server
|
|
967
|
+
log('[%s] Using server-side execution', taskLogId);
|
|
959
968
|
const createResult = await aiAgentService.execSubAgentTask({
|
|
960
969
|
agentId,
|
|
961
970
|
instruction: task.instruction,
|
|
@@ -1006,10 +1015,29 @@ export const createAgentExecutors = (context: {
|
|
|
1006
1015
|
const startTime = Date.now();
|
|
1007
1016
|
|
|
1008
1017
|
while (Date.now() - startTime < maxWait) {
|
|
1009
|
-
// Check if operation has been cancelled
|
|
1018
|
+
// Check if parent operation has been cancelled
|
|
1010
1019
|
const currentOperation = context.get().operations[state.operationId];
|
|
1011
1020
|
if (currentOperation?.status === 'cancelled') {
|
|
1012
1021
|
log('[%s] Operation cancelled, stopping polling', taskLogId);
|
|
1022
|
+
|
|
1023
|
+
// Send interrupt request to stop the server-side task
|
|
1024
|
+
try {
|
|
1025
|
+
await aiAgentService.interruptTask({ threadId: createResult.threadId });
|
|
1026
|
+
log('[%s] Sent interrupt request for cancelled task', taskLogId);
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
log('[%s] Failed to interrupt cancelled task: %O', taskLogId, err);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Update task message to cancelled state
|
|
1032
|
+
await context
|
|
1033
|
+
.get()
|
|
1034
|
+
.optimisticUpdateMessageContent(
|
|
1035
|
+
taskMessageId,
|
|
1036
|
+
'Task was cancelled by user.',
|
|
1037
|
+
undefined,
|
|
1038
|
+
{ operationId: state.operationId },
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1013
1041
|
const updatedMessages = context.get().dbMessagesMap[context.messageKey] || [];
|
|
1014
1042
|
return {
|
|
1015
1043
|
events,
|
|
@@ -1125,6 +1153,8 @@ export const createAgentExecutors = (context: {
|
|
|
1125
1153
|
|
|
1126
1154
|
if (status.status === 'cancel') {
|
|
1127
1155
|
log('[%s] Task was cancelled', taskLogId);
|
|
1156
|
+
// Note: Don't fail the operation here - it was cancelled intentionally
|
|
1157
|
+
// The cancel handler already updated the message
|
|
1128
1158
|
await context
|
|
1129
1159
|
.get()
|
|
1130
1160
|
.optimisticUpdateMessageContent(taskMessageId, 'Task was cancelled', undefined, {
|
|
@@ -1161,6 +1191,15 @@ export const createAgentExecutors = (context: {
|
|
|
1161
1191
|
|
|
1162
1192
|
// Timeout reached
|
|
1163
1193
|
log('[%s] Task timeout after %dms', taskLogId, maxWait);
|
|
1194
|
+
|
|
1195
|
+
// Try to interrupt the task that timed out
|
|
1196
|
+
try {
|
|
1197
|
+
await aiAgentService.interruptTask({ threadId: createResult.threadId });
|
|
1198
|
+
log('[%s] Sent interrupt request for timed out task', taskLogId);
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
log('[%s] Failed to interrupt timed out task: %O', taskLogId, err);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1164
1203
|
await context
|
|
1165
1204
|
.get()
|
|
1166
1205
|
.optimisticUpdateMessageContent(
|
|
@@ -1303,7 +1342,8 @@ export const createAgentExecutors = (context: {
|
|
|
1303
1342
|
const taskMessageId = taskMessageResult.id;
|
|
1304
1343
|
log('[%s] Created task message: %s', taskLogId, taskMessageId);
|
|
1305
1344
|
|
|
1306
|
-
// 2. Create
|
|
1345
|
+
// 2. Create and execute task on server
|
|
1346
|
+
log('[%s] Using server-side execution', taskLogId);
|
|
1307
1347
|
const createResult = await aiAgentService.execSubAgentTask({
|
|
1308
1348
|
agentId,
|
|
1309
1349
|
instruction: task.instruction,
|
|
@@ -1333,16 +1373,35 @@ export const createAgentExecutors = (context: {
|
|
|
1333
1373
|
|
|
1334
1374
|
log('[%s] Task created with threadId: %s', taskLogId, createResult.threadId);
|
|
1335
1375
|
|
|
1336
|
-
//
|
|
1376
|
+
// 4. Poll for task completion
|
|
1337
1377
|
const pollInterval = 3000; // 3 seconds
|
|
1338
1378
|
const maxWait = task.timeout || 1_800_000; // Default 30 minutes
|
|
1339
1379
|
const startTime = Date.now();
|
|
1340
1380
|
|
|
1341
1381
|
while (Date.now() - startTime < maxWait) {
|
|
1342
|
-
// Check if operation has been cancelled
|
|
1382
|
+
// Check if parent operation has been cancelled
|
|
1343
1383
|
const currentOperation = context.get().operations[state.operationId];
|
|
1344
1384
|
if (currentOperation?.status === 'cancelled') {
|
|
1345
1385
|
log('[%s] Operation cancelled, stopping polling', taskLogId);
|
|
1386
|
+
|
|
1387
|
+
// Send interrupt request to stop the server-side task
|
|
1388
|
+
try {
|
|
1389
|
+
await aiAgentService.interruptTask({ threadId: createResult.threadId });
|
|
1390
|
+
log('[%s] Sent interrupt request for cancelled task', taskLogId);
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
log('[%s] Failed to interrupt cancelled task: %O', taskLogId, err);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// Update task message to cancelled state
|
|
1396
|
+
await context
|
|
1397
|
+
.get()
|
|
1398
|
+
.optimisticUpdateMessageContent(
|
|
1399
|
+
taskMessageId,
|
|
1400
|
+
'Task was cancelled by user.',
|
|
1401
|
+
undefined,
|
|
1402
|
+
{ operationId: state.operationId },
|
|
1403
|
+
);
|
|
1404
|
+
|
|
1346
1405
|
return {
|
|
1347
1406
|
error: 'Operation cancelled',
|
|
1348
1407
|
success: false,
|
|
@@ -1370,7 +1429,7 @@ export const createAgentExecutors = (context: {
|
|
|
1370
1429
|
|
|
1371
1430
|
if (status.status === 'completed') {
|
|
1372
1431
|
log('[%s] Task completed successfully', taskLogId);
|
|
1373
|
-
//
|
|
1432
|
+
// 5. Update task message with result
|
|
1374
1433
|
if (status.result) {
|
|
1375
1434
|
await context
|
|
1376
1435
|
.get()
|
|
@@ -1387,13 +1446,14 @@ export const createAgentExecutors = (context: {
|
|
|
1387
1446
|
}
|
|
1388
1447
|
|
|
1389
1448
|
if (status.status === 'failed') {
|
|
1390
|
-
|
|
1449
|
+
const errorMessage = status.error || 'Unknown error';
|
|
1450
|
+
log('[%s] Task failed: %s', taskLogId, errorMessage);
|
|
1391
1451
|
// Update task message with error
|
|
1392
1452
|
await context
|
|
1393
1453
|
.get()
|
|
1394
1454
|
.optimisticUpdateMessageContent(
|
|
1395
1455
|
taskMessageId,
|
|
1396
|
-
`Task failed: ${
|
|
1456
|
+
`Task failed: ${errorMessage}`,
|
|
1397
1457
|
undefined,
|
|
1398
1458
|
{ operationId: state.operationId },
|
|
1399
1459
|
);
|
|
@@ -1407,7 +1467,8 @@ export const createAgentExecutors = (context: {
|
|
|
1407
1467
|
|
|
1408
1468
|
if (status.status === 'cancel') {
|
|
1409
1469
|
log('[%s] Task was cancelled', taskLogId);
|
|
1410
|
-
//
|
|
1470
|
+
// Note: Don't fail the operation here - it was cancelled intentionally
|
|
1471
|
+
// The cancel handler already updated the message
|
|
1411
1472
|
await context
|
|
1412
1473
|
.get()
|
|
1413
1474
|
.optimisticUpdateMessageContent(taskMessageId, 'Task was cancelled', undefined, {
|
|
@@ -1427,6 +1488,15 @@ export const createAgentExecutors = (context: {
|
|
|
1427
1488
|
|
|
1428
1489
|
// Timeout reached
|
|
1429
1490
|
log('[%s] Task timeout after %dms', taskLogId, maxWait);
|
|
1491
|
+
|
|
1492
|
+
// Try to interrupt the task that timed out
|
|
1493
|
+
try {
|
|
1494
|
+
await aiAgentService.interruptTask({ threadId: createResult.threadId });
|
|
1495
|
+
log('[%s] Sent interrupt request for timed out task', taskLogId);
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
log('[%s] Failed to interrupt timed out task: %O', taskLogId, err);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1430
1500
|
// Update task message with timeout error
|
|
1431
1501
|
await context
|
|
1432
1502
|
.get()
|
|
@@ -1453,7 +1523,7 @@ export const createAgentExecutors = (context: {
|
|
|
1453
1523
|
};
|
|
1454
1524
|
}
|
|
1455
1525
|
},
|
|
1456
|
-
{ concurrency:
|
|
1526
|
+
{ concurrency: 15 }, // Limit concurrent tasks
|
|
1457
1527
|
);
|
|
1458
1528
|
|
|
1459
1529
|
log('[%s][exec_tasks] All tasks completed, results: %O', sessionLogId, results);
|
|
@@ -1481,6 +1551,638 @@ export const createAgentExecutors = (context: {
|
|
|
1481
1551
|
} as AgentRuntimeContext,
|
|
1482
1552
|
};
|
|
1483
1553
|
},
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* exec_client_task executor
|
|
1557
|
+
* Executes a single async task on the client side (desktop only)
|
|
1558
|
+
* Used when task requires local tools like file system or shell commands
|
|
1559
|
+
*
|
|
1560
|
+
* Flow:
|
|
1561
|
+
* 1. Create a task message (role: 'task') as placeholder
|
|
1562
|
+
* 2. Create Thread via API (for isolation)
|
|
1563
|
+
* 3. Execute using internal_execAgentRuntime (client-side)
|
|
1564
|
+
* 4. Update Thread status via API on completion
|
|
1565
|
+
* 5. Update task message content with result
|
|
1566
|
+
* 6. Return task_result phase with result
|
|
1567
|
+
*/
|
|
1568
|
+
exec_client_task: async (instruction, state) => {
|
|
1569
|
+
const { parentMessageId, task } = (instruction as AgentInstructionExecClientTask).payload;
|
|
1570
|
+
|
|
1571
|
+
const events: AgentEvent[] = [];
|
|
1572
|
+
const sessionLogId = `${state.operationId}:${state.stepCount}`;
|
|
1573
|
+
|
|
1574
|
+
log(
|
|
1575
|
+
'[%s][exec_client_task] Starting client-side execution of task: %s',
|
|
1576
|
+
sessionLogId,
|
|
1577
|
+
task.description,
|
|
1578
|
+
);
|
|
1579
|
+
|
|
1580
|
+
// Check if we're on desktop - if not, this executor shouldn't have been called
|
|
1581
|
+
if (!isDesktop) {
|
|
1582
|
+
log(
|
|
1583
|
+
'[%s][exec_client_task] ERROR: Not on desktop, cannot execute client-side task',
|
|
1584
|
+
sessionLogId,
|
|
1585
|
+
);
|
|
1586
|
+
return {
|
|
1587
|
+
events,
|
|
1588
|
+
newState: state,
|
|
1589
|
+
nextContext: {
|
|
1590
|
+
payload: {
|
|
1591
|
+
parentMessageId,
|
|
1592
|
+
result: {
|
|
1593
|
+
error: 'Client-side task execution is only available on desktop',
|
|
1594
|
+
success: false,
|
|
1595
|
+
taskMessageId: '',
|
|
1596
|
+
threadId: '',
|
|
1597
|
+
},
|
|
1598
|
+
} as TaskResultPayload,
|
|
1599
|
+
phase: 'task_result',
|
|
1600
|
+
session: {
|
|
1601
|
+
messageCount: state.messages.length,
|
|
1602
|
+
sessionId: state.operationId,
|
|
1603
|
+
status: 'running',
|
|
1604
|
+
stepCount: state.stepCount + 1,
|
|
1605
|
+
},
|
|
1606
|
+
} as AgentRuntimeContext,
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Get context from operation
|
|
1611
|
+
const opContext = getOperationContext();
|
|
1612
|
+
const { agentId, topicId } = opContext;
|
|
1613
|
+
|
|
1614
|
+
if (!agentId || !topicId) {
|
|
1615
|
+
log('[%s][exec_client_task] No valid context, cannot execute task', sessionLogId);
|
|
1616
|
+
return {
|
|
1617
|
+
events,
|
|
1618
|
+
newState: state,
|
|
1619
|
+
nextContext: {
|
|
1620
|
+
payload: {
|
|
1621
|
+
parentMessageId,
|
|
1622
|
+
result: {
|
|
1623
|
+
error: 'No valid context available',
|
|
1624
|
+
success: false,
|
|
1625
|
+
taskMessageId: '',
|
|
1626
|
+
threadId: '',
|
|
1627
|
+
},
|
|
1628
|
+
} as TaskResultPayload,
|
|
1629
|
+
phase: 'task_result',
|
|
1630
|
+
session: {
|
|
1631
|
+
messageCount: state.messages.length,
|
|
1632
|
+
sessionId: state.operationId,
|
|
1633
|
+
status: 'running',
|
|
1634
|
+
stepCount: state.stepCount + 1,
|
|
1635
|
+
},
|
|
1636
|
+
} as AgentRuntimeContext,
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
const taskLogId = `${sessionLogId}:client-task`;
|
|
1641
|
+
|
|
1642
|
+
// Get agent's model and provider configuration
|
|
1643
|
+
const agentState = getAgentStoreState();
|
|
1644
|
+
const taskModel = agentByIdSelectors.getAgentModelById(agentId)(agentState);
|
|
1645
|
+
const taskProvider = agentByIdSelectors.getAgentModelProviderById(agentId)(agentState);
|
|
1646
|
+
|
|
1647
|
+
try {
|
|
1648
|
+
// 1. Create task message as placeholder with model/provider
|
|
1649
|
+
const taskMessageResult = await context.get().optimisticCreateMessage(
|
|
1650
|
+
{
|
|
1651
|
+
agentId,
|
|
1652
|
+
content: '',
|
|
1653
|
+
metadata: { instruction: task.instruction, taskTitle: task.description },
|
|
1654
|
+
model: taskModel,
|
|
1655
|
+
parentId: parentMessageId,
|
|
1656
|
+
provider: taskProvider,
|
|
1657
|
+
role: 'task',
|
|
1658
|
+
topicId,
|
|
1659
|
+
},
|
|
1660
|
+
{ operationId: state.operationId },
|
|
1661
|
+
);
|
|
1662
|
+
|
|
1663
|
+
if (!taskMessageResult) {
|
|
1664
|
+
log('[%s] Failed to create task message', taskLogId);
|
|
1665
|
+
return {
|
|
1666
|
+
events,
|
|
1667
|
+
newState: state,
|
|
1668
|
+
nextContext: {
|
|
1669
|
+
payload: {
|
|
1670
|
+
parentMessageId,
|
|
1671
|
+
result: {
|
|
1672
|
+
error: 'Failed to create task message',
|
|
1673
|
+
success: false,
|
|
1674
|
+
taskMessageId: '',
|
|
1675
|
+
threadId: '',
|
|
1676
|
+
},
|
|
1677
|
+
} as TaskResultPayload,
|
|
1678
|
+
phase: 'task_result',
|
|
1679
|
+
session: {
|
|
1680
|
+
messageCount: state.messages.length,
|
|
1681
|
+
sessionId: state.operationId,
|
|
1682
|
+
status: 'running',
|
|
1683
|
+
stepCount: state.stepCount + 1,
|
|
1684
|
+
},
|
|
1685
|
+
} as AgentRuntimeContext,
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
const taskMessageId = taskMessageResult.id;
|
|
1690
|
+
log('[%s][exec_client_task] Created task message: %s', taskLogId, taskMessageId);
|
|
1691
|
+
|
|
1692
|
+
// 2. Create Thread via API first (to get threadId for operation context)
|
|
1693
|
+
const threadResult = await aiAgentService.createClientTaskThread({
|
|
1694
|
+
agentId,
|
|
1695
|
+
instruction: task.instruction,
|
|
1696
|
+
parentMessageId: taskMessageId,
|
|
1697
|
+
title: task.description,
|
|
1698
|
+
topicId,
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
if (!threadResult.success) {
|
|
1702
|
+
log('[%s][exec_client_task] Failed to create client task thread', taskLogId);
|
|
1703
|
+
await context
|
|
1704
|
+
.get()
|
|
1705
|
+
.optimisticUpdateMessageContent(
|
|
1706
|
+
taskMessageId,
|
|
1707
|
+
'Failed to create task thread',
|
|
1708
|
+
undefined,
|
|
1709
|
+
{ operationId: state.operationId },
|
|
1710
|
+
);
|
|
1711
|
+
return {
|
|
1712
|
+
events,
|
|
1713
|
+
newState: state,
|
|
1714
|
+
nextContext: {
|
|
1715
|
+
payload: {
|
|
1716
|
+
parentMessageId,
|
|
1717
|
+
result: {
|
|
1718
|
+
error: 'Failed to create client task thread',
|
|
1719
|
+
success: false,
|
|
1720
|
+
taskMessageId,
|
|
1721
|
+
threadId: '',
|
|
1722
|
+
},
|
|
1723
|
+
} as TaskResultPayload,
|
|
1724
|
+
phase: 'task_result',
|
|
1725
|
+
session: {
|
|
1726
|
+
messageCount: state.messages.length,
|
|
1727
|
+
sessionId: state.operationId,
|
|
1728
|
+
status: 'running',
|
|
1729
|
+
stepCount: state.stepCount + 1,
|
|
1730
|
+
},
|
|
1731
|
+
} as AgentRuntimeContext,
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
const { threadId, userMessageId, threadMessages, messages } = threadResult;
|
|
1736
|
+
|
|
1737
|
+
// 3. Build sub-task ConversationContext (uses threadId for isolation)
|
|
1738
|
+
const subContext: ConversationContext = { agentId, topicId, threadId, scope: 'thread' };
|
|
1739
|
+
|
|
1740
|
+
// 4. Create a child operation for task execution (now with threadId)
|
|
1741
|
+
const { operationId: taskOperationId } = context.get().startOperation({
|
|
1742
|
+
type: 'execClientTask',
|
|
1743
|
+
context: subContext,
|
|
1744
|
+
parentOperationId: state.operationId,
|
|
1745
|
+
metadata: {
|
|
1746
|
+
startTime: Date.now(),
|
|
1747
|
+
taskDescription: task.description,
|
|
1748
|
+
taskMessageId,
|
|
1749
|
+
executionMode: 'client',
|
|
1750
|
+
},
|
|
1751
|
+
});
|
|
1752
|
+
log(
|
|
1753
|
+
'[%s][exec_client_task] Created thread: %s, userMessageId: %s, threadMessages: %d',
|
|
1754
|
+
taskLogId,
|
|
1755
|
+
threadId,
|
|
1756
|
+
userMessageId,
|
|
1757
|
+
threadMessages.length,
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
// 5. Sync messages to store
|
|
1761
|
+
// Update main chat messages with latest taskDetail status
|
|
1762
|
+
context.get().replaceMessages(messages, { operationId: state.operationId });
|
|
1763
|
+
// Update thread messages
|
|
1764
|
+
context.get().replaceMessages(threadMessages, { context: subContext });
|
|
1765
|
+
|
|
1766
|
+
// 6. Use server-returned thread messages (already persisted)
|
|
1767
|
+
let subMessages = [...threadMessages];
|
|
1768
|
+
|
|
1769
|
+
// Optionally inherit messages from parent conversation
|
|
1770
|
+
if (task.inheritMessages) {
|
|
1771
|
+
const parentMessages = state.messages.filter((m) => m.role !== 'task');
|
|
1772
|
+
subMessages = [...parentMessages, ...subMessages];
|
|
1773
|
+
// Re-sync with inherited messages
|
|
1774
|
+
context.get().replaceMessages(subMessages, { context: subContext });
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// 7. Execute using internal_execAgentRuntime (client-side with local tools access)
|
|
1778
|
+
log('[%s][exec_client_task] Starting client-side AgentRuntime execution', taskLogId);
|
|
1779
|
+
|
|
1780
|
+
const runtimeResult = await context.get().internal_execAgentRuntime({
|
|
1781
|
+
context: subContext,
|
|
1782
|
+
messages: subMessages,
|
|
1783
|
+
parentMessageId: userMessageId, // Use server-returned userMessageId
|
|
1784
|
+
parentMessageType: 'user',
|
|
1785
|
+
operationId: taskOperationId,
|
|
1786
|
+
parentOperationId: state.operationId,
|
|
1787
|
+
});
|
|
1788
|
+
|
|
1789
|
+
log('[%s][exec_client_task] Client-side AgentRuntime execution completed', taskLogId);
|
|
1790
|
+
|
|
1791
|
+
// 8. Get execution result from sub-task messages
|
|
1792
|
+
const subMessageKey = messageMapKey(subContext);
|
|
1793
|
+
const subTaskMessages = context.get().dbMessagesMap[subMessageKey] || [];
|
|
1794
|
+
const lastAssistant = subTaskMessages.findLast((m) => m.role === 'assistant');
|
|
1795
|
+
const resultContent = lastAssistant?.content || 'Task completed';
|
|
1796
|
+
|
|
1797
|
+
log(
|
|
1798
|
+
'[%s][exec_client_task] Got result from sub-task: %d chars',
|
|
1799
|
+
taskLogId,
|
|
1800
|
+
resultContent.length,
|
|
1801
|
+
);
|
|
1802
|
+
|
|
1803
|
+
// Count tool calls
|
|
1804
|
+
const totalToolCalls = subTaskMessages.filter((m) => m.role === 'tool').length;
|
|
1805
|
+
|
|
1806
|
+
// Get usage data from runtime result
|
|
1807
|
+
const { usage, cost } = runtimeResult || {};
|
|
1808
|
+
|
|
1809
|
+
log(
|
|
1810
|
+
'[%s][exec_client_task] Runtime usage: tokens=%d, cost=%s, model=%s',
|
|
1811
|
+
taskLogId,
|
|
1812
|
+
usage?.llm?.tokens?.total,
|
|
1813
|
+
cost?.total,
|
|
1814
|
+
taskModel,
|
|
1815
|
+
);
|
|
1816
|
+
|
|
1817
|
+
// 9. Update task message with result and usage (model/provider already set at creation)
|
|
1818
|
+
await context.get().optimisticUpdateMessageContent(
|
|
1819
|
+
taskMessageId,
|
|
1820
|
+
resultContent,
|
|
1821
|
+
{
|
|
1822
|
+
metadata: {
|
|
1823
|
+
cost: cost?.total,
|
|
1824
|
+
duration: usage?.llm?.processingTimeMs,
|
|
1825
|
+
totalInputTokens: usage?.llm?.tokens?.input,
|
|
1826
|
+
totalOutputTokens: usage?.llm?.tokens?.output,
|
|
1827
|
+
totalTokens: usage?.llm?.tokens?.total,
|
|
1828
|
+
},
|
|
1829
|
+
},
|
|
1830
|
+
{ operationId: state.operationId },
|
|
1831
|
+
);
|
|
1832
|
+
|
|
1833
|
+
// 10. Update Thread status via API with metadata
|
|
1834
|
+
await aiAgentService.updateClientTaskThreadStatus({
|
|
1835
|
+
threadId,
|
|
1836
|
+
completionReason: 'done',
|
|
1837
|
+
resultContent,
|
|
1838
|
+
metadata: {
|
|
1839
|
+
totalCost: cost?.total,
|
|
1840
|
+
totalMessages: subTaskMessages.length,
|
|
1841
|
+
totalTokens: usage?.llm?.tokens?.total,
|
|
1842
|
+
totalToolCalls,
|
|
1843
|
+
},
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
// 11. Complete operation
|
|
1847
|
+
context.get().completeOperation(taskOperationId);
|
|
1848
|
+
|
|
1849
|
+
// 12. Return success result
|
|
1850
|
+
const updatedMessages = context.get().dbMessagesMap[context.messageKey] || [];
|
|
1851
|
+
return {
|
|
1852
|
+
events,
|
|
1853
|
+
newState: { ...state, messages: updatedMessages },
|
|
1854
|
+
nextContext: {
|
|
1855
|
+
payload: {
|
|
1856
|
+
// Use taskMessageId as parent so subsequent messages are created after the task
|
|
1857
|
+
parentMessageId: taskMessageId,
|
|
1858
|
+
result: {
|
|
1859
|
+
result: resultContent,
|
|
1860
|
+
success: true,
|
|
1861
|
+
taskMessageId,
|
|
1862
|
+
threadId,
|
|
1863
|
+
},
|
|
1864
|
+
} as TaskResultPayload,
|
|
1865
|
+
phase: 'task_result',
|
|
1866
|
+
session: {
|
|
1867
|
+
messageCount: updatedMessages.length,
|
|
1868
|
+
sessionId: state.operationId,
|
|
1869
|
+
status: 'running',
|
|
1870
|
+
stepCount: state.stepCount + 1,
|
|
1871
|
+
},
|
|
1872
|
+
} as AgentRuntimeContext,
|
|
1873
|
+
};
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
log('[%s][exec_client_task] Error executing client task: %O', taskLogId, error);
|
|
1876
|
+
|
|
1877
|
+
// Update task message with error
|
|
1878
|
+
// Note: taskMessageId may not exist if error occurred before message creation
|
|
1879
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1880
|
+
|
|
1881
|
+
return {
|
|
1882
|
+
events,
|
|
1883
|
+
newState: state,
|
|
1884
|
+
nextContext: {
|
|
1885
|
+
payload: {
|
|
1886
|
+
parentMessageId,
|
|
1887
|
+
result: {
|
|
1888
|
+
error: errorMessage,
|
|
1889
|
+
success: false,
|
|
1890
|
+
taskMessageId: '',
|
|
1891
|
+
threadId: '',
|
|
1892
|
+
},
|
|
1893
|
+
} as TaskResultPayload,
|
|
1894
|
+
phase: 'task_result',
|
|
1895
|
+
session: {
|
|
1896
|
+
messageCount: state.messages.length,
|
|
1897
|
+
sessionId: state.operationId,
|
|
1898
|
+
status: 'running',
|
|
1899
|
+
stepCount: state.stepCount + 1,
|
|
1900
|
+
},
|
|
1901
|
+
} as AgentRuntimeContext,
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
},
|
|
1905
|
+
|
|
1906
|
+
/**
|
|
1907
|
+
* exec_client_tasks executor
|
|
1908
|
+
* Executes multiple async tasks on the client side in parallel (desktop only)
|
|
1909
|
+
* Used when tasks require local tools like file system or shell commands
|
|
1910
|
+
*
|
|
1911
|
+
* Flow:
|
|
1912
|
+
* 1. For each task, create a task message (role: 'task') as placeholder
|
|
1913
|
+
* 2. Create Thread via API (for isolation)
|
|
1914
|
+
* 3. Execute using internal_execAgentRuntime (client-side)
|
|
1915
|
+
* 4. Update Thread status via API on completion
|
|
1916
|
+
* 5. Update task message content with result
|
|
1917
|
+
* 6. Return tasks_batch_result phase with all results
|
|
1918
|
+
*/
|
|
1919
|
+
exec_client_tasks: async (instruction, state) => {
|
|
1920
|
+
const { parentMessageId, tasks } = (instruction as AgentInstructionExecClientTasks).payload;
|
|
1921
|
+
|
|
1922
|
+
const events: AgentEvent[] = [];
|
|
1923
|
+
const sessionLogId = `${state.operationId}:${state.stepCount}`;
|
|
1924
|
+
|
|
1925
|
+
log(
|
|
1926
|
+
'[%s][exec_client_tasks] Starting client-side execution of %d tasks',
|
|
1927
|
+
sessionLogId,
|
|
1928
|
+
tasks.length,
|
|
1929
|
+
);
|
|
1930
|
+
|
|
1931
|
+
// Check if we're on desktop - if not, this executor shouldn't have been called
|
|
1932
|
+
if (!isDesktop) {
|
|
1933
|
+
log(
|
|
1934
|
+
'[%s][exec_client_tasks] ERROR: Not on desktop, cannot execute client-side tasks',
|
|
1935
|
+
sessionLogId,
|
|
1936
|
+
);
|
|
1937
|
+
return {
|
|
1938
|
+
events,
|
|
1939
|
+
newState: state,
|
|
1940
|
+
nextContext: {
|
|
1941
|
+
payload: {
|
|
1942
|
+
parentMessageId,
|
|
1943
|
+
results: tasks.map(() => ({
|
|
1944
|
+
error: 'Client-side task execution is only available on desktop',
|
|
1945
|
+
success: false,
|
|
1946
|
+
taskMessageId: '',
|
|
1947
|
+
threadId: '',
|
|
1948
|
+
})),
|
|
1949
|
+
} as TasksBatchResultPayload,
|
|
1950
|
+
phase: 'tasks_batch_result',
|
|
1951
|
+
session: {
|
|
1952
|
+
messageCount: state.messages.length,
|
|
1953
|
+
sessionId: state.operationId,
|
|
1954
|
+
status: 'running',
|
|
1955
|
+
stepCount: state.stepCount + 1,
|
|
1956
|
+
},
|
|
1957
|
+
} as AgentRuntimeContext,
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Get context from operation
|
|
1962
|
+
const opContext = getOperationContext();
|
|
1963
|
+
const { agentId, topicId } = opContext;
|
|
1964
|
+
|
|
1965
|
+
if (!agentId || !topicId) {
|
|
1966
|
+
log('[%s][exec_client_tasks] No valid context, cannot execute tasks', sessionLogId);
|
|
1967
|
+
return {
|
|
1968
|
+
events,
|
|
1969
|
+
newState: state,
|
|
1970
|
+
nextContext: {
|
|
1971
|
+
payload: {
|
|
1972
|
+
parentMessageId,
|
|
1973
|
+
results: tasks.map(() => ({
|
|
1974
|
+
error: 'No valid context available',
|
|
1975
|
+
success: false,
|
|
1976
|
+
taskMessageId: '',
|
|
1977
|
+
threadId: '',
|
|
1978
|
+
})),
|
|
1979
|
+
} as TasksBatchResultPayload,
|
|
1980
|
+
phase: 'tasks_batch_result',
|
|
1981
|
+
session: {
|
|
1982
|
+
messageCount: state.messages.length,
|
|
1983
|
+
sessionId: state.operationId,
|
|
1984
|
+
status: 'running',
|
|
1985
|
+
stepCount: state.stepCount + 1,
|
|
1986
|
+
},
|
|
1987
|
+
} as AgentRuntimeContext,
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// Execute all tasks in parallel
|
|
1992
|
+
const results = await pMap(
|
|
1993
|
+
tasks,
|
|
1994
|
+
async (task, taskIndex) => {
|
|
1995
|
+
const taskLogId = `${sessionLogId}:client-task-${taskIndex}`;
|
|
1996
|
+
log('[%s] Starting client task: %s', taskLogId, task.description);
|
|
1997
|
+
|
|
1998
|
+
try {
|
|
1999
|
+
// 1. Create task message as placeholder
|
|
2000
|
+
const taskMessageResult = await context.get().optimisticCreateMessage(
|
|
2001
|
+
{
|
|
2002
|
+
agentId,
|
|
2003
|
+
content: '',
|
|
2004
|
+
metadata: { instruction: task.instruction, taskTitle: task.description },
|
|
2005
|
+
parentId: parentMessageId,
|
|
2006
|
+
role: 'task',
|
|
2007
|
+
topicId,
|
|
2008
|
+
},
|
|
2009
|
+
{ operationId: state.operationId },
|
|
2010
|
+
);
|
|
2011
|
+
|
|
2012
|
+
if (!taskMessageResult) {
|
|
2013
|
+
log('[%s] Failed to create task message', taskLogId);
|
|
2014
|
+
return {
|
|
2015
|
+
error: 'Failed to create task message',
|
|
2016
|
+
success: false,
|
|
2017
|
+
taskMessageId: '',
|
|
2018
|
+
threadId: '',
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
const taskMessageId = taskMessageResult.id;
|
|
2023
|
+
log('[%s] Created task message: %s', taskLogId, taskMessageId);
|
|
2024
|
+
|
|
2025
|
+
// 2. Create Thread via API first (to get threadId for operation context)
|
|
2026
|
+
const threadResult = await aiAgentService.createClientTaskThread({
|
|
2027
|
+
agentId,
|
|
2028
|
+
instruction: task.instruction,
|
|
2029
|
+
parentMessageId: taskMessageId,
|
|
2030
|
+
title: task.description,
|
|
2031
|
+
topicId,
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
if (!threadResult.success) {
|
|
2035
|
+
log('[%s] Failed to create client task thread', taskLogId);
|
|
2036
|
+
await context
|
|
2037
|
+
.get()
|
|
2038
|
+
.optimisticUpdateMessageContent(
|
|
2039
|
+
taskMessageId,
|
|
2040
|
+
'Failed to create task thread',
|
|
2041
|
+
undefined,
|
|
2042
|
+
{ operationId: state.operationId },
|
|
2043
|
+
);
|
|
2044
|
+
return {
|
|
2045
|
+
error: 'Failed to create client task thread',
|
|
2046
|
+
success: false,
|
|
2047
|
+
taskMessageId,
|
|
2048
|
+
threadId: '',
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
const { threadId, userMessageId, threadMessages, messages } = threadResult;
|
|
2053
|
+
log(
|
|
2054
|
+
'[%s] Created thread: %s, userMessageId: %s, threadMessages: %d',
|
|
2055
|
+
taskLogId,
|
|
2056
|
+
threadId,
|
|
2057
|
+
userMessageId,
|
|
2058
|
+
threadMessages.length,
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
// 3. Build sub-task ConversationContext (uses threadId for isolation)
|
|
2062
|
+
const subContext: ConversationContext = {
|
|
2063
|
+
agentId,
|
|
2064
|
+
topicId,
|
|
2065
|
+
threadId,
|
|
2066
|
+
scope: 'thread',
|
|
2067
|
+
};
|
|
2068
|
+
|
|
2069
|
+
// 4. Create a child operation for task execution (now with threadId)
|
|
2070
|
+
const { operationId: taskOperationId } = context.get().startOperation({
|
|
2071
|
+
type: 'execClientTask',
|
|
2072
|
+
context: subContext,
|
|
2073
|
+
parentOperationId: state.operationId,
|
|
2074
|
+
metadata: {
|
|
2075
|
+
startTime: Date.now(),
|
|
2076
|
+
taskDescription: task.description,
|
|
2077
|
+
taskIndex,
|
|
2078
|
+
taskMessageId,
|
|
2079
|
+
executionMode: 'client',
|
|
2080
|
+
},
|
|
2081
|
+
});
|
|
2082
|
+
|
|
2083
|
+
// 5. Sync messages to store
|
|
2084
|
+
// Update main chat messages with latest taskDetail status
|
|
2085
|
+
context.get().replaceMessages(messages, { operationId: state.operationId });
|
|
2086
|
+
// Update thread messages
|
|
2087
|
+
context.get().replaceMessages(threadMessages, { context: subContext });
|
|
2088
|
+
|
|
2089
|
+
// 6. Use server-returned thread messages (already persisted)
|
|
2090
|
+
let subMessages = [...threadMessages];
|
|
2091
|
+
|
|
2092
|
+
// Optionally inherit messages from parent conversation
|
|
2093
|
+
if (task.inheritMessages) {
|
|
2094
|
+
const parentMessages = state.messages.filter((m) => m.role !== 'task');
|
|
2095
|
+
subMessages = [...parentMessages, ...subMessages];
|
|
2096
|
+
// Re-sync with inherited messages
|
|
2097
|
+
context.get().replaceMessages(subMessages, { context: subContext });
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// 7. Execute using internal_execAgentRuntime (client-side with local tools access)
|
|
2101
|
+
log('[%s] Starting client-side AgentRuntime execution', taskLogId);
|
|
2102
|
+
|
|
2103
|
+
await context.get().internal_execAgentRuntime({
|
|
2104
|
+
context: subContext,
|
|
2105
|
+
messages: subMessages,
|
|
2106
|
+
parentMessageId: userMessageId, // Use server-returned userMessageId
|
|
2107
|
+
parentMessageType: 'user',
|
|
2108
|
+
operationId: taskOperationId,
|
|
2109
|
+
parentOperationId: state.operationId,
|
|
2110
|
+
});
|
|
2111
|
+
|
|
2112
|
+
log('[%s] Client-side AgentRuntime execution completed', taskLogId);
|
|
2113
|
+
|
|
2114
|
+
// 7. Get execution result from sub-task messages
|
|
2115
|
+
const subMessageKey = messageMapKey(subContext);
|
|
2116
|
+
const subTaskMessages = context.get().dbMessagesMap[subMessageKey] || [];
|
|
2117
|
+
const lastAssistant = subTaskMessages.findLast((m) => m.role === 'assistant');
|
|
2118
|
+
const resultContent = lastAssistant?.content || 'Task completed';
|
|
2119
|
+
|
|
2120
|
+
log('[%s] Got result from sub-task: %d chars', taskLogId, resultContent.length);
|
|
2121
|
+
|
|
2122
|
+
// 8. Update task message with result
|
|
2123
|
+
await context
|
|
2124
|
+
.get()
|
|
2125
|
+
.optimisticUpdateMessageContent(taskMessageId, resultContent, undefined, {
|
|
2126
|
+
operationId: state.operationId,
|
|
2127
|
+
});
|
|
2128
|
+
|
|
2129
|
+
// 9. Update Thread status via API
|
|
2130
|
+
await aiAgentService.updateClientTaskThreadStatus({
|
|
2131
|
+
threadId,
|
|
2132
|
+
completionReason: 'done',
|
|
2133
|
+
resultContent,
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
// 10. Complete operation
|
|
2137
|
+
context.get().completeOperation(taskOperationId);
|
|
2138
|
+
|
|
2139
|
+
return {
|
|
2140
|
+
result: resultContent,
|
|
2141
|
+
success: true,
|
|
2142
|
+
taskMessageId,
|
|
2143
|
+
threadId,
|
|
2144
|
+
};
|
|
2145
|
+
} catch (error) {
|
|
2146
|
+
log('[%s] Error executing client task: %O', taskLogId, error);
|
|
2147
|
+
return {
|
|
2148
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
2149
|
+
success: false,
|
|
2150
|
+
taskMessageId: '',
|
|
2151
|
+
threadId: '',
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
},
|
|
2155
|
+
{ concurrency: 15 },
|
|
2156
|
+
);
|
|
2157
|
+
|
|
2158
|
+
log('[%s][exec_client_tasks] All tasks completed, results: %O', sessionLogId, results);
|
|
2159
|
+
|
|
2160
|
+
// Get latest messages from store
|
|
2161
|
+
const updatedMessages = context.get().dbMessagesMap[context.messageKey] || [];
|
|
2162
|
+
const newState = { ...state, messages: updatedMessages };
|
|
2163
|
+
|
|
2164
|
+
// Use the last successful task's message ID as parent for subsequent messages
|
|
2165
|
+
const lastSuccessfulTaskId = results.findLast((r) => r.success)?.taskMessageId;
|
|
2166
|
+
|
|
2167
|
+
return {
|
|
2168
|
+
events,
|
|
2169
|
+
newState,
|
|
2170
|
+
nextContext: {
|
|
2171
|
+
payload: {
|
|
2172
|
+
// Use last task message as parent so subsequent messages are created after the tasks
|
|
2173
|
+
parentMessageId: lastSuccessfulTaskId || parentMessageId,
|
|
2174
|
+
results,
|
|
2175
|
+
} as TasksBatchResultPayload,
|
|
2176
|
+
phase: 'tasks_batch_result',
|
|
2177
|
+
session: {
|
|
2178
|
+
messageCount: newState.messages.length,
|
|
2179
|
+
sessionId: state.operationId,
|
|
2180
|
+
status: 'running',
|
|
2181
|
+
stepCount: state.stepCount + 1,
|
|
2182
|
+
},
|
|
2183
|
+
} as AgentRuntimeContext,
|
|
2184
|
+
};
|
|
2185
|
+
},
|
|
1484
2186
|
};
|
|
1485
2187
|
|
|
1486
2188
|
return executors;
|