@lobehub/lobehub 2.0.0-next.335 → 2.0.0-next.337

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 (53) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/changelog/v1.json +21 -0
  3. package/package.json +1 -1
  4. package/packages/builtin-tool-agent-builder/src/manifest.ts +0 -2
  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/builtin-tool-memory/src/client/Render/AddPreferenceMemory/index.tsx +17 -0
  8. package/packages/builtin-tool-memory/src/client/Render/index.ts +2 -0
  9. package/packages/builtin-tool-memory/src/client/Streaming/AddPreferenceMemory/index.tsx +17 -0
  10. package/packages/builtin-tool-memory/src/client/Streaming/index.ts +4 -3
  11. package/packages/builtin-tool-memory/src/client/components/PreferenceMemoryCard.tsx +357 -0
  12. package/packages/builtin-tool-memory/src/client/components/index.ts +1 -0
  13. package/packages/builtin-tool-memory/src/executor/index.ts +3 -3
  14. package/packages/builtin-tool-memory/src/systemRole.ts +1 -0
  15. package/packages/context-engine/src/engine/tools/ToolArgumentsRepairer.ts +129 -0
  16. package/packages/context-engine/src/engine/tools/__tests__/ToolArgumentsRepairer.test.ts +186 -0
  17. package/packages/context-engine/src/engine/tools/index.ts +3 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/index.ts +2 -0
  19. package/packages/conversation-flow/src/__tests__/fixtures/inputs/tasks/with-assistant-group.json +156 -0
  20. package/packages/conversation-flow/src/__tests__/parse.test.ts +22 -0
  21. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +88 -11
  22. package/packages/database/src/models/userMemory/model.ts +1 -1
  23. package/packages/memory-user-memory/src/extractors/context.test.ts +0 -1
  24. package/packages/memory-user-memory/src/extractors/experience.test.ts +0 -1
  25. package/packages/memory-user-memory/src/extractors/identity.test.ts +0 -1
  26. package/packages/memory-user-memory/src/extractors/preference.test.ts +0 -1
  27. package/packages/memory-user-memory/src/schemas/context.ts +0 -2
  28. package/packages/memory-user-memory/src/schemas/experience.ts +0 -2
  29. package/packages/memory-user-memory/src/schemas/identity.ts +1 -2
  30. package/packages/memory-user-memory/src/schemas/preference.ts +0 -2
  31. package/packages/types/src/openai/chat.ts +0 -4
  32. package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +5 -1
  33. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +8 -8
  34. package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx +142 -15
  35. package/src/app/[variants]/(main)/community/(detail)/user/features/useUserDetail.ts +45 -20
  36. package/src/server/routers/lambda/market/agentGroup.ts +179 -1
  37. package/src/server/routers/lambda/userMemories/tools.ts +5 -4
  38. package/src/server/routers/lambda/userMemories.ts +4 -4
  39. package/src/server/services/discover/index.ts +4 -0
  40. package/src/server/services/memory/userMemory/extract.ts +3 -3
  41. package/src/services/chat/chat.test.ts +109 -104
  42. package/src/services/chat/index.ts +13 -32
  43. package/src/services/chat/mecha/agentConfigResolver.test.ts +113 -0
  44. package/src/services/chat/mecha/agentConfigResolver.ts +15 -5
  45. package/src/services/marketApi.ts +14 -0
  46. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +13 -0
  47. package/src/store/chat/agents/createAgentExecutors.ts +13 -1
  48. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +5 -1
  49. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +14 -0
  50. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +131 -7
  51. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +61 -62
  52. package/src/store/chat/slices/plugin/action.test.ts +71 -0
  53. package/src/store/chat/slices/plugin/actions/internals.ts +14 -5
@@ -61,6 +61,12 @@ export interface AgentConfigResolverContext {
61
61
  */
62
62
  groupId?: string;
63
63
 
64
+ /**
65
+ * Whether this is a sub-task execution.
66
+ * When true, filters out lobe-gtd tools to prevent nested sub-task creation.
67
+ */
68
+ isSubTask?: boolean;
69
+
64
70
  /** Current model being used (for template variables) */
65
71
  model?: string;
66
72
  /** Plugins enabled for the agent */
@@ -106,9 +112,13 @@ export interface ResolvedAgentConfig {
106
112
  * For regular agents, this simply returns the config from the store.
107
113
  */
108
114
  export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAgentConfig => {
109
- const { agentId, model, documentContent, plugins, targetAgentConfig } = ctx;
115
+ const { agentId, model, documentContent, plugins, targetAgentConfig, isSubTask } = ctx;
116
+
117
+ log('resolveAgentConfig called with agentId: %s, scope: %s, isSubTask: %s', agentId, ctx.scope, isSubTask);
110
118
 
111
- log('resolveAgentConfig called with agentId: %s, scope: %s', agentId, ctx.scope);
119
+ // Helper to filter out lobe-gtd in sub-task context to prevent nested sub-task creation
120
+ const applySubTaskFilter = (pluginIds: string[]) =>
121
+ isSubTask ? pluginIds.filter((id) => id !== 'lobe-gtd') : pluginIds;
112
122
 
113
123
  const agentStoreState = getAgentStoreState();
114
124
 
@@ -199,7 +209,7 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
199
209
  agentConfig: finalAgentConfig,
200
210
  chatConfig: finalChatConfig,
201
211
  isBuiltinAgent: false,
202
- plugins: pageAgentPlugins,
212
+ plugins: applySubTaskFilter(pageAgentPlugins),
203
213
  };
204
214
  }
205
215
 
@@ -208,7 +218,7 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
208
218
  agentConfig: finalAgentConfig,
209
219
  chatConfig: finalChatConfig,
210
220
  isBuiltinAgent: false,
211
- plugins: finalPlugins,
221
+ plugins: applySubTaskFilter(finalPlugins),
212
222
  };
