@lobehub/lobehub 2.0.0-next.48 → 2.0.0-next.49

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 (90) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/changelog/v1.json +12 -0
  5. package/locales/ar/chat.json +1 -0
  6. package/locales/ar/topic.json +1 -0
  7. package/locales/bg-BG/chat.json +1 -0
  8. package/locales/bg-BG/topic.json +1 -0
  9. package/locales/de-DE/chat.json +1 -0
  10. package/locales/de-DE/topic.json +1 -0
  11. package/locales/en-US/chat.json +1 -0
  12. package/locales/en-US/topic.json +1 -0
  13. package/locales/es-ES/chat.json +1 -0
  14. package/locales/es-ES/topic.json +1 -0
  15. package/locales/fa-IR/chat.json +1 -0
  16. package/locales/fa-IR/topic.json +1 -0
  17. package/locales/fr-FR/chat.json +1 -0
  18. package/locales/fr-FR/topic.json +1 -0
  19. package/locales/it-IT/chat.json +1 -0
  20. package/locales/it-IT/topic.json +1 -0
  21. package/locales/ja-JP/chat.json +1 -0
  22. package/locales/ja-JP/topic.json +1 -0
  23. package/locales/ko-KR/chat.json +1 -0
  24. package/locales/ko-KR/topic.json +1 -0
  25. package/locales/nl-NL/chat.json +1 -0
  26. package/locales/nl-NL/topic.json +1 -0
  27. package/locales/pl-PL/chat.json +1 -0
  28. package/locales/pl-PL/topic.json +1 -0
  29. package/locales/pt-BR/chat.json +1 -0
  30. package/locales/pt-BR/topic.json +1 -0
  31. package/locales/ru-RU/chat.json +1 -0
  32. package/locales/ru-RU/topic.json +1 -0
  33. package/locales/tr-TR/chat.json +1 -0
  34. package/locales/tr-TR/topic.json +1 -0
  35. package/locales/vi-VN/chat.json +1 -0
  36. package/locales/vi-VN/topic.json +1 -0
  37. package/locales/zh-CN/chat.json +1 -0
  38. package/locales/zh-CN/discover.json +1 -1
  39. package/locales/zh-CN/topic.json +1 -0
  40. package/locales/zh-TW/chat.json +1 -0
  41. package/locales/zh-TW/topic.json +1 -0
  42. package/package.json +9 -3
  43. package/packages/agent-runtime/src/core/InterventionChecker.ts +5 -16
  44. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +27 -80
  45. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +32 -13
  46. package/packages/agent-runtime/src/core/runtime.ts +7 -3
  47. package/packages/agent-runtime/src/types/event.ts +2 -1
  48. package/packages/agent-runtime/src/types/generalAgent.ts +1 -0
  49. package/packages/agent-runtime/src/types/instruction.ts +3 -2
  50. package/packages/agent-runtime/src/types/state.ts +3 -1
  51. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +4 -1
  52. package/packages/database/src/models/message.ts +3 -0
  53. package/packages/obervability-otel/src/node.ts +15 -1
  54. package/packages/types/src/message/common/base.ts +2 -2
  55. package/packages/types/src/message/common/tools.ts +16 -10
  56. package/packages/types/src/message/ui/chat.ts +7 -1
  57. package/packages/types/src/tool/intervention.ts +2 -3
  58. package/packages/types/src/user/settings/tool.ts +15 -28
  59. package/renovate.json +28 -11
  60. package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +1 -1
  61. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/Actions.tsx +1 -1
  62. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +20 -15
  63. package/src/features/Conversation/Messages/Group/GroupContext.tsx +15 -0
  64. package/src/features/Conversation/Messages/Group/Tool/Inspector/BuiltinPluginTitle.tsx +2 -4
  65. package/src/features/Conversation/Messages/Group/Tool/Inspector/ToolTitle.tsx +3 -5
  66. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +19 -7
  67. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/index.tsx +14 -12
  68. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ApprovalActions.tsx +143 -0
  69. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/KeyValueEditor.tsx +213 -0
  70. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +134 -0
  71. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +99 -0
  72. package/src/features/Conversation/Messages/Group/Tool/Render/RejectedResponse.tsx +45 -0
  73. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +23 -1
  74. package/src/features/Conversation/Messages/Group/Tool/index.tsx +42 -18
  75. package/src/features/Conversation/Messages/Group/Tools.tsx +3 -1
  76. package/src/locales/default/chat.ts +22 -0
  77. package/src/locales/default/common.ts +1 -0
  78. package/src/locales/default/topic.ts +1 -0
  79. package/src/server/routers/lambda/message.ts +4 -1
  80. package/src/server/services/message/index.ts +13 -0
  81. package/src/services/message/index.ts +17 -2
  82. package/src/store/chat/agents/GeneralChatAgent.ts +141 -24
  83. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +605 -0
  84. package/src/store/chat/agents/createAgentExecutors.ts +144 -26
  85. package/src/store/chat/agents/createToolEngine.ts +22 -0
  86. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +106 -0
  87. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +54 -26
  88. package/src/store/chat/slices/message/reducer.ts +2 -1
  89. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +26 -1
  90. package/src/store/user/slices/settings/action.ts +15 -0
