@lobehub/lobehub 2.0.0-next.307 → 2.0.0-next.309

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 (221) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -0
  3. package/locales/ar/agentGroup.json +5 -0
  4. package/locales/ar/chat.json +26 -0
  5. package/locales/ar/models.json +43 -5
  6. package/locales/ar/plugin.json +4 -5
  7. package/locales/ar/setting.json +11 -0
  8. package/locales/ar/subscription.json +2 -0
  9. package/locales/ar/tool.json +2 -0
  10. package/locales/bg-BG/agentGroup.json +5 -0
  11. package/locales/bg-BG/chat.json +26 -0
  12. package/locales/bg-BG/models.json +49 -3
  13. package/locales/bg-BG/plugin.json +4 -5
  14. package/locales/bg-BG/setting.json +11 -0
  15. package/locales/bg-BG/subscription.json +2 -0
  16. package/locales/bg-BG/tool.json +2 -0
  17. package/locales/de-DE/agentGroup.json +5 -0
  18. package/locales/de-DE/chat.json +26 -0
  19. package/locales/de-DE/models.json +48 -5
  20. package/locales/de-DE/plugin.json +4 -5
  21. package/locales/de-DE/setting.json +11 -0
  22. package/locales/de-DE/subscription.json +2 -0
  23. package/locales/de-DE/tool.json +2 -0
  24. package/locales/en-US/models.json +8 -6
  25. package/locales/en-US/plugin.json +3 -4
  26. package/locales/en-US/setting.json +10 -11
  27. package/locales/en-US/tool.json +2 -0
  28. package/locales/es-ES/agentGroup.json +5 -0
  29. package/locales/es-ES/chat.json +26 -0
  30. package/locales/es-ES/models.json +43 -5
  31. package/locales/es-ES/plugin.json +4 -5
  32. package/locales/es-ES/setting.json +11 -0
  33. package/locales/es-ES/subscription.json +2 -0
  34. package/locales/es-ES/tool.json +2 -0
  35. package/locales/fa-IR/agentGroup.json +5 -0
  36. package/locales/fa-IR/chat.json +26 -0
  37. package/locales/fa-IR/models.json +42 -5
  38. package/locales/fa-IR/plugin.json +4 -5
  39. package/locales/fa-IR/setting.json +11 -0
  40. package/locales/fa-IR/subscription.json +2 -0
  41. package/locales/fa-IR/tool.json +2 -0
  42. package/locales/fr-FR/agentGroup.json +5 -0
  43. package/locales/fr-FR/chat.json +26 -0
  44. package/locales/fr-FR/models.json +5 -5
  45. package/locales/fr-FR/plugin.json +4 -5
  46. package/locales/fr-FR/setting.json +11 -0
  47. package/locales/fr-FR/subscription.json +2 -0
  48. package/locales/fr-FR/tool.json +2 -0
  49. package/locales/it-IT/agentGroup.json +5 -0
  50. package/locales/it-IT/chat.json +26 -0
  51. package/locales/it-IT/models.json +1 -3
  52. package/locales/it-IT/plugin.json +4 -5
  53. package/locales/it-IT/setting.json +11 -0
  54. package/locales/it-IT/subscription.json +2 -0
  55. package/locales/it-IT/tool.json +2 -0
  56. package/locales/ja-JP/agentGroup.json +5 -0
  57. package/locales/ja-JP/chat.json +26 -0
  58. package/locales/ja-JP/models.json +1 -5
  59. package/locales/ja-JP/plugin.json +4 -5
  60. package/locales/ja-JP/setting.json +11 -0
  61. package/locales/ja-JP/subscription.json +2 -0
  62. package/locales/ja-JP/tool.json +2 -0
  63. package/locales/ko-KR/agentGroup.json +5 -0
  64. package/locales/ko-KR/chat.json +26 -0
  65. package/locales/ko-KR/models.json +1 -3
  66. package/locales/ko-KR/plugin.json +4 -5
  67. package/locales/ko-KR/setting.json +11 -0
  68. package/locales/ko-KR/subscription.json +2 -0
  69. package/locales/ko-KR/tool.json +2 -0
  70. package/locales/nl-NL/agentGroup.json +5 -0
  71. package/locales/nl-NL/chat.json +26 -0
  72. package/locales/nl-NL/models.json +35 -3
  73. package/locales/nl-NL/plugin.json +4 -5
  74. package/locales/nl-NL/setting.json +11 -0
  75. package/locales/nl-NL/subscription.json +2 -0
  76. package/locales/nl-NL/tool.json +2 -0
  77. package/locales/pl-PL/agentGroup.json +5 -0
  78. package/locales/pl-PL/chat.json +26 -0
  79. package/locales/pl-PL/models.json +1 -3
  80. package/locales/pl-PL/plugin.json +4 -5
  81. package/locales/pl-PL/setting.json +11 -0
  82. package/locales/pl-PL/subscription.json +2 -0
  83. package/locales/pl-PL/tool.json +2 -0
  84. package/locales/pt-BR/agentGroup.json +5 -0
  85. package/locales/pt-BR/chat.json +26 -0
  86. package/locales/pt-BR/models.json +50 -5
  87. package/locales/pt-BR/plugin.json +4 -5
  88. package/locales/pt-BR/setting.json +11 -0
  89. package/locales/pt-BR/subscription.json +2 -0
  90. package/locales/pt-BR/tool.json +2 -0
  91. package/locales/ru-RU/agentGroup.json +5 -0
  92. package/locales/ru-RU/chat.json +26 -0
  93. package/locales/ru-RU/models.json +22 -3
  94. package/locales/ru-RU/plugin.json +4 -5
  95. package/locales/ru-RU/setting.json +11 -0
  96. package/locales/ru-RU/subscription.json +2 -0
  97. package/locales/ru-RU/tool.json +2 -0
  98. package/locales/tr-TR/agentGroup.json +5 -0
  99. package/locales/tr-TR/chat.json +26 -0
  100. package/locales/tr-TR/models.json +36 -3
  101. package/locales/tr-TR/plugin.json +4 -5
  102. package/locales/tr-TR/setting.json +11 -0
  103. package/locales/tr-TR/subscription.json +2 -0
  104. package/locales/tr-TR/tool.json +2 -0
  105. package/locales/vi-VN/agentGroup.json +5 -0
  106. package/locales/vi-VN/chat.json +26 -0
  107. package/locales/vi-VN/models.json +1 -1
  108. package/locales/vi-VN/plugin.json +4 -5
  109. package/locales/vi-VN/setting.json +11 -0
  110. package/locales/vi-VN/subscription.json +2 -0
  111. package/locales/vi-VN/tool.json +2 -0
  112. package/locales/zh-CN/models.json +52 -5
  113. package/locales/zh-CN/plugin.json +6 -7
  114. package/locales/zh-CN/setting.json +10 -11
  115. package/locales/zh-CN/tool.json +2 -2
  116. package/locales/zh-TW/agentGroup.json +5 -0
  117. package/locales/zh-TW/chat.json +26 -0
  118. package/locales/zh-TW/models.json +54 -5
  119. package/locales/zh-TW/plugin.json +4 -5
  120. package/locales/zh-TW/setting.json +11 -0
  121. package/locales/zh-TW/subscription.json +2 -0
  122. package/locales/zh-TW/tool.json +2 -0
  123. package/package.json +1 -1
  124. package/packages/agent-runtime/src/types/state.ts +2 -0
  125. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/GetAgentInfo/index.tsx +68 -0
  126. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/index.ts +3 -0
  127. package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteAgentTask/index.tsx +52 -8
  128. package/packages/builtin-tool-group-management/src/client/Render/ExecuteTask/index.tsx +2 -21
  129. package/packages/builtin-tool-group-management/src/executor.test.ts +6 -4
  130. package/packages/builtin-tool-group-management/src/manifest.ts +5 -1
  131. package/packages/builtin-tool-group-management/src/types.ts +2 -0
  132. package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +48 -5
  133. package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +39 -0
  134. package/packages/builtin-tool-local-system/src/client/Streaming/index.ts +2 -0
  135. package/packages/builtin-tool-memory/src/manifest.ts +581 -19
  136. package/packages/model-bank/src/aiModels/qiniu.ts +24 -0
  137. package/packages/types/src/topic/thread.ts +2 -2
  138. package/packages/types/src/userMemory/layers.ts +19 -8
  139. package/packages/types/src/userMemory/shared.ts +7 -1
  140. package/src/app/[variants]/(auth)/_layout/index.tsx +0 -2
  141. package/src/app/[variants]/(auth)/layout.tsx +0 -2
  142. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -3
  143. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -3
  144. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +0 -2
  145. package/src/app/[variants]/(main)/_layout/index.tsx +0 -2
  146. package/src/app/[variants]/(main)/agent/_layout/index.tsx +0 -2
  147. package/src/app/[variants]/(main)/agent/features/Portal/index.tsx +0 -2
  148. package/src/app/[variants]/(main)/community/(list)/_layout/index.tsx +0 -2
  149. package/src/app/[variants]/(main)/community/(list)/assistant/_layout/index.tsx +0 -2
  150. package/src/app/[variants]/(main)/community/(list)/mcp/_layout/index.tsx +0 -2
  151. package/src/app/[variants]/(main)/community/(list)/model/_layout/index.tsx +0 -2
  152. package/src/app/[variants]/(main)/community/_layout/index.tsx +0 -2
  153. package/src/app/[variants]/(main)/group/_layout/index.tsx +0 -2
  154. package/src/app/[variants]/(main)/group/features/Conversation/Header/index.tsx +4 -2
  155. package/src/app/[variants]/(main)/group/features/Portal/index.tsx +0 -2
  156. package/src/app/[variants]/(main)/home/_layout/index.tsx +0 -2
  157. package/src/app/[variants]/(main)/home/index.tsx +0 -2
  158. package/src/app/[variants]/(main)/image/_layout/Topics/TopicUrlSync.tsx +0 -2
  159. package/src/app/[variants]/(main)/image/_layout/index.tsx +0 -2
  160. package/src/app/[variants]/(main)/memory/_layout/index.tsx +0 -2
  161. package/src/app/[variants]/(main)/page/_layout/index.tsx +0 -2
  162. package/src/app/[variants]/(main)/resource/(home)/_layout/index.tsx +0 -2
  163. package/src/app/[variants]/(main)/resource/_layout/index.tsx +0 -2
  164. package/src/app/[variants]/(main)/resource/library/_layout/index.tsx +0 -2
  165. package/src/app/[variants]/(main)/resource/library/features/Container.tsx +0 -2
  166. package/src/app/[variants]/(main)/settings/_layout/index.tsx +0 -2
  167. package/src/app/[variants]/(main)/settings/about/index.tsx +0 -2
  168. package/src/app/[variants]/(main)/settings/agent/index.tsx +0 -2
  169. package/src/app/[variants]/(main)/settings/apikey/index.tsx +0 -2
  170. package/src/app/[variants]/(main)/settings/chat-appearance/index.tsx +0 -2
  171. package/src/app/[variants]/(main)/settings/common/index.tsx +0 -2
  172. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +0 -2
  173. package/src/app/[variants]/(main)/settings/image/index.tsx +0 -2
  174. package/src/app/[variants]/(main)/settings/memory/index.tsx +0 -2
  175. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +0 -2
  176. package/src/app/[variants]/(main)/settings/proxy/index.tsx +0 -2
  177. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -3
  178. package/src/app/[variants]/(main)/settings/storage/index.tsx +0 -2
  179. package/src/app/[variants]/(main)/settings/tts/index.tsx +0 -2
  180. package/src/app/[variants]/(mobile)/(home)/_layout/index.tsx +0 -2
  181. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -3
  182. package/src/app/[variants]/(mobile)/chat/_layout/index.tsx +0 -2
  183. package/src/app/[variants]/(mobile)/chat/settings/_layout/index.tsx +0 -2
  184. package/src/app/[variants]/(mobile)/community/(detail)/_layout/index.tsx +0 -2
  185. package/src/app/[variants]/(mobile)/community/(list)/_layout/index.tsx +0 -2
  186. package/src/app/[variants]/(mobile)/community/_layout/index.tsx +0 -2
  187. package/src/app/[variants]/(mobile)/router/MobileClientRouter.tsx +0 -2
  188. package/src/app/[variants]/(mobile)/settings/index.tsx +0 -2
  189. package/src/app/[variants]/onboarding/_layout/index.tsx +0 -2
  190. package/src/app/[variants]/router/DesktopClientRouter.tsx +0 -2
  191. package/src/components/ModelSelect/index.tsx +6 -56
  192. package/src/components/server/MobileNavLayout.tsx +0 -2
  193. package/src/components/server/ServerLayout.tsx +0 -2
  194. package/src/features/ModelSwitchPanel/components/Footer.tsx +0 -2
  195. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +0 -1
  196. package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +0 -1
  197. package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +0 -1
  198. package/src/features/ModelSwitchPanel/components/List/index.tsx +15 -13
  199. package/src/features/ModelSwitchPanel/components/PanelContent.tsx +0 -2
  200. package/src/features/ModelSwitchPanel/index.tsx +21 -23
  201. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +0 -2
  202. package/src/features/User/UserAvatar.tsx +0 -2
  203. package/src/locales/default/plugin.ts +3 -1
  204. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +11 -3
  205. package/src/server/modules/Mecha/AgentToolsEngine/index.ts +14 -6
  206. package/src/server/modules/Mecha/AgentToolsEngine/types.ts +4 -3
  207. package/src/server/routers/lambda/aiAgent.ts +10 -0
  208. package/src/server/services/agent/index.test.ts +155 -0
  209. package/src/server/services/agent/index.ts +25 -0
  210. package/src/server/services/agentRuntime/AgentRuntimeService.ts +2 -0
  211. package/src/server/services/agentRuntime/types.ts +1 -0
  212. package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +29 -9
  213. package/src/server/services/aiAgent/index.ts +175 -6
  214. package/src/server/services/lobehubSkill/index.ts +109 -0
  215. package/src/server/services/toolExecution/builtin.ts +28 -2
  216. package/src/server/services/toolExecution/types.ts +3 -0
  217. package/src/store/chat/agents/GroupOrchestration/__tests__/call-supervisor.test.ts +305 -0
  218. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +2 -1
  219. package/src/store/chat/agents/createAgentExecutors.ts +4 -2
  220. package/src/store/chat/slices/plugin/actions/exector.ts +92 -0
  221. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +82 -177
