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

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 (200) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/changelog/v1.json +12 -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 +2 -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 +5 -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/builtin-tool-group-management/src/client/Inspector/ExecuteAgentTask/index.tsx +52 -8
  125. package/packages/builtin-tool-group-management/src/client/Render/ExecuteTask/index.tsx +2 -21
  126. package/packages/builtin-tool-group-management/src/executor.test.ts +6 -4
  127. package/packages/builtin-tool-group-management/src/manifest.ts +5 -1
  128. package/packages/builtin-tool-group-management/src/types.ts +2 -0
  129. package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +48 -5
  130. package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +39 -0
  131. package/packages/builtin-tool-local-system/src/client/Streaming/index.ts +2 -0
  132. package/packages/model-bank/src/aiModels/qiniu.ts +24 -0
  133. package/src/app/[variants]/(auth)/_layout/index.tsx +0 -2
  134. package/src/app/[variants]/(auth)/layout.tsx +0 -2
  135. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -3
  136. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -3
  137. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +0 -2
  138. package/src/app/[variants]/(main)/_layout/index.tsx +0 -2
  139. package/src/app/[variants]/(main)/agent/_layout/index.tsx +0 -2
  140. package/src/app/[variants]/(main)/agent/features/Portal/index.tsx +0 -2
  141. package/src/app/[variants]/(main)/community/(list)/_layout/index.tsx +0 -2
  142. package/src/app/[variants]/(main)/community/(list)/assistant/_layout/index.tsx +0 -2
  143. package/src/app/[variants]/(main)/community/(list)/mcp/_layout/index.tsx +0 -2
  144. package/src/app/[variants]/(main)/community/(list)/model/_layout/index.tsx +0 -2
  145. package/src/app/[variants]/(main)/community/_layout/index.tsx +0 -2
  146. package/src/app/[variants]/(main)/group/_layout/index.tsx +0 -2
  147. package/src/app/[variants]/(main)/group/features/Conversation/Header/index.tsx +4 -2
  148. package/src/app/[variants]/(main)/group/features/Portal/index.tsx +0 -2
  149. package/src/app/[variants]/(main)/home/_layout/index.tsx +0 -2
  150. package/src/app/[variants]/(main)/home/index.tsx +0 -2
  151. package/src/app/[variants]/(main)/image/_layout/Topics/TopicUrlSync.tsx +0 -2
  152. package/src/app/[variants]/(main)/image/_layout/index.tsx +0 -2
  153. package/src/app/[variants]/(main)/memory/_layout/index.tsx +0 -2
  154. package/src/app/[variants]/(main)/page/_layout/index.tsx +0 -2
  155. package/src/app/[variants]/(main)/resource/(home)/_layout/index.tsx +0 -2
  156. package/src/app/[variants]/(main)/resource/_layout/index.tsx +0 -2
  157. package/src/app/[variants]/(main)/resource/library/_layout/index.tsx +0 -2
  158. package/src/app/[variants]/(main)/resource/library/features/Container.tsx +0 -2
  159. package/src/app/[variants]/(main)/settings/_layout/index.tsx +0 -2
  160. package/src/app/[variants]/(main)/settings/about/index.tsx +0 -2
  161. package/src/app/[variants]/(main)/settings/agent/index.tsx +0 -2
  162. package/src/app/[variants]/(main)/settings/apikey/index.tsx +0 -2
  163. package/src/app/[variants]/(main)/settings/chat-appearance/index.tsx +0 -2
  164. package/src/app/[variants]/(main)/settings/common/index.tsx +0 -2
  165. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +0 -2
  166. package/src/app/[variants]/(main)/settings/image/index.tsx +0 -2
  167. package/src/app/[variants]/(main)/settings/memory/index.tsx +0 -2
  168. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +0 -2
  169. package/src/app/[variants]/(main)/settings/proxy/index.tsx +0 -2
  170. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -3
  171. package/src/app/[variants]/(main)/settings/storage/index.tsx +0 -2
  172. package/src/app/[variants]/(main)/settings/tts/index.tsx +0 -2
  173. package/src/app/[variants]/(mobile)/(home)/_layout/index.tsx +0 -2
  174. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -3
  175. package/src/app/[variants]/(mobile)/chat/_layout/index.tsx +0 -2
  176. package/src/app/[variants]/(mobile)/chat/settings/_layout/index.tsx +0 -2
  177. package/src/app/[variants]/(mobile)/community/(detail)/_layout/index.tsx +0 -2
  178. package/src/app/[variants]/(mobile)/community/(list)/_layout/index.tsx +0 -2
  179. package/src/app/[variants]/(mobile)/community/_layout/index.tsx +0 -2
  180. package/src/app/[variants]/(mobile)/router/MobileClientRouter.tsx +0 -2
  181. package/src/app/[variants]/(mobile)/settings/index.tsx +0 -2
  182. package/src/app/[variants]/onboarding/_layout/index.tsx +0 -2
  183. package/src/app/[variants]/router/DesktopClientRouter.tsx +0 -2
  184. package/src/components/ModelSelect/index.tsx +6 -56
  185. package/src/components/server/MobileNavLayout.tsx +0 -2
  186. package/src/components/server/ServerLayout.tsx +0 -2
  187. package/src/features/ModelSwitchPanel/components/Footer.tsx +0 -2
  188. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +0 -1
  189. package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +0 -1
  190. package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +0 -1
  191. package/src/features/ModelSwitchPanel/components/List/index.tsx +15 -13
  192. package/src/features/ModelSwitchPanel/components/PanelContent.tsx +0 -2
  193. package/src/features/ModelSwitchPanel/index.tsx +21 -23
  194. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +0 -2
  195. package/src/features/User/UserAvatar.tsx +0 -2
  196. package/src/locales/default/plugin.ts +2 -1
  197. package/src/store/chat/agents/GroupOrchestration/__tests__/call-supervisor.test.ts +305 -0
  198. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +2 -1
  199. package/src/store/chat/slices/plugin/actions/exector.ts +92 -0
  200. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +82 -177
@@ -197,6 +197,4 @@ const MasonryView = memo(function MasonryView() {
197
197
  );
198
198
  });
199
199
 
200
- MasonryView.displayName = 'MasonryView';
201
-
202
200
  export default MasonryView;
@@ -88,6 +88,4 @@ const UserAvatar = forwardRef<HTMLDivElement, UserAvatarProps>(
88
88
  },
89
89
  );
90
90
 
91
- UserAvatar.displayName = 'UserAvatar';
92
-
93
91
  export default UserAvatar;
@@ -70,7 +70,8 @@ export default {
70
70
  'builtins.lobe-group-management.apiName.summarize': 'Summarize conversation',
71
71
  'builtins.lobe-group-management.apiName.vote': 'Start vote',
72
72
  'builtins.lobe-group-management.inspector.broadcast.title': 'Following Agents speak:',
73
- 'builtins.lobe-group-management.inspector.executeAgentTask.title': 'Assigning task to:',
73
+ 'builtins.lobe-group-management.inspector.executeAgentTask.assignTo': 'Assign',
74
+ 'builtins.lobe-group-management.inspector.executeAgentTask.task': 'task:',
74
75
  'builtins.lobe-group-management.inspector.executeAgentTasks.title': 'Assigning tasks to:',
75
76
  'builtins.lobe-group-management.inspector.speak.title': 'Designated Agent speaks:',
76
77
  'builtins.lobe-group-management.title': 'Group Coordinator',
@@ -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,
@@ -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
+ };