@@ -13,6 +13,7 @@ import {
13
13
  } from '@lobechat/agent-runtime';
14
14
  import type { ChatToolPayload, CreateMessageParams } from '@lobechat/types';
15
15
  import debug from 'debug';
16
+ import pMap from 'p-map';
16
17
 
17
18
  import type { ChatStore } from '@/store/chat/store';
18
19
 
@@ -43,6 +44,7 @@ export const createAgentExecutors = (context: {
43
44
  }) => {
44
45
  let shouldSkipCreateMessage = context.skipCreateFirstMessage;
45
46
 
47
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
46
48
  const executors: Partial<Record<AgentInstruction['type'], InstructionExecutor>> = {
47
49
  /**
48
50
  * Custom call_llm executor
@@ -239,39 +241,56 @@ export const createAgentExecutors = (context: {
239
241
  // Find the last assistant message (should be created by call_llm)
240
242
  const assistantMessage = latestMessages.findLast((m) => m.role === 'assistant');
241
243
 
242
- // Always create new tool message (following server-side pattern)
243
- // This ensures consistency and avoids duplicate execution
244
- log(
245
- '[%s][call_tool] Creating tool message for tool_call_id: %s',
246
- sessionLogId,
247
- chatToolPayload.id,
248
- );
244
+ let toolMessageId: string;
249
245
 
250
- const toolMessageParams: CreateMessageParams = {
251
- content: '',
252
- groupId: assistantMessage?.groupId,
253
- parentId: payload.parentMessageId,
254
- plugin: chatToolPayload,
255
- role: 'tool',
256
- sessionId: context.get().activeId,
257
- threadId: context.params.threadId,
258
- tool_call_id: chatToolPayload.id,
259
- topicId: context.get().activeTopicId,
260
- };
261
-
262
- const createResult = await context.get().optimisticCreateMessage(toolMessageParams);
246
+ if (payload.skipCreateToolMessage) {
247
+ // Reuse existing tool message (resumption mode)
248
+ toolMessageId = payload.parentMessageId;
249
+ // Check if tool message already exists (e.g., from human approval flow)
250
+ const existingToolMessage = latestMessages.find((m) => m.id === toolMessageId)!;
263
251
 
264
- if (!createResult) {
265
252
  log(
266
- '[%s][call_tool] ERROR: Failed to create tool message for tool_call_id: %s',
253
+ '[%s][call_tool] Resuming with existing tool message: %s (status: %s)',
254
+ sessionLogId,
255
+ toolMessageId,
256
+ existingToolMessage.pluginIntervention?.status,
257
+ );
258
+ } else {
259
+ // Create new tool message (normal mode)
260
+ log(
261
+ '[%s][call_tool] Creating tool message for tool_call_id: %s',
267
262
  sessionLogId,
268
263
  chatToolPayload.id,
269
264
  );
270
- throw new Error(`Failed to create tool message for tool_call_id: ${chatToolPayload.id}`);
271
- }
272
265
 
273
- const toolMessageId = createResult.id;
274
- log('[%s][call_tool] Created tool message, id: %s', sessionLogId, toolMessageId);
266
+ const toolMessageParams: CreateMessageParams = {
267
+ content: '',
268
+ groupId: assistantMessage?.groupId,
269
+ parentId: payload.parentMessageId,
270
+ plugin: chatToolPayload,
271
+ role: 'tool',
272
+ sessionId: context.get().activeId,
273
+ threadId: context.params.threadId,
274
+ tool_call_id: chatToolPayload.id,
275
+ topicId: context.get().activeTopicId,
276
+ };
277
+
278
+ const createResult = await context.get().optimisticCreateMessage(toolMessageParams);
279
+
280
+ if (!createResult) {
281
+ log(
282
+ '[%s][call_tool] ERROR: Failed to create tool message for tool_call_id: %s',
283
+ sessionLogId,
284
+ chatToolPayload.id,
285
+ );
286
+ throw new Error(
287
+ `Failed to create tool message for tool_call_id: ${chatToolPayload.id}`,
288
+ );
289
+ }
290
+
291
+ toolMessageId = createResult.id;
292
+ log('[%s][call_tool] Created tool message, id: %s', sessionLogId, toolMessageId);
293
+ }
275
294
 
276
295
  // Execute tool
277
296
  log('[%s][call_tool] Executing tool %s ...', sessionLogId, toolName);
@@ -371,6 +390,105 @@ export const createAgentExecutors = (context: {
371
390
  }
372
391
  },
373
392
 
393
+ /** Create human approve executor */
394
+ request_human_approve: async (instruction, state) => {
395
+ const { pendingToolsCalling, reason, skipCreateToolMessage } = instruction as Extract<
396
+ AgentInstruction,
397
+ { type: 'request_human_approve' }
398
+ >;
399
+ const newState = structuredClone(state);
400
+ const events: AgentEvent[] = [];
401
+ const sessionLogId = `${state.sessionId}:${state.stepCount}`;
402
+
403
+ log(
404
+ '[%s][request_human_approve] Executor start, pending tools count: %d, reason: %s',
405
+ sessionLogId,
406
+ pendingToolsCalling.length,
407
+ reason || 'human_intervention_required',
408
+ );
409
+
410
+ // Update state to waiting_for_human
411
+ newState.lastModified = new Date().toISOString();
412
+ newState.status = 'waiting_for_human';
413
+ newState.pendingToolsCalling = pendingToolsCalling;
414
+
415
+ // Get assistant message to extract groupId and parentId
416
+ const latestMessages = context.get().dbMessagesMap[context.messageKey] || [];
417
+ const assistantMessage = latestMessages.findLast((m) => m.role === 'assistant');
418
+
419
+ if (!assistantMessage) {
420
+ log('[%s][request_human_approve] ERROR: No assistant message found', sessionLogId);
421
+ throw new Error('No assistant message found for intervention');
422
+ }
423
+
424
+ log(
425
+ '[%s][request_human_approve] Found assistant message: %s',
426
+ sessionLogId,
427
+ assistantMessage.id,
428
+ );
429
+
430
+ if (skipCreateToolMessage) {
431
+ // Resumption mode: Tool messages already exist, just verify them
432
+ log('[%s][request_human_approve] Resuming with existing tool messages', sessionLogId);
433
+ } else {
434
+ // Create tool messages for each pending tool call with intervention status
435
+ await pMap(pendingToolsCalling, async (toolPayload) => {
436
+ const toolName = `${toolPayload.identifier}/${toolPayload.apiName}`;
437
+ log(
438
+ '[%s][request_human_approve] Creating tool message for %s with tool_call_id: %s',
439
+ sessionLogId,
440
+ toolName,
441
+ toolPayload.id,
442
+ );
443
+
444
+ const toolMessageParams: CreateMessageParams = {
445
+ content: '',
446
+ groupId: assistantMessage.groupId,
447
+ parentId: assistantMessage.id,
448
+ plugin: {
449
+ ...toolPayload,
450
+ },
451
+ pluginIntervention: { status: 'pending' },
452
+ role: 'tool',
453
+ sessionId: context.get().activeId,
454
+ threadId: context.params.threadId,
455
+ tool_call_id: toolPayload.id,
456
+ topicId: context.get().activeTopicId,
457
+ };
458
+
459
+ const createResult = await context.get().optimisticCreateMessage(toolMessageParams);
460
+
461
+ if (!createResult) {
462
+ log(
463
+ '[%s][request_human_approve] ERROR: Failed to create tool message for %s',
464
+ sessionLogId,
465
+ toolName,
466
+ );
467
+ throw new Error(`Failed to create tool message for ${toolName}`);
468
+ }
469
+
470
+ log(
471
+ '[%s][request_human_approve] Created tool message: %s for %s',
472
+ sessionLogId,
473
+ createResult.id,
474
+ toolName,
475
+ );
476
+ });
477
+ }
478
+
479
+ log(
480
+ '[%s][request_human_approve] All tool messages created, emitting human_approve_required event',
481
+ sessionLogId,
482
+ );
483
+
484
+ events.push({
485
+ pendingToolsCalling,
486
+ sessionId: newState.sessionId,
487
+ type: 'human_approve_required',
488
+ });
489
+
490
+ return { events, newState };
491
+ },
374
492
  /**
375
493
  * Finish executor
376
494
  * Completes the runtime execution
@@ -0,0 +1,22 @@
1
+ import { WorkingModel } from '@lobechat/types';
2
+
3
+ import { getSearchConfig } from '@/helpers/getSearchConfig';
4
+ import { createToolsEngine } from '@/helpers/toolEngineering';
5
+ import { WebBrowsingManifest } from '@/tools/web-browsing';
6
+
7
+ export const createAgentToolsEngine = (workingModel: WorkingModel) =>
8
+ createToolsEngine({
9
+ // Add WebBrowsingManifest as default tool
10
+ defaultToolIds: [WebBrowsingManifest.identifier],
11
+ // Create search-aware enableChecker for this request
12
+ enableChecker: ({ pluginId }) => {
13
+ // For WebBrowsingManifest, apply search logic
14
+ if (pluginId === WebBrowsingManifest.identifier) {
15
+ const searchConfig = getSearchConfig(workingModel.model, workingModel.provider);
16
+ return searchConfig.useApplicationBuiltinSearchTool;
17
+ }
18
+
19
+ // For all other plugins, enable by default
20
+ return true;
21
+ },
22
+ });
@@ -1,13 +1,19 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
2
  // Disable the auto sort key eslint rule to make the code more logic and readable
3
+ import { AgentRuntime, type AgentRuntimeContext } from '@lobechat/agent-runtime';
3
4
  import { MESSAGE_CANCEL_FLAT } from '@lobechat/const';
4
5
  import { produce } from 'immer';
5
6
  import { StateCreator } from 'zustand/vanilla';
6
7
 
8
+ import { getAgentStoreState } from '@/store/agent';
9
+ import { agentSelectors } from '@/store/agent/slices/chat';
10
+ import { createAgentToolsEngine } from '@/store/chat/agents/createToolEngine';
7
11
  import { ChatStore } from '@/store/chat/store';
8
12
  import { setNamespace } from '@/utils/storeDebug';
9
13
 
14
+ import { displayMessageSelectors } from '../../../selectors';
10
15
  import { messageMapKey } from '../../../utils/messageMapKey';
16
+ import { dbMessageSelectors } from '../../message/selectors';
11
17
  import { MainSendMessageOperation } from '../initialState';
12
18
 
13
19
  const n = setNamespace('ai');
@@ -32,6 +38,14 @@ export interface ConversationControlAction {
32
38
  * Switches to a different branch of a message
33
39
  */
34
40
  switchMessageBranch: (messageId: string, branchIndex: number) => Promise<void>;
41
+ /**
42
+ * Approve tool intervention
43
+ */
44
+ approveToolCalling: (toolMessageId: string, assistantGroupId: string) => Promise<void>;
45
+ /**
46
+ * Reject tool intervention
47
+ */
48
+ rejectToolCalling: (messageId: string, reason?: string) => Promise<void>;
35
49
  /**
36
50
  * Toggle sendMessage operation state
37
51
  */
@@ -131,6 +145,98 @@ export const conversationControl: StateCreator<
131
145
  return undefined;
132
146
  }
133
147
  },
