From ce3ac82ecf4effc72baece233ec63d371197d8b4 Mon Sep 17 00:00:00 2001 From: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com> Date: Thu, 4 Jun 2026 20:04:46 +0530 Subject: [PATCH] =?UTF-8?q?feat(settings):=20make=20action=5Fdir=20editabl?= =?UTF-8?q?e=20via=20Settings=20=E2=86=92=20Agent=20access=20(#3313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Steven Enamakel --- .../settings/panels/AgentAccessPanel.tsx | 3 +- .../__tests__/AgentAccessPanel.test.tsx | 3 -- app/src/lib/i18n/ar.ts | 10 ++++ app/src/lib/i18n/bn.ts | 10 ++++ app/src/lib/i18n/de.ts | 10 ++++ app/src/lib/i18n/en.ts | 10 ++++ app/src/lib/i18n/es.ts | 10 ++++ app/src/lib/i18n/fr.ts | 11 +++++ app/src/lib/i18n/hi.ts | 10 ++++ app/src/lib/i18n/id.ts | 10 ++++ app/src/lib/i18n/it.ts | 10 ++++ app/src/lib/i18n/ko.ts | 10 ++++ app/src/lib/i18n/pl.ts | 10 ++++ app/src/lib/i18n/pt.ts | 10 ++++ app/src/lib/i18n/ru.ts | 10 ++++ app/src/lib/i18n/zh-CN.ts | 10 ++++ app/src/utils/tauriCommands/config.ts | 9 ---- src/openhuman/config/ops.rs | 5 -- src/openhuman/config/schemas.rs | 2 +- src/openhuman/security/live_policy.rs | 47 +++++++++++++++++++ src/openhuman/security/policy.rs | 2 +- 21 files changed, 191 insertions(+), 21 deletions(-) diff --git a/app/src/components/settings/panels/AgentAccessPanel.tsx b/app/src/components/settings/panels/AgentAccessPanel.tsx index d4a082764..0266a2d09 100644 --- a/app/src/components/settings/panels/AgentAccessPanel.tsx +++ b/app/src/components/settings/panels/AgentAccessPanel.tsx @@ -84,8 +84,6 @@ const AgentAccessPanel = () => { // RPC is pending or when not running under Tauri — the JSX falls back to // the documented defaults so the section never renders empty. const [agentPaths, setAgentPaths] = useState(null); - // Editable action_dir (issue #3240). `actionDirEditing` toggles the input; - // `actionDirInput` holds the in-progress value; error/saved are inline status. const [actionDirEditing, setActionDirEditing] = useState(false); const [actionDirInput, setActionDirInput] = useState(''); const [actionDirError, setActionDirError] = useState(null); @@ -130,6 +128,7 @@ const AgentAccessPanel = () => { const pathsResp = await openhumanGetAgentPaths(); if (cancelled) return; setAgentPaths(pathsResp.result); + setActionDirInput(pathsResp.result.action_dir); } catch { // Non-fatal: the Directories section falls back to the documented // defaults below. We don't gate the rest of the panel on this. diff --git a/app/src/components/settings/panels/__tests__/AgentAccessPanel.test.tsx b/app/src/components/settings/panels/__tests__/AgentAccessPanel.test.tsx index 01915952b..c035a970c 100644 --- a/app/src/components/settings/panels/__tests__/AgentAccessPanel.test.tsx +++ b/app/src/components/settings/panels/__tests__/AgentAccessPanel.test.tsx @@ -296,8 +296,6 @@ describe('AgentAccessPanel', () => { ); }); - // ── Editable action_dir (issue #3240) ─────────────────────────────────────── - it('shows an Edit affordance for action_dir when the source is not env', async () => { mockGetAgentPaths.mockResolvedValue({ result: agentPaths({ action_dir: '/Users/sample/projects', action_dir_source: 'default' }), @@ -348,7 +346,6 @@ describe('AgentAccessPanel', () => { expect(await screen.findByTestId('agent-access-action-dir-error')).toHaveTextContent( 'action_dir must be an absolute path' ); - // Still in edit mode so the user can correct the value. expect(screen.getByTestId('agent-access-action-dir-input')).toBeInTheDocument(); }); diff --git a/app/src/lib/i18n/ar.ts b/app/src/lib/i18n/ar.ts index 1eaa48ca1..5913bed48 100644 --- a/app/src/lib/i18n/ar.ts +++ b/app/src/lib/i18n/ar.ts @@ -3683,6 +3683,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'محظور على الوكيل', 'settings.agentAccess.internalStateDesc': 'قواعد بيانات الذاكرة والجلسات والرموز وغيرها من البيانات الأساسية. غير متاحة لأدوات الوكيل.', + 'settings.agentAccess.actionDirInputLabel': 'مسار صندوق إجراءات الوكيل', + 'settings.agentAccess.actionDirSave': 'حفظ', + 'settings.agentAccess.actionDirSaving': 'جارٍ الحفظ…', + 'settings.agentAccess.actionDirSaved': 'تم تحديث صندوق الإجراءات.', + 'settings.agentAccess.actionDirEmptyError': 'لا يمكن أن يكون المسار فارغًا.', + 'settings.agentAccess.actionDirSaveError': 'تعذّر تحديث صندوق الإجراءات.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'تم ضبط OPENHUMAN_ACTION_DIR — قم بإلغاء متغير البيئة لتعديل هذا من الإعدادات.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'تم تجاوزه بواسطة OPENHUMAN_ACTION_DIR — قم بإلغاء متغير البيئة لإدارته من الإعدادات.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/bn.ts b/app/src/lib/i18n/bn.ts index df159b3bc..0adea28bc 100644 --- a/app/src/lib/i18n/bn.ts +++ b/app/src/lib/i18n/bn.ts @@ -3755,6 +3755,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'এজেন্ট-অবরুদ্ধ', 'settings.agentAccess.internalStateDesc': 'মেমরি ডেটাবেস, সেশন, টোকেন এবং অন্যান্য মূল ডেটা। এজেন্ট টুলের কাছে অ্যাক্সেসযোগ্য নয়।', + 'settings.agentAccess.actionDirInputLabel': 'অ্যাকশন স্যান্ডবক্স পাথ', + 'settings.agentAccess.actionDirSave': 'সংরক্ষণ', + 'settings.agentAccess.actionDirSaving': 'সংরক্ষণ করা হচ্ছে…', + 'settings.agentAccess.actionDirSaved': 'অ্যাকশন স্যান্ডবক্স আপডেট করা হয়েছে।', + 'settings.agentAccess.actionDirEmptyError': 'পাথ ফাঁকা থাকতে পারবে না।', + 'settings.agentAccess.actionDirSaveError': 'অ্যাকশন স্যান্ডবক্স আপডেট করা যায়নি।', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR সেট করা আছে — সেটিংস থেকে সম্পাদনা করতে এনভ ভ্যারিয়েবলটি আনসেট করুন।', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'OPENHUMAN_ACTION_DIR দ্বারা ওভাররাইড হয়েছে — সেটিংস থেকে পরিচালনা করতে এনভ ভ্যারিয়েবলটি আনসেট করুন।', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/de.ts b/app/src/lib/i18n/de.ts index 9bfe94940..6dfbf7ffe 100644 --- a/app/src/lib/i18n/de.ts +++ b/app/src/lib/i18n/de.ts @@ -3854,6 +3854,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'Agent-gesperrt', 'settings.agentAccess.internalStateDesc': 'Speicherdatenbanken, Sitzungen, Token und andere Kerndaten. Für Agent-Tools nicht zugänglich.', + 'settings.agentAccess.actionDirInputLabel': 'Pfad der Aktions-Sandbox', + 'settings.agentAccess.actionDirSave': 'Speichern', + 'settings.agentAccess.actionDirSaving': 'Wird gespeichert…', + 'settings.agentAccess.actionDirSaved': 'Aktions-Sandbox aktualisiert.', + 'settings.agentAccess.actionDirEmptyError': 'Pfad darf nicht leer sein.', + 'settings.agentAccess.actionDirSaveError': 'Aktions-Sandbox konnte nicht aktualisiert werden.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR ist gesetzt — entferne die Umgebungsvariable, um dies in den Einstellungen zu bearbeiten.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Durch OPENHUMAN_ACTION_DIR überschrieben — entferne die Umgebungsvariable, um dies in den Einstellungen zu verwalten.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index 95450167b..4ec3d60a0 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -4144,6 +4144,16 @@ const en: TranslationMap = { 'settings.agentAccess.agentBlocked': 'agent-blocked', 'settings.agentAccess.internalStateDesc': 'Memory databases, sessions, tokens, and other core persistence. Not accessible to agent tools.', + 'settings.agentAccess.actionDirInputLabel': 'Action sandbox path', + 'settings.agentAccess.actionDirSave': 'Save', + 'settings.agentAccess.actionDirSaving': 'Saving…', + 'settings.agentAccess.actionDirSaved': 'Action sandbox updated.', + 'settings.agentAccess.actionDirEmptyError': 'Path cannot be empty.', + 'settings.agentAccess.actionDirSaveError': 'Could not update the action sandbox.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR is set — unset the env var to edit this from Settings.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Overridden by OPENHUMAN_ACTION_DIR — unset the env var to manage from Settings.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/es.ts b/app/src/lib/i18n/es.ts index ea9f67e4c..eb84e71e2 100644 --- a/app/src/lib/i18n/es.ts +++ b/app/src/lib/i18n/es.ts @@ -3828,6 +3828,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'bloqueado para el agente', 'settings.agentAccess.internalStateDesc': 'Bases de datos de memoria, sesiones, tokens y otros datos esenciales. No accesible para herramientas del agente.', + 'settings.agentAccess.actionDirInputLabel': 'Ruta del sandbox de acciones', + 'settings.agentAccess.actionDirSave': 'Guardar', + 'settings.agentAccess.actionDirSaving': 'Guardando…', + 'settings.agentAccess.actionDirSaved': 'Sandbox de acciones actualizado.', + 'settings.agentAccess.actionDirEmptyError': 'La ruta no puede estar vacía.', + 'settings.agentAccess.actionDirSaveError': 'No se pudo actualizar el sandbox de acciones.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR está definido — elimina la variable de entorno para editar esto desde Ajustes.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Anulado por OPENHUMAN_ACTION_DIR — elimina la variable de entorno para gestionarlo desde Ajustes.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/fr.ts b/app/src/lib/i18n/fr.ts index 1e9862027..08751a352 100644 --- a/app/src/lib/i18n/fr.ts +++ b/app/src/lib/i18n/fr.ts @@ -3839,6 +3839,17 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': "bloqué pour l'agent", 'settings.agentAccess.internalStateDesc': "Bases de données mémoire, sessions, jetons et autres données essentielles. Non accessible aux outils de l'agent.", + 'settings.agentAccess.actionDirInputLabel': "Chemin du bac à sable d'actions", + 'settings.agentAccess.actionDirSave': 'Enregistrer', + 'settings.agentAccess.actionDirSaving': 'Enregistrement…', + 'settings.agentAccess.actionDirSaved': "Bac à sable d'actions mis à jour.", + 'settings.agentAccess.actionDirEmptyError': 'Le chemin ne peut pas être vide.', + 'settings.agentAccess.actionDirSaveError': + "Impossible de mettre à jour le bac à sable d'actions.", + 'settings.agentAccess.actionDirEnvOverrideError': + "OPENHUMAN_ACTION_DIR est défini — désactivez la variable d'environnement pour modifier cela depuis les Paramètres.", + 'settings.agentAccess.actionDirEnvOverrideNote': + "Remplacé par OPENHUMAN_ACTION_DIR — désactivez la variable d'environnement pour gérer cela depuis les Paramètres.", 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/hi.ts b/app/src/lib/i18n/hi.ts index 30c063459..8eef73e15 100644 --- a/app/src/lib/i18n/hi.ts +++ b/app/src/lib/i18n/hi.ts @@ -3764,6 +3764,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'एजेंट-अवरुद्ध', 'settings.agentAccess.internalStateDesc': 'मेमोरी डेटाबेस, सत्र, टोकन और अन्य मुख्य डेटा। एजेंट टूल के लिए सुलभ नहीं।', + 'settings.agentAccess.actionDirInputLabel': 'क्रिया सैंडबॉक्स पथ', + 'settings.agentAccess.actionDirSave': 'सहेजें', + 'settings.agentAccess.actionDirSaving': 'सहेजा जा रहा है…', + 'settings.agentAccess.actionDirSaved': 'क्रिया सैंडबॉक्स अपडेट किया गया।', + 'settings.agentAccess.actionDirEmptyError': 'पथ खाली नहीं हो सकता।', + 'settings.agentAccess.actionDirSaveError': 'क्रिया सैंडबॉक्स अपडेट नहीं किया जा सका।', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR सेट है — सेटिंग्स से संपादित करने के लिए env चर हटाएँ।', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'OPENHUMAN_ACTION_DIR द्वारा ओवरराइड किया गया — सेटिंग्स से प्रबंधित करने के लिए env चर हटाएँ।', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/id.ts b/app/src/lib/i18n/id.ts index dddfe53bf..69a71238e 100644 --- a/app/src/lib/i18n/id.ts +++ b/app/src/lib/i18n/id.ts @@ -3771,6 +3771,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'diblokir untuk agen', 'settings.agentAccess.internalStateDesc': 'Database memori, sesi, token, dan data inti lainnya. Tidak dapat diakses oleh alat agen.', + 'settings.agentAccess.actionDirInputLabel': 'Jalur sandbox tindakan', + 'settings.agentAccess.actionDirSave': 'Simpan', + 'settings.agentAccess.actionDirSaving': 'Menyimpan…', + 'settings.agentAccess.actionDirSaved': 'Sandbox tindakan diperbarui.', + 'settings.agentAccess.actionDirEmptyError': 'Jalur tidak boleh kosong.', + 'settings.agentAccess.actionDirSaveError': 'Tidak dapat memperbarui sandbox tindakan.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR sudah diatur — hapus variabel env untuk mengedit ini dari Pengaturan.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Ditimpa oleh OPENHUMAN_ACTION_DIR — hapus variabel env untuk mengelola dari Pengaturan.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/it.ts b/app/src/lib/i18n/it.ts index b252ea1a1..04cb740db 100644 --- a/app/src/lib/i18n/it.ts +++ b/app/src/lib/i18n/it.ts @@ -3820,6 +3820,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': "bloccato per l'agente", 'settings.agentAccess.internalStateDesc': "Database memoria, sessioni, token e altri dati fondamentali. Non accessibile dagli strumenti dell'agente.", + 'settings.agentAccess.actionDirInputLabel': 'Percorso della sandbox delle azioni', + 'settings.agentAccess.actionDirSave': 'Salva', + 'settings.agentAccess.actionDirSaving': 'Salvataggio…', + 'settings.agentAccess.actionDirSaved': 'Sandbox delle azioni aggiornata.', + 'settings.agentAccess.actionDirEmptyError': 'Il percorso non può essere vuoto.', + 'settings.agentAccess.actionDirSaveError': 'Impossibile aggiornare la sandbox delle azioni.', + 'settings.agentAccess.actionDirEnvOverrideError': + "OPENHUMAN_ACTION_DIR è impostata — rimuovi la variabile d'ambiente per modificarla dalle Impostazioni.", + 'settings.agentAccess.actionDirEnvOverrideNote': + "Sovrascritta da OPENHUMAN_ACTION_DIR — rimuovi la variabile d'ambiente per gestirla dalle Impostazioni.", 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/ko.ts b/app/src/lib/i18n/ko.ts index 4cb35fb44..8469c7ef3 100644 --- a/app/src/lib/i18n/ko.ts +++ b/app/src/lib/i18n/ko.ts @@ -3717,6 +3717,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': '에이전트 차단됨', 'settings.agentAccess.internalStateDesc': '메모리 데이터베이스, 세션, 토큰 및 기타 핵심 데이터. 에이전트 도구에서 접근할 수 없습니다.', + 'settings.agentAccess.actionDirInputLabel': '작업 샌드박스 경로', + 'settings.agentAccess.actionDirSave': '저장', + 'settings.agentAccess.actionDirSaving': '저장 중…', + 'settings.agentAccess.actionDirSaved': '작업 샌드박스가 업데이트되었습니다.', + 'settings.agentAccess.actionDirEmptyError': '경로는 비워둘 수 없습니다.', + 'settings.agentAccess.actionDirSaveError': '작업 샌드박스를 업데이트할 수 없습니다.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR이 설정되어 있습니다 — 설정에서 편집하려면 환경 변수를 해제하세요.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'OPENHUMAN_ACTION_DIR에 의해 재정의됨 — 설정에서 관리하려면 환경 변수를 해제하세요.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/pl.ts b/app/src/lib/i18n/pl.ts index 31056f76c..14e63c819 100644 --- a/app/src/lib/i18n/pl.ts +++ b/app/src/lib/i18n/pl.ts @@ -3822,6 +3822,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'zablokowane dla agenta', 'settings.agentAccess.internalStateDesc': 'Bazy danych pamięci, sesje, tokeny i inne podstawowe dane. Niedostępne dla narzędzi agenta.', + 'settings.agentAccess.actionDirInputLabel': 'Ścieżka piaskownicy działań', + 'settings.agentAccess.actionDirSave': 'Zapisz', + 'settings.agentAccess.actionDirSaving': 'Zapisywanie…', + 'settings.agentAccess.actionDirSaved': 'Piaskownica działań zaktualizowana.', + 'settings.agentAccess.actionDirEmptyError': 'Ścieżka nie może być pusta.', + 'settings.agentAccess.actionDirSaveError': 'Nie udało się zaktualizować piaskownicy działań.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR jest ustawione — usuń zmienną środowiskową, aby edytować to w Ustawieniach.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Nadpisane przez OPENHUMAN_ACTION_DIR — usuń zmienną środowiskową, aby zarządzać tym w Ustawieniach.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/pt.ts b/app/src/lib/i18n/pt.ts index de690be01..0fa21e243 100644 --- a/app/src/lib/i18n/pt.ts +++ b/app/src/lib/i18n/pt.ts @@ -3821,6 +3821,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'bloqueado para o agente', 'settings.agentAccess.internalStateDesc': 'Bancos de dados de memória, sessões, tokens e outros dados essenciais. Não acessível pelas ferramentas do agente.', + 'settings.agentAccess.actionDirInputLabel': 'Caminho da sandbox de ações', + 'settings.agentAccess.actionDirSave': 'Salvar', + 'settings.agentAccess.actionDirSaving': 'Salvando…', + 'settings.agentAccess.actionDirSaved': 'Sandbox de ações atualizada.', + 'settings.agentAccess.actionDirEmptyError': 'O caminho não pode estar vazio.', + 'settings.agentAccess.actionDirSaveError': 'Não foi possível atualizar a sandbox de ações.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR está definido — remova a variável de ambiente para editar isto em Configurações.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Substituído por OPENHUMAN_ACTION_DIR — remova a variável de ambiente para gerenciar em Configurações.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/ru.ts b/app/src/lib/i18n/ru.ts index dc76e3702..88c240cba 100644 --- a/app/src/lib/i18n/ru.ts +++ b/app/src/lib/i18n/ru.ts @@ -3790,6 +3790,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': 'заблокировано для агента', 'settings.agentAccess.internalStateDesc': 'Базы данных памяти, сеансы, токены и другие основные данные. Недоступно для инструментов агента.', + 'settings.agentAccess.actionDirInputLabel': 'Путь к песочнице действий', + 'settings.agentAccess.actionDirSave': 'Сохранить', + 'settings.agentAccess.actionDirSaving': 'Сохранение…', + 'settings.agentAccess.actionDirSaved': 'Песочница действий обновлена.', + 'settings.agentAccess.actionDirEmptyError': 'Путь не может быть пустым.', + 'settings.agentAccess.actionDirSaveError': 'Не удалось обновить песочницу действий.', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR установлена — снимите переменную окружения, чтобы изменить это в Настройках.', + 'settings.agentAccess.actionDirEnvOverrideNote': + 'Переопределено OPENHUMAN_ACTION_DIR — снимите переменную окружения, чтобы управлять этим в Настройках.', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/lib/i18n/zh-CN.ts b/app/src/lib/i18n/zh-CN.ts index 15157d4d3..a07fd592f 100644 --- a/app/src/lib/i18n/zh-CN.ts +++ b/app/src/lib/i18n/zh-CN.ts @@ -3574,6 +3574,16 @@ const messages: TranslationMap = { 'settings.agentAccess.agentBlocked': '代理已阻止', 'settings.agentAccess.internalStateDesc': '内存数据库、会话、令牌和其他核心数据。代理工具无法访问。', + 'settings.agentAccess.actionDirInputLabel': '操作沙箱路径', + 'settings.agentAccess.actionDirSave': '保存', + 'settings.agentAccess.actionDirSaving': '正在保存…', + 'settings.agentAccess.actionDirSaved': '操作沙箱已更新。', + 'settings.agentAccess.actionDirEmptyError': '路径不能为空。', + 'settings.agentAccess.actionDirSaveError': '无法更新操作沙箱。', + 'settings.agentAccess.actionDirEnvOverrideError': + 'OPENHUMAN_ACTION_DIR 已设置 — 请取消该环境变量以在设置中编辑此项。', + 'settings.agentAccess.actionDirEnvOverrideNote': + '已被 OPENHUMAN_ACTION_DIR 覆盖 — 请取消该环境变量以在设置中管理。', 'settings.agentAccess.approvalHistory': 'Approval history', 'settings.agentAccess.approvalHistoryDesc': 'Review past Approve / Deny decisions the agent requested.', diff --git a/app/src/utils/tauriCommands/config.ts b/app/src/utils/tauriCommands/config.ts index 8c593ac14..0890778a9 100644 --- a/app/src/utils/tauriCommands/config.ts +++ b/app/src/utils/tauriCommands/config.ts @@ -452,18 +452,9 @@ export async function openhumanGetAgentPaths(): Promise> { diff --git a/src/openhuman/config/ops.rs b/src/openhuman/config/ops.rs index 62b1936b7..4681eaa23 100644 --- a/src/openhuman/config/ops.rs +++ b/src/openhuman/config/ops.rs @@ -2167,8 +2167,6 @@ pub struct SandboxSettingsPatch { pub env_passthrough: Option>, } -/// Returns the current sandbox and Docker runtime settings merged into a -/// single JSON object, plus a live status probe for Docker availability. pub async fn get_sandbox_settings() -> Result, String> { let config = load_config_with_timeout().await?; let sandbox = &config.sandbox; @@ -2201,7 +2199,6 @@ pub async fn get_sandbox_settings() -> Result, Str Ok(RpcOutcome::single_log(value, "sandbox settings read")) } -/// Updates sandbox and Docker runtime settings, persists to disk. pub async fn apply_sandbox_settings( config: &mut Config, update: SandboxSettingsPatch, @@ -2271,7 +2268,6 @@ pub async fn load_and_apply_sandbox_settings( apply_sandbox_settings(&mut config, update).await } -/// Probe Docker daemon availability via `docker info` with a 5s timeout. async fn is_docker_available() -> bool { let fut = tokio::process::Command::new("docker") .arg("info") @@ -2284,7 +2280,6 @@ async fn is_docker_available() -> bool { } } -/// Detect which OS-native sandbox backend is available. fn detect_os_sandbox_backend() -> &'static str { #[cfg(target_os = "linux")] { diff --git a/src/openhuman/config/schemas.rs b/src/openhuman/config/schemas.rs index 68493bd8b..9f699dddd 100644 --- a/src/openhuman/config/schemas.rs +++ b/src/openhuman/config/schemas.rs @@ -1037,7 +1037,7 @@ pub fn schemas(function: &str) -> ControllerSchema { namespace: "config", function: "get_agent_paths", description: - "Resolve the agent's filesystem roots (action_dir, workspace_dir, projects_dir) so the UI can render live values instead of hard-coded strings. Read-only.", + "Resolve the agent's filesystem roots (action_dir, workspace_dir, projects_dir) so the UI can render live values instead of hard-coded strings. Read-only. Also returns `action_dir_env_override: bool` so the UI knows when OPENHUMAN_ACTION_DIR is forcing the value (Settings → action_dir editing disabled in that case).", inputs: vec![], outputs: vec![json_output( "paths", diff --git a/src/openhuman/security/live_policy.rs b/src/openhuman/security/live_policy.rs index 4f69e5960..eb1933a25 100644 --- a/src/openhuman/security/live_policy.rs +++ b/src/openhuman/security/live_policy.rs @@ -70,6 +70,53 @@ pub fn generation() -> u64 { .map_or(0, |s| s.generation.load(Ordering::Relaxed)) } +/// Swap in a new `action_dir` and rebuild the live policy around it, +/// bumping the generation counter. Used by +/// [`config_set_action_dir`](crate::openhuman::config::ops::set_action_dir) +/// (issue #3240) so a Settings-driven change of the agent's writable root +/// takes effect immediately instead of waiting for the next session. +/// +/// Returns the new generation on success, or `Err` if no policy is +/// installed yet (typically a CLI-only invocation that never started a +/// session runtime). +pub fn update_action_dir(new_action_dir: PathBuf) -> Result { + let Some(state) = STATE.get() else { + return Err( + "[security:live_policy] no policy installed yet — cannot update action_dir".into(), + ); + }; + { + let mut guard = state + .action_dir + .write() + .map_err(|e| format!("[security:live_policy] action_dir lock poisoned: {e}"))?; + *guard = new_action_dir.clone(); + } + // Rebuild the policy by cloning the current one and swapping the + // action_dir field. This preserves the entire autonomy + trusted_roots + // + forbidden_paths state — the only thing changing is the sandbox root. + let current_policy = state + .policy + .read() + .map(|g| Arc::clone(&g)) + .map_err(|e| format!("[security:live_policy] policy lock poisoned: {e}"))?; + let mut rebuilt: SecurityPolicy = (*current_policy).clone(); + rebuilt.action_dir = new_action_dir; + { + let mut guard = state + .policy + .write() + .map_err(|e| format!("[security:live_policy] policy write lock poisoned: {e}"))?; + *guard = Arc::new(rebuilt); + } + let gen = state.generation.fetch_add(1, Ordering::Relaxed) + 1; + tracing::info!( + generation = gen, + "[security:live_policy] SecurityPolicy reloaded after action_dir change" + ); + Ok(gen) +} + /// Rebuild the policy from `autonomy_config` against the stored workspace dir /// and swap it in, bumping the generation counter. No-op if nothing has been /// installed yet (e.g. a CLI invocation that never started a session runtime). diff --git a/src/openhuman/security/policy.rs b/src/openhuman/security/policy.rs index b620b07d1..0afe6c231 100644 --- a/src/openhuman/security/policy.rs +++ b/src/openhuman/security/policy.rs @@ -1147,7 +1147,7 @@ impl SecurityPolicy { /// through. Gray-area dirs (`/usr`, `/opt`, `/var`, `~/Library`) stay in the /// user-overridable `forbidden_paths` instead, so a grant can still reach /// e.g. `/usr/local/...`. - fn is_always_forbidden(path: &Path) -> bool { + pub(crate) fn is_always_forbidden(path: &Path) -> bool { // Normalize separators + case BEFORE splitting: a Windows backslash // path is a single component on POSIX (and vice-versa), so we segment // the normalized string rather than rely on `Path::components()`.