@lobehub/lobehub 2.0.0-next.85 → 2.0.0-next.86

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 (88) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/src/main/modules/networkProxy/dispatcher.ts +16 -16
  3. package/apps/desktop/src/main/modules/networkProxy/tester.ts +11 -11
  4. package/apps/desktop/src/main/modules/networkProxy/urlBuilder.ts +3 -3
  5. package/apps/desktop/src/main/modules/networkProxy/validator.ts +10 -10
  6. package/changelog/v1.json +9 -0
  7. package/package.json +1 -1
  8. package/packages/agent-runtime/src/core/runtime.ts +36 -1
  9. package/packages/agent-runtime/src/types/event.ts +1 -0
  10. package/packages/agent-runtime/src/types/generalAgent.ts +16 -0
  11. package/packages/agent-runtime/src/types/instruction.ts +30 -0
  12. package/packages/agent-runtime/src/types/runtime.ts +7 -0
  13. package/packages/types/src/message/common/metadata.ts +3 -0
  14. package/packages/types/src/message/common/tools.ts +2 -2
  15. package/packages/types/src/tool/search/index.ts +8 -2
  16. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/index.tsx +2 -2
  17. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/V1Mobile/useSend.ts +7 -2
  18. package/src/app/[variants]/(main)/chat/components/conversation/features/ChatInput/useSend.ts +15 -14
  19. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/index.tsx +2 -2
  20. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  21. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  22. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
  23. package/src/features/Conversation/Messages/User/index.tsx +3 -3
  24. package/src/features/Conversation/Messages/index.tsx +3 -3
  25. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  26. package/src/services/search.ts +2 -2
  27. package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
  28. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
  29. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
  30. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
  31. package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
  32. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
  33. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
  34. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
  35. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
  36. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
  37. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
  38. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
  39. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
  40. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
  41. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
  42. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
  43. package/src/store/chat/agents/createAgentExecutors.ts +313 -80
  44. package/src/store/chat/selectors.ts +1 -0
  45. package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
  46. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
  47. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
  48. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
  49. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
  50. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
  51. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
  52. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
  53. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
  54. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
  55. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
  56. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
  57. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
  58. package/src/store/chat/slices/aiChat/initialState.ts +0 -28
  59. package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
  60. package/src/store/chat/slices/aiChat/selectors.ts +31 -7
  61. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
  62. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
  63. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
  64. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
  65. package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
  66. package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
  67. package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
  68. package/src/store/chat/slices/message/action.test.ts +134 -16
  69. package/src/store/chat/slices/message/actions/internals.ts +33 -7
  70. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
  71. package/src/store/chat/slices/message/initialState.ts +0 -10
  72. package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
  73. package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
  74. package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
  75. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
  76. package/src/store/chat/slices/operation/actions.ts +218 -11
  77. package/src/store/chat/slices/operation/selectors.ts +135 -6
  78. package/src/store/chat/slices/operation/types.ts +29 -3
  79. package/src/store/chat/slices/plugin/action.test.ts +30 -322
  80. package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
  81. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
  82. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
  83. package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
  84. package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
  85. package/src/store/chat/slices/thread/selectors/index.ts +4 -2
  86. package/src/store/chat/slices/translate/action.ts +54 -41
  87. package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
  88. package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