148
+ approveToolCalling: async (toolMessageId) => {
149
+ const { activeId, activeTopicId, activeThreadId, internal_execAgentRuntime } = get();
150
+
151
+ // 1. Get tool message and verify it exists
152
+ const toolMessage = dbMessageSelectors.getDbMessageById(toolMessageId)(get());
153
+ if (!toolMessage) return;
154
+
155
+ // 2. Update intervention status to approved
156
+ await get().optimisticUpdatePlugin(toolMessageId, { intervention: { status: 'approved' } });
157
+
158
+ // 3. Get current messages for state construction
159
+ const currentMessages = displayMessageSelectors.mainAIChats(get());
160
+
161
+ // 4. Get agent configuration and tools information
162
+ const agentStoreState = getAgentStoreState();
163
+ const agentConfigData = agentSelectors.currentAgentConfig(agentStoreState);
164
+
165
+ const toolsEngine = createAgentToolsEngine({
166
+ model: agentConfigData.model,
167
+ provider: agentConfigData.provider!,
168
+ });
169
+ const { enabledToolIds } = toolsEngine.generateToolsDetailed({
170
+ model: agentConfigData.model,
171
+ provider: agentConfigData.provider!,
172
+ toolIds: agentConfigData.plugins,
173
+ });
174
+ const toolManifestMap = Object.fromEntries(
175
+ toolsEngine.getEnabledPluginManifests(enabledToolIds).entries(),
176
+ );
177
+
178
+ // 5. Construct AgentState
179
+ const state = AgentRuntime.createInitialState({
180
+ sessionId: activeId,
181
+ messages: currentMessages,
182
+ maxSteps: 400,
183
+ metadata: {
184
+ sessionId: activeId,
185
+ topicId: activeTopicId,
186
+ threadId: activeThreadId,
187
+ },
188
+ toolManifestMap,
189
+ });
190
+
191
+ console.log('toolMessage:', toolMessage);
192
+ // 6. Construct AgentRuntimeContext with 'human_approved_tool' phase
193
+ const context: AgentRuntimeContext = {
194
+ phase: 'human_approved_tool',
195
+ payload: {
196
+ approvedToolCall: toolMessage.plugin,
197
+ parentMessageId: toolMessageId,
198
+ skipCreateToolMessage: true,
199
+ },
200
+ session: {
201
+ sessionId: activeId,
202
+ messageCount: currentMessages.length,
203
+ status: 'running',
204
+ stepCount: 0,
205
+ },
206
+ };
207
+
208
+ // 7. Execute agent runtime from tool message position
209
+ try {
210
+ await internal_execAgentRuntime({
211
+ messages: currentMessages,
212
+ parentMessageId: toolMessageId, // Start from tool message
213
+ parentMessageType: 'tool', // Type is 'tool'
214
+ threadId: activeThreadId,
215
+ initialState: state,
216
+ initialContext: context,
217
+ });
218
+ } catch (error) {
219
+ console.error('[approveToolCalling] Error executing agent runtime:', error);
220
+ }
221
+ },
222
+
223
+ rejectToolCalling: async (messageId, reason) => {
224
+ const toolMessage = dbMessageSelectors.getDbMessageById(messageId)(get());
225
+ if (!toolMessage) return;
226
+
227
+ // Optimistic update - update status to rejected and save reason
228
+ const intervention = {
229
+ rejectedReason: reason,
230
+ status: 'rejected',
231
+ } as const;
232
+ await get().optimisticUpdatePlugin(toolMessage.id, { intervention });
233
+
234
+ const toolContent = !!reason
235
+ ? `User reject this tool calling with reason: ${reason}`
236
+ : 'User reject this tool calling without reason';
237
+
238
+ await get().optimisticUpdateMessageContent(messageId, toolContent);
239
+ },
134
240
 
