From 14dd5d09dd5fec6932cc56efdd8150c0e783847b Mon Sep 17 00:00:00 2001 From: Rdmclin2 Date: Wed, 11 Mar 2026 23:43:33 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20support=20runtime=20config?= =?UTF-8?q?=20(#12902)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: support runtime config * fix: cloud sandbox default tool ids --- locales/en-US/chat.json | 9 + locales/zh-CN/chat.json | 9 + packages/builtin-tools/src/index.ts | 5 +- packages/types/src/agent/agentConfig.ts | 24 +- packages/types/src/agent/chatConfig.ts | 19 +- src/features/ChatInput/Desktop/index.tsx | 4 + .../RuntimeConfig/ApprovalMode.tsx} | 45 ++- .../RuntimeConfig/WorkingDirectory.tsx} | 6 +- .../ChatInput/RuntimeConfig/index.tsx | 276 ++++++++++++++++++ .../Tool/Detail/Intervention/Fallback.tsx | 4 +- .../Tool/Detail/Intervention/index.tsx | 6 +- .../AssistantGroup/Tool/Detail/index.tsx | 6 - src/helpers/toolEngineering/index.ts | 5 + src/locales/default/chat.ts | 9 + .../Header/WorkingDirectory/index.tsx | 113 ------- .../features/Conversation/Header/index.tsx | 3 - .../(main)/home/features/InputArea/index.tsx | 1 + src/routes/(main)/home/index.tsx | 8 +- .../modules/Mecha/AgentToolsEngine/index.ts | 16 +- .../modules/Mecha/AgentToolsEngine/types.ts | 5 +- .../agentRuntime/AgentRuntimeService.ts | 2 +- .../agent/selectors/agentByIdSelectors.ts | 16 +- .../selectors/chatConfigByIdSelectors.test.ts | 125 ++++++++ .../selectors/chatConfigByIdSelectors.ts | 30 +- .../agent/selectors/chatConfigSelectors.ts | 8 + src/store/agent/selectors/selectors.ts | 14 +- src/store/agent/slices/agent/action.ts | 8 +- .../user/slices/settings/selectors/index.ts | 2 +- .../settings/selectors/toolIntervention.ts | 4 +- 29 files changed, 589 insertions(+), 193 deletions(-) rename src/features/{Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ModeSelector.tsx => ChatInput/RuntimeConfig/ApprovalMode.tsx} (81%) rename src/{routes/(main)/agent/features/Conversation/Header/WorkingDirectory/WorkingDirectoryContent.tsx => features/ChatInput/RuntimeConfig/WorkingDirectory.tsx} (96%) create mode 100644 src/features/ChatInput/RuntimeConfig/index.tsx delete mode 100644 src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/index.tsx diff --git a/locales/en-US/chat.json b/locales/en-US/chat.json index 0589f524f9..360801990e 100644 --- a/locales/en-US/chat.json +++ b/locales/en-US/chat.json @@ -241,6 +241,14 @@ "rag.userQuery.actions.regenerate": "Regenerate Query", "regenerate": "Regenerate", "roleAndArchive": "Agent Profile & History", + "runtimeEnv.mode.cloud": "Cloud Sandbox", + "runtimeEnv.mode.cloudDesc": "Run in a secure cloud sandbox", + "runtimeEnv.mode.local": "Local", + "runtimeEnv.mode.localDesc": "Access local files and commands", + "runtimeEnv.mode.none": "None", + "runtimeEnv.mode.noneDesc": "No runtime environment", + "runtimeEnv.selectMode": "Select Runtime Environment", + "runtimeEnv.title": "Runtime Environment", "search.grounding.imageSearchQueries": "Image Search Keywords", "search.grounding.imageTitle": "Found {{count}} images", "search.grounding.searchQueries": "Search Keywords", @@ -391,6 +399,7 @@ "tokenTag.overload": "Exceeded Limit", "tokenTag.remained": "Remaining", "tokenTag.used": "Used", + "tool.intervention.approvalMode": "Approval Mode", "tool.intervention.approve": "Approve", "tool.intervention.approveAndRemember": "Approve and Remember", "tool.intervention.approveOnce": "Approve This Time Only", diff --git a/locales/zh-CN/chat.json b/locales/zh-CN/chat.json index 97c42d8a95..90aa45da6a 100644 --- a/locales/zh-CN/chat.json +++ b/locales/zh-CN/chat.json @@ -241,6 +241,14 @@ "rag.userQuery.actions.regenerate": "重新生成 Query", "regenerate": "重新生成", "roleAndArchive": "助理档案与记录", + "runtimeEnv.mode.cloud": "云端沙箱", + "runtimeEnv.mode.cloudDesc": "在安全的云端沙箱中运行", + "runtimeEnv.mode.local": "本地", + "runtimeEnv.mode.localDesc": "访问本地文件和命令", + "runtimeEnv.mode.none": "不启用", + "runtimeEnv.mode.noneDesc": "不启用运行环境", + "runtimeEnv.selectMode": "选择运行环境", + "runtimeEnv.title": "运行环境", "search.grounding.imageSearchQueries": "图片搜索关键词", "search.grounding.imageTitle": "找到 {{count}} 张图片", "search.grounding.searchQueries": "搜索关键词", @@ -391,6 +399,7 @@ "tokenTag.overload": "超出限制", "tokenTag.remained": "剩余", "tokenTag.used": "已使用", + "tool.intervention.approvalMode": "审批模式", "tool.intervention.approve": "批准", "tool.intervention.approveAndRemember": "批准并记住", "tool.intervention.approveOnce": "仅本次批准", diff --git a/packages/builtin-tools/src/index.ts b/packages/builtin-tools/src/index.ts index eee1abc401..804f52257f 100644 --- a/packages/builtin-tools/src/index.ts +++ b/packages/builtin-tools/src/index.ts @@ -29,6 +29,8 @@ export const defaultToolIds = [ WebBrowsingManifest.identifier, KnowledgeBaseManifest.identifier, MemoryManifest.identifier, + LocalSystemManifest.identifier, + CloudSandboxManifest.identifier, ]; export const builtinTools: LobeBuiltinTool[] = [ @@ -55,7 +57,7 @@ export const builtinTools: LobeBuiltinTool[] = [ }, { discoverable: isDesktop, - hidden: !isDesktop, + hidden: true, identifier: LocalSystemManifest.identifier, manifest: LocalSystemManifest, type: 'builtin', @@ -73,6 +75,7 @@ export const builtinTools: LobeBuiltinTool[] = [ type: 'builtin', }, { + hidden: true, identifier: CloudSandboxManifest.identifier, manifest: CloudSandboxManifest, type: 'builtin', diff --git a/packages/types/src/agent/agentConfig.ts b/packages/types/src/agent/agentConfig.ts index d700156592..05e167d780 100644 --- a/packages/types/src/agent/agentConfig.ts +++ b/packages/types/src/agent/agentConfig.ts @@ -8,15 +8,25 @@ export type AgentMode = 'auto' | 'plan' | 'ask' | 'implement'; /** - * Local System configuration (desktop only) + * Runtime environment mode + * - local: Access local files and commands (desktop only) + * - cloud: Run in cloud sandbox + * - none: No runtime environment */ -export interface LocalSystemConfig { +export type RuntimeEnvMode = 'cloud' | 'local' | 'none'; + +export type RuntimePlatform = 'desktop' | 'web'; + +/** + * Runtime environment configuration + */ +export interface RuntimeEnvConfig { /** - * Local System working directory (desktop only) + * Runtime environment mode per platform + */ + runtimeMode?: Partial>; + /** + * Working directory (desktop only) */ workingDirectory?: string; - - // Future extensions: - // allowedPaths?: string[]; - // deniedCommands?: string[]; } diff --git a/packages/types/src/agent/chatConfig.ts b/packages/types/src/agent/chatConfig.ts index 8594539fa3..a66f9550d8 100644 --- a/packages/types/src/agent/chatConfig.ts +++ b/packages/types/src/agent/chatConfig.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { type SearchMode } from '../search'; import { type UserMemoryEffort } from '../user/settings/memory'; -import { type LocalSystemConfig } from './agentConfig'; +import { type RuntimeEnvConfig } from './agentConfig'; export interface WorkingModel { model: string; @@ -95,12 +95,12 @@ export interface LobeAgentChatConfig extends AgentMemoryChatConfig { */ imageResolution2?: '512px' | '1K' | '2K' | '4K'; inputTemplate?: string; - /** - * Local System configuration (desktop only) - */ - localSystem?: LocalSystemConfig; reasoningBudgetToken?: number; reasoningEffort?: 'low' | 'medium' | 'high'; + /** + * Runtime environment configuration (desktop only) + */ + runtimeEnv?: RuntimeEnvConfig; searchFCModel?: WorkingModel; searchMode?: SearchMode; @@ -130,9 +130,12 @@ export interface LobeAgentChatConfig extends AgentMemoryChatConfig { } /** - * Zod schema for LocalSystemConfig + * Zod schema for RuntimeEnvConfig */ -export const LocalSystemConfigSchema = z.object({ +const runtimeEnvModeEnum = z.enum(['local', 'cloud', 'none']); + +export const RuntimeEnvConfigSchema = z.object({ + runtimeMode: z.record(z.string(), runtimeEnvModeEnum).optional(), workingDirectory: z.string().optional(), }); @@ -173,7 +176,7 @@ export const AgentChatConfigSchema = z imageAspectRatio2: z.string().optional(), imageResolution: z.enum(['1K', '2K', '4K']).optional(), imageResolution2: z.enum(['512px', '1K', '2K', '4K']).optional(), - localSystem: LocalSystemConfigSchema.optional(), + runtimeEnv: RuntimeEnvConfigSchema.optional(), reasoningBudgetToken: z.number().optional(), reasoningEffort: z.enum(['low', 'medium', 'high']).optional(), searchFCModel: z diff --git a/src/features/ChatInput/Desktop/index.tsx b/src/features/ChatInput/Desktop/index.tsx index 651acfef7d..a1be36286e 100644 --- a/src/features/ChatInput/Desktop/index.tsx +++ b/src/features/ChatInput/Desktop/index.tsx @@ -18,6 +18,7 @@ import { systemStatusSelectors } from '@/store/global/selectors'; import { type ActionToolbarProps } from '../ActionBar'; import ActionBar from '../ActionBar'; import InputEditor from '../InputEditor'; +import RuntimeConfig from '../RuntimeConfig'; import SendArea from '../SendArea'; import TypoBar from '../TypoBar'; import ContextContainer from './ContextContainer'; @@ -59,11 +60,13 @@ interface DesktopChatInputProps extends ActionToolbarProps { leftContent?: ReactNode; sendAreaPrefix?: ReactNode; showFootnote?: boolean; + showRuntimeConfig?: boolean; } const DesktopChatInput = memo( ({ showFootnote, + showRuntimeConfig = true, inputContainerProps, extentHeaderContent, actionBarStyle, @@ -151,6 +154,7 @@ const DesktopChatInput = memo( > + {showRuntimeConfig && } {showFootnote && !expand && (
diff --git a/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ModeSelector.tsx b/src/features/ChatInput/RuntimeConfig/ApprovalMode.tsx similarity index 81% rename from src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ModeSelector.tsx rename to src/features/ChatInput/RuntimeConfig/ApprovalMode.tsx index 08057eca02..2790a18610 100644 --- a/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/ModeSelector.tsx +++ b/src/features/ChatInput/RuntimeConfig/ApprovalMode.tsx @@ -1,14 +1,13 @@ import { type MenuProps } from '@lobehub/ui'; -import { Button, Center, DropdownMenu, Icon } from '@lobehub/ui'; +import { Button, Center, DropdownMenu, Icon, Tooltip } from '@lobehub/ui'; import { createStaticStyles } from 'antd-style'; import { Check, ChevronDown, Hand, ListChecks, Zap } from 'lucide-react'; -import { memo, useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useUserStore } from '@/store/user'; import { toolInterventionSelectors } from '@/store/user/selectors'; - -import { type ApprovalMode } from './index'; +import { type ApprovalMode } from '@/store/user/slices/settings/selectors'; const styles = createStaticStyles(({ css, cssVar }) => ({ icon: css` @@ -39,6 +38,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({ const ModeSelector = memo(() => { const { t } = useTranslation('chat'); + const [dropdownOpen, setDropdownOpen] = useState(false); const approvalMode = useUserStore(toolInterventionSelectors.approvalMode); const updateHumanIntervention = useUserStore((s) => s.updateHumanIntervention); @@ -112,18 +112,33 @@ const ModeSelector = memo(() => { [approvalMode, modeLabels, handleModeChange, styles, t], ); + const button = ( + + ); + return ( - - + +
+ {dropdownOpen ? ( + button + ) : ( + {button} + )} +
); }); diff --git a/src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/WorkingDirectoryContent.tsx b/src/features/ChatInput/RuntimeConfig/WorkingDirectory.tsx similarity index 96% rename from src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/WorkingDirectoryContent.tsx rename to src/features/ChatInput/RuntimeConfig/WorkingDirectory.tsx index c7ab23bef3..b3f9991748 100644 --- a/src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/WorkingDirectoryContent.tsx +++ b/src/features/ChatInput/RuntimeConfig/WorkingDirectory.tsx @@ -27,7 +27,7 @@ const WorkingDirectoryContent = memo(({ agentId, o const activeTopicId = useChatStore((s) => s.activeTopicId); // Actions - const updateAgentLocalSystemConfig = useAgentStore((s) => s.updateAgentLocalSystemConfigById); + const updateAgentRuntimeEnvConfig = useAgentStore((s) => s.updateAgentRuntimeEnvConfigById); const updateTopicMetadata = useChatStore((s) => s.updateTopicMetadata); // Local state for editing @@ -58,7 +58,7 @@ const WorkingDirectoryContent = memo(({ agentId, o setLoading(true); try { // Save agent working directory - await updateAgentLocalSystemConfig(agentId, { + await updateAgentRuntimeEnvConfig(agentId, { workingDirectory: agentDir || undefined, }); @@ -85,7 +85,7 @@ const WorkingDirectoryContent = memo(({ agentId, o useTopicOverride, topicDir, topicWorkingDirectory, - updateAgentLocalSystemConfig, + updateAgentRuntimeEnvConfig, updateTopicMetadata, onClose, ]); diff --git a/src/features/ChatInput/RuntimeConfig/index.tsx b/src/features/ChatInput/RuntimeConfig/index.tsx new file mode 100644 index 0000000000..44b0011bae --- /dev/null +++ b/src/features/ChatInput/RuntimeConfig/index.tsx @@ -0,0 +1,276 @@ +import { isDesktop } from '@lobechat/const'; +import { type RuntimeEnvMode } from '@lobechat/types'; +import { Flexbox, Icon, Popover, Skeleton, Tooltip } from '@lobehub/ui'; +import { createStaticStyles, cssVar, cx } from 'antd-style'; +import { + ChevronDownIcon, + CloudIcon, + FolderIcon, + LaptopIcon, + MonitorOffIcon, + SquircleDashed, +} from 'lucide-react'; +import { memo, useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { useAgentStore } from '@/store/agent'; +import { agentByIdSelectors, chatConfigByIdSelectors } from '@/store/agent/selectors'; +import { useChatStore } from '@/store/chat'; +import { topicSelectors } from '@/store/chat/selectors'; + +import { useAgentId } from '../hooks/useAgentId'; +import { useUpdateAgentConfig } from '../hooks/useUpdateAgentConfig'; +import ApprovalMode from './ApprovalMode'; +import WorkingDirectory from './WorkingDirectory'; + +const MODE_ICONS: Record = { + cloud: CloudIcon, + local: LaptopIcon, + none: MonitorOffIcon, +}; + +const styles = createStaticStyles(({ css }) => ({ + bar: css` + padding-block: 0; + padding-inline: 4px; + `, + button: css` + cursor: pointer; + + display: flex; + gap: 6px; + align-items: center; + + height: 28px; + padding-inline: 8px; + border-radius: 6px; + + font-size: 12px; + color: ${cssVar.colorTextSecondary}; + + transition: all 0.2s; + + &:hover { + color: ${cssVar.colorText}; + background: ${cssVar.colorFillSecondary}; + } + `, + modeDesc: css` + font-size: 12px; + color: ${cssVar.colorTextTertiary}; + `, + modeOption: css` + cursor: pointer; + + width: 100%; + padding-block: 8px; + padding-inline: 8px; + border-radius: ${cssVar.borderRadius}; + + transition: background-color 0.2s; + + &:hover { + background: ${cssVar.colorFillTertiary}; + } + `, + modeOptionActive: css` + background: ${cssVar.colorFillTertiary}; + `, + modeOptionDesc: css` + font-size: 12px; + color: ${cssVar.colorTextDescription}; + `, + modeOptionIcon: css` + border: 1px solid ${cssVar.colorFillTertiary}; + border-radius: ${cssVar.borderRadius}; + background: ${cssVar.colorBgElevated}; + `, + modeOptionTitle: css` + font-size: 14px; + font-weight: 500; + color: ${cssVar.colorText}; + `, +})); + +const RuntimeConfig = memo(() => { + const { t } = useTranslation('chat'); + const { t: tPlugin } = useTranslation('plugin'); + const agentId = useAgentId(); + const { updateAgentChatConfig } = useUpdateAgentConfig(); + const [dirPopoverOpen, setDirPopoverOpen] = useState(false); + const [modePopoverOpen, setModePopoverOpen] = useState(false); + + const [isLoading, runtimeMode] = useAgentStore((s) => [ + agentByIdSelectors.isAgentConfigLoadingById(agentId)(s), + chatConfigByIdSelectors.getRuntimeModeById(agentId)(s), + ]); + + // Get working directory + const topicWorkingDirectory = useChatStore(topicSelectors.currentTopicWorkingDirectory); + const agentWorkingDirectory = useAgentStore((s) => + agentId ? agentByIdSelectors.getAgentWorkingDirectoryById(agentId)(s) : undefined, + ); + const effectiveWorkingDirectory = topicWorkingDirectory || agentWorkingDirectory; + + const switchMode = useCallback( + async (mode: RuntimeEnvMode) => { + if (mode === runtimeMode) return; + + const platform = isDesktop ? 'desktop' : 'web'; + + await updateAgentChatConfig({ + runtimeEnv: { runtimeMode: { [platform]: mode } }, + }); + }, + [runtimeMode, updateAgentChatConfig], + ); + + // Skeleton placeholder to prevent layout jump during loading + if (!agentId || isLoading) { + return ( + + + + + ); + } + + const ModeIcon = MODE_ICONS[runtimeMode]; + const modeLabel = t(`runtimeEnv.mode.${runtimeMode}`); + + const displayName = effectiveWorkingDirectory + ? effectiveWorkingDirectory.split('/').findLast(Boolean) || effectiveWorkingDirectory + : tPlugin('localSystem.workingDirectory.notSet'); + + const modes: { desc: string; icon: typeof LaptopIcon; label: string; mode: RuntimeEnvMode }[] = [ + // Local mode is desktop-only + ...(isDesktop + ? [ + { + desc: t('runtimeEnv.mode.localDesc'), + icon: LaptopIcon, + label: t('runtimeEnv.mode.local'), + mode: 'local' as RuntimeEnvMode, + }, + ] + : []), + { + desc: t('runtimeEnv.mode.cloudDesc'), + icon: CloudIcon, + label: t('runtimeEnv.mode.cloud'), + mode: 'cloud', + }, + { + desc: t('runtimeEnv.mode.noneDesc'), + icon: MonitorOffIcon, + label: t('runtimeEnv.mode.none'), + mode: 'none', + }, + ]; + + const modeContent = ( + + {modes.map(({ mode, icon, label, desc }) => ( + switchMode(mode)} + > + + + + +
{label}
+
{desc}
+
+
+ ))} +
+ ); + + const modeButton = ( +
+ + {modeLabel} + +
+ ); + + const dirButton = ( +
+ + {displayName} + +
+ ); + + const rightContent = () => { + if (runtimeMode === 'local') { + return ( + setDirPopoverOpen(false)} />} + open={dirPopoverOpen} + placement="bottomRight" + trigger="click" + onOpenChange={setDirPopoverOpen} + > +
+ {dirPopoverOpen ? ( + dirButton + ) : ( + + {dirButton} + + )} +
+
+ ); + } + + return null; + }; + + return ( + + {/* Left: Runtime env + working directory */} + + +
+ {modePopoverOpen ? ( + modeButton + ) : ( + {modeButton} + )} +
+
+ {rightContent()} +
+ + {/* Right: Permission control */} + +
+ ); +}); + +RuntimeConfig.displayName = 'RuntimeConfig'; + +export default RuntimeConfig; diff --git a/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/Fallback.tsx b/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/Fallback.tsx index 87b10499ff..8df8bb53c7 100644 --- a/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/Fallback.tsx +++ b/src/features/Conversation/Messages/AssistantGroup/Tool/Detail/Intervention/Fallback.tsx @@ -11,7 +11,6 @@ import { useConversationStore } from '../../../../../store'; import Arguments from '../Arguments'; import ApprovalActions from './ApprovalActions'; import KeyValueEditor from './KeyValueEditor'; -import ModeSelector from './ModeSelector'; interface FallbackInterventionProps { apiName: string; @@ -77,8 +76,7 @@ const FallbackIntervention = memo( } /> - - + ( registerBeforeApprove={registerBeforeApprove} onArgsChange={handleArgsChange} /> - - + ( type: type as any, }} /> - {!disableEditing && ( -
- -
- )}
); diff --git a/src/helpers/toolEngineering/index.ts b/src/helpers/toolEngineering/index.ts index 3370957874..ca6e31016d 100644 --- a/src/helpers/toolEngineering/index.ts +++ b/src/helpers/toolEngineering/index.ts @@ -1,7 +1,9 @@ /** * Tools Engineering - Unified tools processing using ToolsEngine */ +import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox'; import { KnowledgeBaseManifest } from '@lobechat/builtin-tool-knowledge-base'; +import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system'; import { MemoryManifest } from '@lobechat/builtin-tool-memory'; import { WebBrowsingManifest } from '@lobechat/builtin-tool-web-browsing'; import { defaultToolIds } from '@lobechat/builtin-tools'; @@ -102,7 +104,10 @@ export const createAgentToolsEngine = (workingModel: WorkingModel) => { return undefined; // fall through to rules }, rules: { + [CloudSandboxManifest.identifier]: + agentChatConfigSelectors.isCloudSandboxEnabled(agentState), [KnowledgeBaseManifest.identifier]: agentSelectors.hasEnabledKnowledgeBases(agentState), + [LocalSystemManifest.identifier]: agentChatConfigSelectors.isLocalSystemEnabled(agentState), [MemoryManifest.identifier]: agentChatConfigSelectors.isMemoryToolEnabled(agentState), [WebBrowsingManifest.identifier]: searchConfig.useApplicationBuiltinSearchTool, }, diff --git a/src/locales/default/chat.ts b/src/locales/default/chat.ts index 9338b9fc99..9ed9524c02 100644 --- a/src/locales/default/chat.ts +++ b/src/locales/default/chat.ts @@ -273,6 +273,14 @@ export default { 'memory.title': 'Memory', 'search.grounding.imageSearchQueries': 'Image Search Keywords', 'search.grounding.imageTitle': 'Found {{count}} images', + 'runtimeEnv.mode.cloud': 'Cloud Sandbox', + 'runtimeEnv.mode.cloudDesc': 'Run in a secure cloud sandbox', + 'runtimeEnv.mode.local': 'Local', + 'runtimeEnv.mode.localDesc': 'Access local files and commands', + 'runtimeEnv.mode.none': 'Off', + 'runtimeEnv.mode.noneDesc': 'Disable runtime environment', + 'runtimeEnv.selectMode': 'Select Runtime Environment', + 'runtimeEnv.title': 'Runtime Environment', 'search.grounding.searchQueries': 'Search Keywords', 'search.grounding.title': 'Found {{count}} results', 'search.mode.auto.desc': 'Search the web automatically when needed.', @@ -425,6 +433,7 @@ export default { 'tokenTag.overload': 'Exceeded Limit', 'tokenTag.remained': 'Remaining', 'tokenTag.used': 'Used', + 'tool.intervention.approvalMode': 'Approval Mode', 'tool.intervention.approve': 'Approve', 'tool.intervention.approveAndRemember': 'Approve and Remember', 'tool.intervention.approveOnce': 'Approve This Time Only', diff --git a/src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/index.tsx b/src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/index.tsx deleted file mode 100644 index 709700bdf4..0000000000 --- a/src/routes/(main)/agent/features/Conversation/Header/WorkingDirectory/index.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system'; -import { Flexbox, Icon, Popover, Tooltip } from '@lobehub/ui'; -import { createStaticStyles, cx } from 'antd-style'; -import { LaptopIcon, SquircleDashed } from 'lucide-react'; -import { memo, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { useAgentStore } from '@/store/agent'; -import { agentByIdSelectors } from '@/store/agent/selectors'; -import { useChatStore } from '@/store/chat'; -import { topicSelectors } from '@/store/chat/selectors'; - -import WorkingDirectoryContent from './WorkingDirectoryContent'; - -const styles = createStaticStyles(({ css, cssVar }) => { - return { - base: css` - border-radius: 6px; - color: ${cssVar.colorTextTertiary}; - background-color: ${cssVar.colorFillTertiary}; - - :hover { - color: ${cssVar.colorTextSecondary}; - background-color: ${cssVar.colorFillSecondary}; - } - `, - filled: css` - font-family: ${cssVar.fontFamilyCode}; - color: ${cssVar.colorText} !important; - `, - }; -}); - -const WorkingDirectory = memo(() => { - const { t } = useTranslation('plugin'); - const [open, setOpen] = useState(false); - - const agentId = useAgentStore((s) => s.activeAgentId); - - // Check if local-system plugin is enabled for current agent - const plugins = useAgentStore((s) => - agentId ? agentByIdSelectors.getAgentPluginsById(agentId)(s) : [], - ); - const isLocalSystemEnabled = useMemo( - () => plugins.includes(LocalSystemManifest.identifier), - [plugins], - ); - - // Get working directory from Topic (higher priority) or Agent (fallback) - const topicWorkingDirectory = useChatStore(topicSelectors.currentTopicWorkingDirectory); - const agentWorkingDirectory = useAgentStore((s) => - agentId ? agentByIdSelectors.getAgentWorkingDirectoryById(agentId)(s) : undefined, - ); - - const effectiveWorkingDirectory = topicWorkingDirectory || agentWorkingDirectory; - - // Only show when local-system is enabled and agent exists - if (!agentId || !isLocalSystemEnabled) return null; - - // Get last folder name for display - const hasWorkingDirectory = !!effectiveWorkingDirectory; - - const displayName = effectiveWorkingDirectory - ? effectiveWorkingDirectory.split('/').findLast(Boolean) || effectiveWorkingDirectory - : t('localSystem.workingDirectory.notSet'); - - const content = hasWorkingDirectory ? ( - - - {displayName} - - ) : ( - - - {t('localSystem.workingDirectory.notSet')} - - ); - return ( - setOpen(false)} />} - open={open} - placement="bottomRight" - trigger="click" - onOpenChange={setOpen} - > -
- {open ? ( - content - ) : ( - - {content} - - )} -
-
- ); -}); - -WorkingDirectory.displayName = 'WorkingDirectory'; - -export default WorkingDirectory; diff --git a/src/routes/(main)/agent/features/Conversation/Header/index.tsx b/src/routes/(main)/agent/features/Conversation/Header/index.tsx index e20cc09ec9..41e2876cf1 100644 --- a/src/routes/(main)/agent/features/Conversation/Header/index.tsx +++ b/src/routes/(main)/agent/features/Conversation/Header/index.tsx @@ -1,6 +1,5 @@ 'use client'; -import { isDesktop } from '@lobechat/const'; import { Flexbox } from '@lobehub/ui'; import { cssVar } from 'antd-style'; import { memo } from 'react'; @@ -11,7 +10,6 @@ import HeaderActions from './HeaderActions'; import NotebookButton from './NotebookButton'; import ShareButton from './ShareButton'; import Tags from './Tags'; -import WorkingDirectory from './WorkingDirectory'; const Header = memo(() => { return ( @@ -23,7 +21,6 @@ const Header = memo(() => { } right={ - {isDesktop && } diff --git a/src/routes/(main)/home/features/InputArea/index.tsx b/src/routes/(main)/home/features/InputArea/index.tsx index b0f27a2fc7..ea1c7bda4d 100644 --- a/src/routes/(main)/home/features/InputArea/index.tsx +++ b/src/routes/(main)/home/features/InputArea/index.tsx @@ -112,6 +112,7 @@ const InputArea = () => { dropdownPlacement="bottomLeft" extraActionItems={extraActionItems} inputContainerProps={inputContainerProps} + showRuntimeConfig={false} /> diff --git a/src/routes/(main)/home/index.tsx b/src/routes/(main)/home/index.tsx index 8c92a1805b..21c06953f9 100644 --- a/src/routes/(main)/home/index.tsx +++ b/src/routes/(main)/home/index.tsx @@ -16,7 +16,13 @@ const Home: FC = () => { return ( <> {isHomeRoute && } - } /> + + + + } + /> diff --git a/src/server/modules/Mecha/AgentToolsEngine/index.ts b/src/server/modules/Mecha/AgentToolsEngine/index.ts index 836fdec602..f381c3a7d2 100644 --- a/src/server/modules/Mecha/AgentToolsEngine/index.ts +++ b/src/server/modules/Mecha/AgentToolsEngine/index.ts @@ -9,6 +9,7 @@ * - Gets model capabilities from provided function * - No dependency on frontend stores (useToolStore, useAgentStore, etc.) */ +import { CloudSandboxManifest } from '@lobechat/builtin-tool-cloud-sandbox'; import { KnowledgeBaseManifest } from '@lobechat/builtin-tool-knowledge-base'; import { LocalSystemManifest } from '@lobechat/builtin-tool-local-system'; import { MemoryManifest } from '@lobechat/builtin-tool-memory'; @@ -100,11 +101,19 @@ export const createServerAgentToolsEngine = ( const searchMode = agentConfig.chatConfig?.searchMode ?? 'auto'; const isSearchEnabled = searchMode !== 'off'; + // Determine runtime mode based on platform + const isDesktopClient = !!deviceContext?.gatewayConfigured; + const platform = isDesktopClient ? 'desktop' : 'web'; + const runtimeMode = + agentConfig.chatConfig?.runtimeEnv?.runtimeMode?.[platform] ?? + (isDesktopClient ? 'local' : 'none'); + log( - 'Creating agent tools engine for model=%s, provider=%s, searchMode=%s, additionalManifests=%d, deviceGateway=%s', + 'Creating agent tools engine for model=%s, provider=%s, searchMode=%s, runtimeMode=%s, additionalManifests=%d, deviceGateway=%s', model, provider, searchMode, + runtimeMode, additionalManifests?.length ?? 0, !!deviceContext?.gatewayConfigured, ); @@ -116,9 +125,12 @@ export const createServerAgentToolsEngine = ( defaultToolIds, enableChecker: createEnableChecker({ rules: { + [CloudSandboxManifest.identifier]: runtimeMode === 'cloud', [KnowledgeBaseManifest.identifier]: hasEnabledKnowledgeBases, [LocalSystemManifest.identifier]: - !!deviceContext?.gatewayConfigured && !!deviceContext?.deviceOnline, + runtimeMode === 'local' && + !!deviceContext?.gatewayConfigured && + !!deviceContext?.deviceOnline, [MemoryManifest.identifier]: globalMemoryEnabled, [RemoteDeviceManifest.identifier]: !!deviceContext?.gatewayConfigured, [WebBrowsingManifest.identifier]: isSearchEnabled, diff --git a/src/server/modules/Mecha/AgentToolsEngine/types.ts b/src/server/modules/Mecha/AgentToolsEngine/types.ts index bf6e30f5e0..73544ee89c 100644 --- a/src/server/modules/Mecha/AgentToolsEngine/types.ts +++ b/src/server/modules/Mecha/AgentToolsEngine/types.ts @@ -1,5 +1,5 @@ import { type LobeToolManifest, type PluginEnableChecker } from '@lobechat/context-engine'; -import { type LobeTool } from '@lobechat/types'; +import { type LobeTool, type RuntimeEnvConfig } from '@lobechat/types'; /** * Installed plugin with manifest @@ -36,8 +36,9 @@ export interface ServerCreateAgentToolsEngineParams { additionalManifests?: LobeToolManifest[]; /** Agent configuration containing plugins array */ agentConfig: { - /** Optional agent chat config with searchMode */ + /** Optional agent chat config */ chatConfig?: { + runtimeEnv?: RuntimeEnvConfig; searchMode?: 'off' | 'on' | 'auto'; }; /** Plugin IDs enabled for this agent */ diff --git a/src/server/services/agentRuntime/AgentRuntimeService.ts b/src/server/services/agentRuntime/AgentRuntimeService.ts index 03abbba8e9..5ce23fee65 100644 --- a/src/server/services/agentRuntime/AgentRuntimeService.ts +++ b/src/server/services/agentRuntime/AgentRuntimeService.ts @@ -311,7 +311,7 @@ export class AgentRuntimeService { userMemory, userTimezone, webhookDelivery, - workingDirectory: agentConfig?.chatConfig?.localSystem?.workingDirectory, + workingDirectory: agentConfig?.chatConfig?.runtimeEnv?.workingDirectory, ...appContext, }, maxSteps, diff --git a/src/store/agent/selectors/agentByIdSelectors.ts b/src/store/agent/selectors/agentByIdSelectors.ts index 40acd6d3ec..f91e79395f 100644 --- a/src/store/agent/selectors/agentByIdSelectors.ts +++ b/src/store/agent/selectors/agentByIdSelectors.ts @@ -1,7 +1,7 @@ import { DEFAULT_PROVIDER } from '@lobechat/business-const'; import { DEFAULT_MODEL, DEFAUTT_AGENT_TTS_CONFIG } from '@lobechat/const'; import { type AgentBuilderContext } from '@lobechat/context-engine'; -import { type AgentMode, type LobeAgentTTSConfig, type LocalSystemConfig } from '@lobechat/types'; +import { type AgentMode, type LobeAgentTTSConfig, type RuntimeEnvConfig } from '@lobechat/types'; import { type AgentStoreState } from '../initialState'; import { agentSelectors } from './selectors'; @@ -74,13 +74,13 @@ const getAgentEnableModeById = }; /** - * Get local system config by agentId - * Now reads from chatConfig.localSystem + * Get runtime env config by agentId + * Now reads from chatConfig.runtimeEnv */ -const getAgentLocalSystemConfigById = +const getAgentRuntimeEnvConfigById = (agentId: string) => - (s: AgentStoreState): LocalSystemConfig | undefined => - agentSelectors.getAgentConfigById(agentId)(s)?.chatConfig?.localSystem; + (s: AgentStoreState): RuntimeEnvConfig | undefined => + agentSelectors.getAgentConfigById(agentId)(s)?.chatConfig?.runtimeEnv; /** * Get working directory by agentId @@ -88,7 +88,7 @@ const getAgentLocalSystemConfigById = const getAgentWorkingDirectoryById = (agentId: string) => (s: AgentStoreState): string | undefined => - getAgentLocalSystemConfigById(agentId)(s)?.workingDirectory; + getAgentRuntimeEnvConfigById(agentId)(s)?.workingDirectory; /** * Get agent builder context by agentId @@ -128,7 +128,7 @@ export const agentByIdSelectors = { getAgentEnableModeById, getAgentFilesById, getAgentKnowledgeBasesById, - getAgentLocalSystemConfigById, + getAgentRuntimeEnvConfigById, getAgentModeById, getAgentModelById, getAgentModelProviderById, diff --git a/src/store/agent/selectors/chatConfigByIdSelectors.test.ts b/src/store/agent/selectors/chatConfigByIdSelectors.test.ts index 0120dd1dbb..647802ecce 100644 --- a/src/store/agent/selectors/chatConfigByIdSelectors.test.ts +++ b/src/store/agent/selectors/chatConfigByIdSelectors.test.ts @@ -13,6 +13,8 @@ vi.mock('@lobechat/model-runtime', () => ({ isThinkingWithToolClaudeModel: vi.fn((model) => model === 'claude-3-7-sonnet'), })); +// isDesktop defaults to false in test environment (no __ELECTRON__) + const createState = (overrides: Partial = {}): AgentStoreState => ({ ...initialAgentSliceState, ...initialBuiltinAgentSliceState, @@ -427,4 +429,127 @@ describe('chatConfigByIdSelectors', () => { ); }); }); + + describe('getRuntimeEnvConfigById', () => { + it('should return runtimeEnv config for specified agent', () => { + const state = createState({ + agentMap: { + 'agent-1': { + chatConfig: { + runtimeEnv: { runtimeMode: { web: 'cloud' }, workingDirectory: '/home' }, + }, + }, + }, + }); + + expect(chatConfigByIdSelectors.getRuntimeEnvConfigById('agent-1')(state)).toEqual({ + runtimeMode: { web: 'cloud' }, + workingDirectory: '/home', + }); + }); + + it('should return undefined when runtimeEnv is not set', () => { + const state = createState({ + agentMap: { 'agent-1': {} }, + }); + + expect(chatConfigByIdSelectors.getRuntimeEnvConfigById('agent-1')(state)).toBeUndefined(); + }); + }); + + // In test environment, isDesktop is false (no __ELECTRON__), so CURRENT_PLATFORM = 'web' + describe('getRuntimeModeById (web platform)', () => { + it('should return web runtime mode when set', () => { + const state = createState({ + agentMap: { + 'agent-1': { + chatConfig: { runtimeEnv: { runtimeMode: { web: 'cloud' } } }, + }, + }, + }); + + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-1')(state)).toBe('cloud'); + }); + + it('should default to "none" on web when not set', () => { + const state = createState({ + agentMap: { 'agent-1': {} }, + }); + + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-1')(state)).toBe('none'); + }); + + it('should default to "none" for non-existent agent', () => { + const state = createState({ agentMap: {} }); + + expect(chatConfigByIdSelectors.getRuntimeModeById('non-existent')(state)).toBe('none'); + }); + + it('should ignore desktop runtime mode on web', () => { + const state = createState({ + agentMap: { + 'agent-1': { + chatConfig: { runtimeEnv: { runtimeMode: { desktop: 'local' } } }, + }, + }, + }); + + // desktop is set but web is not, should fall back to web default + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-1')(state)).toBe('none'); + }); + + it('should read correct platform when both are set', () => { + const state = createState({ + agentMap: { + 'agent-1': { + chatConfig: { + runtimeEnv: { runtimeMode: { desktop: 'local', web: 'cloud' } }, + }, + }, + }, + }); + + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-1')(state)).toBe('cloud'); + }); + + it('should work with different agents independently', () => { + const state = createState({ + agentMap: { + 'agent-1': { + chatConfig: { runtimeEnv: { runtimeMode: { web: 'cloud' } } }, + }, + 'agent-2': { + chatConfig: { runtimeEnv: { runtimeMode: { web: 'none' } } }, + }, + 'agent-3': { chatConfig: {} }, + }, + }); + + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-1')(state)).toBe('cloud'); + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-2')(state)).toBe('none'); + expect(chatConfigByIdSelectors.getRuntimeModeById('agent-3')(state)).toBe('none'); + }); + }); + + describe('isLocalSystemEnabledById', () => { + it('should return false on web even with desktop set to local', () => { + const state = createState({ + agentMap: { + 'agent-1': { + chatConfig: { runtimeEnv: { runtimeMode: { desktop: 'local' } } }, + }, + }, + }); + + expect(chatConfigByIdSelectors.isLocalSystemEnabledById('agent-1')(state)).toBe(false); + }); + + it('should return false when not set (web defaults to none)', () => { + const state = createState({ + agentMap: { 'agent-1': {} }, + }); + + expect(chatConfigByIdSelectors.isLocalSystemEnabledById('agent-1')(state)).toBe(false); + }); + }); }); diff --git a/src/store/agent/selectors/chatConfigByIdSelectors.ts b/src/store/agent/selectors/chatConfigByIdSelectors.ts index 39922d806b..1a6d71fb52 100644 --- a/src/store/agent/selectors/chatConfigByIdSelectors.ts +++ b/src/store/agent/selectors/chatConfigByIdSelectors.ts @@ -1,5 +1,9 @@ -import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_SEARCH_FC_MODEL } from '@lobechat/const'; -import { type LobeAgentChatConfig } from '@lobechat/types'; +import { + DEFAULT_AGENT_CHAT_CONFIG, + DEFAULT_AGENT_SEARCH_FC_MODEL, + isDesktop, +} from '@lobechat/const'; +import { type LobeAgentChatConfig, type RuntimeEnvMode } from '@lobechat/types'; import { type AgentStoreState } from '@/store/agent/initialState'; @@ -48,15 +52,37 @@ const isMemoryToolEnabledById = (agentId: string) => (s: AgentStoreState) => const getMemoryToolEffortById = (agentId: string) => (s: AgentStoreState) => getChatConfigById(agentId)(s).memory?.effort ?? 'medium'; +const getRuntimeEnvConfigById = (agentId: string) => (s: AgentStoreState) => + getChatConfigById(agentId)(s).runtimeEnv; + +const isLocalSystemEnabledById = (agentId: string) => (s: AgentStoreState) => + getRuntimeModeById(agentId)(s) === 'local'; + +/** + * Get runtime environment mode by agent ID. + * Reads from `runtimeMode[platform]`, defaults to 'local' on desktop, 'none' on web. + */ +const getRuntimeModeById = + (agentId: string) => + (s: AgentStoreState): RuntimeEnvMode => { + const runtimeEnv = getChatConfigById(agentId)(s).runtimeEnv; + const platform = isDesktop ? 'desktop' : 'web'; + + return runtimeEnv?.runtimeMode?.[platform] ?? (isDesktop ? 'local' : 'none'); + }; + export const chatConfigByIdSelectors = { getChatConfigById, getEnableHistoryCountById, getHistoryCountById, + getRuntimeEnvConfigById, getMemoryToolConfigById, getMemoryToolEffortById, + getRuntimeModeById, getSearchFCModelById, getSearchModeById, getUseModelBuiltinSearchById, isEnableSearchById, + isLocalSystemEnabledById, isMemoryToolEnabledById, }; diff --git a/src/store/agent/selectors/chatConfigSelectors.ts b/src/store/agent/selectors/chatConfigSelectors.ts index 9bf370476f..4da6a7b245 100644 --- a/src/store/agent/selectors/chatConfigSelectors.ts +++ b/src/store/agent/selectors/chatConfigSelectors.ts @@ -31,6 +31,12 @@ const historyCount = (s: AgentStoreState): number => const isMemoryToolEnabled = (s: AgentStoreState) => chatConfigByIdSelectors.isMemoryToolEnabledById(s.activeAgentId || '')(s); +const isLocalSystemEnabled = (s: AgentStoreState) => + chatConfigByIdSelectors.isLocalSystemEnabledById(s.activeAgentId || '')(s); + +const isCloudSandboxEnabled = (s: AgentStoreState) => + chatConfigByIdSelectors.getRuntimeModeById(s.activeAgentId || '')(s) === 'cloud'; + const enableHistoryDivider = (historyLength: number, currentIndex: number) => (s: AgentStoreState) => { const config = currentChatConfig(s); @@ -49,6 +55,8 @@ export const agentChatConfigSelectors = { enableHistoryDivider, historyCount, isAgentEnableSearch, + isCloudSandboxEnabled, + isLocalSystemEnabled, isMemoryToolEnabled, searchFCModel, useModelBuiltinSearch, diff --git a/src/store/agent/selectors/selectors.ts b/src/store/agent/selectors/selectors.ts index 847829d03e..03adf5f784 100644 --- a/src/store/agent/selectors/selectors.ts +++ b/src/store/agent/selectors/selectors.ts @@ -11,8 +11,8 @@ import { type KnowledgeItem, type LobeAgentConfig, type LobeAgentTTSConfig, - type LocalSystemConfig, type MetaData, + type RuntimeEnvConfig, } from '@lobechat/types'; import { KnowledgeType } from '@lobechat/types'; import { VoiceList } from '@lobehub/tts'; @@ -248,17 +248,17 @@ const currentAgentMode = (s: AgentStoreState): AgentMode | undefined => { const isAgentModeEnabled = (s: AgentStoreState): boolean => currentAgentMode(s) !== undefined; /** - * Get current agent's local system config - * Now reads from chatConfig.localSystem + * Get current agent's runtime env config + * Now reads from chatConfig.runtimeEnv */ -const currentAgentLocalSystemConfig = (s: AgentStoreState): LocalSystemConfig | undefined => - currentAgentConfig(s)?.chatConfig?.localSystem; +const currentAgentRuntimeEnvConfig = (s: AgentStoreState): RuntimeEnvConfig | undefined => + currentAgentConfig(s)?.chatConfig?.runtimeEnv; /** * Get current agent's working directory */ const currentAgentWorkingDirectory = (s: AgentStoreState): string | undefined => - currentAgentLocalSystemConfig(s)?.workingDirectory; + currentAgentRuntimeEnvConfig(s)?.workingDirectory; const isCurrentAgentExternal = (s: AgentStoreState): boolean => !currentAgentData(s)?.virtual; @@ -269,7 +269,7 @@ export const agentSelectors = { currentAgentDescription, currentAgentFiles, currentAgentKnowledgeBases, - currentAgentLocalSystemConfig, + currentAgentRuntimeEnvConfig, currentAgentMeta, currentAgentMode, currentAgentModel, diff --git a/src/store/agent/slices/agent/action.ts b/src/store/agent/slices/agent/action.ts index 917401a769..d8a734730c 100644 --- a/src/store/agent/slices/agent/action.ts +++ b/src/store/agent/slices/agent/action.ts @@ -15,7 +15,7 @@ import { userProfileSelectors } from '@/store/user/selectors'; import { type LobeAgentChatConfig, type LobeAgentConfig, - type LocalSystemConfig, + type RuntimeEnvConfig, } from '@/types/agent'; import { type MetaData } from '@/types/meta'; import { merge } from '@/utils/merge'; @@ -191,13 +191,13 @@ export class AgentSliceActionImpl { await this.#get().optimisticUpdateAgentConfig(agentId, config, controller.signal); }; - updateAgentLocalSystemConfigById = async ( + updateAgentRuntimeEnvConfigById = async ( agentId: string, - config: Partial, + config: Partial, ): Promise => { if (!agentId) return; - await this.#get().updateAgentChatConfigById(agentId, { localSystem: config }); + await this.#get().updateAgentChatConfigById(agentId, { runtimeEnv: config }); }; updateAgentMeta = async (meta: Partial): Promise => { diff --git a/src/store/user/slices/settings/selectors/index.ts b/src/store/user/slices/settings/selectors/index.ts index 8660ff5e51..fb0e6e474c 100644 --- a/src/store/user/slices/settings/selectors/index.ts +++ b/src/store/user/slices/settings/selectors/index.ts @@ -2,4 +2,4 @@ export { userGeneralSettingsSelectors } from './general'; export { keyVaultsConfigSelectors } from './keyVaults'; export { settingsSelectors } from './settings'; export { systemAgentSelectors } from './systemAgent'; -export { toolInterventionSelectors } from './toolIntervention'; +export { type ApprovalMode, toolInterventionSelectors } from './toolIntervention'; diff --git a/src/store/user/slices/settings/selectors/toolIntervention.ts b/src/store/user/slices/settings/selectors/toolIntervention.ts index f4ae022260..4be37de404 100644 --- a/src/store/user/slices/settings/selectors/toolIntervention.ts +++ b/src/store/user/slices/settings/selectors/toolIntervention.ts @@ -5,11 +5,11 @@ import { currentSettings } from './settings'; /** * User-selectable approval modes (excludes 'headless' which is for backend async tasks only) */ -type UserApprovalMode = 'auto-run' | 'allow-list' | 'manual'; +export type ApprovalMode = 'auto-run' | 'allow-list' | 'manual'; const humanInterventionConfig = (s: UserStore) => currentSettings(s).tool?.humanIntervention || {}; -const interventionApprovalMode = (s: UserStore): UserApprovalMode => { +const interventionApprovalMode = (s: UserStore): ApprovalMode => { const mode = currentSettings(s).tool?.humanIntervention?.approvalMode; // Filter out 'headless' mode as it's not user-selectable (fallback to auto-run as similar behavior) if (mode === 'headless') return 'auto-run';