@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.
- package/CHANGELOG.md +25 -0
- package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
- package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/chat.json +20 -0
- package/locales/ar/common.json +1 -0
- package/locales/ar/components.json +6 -0
- package/locales/ar/plugin.json +1 -0
- package/locales/bg-BG/chat.json +20 -0
- package/locales/bg-BG/common.json +1 -0
- package/locales/bg-BG/components.json +6 -0
- package/locales/bg-BG/plugin.json +1 -0
- package/locales/de-DE/chat.json +20 -0
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/components.json +6 -0
- package/locales/de-DE/plugin.json +1 -0
- package/locales/en-US/chat.json +20 -0
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/components.json +6 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/es-ES/chat.json +20 -0
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/components.json +6 -0
- package/locales/es-ES/plugin.json +1 -0
- package/locales/fa-IR/chat.json +20 -0
- package/locales/fa-IR/common.json +1 -0
- package/locales/fa-IR/components.json +6 -0
- package/locales/fa-IR/plugin.json +1 -0
- package/locales/fr-FR/chat.json +20 -0
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/components.json +6 -0
- package/locales/fr-FR/plugin.json +1 -0
- package/locales/it-IT/chat.json +20 -0
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/components.json +6 -0
- package/locales/it-IT/plugin.json +1 -0
- package/locales/ja-JP/chat.json +20 -0
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/components.json +6 -0
- package/locales/ja-JP/plugin.json +1 -0
- package/locales/ko-KR/chat.json +20 -0
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/components.json +6 -0
- package/locales/ko-KR/plugin.json +1 -0
- package/locales/nl-NL/chat.json +20 -0
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/components.json +6 -0
- package/locales/nl-NL/plugin.json +1 -0
- package/locales/pl-PL/chat.json +20 -0
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/components.json +6 -0
- package/locales/pl-PL/plugin.json +1 -0
- package/locales/pt-BR/chat.json +20 -0
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/components.json +6 -0
- package/locales/pt-BR/plugin.json +1 -0
- package/locales/ru-RU/chat.json +20 -0
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/components.json +6 -0
- package/locales/ru-RU/plugin.json +1 -0
- package/locales/tr-TR/chat.json +20 -0
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/components.json +6 -0
- package/locales/tr-TR/plugin.json +1 -0
- package/locales/vi-VN/chat.json +20 -0
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/components.json +6 -0
- package/locales/vi-VN/plugin.json +1 -0
- package/locales/zh-CN/chat.json +20 -0
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/components.json +6 -0
- package/locales/zh-CN/plugin.json +1 -0
- package/locales/zh-TW/chat.json +20 -0
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/components.json +6 -0
- package/locales/zh-TW/plugin.json +1 -0
- package/package.json +1 -1
- package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
- package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
- package/packages/agent-runtime/src/types/state.ts +7 -1
- package/packages/const/src/settings/tool.ts +1 -5
- package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
- package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
- package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
- package/packages/model-runtime/src/utils/modelParse.ts +6 -0
- package/packages/types/src/tool/builtin.ts +9 -0
- package/packages/types/src/tool/intervention.ts +32 -2
- package/packages/types/src/user/settings/tool.ts +3 -27
- package/src/config/modelProviders/wenxin.ts +2 -3
- package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
- package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
- package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
- package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
- package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
- package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
- package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
- package/src/features/LocalFile/LocalFile.tsx +55 -5
- package/src/locales/default/components.ts +6 -0
- package/src/locales/default/plugin.ts +1 -0
- package/src/services/electron/localFileService.ts +4 -0
- package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
- package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
- package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
- package/src/store/user/selectors.ts +1 -0
- package/src/store/user/slices/settings/action.ts +12 -0
- package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
- package/src/store/user/slices/settings/selectors/index.ts +1 -0
- package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
- package/src/store/user/slices/settings/selectors/settings.ts +0 -5
- package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
- package/src/tools/interventions.ts +8 -0
- package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
- package/src/tools/local-system/Intervention/index.tsx +17 -0
- package/src/tools/local-system/Render/RunCommand/index.tsx +100 -21
- package/src/tools/local-system/Render/index.tsx +2 -0
- package/src/tools/local-system/index.ts +180 -0
- 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
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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={
|
|
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
|
};
|
|
@@ -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
|
-
// '
|
|
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 {
|
|
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 {
|
|
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.
|
|
162
|
-
const
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
sessionId: activeId,
|
|
185
|
-
topicId: activeTopicId,
|
|
186
|
-
threadId: activeThreadId,
|
|
187
|
-
},
|
|
188
|
-
toolManifestMap,
|
|
161
|
+
parentMessageId: toolMessageId,
|
|
162
|
+
threadId: activeThreadId,
|
|
189
163
|
});
|
|
190
164
|
|
|
191
|
-
|
|
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
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
509
|
-
let nextContext
|
|
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',
|