135
241
  internal_updateSendMessageOperation: (key, value, actionName) => {
136
242
  const operationKey = typeof key === 'string' ? key : messageMapKey(key.sessionId, key.topicId);
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
2
  // Disable the auto sort key eslint rule to make the code more logic and readable
3
- import { AgentRuntime, type AgentRuntimeContext } from '@lobechat/agent-runtime';
3
+ import { AgentRuntime, type AgentRuntimeContext, type AgentState } from '@lobechat/agent-runtime';
4
4
  import { isDesktop } from '@lobechat/const';
5
5
  import { knowledgeBaseQAPrompts } from '@lobechat/prompts';
6
6
  import {
@@ -23,6 +23,7 @@ import { agentChatConfigSelectors, agentSelectors } from '@/store/agent/selector
23
23
  import { getAgentStoreState } from '@/store/agent/store';
24
24
  import { GeneralChatAgent } from '@/store/chat/agents/GeneralChatAgent';
25
25
  import { createAgentExecutors } from '@/store/chat/agents/createAgentExecutors';
26
+ import { createAgentToolsEngine } from '@/store/chat/agents/createToolEngine';
26
27
  import { ChatStore } from '@/store/chat/store';
27
28
  import { getFileStoreState } from '@/store/file/store';
28
29
  import { setNamespace } from '@/utils/storeDebug';
@@ -88,6 +89,14 @@ export interface StreamingExecutorAction {
88
89
  skipCreateFirstMessage?: boolean;
89
90
  traceId?: string;
90
91
  ragMetadata?: { ragQueryId: string; fileChunks: MessageSemanticSearchChunk[] };
92
+ /**
93
+ * Initial agent state (for resuming execution from a specific point)
94
+ */
95
+ initialState?: AgentState;
96
+ /**
97
+ * Initial agent runtime context (for resuming execution from a specific phase)
98
+ */
99
+ initialContext?: AgentRuntimeContext;
91
100
  }) => Promise<void>;
92
101
  }
