@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.86](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.85...v2.0.0-next.86)
6
+
7
+ <sup>Released on **2025-11-19**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Support user abort in the agent runtime.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Support user abort in the agent runtime, closes [#10289](https://github.com/lobehub/lobe-chat/issues/10289) ([0925069](https://github.com/lobehub/lobe-chat/commit/0925069))
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.85](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.84...v2.0.0-next.85)
6
31
 
7
32
  <sup>Released on **2025-11-19**</sup>
@@ -10,14 +10,14 @@ import { ProxyUrlBuilder } from './urlBuilder';
10
10
  const logger = createLogger('modules:networkProxy:dispatcher');
11
11
 
12
12
  /**
13
- * 代理管理器
13
+ * Proxy dispatcher manager
14
14
  */
15
15
  export class ProxyDispatcherManager {
16
16
  private static isChanging = false;
17
17
  private static changeQueue: Array<() => Promise<void>> = [];
18
18
 
19
19
  /**
20
- * 应用代理设置(带并发控制)
20
+ * Apply proxy settings (with concurrency control)
21
21
  */
22
22
  static async applyProxySettings(config: NetworkProxySettings): Promise<void> {
23
23
  return new Promise((resolve, reject) => {
@@ -31,17 +31,17 @@ export class ProxyDispatcherManager {
31
31
  };
32
32
 
33
33
  if (this.isChanging) {
34
- // 如果正在切换,加入队列
34
+ // If currently switching, add to queue
35
35
  this.changeQueue.push(operation);
36
36
  } else {
37
- // 立即执行
37
+ // Execute immediately
38
38
  operation();
39
39
  }
40
40
  });
41
41
  }
42
42
 
43
43
  /**
44
- * 执行代理设置应用
44
+ * Execute proxy settings application
45
45
  */
46
46
  private static async doApplyProxySettings(config: NetworkProxySettings): Promise<void> {
47
47
  this.isChanging = true;
@@ -49,22 +49,22 @@ export class ProxyDispatcherManager {
49
49
  try {
50
50
  const currentDispatcher = getGlobalDispatcher();
51
51
 
52
- // 禁用代理,恢复默认连接
52
+ // Disable proxy, restore default connection
53
53
  if (!config.enableProxy) {
54
54
  await this.safeDestroyDispatcher(currentDispatcher);
55
- // 创建一个新的默认 Agent 来替代代理
55
+ // Create a new default Agent to replace the proxy
56
56
  setGlobalDispatcher(new Agent());
57
57
  logger.debug('Proxy disabled, reset to direct connection mode');
58
58
  return;
59
59
  }
60
60
 
61
- // 构建代理 URL
61
+ // Build proxy URL
62
62
  const proxyUrl = ProxyUrlBuilder.build(config);
63
63
 
64
- // 创建代理 agent
64
+ // Create proxy agent
65
65
  const agent = this.createProxyAgent(config.proxyType, proxyUrl);
66
66
 
67
- // 切换代理前销毁旧 dispatcher
67
+ // Destroy old dispatcher before switching proxy
68
68
  await this.safeDestroyDispatcher(currentDispatcher);
69
69
  setGlobalDispatcher(agent);
70
70
 
@@ -77,7 +77,7 @@ export class ProxyDispatcherManager {
77
77
  } finally {
78
78
  this.isChanging = false;
79
79
 
80
- // 处理队列中的下一个操作
80
+ // Process next operation in queue
81
81
  if (this.changeQueue.length > 0) {
82
82
  const nextOperation = this.changeQueue.shift();
83
83
  if (nextOperation) {
@@ -88,12 +88,12 @@ export class ProxyDispatcherManager {
88
88
  }
89
89
 
90
90
  /**
91
- * 创建代理 agent
91
+ * Create proxy agent
92
92
  */
93
93
  static createProxyAgent(proxyType: string, proxyUrl: string) {
94
94
  try {
95
95
  if (proxyType === 'socks5') {
96
- // 解析 SOCKS5 代理 URL
96
+ // Parse SOCKS5 proxy URL
97
97
  const url = new URL(proxyUrl);
98
98
  const socksProxies: SocksProxies = [
99
99
  {
@@ -109,10 +109,10 @@ export class ProxyDispatcherManager {
109
109
  },
110
110
  ];
111
111
 
112
- // 使用 fetch-socks 处理 SOCKS5 代理
112
+ // Use fetch-socks to handle SOCKS5 proxy
113
113
  return socksDispatcher(socksProxies);
114
114
  } else {
115
- // undici ProxyAgent 支持 http, https
115
+ // undici's ProxyAgent supports http, https
116
116
  return new ProxyAgent({ uri: proxyUrl });
117
117
  }
118
118
  } catch (error) {
@@ -124,7 +124,7 @@ export class ProxyDispatcherManager {
124
124
  }
125
125
 
126
126
  /**
127
- * 安全销毁 dispatcher
127
+ * Safely destroy dispatcher
128
128
  */
129
129
  private static async safeDestroyDispatcher(dispatcher: any): Promise<void> {
130
130
  try {
@@ -11,7 +11,7 @@ import { ProxyConfigValidator } from './validator';
11
11
  const logger = createLogger('modules:networkProxy:tester');
12
12
 
13
13
  /**
14
- * 代理连接测试结果
14
+ * Proxy connection test result
15
15
  */
16
16
  export interface ProxyTestResult {
17
17
  message?: string;
@@ -20,14 +20,14 @@ export interface ProxyTestResult {
20
20
  }
21
21
 
22
22
  /**
23
- * 代理连接测试器
23
+ * Proxy connection tester
24
24
  */
25
25
  export class ProxyConnectionTester {
26
- private static readonly DEFAULT_TIMEOUT = 10_000; // 10秒超时
26
+ private static readonly DEFAULT_TIMEOUT = 10_000; // 10 seconds timeout
27
27
  private static readonly DEFAULT_TEST_URL = 'https://www.google.com';
28
28
 
29
29
  /**
30
- * 测试代理连接
30
+ * Test proxy connection
31
31
  */
32
32
  static async testConnection(
33
33
  url: string = this.DEFAULT_TEST_URL,
@@ -77,13 +77,13 @@ export class ProxyConnectionTester {
77
77
  }
78
78
 
79
79
  /**
80
- * 测试指定代理配置的连接
80
+ * Test connection with specified proxy configuration
81
81
  */
82
82
  static async testProxyConfig(
83
83
  config: NetworkProxySettings,
84
84
  testUrl: string = this.DEFAULT_TEST_URL,
85
85
  ): Promise<ProxyTestResult> {
86
- // 验证配置
86
+ // Validate configuration
87
87
  const validation = ProxyConfigValidator.validate(config);
88
88
  if (!validation.isValid) {
89
89
  return {
@@ -92,12 +92,12 @@ export class ProxyConnectionTester {
92
92
  };
93
93
  }
94
94
 
95
- // 如果未启用代理,直接测试
95
+ // If proxy is not enabled, test directly
96
96
  if (!config.enableProxy) {
97
97
  return this.testConnection(testUrl);
98
98
  }
99
99
 
100
- // 创建临时代理 agent 进行测试
100
+ // Create temporary proxy agent for testing
101
101
  try {
102
102
  const proxyUrl = ProxyUrlBuilder.build(config);
103
103
  logger.debug(`Testing proxy with URL: ${proxyUrl}`);
@@ -108,7 +108,7 @@ export class ProxyConnectionTester {
108
108
  const controller = new AbortController();
109
109
  const timeoutId = setTimeout(() => controller.abort(), this.DEFAULT_TIMEOUT);
110
110
 
111
- // 临时设置代理进行测试
111
+ // Temporarily set proxy for testing
112
112
  const originalDispatcher = getGlobalDispatcher();
113
113
  setGlobalDispatcher(agent);
114
114
 
@@ -138,9 +138,9 @@ export class ProxyConnectionTester {
138
138
  clearTimeout(timeoutId);
139
139
  throw fetchError;
140
140
  } finally {
141
- // 恢复原来的 dispatcher
141
+ // Restore original dispatcher
142
142
  setGlobalDispatcher(originalDispatcher);
143
- // 清理临时创建的代理 agent
143
+ // Clean up temporary proxy agent
144
144
  if (agent && typeof agent.destroy === 'function') {
145
145
  try {
146
146
  await agent.destroy();
@@ -1,11 +1,11 @@
1
1
  import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
2
2
 
3
3
  /**
4
- * 代理 URL 构建器
4
+ * Proxy URL builder
5
5
  */
6
6
  export const ProxyUrlBuilder = {
7
7
  /**
8
- * 构建代理 URL
8
+ * Build proxy URL
9
9
  */
10
10
  build(config: NetworkProxySettings): string {
11
11
  const { proxyType, proxyServer, proxyPort, proxyRequireAuth, proxyUsername, proxyPassword } =
@@ -13,7 +13,7 @@ export const ProxyUrlBuilder = {
13
13
 
14
14
  let proxyUrl = `${proxyType}://${proxyServer}:${proxyPort}`;
15
15
 
16
- // 添加认证信息
16
+ // Add authentication information
17
17
  if (proxyRequireAuth && proxyUsername && proxyPassword) {
18
18
  const encodedUsername = encodeURIComponent(proxyUsername);
19
19
  const encodedPassword = encodeURIComponent(proxyPassword);
@@ -1,7 +1,7 @@
1
1
  import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
2
2
 
3
3
  /**
4
- * 代理配置验证结果
4
+ * Proxy configuration validation result
5
5
  */
6
6
  export interface ProxyValidationResult {
7
7
  errors: string[];
@@ -9,38 +9,38 @@ export interface ProxyValidationResult {
9
9
  }
10
10
 
11
11
  /**
12
- * 代理配置验证器
12
+ * Proxy configuration validator
13
13
  */
14
14
  export class ProxyConfigValidator {
15
15
  private static readonly SUPPORTED_TYPES = ['http', 'https', 'socks5'] as const;
16
16
  private static readonly DEFAULT_BYPASS = 'localhost,127.0.0.1,::1';
17
17
 
18
18
  /**
19
- * 验证代理配置
19
+ * Validate proxy configuration
20
20
  */
21
21
  static validate(config: NetworkProxySettings): ProxyValidationResult {
22
22
  const errors: string[] = [];
23
23
 
24
- // 如果未启用代理,跳过验证
24
+ // If proxy is not enabled, skip validation
25
25
  if (!config.enableProxy) {
26
26
  return { errors: [], isValid: true };
27
27
  }
28
28
 
29
- // 验证代理类型
29
+ // Validate proxy type
30
30
  if (!this.SUPPORTED_TYPES.includes(config.proxyType as any)) {
31
31
  errors.push(
32
32
  `Unsupported proxy type: ${config.proxyType}. Supported types: ${this.SUPPORTED_TYPES.join(', ')}`,
33
33
  );
34
34
  }
35
35
 
36
- // 验证代理服务器
36
+ // Validate proxy server
37
37
  if (!config.proxyServer?.trim()) {
38
38
  errors.push('Proxy server is required when proxy is enabled');
39
39
  } else if (!this.isValidHost(config.proxyServer)) {
40
40
  errors.push('Invalid proxy server format');
41
41
  }
42
42
 
43
- // 验证代理端口
43
+ // Validate proxy port
44
44
  if (!config.proxyPort?.trim()) {
45
45
  errors.push('Proxy port is required when proxy is enabled');
46
46
  } else {
@@ -50,7 +50,7 @@ export class ProxyConfigValidator {
50
50
  }
51
51
  }
52
52
 
53
- // 验证认证信息
53
+ // Validate authentication information
54
54
  if (config.proxyRequireAuth) {
55
55
  if (!config.proxyUsername?.trim()) {
56
56
  errors.push('Proxy username is required when authentication is enabled');
@@ -67,10 +67,10 @@ export class ProxyConfigValidator {
67
67
  }
68
68
 
69
69
  /**
70
- * 验证主机名格式
70
+ * Validate host format
71
71
  */
72
72
  private static isValidHost(host: string): boolean {
73
- // 简单的主机名验证(IP 地址或域名)
73
+ // Simple host validation (IP address or domain name)
74
74
  const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
75
75
  const domainRegex =
76
76
  /^[\dA-Za-z]([\dA-Za-z-]*[\dA-Za-z])?(\.[\dA-Za-z]([\dA-Za-z-]*[\dA-Za-z])?)*$/;
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Support user abort in the agent runtime."
6
+ ]
7
+ },
8
+ "date": "2025-11-19",
9
+ "version": "2.0.0-next.86"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.85",
3
+ "version": "2.0.0-next.86",
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",
@@ -24,11 +24,16 @@ import {
24
24
  */
25
25
  export class AgentRuntime {
26
26
  private executors: Record<AgentInstruction['type'], InstructionExecutor>;
27
+ private operationId?: string;
28
+ private getOperation?: RuntimeConfig['getOperation'];
27
29
 
28
30
  constructor(
29
31
  private agent: Agent,
30
32
  private config: RuntimeConfig = {},
31
33
  ) {
34
+ this.operationId = config.operationId;
35
+ this.getOperation = config.getOperation;
36
+
32
37
  // Build executors with priority: agent.executors > config.executors > built-in
33
38
  this.executors = {
34
39
  call_llm: this.createCallLLMExecutor(),
@@ -44,6 +49,28 @@ export class AgentRuntime {
44
49
  };
45
50
  }
46
51
 
52
+ /**
53
+ * Get operation context (sessionId, topicId, etc.)
54
+ * Returns the business context captured by the operation
55
+ */
56
+ getContext() {
57
+ if (!this.operationId || !this.getOperation) {
58
+ return undefined;
59
+ }
60
+ return this.getOperation(this.operationId).context;
61
+ }
62
+
63
+ /**
64
+ * Get operation abort controller
65
+ * Returns the AbortController for cancellation
66
+ */
67
+ getAbortController(): AbortController | undefined {
68
+ if (!this.operationId || !this.getOperation) {
69
+ return undefined;
70
+ }
71
+ return this.getOperation(this.operationId).abortController;
72
+ }
73
+
47
74
  /**
48
75
  * Executes a single step of the Plan -> Execute loop.
49
76
  * @param state - Current agent state
@@ -194,6 +221,7 @@ export class AgentRuntime {
194
221
  approvedToolCall: ChatToolPayload,
195
222
  ): Promise<{ events: AgentEvent[]; newState: AgentState; nextContext?: AgentRuntimeContext }> {
196
223
  const context: AgentRuntimeContext = {
224
+ operationId: this.operationId,
197
225
  payload: { approvedToolCall },
198
226
  phase: 'human_approved_tool',
199
227
  session: this.createSessionContext(state),
@@ -289,10 +317,11 @@ export class AgentRuntime {
289
317
  }
290
318
 
291
319
  // Otherwise, just return the resumed state
320
+ const initialContext = this.createInitialContext(newState);
292
321
  return {
293
322
  events: [resumeEvent],
294
323
  newState,
295
- nextContext: this.createInitialContext(newState),
324
+ nextContext: initialContext,
296
325
  };
297
326
  }
298
327
 
@@ -439,6 +468,7 @@ export class AgentRuntime {
439
468
 
440
469
  // Provide next context based on LLM result
441
470
  const nextContext: AgentRuntimeContext = {
471
+ operationId: this.operationId,
442
472
  payload: {
443
473
  hasToolCalls: toolCalls.length > 0,
444
474
  result: { content: assistantContent, tool_calls: toolCalls },
@@ -511,6 +541,7 @@ export class AgentRuntime {
511
541
 
512
542
  // Provide next context for tool result
513
543
  const nextContext: AgentRuntimeContext = {
544
+ operationId: this.operationId,
514
545
  payload: {
515
546
  result,
516
547
  toolCall,
@@ -726,6 +757,7 @@ export class AgentRuntime {
726
757
  events: allEvents,
727
758
  newState,
728
759
  nextContext: {
760
+ operationId: this.operationId,
729
761
  payload: {
730
762
  parentMessageId: lastParentMessageId,
731
763
  toolCount: results.length,
@@ -792,6 +824,7 @@ export class AgentRuntime {
792
824
  events: [warningEvent],
793
825
  newState,
794
826
  nextContext: {
827
+ operationId: this.operationId,
795
828
  payload: { error: warningEvent.error, isCostWarning: true },
796
829
  phase: 'error' as const,
797
830
  session: this.createSessionContext(newState),
@@ -821,6 +854,7 @@ export class AgentRuntime {
821
854
 
822
855
  if (lastMessage?.role === 'user') {
823
856
  return {
857
+ operationId: this.operationId,
824
858
  payload: {
825
859
  isFirstMessage: state.messages.length === 1,
826
860
  message: lastMessage,
@@ -831,6 +865,7 @@ export class AgentRuntime {
831
865
  }
832
866
 
833
867
  return {
868
+ operationId: this.operationId,
834
869
  payload: undefined,
835
870
  phase: 'init',
836
871
  session: this.createSessionContext(state),
@@ -61,6 +61,7 @@ export interface AgentEventHumanSelectRequired {
61
61
  export type FinishReason =
62
62
  | 'completed' // Normal completion
63
63
  | 'user_requested' // User requested to end
64
+ | 'user_aborted' // User abort
64
65
  | 'max_steps_exceeded' // Reached maximum steps limit
65
66
  | 'cost_limit_exceeded' // Reached cost limit
66
67
  | 'timeout' // Execution timeout
@@ -42,6 +42,22 @@ export interface GeneralAgentCallToolsBatchResultPayload {
42
42
  toolResults: GeneralAgentCallToolResultPayload[];
43
43
  }
44
44
 
45
+ export interface GeneralAgentHumanAbortPayload {
46
+ /** Whether there are pending tool calls */
47
+ hasToolsCalling?: boolean;
48
+ /** Parent message ID (assistant message) */
49
+ parentMessageId: string;
50
+ /** Reason for the abort */
51
+ reason: string;
52
+ /** LLM result including content and tool_calls */
53
+ result?: {
54
+ content: string;
55
+ tool_calls?: any[];
56
+ };
57
+ /** Pending tool calls that need to be cancelled */
58
+ toolsCalling?: ChatToolPayload[];
59
+ }
60
+
45
61
  export interface GeneralAgentConfig {
46
62
  agentConfig?: {
47
63
  [key: string]: any;
@@ -24,6 +24,7 @@ export interface AgentRuntimeContext {
24
24
  | 'tools_batch_result'
25
25
  | 'human_response'
26
26
  | 'human_approved_tool'
27
+ | 'human_abort'
27
28
  | 'error';
28
29
 
29
30
  /** Session info (kept for backward compatibility, will be optional in the future) */
@@ -104,6 +105,22 @@ export interface CallingToolPayload {
104
105
  type: 'mcp' | 'default' | 'markdown' | 'standalone';
105
106
  }
106
107
 
108
+ export interface HumanAbortPayload {
109
+ /** Whether there are pending tool calls */
110
+ hasToolsCalling?: boolean;
111
+ /** Parent message ID (assistant message) */
112
+ parentMessageId: string;
113
+ /** Reason for the abort */
114
+ reason: string;
115
+ /** LLM result including content and tool_calls */
116
+ result?: {
117
+ content: string;
118
+ tool_calls?: any[];
119
+ };
120
+ /** Pending tool calls that need to be cancelled */
121
+ toolsCalling?: ChatToolPayload[];
122
+ }
123
+
107
124
  export interface AgentInstructionCallLlm {
108
125
  payload: any;
109
126
  type: 'call_llm';
@@ -154,6 +171,18 @@ export interface AgentInstructionFinish {
154
171
  type: 'finish';
155
172
  }
156
173
 
174
+ export interface AgentInstructionResolveAbortedTools {
175
+ payload: {
176
+ /** Parent message ID (assistant message) */
177
+ parentMessageId: string;
178
+ /** Reason for the abort */
179
+ reason?: string;
180
+ /** Tool calls that need to be resolved/cancelled */
181
+ toolsCalling: ChatToolPayload[];
182
+ };
183
+ type: 'resolve_aborted_tools';
184
+ }
185
+
157
186
  /**
158
187
  * A serializable instruction object that the "Agent" (Brain) returns
159
188
  * to the "AgentRuntime" (Engine) to execute.
@@ -165,4 +194,5 @@ export type AgentInstruction =
165
194
  | AgentInstructionRequestHumanPrompt
166
195
  | AgentInstructionRequestHumanSelect
167
196
  | AgentInstructionRequestHumanApprove
197
+ | AgentInstructionResolveAbortedTools
168
198
  | AgentInstructionFinish;
@@ -15,4 +15,11 @@ export type InstructionExecutor = (
15
15
  export interface RuntimeConfig {
16
16
  /** Custom executors for specific instruction types */
17
17
  executors?: Partial<Record<AgentInstruction['type'], InstructionExecutor>>;
18
+ /** Function to get operation context and abort controller */
19
+ getOperation?: (operationId: string) => {
20
+ abortController: AbortController;
21
+ context: Record<string, any>;
22
+ };
23
+ /** Operation ID for tracking this runtime instance */
24
+ operationId?: string;
18
25
  }
@@ -108,10 +108,13 @@ export interface ModelPerformance {
108
108
  export interface MessageMetadata extends ModelUsage, ModelPerformance {
109
109
  activeBranchIndex?: number;
110
110
  activeColumn?: boolean;
111
+ finishType?: string;
111
112
  /**
112
113
  * Message collapse state
113
114
  * true: collapsed, false/undefined: expanded
114
115
  */
115
116
  collapsed?: boolean;
116
117
  compare?: boolean;
118
+ usage?: ModelUsage;
119
+ performance?: ModelPerformance;
117
120
  }
@@ -7,12 +7,12 @@ import { LobeToolRenderType } from '../../tool';
7
7
  // ToolIntervention must be defined first to avoid circular dependency
8
8
  export interface ToolIntervention {
9
9
  rejectedReason?: string;
10
- status?: 'pending' | 'approved' | 'rejected' | 'none';
10
+ status?: 'pending' | 'approved' | 'rejected' | 'aborted' | 'none';
11
11
  }
12
12
 
13
13
  export const ToolInterventionSchema = z.object({
14
14
  rejectedReason: z.string().optional(),
15
- status: z.enum(['pending', 'approved', 'rejected', 'none']).optional(),
15
+ status: z.enum(['pending', 'approved', 'rejected', 'aborted', 'none']).optional(),
16
16
  });
17
17
 
18
18
  export interface ChatPluginPayload {
@@ -48,6 +48,12 @@ export interface UniformSearchResponse {
48
48
  }
49
49
 
50
50
  export interface SearchServiceImpl {
51
- crawlPages(params: CrawlMultiPagesQuery): Promise<{ results: CrawlUniformResult[] }>;
52
- webSearch(params: SearchQuery): Promise<UniformSearchResponse>;
51
+ crawlPages(
52
+ params: CrawlMultiPagesQuery,
53
+ options?: { signal?: AbortSignal },
54
+ ): Promise<{ results: CrawlUniformResult[] }>;
55
+ webSearch(
56
+ params: SearchQuery,
57
+ options?: { signal?: AbortSignal },
58
+ ): Promise<UniformSearchResponse>;
53
59
  }
@@ -9,7 +9,7 @@ import { Flexbox } from 'react-layout-kit';
9
9
  import { ActionKeys } from '@/features/ChatInput/ActionBar/config';
10
10
  import { useInitAgentConfig } from '@/hooks/useInitAgentConfig';
11
11
  import { useChatStore } from '@/store/chat';
12
- import { messageStateSelectors } from '@/store/chat/selectors';
12
+ import { operationSelectors } from '@/store/chat/selectors';
13
13
 
14
14
  import ActionBar from './ActionBar';
15
15
  import Files from './Files';
@@ -37,7 +37,7 @@ const MobileChatInput = memo(() => {
37
37
  const { isLoading } = useInitAgentConfig();
38
38
 
39
39
  const [loading, value, onInput, onStop] = useChatStore((s) => [
40
- messageStateSelectors.isAIGenerating(s),
40
+ operationSelectors.isAgentRuntimeRunning(s),
41
41
  s.inputMessage,
42
42
  s.updateMessageInput,
43
43
  s.stopGenerateMessage,
@@ -6,7 +6,12 @@ import { useGeminiChineseWarning } from '@/hooks/useGeminiChineseWarning';
6
6
  import { getAgentStoreState } from '@/store/agent';
7
7
  import { agentSelectors } from '@/store/agent/selectors';
8
8
  import { useChatStore } from '@/store/chat';
9
- import { chatSelectors, messageStateSelectors, topicSelectors } from '@/store/chat/selectors';
9
+ import {
10
+ chatSelectors,
11
+ messageStateSelectors,
12
+ operationSelectors,
13
+ topicSelectors,
14
+ } from '@/store/chat/selectors';
10
15
  import { fileChatSelectors, useFileStore } from '@/store/file';
11
16
  import { getUserStoreState } from '@/store/user';
12
17
 
@@ -34,7 +39,7 @@ export const useSendMessage = () => {
34
39
 
35
40
  const send = useCallback(async (params: UseSendMessageParams = {}) => {
36
41
  const store = useChatStore.getState();
37
- if (messageStateSelectors.isAIGenerating(store)) return;
42
+ if (operationSelectors.isAgentRuntimeRunning(store)) return;
38
43
 
39
44
  // if uploading file or send button is disabled by message, then we should not send the message
40
45
  const isUploadingFiles = fileChatSelectors.isUploadingFiles(useFileStore.getState());