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

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 (95) hide show
  1. package/CHANGELOG.md +50 -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 +18 -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/components/Analytics/MainInterfaceTracker.tsx +2 -2
  21. package/src/features/ChatInput/ActionBar/STT/browser.tsx +2 -2
  22. package/src/features/ChatInput/ActionBar/STT/openai.tsx +2 -2
  23. package/src/features/Conversation/MarkdownElements/LobeThinking/Render.tsx +3 -3
  24. package/src/features/Conversation/MarkdownElements/Thinking/Render.tsx +3 -3
  25. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +1 -1
  26. package/src/features/Conversation/Messages/User/index.tsx +3 -3
  27. package/src/features/Conversation/Messages/index.tsx +3 -3
  28. package/src/features/Conversation/components/AutoScroll.tsx +2 -2
  29. package/src/features/PluginsUI/Render/StandaloneType/Iframe.tsx +3 -3
  30. package/src/features/Portal/Home/Body/Plugins/ArtifactList/index.tsx +3 -3
  31. package/src/features/ShareModal/ShareText/index.tsx +3 -3
  32. package/src/services/search.ts +2 -2
  33. package/src/store/chat/agents/GeneralChatAgent.ts +98 -0
  34. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +366 -0
  35. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +1217 -0
  36. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +1976 -0
  37. package/src/store/chat/agents/__tests__/createAgentExecutors/finish.test.ts +453 -0
  38. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/index.ts +4 -0
  39. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockInstructions.ts +126 -0
  40. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockMessages.ts +94 -0
  41. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockOperations.ts +96 -0
  42. package/src/store/chat/agents/__tests__/createAgentExecutors/fixtures/mockStore.ts +138 -0
  43. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +185 -0
  44. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/index.ts +3 -0
  45. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/operationTestUtils.ts +94 -0
  46. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/testExecutor.ts +139 -0
  47. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +545 -0
  48. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +686 -0
  49. package/src/store/chat/agents/createAgentExecutors.ts +313 -80
  50. package/src/store/chat/selectors.ts +1 -0
  51. package/src/store/chat/slices/aiChat/__tests__/ai-chat.integration.test.ts +667 -0
  52. package/src/store/chat/slices/aiChat/actions/__tests__/cancel-functionality.test.ts +137 -27
  53. package/src/store/chat/slices/aiChat/actions/__tests__/conversationControl.test.ts +163 -125
  54. package/src/store/chat/slices/aiChat/actions/__tests__/conversationLifecycle.test.ts +12 -2
  55. package/src/store/chat/slices/aiChat/actions/__tests__/fixtures.ts +0 -2
  56. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +0 -2
  57. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +286 -19
  58. package/src/store/chat/slices/aiChat/actions/__tests__/streamingStates.test.ts +0 -112
  59. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +42 -99
  60. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +90 -57
  61. package/src/store/chat/slices/aiChat/actions/generateAIGroupChat.ts +5 -25
  62. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +220 -98
  63. package/src/store/chat/slices/aiChat/actions/streamingStates.ts +0 -34
  64. package/src/store/chat/slices/aiChat/initialState.ts +0 -28
  65. package/src/store/chat/slices/aiChat/selectors.test.ts +280 -0
  66. package/src/store/chat/slices/aiChat/selectors.ts +31 -7
  67. package/src/store/chat/slices/builtinTool/actions/__tests__/localSystem.test.ts +21 -30
  68. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +29 -49
  69. package/src/store/chat/slices/builtinTool/actions/interpreter.ts +83 -48
  70. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +78 -28
  71. package/src/store/chat/slices/builtinTool/actions/search.ts +146 -59
  72. package/src/store/chat/slices/builtinTool/selectors.test.ts +258 -0
  73. package/src/store/chat/slices/builtinTool/selectors.ts +25 -4
  74. package/src/store/chat/slices/message/action.test.ts +134 -16
  75. package/src/store/chat/slices/message/actions/internals.ts +33 -7
  76. package/src/store/chat/slices/message/actions/optimisticUpdate.ts +85 -52
  77. package/src/store/chat/slices/message/initialState.ts +0 -10
  78. package/src/store/chat/slices/message/selectors/messageState.ts +34 -12
  79. package/src/store/chat/slices/operation/__tests__/actions.test.ts +712 -16
  80. package/src/store/chat/slices/operation/__tests__/integration.test.ts +342 -0
  81. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +257 -17
  82. package/src/store/chat/slices/operation/actions.ts +218 -11
  83. package/src/store/chat/slices/operation/selectors.ts +135 -6
  84. package/src/store/chat/slices/operation/types.ts +29 -3
  85. package/src/store/chat/slices/plugin/action.test.ts +30 -322
  86. package/src/store/chat/slices/plugin/actions/internals.ts +0 -14
  87. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +21 -19
  88. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +45 -27
  89. package/src/store/chat/slices/plugin/actions/publicApi.ts +3 -4
  90. package/src/store/chat/slices/plugin/actions/workflow.ts +0 -55
  91. package/src/store/chat/slices/thread/selectors/index.ts +4 -2
  92. package/src/store/chat/slices/topic/action.ts +3 -3
  93. package/src/store/chat/slices/translate/action.ts +54 -41
  94. package/src/tools/web-browsing/ExecutionRuntime/index.ts +5 -2
  95. package/src/tools/web-browsing/Portal/Search/Footer.tsx +11 -9
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Integration test for Operation Management System
3
+ * Tests the full lifecycle of operations in realistic scenarios
4
+ */
5
+ import { act, renderHook } from '@testing-library/react';
6
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
7
+
8
+ import { operationSelectors } from '@/store/chat/slices/operation/selectors';
9
+ import { useChatStore } from '@/store/chat/store';
10
+ import { messageMapKey } from '@/store/chat/utils/messageMapKey';
11
+
12
+ vi.mock('zustand/traditional');
13
+
14
+ describe('Operation Management Integration Tests', () => {
15
+ beforeEach(() => {
16
+ act(() => {
17
+ useChatStore.setState({
18
+ activeId: 'test-session',
19
+ activeTopicId: 'test-topic',
20
+ operations: {},
21
+ operationsByType: {} as any,
22
+ operationsByMessage: {},
23
+ operationsByContext: {},
24
+ messageOperationMap: {},
25
+ });
26
+ });
27
+ });
28
+
29
+ describe('Complete Operation Lifecycle', () => {
30
+ it('should handle full AI generation lifecycle', async () => {
31
+ const { result } = renderHook(() => useChatStore());
32
+ const sessionId = 'test-session';
33
+ const topicId = 'test-topic';
34
+ const messageId = 'user-msg-1';
35
+
36
+ // 1. Start operation
37
+ let operationId: string = '';
38
+ act(() => {
39
+ const { operationId: id } = result.current.startOperation({
40
+ type: 'execAgentRuntime',
41
+ context: { sessionId, topicId, messageId },
42
+ label: 'AI Generation',
43
+ });
44
+ operationId = id;
45
+ });
46
+
47
+ // Verify operation created
48
+ expect(result.current.operations[operationId!]).toBeDefined();
49
+ expect(result.current.operations[operationId!].status).toBe('running');
50
+ expect(result.current.operations[operationId!].context).toEqual({
51
+ sessionId,
52
+ topicId,
53
+ messageId,
54
+ });
55
+
56
+ // 2. Associate message
57
+ act(() => {
58
+ result.current.associateMessageWithOperation(messageId, operationId!);
59
+ });
60
+
61
+ expect(result.current.messageOperationMap[messageId]).toBe(operationId);
62
+
63
+ // 3. Check status during execution
64
+ expect(operationSelectors.hasAnyRunningOperation(result.current)).toBe(true);
65
+ expect(operationSelectors.canSendMessage(result.current)).toBe(false);
66
+ expect(operationSelectors.isAgentRuntimeRunning(result.current)).toBe(true);
67
+
68
+ // 4. Complete operation
69
+ act(() => {
70
+ result.current.completeOperation(operationId!);
71
+ });
72
+
73
+ expect(result.current.operations[operationId!].status).toBe('completed');
74
+ expect(operationSelectors.hasAnyRunningOperation(result.current)).toBe(false);
75
+ expect(operationSelectors.canSendMessage(result.current)).toBe(true);
76
+ });
77
+
78
+ it('should handle operation cancellation', async () => {
79
+ const { result } = renderHook(() => useChatStore());
80
+
81
+ let operationId: string = '';
82
+ let abortController: AbortController | undefined;
83
+
84
+ act(() => {
85
+ const res = result.current.startOperation({
86
+ type: 'execAgentRuntime',
87
+ context: { sessionId: 'test-session' },
88
+ });
89
+ operationId = res.operationId;
90
+ abortController = res.abortController;
91
+ });
92
+
93
+ const abortSpy = vi.spyOn(abortController!, 'abort');
94
+
95
+ // Cancel operation
96
+ act(() => {
97
+ result.current.cancelOperation(operationId!, 'User cancelled');
98
+ });
99
+
100
+ expect(result.current.operations[operationId!].status).toBe('cancelled');
101
+ expect(abortSpy).toHaveBeenCalled();
102
+ });
103
+ });
104
+
105
+ describe('Parent-Child Operation Relationship', () => {
106
+ it('should handle nested operations with context inheritance', async () => {
107
+ const { result } = renderHook(() => useChatStore());
108
+
109
+ // Create parent operation
110
+ let parentOpId: string = '';
111
+ act(() => {
112
+ const { operationId } = result.current.startOperation({
113
+ type: 'execAgentRuntime',
114
+ context: { sessionId: 'test-session', topicId: 'test-topic' },
115
+ });
116
+ parentOpId = operationId;
117
+ });
118
+
119
+ // Create child operation (should inherit context)
120
+ let childOpId: string = '';
121
+ act(() => {
122
+ const { operationId } = result.current.startOperation({
123
+ type: 'toolCalling',
124
+ parentOperationId: parentOpId!,
125
+ });
126
+ childOpId = operationId;
127
+ });
128
+
129
+ // Verify context inheritance
130
+ const parentOp = result.current.operations[parentOpId!];
131
+ const childOp = result.current.operations[childOpId!];
132
+
133
+ expect(childOp.context.sessionId).toBe(parentOp.context.sessionId);
134
+ expect(childOp.context.topicId).toBe(parentOp.context.topicId);
135
+ expect(childOp.parentOperationId).toBe(parentOpId);
136
+ expect(parentOp.childOperationIds).toContain(childOpId);
137
+ });
138
+
139
+ it('should cascade cancel child operations', async () => {
140
+ const { result } = renderHook(() => useChatStore());
141
+
142
+ // Create parent and child operations
143
+ let parentOpId: string = '';
144
+ let childOpId1: string = '';
145
+ let childOpId2: string = '';
146
+
147
+ act(() => {
148
+ const { operationId } = result.current.startOperation({
149
+ type: 'execAgentRuntime',
150
+ context: { sessionId: 'test-session' },
151
+ });
152
+ parentOpId = operationId;
153
+ });
154
+
155
+ act(() => {
156
+ const { operationId } = result.current.startOperation({
157
+ type: 'toolCalling',
158
+ parentOperationId: parentOpId!,
159
+ });
160
+ childOpId1 = operationId;
161
+ });
162
+
163
+ act(() => {
164
+ const { operationId } = result.current.startOperation({
165
+ type: 'toolCalling',
166
+ parentOperationId: parentOpId!,
167
+ });
168
+ childOpId2 = operationId;
169
+ });
170
+
171
+ // Cancel parent
172
+ act(() => {
173
+ result.current.cancelOperation(parentOpId!, 'Parent cancelled');
174
+ });
175
+
176
+ // Verify all cancelled
177
+ expect(result.current.operations[parentOpId!].status).toBe('cancelled');
178
+ expect(result.current.operations[childOpId1!].status).toBe('cancelled');
179
+ expect(result.current.operations[childOpId2!].status).toBe('cancelled');
180
+ });
181
+ });
182
+
183
+ describe('Context Isolation', () => {
184
+ it('should maintain correct context when switching topics', async () => {
185
+ const { result } = renderHook(() => useChatStore());
186
+
187
+ // Create operation in topic A
188
+ let opIdTopicA: string = '';
189
+ act(() => {
190
+ const { operationId } = result.current.startOperation({
191
+ type: 'execAgentRuntime',
192
+ context: { sessionId: 'session-1', topicId: 'topic-a' },
193
+ });
194
+ opIdTopicA = operationId;
195
+ });
196
+
197
+ // Switch to topic B
198
+ act(() => {
199
+ useChatStore.setState({ activeTopicId: 'topic-b' });
200
+ });
201
+
202
+ // Create operation in topic B
203
+ let opIdTopicB: string = '';
204
+ act(() => {
205
+ const { operationId } = result.current.startOperation({
206
+ type: 'execAgentRuntime',
207
+ context: { sessionId: 'session-1', topicId: 'topic-b' },
208
+ });
209
+ opIdTopicB = operationId;
210
+ });
211
+
212
+ // Verify context isolation
213
+ const opA = result.current.operations[opIdTopicA!];
214
+ const opB = result.current.operations[opIdTopicB!];
215
+
216
+ expect(opA.context.topicId).toBe('topic-a');
217
+ expect(opB.context.topicId).toBe('topic-b');
218
+
219
+ // Get operations by context
220
+ const contextA = messageMapKey('session-1', 'topic-a');
221
+ const contextB = messageMapKey('session-1', 'topic-b');
222
+
223
+ const opsInA = result.current.operationsByContext[contextA] || [];
224
+ const opsInB = result.current.operationsByContext[contextB] || [];
225
+
226
+ expect(opsInA).toContain(opIdTopicA);
227
+ expect(opsInB).toContain(opIdTopicB);
228
+ expect(opsInA).not.toContain(opIdTopicB);
229
+ expect(opsInB).not.toContain(opIdTopicA);
230
+ });
231
+ });
232
+
233
+ describe('Batch Operations', () => {
234
+ it('should handle multiple concurrent operations', async () => {
235
+ const { result } = renderHook(() => useChatStore());
236
+
237
+ const opIds: string[] = [];
238
+
239
+ // Start 5 operations concurrently
240
+ act(() => {
241
+ for (let i = 0; i < 5; i++) {
242
+ const { operationId } = result.current.startOperation({
243
+ type: i % 2 === 0 ? 'execAgentRuntime' : 'toolCalling',
244
+ context: { sessionId: 'session-1', messageId: `msg-${i}` },
245
+ });
246
+ opIds.push(operationId);
247
+ }
248
+ });
249
+
250
+ // Verify all running
251
+ expect(operationSelectors.getRunningOperations(result.current)).toHaveLength(5);
252
+
253
+ // Cancel all generateAI operations
254
+ let cancelledIds: string[];
255
+ act(() => {
256
+ cancelledIds = result.current.cancelOperations({
257
+ type: 'execAgentRuntime',
258
+ status: 'running',
259
+ });
260
+ });
261
+
262
+ // Verify correct operations cancelled
263
+ expect(cancelledIds!).toHaveLength(3); // 0, 2, 4
264
+ expect(operationSelectors.getRunningOperations(result.current)).toHaveLength(2);
265
+ });
266
+ });
267
+
268
+ describe('Memory Management', () => {
269
+ it('should cleanup old completed operations', async () => {
270
+ const { result } = renderHook(() => useChatStore());
271
+
272
+ const oldTimestamp = Date.now() - 2 * 60 * 1000; // 2 minutes ago
273
+ const recentTimestamp = Date.now() - 30 * 1000; // 30 seconds ago
274
+
275
+ // Create old completed operation
276
+ let oldOpId: string;
277
+ act(() => {
278
+ const { operationId } = result.current.startOperation({
279
+ type: 'execAgentRuntime',
280
+ context: { sessionId: 'session-1' },
281
+ });
282
+ oldOpId = operationId;
283
+ });
284
+
285
+ // Complete and manually set old timestamp
286
+ act(() => {
287
+ result.current.completeOperation(oldOpId!);
288
+ useChatStore.setState((state) => ({
289
+ operations: {
290
+ ...state.operations,
291
+ [oldOpId!]: {
292
+ ...state.operations[oldOpId!],
293
+ status: 'completed' as const,
294
+ metadata: {
295
+ ...state.operations[oldOpId!].metadata,
296
+ startTime: oldTimestamp,
297
+ endTime: oldTimestamp + 1000,
298
+ },
299
+ },
300
+ },
301
+ }));
302
+ });
303
+
304
+ // Create recent completed operation
305
+ let recentOpId: string;
306
+ act(() => {
307
+ const { operationId } = result.current.startOperation({
308
+ type: 'execAgentRuntime',
309
+ context: { sessionId: 'session-1' },
310
+ });
311
+ recentOpId = operationId;
312
+ });
313
+
314
+ act(() => {
315
+ result.current.completeOperation(recentOpId!);
316
+ useChatStore.setState((state) => ({
317
+ operations: {
318
+ ...state.operations,
319
+ [recentOpId!]: {
320
+ ...state.operations[recentOpId!],
321
+ status: 'completed' as const,
322
+ metadata: {
323
+ ...state.operations[recentOpId!].metadata,
324
+ startTime: recentTimestamp,
325
+ endTime: recentTimestamp + 1000,
326
+ },
327
+ },
328
+ },
329
+ }));
330
+ });
331
+
332
+ // Cleanup operations older than 1 minute
333
+ act(() => {
334
+ result.current.cleanupCompletedOperations(60 * 1000);
335
+ });
336
+
337
+ // Verify old operation cleaned up, recent kept
338
+ expect(result.current.operations[oldOpId!]).toBeUndefined();
339
+ expect(result.current.operations[recentOpId!]).toBeDefined();
340
+ });
341
+ });
342
+ });