213
223
  }
214
224
 
@@ -329,7 +339,7 @@ export const resolveAgentConfig = (ctx: AgentConfigResolverContext): ResolvedAge
329
339
  agentConfig: finalAgentConfig,
330
340
  chatConfig: resolvedChatConfig,
331
341
  isBuiltinAgent: true,
332
- plugins: finalPlugins,
342
+ plugins: applySubTaskFilter(finalPlugins),
333
343
  slug,
334
344
  };
335
345
  };
@@ -146,6 +146,20 @@ export class MarketApiService {
146
146
  return lambdaClient.market.agent.getAgentForkSource.query({ identifier });
147
147
  }
148
148
 
149
+ // ==================== Agent Group Status Management ====================
150
+
151
+ async publishAgentGroup(identifier: string): Promise<void> {
152
+ await lambdaClient.market.agentGroup.publishAgentGroup.mutate({ identifier });
153
+ }
154
+
155
+ async unpublishAgentGroup(identifier: string): Promise<void> {
156
+ await lambdaClient.market.agentGroup.unpublishAgentGroup.mutate({ identifier });
157
+ }
158
+
159
+ async deprecateAgentGroup(identifier: string): Promise<void> {
160
+ await lambdaClient.market.agentGroup.deprecateAgentGroup.mutate({ identifier });
161
+ }
162
+
149
163
  // ==================== Fork Agent Group API ====================
150
164
 
151
165
  /**
@@ -1,9 +1,21 @@
1
1
  import type { AgentInstruction, AgentState } from '@lobechat/agent-runtime';
2
2
 
3
+ import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_CONFIG } from '@/const/settings';
4
+ import type { ResolvedAgentConfig } from '@/services/chat/mecha';
3
5
  import { createAgentExecutors } from '@/store/chat/agents/createAgentExecutors';
4
6
  import type { OperationType } from '@/store/chat/slices/operation/types';
5
7
  import type { ChatStore } from '@/store/chat/store';
6
8
 
9
+ /**
10
+ * Create a mock ResolvedAgentConfig for testing
11
+ */
12
+ const createMockResolvedAgentConfig = (): ResolvedAgentConfig => ({
13
+ agentConfig: { ...DEFAULT_AGENT_CONFIG },
14
+ chatConfig: { ...DEFAULT_AGENT_CHAT_CONFIG },
15
+ isBuiltinAgent: false,
16
+ plugins: [],
17
+ });
18
+
7
19
  /**
8
20
  * Execute an executor with mock context
9
21
  *
@@ -60,6 +72,7 @@ export const executeWithMockContext = async ({
60
72
 
61
73
  // Create executors with mock context
62
74
  const executors = createAgentExecutors({
75
+ agentConfig: createMockResolvedAgentConfig(),
63
76
  get: () => mockStore,
64
77
  messageKey: context.messageKey,
65
78
  operationId: context.operationId,
@@ -22,6 +22,8 @@ import type { ChatToolPayload, ConversationContext, CreateMessageParams } from '
22
22
  import debug from 'debug';
23
23
  import pMap from 'p-map';
24
24
 
25
+ import type { ResolvedAgentConfig } from '@/services/chat/mecha';
26
+
25
27
  import { LOADING_FLAT } from '@/const/message';
26
28
  import { aiAgentService } from '@/services/aiAgent';
27
29
  import { agentByIdSelectors } from '@/store/agent/selectors';
@@ -49,6 +51,8 @@ const TOOL_PRICING: Record<string, number> = {
49
51
  * @param context.skipCreateFirstMessage - Skip first message creation
50
52
  */
