@lobehub/lobehub 2.0.0-next.50 → 2.0.0-next.52

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 (171) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/apps/desktop/src/main/controllers/LocalFileCtr.ts +25 -5
  3. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
  4. package/apps/desktop/src/main/controllers/__tests__/LocalFileCtr.test.ts +4 -1
  5. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
  6. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +357 -0
  7. package/apps/desktop/src/main/modules/fileSearch/impl/macOS.ts +30 -22
  8. package/changelog/v1.json +18 -0
  9. package/locales/ar/chat.json +20 -0
  10. package/locales/ar/common.json +1 -0
  11. package/locales/ar/components.json +6 -0
  12. package/locales/ar/models.json +119 -126
  13. package/locales/ar/plugin.json +2 -1
  14. package/locales/bg-BG/chat.json +20 -0
  15. package/locales/bg-BG/common.json +1 -0
  16. package/locales/bg-BG/components.json +6 -0
  17. package/locales/bg-BG/models.json +104 -132
  18. package/locales/bg-BG/plugin.json +2 -1
  19. package/locales/de-DE/chat.json +20 -0
  20. package/locales/de-DE/common.json +1 -0
  21. package/locales/de-DE/components.json +6 -0
  22. package/locales/de-DE/models.json +119 -126
  23. package/locales/de-DE/plugin.json +2 -1
  24. package/locales/en-US/chat.json +20 -0
  25. package/locales/en-US/common.json +1 -0
  26. package/locales/en-US/components.json +6 -0
  27. package/locales/en-US/models.json +167 -126
  28. package/locales/en-US/plugin.json +2 -1
  29. package/locales/es-ES/chat.json +20 -0
  30. package/locales/es-ES/common.json +1 -0
  31. package/locales/es-ES/components.json +6 -0
  32. package/locales/es-ES/models.json +119 -126
  33. package/locales/es-ES/plugin.json +2 -1
  34. package/locales/fa-IR/chat.json +20 -0
  35. package/locales/fa-IR/common.json +1 -0
  36. package/locales/fa-IR/components.json +6 -0
  37. package/locales/fa-IR/models.json +119 -126
  38. package/locales/fa-IR/plugin.json +2 -1
  39. package/locales/fr-FR/chat.json +20 -0
  40. package/locales/fr-FR/common.json +1 -0
  41. package/locales/fr-FR/components.json +6 -0
  42. package/locales/fr-FR/models.json +119 -126
  43. package/locales/fr-FR/plugin.json +2 -1
  44. package/locales/it-IT/chat.json +20 -0
  45. package/locales/it-IT/common.json +1 -0
  46. package/locales/it-IT/components.json +6 -0
  47. package/locales/it-IT/models.json +119 -126
  48. package/locales/it-IT/plugin.json +2 -1
  49. package/locales/ja-JP/chat.json +20 -0
  50. package/locales/ja-JP/common.json +1 -0
  51. package/locales/ja-JP/components.json +6 -0
  52. package/locales/ja-JP/models.json +119 -126
  53. package/locales/ja-JP/plugin.json +2 -1
  54. package/locales/ko-KR/chat.json +20 -0
  55. package/locales/ko-KR/common.json +1 -0
  56. package/locales/ko-KR/components.json +6 -0
  57. package/locales/ko-KR/models.json +119 -126
  58. package/locales/ko-KR/plugin.json +2 -1
  59. package/locales/nl-NL/chat.json +20 -0
  60. package/locales/nl-NL/common.json +1 -0
  61. package/locales/nl-NL/components.json +6 -0
  62. package/locales/nl-NL/models.json +119 -126
  63. package/locales/nl-NL/plugin.json +2 -1
  64. package/locales/pl-PL/chat.json +20 -0
  65. package/locales/pl-PL/common.json +1 -0
  66. package/locales/pl-PL/components.json +6 -0
  67. package/locales/pl-PL/models.json +119 -126
  68. package/locales/pl-PL/plugin.json +2 -1
  69. package/locales/pt-BR/chat.json +20 -0
  70. package/locales/pt-BR/common.json +1 -0
  71. package/locales/pt-BR/components.json +6 -0
  72. package/locales/pt-BR/models.json +119 -126
  73. package/locales/pt-BR/plugin.json +2 -1
  74. package/locales/ru-RU/chat.json +20 -0
  75. package/locales/ru-RU/common.json +1 -0
  76. package/locales/ru-RU/components.json +6 -0
  77. package/locales/ru-RU/models.json +119 -126
  78. package/locales/ru-RU/plugin.json +2 -1
  79. package/locales/tr-TR/chat.json +20 -0
  80. package/locales/tr-TR/common.json +1 -0
  81. package/locales/tr-TR/components.json +6 -0
  82. package/locales/tr-TR/models.json +119 -126
  83. package/locales/tr-TR/plugin.json +2 -1
  84. package/locales/vi-VN/chat.json +20 -0
  85. package/locales/vi-VN/common.json +1 -0
  86. package/locales/vi-VN/components.json +6 -0
  87. package/locales/vi-VN/models.json +119 -126
  88. package/locales/vi-VN/plugin.json +2 -1
  89. package/locales/zh-CN/chat.json +20 -0
  90. package/locales/zh-CN/common.json +1 -0
  91. package/locales/zh-CN/components.json +6 -0
  92. package/locales/zh-CN/models.json +173 -80
  93. package/locales/zh-CN/plugin.json +2 -1
  94. package/locales/zh-TW/chat.json +20 -0
  95. package/locales/zh-TW/common.json +1 -0
  96. package/locales/zh-TW/components.json +6 -0
  97. package/locales/zh-TW/models.json +119 -126
  98. package/locales/zh-TW/plugin.json +2 -1
  99. package/package.json +1 -1
  100. package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
  101. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
  102. package/packages/agent-runtime/src/types/state.ts +7 -1
  103. package/packages/const/src/settings/tool.ts +1 -5
  104. package/packages/electron-client-ipc/src/types/localSystem.ts +26 -2
  105. package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
  106. package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
  107. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +58 -0
  108. package/packages/model-runtime/src/core/contextBuilders/openai.ts +24 -10
  109. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +3 -2
  110. package/packages/model-runtime/src/providers/openai/index.test.ts +44 -0
  111. package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
  112. package/packages/model-runtime/src/utils/modelParse.ts +6 -0
  113. package/packages/types/src/tool/builtin.ts +15 -4
  114. package/packages/types/src/tool/intervention.ts +32 -2
  115. package/packages/types/src/user/settings/tool.ts +3 -27
  116. package/src/config/modelProviders/wenxin.ts +2 -3
  117. package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
  118. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
  119. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
  120. package/src/features/Conversation/Messages/Assistant/Tool/Render/LoadingPlaceholder/index.tsx +3 -3
  121. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
  122. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
  123. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
  124. package/src/features/Conversation/Messages/Group/Tool/Render/LoadingPlaceholder/index.tsx +3 -3
  125. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
  126. package/src/features/LocalFile/LocalFile.tsx +55 -5
  127. package/src/features/PluginsUI/Render/BuiltinType/index.test.tsx +10 -4
  128. package/src/features/PluginsUI/Render/BuiltinType/index.tsx +2 -2
  129. package/src/locales/default/components.ts +6 -0
  130. package/src/locales/default/plugin.ts +2 -1
  131. package/src/services/chat/chat.test.ts +1 -0
  132. package/src/services/electron/localFileService.ts +4 -0
  133. package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +62 -0
  134. package/src/store/aiInfra/slices/aiProvider/selectors.ts +1 -1
  135. package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
  136. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
  137. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
  138. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
  139. package/src/store/user/selectors.ts +1 -0
  140. package/src/store/user/slices/settings/action.ts +12 -0
  141. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
  142. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  143. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
  144. package/src/store/user/slices/settings/selectors/settings.ts +0 -5
  145. package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
  146. package/src/tools/code-interpreter/Render/index.tsx +1 -1
  147. package/src/tools/interventions.ts +32 -0
  148. package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
  149. package/src/tools/local-system/Placeholder/ListFiles.tsx +3 -5
  150. package/src/tools/local-system/Placeholder/SearchFiles.tsx +2 -5
  151. package/src/tools/local-system/Render/ListFiles/index.tsx +16 -21
  152. package/src/tools/local-system/Render/RenameLocalFile/index.tsx +15 -20
  153. package/src/tools/local-system/Render/RunCommand/index.tsx +103 -27
  154. package/src/tools/local-system/Render/SearchFiles/SearchQuery/index.tsx +0 -1
  155. package/src/tools/local-system/Render/SearchFiles/index.tsx +15 -20
  156. package/src/tools/local-system/Render/WriteFile/index.tsx +2 -8
  157. package/src/tools/local-system/index.ts +184 -4
  158. package/src/tools/local-system/systemRole.ts +62 -8
  159. package/src/tools/placeholders.ts +39 -8
  160. package/src/tools/renders.ts +56 -9
  161. package/src/tools/web-browsing/Placeholder/{PageContent.tsx → CrawlMultiPages.tsx} +4 -1
  162. package/src/tools/web-browsing/Placeholder/CrawlSinglePage.tsx +12 -0
  163. package/src/tools/web-browsing/Placeholder/Search.tsx +4 -4
  164. package/src/tools/web-browsing/Render/CrawlMultiPages.tsx +15 -0
  165. package/src/tools/web-browsing/Render/CrawlSinglePage.tsx +15 -0
  166. package/src/tools/web-browsing/Render/Search/index.tsx +39 -44
  167. package/packages/database/migrations/0044_add_tool_intervention.sql +0 -1
  168. package/src/tools/local-system/Placeholder/index.tsx +0 -25
  169. package/src/tools/local-system/Render/index.tsx +0 -40
  170. package/src/tools/web-browsing/Placeholder/index.tsx +0 -40
  171. package/src/tools/web-browsing/Render/index.tsx +0 -57