93
102
 
@@ -397,6 +406,7 @@ export const streamingExecutor: StateCreator<
397
406
  // ===========================================
398
407
  // Step 1: RAG Preprocessing (if enabled)
399
408
  // ===========================================
409
+ // Skip RAG preprocessing if initialState is provided (messages already preprocessed)
400
410
  if (params.ragQuery && parentMessageType === 'user') {
401
411
  const userMessageId = parentMessageId;
402
412
  log('[internal_execAgentRuntime] RAG preprocessing start');
@@ -458,6 +468,17 @@ export const streamingExecutor: StateCreator<
458
468
  provider: provider!,
459
469
  },
460
470
  });
471
+
472
+ const toolsEngine = createAgentToolsEngine({ model: model, provider: provider! });
473
+ const { enabledToolIds } = toolsEngine.generateToolsDetailed({
474
+ model: model,
475
+ provider: provider!,
476
+ toolIds: agentConfigData.plugins,
477
+ });
478
+ const toolManifestMap = Object.fromEntries(
479
+ toolsEngine.getEnabledPluginManifests(enabledToolIds).entries(),
480
+ );
481
+
461
482
  const runtime = new AgentRuntime(agent, {
462
483
  executors: createAgentExecutors({
463
484
  get,
@@ -468,21 +489,24 @@ export const streamingExecutor: StateCreator<
468
489
  }),
469
490
  });
