@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
@@ -27,7 +27,7 @@ import { ModelProvider } from 'model-bank';
27
27
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
28
28
  import { enableAuth } from '@/envs/auth';
29
29
  import { getSearchConfig } from '@/helpers/getSearchConfig';
30
- import { createAgentToolsEngine, createToolsEngine } from '@/helpers/toolEngineering';
30
+ import { createAgentToolsEngine } from '@/helpers/toolEngineering';
31
31
  import { getAgentStoreState } from '@/store/agent';
32
32
  import {
33
33
  agentByIdSelectors,
@@ -58,10 +58,10 @@ import { createHeaderWithAuth } from '../_auth';
58
58
  import { API_ENDPOINTS } from '../_url';
59
59
  import { findDeploymentName, isEnableFetchOnClient, resolveRuntimeProvider } from './helper';
60
60
  import {
61
+ type ResolvedAgentConfig,
61
62
  contextEngineering,
62
63
  getTargetAgentId,
63
64
  initializeWithClientStore,
64
- resolveAgentConfig,
65
65
  resolveModelExtendParams,
66
66
  } from './mecha';
67
67
  import { type FetchOptions } from './types';
@@ -70,6 +70,11 @@ interface GetChatCompletionPayload extends Partial<Omit<ChatStreamPayload, 'mess
70
70
  agentId?: string;
71
71
  groupId?: string;
72
72
  messages: UIChatMessage[];
73
+ /**
74
+ * Pre-resolved agent config from AgentRuntime layer.
75
+ * Required to ensure config consistency and proper isSubTask filtering.
76
+ */
77
+ resolvedAgentConfig: ResolvedAgentConfig;
73
78
  scope?: MessageMapScope;
74
79
  topicId?: string;
75
80
  }
