@lobehub/lobehub 2.0.0-next.104 → 2.0.0-next.105

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 (31) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/package.json +2 -2
  3. package/changelog/v1.json +5 -0
  4. package/package.json +9 -3
  5. package/packages/database/src/repositories/knowledge/index.ts +5 -8
  6. package/packages/model-bank/src/aiModels/moonshot.ts +46 -0
  7. package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
  8. package/packages/model-runtime/src/providers/moonshot/index.ts +17 -4
  9. package/packages/types/src/user/settings/keyVaults.ts +0 -68
  10. package/packages/utils/src/client/parserPlaceholder.ts +1 -1
  11. package/src/services/__tests__/_auth.test.ts +1 -4
  12. package/src/services/_auth.ts +2 -3
  13. package/src/services/_header.ts +1 -8
  14. package/src/store/chat/agents/__tests__/createAgentExecutors/call-llm.test.ts +18 -0
  15. package/src/store/chat/agents/__tests__/createAgentExecutors/call-tool.test.ts +40 -11
  16. package/src/store/chat/agents/__tests__/createAgentExecutors/helpers/assertions.ts +3 -0
  17. package/src/store/chat/agents/__tests__/createAgentExecutors/request-human-approve.test.ts +15 -0
  18. package/src/store/chat/agents/__tests__/createAgentExecutors/resolve-aborted-tools.test.ts +37 -11
  19. package/src/store/chat/agents/createAgentExecutors.ts +22 -13
  20. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +4 -8
  21. package/src/store/chat/slices/builtinTool/actions/__tests__/search.test.ts +16 -2
  22. package/src/store/chat/slices/builtinTool/actions/localSystem.ts +5 -1
  23. package/src/store/chat/slices/builtinTool/actions/search.ts +5 -1
  24. package/src/store/chat/slices/message/actions/publicApi.ts +10 -2
  25. package/src/store/chat/slices/message/actions/query.ts +17 -4
  26. package/src/store/chat/slices/operation/__tests__/selectors.test.ts +93 -5
  27. package/src/store/chat/slices/operation/selectors.ts +16 -3
  28. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +24 -18
  29. package/src/store/user/slices/settings/selectors/keyVaults.ts +0 -5
  30. package/src/features/ChatList/Error/AccessCodeForm.tsx +0 -63
  31. package/src/services/__tests__/share.test.ts +0 -61
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.105](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.104...v2.0.0-next.105)
6
+
7
+ <sup>Released on **2025-11-23**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **operation**: Isolate loading state to current active topic.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **operation**: Isolate loading state to current active topic, closes [#10360](https://github.com/lobehub/lobe-chat/issues/10360) ([c568369](https://github.com/lobehub/lobe-chat/commit/c568369))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.104](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.103...v2.0.0-next.104)
6
31
 
7
32
  <sup>Released on **2025-11-22**</sup>
@@ -58,7 +58,7 @@
58
58
  "electron-is": "^3.0.0",
59
59
  "electron-log": "^5.4.3",
60
60
  "electron-store": "^8.2.0",
61
- "electron-vite": "^3.1.0",
61
+ "electron-vite": "^4.0.1",
62
62
  "execa": "^9.6.0",
63
63
  "fast-glob": "^3.3.3",
64
64
  "fix-path": "^5.0.0",
@@ -73,7 +73,7 @@
73
73
  "tsx": "^4.20.6",
74
74
  "typescript": "^5.9.3",
75
75
  "undici": "^7.16.0",
76
- "vite": "^6.4.1",
76
+ "vite": "^7.2.4",
77
77
  "vitest": "^3.2.4"
78
78
  },
79
79
  "pnpm": {
package/changelog/v1.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2025-11-23",
5
+ "version": "2.0.0-next.105"
6
+ },
2
7
  {
3
8
  "children": {
4
9
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.104",
3
+ "version": "2.0.0-next.105",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -123,6 +123,9 @@
123
123
  "eslint --fix"
124
124
  ]
125
125
  },
126
+ "overrides": {
127
+ "stylelint-config-clean-order": "7.0.0"
128
+ },
126
129
  "dependencies": {
127
130
  "@ant-design/icons": "^5.6.1",
128
131
  "@ant-design/pro-components": "^2.8.10",
@@ -295,7 +298,7 @@
295
298
  "unstructured-client": "^0.19.0",
296
299
  "url-join": "^5.0.0",
297
300
  "use-merge-value": "^1.2.0",
298
- "uuid": "^11.1.0",
301
+ "uuid": "^13.0.0",
299
302
  "virtua": "^0.47.0",
300
303
  "word-extractor": "^1.0.4",
301
304
  "ws": "^8.18.3",
@@ -399,6 +402,9 @@
399
402
  "pnpm": {
400
403
  "onlyBuiltDependencies": [
401
404
  "@vercel/speed-insights"
402
- ]
405
+ ],
406
+ "overrides": {
407
+ "stylelint-config-clean-order": "7.0.0"
408
+ }
403
409
  }