@@ -6,6 +6,7 @@ import {
6
6
  CreateMessageParams,
7
7
  GroundingSearch,
8
8
  MessageMetadata,
9
+ MessagePluginItem,
9
10
  MessageToolCall,
10
11
  ModelReasoning,
11
12
  UIChatMessage,
@@ -21,8 +22,7 @@ import { ChatStore } from '@/store/chat/store';
21
22
  * Context for optimistic updates to specify session/topic isolation
22
23
  */
23
24
  export interface OptimisticUpdateContext {
24
- sessionId?: string;
25
- topicId?: string | null;
25
+ operationId?: string;
26
26
  }
27
27
 
28
28
  /**
@@ -38,10 +38,8 @@ export interface MessageOptimisticUpdateAction {
38
38
  params: CreateMessageParams,
39
39
  context?: {
40
40
  groupMessageId?: string;
41
- sessionId?: string;
42
- skipRefresh?: boolean;
41
+ operationId?: string;
43
42
  tempMessageId?: string;
44
- topicId?: string | null;
45
43
  },
46
44
  ) => Promise<{ id: string; messages: UIChatMessage[] } | undefined>;
47
45
 
@@ -94,6 +92,16 @@ export interface MessageOptimisticUpdateAction {
94
92
  context?: OptimisticUpdateContext,
95
93
  ) => Promise<void>;
96
94
 
95
+ /**
96
+ * Optimistic update for message pluginIntervention field (frontend only, no database persistence)
97
+ * Use this when you need to update plugin intervention status
98
+ */
99
+ optimisticUpdateMessagePlugin: (
100
+ id: string,
101
+ value: Partial<MessagePluginItem>,
102
+ context?: OptimisticUpdateContext,
103
+ ) => Promise<void>;
104
+
97
105
  /**
98
106
  * update the message plugin error with optimistic update
99
107
  */
@@ -136,28 +144,28 @@ export const messageOptimisticUpdate: StateCreator<
136
144
  try {
137
145
  const result = await messageService.createMessage(message);
138
146
 
139
- if (!context?.skipRefresh) {
140
- // Use the messages returned from createMessage (already grouped)
141
- const sessionId = context?.sessionId ?? get().activeId;
142
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
143
- replaceMessages(result.messages, { sessionId, topicId });
144
- }
147
+ // Use the messages returned from createMessage (already grouped)
148
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
149
+ replaceMessages(result.messages, { sessionId, topicId });
145
150
 
146
151
  internal_toggleMessageLoading(false, tempId);
147
152
  return result;
148
153
  } catch (e) {
149
154
  internal_toggleMessageLoading(false, tempId);
150
- internal_dispatchMessage({
151
- id: tempId,
152
- type: 'updateMessage',
153
- value: {
154
- error: {
155
- body: e,
156
- message: (e as Error).message,
157
- type: ChatErrorType.CreateMessageError,
155
+ internal_dispatchMessage(
156
+ {
157
+ id: tempId,
158
+ type: 'updateMessage',
159
+ value: {
160
+ error: {
161
+ body: e,
162
+ message: (e as Error).message,
163
+ type: ChatErrorType.CreateMessageError,
164
+ },
158
165
  },
159
166
  },
160
- });
167
+ context,
168
+ );
161
169
  }
162
170
  },
163
171
 
@@ -172,9 +180,8 @@ export const messageOptimisticUpdate: StateCreator<
172
180
  },
173
181
 
174
182
  optimisticDeleteMessage: async (id: string, context) => {
175
- get().internal_dispatchMessage({ id, type: 'deleteMessage' });
176
- const sessionId = context?.sessionId ?? get().activeId;
177
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
183
+ get().internal_dispatchMessage({ id, type: 'deleteMessage' }, context);
184
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
178
185
  const result = await messageService.removeMessage(id, {
179
186
  sessionId,
180
187
  topicId,
@@ -185,9 +192,8 @@ export const messageOptimisticUpdate: StateCreator<
185
192
  },
186
193
 
187
194
  optimisticDeleteMessages: async (ids, context) => {
188
- get().internal_dispatchMessage({ ids, type: 'deleteMessages' });
189
- const sessionId = context?.sessionId ?? get().activeId;
190
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
195
+ get().internal_dispatchMessage({ ids, type: 'deleteMessages' }, context);
196
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
191
197
  const result = await messageService.removeMessages(ids, {
192
198
  sessionId,
193
199
  topicId,
@@ -209,21 +215,26 @@ export const messageOptimisticUpdate: StateCreator<
209
215
  // we need to update the message content at the frontend to avoid the update flick
210
216
  // refs: https://medium.com/@kyledeguzmanx/what-are-optimistic-updates-483662c3e171
211
217
  if (extra?.toolCalls) {
212
- internal_dispatchMessage({
213
- id,
214
- type: 'updateMessage',
215
- value: { tools: internal_transformToolCalls(extra?.toolCalls) },
216
- });
218
+ internal_dispatchMessage(
219
+ {
220
+ id,
221
+ type: 'updateMessage',
222
+ value: { tools: internal_transformToolCalls(extra?.toolCalls) },
223
+ },
224
+ context,
225
+ );
217
226
  } else {
218
- internal_dispatchMessage({
219
- id,
220
- type: 'updateMessage',
221
- value: { content },
222
- });
227
+ internal_dispatchMessage(
228
+ {
229
+ id,
230
+ type: 'updateMessage',
231
+ value: { content },
232
+ },
233
+ context,
234
+ );
223
235
  }
224
236
 
225
- const sessionId = context?.sessionId ?? get().activeId;
226
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
237
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
227
238
 
228
239
  const result = await messageService.updateMessage(
229
240
  id,
@@ -252,9 +263,8 @@ export const messageOptimisticUpdate: StateCreator<
252
263
  },
253
264
 
254
265
  optimisticUpdateMessageError: async (id, error, context) => {
255
- get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } });
256
- const sessionId = context?.sessionId ?? get().activeId;
257
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
266
+ get().internal_dispatchMessage({ id, type: 'updateMessage', value: { error } }, context);
267
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
258
268
  const result = await messageService.updateMessage(id, { error }, { sessionId, topicId });
259
269
  if (result?.success && result.messages) {
260
270
  get().replaceMessages(result.messages, { sessionId, topicId });
@@ -267,14 +277,16 @@ export const messageOptimisticUpdate: StateCreator<
267
277
  const { internal_dispatchMessage, refreshMessages, replaceMessages } = get();
268
278
 
269
279
  // Optimistic update: update the frontend immediately
270
- internal_dispatchMessage({
271
- id,
272
- type: 'updateMessageMetadata',
273
- value: metadata,
274
- });
280
+ internal_dispatchMessage(
281
+ {
282
+ id,
283
+ type: 'updateMessageMetadata',
284
+ value: metadata,
285
+ },
286
+ context,
287
+ );
275
288
 
276
- const sessionId = context?.sessionId ?? get().activeId;
277
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
289
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
278
290
 
279
291
  // Persist to database
280
292
  const result = await messageService.updateMessageMetadata(id, metadata, {
@@ -289,9 +301,31 @@ export const messageOptimisticUpdate: StateCreator<
289
301
  }
290
302
  },
291
303
 
304
+ optimisticUpdateMessagePlugin: async (id, value, context) => {
305
+ const { internal_dispatchMessage, replaceMessages } = get();
306
+
307
+ // Optimistic update: update the frontend immediately
308
+ internal_dispatchMessage(
309
+ {
310
+ id,
311
+ type: 'updateMessagePlugin',
312
+ value,
313
+ },
314
+ context,
315
+ );
316
+
317
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
318
+
319
+ // Persist to database
320
+ const result = await messageService.updateMessagePlugin(id, value, { sessionId, topicId });
321
+
322
+ if (result?.success && result.messages) {
323
+ replaceMessages(result.messages, { sessionId, topicId });
324
+ }
325
+ },
326
+
292
327
  optimisticUpdateMessagePluginError: async (id, error, context) => {
293
- const sessionId = context?.sessionId ?? get().activeId;
294
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
328
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
295
329
  const result = await messageService.updateMessagePluginError(id, error, {
296
330
  sessionId,
297
331
  topicId,
@@ -302,8 +336,7 @@ export const messageOptimisticUpdate: StateCreator<
302
336
  },
303
337
 
304
338
  optimisticUpdateMessageRAG: async (id, data, context) => {
305
- const sessionId = context?.sessionId ?? get().activeId;
306
- const topicId = context?.topicId !== undefined ? context.topicId : get().activeTopicId;
339
+ const { sessionId, topicId } = get().internal_getSessionContext(context);
307
340
  const result = await messageService.updateMessageRAG(id, data, {
308
341
  sessionId,
309
342
  topicId,
@@ -15,10 +15,6 @@ export interface ChatMessageState {
15
15
  * Derived from session.type, used for caching to avoid repeated lookups
16
16
  */
17
17
  activeSessionType?: 'agent' | 'group';
18
- /**
19
- * is the message is continuing generation (used for disable continue button)
20
- */
21
- continuingIds: string[];
22
18
  /**
23
19
  * Raw messages from database (flat structure)
24
20
  */
@@ -52,10 +48,6 @@ export interface ChatMessageState {
52
48
  * Parsed messages for display (includes assistantGroup from conversation-flow)
53
49
  */
54
50
  messagesMap: Record<string, UIChatMessage[]>;
55
- /**
56
- * is the message is regenerating (used for disable regenerate button)
57
- */
58
- regeneratingIds: string[];
59
51
  /**
60
52
  * Supervisor decision debounce timers by group ID
61
53
  */
@@ -77,7 +69,6 @@ export interface ChatMessageState {
77
69
  export const initialMessageState: ChatMessageState = {
78
70
  activeId: 'inbox',
79
71
  activeSessionType: undefined,
80
- continuingIds: [],
81
72
  dbMessagesMap: {},
82
73
  groupAgentMaps: {},
83
74
  groupMaps: {},
@@ -87,7 +78,6 @@ export const initialMessageState: ChatMessageState = {
87
78
  messageLoadingIds: [],
88
79
  messagesInit: false,
89
80
  messagesMap: {},
90
- regeneratingIds: [],
91
81
  supervisorDebounceTimers: {},
92
82
  supervisorDecisionAbortControllers: {},
93
83
  supervisorDecisionLoading: [],
@@ -1,26 +1,50 @@
1
1
  import type { ChatStoreState } from '../../../initialState';
2
+ import { operationSelectors } from '../../operation/selectors';
2
3
  import { mainDisplayChatIDs } from './chat';
3
4
  import { getDbMessageByToolCallId } from './dbMessage';
4
5
  import { getDisplayMessageById } from './displayMessage';
5
6
 
6
7
  const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
7
- const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
8
- const isMessageRegenerating = (id: string) => (s: ChatStoreState) => s.regeneratingIds.includes(id);
9
- const isMessageContinuing = (id: string) => (s: ChatStoreState) => s.continuingIds.includes(id);
10
8
 
11
- const isMessageGenerating = (id: string) => (s: ChatStoreState) => s.chatLoadingIds.includes(id);
9
+ /**
10
+ * Check if a message is in loading state
11
+ * Priority: operation system (for AI flows) > legacy messageLoadingIds (for CRUD operations)
12
+ */
13
+ const isMessageLoading = (id: string) => (s: ChatStoreState) => {
14
+ // First check operation system (sendMessage, etc.)
15
+ const hasOperation = operationSelectors.isMessageProcessing(id)(s);
16
+ if (hasOperation) return true;
17
+
18
+ // Fallback to legacy loading state (for non-operation CRUD)
19
+ return s.messageLoadingIds.includes(id);
20
+ };
21
+
22
+ // Use operation system for AI-related loading states
23
+ const isMessageRegenerating = (id: string) => (s: ChatStoreState) =>
24
+ operationSelectors.isMessageRegenerating(id)(s);
25
+ const isMessageContinuing = (id: string) => (s: ChatStoreState) =>
26
+ operationSelectors.isMessageContinuing(id)(s);
27
+ const isMessageGenerating = (id: string) => (s: ChatStoreState) =>
28
+ operationSelectors.isMessageGenerating(id)(s); // Only check generateAI operations
29
+ const isMessageInChatReasoning = (id: string) => (s: ChatStoreState) =>
30
+ operationSelectors.isMessageInReasoning(id)(s);
31
+
32
+ // Use operation system for message CRUD operations
33
+ const isMessageCreating = (id: string) => (s: ChatStoreState) =>
34
+ operationSelectors.isMessageCreating(id)(s); // Only check sendMessage operations
35
+
36
+ // RAG flow still uses dedicated state
12
37
  const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
13
38
  s.messageRAGLoadingIds.includes(id);
14
- const isMessageInChatReasoning = (id: string) => (s: ChatStoreState) =>
15
- s.reasoningLoadingIds.includes(id);
16
39
 
17
40
  const isMessageCollapsed = (id: string) => (s: ChatStoreState) => {
18
41
  const message = getDisplayMessageById(id)(s);
19
42
  return message?.metadata?.collapsed ?? false;
20
43
  };
21
44
 
45
+ // Use operation system for plugin API invocation
22
46
  const isPluginApiInvoking = (id: string) => (s: ChatStoreState) =>
23
- s.pluginApiLoadingIds.includes(id);
47
+ operationSelectors.isMessageInToolCalling(id)(s);
24
48
 
25
49
  const isToolCallStreaming = (id: string, index: number) => (s: ChatStoreState) => {
26
50
  const isLoading = s.toolCallingStreamIds[id];
@@ -33,7 +57,8 @@ const isToolCallStreaming = (id: string, index: number) => (s: ChatStoreState) =
33
57
  const isInToolsCalling = (id: string, index: number) => (s: ChatStoreState) => {
34
58
  const isStreamingToolsCalling = isToolCallStreaming(id, index)(s);
35
59
 
36
- const isInvokingPluginApi = s.messageInToolsCallingIds.includes(id);
60
+ // Check if assistant message has any tool calling operations
61
+ const isInvokingPluginApi = operationSelectors.isMessageInToolCalling(id)(s);
37
62
 
38
63
  return isStreamingToolsCalling || isInvokingPluginApi;
39
64
  };
@@ -47,9 +72,6 @@ const isToolApiNameShining =
47
72
  return isStreaming || isPluginInvoking;
48
73
  };
49
74
 
50
- const isAIGenerating = (s: ChatStoreState) =>
51
- s.chatLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
52
-
53
75
  const isInRAGFlow = (s: ChatStoreState) =>
54
76
  s.messageRAGLoadingIds.some((id) => mainDisplayChatIDs(s).includes(id));
55
77
 
@@ -72,13 +94,13 @@ const isSendButtonDisabledByMessage = (s: ChatStoreState) =>
72
94
  isInRAGFlow(s);
73
95
 
74
96
  export const messageStateSelectors = {
75
- isAIGenerating,
76
97
  isCreatingMessage,
77
98
  isHasMessageLoading,
78
99
  isInRAGFlow,
79
100
  isInToolsCalling,
80
101
  isMessageCollapsed,
81
102
  isMessageContinuing,
103
+ isMessageCreating,
82
104
  isMessageEditing,
83
105
  isMessageGenerating,
84
106
  isMessageInChatReasoning,