470
491
 
471
- // Create initial state
472
- let state = AgentRuntime.createInitialState({
473
- sessionId: activeId,
474
- messages,
475
- // Prevent infinite loops
476
- maxSteps: 400,
477
- metadata: {
492
+ // Create initial state or use provided state
493
+ let state =
494
+ params.initialState ||
495
+ AgentRuntime.createInitialState({
478
496
  sessionId: activeId,
479
- topicId: activeTopicId,
480
- threadId: params.threadId,
481
- },
482
- });
497
+ messages,
498
+ // Prevent infinite loops
499
+ maxSteps: 400,
500
+ metadata: {
501
+ sessionId: activeId,
502
+ topicId: activeTopicId,
503
+ threadId: params.threadId,
504
+ },
505
+ toolManifestMap,
506
+ });
483
507
 
484
- // Initial context - use 'init' phase since state already contains messages
485
- let nextContext: AgentRuntimeContext = {
508
+ // Initial context - use provided context or create default 'init' phase
509
+ let nextContext: AgentRuntimeContext = params.initialContext || {
486
510
  phase: 'init',
487
511
  payload: { model, provider, parentMessageId: params.parentMessageId },
488
512
  session: {
@@ -520,20 +544,24 @@ export const streamingExecutor: StateCreator<
520
544
 
521
545
  // Handle completion and error events
522
546
  for (const event of result.events) {
523
- if (event.type === 'done') {
524
- log('[internal_execAgentRuntime] Received done event');
525
- }
547
+ switch (event.type) {
548
+ case 'done': {
549
+ log('[internal_execAgentRuntime] Received done event');
550
+ break;
551
+ }
526
552
 
527
- if (event.type === 'error') {
528
- log('[internal_execAgentRuntime] Received error event: %o', event.error);
529
- // Find the assistant message to update error
530
- const currentMessages = get().messagesMap[messageKey] || [];
531
- const assistantMessage = currentMessages.findLast((m) => m.role === 'assistant');
532
- if (assistantMessage) {
533
- await messageService.updateMessageError(assistantMessage.id, event.error);
553
+ case 'error': {
554
+ log('[internal_execAgentRuntime] Received error event: %o', event.error);
555
+ // Find the assistant message to update error
556
+ const currentMessages = get().messagesMap[messageKey] || [];
557
+ const assistantMessage = currentMessages.findLast((m) => m.role === 'assistant');
558
+ if (assistantMessage) {
559
+ await messageService.updateMessageError(assistantMessage.id, event.error);
560
+ }
561
+ const finalMessages = get().messagesMap[messageKey] || [];
562
+ get().replaceMessages(finalMessages);
563
+ break;
534
564
  }
535
- const finalMessages = get().messagesMap[messageKey] || [];
536
- get().replaceMessages(finalMessages);
537
565
  }
538
566
  }
539
567
 
@@ -3,6 +3,7 @@ import {
3
3
  ChatPluginPayload,
4
4
  ChatToolPayload,
5
5
  CreateMessageParams,
6
+ MessagePluginItem,
6
7
  UIChatMessage,
7
8
  } from '@lobechat/types';
8
9
  import isEqual from 'fast-deep-equal';
@@ -47,7 +48,7 @@ interface UpdatePluginState {
47
48
  interface UpdateMessagePlugin {
48
49
  id: string;
49
50
  type: 'updateMessagePlugin';
50
- value: Partial<ChatPluginPayload>;
51
+ value: Partial<MessagePluginItem>;
51
52
  }
52
53
 
53
54
  interface UpdateMessageTools {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
- import { ChatMessageError, ChatToolPayload } from '@lobechat/types';
2
+ import { ChatMessageError, ChatToolPayload, MessagePluginItem } from '@lobechat/types';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { StateCreator } from 'zustand/vanilla';
5
5
 
@@ -29,6 +29,11 @@ export interface PluginOptimisticUpdateAction {
29
29
  replace?: boolean,
30
30
  ) => Promise<void>;
31
31
 
32
+ /**
33
+ * Update plugin with optimistic update (generic method for any plugin field)
34
+ */
35
+ optimisticUpdatePlugin: (id: string, value: Partial<MessagePluginItem>) => Promise<void>;
36
+
32
37
  /**
33
38
  * Add tool to assistant message with optimistic update
34
39
  */
@@ -119,6 +124,26 @@ export const pluginOptimisticUpdate: StateCreator<
119
124
  await refreshMessages();
120
125
  },
121
126
 
127
+ optimisticUpdatePlugin: async (id, value) => {
128
+ const { replaceMessages } = get();
129
+
130
+ // optimistic update
131
+ get().internal_dispatchMessage({
132
+ id,
133
+ type: 'updateMessagePlugin',
134
+ value,
135
+ });
136
+
137
+ const result = await messageService.updateMessagePlugin(id, value, {
138
+ sessionId: get().activeId,
139
+ topicId: get().activeTopicId,
140
+ });
141
+
142
+ if (result?.success && result.messages) {
143
+ replaceMessages(result.messages);
144
+ }
145
+ },
146
+
122
147
  optimisticAddToolToAssistantMessage: async (id, tool) => {
123
148
  const assistantMessage = displayMessageSelectors.getDisplayMessageById(id)(get());
124
149
  if (!assistantMessage) return;
@@ -18,6 +18,7 @@ import { difference } from '@/utils/difference';
18
18
  import { merge } from '@/utils/merge';
19
19
 
20
20
  export interface UserSettingsAction {
21
+ addToolToAllowList: (toolKey: string) => Promise<void>;
21
22
  importAppSettings: (settings: UserSettings) => Promise<void>;
22
23
  importUrlShareSettings: (settingsParams: string | null) => Promise<void>;
23
24
  internal_createSignal: () => AbortController;
@@ -39,6 +40,20 @@ export const createSettingsSlice: StateCreator<
39
40
  [],
40
41
  UserSettingsAction
41
42
  > = (set, get) => ({
43
+ addToolToAllowList: async (toolKey) => {
44
+ const currentAllowList = get().settings.tool?.humanIntervention?.allowList || [];
45
+
46
+ if (currentAllowList.includes(toolKey)) return;
47
+
48
+ await get().setSettings({
49
+ tool: {
50
+ humanIntervention: {
51
+ allowList: [...currentAllowList, toolKey],
52
+ },
53
+ },
54
+ });
55
+ },
56
+
42
57
  importAppSettings: async (importAppSettings) => {
43
58
  const { setSettings } = get();
44
59