@@ -601,5 +601,178 @@ describe('GeneralChatAgent', () => {
601
601
  },
602
602
  ]);
603
603
  });
604
+
605
+ it('should execute all tools when user approvalMode is auto-run', async () => {
606
+ const agent = new GeneralChatAgent({
607
+ agentConfig: { maxSteps: 100 },
608
+ sessionId: 'test-session',
609
+ modelRuntimeConfig: mockModelRuntimeConfig,
610
+ });
611
+
612
+ const toolCall: ChatToolPayload = {
613
+ id: 'call-1',
614
+ identifier: 'dangerous-tool',
615
+ apiName: 'delete',
616
+ arguments: '{}',
617
+ type: 'default',
618
+ };
619
+
620
+ const state = createMockState({
621
+ toolManifestMap: {
622
+ 'dangerous-tool': {
623
+ identifier: 'dangerous-tool',
624
+ humanIntervention: 'required', // Tool requires approval
625
+ },
626
+ },
627
+ userInterventionConfig: {
628
+ approvalMode: 'auto-run', // But user config overrides
629
+ allowList: [],
630
+ },
631
+ });
632
+
633
+ const context = createMockContext('llm_result', {
634
+ hasToolsCalling: true,
635
+ toolsCalling: [toolCall],
636
+ parentMessageId: 'msg-1',
637
+ });
638
+
639
+ const result = await agent.runner(context, state);
640
+
641
+ // Should execute directly despite tool requiring approval
642
+ expect(result).toEqual([
643
+ {
644
+ type: 'call_tool',
645
+ payload: {
646
+ parentMessageId: 'msg-1',
647
+ toolCalling: toolCall,
648
+ },
649
+ },
650
+ ]);
651
+ });
652
+
653
+ it('should respect allowList when approvalMode is allow-list', async () => {
654
+ const agent = new GeneralChatAgent({
655
+ agentConfig: { maxSteps: 100 },
656
+ sessionId: 'test-session',
657
+ modelRuntimeConfig: mockModelRuntimeConfig,
658
+ });
659
+
660
+ const allowedTool: ChatToolPayload = {
661
+ id: 'call-1',
662
+ identifier: 'bash',
663
+ apiName: 'bash',
664
+ arguments: '{"command":"ls"}',
665
+ type: 'builtin',
666
+ };
667
+
668
+ const blockedTool: ChatToolPayload = {
669
+ id: 'call-2',
670
+ identifier: 'bash',
671
+ apiName: 'dangerous-command',
672
+ arguments: '{"command":"rm -rf"}',
673
+ type: 'builtin',
674
+ };
675
+
676
+ const state = createMockState({
677
+ toolManifestMap: {
678
+ bash: {
679
+ identifier: 'bash',
680
+ humanIntervention: 'never', // Tool doesn't require approval by default
681
+ },
682
+ },
683
+ userInterventionConfig: {
684
+ approvalMode: 'allow-list',
685
+ allowList: ['bash/bash'], // Only bash/bash is allowed
686
+ },
687
+ });
688
+
689
+ const context = createMockContext('llm_result', {
690
+ hasToolsCalling: true,
691
+ toolsCalling: [allowedTool, blockedTool],
692
+ parentMessageId: 'msg-1',
693
+ });
694
+
695
+ const result = await agent.runner(context, state);
696
+
697
+ // Should execute allowed tool first, then request approval for blocked tool
698
+ expect(result).toEqual([
699
+ {
700
+ type: 'call_tool',
701
+ payload: {
702
+ parentMessageId: 'msg-1',
703
+ toolCalling: allowedTool,
704
+ },
705
+ },
706
+ {
707
+ type: 'request_human_approve',
708
+ pendingToolsCalling: [blockedTool],
709
+ reason: 'human_intervention_required',
710
+ },
711
+ ]);
712
+ });
713
+
714
+ it('should use tool config when approvalMode is manual', async () => {
715
+ const agent = new GeneralChatAgent({
716
+ agentConfig: { maxSteps: 100 },
717
+ sessionId: 'test-session',
718
+ modelRuntimeConfig: mockModelRuntimeConfig,
719
+ });
720
+
721
+ const safeTool: ChatToolPayload = {
722
+ id: 'call-1',
723
+ identifier: 'web-search',
724
+ apiName: 'search',
725
+ arguments: '{}',
726
+ type: 'default',
727
+ };
728
+
729
+ const dangerousTool: ChatToolPayload = {
730
+ id: 'call-2',
731
+ identifier: 'bash',
732
+ apiName: 'bash',
733
+ arguments: '{}',
734
+ type: 'builtin',
735
+ };
736
+
737
+ const state = createMockState({
738
+ toolManifestMap: {
739
+ 'web-search': {
740
+ identifier: 'web-search',
741
+ humanIntervention: 'never', // Safe tool
742
+ },
743
+ 'bash': {
744
+ identifier: 'bash',
745
+ humanIntervention: 'required', // Dangerous tool
746
+ },
747
+ },
748
+ userInterventionConfig: {
749
+ approvalMode: 'manual', // Use tool's own config
750
+ },
751
+ });
752
+
753
+ const context = createMockContext('llm_result', {
754
+ hasToolsCalling: true,
755
+ toolsCalling: [safeTool, dangerousTool],
756
+ parentMessageId: 'msg-1',
757
+ });
758
+
759
+ const result = await agent.runner(context, state);
760
+
761
+ // Should execute safe tool, request approval for dangerous tool
762
+ expect(result).toEqual([
763
+ {
764
+ type: 'call_tool',
765
+ payload: {
766
+ parentMessageId: 'msg-1',
767
+ toolCalling: safeTool,
768
+ },
769
+ },
770
+ {
771
+ type: 'request_human_approve',
772
+ pendingToolsCalling: [dangerousTool],
773
+ reason: 'human_intervention_required',
774
+ },
775
+ ]);
776
+ });
604
777
  });