404
410
  }
@@ -129,12 +129,6 @@ export class KnowledgeRepo {
129
129
  };
130
130
  });
131
131
 
132
- console.log('[KnowledgeRepo.query] Fetched items:', {
133
- count: mappedResults.length,
134
- documents: mappedResults.filter((item) => item.sourceType === 'document'),
135
- sampleEditorData: mappedResults.find((item) => item.sourceType === 'document')?.editorData,
136
- });
137
-
138
132
  return mappedResults;
139
133
  }
140
134
 
@@ -308,7 +302,10 @@ export class KnowledgeRepo {
308
302
 
309
303
  // Exclude custom/document and source_type='file' from Documents category
310
304
  if (category === FilesTabs.Documents) {
311
- whereConditions.push(sql`${documents.fileType} != ${'custom/document'}`, sql`${documents.sourceType} != ${'file'}`);
305
+ whereConditions.push(
306
+ sql`${documents.fileType} != ${'custom/document'}`,
307
+ sql`${documents.sourceType} != ${'file'}`,
308
+ );
312
309
  }
313
310
  } else if (fileTypePrefix) {
314
311
  whereConditions.push(sql`${documents.fileType} ILIKE ${`${fileTypePrefix}%`}`);
@@ -338,7 +335,7 @@ export class KnowledgeRepo {
338
335
  // Documents don't have knowledge base association currently, so skip if knowledgeBaseId is set
339
336
  if (knowledgeBaseId) {
340
337
  return sql`
341
- SELECT
338
+ SELECT
342
339
  NULL::varchar(30) as id,
343
340
  NULL::text as name,
344
341
  NULL::varchar(255) as file_type,
@@ -2,6 +2,52 @@ import { AIChatModelCard } from '../types/aiModel';
2
2
 
3
3
  // https://platform.moonshot.cn/docs/pricing/chat
4
4
  const moonshotChatModels: AIChatModelCard[] = [
5
+ {
6
+ abilities: {
7
+ functionCall: true,
8
+ reasoning: true,
9
+ structuredOutput: true,
10
+ },
11
+ contextWindowTokens: 262_144,
12
+ description: 'K2 长思考模型,支持 256k 上下文,支持多步工具调用与思考,擅长解决更复杂的问题。',
13
+ displayName: 'Kimi K2 Thinking',
14
+ enabled: true,
15
+ id: 'kimi-k2-thinking',
16
+ maxOutput: 65_536,
17
+ pricing: {
18
+ currency: 'CNY',
19
+ units: [
20
+ { name: 'textInput_cacheRead', rate: 1, strategy: 'fixed', unit: 'millionTokens' },
21
+ { name: 'textInput', rate: 4, strategy: 'fixed', unit: 'millionTokens' },
22
+ { name: 'textOutput', rate: 16, strategy: 'fixed', unit: 'millionTokens' },
23
+ ],
24
+ },
25
+ releasedAt: '2025-11-06',
26
+ type: 'chat',
27
+ },
28
+ {
29
+ abilities: {
30
+ functionCall: true,
31
+ reasoning: true,
32
+ structuredOutput: true,
33
+ },
34
+ contextWindowTokens: 262_144,
35
+ description:
36
+ 'K2 长思考模型的高速版本,支持 256k 上下文,擅长深度推理,输出速度提升至每秒 60-100 tokens 。',
37
+ displayName: 'Kimi K2 Thinking Turbo',
38
+ id: 'kimi-k2-thinking-turbo',
39
+ maxOutput: 65_536,
40
+ pricing: {
41
+ currency: 'CNY',
42
+ units: [
43
+ { name: 'textInput_cacheRead', rate: 1, strategy: 'fixed', unit: 'millionTokens' },
44
+ { name: 'textInput', rate: 8, strategy: 'fixed', unit: 'millionTokens' },
45
+ { name: 'textOutput', rate: 58, strategy: 'fixed', unit: 'millionTokens' },
46
+ ],
47
+ },
48
+ releasedAt: '2025-11-06',
49
+ type: 'chat',
50
+ },
5
51
  {
6
52
  abilities: {
7
53
  functionCall: true,
@@ -49,7 +49,7 @@ export const convertOpenAIMessages = async (messages: OpenAI.ChatCompletionMessa
49
49
  if (msg.tool_call_id !== undefined) result.tool_call_id = msg.tool_call_id;
50
50
  if (msg.function_call !== undefined) result.function_call = msg.function_call;
51
51
 
52
- // it's compatible for DeepSeek
52
+ // it's compatible for DeepSeek & Moonshot
53
53
  if (msg.reasoning_content !== undefined) result.reasoning_content = msg.reasoning_content;
54
54
  // MiniMax uses reasoning_details for historical thinking, so forward it unchanged
55
55
  if (msg.reasoning_details !== undefined) result.reasoning_details = msg.reasoning_details;
@@ -18,12 +18,25 @@ export const params = {
18
18
  handlePayload: (payload: ChatStreamPayload) => {
19
19
  const { enabledSearch, messages, temperature, tools, ...rest } = payload;
20
20
 
21
- // assistant 空消息添加一个空格 (#8418)
22
- const filteredMessages = messages.map((message) => {
21
+ const filteredMessages = messages.map((message: any) => {
22
+ let normalizedMessage = message;
23
+
24
+ // 为 assistant 空消息添加一个空格 (#8418)
23
25
  if (message.role === 'assistant' && (!message.content || message.content === '')) {
24
- return { ...message, content: ' ' };
26
+ normalizedMessage = { ...normalizedMessage, content: ' ' };
27
+ }
28
+
29
+ // Interleaved thinking
30
+ if (message.role === 'assistant' && message.reasoning) {
31
+ const { reasoning, ...messageWithoutReasoning } = normalizedMessage;
32
+ return {
33
+ ...messageWithoutReasoning,
34
+ ...(!reasoning.signature && reasoning.content
35
+ ? { reasoning_content: reasoning.content }
36
+ : {}),
37
+ };
25
38
  }
26
- return message;
39
+ return normalizedMessage;
27
40
  });
28
41
 
29
42
  const moonshotTools = enabledSearch
@@ -51,73 +51,5 @@ export interface SearchEngineKeyVaults {
51
51
  }
52
52
 
53
53
  export interface UserKeyVaults extends SearchEngineKeyVaults {
54
- ai21?: OpenAICompatibleKeyVault;
55
- ai302?: OpenAICompatibleKeyVault;
56
- ai360?: OpenAICompatibleKeyVault;
57
- aihubmix?: OpenAICompatibleKeyVault;
58
- akashchat?: OpenAICompatibleKeyVault;
59
- anthropic?: OpenAICompatibleKeyVault;
60
- azure?: AzureOpenAIKeyVault;
61
- azureai?: AzureOpenAIKeyVault;
62
- baichuan?: OpenAICompatibleKeyVault;
63
- bedrock?: AWSBedrockKeyVault;
64
- bfl?: any;
65
- cerebras?: OpenAICompatibleKeyVault;
66
- cloudflare?: CloudflareKeyVault;
67
- cohere?: OpenAICompatibleKeyVault;
68
- cometapi?: OpenAICompatibleKeyVault;
69
- comfyui?: ComfyUIKeyVault;
70
- deepseek?: OpenAICompatibleKeyVault;
71
- fal?: FalKeyVault;
72
- fireworksai?: OpenAICompatibleKeyVault;
73
- giteeai?: OpenAICompatibleKeyVault;
74
- github?: OpenAICompatibleKeyVault;
75
- google?: OpenAICompatibleKeyVault;
76
- groq?: OpenAICompatibleKeyVault;
77
- higress?: OpenAICompatibleKeyVault;
78
- huggingface?: OpenAICompatibleKeyVault;
79
- hunyuan?: OpenAICompatibleKeyVault;
80
- infiniai?: OpenAICompatibleKeyVault;
81
- internlm?: OpenAICompatibleKeyVault;
82
- jina?: OpenAICompatibleKeyVault;
83
- lmstudio?: OpenAICompatibleKeyVault;
84
- lobehub?: any;
85
- minimax?: OpenAICompatibleKeyVault;
86
- mistral?: OpenAICompatibleKeyVault;
87
- modelscope?: OpenAICompatibleKeyVault;
88
- moonshot?: OpenAICompatibleKeyVault;
89
- nebius?: OpenAICompatibleKeyVault;
90
- newapi?: OpenAICompatibleKeyVault;
91
- novita?: OpenAICompatibleKeyVault;
92
- nvidia?: OpenAICompatibleKeyVault;
93
- ollama?: OpenAICompatibleKeyVault;
94
- ollamacloud?: OpenAICompatibleKeyVault;
95
- openai?: OpenAICompatibleKeyVault;
96
- openrouter?: OpenAICompatibleKeyVault;
97
- password?: string;
98
- perplexity?: OpenAICompatibleKeyVault;
99
- ppio?: OpenAICompatibleKeyVault;
100
- qiniu?: OpenAICompatibleKeyVault;
101
- qwen?: OpenAICompatibleKeyVault;
102
- sambanova?: OpenAICompatibleKeyVault;
103
54
  search1api?: OpenAICompatibleKeyVault;
104
- sensenova?: OpenAICompatibleKeyVault;
105
- siliconcloud?: OpenAICompatibleKeyVault;
106
- spark?: OpenAICompatibleKeyVault;
107
- stepfun?: OpenAICompatibleKeyVault;
108
- taichu?: OpenAICompatibleKeyVault;
109
- tencentcloud?: OpenAICompatibleKeyVault;
110
- togetherai?: OpenAICompatibleKeyVault;
111
- upstage?: OpenAICompatibleKeyVault;
112
- v0?: OpenAICompatibleKeyVault;
113
- vercelaigateway?: OpenAICompatibleKeyVault;
114
- vertexai?: VertexAIKeyVault;
115
- vllm?: OpenAICompatibleKeyVault;
116
- volcengine?: OpenAICompatibleKeyVault;
117
- wenxin?: OpenAICompatibleKeyVault;
118
- xai?: OpenAICompatibleKeyVault;
119
- xinference?: OpenAICompatibleKeyVault;
120
- zenmux?: OpenAICompatibleKeyVault;
121
- zeroone?: OpenAICompatibleKeyVault;
122
- zhipu?: OpenAICompatibleKeyVault;
123
55
  }
@@ -87,7 +87,7 @@ export const VARIABLE_GENERATORS = {
87
87
  random_digit: () => Math.floor(Math.random() * 10).toString(),
88
88
 
89
89
  /**
90
- * UUID 类模板变量
90
+ * UUID-type template variables
91
91
  *
92
92
  * | Value | Example |
93
93
  * |-------|---------|
@@ -23,10 +23,7 @@ const mockTogetherAIAPIKey = 'togetherai-api-key';
23
23
  // mock the traditional zustand
24
24
  vi.mock('zustand/traditional');
25
25
 
26
- const setModelProviderConfig = <T extends GlobalLLMProviderKey>(
27
- provider: T,
28
- config: Partial<UserKeyVaults[T]>,
29
- ) => {
26
+ const setModelProviderConfig = (provider: string, config: any) => {
30
27
  useUserStore.setState({
31
28
  settings: { keyVaults: { [provider]: config } },
32
29
  });
@@ -13,7 +13,7 @@ import { ModelProvider } from 'model-bank';
13
13
 
14
14
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
15
15
  import { useUserStore } from '@/store/user';
16
- import { keyVaultsConfigSelectors, userProfileSelectors } from '@/store/user/selectors';
16
+ import { userProfileSelectors } from '@/store/user/selectors';
17
17
  import { obfuscatePayloadWithXOR } from '@/utils/client/xor-obfuscation';
18
18
 
19
19
  import { resolveRuntimeProvider } from './chat/helper';
@@ -105,10 +105,9 @@ export const getProviderAuthPayload = (
105
105
  };
106
106
 
107
107
  const createAuthTokenWithPayload = (payload = {}) => {
108
- const accessCode = keyVaultsConfigSelectors.password(useUserStore.getState());
109
108
  const userId = userProfileSelectors.userId(useUserStore.getState());
110
109
 
111
- return obfuscatePayloadWithXOR<ClientSecretPayload>({ accessCode, userId, ...payload });
110
+ return obfuscatePayloadWithXOR<ClientSecretPayload>({ userId, ...payload });
112
111
  };
113
112
 
114
113
  interface AuthParams {
@@ -1,12 +1,6 @@
1
- import {
2
- LOBE_CHAT_ACCESS_CODE,
3
- LOBE_USER_ID,
4
- OPENAI_API_KEY_HEADER_KEY,
5
- OPENAI_END_POINT,
6
- } from '@/const/fetch';
1
+ import { LOBE_USER_ID, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
7
2
  import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
8
3
  import { useUserStore } from '@/store/user';
9
- import { keyVaultsConfigSelectors } from '@/store/user/selectors';
10
4
 
11
5
  /**
12
6
  * TODO: Need to be removed after tts refactor
@@ -22,7 +16,6 @@ export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
22
16
  // eslint-disable-next-line no-undef
23
17
  return {
24
18
  ...header,
25
- [LOBE_CHAT_ACCESS_CODE]: keyVaultsConfigSelectors.password(state),
26
19
  [LOBE_USER_ID]: state.user?.id || '',
27
20
  [OPENAI_API_KEY_HEADER_KEY]: keyVaults.apiKey || '',
28
21
  [OPENAI_END_POINT]: keyVaults.baseURL || '',
@@ -58,6 +58,9 @@ describe('call_llm executor', () => {
58
58
  sessionId: 'test-session',
59
59
  topicId: 'test-topic',
60
60
  }),
61
+ expect.objectContaining({
62
+ operationId: expect.any(String),
63
+ }),
61
64
  );
62
65
  });
63
66
 
@@ -229,6 +232,9 @@ describe('call_llm executor', () => {
229
232
  expect.objectContaining({
230
233
  parentId: 'msg_payload_parent',
231
234
  }),
235
+ expect.objectContaining({
236
+ operationId: expect.any(String),
237
+ }),
232
238
  );
233
239
  });
234
240
 
@@ -262,6 +268,9 @@ describe('call_llm executor', () => {
262
268
  expect.objectContaining({
263
269
  parentId: 'msg_context_parent',
264
270
  }),
271
+ expect.objectContaining({
272
+ operationId: expect.any(String),
273
+ }),
265
274
  );
266
275
  });
267
276
  });
@@ -1061,6 +1070,9 @@ describe('call_llm executor', () => {
1061
1070
  model: 'claude-3-opus',
1062
1071
  provider: 'anthropic',
1063
1072
  }),
1073
+ expect.objectContaining({
1074
+ operationId: expect.any(String),
1075
+ }),
1064
1076
  );
1065
1077
  expect(mockStore.internal_fetchAIChatMessage).toHaveBeenCalledWith(
1066
1078
  expect.objectContaining({
@@ -1180,6 +1192,9 @@ describe('call_llm executor', () => {
1180
1192
  expect.objectContaining({
1181
1193
  threadId,
1182
1194
  }),
1195
+ expect.objectContaining({
1196
+ operationId: expect.any(String),
1197
+ }),
1183
1198
  );
1184
1199
  });
1185
1200
 
@@ -1211,6 +1226,9 @@ describe('call_llm executor', () => {
1211
1226
  expect.objectContaining({
1212
1227
  threadId: undefined,
1213
1228
  }),
1229
+ expect.objectContaining({
1230
+ operationId: expect.any(String),
1231
+ }),
1214
1232
  );
1215
1233
  });
1216
1234
  });
@@ -156,17 +156,22 @@ describe('call_tool executor', () => {
156
156
  });
157
157
 
158
158
  // Then
159
- expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith({
160
- content: '',
161
- groupId: 'group_789',
162
- parentId: 'msg_parent_123',
163
- plugin: toolCall,
164
- role: 'tool',
165
- sessionId: 'sess_123',
166
- threadId: undefined,
167
- tool_call_id: 'tool_call_xyz',
168
- topicId: 'topic_456',
169
- });
159
+ expect(mockStore.optimisticCreateMessage).toHaveBeenCalledWith(
160
+ expect.objectContaining({
161
+ content: '',
162
+ groupId: 'group_789',
163
+ parentId: 'msg_parent_123',
164
+ plugin: toolCall,
165
+ role: 'tool',
166
+ sessionId: 'sess_123',
167
+ threadId: undefined,
168
+ tool_call_id: 'tool_call_xyz',
169
+ topicId: 'topic_456',
170
+ }),
171
+ expect.objectContaining({
172
+ operationId: expect.any(String),
173
+ }),
174
+ );
170
175
  });
171
176
 
172
177
  it('should use assistant message groupId for tool message', async () => {
@@ -194,6 +199,9 @@ describe('call_tool executor', () => {
194
199
  expect.objectContaining({
195
200
  groupId: 'group_special',
196
201
  }),
202
+ expect.objectContaining({
203
+ operationId: expect.any(String),
204
+ }),
197
205
  );
198
206
  });
199
207
 
@@ -222,6 +230,9 @@ describe('call_tool executor', () => {
222
230
  expect.objectContaining({
223
231
  parentId: 'msg_custom_parent',
224
232
  }),
233
+ expect.objectContaining({
234
+ operationId: expect.any(String),
235
+ }),
225
236
  );
226
237
  });
227
238
 
@@ -262,6 +273,9 @@ describe('call_tool executor', () => {
262
273
  expect.objectContaining({
263
274
  plugin: toolCall,
264
275
  }),
276
+ expect.objectContaining({
277
+ operationId: expect.any(String),
278
+ }),
265
279
  );
266
280
  });
267
281
  });
@@ -1542,6 +1556,9 @@ describe('call_tool executor', () => {
1542
1556
  expect.objectContaining({
1543
1557
  groupId: undefined,
1544
1558
  }),
1559
+ expect.objectContaining({
1560
+ operationId: expect.any(String),
1561
+ }),
1545
1562
  );
1546
1563
  expect(result.events).toHaveLength(1);
1547
1564
  });
@@ -1570,6 +1587,9 @@ describe('call_tool executor', () => {
1570
1587
  expect.objectContaining({
1571
1588
  groupId: undefined,
1572
1589
  }),
1590
+ expect.objectContaining({
1591
+ operationId: expect.any(String),
1592
+ }),
1573
1593
  );
1574
1594
  });
1575
1595
 
@@ -1641,6 +1661,9 @@ describe('call_tool executor', () => {
1641
1661
  expect.objectContaining({
1642
1662
  topicId: undefined,
1643
1663
  }),
1664
+ expect.objectContaining({
1665
+ operationId: expect.any(String),
1666
+ }),
1644
1667
  );
1645
1668
  });
1646
1669
 
@@ -1679,6 +1702,9 @@ describe('call_tool executor', () => {
1679
1702
  type: 'builtin',
1680
1703
  }),
1681
1704
  }),
1705
+ expect.objectContaining({
1706
+ operationId: expect.any(String),
1707
+ }),
1682
1708
  );
1683
1709
  expect(result.events).toHaveLength(1);
1684
1710
  });
@@ -1768,6 +1794,9 @@ describe('call_tool executor', () => {
1768
1794
  expect.objectContaining({
1769
1795
  groupId: 'group_latest',
1770
1796
  }),
1797
+ expect.objectContaining({
1798
+ operationId: expect.any(String),
1799
+ }),
1771
1800
  );
1772
1801
  });
1773
1802
  });
@@ -22,6 +22,9 @@ export const expectMessageCreated = (mockStore: ChatStore, role: 'assistant' | '
22
22
  expect.objectContaining({
23
23
  role,
24
24
  }),
25
+ expect.objectContaining({
26
+ operationId: expect.any(String),
27
+ }),
25
28
  );
26
29
  };
27
30
 
@@ -51,6 +51,9 @@ describe('request_human_approve executor', () => {
51
51
  parentId: 'msg_assistant',
52
52
  groupId: assistantMessage.groupId,
53
53
  }),
54
+ expect.objectContaining({
55
+ operationId: expect.any(String),
56
+ }),
54
57
  );
55
58
  });
56
59
 
@@ -205,6 +208,9 @@ describe('request_human_approve executor', () => {
205
208
  expect.objectContaining({
206
209
  groupId: 'group_123',
207
210
  }),
211
+ expect.objectContaining({
212
+ operationId: expect.any(String),
213
+ }),
208
214
  );
209
215
  });
210
216
 
@@ -232,6 +238,9 @@ describe('request_human_approve executor', () => {
232
238
  expect.objectContaining({
233
239
  parentId: 'msg_assistant_456',
234
240
  }),
241
+ expect.objectContaining({
242
+ operationId: expect.any(String),
243
+ }),
235
244
  );
236
245
  });
237
246
  });
@@ -330,6 +339,9 @@ describe('request_human_approve executor', () => {
330
339
  tool_call_id: toolCall.id,
331
340
  pluginIntervention: { status: 'pending' },
332
341
  }),
342
+ expect.objectContaining({
343
+ operationId: expect.any(String),
344
+ }),
333
345
  );
334
346
  });
335
347
  });
@@ -539,6 +551,9 @@ describe('request_human_approve executor', () => {
539
551
  expect.objectContaining({
540
552
  parentId: 'msg_3_last',
541
553
  }),
554
+ expect.objectContaining({
555
+ operationId: expect.any(String),
556
+ }),
542
557
  );
543
558
  });
544
559
  });