@@ -4,6 +4,7 @@ import { type ChatToolPayload } from '@lobechat/types';
4
4
  import { safeParseJSON } from '@lobechat/utils';
5
5
  import debug from 'debug';
6
6
 
7
+ import { LobehubSkillService } from '@/server/services/lobehubSkill';
7
8
  import { SearchService } from '@/server/services/search';
8
9
 
9
10
  import { type IToolExecutor, type ToolExecutionContext, type ToolExecutionResult } from './types';
@@ -19,11 +20,36 @@ export class BuiltinToolsExecutor implements IToolExecutor {
19
20
  payload: ChatToolPayload,
20
21
  context: ToolExecutionContext,
21
22
  ): Promise<ToolExecutionResult> {
22
- const { identifier, apiName, arguments: argsStr } = payload;
23
+ const { identifier, apiName, arguments: argsStr, source } = payload;
23
24
  const args = safeParseJSON(argsStr) || {};
24
25
 
25
- log('Executing builtin tool: %s:%s with args: %O', identifier, apiName, args, context);
26
+ log(
27
+ 'Executing builtin tool: %s:%s (source: %s) with args: %O',
28
+ identifier,
29
+ apiName,
30
+ source,
31
+ args,
32
+ );
26
33
 
34
+ // Route LobeHub Skills to dedicated service
35
+ if (source === 'lobehubSkill') {
36
+ if (!context.serverDB || !context.userId) {
37
+ return {
38
+ content: 'Server context not available for LobeHub Skills execution.',
39
+ error: { code: 'CONTEXT_NOT_AVAILABLE' },
40
+ success: false,
41
+ };
42
+ }
43
+
44
+ const skillService = new LobehubSkillService(context.serverDB, context.userId);
45
+ return skillService.execute({
46
+ args,
47
+ provider: identifier,
48
+ toolName: apiName,
49
+ });
50
+ }
51
+
52
+ // Default: original builtin runtime logic
27
53
  const ServerRuntime = BuiltinToolServerRuntimes[identifier];
28
54
 
29
55
  if (!ServerRuntime) {
@@ -1,7 +1,10 @@
1
1
  import { type LobeToolManifest } from '@lobechat/context-engine';
2
+ import { type LobeChatDatabase } from '@lobechat/database';
2
3
  import { type ChatToolPayload } from '@lobechat/types';
3
4
 
4
5
  export interface ToolExecutionContext {
6
+ /** Server database for LobeHub Skills execution */
7
+ serverDB?: LobeChatDatabase;
5
8
  toolManifestMap: Record<string, LobeToolManifest>;
6
9
  userId?: string;
7
10
  }
@@ -0,0 +1,305 @@
1
+ import type { AgentState } from '@lobechat/agent-runtime';
2
+ import type { UIChatMessage } from '@lobechat/types';
3
+ import { nanoid } from '@lobechat/utils';
4
+ import { describe, expect, it, vi } from 'vitest';
5
+
6
+ import type { ChatStore } from '@/store/chat/store';
7
+
8
+ import { createGroupOrchestrationExecutors } from '../createGroupOrchestrationExecutors';
9
+
10
+ const TEST_IDS = {
11
+ GROUP_ID: 'test-group-id',
12
+ OPERATION_ID: 'test-operation-id',
13
+ ORCHESTRATION_OPERATION_ID: 'test-orchestration-operation-id',
14
+ SUPERVISOR_AGENT_ID: 'test-supervisor-agent-id',
15
+ TOPIC_ID: 'test-topic-id',
16
+ USER_MESSAGE_ID: 'test-user-message-id',
17
+ };
18
+
19
+ /**
20
+ * Create a minimal mock store for group orchestration executor tests
21
+ */
22
+ const createMockStore = (overrides: Partial<ChatStore> = {}): ChatStore => {
23
+ const operations: Record<string, any> = {};
24
+
25
+ return {
26
+ dbMessagesMap: {},
27
+ internal_execAgentRuntime: vi.fn().mockResolvedValue(undefined),
28
+ messagesMap: {},
29
+ operations,
30
+ startOperation: vi.fn().mockImplementation((config) => {
31
+ const operationId = `op_${nanoid()}`;
32
+ const abortController = new AbortController();
33
+ operations[operationId] = {
34
+ abortController,
35
+ context: config.context || {},
36
+ id: operationId,
37
+ status: 'running',
38
+ type: config.type,
39
+ };
40
+ return { abortController, operationId };
41
+ }),
42
+ ...overrides,
43
+ } as unknown as ChatStore;
44
+ };
45
+
46
+ /**
47
+ * Create initial agent state for testing
48
+ */
49
+ const createInitialState = (overrides: Partial<AgentState> = {}): AgentState => {
50
+ return {
51
+ cost: {
52
+ calculatedAt: new Date().toISOString(),
53
+ currency: 'USD',
54
+ llm: { byModel: [], currency: 'USD', total: 0 },
55
+ tools: { byTool: [], currency: 'USD', total: 0 },
56
+ total: 0,
57
+ },
58
+ createdAt: new Date().toISOString(),
59
+ lastModified: new Date().toISOString(),
60
+ maxSteps: 10,
61
+ messages: [],
62
+ operationId: TEST_IDS.OPERATION_ID,
63
+ status: 'running',
64
+ stepCount: 0,
65
+ toolManifestMap: {},
66
+ usage: {
67
+ humanInteraction: {
68
+ approvalRequests: 0,
69
+ promptRequests: 0,
70
+ selectRequests: 0,
71
+ totalWaitingTimeMs: 0,
72
+ },
73
+ llm: { apiCalls: 0, processingTimeMs: 0, tokens: { input: 0, output: 0, total: 0 } },
74
+ tools: { byTool: [], totalCalls: 0, totalTimeMs: 0 },
75
+ },
76
+ userInterventionConfig: { allowList: [], approvalMode: 'auto' },
77
+ ...overrides,
78
+ } as AgentState;
79
+ };
80
+
81
+ describe('createGroupOrchestrationExecutors', () => {
82
+ describe('call_supervisor executor', () => {
83
+ it('should NOT pass operationId to internal_execAgentRuntime (creates new child operation)', async () => {
84
+ const mockStore = createMockStore({
85
+ dbMessagesMap: {
86
+ [`group_${TEST_IDS.GROUP_ID}_${TEST_IDS.TOPIC_ID}`]: [
87
+ {
88
+ content: 'Hello',
89
+ createdAt: Date.now(),
90
+ id: TEST_IDS.USER_MESSAGE_ID,
91
+ role: 'user',
92
+ updatedAt: Date.now(),
93
+ } as UIChatMessage,
94
+ ],
95
+ },
96
+ });
97
+
98
+ const executors = createGroupOrchestrationExecutors({
99
+ get: () => mockStore,
100
+ messageContext: {
101
+ agentId: TEST_IDS.GROUP_ID,
102
+ scope: 'group',
103
+ topicId: TEST_IDS.TOPIC_ID,
104
+ },
105
+ orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
106
+ supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
107
+ });
108
+
109
+ const callSupervisorExecutor = executors.call_supervisor!;
110
+
111
+ await callSupervisorExecutor(
112
+ {
113
+ payload: { round: 1, supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID },
114
+ type: 'call_supervisor',
115
+ },
116
+ createInitialState(),
117
+ );
118
+
119
+ // Verify internal_execAgentRuntime was called
120
+ expect(mockStore.internal_execAgentRuntime).toHaveBeenCalledTimes(1);
121
+
122
+ // Verify operationId is NOT passed (should be undefined)
123
+ // This ensures a new child operation is created
124
+ const callArgs = (mockStore.internal_execAgentRuntime as any).mock.calls[0][0];
125
+ expect(callArgs.operationId).toBeUndefined();
126
+
127
+ // Verify parentOperationId is passed correctly
128
+ expect(callArgs.parentOperationId).toBe(TEST_IDS.ORCHESTRATION_OPERATION_ID);
129
+
130
+ // Verify isSupervisor is passed in context
131
+ expect(callArgs.context.isSupervisor).toBe(true);
132
+
133
+ // Verify agentId is the supervisor agent id
134
+ expect(callArgs.context.agentId).toBe(TEST_IDS.SUPERVISOR_AGENT_ID);
135
+ });
136
+
137
+ it('should pass isSupervisor: true in context for supervisor messages metadata', async () => {
138
+ const mockStore = createMockStore({
139
+ dbMessagesMap: {
140
+ [`group_${TEST_IDS.GROUP_ID}_${TEST_IDS.TOPIC_ID}`]: [
141
+ {
142
+ content: 'Hello',
143
+ createdAt: Date.now(),
144
+ id: TEST_IDS.USER_MESSAGE_ID,
145
+ role: 'user',
146
+ updatedAt: Date.now(),
147
+ } as UIChatMessage,
148
+ ],
149
+ },
150
+ });
151
+
152
+ const executors = createGroupOrchestrationExecutors({
153
+ get: () => mockStore,
154
+ messageContext: {
155
+ agentId: TEST_IDS.GROUP_ID,
156
+ scope: 'group',
157
+ topicId: TEST_IDS.TOPIC_ID,
158
+ },
159
+ orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
160
+ supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
161
+ });
162
+
163
+ const callSupervisorExecutor = executors.call_supervisor!;
164
+
165
+ await callSupervisorExecutor(
166
+ {
167
+ payload: { round: 1, supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID },
168
+ type: 'call_supervisor',
169
+ },
170
+ createInitialState(),
171
+ );
172
+
173
+ const callArgs = (mockStore.internal_execAgentRuntime as any).mock.calls[0][0];
174
+
175
+ // The key assertion: isSupervisor must be true
176
+ // This is used by createAgentExecutors to set metadata.isSupervisor on assistant messages
177
+ expect(callArgs.context).toMatchObject({
178
+ agentId: TEST_IDS.SUPERVISOR_AGENT_ID,
179
+ isSupervisor: true,
180
+ scope: 'group',
181
+ topicId: TEST_IDS.TOPIC_ID,
182
+ });
183
+ });
184
+ });
185
+
186
+ describe('call_agent executor', () => {
187
+ it('should NOT pass operationId to internal_execAgentRuntime (creates new child operation)', async () => {
188
+ const mockStore = createMockStore({
189
+ dbMessagesMap: {
190
+ [`group_${TEST_IDS.GROUP_ID}_${TEST_IDS.TOPIC_ID}`]: [
191
+ {
192
+ content: 'Hello',
193
+ createdAt: Date.now(),
194
+ id: TEST_IDS.USER_MESSAGE_ID,
195
+ role: 'user',
196
+ updatedAt: Date.now(),
197
+ } as UIChatMessage,
198
+ ],
199
+ },
200
+ });
201
+
202
+ const executors = createGroupOrchestrationExecutors({
203
+ get: () => mockStore,
204
+ messageContext: {
205
+ agentId: TEST_IDS.GROUP_ID,
206
+ scope: 'group',
207
+ topicId: TEST_IDS.TOPIC_ID,
208
+ },
209
+ orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
210
+ supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
211
+ });
212
+
213
+ const callAgentExecutor = executors.call_agent!;
214
+ const targetAgentId = 'target-agent-id';
215
+
216
+ await callAgentExecutor(
217
+ {
218
+ payload: { agentId: targetAgentId, instruction: 'Please respond' },
219
+ type: 'call_agent',
220
+ },
221
+ createInitialState(),
222
+ );
223
+
224
+ // Verify internal_execAgentRuntime was called
225
+ expect(mockStore.internal_execAgentRuntime).toHaveBeenCalledTimes(1);
226
+
227
+ // Verify operationId is NOT passed (should be undefined)
228
+ const callArgs = (mockStore.internal_execAgentRuntime as any).mock.calls[0][0];
229
+ expect(callArgs.operationId).toBeUndefined();
230
+
231
+ // Verify parentOperationId is passed correctly
232
+ expect(callArgs.parentOperationId).toBe(TEST_IDS.ORCHESTRATION_OPERATION_ID);
233
+
234
+ // Verify subAgentId is passed (NOT isSupervisor)
235
+ expect(callArgs.context.subAgentId).toBe(targetAgentId);
236
+ expect(callArgs.context.isSupervisor).toBeUndefined();
237
+ });
238
+ });
239
+
240
+ describe('operation structure comparison', () => {
241
+ it('call_supervisor and call_agent should both create independent child operations', async () => {
242
+ const mockStore = createMockStore({
243
+ dbMessagesMap: {
244
+ [`group_${TEST_IDS.GROUP_ID}_${TEST_IDS.TOPIC_ID}`]: [
245
+ {
246
+ content: 'Hello',
247
+ createdAt: Date.now(),
248
+ id: TEST_IDS.USER_MESSAGE_ID,
249
+ role: 'user',
250
+ updatedAt: Date.now(),
251
+ } as UIChatMessage,
252
+ ],
253
+ },
254
+ });
255
+
256
+ const executors = createGroupOrchestrationExecutors({
257
+ get: () => mockStore,
258
+ messageContext: {
259
+ agentId: TEST_IDS.GROUP_ID,
260
+ scope: 'group',
261
+ topicId: TEST_IDS.TOPIC_ID,
262
+ },
263
+ orchestrationOperationId: TEST_IDS.ORCHESTRATION_OPERATION_ID,
264
+ supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID,
265
+ });
266
+
267
+ // Execute call_supervisor
268
+ await executors.call_supervisor!(
269
+ {
270
+ payload: { round: 1, supervisorAgentId: TEST_IDS.SUPERVISOR_AGENT_ID },
271
+ type: 'call_supervisor',
272
+ },
273
+ createInitialState(),
274
+ );
275
+
276
+ // Execute call_agent
277
+ await executors.call_agent!(
278
+ {
279
+ payload: { agentId: 'agent-1', instruction: 'test' },
280
+ type: 'call_agent',
281
+ },
282
+ createInitialState(),
283
+ );
284
+
285
+ // Verify both were called
286
+ expect(mockStore.internal_execAgentRuntime).toHaveBeenCalledTimes(2);
287
+
288
+ // Get both call arguments
289
+ const supervisorCallArgs = (mockStore.internal_execAgentRuntime as any).mock.calls[0][0];
290
+ const agentCallArgs = (mockStore.internal_execAgentRuntime as any).mock.calls[1][0];
291
+
292
+ // Both should NOT have operationId (create new child operations)
293
+ expect(supervisorCallArgs.operationId).toBeUndefined();
294
+ expect(agentCallArgs.operationId).toBeUndefined();
295
+
296
+ // Both should have same parentOperationId (orchestration operation)
297
+ expect(supervisorCallArgs.parentOperationId).toBe(TEST_IDS.ORCHESTRATION_OPERATION_ID);
298
+ expect(agentCallArgs.parentOperationId).toBe(TEST_IDS.ORCHESTRATION_OPERATION_ID);
299
+
300
+ // Supervisor should have isSupervisor: true
301
+ expect(supervisorCallArgs.context.isSupervisor).toBe(true);
302
+ expect(agentCallArgs.context.isSupervisor).toBeUndefined();
303
+ });
304
+ });
305
+ });
@@ -126,10 +126,11 @@ export const createGroupOrchestrationExecutors = (
126
126
 
127
127
  // Execute Supervisor agent with the supervisor's agentId in context
128
128
  // Mark isSupervisor=true so assistant messages get metadata.isSupervisor for UI rendering
129
+ // Note: Don't pass operationId - let it create a new child operation (same as call_agent)
130
+ // This ensures each call has its own immutable context with isSupervisor properly set
129
131
  await get().internal_execAgentRuntime({
130
132
  context: { ...messageContext, agentId: supervisorAgentId, isSupervisor: true },
131
133
  messages,
132
- operationId: state.operationId,
133
134
  parentMessageId: lastMessage.id,
134
135
  parentMessageType: lastMessage.role as 'user' | 'assistant' | 'tool',
135
136
  parentOperationId: orchestrationOperationId,
@@ -1087,12 +1087,14 @@ export const createAgentExecutors = (context: {
1087
1087
  }
1088
1088
 
1089
1089
  if (status.status === 'failed') {
1090
- log('[%s] Task failed: %s', taskLogId, status.error);
1090
+ // Extract error message (error is always a string in TaskStatusResult)
1091
+ const errorMessage = status.error || 'Unknown error';
1092
+ log('[%s] Task failed: %s', taskLogId, errorMessage);
1091
1093
  await context
1092
1094
  .get()
1093
1095
  .optimisticUpdateMessageContent(
1094
1096
  taskMessageId,
1095
- `Task failed: ${status.error}`,
1097
+ `Task failed: ${errorMessage}`,
1096
1098
  undefined,
1097
1099
  { operationId: state.operationId },
1098
1100
  );
@@ -0,0 +1,92 @@
1
+ import { type MCPToolCallResult } from '@/libs/mcp';
2
+ import { useToolStore } from '@/store/tool';
3
+ import { type ChatToolPayload } from '@/types/message';
4
+ import { safeParseJSON } from '@/utils/safeParseJSON';
5
+
6
+ /**
7
+ * Executor function type for remote tool invocation
8
+ * @param payload - Tool call payload
9
+ * @returns Promise with MCPToolCallResult data
10
+ */
11
+ export type RemoteToolExecutor = (payload: ChatToolPayload) => Promise<MCPToolCallResult>;
12
+
13
+ /**
14
+ * Create a failed MCPToolCallResult
15
+ */
16
+ const createFailedResult = (
17
+ errorMessage: string,
18
+ ): { content: string; error: any; state: any; success: false } => ({
19
+ content: errorMessage,
20
+ error: { message: errorMessage },
21
+ state: {},
22
+ success: false,
23
+ });
24
+
25
+ export const klavisExecutor: RemoteToolExecutor = async (p) => {
26
+ // payload.identifier 现在是存储用的 identifier(如 'google-calendar')
27
+ const identifier = p.identifier;
28
+ const klavisServers = useToolStore.getState().servers || [];
29
+ const server = klavisServers.find((s) => s.identifier === identifier);
30
+
31
+ if (!server) {
32
+ return createFailedResult(`Klavis server not found: ${identifier}`);
33
+ }
34
+
35
+ // Parse arguments
36
+ const args = safeParseJSON(p.arguments) || {};
37
+
38
+ // Call Klavis tool via store action
39
+ const result = await useToolStore.getState().callKlavisTool({
40
+ serverUrl: server.serverUrl,
41
+ toolArgs: args,
42
+ toolName: p.apiName,
43
+ });
44
+
45
+ if (!result.success) {
46
+ return createFailedResult(result.error || 'Klavis tool execution failed');
47
+ }
48
+
49
+ // result.data is MCPToolCallProcessedResult from server
50
+ // Convert to MCPToolCallResult format
51
+ const toolResult = result.data;
52
+ if (toolResult) {
53
+ return {
54
+ content: toolResult.content,
55
+ error: toolResult.state?.isError ? toolResult.state : undefined,
56
+ state: toolResult.state,
57
+ success: toolResult.success,
58
+ };
59
+ }
60
+
61
+ return createFailedResult('Klavis tool returned empty result');
62
+ };
63
+
64
+ export const lobehubSkillExecutor: RemoteToolExecutor = async (p) => {
65
+ // payload.identifier is the provider id (e.g., 'linear', 'microsoft')
66
+ const provider = p.identifier;
67
+
68
+ // Parse arguments
69
+ const args = safeParseJSON(p.arguments) || {};
70
+
71
+ // Call LobeHub Skill tool via store action
72
+ const result = await useToolStore.getState().callLobehubSkillTool({
73
+ args,
74
+ provider,
75
+ toolName: p.apiName,
76
+ });
77
+
78
+ if (!result.success) {
79
+ return createFailedResult(
80
+ result.error || `LobeHub Skill tool ${provider} ${p.apiName} execution failed`,
81
+ );
82
+ }
83
+
84
+ // Convert to MCPToolCallResult format
85
+ const content = typeof result.data === 'string' ? result.data : JSON.stringify(result.data);
86
+ return {
87
+ content,
88
+ error: undefined,
89
+ state: { content: [{ text: content, type: 'text' }] },
90
+ success: true,
91
+ };
92
+ };