@lobehub/lobehub 2.0.0-next.334 → 2.0.0-next.336
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 +58 -0
- package/changelog/v1.json +17 -0
- package/docs/development/database-schema.dbml +34 -0
- package/package.json +1 -1
- package/packages/builtin-tool-group-management/src/manifest.ts +54 -53
- package/packages/builtin-tool-group-management/src/systemRole.ts +43 -111
- package/packages/context-engine/src/engine/tools/ToolArgumentsRepairer.ts +129 -0
- package/packages/context-engine/src/engine/tools/__tests__/ToolArgumentsRepairer.test.ts +186 -0
- package/packages/context-engine/src/engine/tools/index.ts +3 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/index.ts +2 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/with-assistant-group.json +156 -0
- package/packages/conversation-flow/src/__tests__/parse.test.ts +22 -0
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +88 -11
- package/packages/database/migrations/0070_add_user_memory_activities.sql +35 -0
- package/packages/database/migrations/meta/0070_snapshot.json +10656 -0
- package/packages/database/migrations/meta/_journal.json +8 -1
- package/packages/database/src/schemas/userMemories/index.ts +71 -0
- package/packages/types/src/openai/chat.ts +0 -4
- package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +5 -1
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +8 -8
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx +142 -15
- package/src/app/[variants]/(main)/community/(detail)/user/features/useUserDetail.ts +45 -20
- package/src/server/routers/lambda/market/agentGroup.ts +179 -1
- package/src/server/services/discover/index.ts +4 -0
- package/src/services/chat/chat.test.ts +109 -104
- package/src/services/chat/index.ts +13 -32
- package/src/services/chat/mecha/agentConfigResolver.test.ts +113 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +15 -5
- package/src/services/marketApi.ts +14 -0
- package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +13 -0
- package/src/store/chat/agents/createAgentExecutors.ts +13 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +5 -1
- package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +14 -0
- package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +131 -7
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +61 -62
- package/src/store/chat/slices/plugin/action.test.ts +71 -0
- package/src/store/chat/slices/plugin/actions/internals.ts +14 -5
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createMockAgentConfig,
|
|
14
14
|
createMockChatConfig,
|
|
15
15
|
createMockMessage,
|
|
16
|
+
createMockResolvedAgentConfig,
|
|
16
17
|
} from './fixtures';
|
|
17
18
|
import { resetTestEnvironment, setupMockSelectors, spyOnMessageService } from './helpers';
|
|
18
19
|
|
|
@@ -57,6 +58,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
57
58
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
58
59
|
model: 'gpt-4o-mini',
|
|
59
60
|
provider: 'openai',
|
|
61
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
60
62
|
});
|
|
61
63
|
expect(response.isFunctionCall).toEqual(false);
|
|
62
64
|
expect(response.content).toEqual(TEST_CONTENT.AI_RESPONSE);
|
|
@@ -83,6 +85,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
83
85
|
provider: 'openai',
|
|
84
86
|
messages,
|
|
85
87
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
88
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
86
89
|
});
|
|
87
90
|
});
|
|
88
91
|
|
|
@@ -127,6 +130,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
127
130
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
128
131
|
model: 'gpt-4o-mini',
|
|
129
132
|
provider: 'openai',
|
|
133
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
130
134
|
});
|
|
131
135
|
expect(response.isFunctionCall).toEqual(true);
|
|
132
136
|
});
|
|
@@ -165,6 +169,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
165
169
|
model: 'gpt-4o-mini',
|
|
166
170
|
provider: 'openai',
|
|
167
171
|
operationId,
|
|
172
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
168
173
|
});
|
|
169
174
|
});
|
|
170
175
|
|
|
@@ -213,6 +218,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
213
218
|
model: 'gpt-4o-mini',
|
|
214
219
|
provider: 'openai',
|
|
215
220
|
operationId,
|
|
221
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
216
222
|
});
|
|
217
223
|
});
|
|
218
224
|
|
|
@@ -251,6 +257,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
251
257
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
252
258
|
model: 'gpt-4o-mini',
|
|
253
259
|
provider: 'openai',
|
|
260
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
254
261
|
});
|
|
255
262
|
});
|
|
256
263
|
|
|
@@ -300,6 +307,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
300
307
|
model: 'gpt-4o-mini',
|
|
301
308
|
provider: 'openai',
|
|
302
309
|
operationId,
|
|
310
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
303
311
|
});
|
|
304
312
|
});
|
|
305
313
|
|
|
@@ -355,6 +363,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
355
363
|
model: 'gpt-4o-mini',
|
|
356
364
|
provider: 'openai',
|
|
357
365
|
operationId,
|
|
366
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
358
367
|
});
|
|
359
368
|
});
|
|
360
369
|
|
|
@@ -394,6 +403,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
394
403
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
395
404
|
model: 'gpt-4o-mini',
|
|
396
405
|
provider: 'openai',
|
|
406
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
397
407
|
});
|
|
398
408
|
expect(response.isFunctionCall).toEqual(true);
|
|
399
409
|
});
|
|
@@ -419,6 +429,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
419
429
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
420
430
|
model: 'gpt-4o-mini',
|
|
421
431
|
provider: 'openai',
|
|
432
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
422
433
|
});
|
|
423
434
|
});
|
|
424
435
|
|
|
@@ -435,7 +446,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
435
446
|
});
|
|
436
447
|
|
|
437
448
|
describe('effectiveAgentId for group orchestration', () => {
|
|
438
|
-
it('should pass
|
|
449
|
+
it('should pass pre-resolved config for sub-agent when subAgentId is set in operation context', async () => {
|
|
439
450
|
const { result } = renderHook(() => useChatStore());
|
|
440
451
|
const messages = [createMockMessage({ role: 'user' })];
|
|
441
452
|
const supervisorAgentId = 'supervisor-agent-id';
|
|
@@ -453,6 +464,9 @@ describe('StreamingExecutor actions', () => {
|
|
|
453
464
|
label: 'Test Group Orchestration',
|
|
454
465
|
});
|
|
455
466
|
|
|
467
|
+
// Pre-resolved config for the sub-agent (in real usage, resolved by internal_createAgentState)
|
|
468
|
+
const subAgentConfig = createMockResolvedAgentConfig();
|
|
469
|
+
|
|
456
470
|
const streamSpy = vi
|
|
457
471
|
.spyOn(chatService, 'createAssistantMessageStream')
|
|
458
472
|
.mockImplementation(async ({ onFinish }) => {
|
|
@@ -466,14 +480,18 @@ describe('StreamingExecutor actions', () => {
|
|
|
466
480
|
model: 'gpt-4o-mini',
|
|
467
481
|
provider: 'openai',
|
|
468
482
|
operationId,
|
|
483
|
+
agentConfig: subAgentConfig,
|
|
469
484
|
});
|
|
470
485
|
});
|
|
471
486
|
|
|
472
|
-
//
|
|
487
|
+
// With the new architecture:
|
|
488
|
+
// - agentId param is for context/tracing (supervisor ID)
|
|
489
|
+
// - resolvedAgentConfig contains the sub-agent's config (passed in by caller)
|
|
473
490
|
expect(streamSpy).toHaveBeenCalledWith(
|
|
474
491
|
expect.objectContaining({
|
|
475
492
|
params: expect.objectContaining({
|
|
476
|
-
agentId:
|
|
493
|
+
agentId: supervisorAgentId, // For context/tracing purposes
|
|
494
|
+
resolvedAgentConfig: subAgentConfig, // Pre-resolved sub-agent config
|
|
477
495
|
}),
|
|
478
496
|
}),
|
|
479
497
|
);
|
|
@@ -511,6 +529,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
511
529
|
model: 'gpt-4o-mini',
|
|
512
530
|
provider: 'openai',
|
|
513
531
|
operationId,
|
|
532
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
514
533
|
});
|
|
515
534
|
});
|
|
516
535
|
|
|
@@ -526,7 +545,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
526
545
|
streamSpy.mockRestore();
|
|
527
546
|
});
|
|
528
547
|
|
|
529
|
-
it('should
|
|
548
|
+
it('should pass resolvedAgentConfig through chatService when subAgentId is present', async () => {
|
|
530
549
|
const { result } = renderHook(() => useChatStore());
|
|
531
550
|
const messages = [createMockMessage({ role: 'user' })];
|
|
532
551
|
const supervisorAgentId = 'supervisor-agent-id';
|
|
@@ -547,6 +566,9 @@ describe('StreamingExecutor actions', () => {
|
|
|
547
566
|
label: 'Test Speak Executor',
|
|
548
567
|
});
|
|
549
568
|
|
|
569
|
+
// Create a mock resolved config that represents the speaking agent's config
|
|
570
|
+
const speakingAgentConfig = createMockResolvedAgentConfig();
|
|
571
|
+
|
|
550
572
|
const streamSpy = vi
|
|
551
573
|
.spyOn(chatService, 'createAssistantMessageStream')
|
|
552
574
|
.mockImplementation(async ({ onFinish }) => {
|
|
@@ -560,15 +582,23 @@ describe('StreamingExecutor actions', () => {
|
|
|
560
582
|
model: 'gpt-4o-mini',
|
|
561
583
|
provider: 'openai',
|
|
562
584
|
operationId,
|
|
585
|
+
// Pass pre-resolved config for the speaking agent
|
|
586
|
+
// In real usage, this is resolved in internal_createAgentState using subAgentId
|
|
587
|
+
agentConfig: speakingAgentConfig,
|
|
563
588
|
});
|
|
564
589
|
});
|
|
565
590
|
|
|
566
|
-
//
|
|
567
|
-
//
|
|
591
|
+
// With the new architecture, config is pre-resolved and passed via resolvedAgentConfig.
|
|
592
|
+
// The agentId param is for context/tracing only.
|
|
593
|
+
// The speaking agent's config is ensured by the caller (internal_createAgentState)
|
|
594
|
+
// resolving config with subAgentId and passing it as agentConfig param.
|
|
568
595
|
expect(streamSpy).toHaveBeenCalledWith(
|
|
569
596
|
expect.objectContaining({
|
|
570
597
|
params: expect.objectContaining({
|
|
571
|
-
agentId
|
|
598
|
+
// agentId is supervisor for context purposes
|
|
599
|
+
agentId: supervisorAgentId,
|
|
600
|
+
// resolvedAgentConfig contains the speaking agent's config
|
|
601
|
+
resolvedAgentConfig: speakingAgentConfig,
|
|
572
602
|
}),
|
|
573
603
|
}),
|
|
574
604
|
);
|
|
@@ -902,6 +932,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
902
932
|
model: 'gpt-4o-mini',
|
|
903
933
|
provider: 'openai',
|
|
904
934
|
operationId,
|
|
935
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
905
936
|
});
|
|
906
937
|
});
|
|
907
938
|
|
|
@@ -943,6 +974,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
943
974
|
messageId: TEST_IDS.ASSISTANT_MESSAGE_ID,
|
|
944
975
|
model: 'gpt-4o-mini',
|
|
945
976
|
provider: 'openai',
|
|
977
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
946
978
|
});
|
|
947
979
|
});
|
|
948
980
|
|
|
@@ -1040,6 +1072,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
1040
1072
|
stepCount: 0,
|
|
1041
1073
|
},
|
|
1042
1074
|
},
|
|
1075
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
1043
1076
|
});
|
|
1044
1077
|
|
|
1045
1078
|
// Execute internal_execAgentRuntime with the pre-created operationId
|
|
@@ -1135,6 +1168,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
1135
1168
|
stepCount: 0,
|
|
1136
1169
|
},
|
|
1137
1170
|
},
|
|
1171
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
1138
1172
|
});
|
|
1139
1173
|
|
|
1140
1174
|
// Suppress console.error for this test
|
|
@@ -1235,6 +1269,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
1235
1269
|
stepCount: 0,
|
|
1236
1270
|
},
|
|
1237
1271
|
},
|
|
1272
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
1238
1273
|
});
|
|
1239
1274
|
|
|
1240
1275
|
// Should not throw
|
|
@@ -1489,6 +1524,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
1489
1524
|
stepCount: 1,
|
|
1490
1525
|
},
|
|
1491
1526
|
},
|
|
1527
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
1492
1528
|
});
|
|
1493
1529
|
|
|
1494
1530
|
await act(async () => {
|
|
@@ -1583,6 +1619,7 @@ describe('StreamingExecutor actions', () => {
|
|
|
1583
1619
|
stepCount: 1,
|
|
1584
1620
|
},
|
|
1585
1621
|
},
|
|
1622
|
+
agentConfig: createMockResolvedAgentConfig(),
|
|
1586
1623
|
});
|
|
1587
1624
|
|
|
1588
1625
|
await act(async () => {
|
|
@@ -1602,4 +1639,91 @@ describe('StreamingExecutor actions', () => {
|
|
|
1602
1639
|
expect(result.current.operations[operationId!].status).toBe('failed');
|
|
1603
1640
|
});
|
|
1604
1641
|
});
|
|
1642
|
+
|
|
1643
|
+
describe('isSubTask filtering', () => {
|
|
1644
|
+
it('should filter out lobe-gtd tools when isSubTask is true', async () => {
|
|
1645
|
+
const { result } = renderHook(() => useChatStore());
|
|
1646
|
+
const messages = [createMockMessage({ role: 'user' })];
|
|
1647
|
+
|
|
1648
|
+
// Mock resolveAgentConfig to return plugins including lobe-gtd
|
|
1649
|
+
const resolveAgentConfigSpy = vi
|
|
1650
|
+
.spyOn(agentConfigResolver, 'resolveAgentConfig')
|
|
1651
|
+
.mockReturnValue({
|
|
1652
|
+
agentConfig: createMockAgentConfig(),
|
|
1653
|
+
chatConfig: createMockChatConfig(),
|
|
1654
|
+
isBuiltinAgent: false,
|
|
1655
|
+
plugins: ['lobe-gtd', 'lobe-local-system', 'other-plugin'],
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
// Create operation
|
|
1659
|
+
let operationId: string;
|
|
1660
|
+
act(() => {
|
|
1661
|
+
const res = result.current.startOperation({
|
|
1662
|
+
type: 'execClientTask',
|
|
1663
|
+
context: {
|
|
1664
|
+
agentId: TEST_IDS.SESSION_ID,
|
|
1665
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
1666
|
+
},
|
|
1667
|
+
});
|
|
1668
|
+
operationId = res.operationId;
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
// Call internal_createAgentState with isSubTask: true
|
|
1672
|
+
act(() => {
|
|
1673
|
+
result.current.internal_createAgentState({
|
|
1674
|
+
messages,
|
|
1675
|
+
parentMessageId: TEST_IDS.USER_MESSAGE_ID,
|
|
1676
|
+
operationId,
|
|
1677
|
+
isSubTask: true,
|
|
1678
|
+
});
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
// Verify that resolveAgentConfig was called
|
|
1682
|
+
expect(resolveAgentConfigSpy).toHaveBeenCalled();
|
|
1683
|
+
|
|
1684
|
+
resolveAgentConfigSpy.mockRestore();
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
it('should NOT filter out lobe-gtd tools when isSubTask is false or undefined', async () => {
|
|
1688
|
+
const { result } = renderHook(() => useChatStore());
|
|
1689
|
+
const messages = [createMockMessage({ role: 'user' })];
|
|
1690
|
+
|
|
1691
|
+
// Mock resolveAgentConfig to return plugins including lobe-gtd
|
|
1692
|
+
const resolveAgentConfigSpy = vi
|
|
1693
|
+
.spyOn(agentConfigResolver, 'resolveAgentConfig')
|
|
1694
|
+
.mockReturnValue({
|
|
1695
|
+
agentConfig: createMockAgentConfig(),
|
|
1696
|
+
chatConfig: createMockChatConfig(),
|
|
1697
|
+
isBuiltinAgent: false,
|
|
1698
|
+
plugins: ['lobe-gtd', 'lobe-local-system', 'other-plugin'],
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
// Create operation without isSubTask (normal conversation)
|
|
1702
|
+
let operationId: string;
|
|
1703
|
+
act(() => {
|
|
1704
|
+
const res = result.current.startOperation({
|
|
1705
|
+
type: 'execAgentRuntime',
|
|
1706
|
+
context: {
|
|
1707
|
+
agentId: TEST_IDS.SESSION_ID,
|
|
1708
|
+
topicId: TEST_IDS.TOPIC_ID,
|
|
1709
|
+
},
|
|
1710
|
+
});
|
|
1711
|
+
operationId = res.operationId;
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1714
|
+
// Call internal_createAgentState without isSubTask
|
|
1715
|
+
act(() => {
|
|
1716
|
+
result.current.internal_createAgentState({
|
|
1717
|
+
messages,
|
|
1718
|
+
parentMessageId: TEST_IDS.USER_MESSAGE_ID,
|
|
1719
|
+
operationId,
|
|
1720
|
+
});
|
|
1721
|
+
});
|
|
1722
|
+
|
|
1723
|
+
// Verify that resolveAgentConfig was called
|
|
1724
|
+
expect(resolveAgentConfigSpy).toHaveBeenCalled();
|
|
1725
|
+
|
|
1726
|
+
resolveAgentConfigSpy.mockRestore();
|
|
1727
|
+
});
|
|
1728
|
+
});
|
|
1605
1729
|
});
|
|
@@ -28,7 +28,7 @@ import { type StateCreator } from 'zustand/vanilla';
|
|
|
28
28
|
|
|
29
29
|
import { createAgentToolsEngine } from '@/helpers/toolEngineering';
|
|
30
30
|
import { chatService } from '@/services/chat';
|
|
31
|
-
import { resolveAgentConfig } from '@/services/chat/mecha';
|
|
31
|
+
import { type ResolvedAgentConfig, resolveAgentConfig } from '@/services/chat/mecha';
|
|
32
32
|
import { messageService } from '@/services/message';
|
|
33
33
|
import { createAgentExecutors } from '@/store/chat/agents/createAgentExecutors';
|
|
34
34
|
import { type ChatStore } from '@/store/chat/store';
|
|
@@ -72,9 +72,15 @@ export interface StreamingExecutorAction {
|
|
|
72
72
|
* Used to get Agent config (model, provider, plugins) instead of agentId
|
|
73
73
|
*/
|
|
74
74
|
subAgentId?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Whether this is a sub-task execution (disables lobe-gtd tools to prevent nested sub-tasks)
|
|
77
|
+
*/
|
|
78
|
+
isSubTask?: boolean;
|
|
75
79
|
}) => {
|
|
76
80
|
state: AgentState;
|
|
77
81
|
context: AgentRuntimeContext;
|
|
82
|
+
/** Resolved agent config with isSubTask filtering applied */
|
|
83
|
+
agentConfig: ResolvedAgentConfig;
|
|
78
84
|
};
|
|
79
85
|
/**
|
|
80
86
|
* Retrieves an AI-generated chat message from the backend service with streaming
|
|
@@ -85,7 +91,8 @@ export interface StreamingExecutorAction {
|
|
|
85
91
|
model: string;
|
|
86
92
|
provider: string;
|
|
87
93
|
operationId?: string;
|
|
88
|
-
|
|
94
|
+
/** Pre-resolved agent config (from internal_createAgentState) with isSubTask filtering applied */
|
|
95
|
+
agentConfig: ResolvedAgentConfig;
|
|
89
96
|
traceId?: string;
|
|
90
97
|
/** Initial context for page editor (captured at operation start) */
|
|
91
98
|
initialContext?: RuntimeInitialContext;
|
|
@@ -132,6 +139,10 @@ export interface StreamingExecutorAction {
|
|
|
132
139
|
*/
|
|
133
140
|
parentOperationId?: string;
|
|
134
141
|
skipCreateFirstMessage?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Whether this is a sub-task execution (disables lobe-gtd tools to prevent nested sub-tasks)
|
|
144
|
+
*/
|
|
145
|
+
isSubTask?: boolean;
|
|
135
146
|
}) => Promise<{ cost?: Cost; usage?: Usage } | void>;
|
|
136
147
|
}
|
|
137
148
|
|
|
@@ -151,6 +162,7 @@ export const streamingExecutor: StateCreator<
|
|
|
151
162
|
initialContext,
|
|
152
163
|
operationId,
|
|
153
164
|
subAgentId: paramSubAgentId,
|
|
165
|
+
isSubTask,
|
|
154
166
|
}) => {
|
|
155
167
|
// Use provided agentId/topicId or fallback to global state
|
|
156
168
|
const { activeAgentId, activeTopicId } = get();
|
|
@@ -169,11 +181,16 @@ export const streamingExecutor: StateCreator<
|
|
|
169
181
|
|
|
170
182
|
// Resolve agent config with builtin agent runtime config merged
|
|
171
183
|
// This ensures runtime plugins (e.g., 'lobe-agent-builder' for Agent Builder) are included
|
|
172
|
-
|
|
184
|
+
// isSubTask is passed to filter out lobe-gtd tools to prevent nested sub-task creation
|
|
185
|
+
const agentConfig = resolveAgentConfig({
|
|
173
186
|
agentId: effectiveAgentId || '',
|
|
174
187
|
groupId, // Pass groupId for supervisor detection
|
|
188
|
+
isSubTask, // Filter out lobe-gtd in sub-task context
|
|
175
189
|
scope, // Pass scope from operation context
|
|
176
190
|
});
|
|
191
|
+
const { agentConfig: agentConfigData, plugins: pluginIds } = agentConfig;
|
|
192
|
+
|
|
193
|
+
log('[internal_createAgentState] resolved plugins=%o, isSubTask=%s', pluginIds, isSubTask);
|
|
177
194
|
|
|
178
195
|
// Get tools manifest map
|
|
179
196
|
const toolsEngine = createAgentToolsEngine({
|
|
@@ -260,7 +277,7 @@ export const streamingExecutor: StateCreator<
|
|
|
260
277
|
initialContext: runtimeInitialContext,
|
|
261
278
|
};
|
|
262
279
|
|
|
263
|
-
return { state, context };
|
|
280
|
+
return { state, context, agentConfig };
|
|
264
281
|
},
|
|
265
282
|
|
|
266
283
|
internal_fetchAIChatMessage: async ({
|
|
@@ -333,23 +350,10 @@ export const streamingExecutor: StateCreator<
|
|
|
333
350
|
// Create base context for child operations and message queries
|
|
334
351
|
const fetchContext = { agentId, topicId, threadId, groupId, scope };
|
|
335
352
|
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// Resolve agent config with params adjusted based on chatConfig
|
|
342
|
-
// If agentConfig is passed in, use it directly (it's already resolved)
|
|
343
|
-
// Otherwise, resolve from mecha layer which handles:
|
|
344
|
-
// - Builtin agent runtime config merging
|
|
345
|
-
// - max_tokens/reasoning_effort based on chatConfig settings
|
|
346
|
-
const resolved = resolveAgentConfig({
|
|
347
|
-
agentId: effectiveAgentId,
|
|
348
|
-
groupId, // Pass groupId for supervisor detection
|
|
349
|
-
scope, // scope is already available from line 329
|
|
350
|
-
});
|
|
351
|
-
const finalAgentConfig = agentConfig || resolved.agentConfig;
|
|
352
|
-
const chatConfig = resolved.chatConfig;
|
|
353
|
+
// Use pre-resolved agent config (from internal_createAgentState)
|
|
354
|
+
// This ensures isSubTask filtering and other runtime modifications are preserved
|
|
355
|
+
const { agentConfig: agentConfigData, chatConfig, plugins: pluginIds } = agentConfig;
|
|
356
|
+
log('[internal_fetchAIChatMessage] using pre-resolved config, plugins=%o', pluginIds);
|
|
353
357
|
|
|
354
358
|
let finalUsage: ModelUsage | undefined;
|
|
355
359
|
let finalToolCalls: MessageToolCall[] | undefined;
|
|
@@ -437,18 +441,18 @@ export const streamingExecutor: StateCreator<
|
|
|
437
441
|
await chatService.createAssistantMessageStream({
|
|
438
442
|
abortController,
|
|
439
443
|
params: {
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
// In normal chat: agentId for the main agent
|
|
443
|
-
agentId: effectiveAgentId || undefined,
|
|
444
|
+
// agentId is used for context, not for config resolution (config is pre-resolved)
|
|
445
|
+
agentId: agentId || undefined,
|
|
444
446
|
groupId,
|
|
445
447
|
messages,
|
|
446
448
|
model,
|
|
447
449
|
provider,
|
|
450
|
+
// Pass pre-resolved config to avoid duplicate resolveAgentConfig calls
|
|
451
|
+
// This ensures isSubTask filtering and other runtime modifications are preserved
|
|
452
|
+
resolvedAgentConfig: agentConfig,
|
|
448
453
|
scope, // Pass scope to chat service for page-agent injection
|
|
449
|
-
topicId, // Pass topicId for GTD context injection
|
|
450
|
-
...
|
|
451
|
-
plugins: finalAgentConfig.plugins,
|
|
454
|
+
topicId: topicId ?? undefined, // Pass topicId for GTD context injection
|
|
455
|
+
...agentConfigData.params,
|
|
452
456
|
},
|
|
453
457
|
historySummary: historySummary?.content,
|
|
454
458
|
// Pass page editor context from agent runtime
|
|
@@ -544,7 +548,13 @@ export const streamingExecutor: StateCreator<
|
|
|
544
548
|
},
|
|
545
549
|
|
|
546
550
|
internal_execAgentRuntime: async (params) => {
|
|
547
|
-
const {
|
|
551
|
+
const {
|
|
552
|
+
messages: originalMessages,
|
|
553
|
+
parentMessageId,
|
|
554
|
+
parentMessageType,
|
|
555
|
+
context,
|
|
556
|
+
isSubTask,
|
|
557
|
+
} = params;
|
|
548
558
|
|
|
549
559
|
// Extract values from context
|
|
550
560
|
const { agentId, topicId, threadId, subAgentId, groupId } = context;
|
|
@@ -593,30 +603,32 @@ export const streamingExecutor: StateCreator<
|
|
|
593
603
|
// Create a new array to avoid modifying the original messages
|
|
594
604
|
let messages = [...originalMessages];
|
|
595
605
|
|
|
596
|
-
//
|
|
597
|
-
//
|
|
598
|
-
//
|
|
599
|
-
//
|
|
600
|
-
const {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
606
|
+
// ===========================================
|
|
607
|
+
// Step 1: Create Agent State (resolves config once)
|
|
608
|
+
// ===========================================
|
|
609
|
+
// agentConfig contains isSubTask filtering and is passed to callLLM executor
|
|
610
|
+
const {
|
|
611
|
+
state: initialAgentState,
|
|
612
|
+
context: initialAgentContext,
|
|
613
|
+
agentConfig,
|
|
614
|
+
} = get().internal_createAgentState({
|
|
615
|
+
messages,
|
|
616
|
+
parentMessageId: params.parentMessageId,
|
|
617
|
+
agentId,
|
|
618
|
+
topicId,
|
|
619
|
+
threadId: threadId ?? undefined,
|
|
620
|
+
initialState: params.initialState,
|
|
621
|
+
initialContext: params.initialContext,
|
|
622
|
+
operationId,
|
|
623
|
+
subAgentId, // Pass subAgentId for agent config retrieval
|
|
624
|
+
isSubTask, // Pass isSubTask to filter out lobe-gtd tools in sub-task context
|
|
604
625
|
});
|
|
605
626
|
|
|
606
|
-
// Use
|
|
627
|
+
// Use model/provider from resolved agentConfig
|
|
628
|
+
const { agentConfig: agentConfigData } = agentConfig;
|
|
607
629
|
const model = agentConfigData.model;
|
|
608
630
|
const provider = agentConfigData.provider;
|
|
609
631
|
|
|
610
|
-
// ===========================================
|
|
611
|
-
// Step 1: Knowledge Base Tool Integration
|
|
612
|
-
// ===========================================
|
|
613
|
-
// RAG retrieval is now handled by the Knowledge Base Tool
|
|
614
|
-
// The AI will decide when to call searchKnowledgeBase and readKnowledge tools
|
|
615
|
-
// based on the conversation context and available knowledge bases
|
|
616
|
-
|
|
617
|
-
// TODO: Implement selected files full-text injection if needed
|
|
618
|
-
// User-selected files should be handled differently from knowledge base files
|
|
619
|
-
|
|
620
632
|
// ===========================================
|
|
621
633
|
// Step 2: Create and Execute Agent Runtime
|
|
622
634
|
// ===========================================
|
|
@@ -633,6 +645,7 @@ export const streamingExecutor: StateCreator<
|
|
|
633
645
|
|
|
634
646
|
const runtime = new AgentRuntime(agent, {
|
|
635
647
|
executors: createAgentExecutors({
|
|
648
|
+
agentConfig, // Pass pre-resolved config to callLLM executor
|
|
636
649
|
get,
|
|
637
650
|
messageKey,
|
|
638
651
|
operationId,
|
|
@@ -650,20 +663,6 @@ export const streamingExecutor: StateCreator<
|
|
|
650
663
|
operationId,
|
|
651
664
|
});
|
|
652
665
|
|
|
653
|
-
// Create agent state and context with user intervention config
|
|
654
|
-
const { state: initialAgentState, context: initialAgentContext } =
|
|
655
|
-
get().internal_createAgentState({
|
|
656
|
-
messages,
|
|
657
|
-
parentMessageId: params.parentMessageId,
|
|
658
|
-
agentId,
|
|
659
|
-
topicId,
|
|
660
|
-
threadId: threadId ?? undefined,
|
|
661
|
-
initialState: params.initialState,
|
|
662
|
-
initialContext: params.initialContext,
|
|
663
|
-
operationId,
|
|
664
|
-
subAgentId, // Pass subAgentId for agent config retrieval
|
|
665
|
-
});
|
|
666
|
-
|
|
667
666
|
let state = initialAgentState;
|
|
668
667
|
let nextContext = initialAgentContext;
|
|
669
668
|
|
|
@@ -1060,6 +1060,77 @@ describe('ChatPluginAction', () => {
|
|
|
1060
1060
|
|
|
1061
1061
|
expect(transformed[0].apiName).toBe(longApiName);
|
|
1062
1062
|
});
|
|
1063
|
+
|
|
1064
|
+
it('should repair malformed JSON arguments with escaped string issue', () => {
|
|
1065
|
+
// This is the malformed data from haiku-4.5 model
|
|
1066
|
+
// The entire JSON got stuffed into the "description" field with escaped quotes
|
|
1067
|
+
const malformedArguments = JSON.stringify({
|
|
1068
|
+
description:
|
|
1069
|
+
'Synthesize all 10 batch analyses into 10 most important themes for product builders", "instruction": "You have access to 10 batch analysis files", "runInClient": true, "timeout": 120000}',
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
const toolCalls: MessageToolCall[] = [
|
|
1073
|
+
{
|
|
1074
|
+
id: 'tool1',
|
|
1075
|
+
function: {
|
|
1076
|
+
name: ['lobe-gtd', 'execTask', 'default'].join(PLUGIN_SCHEMA_SEPARATOR),
|
|
1077
|
+
arguments: malformedArguments,
|
|
1078
|
+
},
|
|
1079
|
+
type: 'function',
|
|
1080
|
+
},
|
|
1081
|
+
];
|
|
1082
|
+
|
|
1083
|
+
// Setup builtin tool manifest with schema that has required fields
|
|
1084
|
+
act(() => {
|
|
1085
|
+
useToolStore.setState({
|
|
1086
|
+
builtinTools: [
|
|
1087
|
+
{
|
|
1088
|
+
type: 'builtin',
|
|
1089
|
+
identifier: 'lobe-gtd',
|
|
1090
|
+
manifest: {
|
|
1091
|
+
identifier: 'lobe-gtd',
|
|
1092
|
+
api: [
|
|
1093
|
+
{
|
|
1094
|
+
name: 'execTask',
|
|
1095
|
+
description: 'Execute async task',
|
|
1096
|
+
parameters: {
|
|
1097
|
+
type: 'object',
|
|
1098
|
+
required: ['description', 'instruction'],
|
|
1099
|
+
properties: {
|
|
1100
|
+
description: { type: 'string' },
|
|
1101
|
+
instruction: { type: 'string' },
|
|
1102
|
+
runInClient: { type: 'boolean' },
|
|
1103
|
+
timeout: { type: 'number' },
|
|
1104
|
+
},
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1107
|
+
],
|
|
1108
|
+
type: 'builtin',
|
|
1109
|
+
} as any,
|
|
1110
|
+
},
|
|
1111
|
+
],
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
const { result } = renderHook(() => useChatStore());
|
|
1116
|
+
|
|
1117
|
+
const transformed = result.current.internal_transformToolCalls(toolCalls);
|
|
1118
|
+
|
|
1119
|
+
// Parse the transformed arguments
|
|
1120
|
+
const repairedArgs = JSON.parse(transformed[0].arguments);
|
|
1121
|
+
|
|
1122
|
+
// Verify all fields are correctly extracted
|
|
1123
|
+
expect(repairedArgs).toHaveProperty('description');
|
|
1124
|
+
expect(repairedArgs).toHaveProperty('instruction');
|
|
1125
|
+
expect(repairedArgs).toHaveProperty('runInClient', true);
|
|
1126
|
+
expect(repairedArgs).toHaveProperty('timeout', 120000);
|
|
1127
|
+
|
|
1128
|
+
// Verify description is the correct short value, not the entire malformed string
|
|
1129
|
+
expect(repairedArgs.description).toBe(
|
|
1130
|
+
'Synthesize all 10 batch analyses into 10 most important themes for product builders',
|
|
1131
|
+
);
|
|
1132
|
+
expect(repairedArgs.instruction).toBe('You have access to 10 batch analysis files');
|
|
1133
|
+
});
|
|
1063
1134
|
});
|
|
1064
1135
|
|
|
1065
1136
|
describe('internal_updatePluginError', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
|
-
import { ToolNameResolver } from '@lobechat/context-engine';
|
|
2
|
+
import { ToolArgumentsRepairer, ToolNameResolver } from '@lobechat/context-engine';
|
|
3
3
|
import { type ChatToolPayload, type MessageToolCall } from '@lobechat/types';
|
|
4
4
|
import { type LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
|
|
5
5
|
import { type StateCreator } from 'zustand/vanilla';
|
|
@@ -78,9 +78,18 @@ export const pluginInternals: StateCreator<
|
|
|
78
78
|
|
|
79
79
|
// Resolve tool calls and add source field
|
|
80
80
|
const resolved = toolNameResolver.resolve(toolCalls, manifests);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
|
|
82
|
+
return resolved.map((payload) => {
|
|
83
|
+
// Parse and repair arguments if needed
|
|
84
|
+
const manifest = manifests[payload.identifier];
|
|
85
|
+
const repairer = new ToolArgumentsRepairer(manifest);
|
|
86
|
+
const repairedArgs = repairer.parse(payload.apiName, payload.arguments);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...payload,
|
|
90
|
+
arguments: JSON.stringify(repairedArgs),
|
|
91
|
+
source: sourceMap[payload.identifier],
|
|
92
|
+
};
|
|
93
|
+
});
|
|
85
94
|
},
|
|
86
95
|
});
|