605
778
  });
@@ -1,13 +1,10 @@
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 { type AgentRuntimeContext } from '@lobechat/agent-runtime';
4
4
  import { MESSAGE_CANCEL_FLAT } from '@lobechat/const';
5
5
  import { produce } from 'immer';
6
6
  import { StateCreator } from 'zustand/vanilla';
7
7
 
8
- import { getAgentStoreState } from '@/store/agent';
9
- import { agentSelectors } from '@/store/agent/slices/chat';
10
- import { createAgentToolsEngine } from '@/store/chat/agents/createToolEngine';
11
8
  import { ChatStore } from '@/store/chat/store';
12
9
  import { setNamespace } from '@/utils/storeDebug';
13
10
 
@@ -146,7 +143,7 @@ export const conversationControl: StateCreator<
146
143
  }
147
144
  },
148
145
  approveToolCalling: async (toolMessageId) => {
149
- const { activeId, activeTopicId, activeThreadId, internal_execAgentRuntime } = get();
146
+ const { activeThreadId, internal_execAgentRuntime } = get();
150
147
 
151
148
  // 1. Get tool message and verify it exists
152
149
  const toolMessage = dbMessageSelectors.getDbMessageById(toolMessageId)(get());
@@ -158,51 +155,22 @@ export const conversationControl: StateCreator<
158
155
  // 3. Get current messages for state construction
159
156
  const currentMessages = displayMessageSelectors.mainAIChats(get());
160
157
 
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,
158
+ // 4. Create agent state and context with user intervention config
159
+ const { state, context: initialContext } = get().internal_createAgentState({
181
160
  messages: currentMessages,
182
- maxSteps: 400,
183
- metadata: {
184
- sessionId: activeId,
185
- topicId: activeTopicId,
186
- threadId: activeThreadId,
187
- },
188
- toolManifestMap,
161
+ parentMessageId: toolMessageId,
162
+ threadId: activeThreadId,
189
163
  });
190
164
 
191
- console.log('toolMessage:', toolMessage);
192
- // 6. Construct AgentRuntimeContext with 'human_approved_tool' phase
165
+ // 5. Override context with 'human_approved_tool' phase
193
166
  const context: AgentRuntimeContext = {
167
+ ...initialContext,
194
168
  phase: 'human_approved_tool',
195
169
  payload: {
196
170
  approvedToolCall: toolMessage.plugin,
197
171
  parentMessageId: toolMessageId,
198
172
  skipCreateToolMessage: true,
199
173
  },
200
- session: {
201
- sessionId: activeId,
202
- messageCount: currentMessages.length,
203
- status: 'running',
204
- stepCount: 0,
205
- },
206
174
  };
207
175
 
208
176
  // 7. Execute agent runtime from tool message position
@@ -26,6 +26,8 @@ import { createAgentExecutors } from '@/store/chat/agents/createAgentExecutors';
26
26
  import { createAgentToolsEngine } from '@/store/chat/agents/createToolEngine';
27
27
  import { ChatStore } from '@/store/chat/store';
28
28
  import { getFileStoreState } from '@/store/file/store';
29
+ import { toolInterventionSelectors } from '@/store/user/selectors';
30
+ import { getUserStoreState } from '@/store/user/store';
29
31
  import { setNamespace } from '@/utils/storeDebug';
30
32
 
31
33
  import { topicSelectors } from '../../../selectors';
@@ -54,6 +56,19 @@ interface ProcessMessageParams {
54
56
  * Core streaming execution actions for AI chat
55
57
  */
56
58
  export interface StreamingExecutorAction {
59
+ /**
60
+ * Creates initial agent state and context with user intervention config
61
+ */
62
+ internal_createAgentState: (params: {
63
+ messages: UIChatMessage[];
64
+ parentMessageId: string;
65
+ threadId?: string;
66
+ initialState?: AgentState;
67
+ initialContext?: AgentRuntimeContext;
68
+ }) => {
69
+ state: AgentState;
70
+ context: AgentRuntimeContext;
71
+ };
57
72
  /**
58
73
  * Retrieves an AI-generated chat message from the backend service with streaming
59
74
  */
@@ -106,6 +121,73 @@ export const streamingExecutor: StateCreator<
106
121
  [],
107
122
  StreamingExecutorAction
108
123
  > = (set, get) => ({
124
+ internal_createAgentState: ({
125
+ messages,
126
+ parentMessageId,
127
+ threadId,
128
+ initialState,
129
+ initialContext,
130
+ }) => {
131
+ const { activeId, activeTopicId } = get();
132
+ const agentStoreState = getAgentStoreState();
133
+ const agentConfigData = agentSelectors.currentAgentConfig(agentStoreState);
134
+
135
+ // Get tools manifest map
136
+ const toolsEngine = createAgentToolsEngine({
137
+ model: agentConfigData.model,
138
+ provider: agentConfigData.provider!,
139
+ });
140
+ const { enabledToolIds } = toolsEngine.generateToolsDetailed({
141
+ model: agentConfigData.model,
142
+ provider: agentConfigData.provider!,
143
+ toolIds: agentConfigData.plugins,
144
+ });
145
+ const toolManifestMap = Object.fromEntries(
146
+ toolsEngine.getEnabledPluginManifests(enabledToolIds).entries(),
147
+ );
148
+
149
+ // Get user intervention config
150
+ const userStore = getUserStoreState();
151
+ const userInterventionConfig = {
152
+ approvalMode: toolInterventionSelectors.approvalMode(userStore),
153
+ allowList: toolInterventionSelectors.allowList(userStore),
154
+ };
155
+
156
+ // Create initial state or use provided state
157
+ const state =
158
+ initialState ||
159
+ AgentRuntime.createInitialState({
160
+ sessionId: activeId,
161
+ messages,
162
+ maxSteps: 400,
163
+ metadata: {
164
+ sessionId: activeId,
165
+ topicId: activeTopicId,
166
+ threadId,
167
+ },
168
+ toolManifestMap,
169
+ userInterventionConfig,
170
+ });
171
+
172
+ // Create initial context or use provided context
173
+ const context: AgentRuntimeContext = initialContext || {
174
+ phase: 'init',
175
+ payload: {
176
+ model: agentConfigData.model,
177
+ provider: agentConfigData.provider,
178
+ parentMessageId,
179
+ },
180
+ session: {
181
+ sessionId: activeId,
182
+ messageCount: messages.length,
183
+ status: state.status,
184
+ stepCount: 0,
185
+ },
186
+ };
187
+
188
+ return { state, context };
189
+ },
190
+
109
191
  internal_fetchAIChatMessage: async ({ messages, messageId, params, provider, model }) => {
110
192
  const {
111
193
  internal_toggleChatLoading,
@@ -469,16 +551,6 @@ export const streamingExecutor: StateCreator<
469
551
  },
470
552
  });
471
553
 
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
-
482
554
  const runtime = new AgentRuntime(agent, {
483
555
  executors: createAgentExecutors({
484
556
  get,
@@ -489,33 +561,18 @@ export const streamingExecutor: StateCreator<
489
561
  }),
490
562
  });
491
563
 
492
- // Create initial state or use provided state
493
- let state =
494
- params.initialState ||
495
- AgentRuntime.createInitialState({
496
- sessionId: activeId,
564
+ // Create agent state and context with user intervention config
565
+ const { state: initialAgentState, context: initialAgentContext } =
566
+ get().internal_createAgentState({
497
567
  messages,
498
- // Prevent infinite loops
499
- maxSteps: 400,
500
- metadata: {
501
- sessionId: activeId,
502
- topicId: activeTopicId,
503
- threadId: params.threadId,
504
- },
505
- toolManifestMap,
568
+ parentMessageId: params.parentMessageId,
569
+ threadId: params.threadId,
570
+ initialState: params.initialState,
571
+ initialContext: params.initialContext,
506
572
  });
507
573
 
508
- // Initial context - use provided context or create default 'init' phase
509
- let nextContext: AgentRuntimeContext = params.initialContext || {
510
- phase: 'init',
511
- payload: { model, provider, parentMessageId: params.parentMessageId },
512
- session: {
513
- sessionId: activeId,
514
- messageCount: messages.length,
515
- status: state.status,
516
- stepCount: 0,
517
- },
518
- };
574
+ let state = initialAgentState;
575
+ let nextContext = initialAgentContext;
519
576
 
520
577
  log(
521
578
  '[internal_execAgentRuntime] Agent runtime loop start, initial phase: %s',
@@ -4,5 +4,6 @@ export {
4
4
  keyVaultsConfigSelectors,
5
5
  settingsSelectors,
6
6
  systemAgentSelectors,
7
+ toolInterventionSelectors,
7
8
  userGeneralSettingsSelectors,
8
9
  } from './slices/settings/selectors';
@@ -26,6 +26,10 @@ export interface UserSettingsAction {
26
26
  setSettings: (settings: PartialDeep<UserSettings>) => Promise<void>;
27
27
  updateDefaultAgent: (agent: PartialDeep<LobeAgentSettings>) => Promise<void>;
28
28
  updateGeneralConfig: (settings: Partial<UserGeneralConfig>) => Promise<void>;
29
+ updateHumanIntervention: (config: {
30
+ allowList?: string[];
31
+ approvalMode?: 'auto-run' | 'allow-list' | 'manual';
32
+ }) => Promise<void>;
29
33
  updateKeyVaults: (settings: Partial<UserKeyVaults>) => Promise<void>;
30
34
 
31
35
  updateSystemAgent: (
@@ -111,6 +115,14 @@ export const createSettingsSlice: StateCreator<
111
115
  updateGeneralConfig: async (general) => {
112
116
  await get().setSettings({ general });
113
117
  },
118
+ updateHumanIntervention: async (config) => {
119
+ const current = get().settings.tool?.humanIntervention || {};
120
+ await get().setSettings({
121
+ tool: {
122
+ humanIntervention: { ...current, ...config },
123
+ },
124
+ });
125
+ },
114
126
  updateKeyVaults: async (keyVaults) => {
115
127
  await get().setSettings({ keyVaults });
116
128
  },
@@ -95,13 +95,6 @@ exports[`settingsSelectors > currentTTS > should merge DEFAULT_TTS_CONFIG and s.
95
95
  }
96
96
  `;
97
97
 
98
- exports[`settingsSelectors > dalleConfig > should return the dalle configuration 1`] = `
99
- {
100
- "apiKey": "dalle-api-key",
101
- "autoGenerate": true,
102
- }
103
- `;
104
-
105
98
  exports[`settingsSelectors > defaultAgent > should merge DEFAULT_AGENT and s.settings.defaultAgent correctly 1`] = `
106
99
  {
107
100
  "config": {
@@ -2,3 +2,4 @@ export { userGeneralSettingsSelectors } from './general';
2
2
  export { keyVaultsConfigSelectors } from './keyVaults';
3
3
  export { settingsSelectors } from './settings';
4
4
  export { systemAgentSelectors } from './systemAgent';
5
+ export { toolInterventionSelectors } from './toolIntervention';
@@ -120,43 +120,6 @@ describe('settingsSelectors', () => {
120
120
  });
121
121
  });
122
122
 
123
- describe('dalleConfig', () => {
124
- it('should return the dalle configuration', () => {
125
- const s = {
126
- settings: {
127
- tool: {
128
- dalle: {
129
- apiKey: 'dalle-api-key',
130
- autoGenerate: true,
131
- },
132
- },
133
- },
134
- } as unknown as UserStore;
135
-
136
- const result = settingsSelectors.dalleConfig(s);
137
-
138
- expect(result).toMatchSnapshot();
139
- });
140
- });
141
-
142
- describe('isDalleAutoGenerating', () => {
143
- it('should return the autoGenerate flag from dalle configuration', () => {
144
- const s = {
145
- settings: {
146
- tool: {
147
- dalle: {
148
- autoGenerate: true,
149
- },
150
- },
151
- },
152
- } as unknown as UserStore;
153
-
154
- const result = settingsSelectors.isDalleAutoGenerating(s);
155
-
156
- expect(result).toBe(true);
157
- });
158
- });
159
-
160
123
  describe('getProviderConfigById', () => {
161
124
  it('should return the provider config for a given provider id', () => {
162
125
  const providerConfig = {
@@ -36,9 +36,6 @@ const defaultAgentMeta = (s: UserStore) => merge(DEFAULT_AGENT_META, defaultAgen
36
36
 
37
37
  const exportSettings = currentSettings;
38
38
 
39
- const dalleConfig = (s: UserStore) => currentSettings(s).tool?.dalle || {};
40
- const isDalleAutoGenerating = (s: UserStore) => currentSettings(s).tool?.dalle?.autoGenerate;
41
-
42
39
  const currentSystemAgent = (s: UserStore) =>
43
40
  merge(DEFAULT_SYSTEM_AGENT_CONFIG, currentSettings(s).systemAgent);
44
41
 
@@ -50,12 +47,10 @@ export const settingsSelectors = {
50
47
  currentSettings,
51
48
  currentSystemAgent,
52
49
  currentTTS,
53
- dalleConfig,
54
50
  defaultAgent,
55
51
  defaultAgentConfig,
56
52
  defaultAgentMeta,
57
53
  exportSettings,
58
54
  getHotkeyById,
59
- isDalleAutoGenerating,
60
55
  providerConfig: getProviderConfigById,
61
56
  };
@@ -0,0 +1,17 @@
1
+ import type { UserStore } from '@/store/user';
2
+
3
+ import { currentSettings } from './settings';
4
+
5
+ const humanInterventionConfig = (s: UserStore) => currentSettings(s).tool?.humanIntervention || {};
6
+
7
+ const interventionApprovalMode = (s: UserStore) =>
8
+ currentSettings(s).tool?.humanIntervention?.approvalMode || 'manual';
9
+
10
+ const interventionAllowList = (s: UserStore) =>
11
+ currentSettings(s).tool?.humanIntervention?.allowList || [];
12
+
13
+ export const toolInterventionSelectors = {
14
+ allowList: interventionAllowList,
15
+ approvalMode: interventionApprovalMode,
16
+ config: humanInterventionConfig,
17
+ };
@@ -17,7 +17,7 @@ import { chatToolSelectors } from '@/store/chat/slices/builtinTool/selectors';
17
17
  import ResultFileGallery from './components/ResultFileGallery';
18
18
 
19
19
  const CodeInterpreter = memo<
20
- BuiltinRenderProps<CodeInterpreterResponse, CodeInterpreterParams, CodeInterpreterState>
20
+ BuiltinRenderProps<CodeInterpreterParams, CodeInterpreterState, CodeInterpreterResponse>
21
21
  >(({ content, args, pluginState, messageId, apiName }) => {
22
22
  const { t } = useTranslation('tool');
23
23
  const theme = useTheme();
@@ -0,0 +1,32 @@
1
+ import { BuiltinIntervention } from '@lobechat/types';
2
+
3
+ import { LocalSystemApiName, LocalSystemManifest } from './local-system';
4
+ import RunCommand from './local-system/Intervention/RunCommand';
5
+
6
+ /**
7
+ * Builtin tools interventions registry
8
+ * Organized by toolset (identifier) -> API name
9
+ * Only register APIs that have custom intervention UI
10
+ */
11
+ export const BuiltinToolInterventions: Record<string, Record<string, any>> = {
12
+ [LocalSystemManifest.identifier]: {
13
+ [LocalSystemApiName.runCommand]: RunCommand,
14
+ },
15
+ };
16
+
17
+ /**
18
+ * Get builtin intervention component for a specific API
19
+ * @param identifier - Tool identifier (e.g., 'lobe-local-system')
20
+ * @param apiName - API name (e.g., 'runCommand')
21
+ */
22
+ export const getBuiltinIntervention = (
23
+ identifier?: string,
24
+ apiName?: string,
25
+ ): BuiltinIntervention | undefined => {
26
+ if (!identifier || !apiName) return undefined;
27
+
28
+ const toolset = BuiltinToolInterventions[identifier];
29
+ if (!toolset) return undefined;
30
+
31
+ return toolset[apiName];
32
+ };
@@ -0,0 +1,56 @@
1
+ import { RunCommandParams } from '@lobechat/electron-client-ipc';
2
+ import { Highlighter, Text } from '@lobehub/ui';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ const formatTimeout = (ms?: number) => {
7
+ if (!ms) return null;
8
+
9
+ const seconds = ms / 1000;
10
+
11
+ // >= 60s 显示分钟
12
+ if (seconds >= 60) {
13
+ const minutes = seconds / 60;
14
+ return `${minutes.toFixed(1)}min`;
15
+ }
16
+
17
+ // >= 1s 显示秒
18
+ if (seconds >= 1) {
19
+ return `${seconds.toFixed(1)}s`;
20
+ }
21
+
22
+ // < 1s 显示毫秒
23
+ return `${ms}ms`;
24
+ };
25
+
26
+ interface RunCommandProps extends RunCommandParams {
27
+ messageId: string;
28
+ }
29
+
30
+ const RunCommand = memo<RunCommandProps>(({ description, command, timeout }) => {
31
+ return (
32
+ <Flexbox gap={8}>
33
+ <Flexbox horizontal justify={'space-between'}>
34
+ {description && <Text>{description}</Text>}
35
+ {timeout && (
36
+ <Text style={{ fontSize: 12 }} type={'secondary'}>
37
+ timeout: {formatTimeout(timeout)}
38
+ </Text>
39
+ )}
40
+ </Flexbox>
41
+ {command && (
42
+ <Highlighter
43
+ language={'sh'}
44
+ showLanguage={false}
45
+ style={{ padding: '4px 8px' }}
46
+ variant={'outlined'}
47
+ wrap
48
+ >
49
+ {command}
50
+ </Highlighter>
51
+ )}
52
+ </Flexbox>
53
+ );
54
+ });
55
+
56
+ export default RunCommand;
@@ -1,17 +1,15 @@
1
1
  import { ListLocalFileParams } from '@lobechat/electron-client-ipc';
2
+ import { BuiltinPlaceholderProps } from '@lobechat/types';
2
3
  import { Skeleton } from 'antd';
3
4
  import React, { memo } from 'react';
4
5
  import { Center, Flexbox } from 'react-layout-kit';
5
6
 
6
7
  import { LocalFolder } from '@/features/LocalFile';
7
8
 
8
- interface ListFilesProps {
9
- args: ListLocalFileParams;
10
- }
11
- export const ListFiles = memo<ListFilesProps>(({ args }) => {
9
+ export const ListFiles = memo<BuiltinPlaceholderProps<ListLocalFileParams>>(({ args }) => {
12
10
  return (
13
11
  <Flexbox gap={12}>
14
- <LocalFolder path={args.path} />
12
+ {args?.path && <LocalFolder path={args.path} />}
15
13
  <Center height={140}>
16
14
  <Flexbox gap={4} width={'90%'}>
17
15
  <Skeleton.Button active block style={{ height: 16 }} />