@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +17 -0
  3. package/docs/development/database-schema.dbml +34 -0
  4. package/package.json +1 -1
  5. package/packages/builtin-tool-group-management/src/manifest.ts +54 -53
  6. package/packages/builtin-tool-group-management/src/systemRole.ts +43 -111
  7. package/packages/context-engine/src/engine/tools/ToolArgumentsRepairer.ts +129 -0
  8. package/packages/context-engine/src/engine/tools/__tests__/ToolArgumentsRepairer.test.ts +186 -0
  9. package/packages/context-engine/src/engine/tools/index.ts +3 -0
  10. package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/index.ts +2 -0
  11. package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/with-assistant-group.json +156 -0
  12. package/packages/conversation-flow/src/__tests__/parse.test.ts +22 -0
  13. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +88 -11
  14. package/packages/database/migrations/0070_add_user_memory_activities.sql +35 -0
  15. package/packages/database/migrations/meta/0070_snapshot.json +10656 -0
  16. package/packages/database/migrations/meta/_journal.json +8 -1
  17. package/packages/database/src/schemas/userMemories/index.ts +71 -0
  18. package/packages/types/src/openai/chat.ts +0 -4
  19. package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +5 -1
  20. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +8 -8
  21. package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx +142 -15
  22. package/src/app/[variants]/(main)/community/(detail)/user/features/useUserDetail.ts +45 -20
  23. package/src/server/routers/lambda/market/agentGroup.ts +179 -1
  24. package/src/server/services/discover/index.ts +4 -0
  25. package/src/services/chat/chat.test.ts +109 -104
  26. package/src/services/chat/index.ts +13 -32
  27. package/src/services/chat/mecha/agentConfigResolver.test.ts +113 -0
  28. package/src/services/chat/mecha/agentConfigResolver.ts +15 -5
  29. package/src/services/marketApi.ts +14 -0
  30. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +13 -0
  31. package/src/store/chat/agents/createAgentExecutors.ts +13 -1
  32. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +5 -1
  33. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +14 -0
  34. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +131 -7
  35. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +61 -62
  36. package/src/store/chat/slices/plugin/action.test.ts +71 -0
  37. 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 effectiveAgentId (subAgentId) to chatService when subAgentId is set in operation context', async () => {
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
- // Verify chatService was called with subAgentId (effectiveAgentId), not supervisorAgentId
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: subAgentId, // Should be subAgentId, not supervisorAgentId
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 use subAgentId for agent config resolution when present', async () => {
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
- // The key assertion: chatService should receive subAgentId for agent config resolution
567
- // This ensures the speaking agent's system role and tools are used, not the supervisor's
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: subAgentId,
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
- agentConfig?: any;
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
- const { agentConfig: agentConfigData, plugins: pluginIds } = resolveAgentConfig({
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
- // For group orchestration scenarios:
337
- // - subAgentId is used for agent config retrieval (model, provider, plugins)
338
- // - agentId is used for session ID (message storage location)
339
- const effectiveAgentId = subAgentId || agentId;
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
- // Use effectiveAgentId for agent config resolution (system role, tools, etc.)
441
- // In group orchestration: subAgentId for the actual speaking agent
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
- ...finalAgentConfig.params,
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 { messages: originalMessages, parentMessageId, parentMessageType, context } = params;
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
- // Use effectiveAgentId to get agent config (subAgentId in group orchestration, agentId otherwise)
597
- // resolveAgentConfig handles:
598
- // - Builtin agent runtime config merging
599
- // - max_tokens/reasoning_effort based on chatConfig settings
600
- const { agentConfig: agentConfigData } = resolveAgentConfig({
601
- agentId: effectiveAgentId || '',
602
- groupId, // Pass groupId for supervisor detection
603
- scope: context.scope, // Pass scope from context parameter
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 agent config from agentId
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
- return resolved.map((payload) => ({
82
- ...payload,
83
- source: sourceMap[payload.identifier],
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
  });