51
53
  export const createAgentExecutors = (context: {
54
+ /** Pre-resolved agent config with isSubTask filtering applied */
55
+ agentConfig: ResolvedAgentConfig;
52
56
  get: () => ChatStore;
53
57
  messageKey: string;
54
58
  operationId: string;
@@ -169,6 +173,7 @@ export const createAgentExecutors = (context: {
169
173
  model: llmPayload.model,
170
174
  provider: llmPayload.provider,
171
175
  operationId: context.operationId,
176
+ agentConfig: context.agentConfig, // Pass pre-resolved config
172
177
  // Pass runtime context for page editor injection
173
178
  initialContext: runtimeContext?.initialContext,
174
179
  stepContext: runtimeContext?.stepContext,
@@ -1735,7 +1740,12 @@ export const createAgentExecutors = (context: {
1735
1740
  const { threadId, userMessageId, threadMessages, messages } = threadResult;
1736
1741
 
1737
1742
  // 3. Build sub-task ConversationContext (uses threadId for isolation)
1738
- const subContext: ConversationContext = { agentId, topicId, threadId, scope: 'thread' };
1743
+ const subContext: ConversationContext = {
1744
+ agentId,
1745
+ topicId,
1746
+ threadId,
1747
+ scope: 'thread',
1748
+ };
1739
1749
 
1740
1750
  // 4. Create a child operation for task execution (now with threadId)
1741
1751
  const { operationId: taskOperationId } = context.get().startOperation({
@@ -1784,6 +1794,7 @@ export const createAgentExecutors = (context: {
1784
1794
  parentMessageType: 'user',
1785
1795
  operationId: taskOperationId,
1786
1796
  parentOperationId: state.operationId,
1797
+ isSubTask: true, // Disable lobe-gtd tools to prevent nested sub-tasks
1787
1798
  });
1788
1799
 
1789
1800
  log('[%s][exec_client_task] Client-side AgentRuntime execution completed', taskLogId);
@@ -2107,6 +2118,7 @@ export const createAgentExecutors = (context: {
2107
2118
  parentMessageType: 'user',
2108
2119
  operationId: taskOperationId,
2109
2120
  parentOperationId: state.operationId,
2121
+ isSubTask: true, // Disable lobe-gtd tools to prevent nested sub-tasks
2110
2122
  });
2111
2123
 
2112
2124
  log('[%s] Client-side AgentRuntime execution completed', taskLogId);
@@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
4
 
5
5
  import { useChatStore } from '../../../../store';
6
6
  import { messageMapKey } from '../../../../utils/messageMapKey';
7
- import { TEST_IDS, createMockMessage } from './fixtures';
7
+ import { TEST_IDS, createMockMessage, createMockResolvedAgentConfig } from './fixtures';
8
8
  import { resetTestEnvironment } from './helpers';
9
9
 
10
10
  // Keep zustand mock as it's needed globally
@@ -425,6 +425,7 @@ describe('ConversationControl actions', () => {
425
425
  .mockReturnValue({
426
426
  state: {} as any,
427
427
  context: { phase: 'init' } as any,
428
+ agentConfig: createMockResolvedAgentConfig(),
428
429
  });
429
430
  const internal_execAgentRuntimeSpy = vi
430
431
  .spyOn(result.current, 'internal_execAgentRuntime')
@@ -497,6 +498,7 @@ describe('ConversationControl actions', () => {
497
498
  .mockReturnValue({
498
499
  state: {} as any,
499
500
  context: { phase: 'init' } as any,
501
+ agentConfig: createMockResolvedAgentConfig(),
500
502
  });
501
503
  const internal_execAgentRuntimeSpy = vi
502
504
  .spyOn(result.current, 'internal_execAgentRuntime')
@@ -596,6 +598,7 @@ describe('ConversationControl actions', () => {
596
598
  .mockReturnValue({
597
599
  state: {} as any,
598
600
  context: { phase: 'init' } as any,
601
+ agentConfig: createMockResolvedAgentConfig(),
599
602
  });
600
603
  const internal_execAgentRuntimeSpy = vi
601
604
  .spyOn(result.current, 'internal_execAgentRuntime')
@@ -669,6 +672,7 @@ describe('ConversationControl actions', () => {
669
672
  .mockReturnValue({
670
673
  state: {} as any,
671
674
  context: { phase: 'init' } as any,
675
+ agentConfig: createMockResolvedAgentConfig(),
672
676
  });
673
677
  const internal_execAgentRuntimeSpy = vi
674
678
  .spyOn(result.current, 'internal_execAgentRuntime')
@@ -1,6 +1,7 @@
1
1
  import { type UIChatMessage } from '@lobechat/types';
2
2
 
3
3
  import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_CONFIG } from '@/const/settings';
4
+ import type { ResolvedAgentConfig } from '@/services/chat/mecha';
4
5
 
5
6
  // Test Constants
6
7
  export const TEST_IDS = {
@@ -63,3 +64,16 @@ export const createMockStoreState = (overrides = {}) => ({
63
64
  toolCallingStreamIds: {},
64
65
  ...overrides,
65
66
  });
67
+
68
+ /**
69
+ * Create a mock ResolvedAgentConfig for testing
70
+ */
71
+ export const createMockResolvedAgentConfig = (
72
+ overrides: Partial<ResolvedAgentConfig> = {},
73
+ ): ResolvedAgentConfig => ({
74
+ agentConfig: createMockAgentConfig(),
75
+ chatConfig: createMockChatConfig(),
76
+ isBuiltinAgent: false,
77
+ plugins: [],
78
+ ...overrides,
79
+ });
@@ -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
  });