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

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 (118) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
  3. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
  4. package/changelog/v1.json +9 -0
  5. package/locales/ar/chat.json +20 -0
  6. package/locales/ar/common.json +1 -0
  7. package/locales/ar/components.json +6 -0
  8. package/locales/ar/plugin.json +1 -0
  9. package/locales/bg-BG/chat.json +20 -0
  10. package/locales/bg-BG/common.json +1 -0
  11. package/locales/bg-BG/components.json +6 -0
  12. package/locales/bg-BG/plugin.json +1 -0
  13. package/locales/de-DE/chat.json +20 -0
  14. package/locales/de-DE/common.json +1 -0
  15. package/locales/de-DE/components.json +6 -0
  16. package/locales/de-DE/plugin.json +1 -0
  17. package/locales/en-US/chat.json +20 -0
  18. package/locales/en-US/common.json +1 -0
  19. package/locales/en-US/components.json +6 -0
  20. package/locales/en-US/plugin.json +1 -0
  21. package/locales/es-ES/chat.json +20 -0
  22. package/locales/es-ES/common.json +1 -0
  23. package/locales/es-ES/components.json +6 -0
  24. package/locales/es-ES/plugin.json +1 -0
  25. package/locales/fa-IR/chat.json +20 -0
  26. package/locales/fa-IR/common.json +1 -0
  27. package/locales/fa-IR/components.json +6 -0
  28. package/locales/fa-IR/plugin.json +1 -0
  29. package/locales/fr-FR/chat.json +20 -0
  30. package/locales/fr-FR/common.json +1 -0
  31. package/locales/fr-FR/components.json +6 -0
  32. package/locales/fr-FR/plugin.json +1 -0
  33. package/locales/it-IT/chat.json +20 -0
  34. package/locales/it-IT/common.json +1 -0
  35. package/locales/it-IT/components.json +6 -0
  36. package/locales/it-IT/plugin.json +1 -0
  37. package/locales/ja-JP/chat.json +20 -0
  38. package/locales/ja-JP/common.json +1 -0
  39. package/locales/ja-JP/components.json +6 -0
  40. package/locales/ja-JP/plugin.json +1 -0
  41. package/locales/ko-KR/chat.json +20 -0
  42. package/locales/ko-KR/common.json +1 -0
  43. package/locales/ko-KR/components.json +6 -0
  44. package/locales/ko-KR/plugin.json +1 -0
  45. package/locales/nl-NL/chat.json +20 -0
  46. package/locales/nl-NL/common.json +1 -0
  47. package/locales/nl-NL/components.json +6 -0
  48. package/locales/nl-NL/plugin.json +1 -0
  49. package/locales/pl-PL/chat.json +20 -0
  50. package/locales/pl-PL/common.json +1 -0
  51. package/locales/pl-PL/components.json +6 -0
  52. package/locales/pl-PL/plugin.json +1 -0
  53. package/locales/pt-BR/chat.json +20 -0
  54. package/locales/pt-BR/common.json +1 -0
  55. package/locales/pt-BR/components.json +6 -0
  56. package/locales/pt-BR/plugin.json +1 -0
  57. package/locales/ru-RU/chat.json +20 -0
  58. package/locales/ru-RU/common.json +1 -0
  59. package/locales/ru-RU/components.json +6 -0
  60. package/locales/ru-RU/plugin.json +1 -0
  61. package/locales/tr-TR/chat.json +20 -0
  62. package/locales/tr-TR/common.json +1 -0
  63. package/locales/tr-TR/components.json +6 -0
  64. package/locales/tr-TR/plugin.json +1 -0
  65. package/locales/vi-VN/chat.json +20 -0
  66. package/locales/vi-VN/common.json +1 -0
  67. package/locales/vi-VN/components.json +6 -0
  68. package/locales/vi-VN/plugin.json +1 -0
  69. package/locales/zh-CN/chat.json +20 -0
  70. package/locales/zh-CN/common.json +1 -0
  71. package/locales/zh-CN/components.json +6 -0
  72. package/locales/zh-CN/plugin.json +1 -0
  73. package/locales/zh-TW/chat.json +20 -0
  74. package/locales/zh-TW/common.json +1 -0
  75. package/locales/zh-TW/components.json +6 -0
  76. package/locales/zh-TW/plugin.json +1 -0
  77. package/package.json +1 -1
  78. package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
  79. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
  80. package/packages/agent-runtime/src/types/state.ts +7 -1
  81. package/packages/const/src/settings/tool.ts +1 -5
  82. package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
  83. package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
  84. package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
  85. package/packages/model-runtime/src/utils/modelParse.ts +6 -0
  86. package/packages/types/src/tool/builtin.ts +9 -0
  87. package/packages/types/src/tool/intervention.ts +32 -2
  88. package/packages/types/src/user/settings/tool.ts +3 -27
  89. package/src/config/modelProviders/wenxin.ts +2 -3
  90. package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
  91. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
  92. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
  93. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
  94. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
  95. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
  96. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
  97. package/src/features/LocalFile/LocalFile.tsx +55 -5
  98. package/src/locales/default/components.ts +6 -0
  99. package/src/locales/default/plugin.ts +1 -0
  100. package/src/services/electron/localFileService.ts +4 -0
  101. package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
  102. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
  103. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
  104. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
  105. package/src/store/user/selectors.ts +1 -0
  106. package/src/store/user/slices/settings/action.ts +12 -0
  107. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
  108. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  109. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
  110. package/src/store/user/slices/settings/selectors/settings.ts +0 -5
  111. package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
  112. package/src/tools/interventions.ts +8 -0
  113. package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
  114. package/src/tools/local-system/Intervention/index.tsx +17 -0
  115. package/src/tools/local-system/Render/RunCommand/index.tsx +100 -21
  116. package/src/tools/local-system/Render/index.tsx +2 -0
  117. package/src/tools/local-system/index.ts +180 -0
  118. package/src/tools/local-system/systemRole.ts +61 -7
