Compare commits

...

13 Commits

Author SHA1 Message Date
rdmclin2 565044f41e chore: add tooltip 2026-03-10 23:03:13 +08:00
rdmclin2 18bcfcecb6 chore: refact variable and dir name 2026-03-10 22:49:34 +08:00
rdmclin2 82c078efce chore: change working dir icon 2026-03-10 22:32:19 +08:00
rdmclin2 e50f45f64e chore: move working directory to left select 2026-03-10 22:29:52 +08:00
rdmclin2 3e1913b50b feat: move cloud sandbox to runtime env 2026-03-10 22:08:00 +08:00
rdmclin2 bd7b58524b feat: support runtime env footer 2026-03-10 21:05:27 +08:00
rdmclin2 b836f199a8 chore: update i18n files 2026-03-10 15:15:01 +08:00
rdmclin2 c775a233a8 chore: add work directory to header in home page 2026-03-10 15:14:55 +08:00
rdmclin2 e4fa7682ae feat: support local system enabled function in action bar 2026-03-10 15:14:52 +08:00
rdmclin2 f39ba2cfb4 chore: update i18n files 2026-03-04 14:31:16 +08:00
rdmclin2 8551a235d5 chore: update i18n files 2026-03-04 14:29:41 +08:00
rdmclin2 20497d8f07 chore: add work directory to header in home page 2026-03-04 14:29:41 +08:00
rdmclin2 9ca78ae292 feat: support local system enabled function in action bar 2026-03-04 14:29:41 +08:00
42 changed files with 512 additions and 188 deletions
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "المكتبة",
"knowledgeBase.uploadGuide": "يمكن عرض الملفات المرفوعة في قسم 'الموارد'.",
"knowledgeBase.viewMore": "عرض المزيد",
"localSystem.off.desc": "تعطيل الوصول إلى النظام المحلي.",
"localSystem.off.title": "إيقاف",
"localSystem.on.desc": "السماح بالوصول إلى الملفات والأوامر المحلية.",
"localSystem.on.title": "مفعل",
"localSystem.title": "النظام المحلي",
"memberSelection.addMember": "إضافة عضو",
"memberSelection.allMembers": "جميع الأعضاء",
"memberSelection.createGroup": "إنشاء مجموعة",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Библиотека",
"knowledgeBase.uploadGuide": "Качените файлове могат да се видят в секцията 'Ресурси'.",
"knowledgeBase.viewMore": "Виж повече",
"localSystem.off.desc": "Деактивиране на достъпа до локалната система.",
"localSystem.off.title": "Изключено",
"localSystem.on.desc": "Разрешаване на достъп до локални файлове и команди.",
"localSystem.on.title": "Активирано",
"localSystem.title": "Локална система",
"memberSelection.addMember": "Добави член",
"memberSelection.allMembers": "Всички членове",
"memberSelection.createGroup": "Създай група",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Bibliothek",
"knowledgeBase.uploadGuide": "Hochgeladene Dateien sind im Bereich 'Ressourcen' einsehbar.",
"knowledgeBase.viewMore": "Mehr anzeigen",
"localSystem.off.desc": "Zugriff auf das lokale System deaktivieren.",
"localSystem.off.title": "Aus",
"localSystem.on.desc": "Zugriff auf lokale Dateien und Befehle erlauben.",
"localSystem.on.title": "Aktiviert",
"localSystem.title": "Lokales System",
"memberSelection.addMember": "Mitglied hinzufügen",
"memberSelection.allMembers": "Alle Mitglieder",
"memberSelection.createGroup": "Gruppe erstellen",
+9
View File
@@ -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",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Biblioteca",
"knowledgeBase.uploadGuide": "Los archivos subidos se pueden ver en la sección 'Recursos'.",
"knowledgeBase.viewMore": "Ver más",
"localSystem.off.desc": "Desactivar el acceso al sistema local.",
"localSystem.off.title": "Desactivado",
"localSystem.on.desc": "Permitir acceso a archivos y comandos locales.",
"localSystem.on.title": "Activado",
"localSystem.title": "Sistema Local",
"memberSelection.addMember": "Agregar miembro",
"memberSelection.allMembers": "Todos los miembros",
"memberSelection.createGroup": "Crear grupo",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "کتابخانه",
"knowledgeBase.uploadGuide": "فایل‌های بارگذاری‌شده در بخش «منابع» قابل مشاهده هستند.",
"knowledgeBase.viewMore": "مشاهده بیشتر",
"localSystem.off.desc": "غیرفعال کردن دسترسی به سیستم محلی.",
"localSystem.off.title": "خاموش",
"localSystem.on.desc": "اجازه دسترسی به فایل‌ها و دستورات محلی.",
"localSystem.on.title": "فعال",
"localSystem.title": "سیستم محلی",
"memberSelection.addMember": "افزودن عضو",
"memberSelection.allMembers": "همه اعضا",
"memberSelection.createGroup": "ایجاد گروه",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Bibliothèque",
"knowledgeBase.uploadGuide": "Les fichiers téléchargés peuvent être consultés dans la section « Ressources ».",
"knowledgeBase.viewMore": "Voir plus",
"localSystem.off.desc": "Désactiver l'accès au système local.",
"localSystem.off.title": "Désactivé",
"localSystem.on.desc": "Autoriser l'accès aux fichiers et commandes locaux.",
"localSystem.on.title": "Activé",
"localSystem.title": "Système Local",
"memberSelection.addMember": "Ajouter un membre",
"memberSelection.allMembers": "Tous les membres",
"memberSelection.createGroup": "Créer un groupe",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Libreria",
"knowledgeBase.uploadGuide": "I file caricati possono essere visualizzati nella sezione 'Risorse'.",
"knowledgeBase.viewMore": "Visualizza Altro",
"localSystem.off.desc": "Disabilita l'accesso al sistema locale.",
"localSystem.off.title": "Spento",
"localSystem.on.desc": "Consenti l'accesso ai file e ai comandi locali.",
"localSystem.on.title": "Abilitato",
"localSystem.title": "Sistema Locale",
"memberSelection.addMember": "Aggiungi membro",
"memberSelection.allMembers": "Tutti i membri",
"memberSelection.createGroup": "Crea gruppo",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "ライブラリ",
"knowledgeBase.uploadGuide": "アップロードしたファイルは「リソース」で確認できます",
"knowledgeBase.viewMore": "さらに表示",
"localSystem.off.desc": "ローカルシステムへのアクセスを無効にします。",
"localSystem.off.title": "オフ",
"localSystem.on.desc": "ローカルファイルやコマンドへのアクセスを許可します。",
"localSystem.on.title": "有効",
"localSystem.title": "ローカルシステム",
"memberSelection.addMember": "メンバーを追加",
"memberSelection.allMembers": "全メンバー",
"memberSelection.createGroup": "グループを作成",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "자료실",
"knowledgeBase.uploadGuide": "업로드한 파일은 '자료'에서 확인할 수 있습니다",
"knowledgeBase.viewMore": "더 보기",
"localSystem.off.desc": "로컬 시스템 액세스를 비활성화합니다.",
"localSystem.off.title": "꺼짐",
"localSystem.on.desc": "로컬 파일 및 명령에 대한 액세스를 허용합니다.",
"localSystem.on.title": "사용",
"localSystem.title": "로컬 시스템",
"memberSelection.addMember": "구성원 추가",
"memberSelection.allMembers": "전체 구성원",
"memberSelection.createGroup": "그룹 만들기",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Bibliotheek",
"knowledgeBase.uploadGuide": "Geüploade bestanden zijn te bekijken in het gedeelte 'Bronnen'.",
"knowledgeBase.viewMore": "Meer bekijken",
"localSystem.off.desc": "Toegang tot het lokale systeem uitschakelen.",
"localSystem.off.title": "Uit",
"localSystem.on.desc": "Toegang tot lokale bestanden en opdrachten toestaan.",
"localSystem.on.title": "Ingeschakeld",
"localSystem.title": "Lokaal Systeem",
"memberSelection.addMember": "Lid toevoegen",
"memberSelection.allMembers": "Alle leden",
"memberSelection.createGroup": "Groep aanmaken",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Biblioteka",
"knowledgeBase.uploadGuide": "Przesłane pliki można przeglądać w sekcji „Zasoby”.",
"knowledgeBase.viewMore": "Zobacz więcej",
"localSystem.off.desc": "Wyłącz dostęp do systemu lokalnego.",
"localSystem.off.title": "Wyłączone",
"localSystem.on.desc": "Zezwól na dostęp do lokalnych plików i poleceń.",
"localSystem.on.title": "Włączone",
"localSystem.title": "System lokalny",
"memberSelection.addMember": "Dodaj członka",
"memberSelection.allMembers": "Wszyscy członkowie",
"memberSelection.createGroup": "Utwórz grupę",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Biblioteca",
"knowledgeBase.uploadGuide": "Arquivos enviados podem ser visualizados na seção 'Recursos'.",
"knowledgeBase.viewMore": "Ver mais",
"localSystem.off.desc": "Desativar acesso ao sistema local.",
"localSystem.off.title": "Desligado",
"localSystem.on.desc": "Permitir acesso a arquivos e comandos locais.",
"localSystem.on.title": "Ativado",
"localSystem.title": "Sistema Local",
"memberSelection.addMember": "Adicionar Membro",
"memberSelection.allMembers": "Todos os membros",
"memberSelection.createGroup": "Criar Grupo",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Библиотека",
"knowledgeBase.uploadGuide": "Загруженные файлы можно просмотреть в разделе «Ресурсы».",
"knowledgeBase.viewMore": "Показать больше",
"localSystem.off.desc": "Отключить доступ к локальной системе.",
"localSystem.off.title": "Выключено",
"localSystem.on.desc": "Разрешить доступ к локальным файлам и командам.",
"localSystem.on.title": "Включено",
"localSystem.title": "Локальная система",
"memberSelection.addMember": "Добавить участника",
"memberSelection.allMembers": "Все участники",
"memberSelection.createGroup": "Создать группу",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Kütüphane",
"knowledgeBase.uploadGuide": "Yüklenen dosyalar 'Kaynaklar' bölümünde görüntülenebilir.",
"knowledgeBase.viewMore": "Daha Fazla Görüntüle",
"localSystem.off.desc": "Yerel sistem erişimini devre dışı bırak.",
"localSystem.off.title": "Kapalı",
"localSystem.on.desc": "Yerel dosyalara ve komutlara erişime izin ver.",
"localSystem.on.title": "Etkin",
"localSystem.title": "Yerel Sistem",
"memberSelection.addMember": "Üye Ekle",
"memberSelection.allMembers": "Tüm üyeler",
"memberSelection.createGroup": "Grup Oluştur",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "Thư viện",
"knowledgeBase.uploadGuide": "Các tệp đã tải lên có thể xem trong phần 'Tài nguyên'.",
"knowledgeBase.viewMore": "Xem thêm",
"localSystem.off.desc": "Tắt quyền truy cập hệ thống cục bộ.",
"localSystem.off.title": "Tắt",
"localSystem.on.desc": "Cho phép truy cập vào tệp và lệnh cục bộ.",
"localSystem.on.title": "Bật",
"localSystem.title": "Hệ thống cục bộ",
"memberSelection.addMember": "Thêm thành viên",
"memberSelection.allMembers": "Tất cả thành viên",
"memberSelection.createGroup": "Tạo nhóm",
+9
View File
@@ -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": "仅本次批准",
+5
View File
@@ -150,6 +150,11 @@
"knowledgeBase.title": "資源庫",
"knowledgeBase.uploadGuide": "上傳的檔案可在「資源」中查看喔",
"knowledgeBase.viewMore": "查看更多",
"localSystem.off.desc": "停用本機系統存取。",
"localSystem.off.title": "關閉",
"localSystem.on.desc": "允許存取本機檔案與指令。",
"localSystem.on.title": "開啟",
"localSystem.title": "本機系統",
"memberSelection.addMember": "添加成員",
"memberSelection.allMembers": "所有成員",
"memberSelection.createGroup": "建立群組",
+3 -1
View File
@@ -29,6 +29,7 @@ export const defaultToolIds = [
WebBrowsingManifest.identifier,
KnowledgeBaseManifest.identifier,
MemoryManifest.identifier,
LocalSystemManifest.identifier,
];
export const builtinTools: LobeBuiltinTool[] = [
@@ -55,7 +56,7 @@ export const builtinTools: LobeBuiltinTool[] = [
},
{
discoverable: isDesktop,
hidden: !isDesktop,
hidden: true,
identifier: LocalSystemManifest.identifier,
manifest: LocalSystemManifest,
type: 'builtin',
@@ -73,6 +74,7 @@ export const builtinTools: LobeBuiltinTool[] = [
type: 'builtin',
},
{
hidden: true,
identifier: CloudSandboxManifest.identifier,
manifest: CloudSandboxManifest,
type: 'builtin',
+15 -7
View File
@@ -8,15 +8,23 @@
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';
/**
* Runtime environment configuration (desktop only)
*/
export interface RuntimeEnvConfig {
/**
* Local System working directory (desktop only)
* Runtime environment mode
*/
runtimeMode?: RuntimeEnvMode;
/**
* Working directory (desktop only)
*/
workingDirectory?: string;
// Future extensions:
// allowedPaths?: string[];
// deniedCommands?: string[];
}
+9 -8
View File
@@ -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,10 @@ export interface LobeAgentChatConfig extends AgentMemoryChatConfig {
}
/**
* Zod schema for LocalSystemConfig
* Zod schema for RuntimeEnvConfig
*/
export const LocalSystemConfigSchema = z.object({
export const RuntimeEnvConfigSchema = z.object({
runtimeMode: z.enum(['local', 'cloud', 'none']).optional(),
workingDirectory: z.string().optional(),
});
@@ -173,7 +174,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
+4
View File
@@ -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<DesktopChatInputProps>(
({
showFootnote,
showRuntimeConfig = true,
inputContainerProps,
extentHeaderContent,
actionBarStyle,
@@ -151,6 +154,7 @@ const DesktopChatInput = memo<DesktopChatInputProps>(
>
<InputEditor />
</ChatInput>
{showRuntimeConfig && <RuntimeConfig />}
{showFootnote && !expand && (
<Center style={{ pointerEvents: 'none', zIndex: 100 }}>
<Text className={styles.footnote} type={'secondary'}>
@@ -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 = (
<Button
className={styles.modeButton}
color={'default'}
icon={ChevronDown}
iconPlacement="end"
size="small"
variant={'text'}
>
{modeLabels[approvalMode]}
</Button>
);
return (
<DropdownMenu items={menuItems} placement="bottomLeft">
<Button
className={styles.modeButton}
color={'default'}
icon={ChevronDown}
iconPlacement="end"
size="small"
variant={'text'}
>
{modeLabels[approvalMode]}
</Button>
<DropdownMenu
items={menuItems}
open={dropdownOpen}
placement="bottomLeft"
onOpenChange={setDropdownOpen}
>
<div>
{dropdownOpen ? (
button
) : (
<Tooltip title={t('tool.intervention.approvalMode')}>{button}</Tooltip>
)}
</div>
</DropdownMenu>
);
});
@@ -27,7 +27,7 @@ const WorkingDirectoryContent = memo<WorkingDirectoryContentProps>(({ 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<WorkingDirectoryContentProps>(({ 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<WorkingDirectoryContentProps>(({ agentId, o
useTopicOverride,
topicDir,
topicWorkingDirectory,
updateAgentLocalSystemConfig,
updateAgentRuntimeEnvConfig,
updateTopicMetadata,
onClose,
]);
@@ -0,0 +1,274 @@
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<RuntimeEnvMode, typeof LaptopIcon> = {
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;
await updateAgentChatConfig({
runtimeEnv: { runtimeMode: mode },
});
},
[runtimeMode, updateAgentChatConfig],
);
// Skeleton placeholder to prevent layout jump during loading
if (!agentId || isLoading) {
return (
<Flexbox horizontal align={'center'} className={styles.bar} gap={4}>
<Skeleton.Button active size="small" style={{ height: 22, minWidth: 64, width: 64 }} />
<Skeleton.Button active size="small" style={{ height: 22, minWidth: 100, width: 100 }} />
</Flexbox>
);
}
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 = (
<Flexbox gap={4} style={{ minWidth: 280 }}>
{modes.map(({ mode, icon, label, desc }) => (
<Flexbox
horizontal
align={'flex-start'}
className={cx(styles.modeOption, runtimeMode === mode && styles.modeOptionActive)}
gap={12}
key={mode}
onClick={() => switchMode(mode)}
>
<Flexbox
align={'center'}
className={styles.modeOptionIcon}
flex={'none'}
height={32}
justify={'center'}
width={32}
>
<Icon icon={icon} />
</Flexbox>
<Flexbox flex={1}>
<div className={styles.modeOptionTitle}>{label}</div>
<div className={styles.modeOptionDesc}>{desc}</div>
</Flexbox>
</Flexbox>
))}
</Flexbox>
);
const modeButton = (
<div className={styles.button}>
<Icon icon={ModeIcon} size={14} />
<span>{modeLabel}</span>
<Icon icon={ChevronDownIcon} size={12} />
</div>
);
const dirButton = (
<div className={styles.button}>
<Icon icon={effectiveWorkingDirectory ? FolderIcon : SquircleDashed} size={14} />
<span>{displayName}</span>
<Icon icon={ChevronDownIcon} size={12} />
</div>
);
const rightContent = () => {
if (runtimeMode === 'local') {
return (
<Popover
content={<WorkingDirectory agentId={agentId} onClose={() => setDirPopoverOpen(false)} />}
open={dirPopoverOpen}
placement="bottomRight"
trigger="click"
onOpenChange={setDirPopoverOpen}
>
<div>
{dirPopoverOpen ? (
dirButton
) : (
<Tooltip
title={effectiveWorkingDirectory || tPlugin('localSystem.workingDirectory.notSet')}
>
{dirButton}
</Tooltip>
)}
</div>
</Popover>
);
}
return null;
};
return (
<Flexbox horizontal align={'center'} className={styles.bar} justify={'space-between'}>
{/* Left: Runtime env + working directory */}
<Flexbox horizontal align={'center'} gap={4}>
<Popover
content={modeContent}
open={modePopoverOpen}
placement="top"
styles={{ content: { padding: 4 } }}
trigger="click"
onOpenChange={setModePopoverOpen}
>
<div>
{modePopoverOpen ? (
modeButton
) : (
<Tooltip title={t('runtimeEnv.selectMode')}>{modeButton}</Tooltip>
)}
</div>
</Popover>
{rightContent()}
</Flexbox>
{/* Right: Permission control */}
<ApprovalMode />
</Flexbox>
);
});
RuntimeConfig.displayName = 'RuntimeConfig';
export default RuntimeConfig;
@@ -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<FallbackInterventionProps>(
}
/>
<Flexbox horizontal justify={'space-between'}>
<ModeSelector />
<Flexbox horizontal justify={'flex-end'}>
<ApprovalActions
apiName={apiName}
approvalMode={approvalMode}
@@ -11,10 +11,9 @@ import Arguments from '../Arguments';
import ApprovalActions from './ApprovalActions';
import Fallback from './Fallback';
import KeyValueEditor from './KeyValueEditor';
import ModeSelector from './ModeSelector';
import SecurityBlacklistWarning from './SecurityBlacklistWarning';
export type ApprovalMode = 'auto-run' | 'allow-list' | 'manual';
export type { ApprovalMode } from '@/store/user/slices/settings/selectors';
interface InterventionProps {
apiName: string;
@@ -110,8 +109,7 @@ const Intervention = memo<InterventionProps>(
registerBeforeApprove={registerBeforeApprove}
onArgsChange={handleArgsChange}
/>
<Flexbox horizontal justify={'space-between'}>
<ModeSelector />
<Flexbox horizontal justify={'flex-end'}>
<ApprovalActions
apiName={apiName}
approvalMode={approvalMode}
@@ -6,7 +6,6 @@ import { memo, Suspense } from 'react';
import AbortResponse from './AbortResponse';
import Intervention from './Intervention';
import ModeSelector from './Intervention/ModeSelector';
import LoadingPlaceholder from './LoadingPlaceholder';
import RejectedResponse from './RejectedResponse';
import ToolRender from './Render';
@@ -124,11 +123,6 @@ const Render = memo<RenderProps>(
type: type as any,
}}
/>
{!disableEditing && (
<div>
<ModeSelector />
</div>
)}
</Flexbox>
</Suspense>
);
+5
View File
@@ -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,
},
+9
View File
@@ -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': 'None',
'runtimeEnv.mode.noneDesc': 'No 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',
@@ -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 ? (
<Flexbox
horizontal
align="center"
className={cx(styles.base, styles.filled)}
gap={6}
style={{ cursor: 'pointer', height: 32, padding: '0 12px' }}
>
<Icon icon={LaptopIcon} size={18} />
<span>{displayName}</span>
</Flexbox>
) : (
<Flexbox
horizontal
align="center"
className={styles.base}
gap={6}
style={{ cursor: 'pointer', height: 32, padding: '0 12px' }}
>
<Icon icon={SquircleDashed} size={16} />
<span>{t('localSystem.workingDirectory.notSet')}</span>
</Flexbox>
);
return (
<Popover
content={<WorkingDirectoryContent agentId={agentId} onClose={() => setOpen(false)} />}
open={open}
placement="bottomRight"
trigger="click"
onOpenChange={setOpen}
>
<div>
{open ? (
content
) : (
<Tooltip title={effectiveWorkingDirectory || t('localSystem.workingDirectory.notSet')}>
{content}
</Tooltip>
)}
</div>
</Popover>
);
});
WorkingDirectory.displayName = 'WorkingDirectory';
export default WorkingDirectory;
@@ -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={
<Flexbox horizontal align={'center'} style={{ backgroundColor: cssVar.colorBgContainer }}>
{isDesktop && <WorkingDirectory />}
<NotebookButton />
<ShareButton />
<HeaderActions />
@@ -112,6 +112,7 @@ const InputArea = () => {
dropdownPlacement="bottomLeft"
extraActionItems={extraActionItems}
inputContainerProps={inputContainerProps}
showRuntimeConfig={false}
/>
</ChatInputProvider>
</DragUploadZone>
+7 -1
View File
@@ -16,7 +16,13 @@ const Home: FC = () => {
return (
<>
{isHomeRoute && <PageTitle title="" />}
<NavHeader right={<WideScreenButton />} />
<NavHeader
right={
<Flexbox horizontal align="center">
<WideScreenButton />
</Flexbox>
}
/>
<Flexbox height={'100%'} style={{ overflowY: 'auto', paddingBottom: '16vh' }} width={'100%'}>
<WideScreenContainer>
<HomeContent />
@@ -311,7 +311,7 @@ export class AgentRuntimeService {
userMemory,
userTimezone,
webhookDelivery,
workingDirectory: agentConfig?.chatConfig?.localSystem?.workingDirectory,
workingDirectory: agentConfig?.chatConfig?.runtimeEnv?.workingDirectory,
...appContext,
},
maxSteps,
@@ -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,
@@ -1,6 +1,6 @@
import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_SEARCH_FC_MODEL } from '@lobechat/const';
import { isContextCachingModel, isThinkingWithToolClaudeModel } from '@lobechat/model-runtime';
import { type LobeAgentChatConfig } from '@lobechat/types';
import { type LobeAgentChatConfig, type RuntimeEnvMode } from '@lobechat/types';
import { type AgentStoreState } from '@/store/agent/initialState';
@@ -63,15 +63,34 @@ 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.
* Defaults to 'local' on desktop if not set.
*/
const getRuntimeModeById =
(agentId: string) =>
(s: AgentStoreState): RuntimeEnvMode => {
return getChatConfigById(agentId)(s).runtimeEnv?.runtimeMode ?? 'local';
};
export const chatConfigByIdSelectors = {
getChatConfigById,
getEnableHistoryCountById,
getHistoryCountById,
getRuntimeEnvConfigById,
getMemoryToolConfigById,
getMemoryToolEffortById,
getRuntimeModeById,
getSearchFCModelById,
getSearchModeById,
getUseModelBuiltinSearchById,
isEnableSearchById,
isLocalSystemEnabledById,
isMemoryToolEnabledById,
};
@@ -30,6 +30,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);
@@ -48,6 +54,8 @@ export const agentChatConfigSelectors = {
enableHistoryDivider,
historyCount,
isAgentEnableSearch,
isCloudSandboxEnabled,
isLocalSystemEnabled,
isMemoryToolEnabled,
searchFCModel,
useModelBuiltinSearch,
+7 -7
View File
@@ -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,
+4 -4
View File
@@ -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<LocalSystemConfig>,
config: Partial<RuntimeEnvConfig>,
): Promise<void> => {
if (!agentId) return;
await this.#get().updateAgentChatConfigById(agentId, { localSystem: config });
await this.#get().updateAgentChatConfigById(agentId, { runtimeEnv: config });
};
updateAgentMeta = async (meta: Partial<MetaData>): Promise<void> => {
@@ -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';
@@ -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';