@@ -107,12 +112,11 @@ interface CreateAssistantMessageStream extends FetchSSEOptions {
107
112
  class ChatService {
108
113
  createAssistantMessage = async (
109
114
  {
110
- plugins: enabledPlugins,
111
115
  messages,
112
116
  agentId,
113
117
  groupId,
114
- scope,
115
118
  topicId,
119
+ resolvedAgentConfig,
116
120
  ...params
117
121
  }: GetChatCompletionPayload,
118
122
  options?: FetchOptions,
@@ -126,24 +130,13 @@ class ChatService {
126
130
  params,
127
131
  );
128
132
 
129
- // =================== 1. resolve agent config =================== //
133
+ // =================== 1. use pre-resolved agent config =================== //
134
+ // Config is resolved in AgentRuntime layer (internal_createAgentState)
135
+ // which handles isSubTask filtering and other runtime modifications
130
136
 
131
137
  const targetAgentId = getTargetAgentId(agentId);
132
138
 
133
- // Resolve agent config with builtin agent runtime config merged
134
- // plugins is already merged (runtime plugins > agent config plugins)
135
- const {
136
- agentConfig,
137
- chatConfig,
138
- plugins: pluginIds,
139
- } = resolveAgentConfig({
140
- agentId: targetAgentId,
141
- groupId, // Pass groupId for supervisor detection
142
- model: payload.model,
143
- plugins: enabledPlugins,
144
- provider: payload.provider,
145
- scope, // Pass scope to preserve page-agent injection
146
- });
139
+ const { agentConfig, chatConfig, plugins: pluginIds } = resolvedAgentConfig;
147
140
 
148
141
  // Get search config with agentId for agent-specific settings
149
142
  const searchConfig = getSearchConfig(payload.model, payload.provider!, targetAgentId);
@@ -495,26 +488,14 @@ class ChatService {
495
488
  onLoadingChange?.(true);
496
489
 
497
490
  try {
498
- // Use simple tools engine without complex search logic
499
- const toolsEngine = createToolsEngine();
500
- const { tools, enabledManifests } = toolsEngine.generateToolsDetailed({
501
- model: params.model!,
502
- provider: params.provider!,
503
- toolIds: params.plugins,
504
- });
505
-
506
491
  const llmMessages = await contextEngineering({
507
- manifests: enabledManifests,
508
492
  messages: params.messages as any,
509
493
  model: params.model!,
510
494
  provider: params.provider!,
511
- tools: params.plugins,
512
495
  });
513
496
 
514
- // remove plugins
515
- delete params.plugins;
516
497
  await this.getChatCompletion(
517
- { ...params, messages: llmMessages, tools },
498
+ { ...params, messages: llmMessages },
518
499
  {
519
500
  onErrorHandle: (error) => {
520
501
  errorHandle(new Error(error.message), error);
@@ -800,4 +800,117 @@ describe('resolveAgentConfig', () => {
800
800
  expect(result.agentConfig.systemRole).toBe('Supervisor system role');
801
801
  });
802
802
  });
803
+
804
+ describe('sub-task filtering (isSubTask)', () => {
805
+ beforeEach(() => {
806
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(() => undefined);
807
+ });
808
+
809
+ it('should filter out lobe-gtd when isSubTask is true for regular agent', () => {
810
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentConfigById').mockReturnValue(
811
+ () =>
812
+ ({
813
+ ...mockAgentConfig,
814
+ plugins: ['lobe-gtd', 'plugin-a', 'plugin-b'],
815
+ }) as any,
816
+ );
817
+
818
+ const result = resolveAgentConfig({
819
+ agentId: 'test-agent',
820
+ isSubTask: true,
821
+ });
822
+
823
+ expect(result.plugins).not.toContain('lobe-gtd');
824
+ expect(result.plugins).toEqual(['plugin-a', 'plugin-b']);
825
+ });
826
+
827
+ it('should keep lobe-gtd when isSubTask is false', () => {
828
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentConfigById').mockReturnValue(
829
+ () =>
830
+ ({
831
+ ...mockAgentConfig,
832
+ plugins: ['lobe-gtd', 'plugin-a', 'plugin-b'],
833
+ }) as any,
834
+ );
835
+
836
+ const result = resolveAgentConfig({
837
+ agentId: 'test-agent',
838
+ isSubTask: false,
839
+ });
840
+
841
+ expect(result.plugins).toContain('lobe-gtd');
842
+ expect(result.plugins).toEqual(['lobe-gtd', 'plugin-a', 'plugin-b']);
843
+ });
844
+
845
+ it('should keep lobe-gtd when isSubTask is undefined', () => {
846
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentConfigById').mockReturnValue(
847
+ () =>
848
+ ({
849
+ ...mockAgentConfig,
850
+ plugins: ['lobe-gtd', 'plugin-a'],
851
+ }) as any,
852
+ );
853
+
854
+ const result = resolveAgentConfig({ agentId: 'test-agent' });
855
+
856
+ expect(result.plugins).toContain('lobe-gtd');
857
+ });
858
+
859
+ it('should filter lobe-gtd in page scope when isSubTask is true', () => {
860
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentConfigById').mockReturnValue(
861
+ () =>
862
+ ({
863
+ ...mockAgentConfig,
864
+ plugins: ['lobe-gtd', 'plugin-a'],
865
+ }) as any,
866
+ );
867
+ vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
868
+ systemRole: 'Page agent system role',
869
+ });
870
+
871
+ const result = resolveAgentConfig({
872
+ agentId: 'test-agent',
873
+ scope: 'page',
874
+ isSubTask: true,
875
+ });
876
+
877
+ expect(result.plugins).not.toContain('lobe-gtd');
878
+ expect(result.plugins).toContain(PageAgentIdentifier);
879
+ });
880
+
881
+ it('should filter lobe-gtd for builtin agent when isSubTask is true', () => {
882
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
883
+ () => 'some-builtin-slug',
884
+ );
885
+ vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
886
+ plugins: ['lobe-gtd', 'runtime-plugin'],
887
+ systemRole: 'Runtime system role',
888
+ });
889
+
890
+ const result = resolveAgentConfig({
891
+ agentId: 'builtin-agent',
892
+ isSubTask: true,
893
+ });
894
+
895
+ expect(result.plugins).not.toContain('lobe-gtd');
896
+ expect(result.plugins).toContain('runtime-plugin');
897
+ });
898
+
899
+ it('should keep lobe-gtd for builtin agent when isSubTask is false', () => {
900
+ vi.spyOn(agentSelectors.agentSelectors, 'getAgentSlugById').mockReturnValue(
901
+ () => 'some-builtin-slug',
902
+ );
903
+ vi.spyOn(builtinAgents, 'getAgentRuntimeConfig').mockReturnValue({
904
+ plugins: ['lobe-gtd', 'runtime-plugin'],
905
+ systemRole: 'Runtime system role',
906
+ });
907
+
908
+ const result = resolveAgentConfig({
909
+ agentId: 'builtin-agent',
910
+ isSubTask: false,
911
+ });
912
+
913
+ expect(result.plugins).toContain('lobe-gtd');
914
+ });
915
+ });
803
916
  });
@@ -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
+ });