@@ -1,10 +1,12 @@
1
1
  import { LOADING_FLAT } from '@lobechat/const';
2
2
  import { ChatToolResult, ToolIntervention } from '@lobechat/types';
3
3
  import { Suspense, memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
4
5
 
5
6
  import CustomRender from './CustomRender';
6
7
  import ErrorResponse from './ErrorResponse';
7
8
  import Intervention from './Intervention';
9
+ import ModeSelector from './Intervention/ModeSelector';
8
10
  import LoadingPlaceholder from './LoadingPlaceholder';
9
11
  import RejectedResponse from './RejectedResponse';
10
12
 
@@ -99,24 +101,29 @@ const Render = memo<RenderProps>(
99
101
 
100
102
  return (
101
103
  <Suspense fallback={placeholder}>
102
- <CustomRender
103
- content={result.content || ''}
104
- id={toolCallId}
105
- plugin={
106
- type
107
- ? ({
108
- apiName,
109
- arguments: requestArgs || '',
110
- identifier,
111
- type,
112
- } as any)
113
- : undefined
114
- }
115
- pluginState={result.state}
116
- requestArgs={requestArgs}
117
- setShowPluginRender={setShowPluginRender}
118
- showPluginRender={showPluginRender}
119
- />
104
+ <Flexbox gap={8}>
105
+ <CustomRender
106
+ content={result.content || ''}
107
+ id={toolCallId}
108
+ plugin={
109
+ type
110
+ ? ({
111
+ apiName,
112
+ arguments: requestArgs || '',
113
+ identifier,
114
+ type,
115
+ } as any)
116
+ : undefined
117
+ }
118
+ pluginState={result.state}
119
+ requestArgs={requestArgs}
120
+ setShowPluginRender={setShowPluginRender}
121
+ showPluginRender={showPluginRender}
122
+ />
123
+ <div>
124
+ <ModeSelector />
125
+ </div>
126
+ </Flexbox>
120
127
  </Suspense>
121
128
  );
122
129
  },
@@ -1,5 +1,9 @@
1
+ import { Button } from '@lobehub/ui';
2
+ import { Popover, Space } from 'antd';
1
3
  import { createStyles } from 'antd-style';
4
+ import { ExternalLink, FolderOpen } from 'lucide-react';
2
5
  import React from 'react';
6
+ import { useTranslation } from 'react-i18next';
3
7
  import { Flexbox } from 'react-layout-kit';
4
8
 
5
9
  import FileIcon from '@/components/FileIcon';
@@ -13,7 +17,7 @@ const useStyles = createStyles(({ css, token }) => ({
13
17
  padding-inline: 4px 8px;
14
18
  border-radius: 4px;
15
19
 
16
- color: ${token.colorTextSecondary};
20
+ color: ${token.colorText};
17
21
 
18
22
  :hover {
19
23
  color: ${token.colorText};
@@ -39,19 +43,25 @@ interface LocalFileProps {
39
43
 
40
44
  export const LocalFile = ({ name, path, isDirectory = false }: LocalFileProps) => {
41
45
  const { styles } = useStyles();
42
- const handleClick = () => {
43
- if (!path) return;
46
+ const { t } = useTranslation('components');
44
47
 
48
+ const handleOpenFile = () => {
49
+ if (!path) return;
45
50
  localFileService.openLocalFileOrFolder(path, isDirectory);
46
51
  };
47
52
 
48
- return (
53
+ const handleOpenFolder = () => {
54
+ if (!path) return;
55
+ localFileService.openFileFolder(path);
56
+ };
57
+
58
+ const fileContent = (
49
59
  <Flexbox
50
60
  align={'center'}
51
61
  className={styles.container}
52
62
  gap={4}
53
63
  horizontal
54
- onClick={handleClick}
64
+ onClick={isDirectory ? handleOpenFile : undefined}
55
65
  style={{ display: 'inline-flex', verticalAlign: 'middle' }}
56
66
  >
57
67
  <FileIcon fileName={name} isDirectory={isDirectory} size={22} variant={'raw'} />
@@ -60,4 +70,44 @@ export const LocalFile = ({ name, path, isDirectory = false }: LocalFileProps) =
60
70
  </Flexbox>
61
71
  </Flexbox>
62
72
  );
73
+
74
+ // Directory: no popover, just click to open
75
+ if (isDirectory) {
76
+ return fileContent;
77
+ }
78
+
79
+ // File: show popover with two actions
80
+ const popoverContent = (
81
+ <Space.Compact>
82
+ <Button
83
+ icon={ExternalLink}
84
+ onClick={handleOpenFile}
85
+ size="small"
86
+ title={t('LocalFile.action.open')}
87
+ >
88
+ {t('LocalFile.action.open')}
89
+ </Button>
90
+ <Button
91
+ icon={FolderOpen}
92
+ onClick={handleOpenFolder}
93
+ size="small"
94
+ title={t('LocalFile.action.showInFolder')}
95
+ >
96
+ {t('LocalFile.action.showInFolder')}
97
+ </Button>
98
+ </Space.Compact>
99
+ );
100
+
101
+ return (
102
+ <Popover
103
+ arrow={false}
104
+ content={popoverContent}
105
+ styles={{
106
+ body: { padding: 0 },
107
+ }}
108
+ trigger={['hover']}
109
+ >
110
+ {fileContent}
111
+ </Popover>
112
+ );
63
113
  };
@@ -108,6 +108,12 @@ export default {
108
108
  keyPlaceholder: '键',
109
109
  valuePlaceholder: '值',
110
110
  },
111
+ LocalFile: {
112
+ action: {
113
+ open: '打开',
114
+ showInFolder: '在文件夹中显示',
115
+ },
116
+ },
111
117
  MaxTokenSlider: {
112
118
  unlimited: '无限制',
113
119
  },
@@ -256,6 +256,7 @@ export default {
256
256
  moveLocalFiles: '移动文件',
257
257
  readLocalFile: '读取文件内容',
258
258
  renameLocalFile: '重命名',
259
+ runCommand: '执行代码',
259
260
  searchLocalFiles: '搜索文件',
260
261
  writeLocalFile: '写入文件',
261
262
  },
@@ -98,6 +98,10 @@ class LocalFileService {
98
98
  return this.openLocalFile({ path });
99
99
  }
100
100
  }
101
+
102
+ async openFileFolder(path: string) {
103
+ return this.openLocalFolder({ isDirectory: false, path });
104
+ }
101
105
  }
102
106
 
103
107
  export const localFileService = new LocalFileService();
@@ -54,6 +54,7 @@ export class GeneralChatAgent implements Agent {
54
54
 
55
55
  /**
56
56
  * Check if tool calls need human intervention
57
+ * Combines user's global config with tool's own config
57
58
  * Returns [toolsNeedingIntervention, toolsToExecute]
58
59
  */
59
60
  private checkInterventionNeeded(
@@ -63,7 +64,31 @@ export class GeneralChatAgent implements Agent {
63
64
  const toolsNeedingIntervention: ChatToolPayload[] = [];
64
65
  const toolsToExecute: ChatToolPayload[] = [];
65
66
 
67
+ // Get user config (default to 'manual' mode)
68
+ const userConfig = state.userInterventionConfig || { approvalMode: 'manual' };
69
+ const { approvalMode, allowList = [] } = userConfig;
70
+
66
71
  for (const toolCalling of toolsCalling) {
72
+ const { identifier, apiName } = toolCalling;
73
+ const toolKey = `${identifier}/${apiName}`;
74
+
75
+ // Priority 1: User config is 'auto-run', all tools execute directly
76
+ if (approvalMode === 'auto-run') {
77
+ toolsToExecute.push(toolCalling);
78
+ continue;
79
+ }
80
+
81
+ // Priority 2: User config is 'allow-list', check if tool is in whitelist
82
+ if (approvalMode === 'allow-list') {
83
+ if (allowList.includes(toolKey)) {
84
+ toolsToExecute.push(toolCalling);
85
+ } else {
86
+ toolsNeedingIntervention.push(toolCalling);
87
+ }
88
+ continue;
89
+ }
90
+
91
+ // Priority 3: User config is 'manual' (default), use tool's own config
67
92
  const config = this.getToolInterventionConfig(toolCalling, state);
68
93
 
69
94
  // Parse arguments for intervention checking
@@ -82,7 +107,7 @@ export class GeneralChatAgent implements Agent {
82
107
  if (policy === 'never') {
83
108
  toolsToExecute.push(toolCalling);
84
109
  } else {
85
- // 'require' or 'first' (when not confirmed) requires intervention
110
+ // 'required' or undefined requires intervention
86
111
  toolsNeedingIntervention.push(toolCalling);
87
112
  }
88
113
  }
@@ -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';